SimpleUI: Add Poll Status and Update Status + improve target filtering (#2404)

* simple-ui: improve target filters

- improve the overall ui of the filters by using the proper icons and layout
- add target filter update and the ability to duplicate filters

Signed-off-by: Mohamed Zenadi <mohamed.zenadi@wattsense.com>

* simple-ui: add poll status and target update status icons

Signed-off-by: Mohamed Zenadi <mohamed.zenadi@wattsense.com>

* apply official style + improve update / create listeners

* one last clean up - revert import bad changes

---------

Signed-off-by: Mohamed Zenadi <mohamed.zenadi@wattsense.com>
This commit is contained in:
Mohamed Zenadi
2025-05-19 10:58:58 +02:00
committed by GitHub
parent 91811f7aa8
commit 045ba66738
2 changed files with 170 additions and 53 deletions

View File

@@ -9,7 +9,6 @@
*/ */
package org.eclipse.hawkbit.ui.simple.view; package org.eclipse.hawkbit.ui.simple.view;
import java.time.ZoneOffset;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.LinkedList; import java.util.LinkedList;
@@ -18,18 +17,21 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.time.ZoneOffset;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage; import java.util.concurrent.CompletionStage;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import jakarta.annotation.security.RolesAllowed; import jakarta.annotation.security.RolesAllowed;
import com.vaadin.flow.component.Component; import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Key; import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.Unit;
import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant; import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.checkbox.CheckboxGroup; import com.vaadin.flow.component.checkbox.CheckboxGroup;
import com.vaadin.flow.component.datetimepicker.DateTimePicker; import com.vaadin.flow.component.datetimepicker.DateTimePicker;
import com.vaadin.flow.component.dependency.Uses; import com.vaadin.flow.component.dependency.Uses;
@@ -46,6 +48,8 @@ import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.renderer.ComponentRenderer; import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route; import com.vaadin.flow.router.Route;
import com.vaadin.flow.theme.lumo.LumoUtility;
import org.eclipse.hawkbit.mgmt.json.model.MgmtPollStatus;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType; 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.distributionset.MgmtDistributionSet;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentRequestBody; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentRequestBody;
@@ -62,6 +66,7 @@ import org.eclipse.hawkbit.ui.simple.view.util.SelectionGrid;
import org.eclipse.hawkbit.ui.simple.view.util.TableView; import org.eclipse.hawkbit.ui.simple.view.util.TableView;
import org.eclipse.hawkbit.ui.simple.view.util.Utils; import org.eclipse.hawkbit.ui.simple.view.util.Utils;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import java.util.stream.Collectors;
@PageTitle("Targets") @PageTitle("Targets")
@Route(value = "targets", layout = MainLayout.class) @Route(value = "targets", layout = MainLayout.class)
@@ -69,6 +74,7 @@ import org.springframework.util.ObjectUtils;
@Uses(Icon.class) @Uses(Icon.class)
public class TargetView extends TableView<MgmtTarget, String> { public class TargetView extends TableView<MgmtTarget, String> {
public static final String STATUS = "Status";
public static final String CONTROLLER_ID = "Controller Id"; public static final String CONTROLLER_ID = "Controller Id";
public static final String TAG = "Tag"; public static final String TAG = "Tag";
@@ -79,6 +85,8 @@ public class TargetView extends TableView<MgmtTarget, String> {
@Override @Override
protected void addColumns(final Grid<MgmtTarget> grid) { protected void addColumns(final Grid<MgmtTarget> grid) {
grid.addColumn(new ComponentRenderer<Component, MgmtTarget>(TargetStatusCell::new)).setHeader(STATUS).setAutoWidth(true)
.setFlexGrow(0);
grid.addColumn(MgmtTarget::getControllerId).setHeader(CONTROLLER_ID).setAutoWidth(true); grid.addColumn(MgmtTarget::getControllerId).setHeader(CONTROLLER_ID).setAutoWidth(true);
grid.addColumn(MgmtTarget::getName).setHeader(Constants.NAME).setAutoWidth(true); grid.addColumn(MgmtTarget::getName).setHeader(Constants.NAME).setAutoWidth(true);
grid.addColumn(MgmtTarget::getTargetTypeName).setHeader(Constants.TYPE).setAutoWidth(true); grid.addColumn(MgmtTarget::getTargetTypeName).setHeader(Constants.TYPE).setAutoWidth(true);
@@ -157,64 +165,126 @@ public class TargetView extends TableView<MgmtTarget, String> {
} }
} }
@SuppressWarnings({"java:S1171", "java:S3599"}) @SuppressWarnings({ "java:S1171", "java:S3599" })
private static class RawFilter implements Filter.Rsql { private static class RawFilter implements Filter.Rsql {
private final TextField textFilter = new TextField("Raw Filter"); private final TextField textFilter = new TextField("Raw Filter", "<raw filter>");
private final VerticalLayout layout = new VerticalLayout(); private final VerticalLayout layout = new VerticalLayout();
private final Select<MgmtTargetFilterQuery> savedFilters = new Select<>();
private RawFilter(final HawkbitMgmtClient hawkbitClient) { private RawFilter(final HawkbitMgmtClient hawkbitClient) {
textFilter.setPlaceholder("<raw filter>"); final Button createBtn = Utils.tooltip(new Button("Save", VaadinIcon.PLUS.create()), "Save");
final Select<MgmtTargetFilterQuery> savedFilters = new Select<>( final Button updateBtn = Utils.tooltip(new Button(VaadinIcon.HARDDRIVE.create()), "Update");
"Saved Filters", updateBtn.setEnabled(false);
e -> {
if (e.getValue() != null) { savedFilters.setLabel("Saved Filters");
textFilter.setValue(e.getValue().getQuery()); savedFilters.addValueChangeListener(e -> {
} if (e.getValue() != null) {
}); textFilter.setValue(e.getValue().getQuery());
updateBtn.setEnabled(true);
createBtn.setText("Save as");
} else {
textFilter.clear();
updateBtn.setEnabled(false);
createBtn.setText("Save");
}
});
savedFilters.setEmptySelectionAllowed(true); savedFilters.setEmptySelectionAllowed(true);
savedFilters.setItems( savedFilters.setItems(listFilters(hawkbitClient));
Optional.ofNullable(
hawkbitClient.getTargetFilterQueryRestApi()
.getFilters(0, 30, null, null, null)
.getBody().getContent())
.orElse(Collections.emptyList()));
savedFilters.setItemLabelGenerator( savedFilters.setItemLabelGenerator(
query -> Optional.ofNullable(query).map(MgmtTargetFilterQuery::getName).orElse("<select saved filter>")); query -> Optional.ofNullable(query).map(MgmtTargetFilterQuery::getName).orElse("<select saved filter>"));
savedFilters.setWidthFull(); savedFilters.setWidthFull();
textFilter.setWidthFull(); textFilter.setWidthFull();
final Button saveBtn = Utils.tooltip(new Button(VaadinIcon.ARCHIVE.create()), "Save (Enter)"); createBtn.addClickListener(createBtnListener(hawkbitClient));
saveBtn.addClickListener(e -> updateBtn.addClickListener(updateBtnListener(hawkbitClient));
new Utils.BaseDialog<Void>("Save Filter") {{
final Button finishBtn = Utils.tooltip(new Button("Save"), "Save (Enter)");
final TextField name = Utils.textField(
Constants.NAME,
e -> finishBtn.setEnabled(!e.getHasValue().isEmpty()));
name.focus();
finishBtn.addClickShortcut(Key.ENTER);
finishBtn.setEnabled(false);
finishBtn.addClickListener(e -> {
final MgmtTargetFilterQueryRequestBody createRequest = new MgmtTargetFilterQueryRequestBody();
createRequest.setName(name.getValue());
createRequest.setQuery(textFilter.getValue());
hawkbitClient.getTargetFilterQueryRestApi().createFilter(createRequest);
savedFilters.setItems(
hawkbitClient.getTargetFilterQueryRestApi()
.getFilters(0, 30, null, null, null).getBody().getContent());
close();
});
getFooter().add(finishBtn);
add(name);
open();
}});
saveBtn.addClickShortcut(Key.ENTER);
layout.setSpacing(false); layout.setSpacing(false);
final HorizontalLayout textSaveLayout = new HorizontalLayout(textFilter, saveBtn); layout.setPadding(false);
final HorizontalLayout textSaveLayout = new HorizontalLayout(textFilter, createBtn, updateBtn);
textSaveLayout.setAlignItems(FlexComponent.Alignment.BASELINE); textSaveLayout.setAlignItems(FlexComponent.Alignment.BASELINE);
textSaveLayout.setWidthFull(); textSaveLayout.setWidthFull();
layout.add(savedFilters, textSaveLayout); layout.add(savedFilters, textSaveLayout);
layout.addClassNames(LumoUtility.Gap.SMALL);
}
private static List<MgmtTargetFilterQuery> listFilters(HawkbitMgmtClient hawkbitClient) {
return Optional.ofNullable(
hawkbitClient.getTargetFilterQueryRestApi()
.getFilters(0, 30, null, null, null)
.getBody().getContent()
).orElse(Collections.emptyList());
}
private ComponentEventListener<ClickEvent<Button>> createBtnListener(HawkbitMgmtClient hawkbitClient) {
return e ->
new Utils.BaseDialog<Void>("Create New Filter") {
{
final Button finishBtn = Utils.tooltip(new Button("Save"), "Save (Enter)");
final TextField name = Utils.textField(
Constants.NAME,
e -> finishBtn.setEnabled(!e.getHasValue().isEmpty())
);
name.focus();
finishBtn.addClickShortcut(Key.ENTER);
finishBtn.setEnabled(false);
finishBtn.addClickListener(e -> {
final MgmtTargetFilterQueryRequestBody createRequest = new MgmtTargetFilterQueryRequestBody();
createRequest.setName(name.getValue());
createRequest.setQuery(textFilter.getValue());
hawkbitClient.getTargetFilterQueryRestApi().createFilter(createRequest);
savedFilters.setItems(
listFilters(hawkbitClient));
close();
});
getFooter().add(finishBtn);
add(name);
open();
}
};
}
private ComponentEventListener<ClickEvent<Button>> updateBtnListener(HawkbitMgmtClient hawkbitClient) {
return e -> {
final MgmtTargetFilterQuery selected = savedFilters.getValue();
if (selected == null) return;
new Utils.BaseDialog<Void>("Update Filter") {
{
final Button finishBtn = Utils.tooltip(new Button("Update"), "Update (Enter)");
finishBtn.setEnabled(false);
final TextField name = Utils.textField(
Constants.NAME,
e -> finishBtn.setEnabled(!e.getHasValue().isEmpty())
);
name.focus();
name.setValue(selected.getName());
final TextArea filterValue = new TextArea("Filter Value");
filterValue.setReadOnly(true);
filterValue.setValue(textFilter.getValue());
filterValue.setWidthFull();
finishBtn.addClickShortcut(Key.ENTER);
finishBtn.addClickListener(e -> {
final MgmtTargetFilterQueryRequestBody updateRequest = new MgmtTargetFilterQueryRequestBody();
updateRequest.setName(name.getValue());
updateRequest.setQuery(textFilter.getValue());
hawkbitClient.getTargetFilterQueryRestApi().updateFilter(selected.getId(), updateRequest);
savedFilters.setItems(listFilters(hawkbitClient));
close();
});
getFooter().add(finishBtn);
add(name);
add(filterValue);
open();
}
};
};
} }
@Override @Override
@@ -286,7 +356,8 @@ public class TargetView extends TableView<MgmtTarget, String> {
final Button register = Utils.tooltip(new Button("Register"), "Register (Enter)"); final Button register = Utils.tooltip(new Button("Register"), "Register (Enter)");
type = new Select<>( type = new Select<>(
"Type", "Type",
e -> {}, e -> {
},
hawkbitClient.getTargetTypeRestApi() hawkbitClient.getTargetTypeRestApi()
.getTargetTypes(0, 30, Constants.NAME_ASC, null) .getTargetTypes(0, 30, Constants.NAME_ASC, null)
.getBody() .getBody()
@@ -310,7 +381,7 @@ public class TargetView extends TableView<MgmtTarget, String> {
register.addThemeVariants(ButtonVariant.LUMO_PRIMARY); register.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
final Button cancel = Utils.tooltip(new Button("Cancel"), "Cancel (Esc)"); final Button cancel = Utils.tooltip(new Button("Cancel"), "Cancel (Esc)");
cancel.addClickListener(e -> close()); cancel.addClickListener(e -> close());
register.addClickShortcut(Key.ESCAPE); cancel.addClickShortcut(Key.ESCAPE);
getFooter().add(cancel); getFooter().add(cancel);
getFooter().add(register); getFooter().add(register);
@@ -413,9 +484,54 @@ public class TargetView extends TableView<MgmtTarget, String> {
requests.add(request); requests.add(request);
} }
hawkbitClient.getDistributionSetRestApi().createAssignedTarget(distributionSet.getValue().getId(), requests, null).getBody(); hawkbitClient.getDistributionSetRestApi().createAssignedTarget(distributionSet.getValue().getId(), requests, null).getBody();
}); });
} }
} }
private static class TargetStatusCell extends HorizontalLayout {
private TargetStatusCell(MgmtTarget target) {
MgmtPollStatus pollStatus = target.getPollStatus();
String targetUpdateStatus = Optional.ofNullable(target.getUpdateStatus()).orElse("unknown");
add(pollStatusIconMapper(pollStatus), targetUpdateStatusMapper(targetUpdateStatus));
setWidth(50, Unit.PIXELS);
}
private Icon targetUpdateStatusMapper(String targetUpdateStatus) {
VaadinIcon icon = switch (targetUpdateStatus) {
case "error" -> VaadinIcon.EXCLAMATION_CIRCLE;
case "in_sync" -> VaadinIcon.CHECK_CIRCLE;
case "pending" -> VaadinIcon.ADJUST;
case "registered" -> VaadinIcon.DOT_CIRCLE;
default -> VaadinIcon.QUESTION_CIRCLE;
};
String color = switch (targetUpdateStatus) {
case "error" -> "red";
case "in_sync" -> "green";
case "pending" -> "orange";
case "registered" -> "lightblue";
default -> "blue";
};
Icon statusIcon = Utils.tooltip(icon.create(), targetUpdateStatus);
statusIcon.setColor(color);
statusIcon.addClassNames(LumoUtility.IconSize.SMALL);
return statusIcon;
}
private Icon pollStatusIconMapper(MgmtPollStatus pollStatus) {
Icon pollIcon;
if (pollStatus == null) {
pollIcon = Utils.tooltip(VaadinIcon.QUESTION_CIRCLE.create(), "No Poll Status");
} else if (pollStatus.isOverdue()) {
pollIcon = Utils.tooltip(VaadinIcon.EXCLAMATION_CIRCLE.create(), "Overdue");
} else {
pollIcon = Utils.tooltip(VaadinIcon.CLOCK.create(), "In Time");
}
pollIcon.addClassNames(LumoUtility.IconSize.SMALL);
return pollIcon;
}
}
} }

