Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import org.eclipse.hawkbit.ui.view.DistributionSetView;
import org.eclipse.hawkbit.ui.view.RolloutView;
import org.eclipse.hawkbit.ui.view.SoftwareModuleView;
import org.eclipse.hawkbit.ui.view.TargetFilterQueryView;
import org.eclipse.hawkbit.ui.view.TargetView;

/**
Expand Down Expand Up @@ -113,6 +114,9 @@ private SideNav createNavigation() {
if (accessChecker.hasAccess(TargetView.class)) {
nav.addItem(new SideNavItem("Targets", TargetView.class, VaadinIcon.FILTER.create()));
}
if (accessChecker.hasAccess(TargetFilterQueryView.class)) {
nav.addItem(new SideNavItem("Target Filter Queries", TargetFilterQueryView.class, VaadinIcon.FILTER.create()));
}
if (accessChecker.hasAccess(RolloutView.class)) {
nav.addItem(new SideNavItem("Rollouts", RolloutView.class, VaadinIcon.COGS.create()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ private CreateDialog(final HawkbitMgmtClient hawkbitClient) {
description.setMinLength(2);
description.setWidthFull();

actionType = Utils.actionTypeControls(forceTime);
actionType = Utils.actionTypeControls(MgmtActionType.FORCED, forceTime);

startType = new Select<>();
startType.setValue(StartType.MANUAL);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
package org.eclipse.hawkbit.ui.view;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.confirmdialog.ConfirmDialog;
import com.vaadin.flow.component.dependency.Uses;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.Paragraph;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.select.Select;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.theme.lumo.LumoUtility;
import jakarta.annotation.security.RolesAllowed;
import lombok.Getter;
import org.eclipse.hawkbit.mgmt.json.model.PagedList;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet;
import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtDistributionSetAutoAssignment;
import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQuery;
import org.eclipse.hawkbit.ui.HawkbitMgmtClient;
import org.eclipse.hawkbit.ui.MainLayout;
import org.eclipse.hawkbit.ui.view.util.Filter;
import org.eclipse.hawkbit.ui.view.util.SelectionGrid;
import org.eclipse.hawkbit.ui.view.util.TableView;
import org.eclipse.hawkbit.ui.view.util.Utils;

import java.util.*;
import java.util.concurrent.CompletableFuture;

@PageTitle("Target Filter Queries")
@Route(value = "target_filter_queries", layout = MainLayout.class)
@RolesAllowed({ "TARGET_READ" })
@Uses(Icon.class)
public class TargetFilterQueryView extends TableView<TargetFilterQueryView.TargetFilterQueryGridItem, Long> {
public TargetFilterQueryView(final HawkbitMgmtClient hawkbitClient) {
super(
new TargetFilterQueryFilter(),
new SelectionGrid.EntityRepresentation<>(TargetFilterQueryGridItem.class, TargetFilterQueryGridItem::getId) {

@Override
protected void addColumns(final Grid<TargetFilterQueryGridItem> grid) {
grid.addColumn(MgmtTargetFilterQuery::getId).setHeader(Constants.ID).setAutoWidth(true).setKey("id").setSortable(true);
grid.addColumn(MgmtTargetFilterQuery::getName).setHeader(Constants.NAME).setAutoWidth(true).setKey("name").setSortable(true).setResizable(true);
grid.addColumn(MgmtTargetFilterQuery::getCreatedBy).setHeader(Constants.CREATED_BY).setKey("createdBy").setSortable(true).setAutoWidth(true);
grid.addColumn(Utils.localDateTimeRenderer(MgmtTargetFilterQuery::getCreatedAt)).setHeader(Constants.CREATED_AT).setKey("createdAt").setSortable(true).setAutoWidth(true);
grid.addColumn(MgmtTargetFilterQuery::getLastModifiedBy).setHeader(Constants.LAST_MODIFIED_BY).setKey("lastModifiedBy").setSortable(true).setAutoWidth(true);
grid.addColumn(Utils.localDateTimeRenderer(MgmtTargetFilterQuery::getLastModifiedAt)).setHeader(Constants.LAST_MODIFIED_AT).setKey("lastModifiedAt").setSortable(true).setAutoWidth(true);
grid.addColumn(new ComponentRenderer<>(DistributionSetCell::new)).setHeader(Constants.DISTRIBUTION_SET).setAutoWidth(true).setFlexGrow(0);

grid.addComponentColumn(rollout -> new Actions(rollout, grid, hawkbitClient)).setHeader(
Constants.ACTIONS).setAutoWidth(true);
}
},
(query, filter) -> Optional.ofNullable(
hawkbitClient.getTargetFilterQueryRestApi()
.getFilters(filter, query.getOffset(), query.getPageSize(), Utils.getSortParam(query.getSortOrders(), Constants.NAME_ASC), "compact")
.getBody())
.stream()
.map(PagedList::getContent)
.flatMap(List::stream)
.map(m -> TargetFilterQueryGridItem.from(hawkbitClient, m)),
null,
selectionGrid -> {
selectionGrid.getSelectedItems()
.forEach(toDelete -> hawkbitClient.getTargetFilterQueryRestApi().deleteFilter(toDelete.getId()));
return CompletableFuture.completedFuture(null);
}
);
}

private static class TargetFilterQueryFilter implements Filter.Rsql {

private final TextField name = Utils.textField(Constants.NAME);

private TargetFilterQueryFilter() {
name.setPlaceholder("<name filter>");
}

@Override
public List<Component> components() {
return List.of(name);
}

@Override
public String filter() {
return Filter.filter(
Map.of(
"name", name.getOptionalValue().map(s -> "*" + s + "*")
));
}
}

private static class DistributionSetCell extends HorizontalLayout {

private DistributionSetCell(final TargetFilterQueryGridItem filterQuery) {
filterQuery.getDs().ifPresent(ds -> {
Icon icon = getActionTypeIcon(filterQuery.getAutoAssignActionType());
Span dsName = new Span(ds.getName() + ":" + ds.getVersion());

add(icon, dsName);
});
}

private Icon getActionTypeIcon(MgmtActionType actionType) {
Icon icon = switch (actionType) {
case FORCED -> VaadinIcon.BOLT.create();
case SOFT -> VaadinIcon.USER_CHECK.create();
case DOWNLOAD_ONLY -> VaadinIcon.DOWNLOAD.create();
default -> VaadinIcon.QUESTION_CIRCLE.create();
};
icon.addClassNames(LumoUtility.IconSize.SMALL);
return Utils.tooltip(icon, actionType.getName());
}
}

private static class Actions extends HorizontalLayout {

private final Grid<TargetFilterQueryGridItem> grid;
private final transient HawkbitMgmtClient hawkbitClient;

private Actions(final MgmtTargetFilterQuery filter, final Grid<TargetFilterQueryGridItem> grid,
final HawkbitMgmtClient hawkbitClient) {
this.grid = grid;
this.hawkbitClient = hawkbitClient;
init(filter);
}

private void init(final MgmtTargetFilterQuery filter) {
if (filter.getAutoAssignDistributionSet() == null) {
add(Utils.tooltip(new Button(VaadinIcon.LINK.create()) {
{
addClickListener(v -> {
new AutoAssignDialog(filter.getId(), hawkbitClient, () -> refresh(filter.getId())).open();
});
}
}, "Auto assign"));
} else {
add(Utils.tooltip(new Button(VaadinIcon.UNLINK.create()) {
{
addClickListener(v -> {
ConfirmDialog dialog = Utils.confirmDialog("Unassign Distribution Set",
"Are you sure you want to unassign the distribution set of target filter query '" + filter.getName() + "'?",
"Unassign",
() -> {
hawkbitClient.getTargetFilterQueryRestApi().deleteAssignedDistributionSet(filter.getId());
refresh(filter.getId());
});
dialog.open();
});
}
}, "Unassign"));
}
add(Utils.tooltip(new Button(VaadinIcon.TRASH.create()) {
{
addClickListener(v -> {
ConfirmDialog dialog = Utils.confirmDialog("Delete Target Filter Query",
"Are you sure you want to delete the target filter query '" + filter.getName() + "'?",
"Delete",
() -> {
hawkbitClient.getTargetFilterQueryRestApi().deleteFilter(filter.getId());
grid.getDataProvider().refreshAll();
});
dialog.open();
});
}
}, "Delete"));
}

private void refresh(Long filterId) {
removeAll();
final MgmtTargetFilterQuery body = hawkbitClient.getTargetFilterQueryRestApi().getFilter(filterId).getBody();
if (body != null) {
grid.getDataProvider().refreshItem(TargetFilterQueryGridItem.from(hawkbitClient, body));
init(body);
}
}
}

private static class AutoAssignDialog extends Utils.BaseDialog<Void> {

private final Long filterId;
private final Select<MgmtActionType> actionType;
private final ComboBox<MgmtDistributionSet> distributionSet;
private final Button assign = new Button("Assign");

private AutoAssignDialog(final Long filterId, final HawkbitMgmtClient hawkbitClient, Runnable onSuccess) {
super("Select auto assignment distribution set");

this.filterId = filterId;

Paragraph description = new Paragraph("When an auto assign distribution set is selected, " +
"it will be automatically assigned to all targets that match the target filter.");

actionType = Utils.actionTypeControls(new MgmtActionType[]{MgmtActionType.SOFT, MgmtActionType.FORCED, MgmtActionType.DOWNLOAD_ONLY}, MgmtActionType.FORCED, null);

distributionSet = Utils.nameComboBox("Distribution Set", this::readyToAssign, query -> Optional.ofNullable(
hawkbitClient.getDistributionSetRestApi()
.getDistributionSets(
query.getFilter().orElse(null),
query.getOffset(),
query.getLimit(),
Constants.NAME_ASC)
.getBody()).stream().flatMap(body -> body.getContent().stream()));
distributionSet.setItemLabelGenerator(ds -> ds.getName() + ":" + ds.getVersion());
distributionSet.focus();
distributionSet.setRequiredIndicatorVisible(true);
distributionSet.setWidthFull();

assign.setEnabled(false);
assign.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
addAssignClickListener(hawkbitClient, onSuccess);
final Button cancel = Utils.tooltip(new Button(CANCEL), CANCEL_ESC);
cancel.addClickListener(e -> close());
cancel.addClickShortcut(Key.ESCAPE);
getFooter().add(cancel);
getFooter().add(assign);

final VerticalLayout layout = new VerticalLayout();
layout.setSizeFull();
layout.setSpacing(false);
layout.add(description, actionType, distributionSet);
add(layout);
open();
}

private void readyToAssign(final Object v) {
final boolean createEnabled = !distributionSet.isEmpty();
if (assign.isEnabled() != createEnabled) {
assign.setEnabled(createEnabled);
}
}

private void addAssignClickListener(final HawkbitMgmtClient hawkbitClient, Runnable onSuccess) {
assign.addClickListener(e -> {
MgmtDistributionSetAutoAssignment newAssignment = new MgmtDistributionSetAutoAssignment();
newAssignment.setId(distributionSet.getValue().getId());
newAssignment.setType(actionType.getValue());
hawkbitClient.getTargetFilterQueryRestApi().postAssignedDistributionSet(filterId, newAssignment);
onSuccess.run();
close();
});
}
}

// todo change /targetfilters api to reduce api calls ?
@Getter
public static class TargetFilterQueryGridItem extends MgmtTargetFilterQuery {

TargetFilterQueryGridItem() {
super();
}

private Optional<MgmtDistributionSet> ds;
static ObjectMapper objectMapper = new ObjectMapper();

public static TargetFilterQueryGridItem from(final HawkbitMgmtClient hawkbitClient, MgmtTargetFilterQuery filter) {
TargetFilterQueryGridItem filterGridItem = objectMapper.convertValue(filter, TargetFilterQueryGridItem.class);

if (filterGridItem.getAutoAssignDistributionSet() != null) {
filterGridItem.ds = Optional.ofNullable(
hawkbitClient.getTargetFilterQueryRestApi().getAssignedDistributionSet(filterGridItem.getId()).getBody()
);
} else {
filterGridItem.ds = Optional.empty();
}
return filterGridItem;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof TargetFilterQueryGridItem other)) return false;
return Objects.equals(getId(), other.getId());
}

@Override
public int hashCode() {
return Objects.hashCode(getId());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -618,11 +618,10 @@ private TargetMetadata(HawkbitMgmtClient hawkbitClient) {
metadataArea.setEmptyStateText("No metadata found");
metadataArea.addColumn(MgmtMetadata::getKey).setHeader(KEY).setAutoWidth(true);
metadataArea.addColumn(MgmtMetadata::getValue).setHeader(VALUE).setAutoWidth(true);
metadataArea.addComponentColumn(metadata -> {
final Button deleteBtn = Utils.tooltip(new Button(VaadinIcon.TRASH.create()), "Delete Metadata");
deleteBtn.addClickListener(e -> confirmDeleteDialog(metadata.getKey()));
return deleteBtn;
}).setHeader("Actions").setAutoWidth(true).setFlexGrow(0);
metadataArea.addComponentColumn(metadata -> Utils.deleteButton("Delete metadata", () -> {
hawkbitClient.getTargetRestApi().deleteMetadata(target.getControllerId(), metadata.getKey());
refreshMetadatas();
})).setHeader("Actions").setAutoWidth(true).setFlexGrow(0);
metadataArea.setWidthFull();
add(metadataArea);

Expand Down Expand Up @@ -651,24 +650,6 @@ private void refreshMetadatas() {
.map(PagedList::getContent)
.orElse(Collections.emptyList()));
}

private void confirmDeleteDialog(String key) {
final ConfirmDialog dialog = new ConfirmDialog();
dialog.setHeader("Confirm Deletion");
dialog.setText("Are you sure you want to delete metadata " + key + "?");

dialog.setCancelable(true);
dialog.addCancelListener(event -> dialog.close());

dialog.setConfirmButtonTheme(ButtonVariant.LUMO_ERROR.getVariantName());
dialog.setConfirmText("Delete");
dialog.addConfirmListener(event -> {
hawkbitClient.getTargetRestApi().deleteMetadata(target.getControllerId(), key);
refreshMetadatas();
dialog.close();
});
dialog.open();
}
}

public static class TargetActionsHistoryLayout extends VerticalLayout {
Expand Down Expand Up @@ -867,7 +848,7 @@ private AssignDialog(final HawkbitMgmtClient hawkbitClient, Set<TargetWithDs> se
distributionSet.setItemLabelGenerator(distributionSetO -> distributionSetO.getName() + ":" + distributionSetO.getVersion());
distributionSet.setWidthFull();

actionType = Utils.actionTypeControls(forceTime);
actionType = Utils.actionTypeControls(MgmtActionType.FORCED, forceTime);

assign.setEnabled(false);
assign.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
Expand Down
Loading