View File

@@ -20,6 +20,7 @@ import java.util.stream.Stream;
import com.vaadin.flow.component.Component; import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.HasValue; import com.vaadin.flow.component.HasValue;
import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant; import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.html.Div; import com.vaadin.flow.component.html.Div;
@@ -46,25 +47,25 @@ public class Filter extends Div {
filtersDiv.add(primaryRsql.components()); filtersDiv.add(primaryRsql.components());
filtersDiv.addClassName(LumoUtility.Gap.SMALL); filtersDiv.addClassName(LumoUtility.Gap.SMALL);
final Button searchBtn = Utils.tooltip(new Button(VaadinIcon.REFRESH.create()), "Search"); final Button searchBtn = Utils.tooltip(new Button(VaadinIcon.SEARCH.create()), "Search (Enter)");
searchBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY); searchBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
searchBtn.addClickListener(e -> changeListener.accept(rsql.filter())); searchBtn.addClickListener(e -> changeListener.accept(rsql.filter()));
final Button resetBtn = Utils.tooltip(new Button(VaadinIcon.ERASER.create()), "Reset"); searchBtn.addClickShortcut(Key.ENTER);
final Button resetBtn = Utils.tooltip(new Button(VaadinIcon.REFRESH.create()), "Reset");
resetBtn.addThemeVariants(ButtonVariant.LUMO_TERTIARY); resetBtn.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
resetBtn.addClickListener(e -> { resetBtn.addClickListener(e -> {
clear(layout.getChildren()); clear(layout.getChildren());
changeListener.accept(primaryRsql.filter()); changeListener.accept(primaryRsql.filter());
}); });
final Div actionDiv = new Div(); final HorizontalLayout actions = new HorizontalLayout(searchBtn, resetBtn);
actionDiv.add(searchBtn, resetBtn); actions.addClassNames(LumoUtility.Gap.SMALL);
actionDiv.addClassNames(LumoUtility.Gap.SMALL);
layout.setPadding(true); layout.setPadding(true);
layout.setAlignItems(FlexComponent.Alignment.BASELINE); layout.setAlignItems(FlexComponent.Alignment.BASELINE);
layout.add(filtersDiv); layout.add(filtersDiv);
layout.add(actions);
layout.add(actionDiv);
if (secondaryOptionalRsql != null) { if (secondaryOptionalRsql != null) {
final Button toggleBtn = Utils.tooltip(new Button(VaadinIcon.FLIP_V.create()), "Toggle Search"); final Button toggleBtn = Utils.tooltip(new Button(VaadinIcon.FLIP_V.create()), "Toggle Search");
toggleBtn.addThemeVariants(ButtonVariant.LUMO_TERTIARY); toggleBtn.addThemeVariants(ButtonVariant.LUMO_TERTIARY);