Improve Simple UI (#2554)

* feat[Simple-UI]: add action status list

* fix[Simple-UI]: various ui issues

* chore: add devtool

* feat[Simple-UI: add DS metadata

* feat[Simple-UI]: add sort in DS view

* feat[Simple-UI]: add created at to DS view

* style[Simple-UI]: remove id from DS view

* feat[Simple-UI]: add rsql filter & url filter

* feat[Simple-UI]: if one ds in result show details

* feat[Simple-UI]: add filter from url to targetview

* feat[Simple-UI]: add link from target details view to DS

* feat[Simple-UI]: add sort & version on target view

* refacto[Simple-UI]: linkted text area

* feat[Simple-UI]: dynamic homepage depending on permissions

* feat[Simple-UI]: sort by newest version

* feat[Simple-UI]: add target address

* feat[Simple-UI]: sort by last modified target

* fix[Simple-UI]: securityToken null if no permission

* fix[Simple-UI]: green circle on bad update

* feat[Simple-UI]: use local date format

* docs: update user config

* fix: tag filter

* feat[Simple-UI]: search on multiple attributes

* refacto: rename TargetActions -> TargetActionsHistory

* refacto: move TargetActionsHistory to a new file
This commit is contained in:
Florian BEZANNIER
2025-07-28 15:07:25 +02:00
committed by GitHub
parent 2b66449ff1
commit d2b8e74056
24 changed files with 1143 additions and 538 deletions

View File

@@ -1 +1,7 @@
/* Import your application global css files here or add the styles directly to this file */
a.nocolor:link {
color: inherit;
}
a.nocolor:visited {
color: inherit;
}

View File

@@ -9,8 +9,11 @@
*/
package org.eclipse.hawkbit.ui.simple;
import java.util.List;
import java.util.Optional;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.Unit;
import com.vaadin.flow.component.applayout.AppLayout;
import com.vaadin.flow.component.applayout.DrawerToggle;
@@ -47,9 +50,12 @@ import org.eclipse.hawkbit.ui.simple.view.TargetView;
*/
public class MainLayout extends AppLayout {
static final List<Class<? extends Component>> DEFAULT_VIEW_PRIORITY = List.of(TargetView.class, DistributionSetView.class,
SoftwareModuleView.class, RolloutView.class);
private final transient AuthenticatedUser authenticatedUser;
private final AccessAnnotationChecker accessChecker;
private H2 viewTitle;
private transient Optional<Class<? extends Component>> defaultView;
public MainLayout(final AuthenticatedUser authenticatedUser, final AccessAnnotationChecker accessChecker) {
this.authenticatedUser = authenticatedUser;
@@ -68,6 +74,9 @@ public class MainLayout extends AppLayout {
Optional.ofNullable(getContent().getClass().getAnnotation(PageTitle.class))
.map(PageTitle::value)
.orElse(""));
if (UI.getCurrent().getActiveViewLocation().getPath().isEmpty()) {
defaultView.ifPresent(c -> UI.getCurrent().navigate(c));
}
}
private void addHeaderContent() {
@@ -81,7 +90,7 @@ public class MainLayout extends AppLayout {
}
private void addDrawerContent() {
final H1 appName = new H1("hawkBit UI (Experimental!)");
final H1 appName = new H1("hawkBit UI");
final HorizontalLayout layout = new HorizontalLayout();
layout.setPadding(true);
layout.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
@@ -117,6 +126,7 @@ public class MainLayout extends AppLayout {
if (accessChecker.hasAccess(AboutView.class)) {
nav.addItem(new SideNavItem("About", AboutView.class, VaadinIcon.INFO_CIRCLE.create()));
}
defaultView = DEFAULT_VIEW_PRIORITY.stream().filter(accessChecker::hasAccess).findFirst();
return nav;
}

View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) 2025 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.ui.simple;
import java.util.Arrays;
import java.util.Locale;
import com.vaadin.flow.i18n.DefaultI18NProvider;
import org.springframework.stereotype.Component;
@Component
public class SimpleI18NProvider extends DefaultI18NProvider {
SimpleI18NProvider() {
super(Arrays.stream(Locale.getAvailableLocales()).toList());
}
}

View File

@@ -0,0 +1,29 @@
/**
* Copyright (c) 2025 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.ui.simple;
import com.vaadin.flow.server.ServiceInitEvent;
import com.vaadin.flow.server.VaadinServiceInitListener;
import com.vaadin.flow.spring.annotation.SpringComponent;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@SpringComponent
public class VaadinServiceInit implements VaadinServiceInitListener {
@Override
public void serviceInit(ServiceInitEvent event) {
// cache zoneId of client as soon as possible
event.getSource().addUIInitListener(uiEvent -> {
uiEvent.getUI().getPage().retrieveExtendedClientDetails(details -> {});
});
}
}

View File

@@ -0,0 +1,250 @@
/**
* Copyright (c) 2025 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.ui.simple.component;
import static org.eclipse.hawkbit.ui.simple.view.Constants.STATUS;
import java.util.List;
import java.util.Optional;
import com.vaadin.flow.component.AttachEvent;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Unit;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.confirmdialog.ConfirmDialog;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.theme.lumo.LumoUtility;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtAction;
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtActionRequestBodyPut;
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.target.MgmtTarget;
import org.eclipse.hawkbit.ui.simple.HawkbitMgmtClient;
import org.eclipse.hawkbit.ui.simple.view.TargetView;
import org.eclipse.hawkbit.ui.simple.view.util.Utils;
@Slf4j
public class TargetActionsHistory extends Grid<TargetActionsHistory.ActionStatusEntry> {
private final transient HawkbitMgmtClient hawkbitClient;
private transient MgmtTarget target;
private final TargetView.TargetActionsHistoryLayout.ActionStepsGrid actionStepsGrid;
public TargetActionsHistory(final HawkbitMgmtClient hawkbitClient, TargetView.TargetActionsHistoryLayout.ActionStepsGrid actionStepsGrid) {
this.hawkbitClient = hawkbitClient;
setWidthFull();
addColumn(new ComponentRenderer<>(ActionStatusEntry::getStatusIcon)).setHeader(STATUS).setAutoWidth(true).setFlexGrow(0);
addColumn(ActionStatusEntry::getDistributionSetName).setHeader("Distribution Set").setAutoWidth(true);
addColumn(Utils.localDateTimeRenderer(ActionStatusEntry::getLastModifiedAt))
.setHeader("Last Modified")
.setAutoWidth(true)
.setFlexGrow(0)
.setComparator(ActionStatusEntry::getLastModifiedAt);
addColumn(new ComponentRenderer<>(ActionStatusEntry::getForceTypeIcon)).setHeader("Type").setAutoWidth(true).setFlexGrow(0);
addColumn(new ComponentRenderer<>(ActionStatusEntry::getActionsLayout)).setHeader("Actions").setAutoWidth(true).setFlexGrow(0);
addColumn(new ComponentRenderer<>(ActionStatusEntry::getForceQuitLayout)).setHeader("Force Quit").setAutoWidth(true)
.setFlexGrow(0);
addItemClickListener(e -> actionStepsGrid.setActionId(e.getItem().action.getId()));
this.actionStepsGrid = actionStepsGrid;
}
public void setItem(final MgmtTarget target) {
this.target = target;
this.actionStepsGrid.setTarget(target);
}
private List<ActionStatusEntry> fetchActions() {
return hawkbitClient.getTargetRestApi().getActionHistory(target.getControllerId(), null, 0, 30, null)
.getBody()
.getContent()
.stream()
.map(action -> new ActionStatusEntry(action, () -> setItems(fetchActions())))
.filter(value -> value.action != null)
.toList();
}
@Override
protected void onAttach(AttachEvent attachEvent) {
List<ActionStatusEntry> actionStatusEntries = fetchActions();
setItems(actionStatusEntries);
actionStatusEntries.stream().findFirst().ifPresentOrElse(e -> {
// select first action in the list by default
asSingleSelect().setValue(e);
actionStepsGrid.setActionId(e.action.getId());
}, () -> actionStepsGrid.setActionId(null));
}
protected class ActionStatusEntry {
final MgmtAction action;
final Runnable onUpdate;
MgmtDistributionSet distributionSet;
public ActionStatusEntry(final MgmtAction mgmtAction, final Runnable onUpdate) {
this.action = hawkbitClient.getActionRestApi().getAction(mgmtAction.getId()).getBody();
this.onUpdate = onUpdate;
if (action == null) {
log.error("Unable to fetch the action with id : {}", mgmtAction.getId());
return;
}
this.action.getLink("distributionset").ifPresent(link -> {
try {
Long dsId = Long.parseLong(link.getHref().substring(link.getHref().lastIndexOf("/") + 1));
this.distributionSet = hawkbitClient.getDistributionSetRestApi().getDistributionSet(dsId).getBody();
} catch (NumberFormatException e) {
log.error("Error parsing distribution set ID", e);
}
});
}
private boolean isActive() {
return action.getStatus().equals(MgmtAction.ACTION_PENDING);
}
private boolean isCancelingOrCanceled() {
return action.getType().equals(MgmtAction.ACTION_CANCEL);
}
public Component getStatusIcon() {
final HorizontalLayout layout = new HorizontalLayout();
final Icon icon;
if (isActive()) {
if (isCancelingOrCanceled()) {
icon = Utils.tooltip(VaadinIcon.ADJUST.create(), "Pending Cancellation");
icon.setColor("red");
} else {
icon = Utils.tooltip(VaadinIcon.ADJUST.create(), "Pending Update");
icon.setColor("orange");
}// todo getDetailStatus should return an enum from src/main/java/org/eclipse/hawkbit/repository/model/Action.java
} else if (action.getType().equals(MgmtAction.ACTION_UPDATE) && action.getDetailStatus().equals("finished")) {
icon = Utils.tooltip(VaadinIcon.CHECK_CIRCLE.create(), "Updated");
icon.setColor("green");
} else {
icon = Utils.tooltip(VaadinIcon.CLOSE_CIRCLE.create(), "Canceled");
icon.setColor("red");
}
icon.addClassNames(LumoUtility.IconSize.SMALL);
layout.add(icon);
layout.setWidth(50, Unit.PIXELS);
layout.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
return layout;
}
public String getDistributionSetName() {
return Optional.ofNullable(distributionSet).map(d -> d.getName() + ":" + d.getVersion()).orElse(
"Distribution Set not found");
}
public Long getLastModifiedAt() {
return action.getLastModifiedAt();
}
public Icon getForceTypeIcon() {
Icon icon = switch (action.getForceType()) {
case FORCED -> VaadinIcon.BOLT.create();
case TIMEFORCED -> VaadinIcon.USER_CLOCK.create();
case SOFT -> VaadinIcon.USER_CHECK.create();
case DOWNLOAD_ONLY -> VaadinIcon.DOWNLOAD.create();
};
return Utils.tooltip(icon, action.getForceType().getName());
}
public HorizontalLayout getActionsLayout() {
final HorizontalLayout actionsLayout = new HorizontalLayout();
actionsLayout.setSpacing(true);
final Button cancelButton = Utils.tooltip(new Button(VaadinIcon.CLOSE.create()), "Cancel Action");
if (isActive() && !isCancelingOrCanceled()) {
cancelButton.addClickListener(e -> {
String message = "Are you sure you want to cancel the action ?";
promptForConfirmAction(
message, onUpdate,
() -> hawkbitClient.getTargetRestApi().cancelAction(target.getControllerId(), action.getId(), false))
.open();
});
} else {
cancelButton.setEnabled(false);
}
final Button forceButton = Utils.tooltip(new Button(VaadinIcon.BOLT.create()), "Force Action");
if (isActive() && !isCancelingOrCanceled() && action.getForceType() != MgmtActionType.FORCED) {
forceButton.addClickListener(e -> {
String message = "Are you sure you want to force the action ?";
promptForConfirmAction(
message, onUpdate, () -> {
MgmtActionRequestBodyPut setForced = new MgmtActionRequestBodyPut();
setForced.setForceType(MgmtActionType.FORCED);
hawkbitClient.getTargetRestApi()
.updateAction(target.getControllerId(), action.getId(), setForced);
}
).open();
});
} else {
forceButton.setEnabled(false);
}
actionsLayout.add(cancelButton, forceButton);
return actionsLayout;
}
public HorizontalLayout getForceQuitLayout() {
final HorizontalLayout forceQuitLayout = new HorizontalLayout();
forceQuitLayout.setSpacing(true);
forceQuitLayout.setPadding(true);
forceQuitLayout.setWidthFull();
forceQuitLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
final Button forceQuitButton = Utils.tooltip(new Button(VaadinIcon.CLOSE.create()), "Force Cancel");
forceQuitButton.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY_INLINE);
if (isActive() && isCancelingOrCanceled()) {
forceQuitButton.addClickListener(e -> {
String message = "Are you sure you want to force cancel the action ?";
promptForConfirmAction(
message, onUpdate,
() -> hawkbitClient.getTargetRestApi().cancelAction(target.getControllerId(), action.getId(), true)).open();
});
} else {
forceQuitButton.setEnabled(false);
}
forceQuitLayout.add(forceQuitButton);
return forceQuitLayout;
}
private static ConfirmDialog promptForConfirmAction(String message, Runnable refreshActions, Runnable actionConsumer) {
final ConfirmDialog dialog = new ConfirmDialog();
dialog.setHeader("Confirm Action");
dialog.setText(message);
dialog.setCancelable(true);
dialog.addCancelListener(event -> dialog.close());
dialog.setConfirmButtonTheme(ButtonVariant.LUMO_ERROR.getVariantName());
dialog.setConfirmText("Confirm");
dialog.addConfirmListener(event -> {
actionConsumer.run();
refreshActions.run();
dialog.close();
});
return dialog;
}
}
}

View File

@@ -10,12 +10,13 @@
package org.eclipse.hawkbit.ui.simple.view;
// java:S1214 - implementations of Constants interface extends other classes, so if make this class we shall go for static imports
// which is not not better
// which is not not better
@SuppressWarnings("java:S1214")
public interface Constants {
// properties
String ID = "Id";
String ADDRESS = "Address";
String NAME = "Name";
String DESCRIPTION = "Description";
String VERSION = "Version";
@@ -54,7 +55,10 @@ public interface Constants {
String CANCEL = "Cancel";
String CANCEL_ESC = "Cancel (Esc)";
String CREATED_AT_DESC = "createdAt:desc";
String NAME_ASC = "name:asc";
String NAME_DESC = "name:desc";
String NOT_AVAILABLE_NULL = "n/a (null)";
}

View File

@@ -11,15 +11,17 @@ package org.eclipse.hawkbit.ui.simple.view;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.vaadin.flow.component.grid.GridSortOrder;
import com.vaadin.flow.data.provider.SortDirection;
import jakarta.annotation.security.RolesAllowed;
import com.vaadin.flow.component.Component;
@@ -64,25 +66,32 @@ public class DistributionSetView extends TableView<MgmtDistributionSet, Long> {
public DistributionSetView(final HawkbitMgmtClient hawkbitClient) {
super(
new DistributionSetFilter(hawkbitClient),
new DistributionSetRawFilter(),
new SelectionGrid.EntityRepresentation<>(MgmtDistributionSet.class, MgmtDistributionSet::getId) {
private final DistributionSetDetails details = new DistributionSetDetails(hawkbitClient);
@Override
protected void addColumns(Grid<MgmtDistributionSet> grid) {
grid.addColumn(MgmtDistributionSet::getId).setHeader(Constants.ID).setAutoWidth(true);
grid.addColumn(MgmtDistributionSet::getName).setHeader(Constants.NAME).setAutoWidth(true);
grid.addColumn(MgmtDistributionSet::getVersion).setHeader(Constants.VERSION).setAutoWidth(true);
grid.addColumn(MgmtDistributionSet::getTypeName).setHeader(Constants.TYPE).setAutoWidth(true);
var createdAtCol = grid.addColumn(Utils.localDateTimeRenderer(MgmtDistributionSet::getCreatedAt)).setHeader(
Constants.CREATED_AT).setAutoWidth(true).setKey("createdAt").setSortable(true);
grid.addColumn(MgmtDistributionSet::getName).setHeader(Constants.NAME).setAutoWidth(true).setKey("name").setSortable(
true);
grid.addColumn(MgmtDistributionSet::getVersion).setHeader(Constants.VERSION).setAutoWidth(true).setKey("version")
.setSortable(true);
grid.addColumn(MgmtDistributionSet::getTypeName).setHeader(Constants.TYPE).setAutoWidth(true).setKey("typename")
.setSortable(true);
grid.sort(List.of(new GridSortOrder<>(createdAtCol, SortDirection.DESCENDING)));
grid.setItemDetailsRenderer(new ComponentRenderer<>(
() -> details, DistributionSetDetails::setItem));
}
},
(query, rsqlFilter) -> Optional.ofNullable(
hawkbitClient.getDistributionSetRestApi()
.getDistributionSets(rsqlFilter, query.getOffset(), query.getPageSize(), Constants.NAME_ASC)
.getBody())
hawkbitClient.getDistributionSetRestApi()
.getDistributionSets(rsqlFilter, query.getOffset(), query.getPageSize(), Utils.getSortParam(query
.getSortOrders()))
.getBody())
.stream().flatMap(body -> body.getContent().stream()),
e -> new CreateDialog(hawkbitClient).result(),
selectionGrid -> {
@@ -90,7 +99,7 @@ public class DistributionSetView extends TableView<MgmtDistributionSet, Long> {
distributionSet -> hawkbitClient.getDistributionSetRestApi()
.deleteDistributionSet(distributionSet.getId()));
return CompletableFuture.completedFuture(null);
});
}, null);
}
private static SelectionGrid<MgmtSoftwareModule, Long> selectSoftwareModuleGrid() {
@@ -109,42 +118,66 @@ public class DistributionSetView extends TableView<MgmtDistributionSet, Long> {
});
}
private static class DistributionSetFilter implements Filter.Rsql {
private static class DistributionSetRawFilter implements Filter.Rsql, Filter.RsqlRw {
private final TextField name = Utils.textField("Name");
private DistributionSetRawFilter() {
name.setPlaceholder("<rsql filter>");
}
@Override
public List<Component> components() {
return List.of(name);
}
@Override
public String filter() {
return name.getOptionalValue().orElse(null);
}
@Override
public void setFilter(String filter) {
name.setValue(filter);
}
}
private static class DistributionSetFilter implements Filter.Rsql {
private final TextField textFilter = Utils.textField("Filter");
private final CheckboxGroup<MgmtDistributionSetType> type = new CheckboxGroup<>("Type");
private final CheckboxGroup<MgmtTag> tag = new CheckboxGroup<>("Tag");
private DistributionSetFilter(final HawkbitMgmtClient hawkbitClient) {
name.setPlaceholder("<name filter>");
textFilter.setPlaceholder("<name/version filter>");
type.setItemLabelGenerator(MgmtDistributionSetType::getName);
type.setItems(Optional.ofNullable(
hawkbitClient.getDistributionSetTypeRestApi()
.getDistributionSetTypes(null, 0, 20, Constants.NAME_ASC)
.getBody())
hawkbitClient.getDistributionSetTypeRestApi()
.getDistributionSetTypes(null, 0, 20, Constants.NAME_ASC)
.getBody())
.map(PagedList::getContent)
.orElseGet(Collections::emptyList));
tag.setItemLabelGenerator(MgmtTag::getName);
tag.setItems(Optional.ofNullable(
hawkbitClient.getDistributionSetTagRestApi()
.getDistributionSetTags(null, 0, 20, Constants.NAME_ASC)
.getBody())
hawkbitClient.getDistributionSetTagRestApi()
.getDistributionSetTags(null, 0, 20, Constants.NAME_ASC)
.getBody())
.map(PagedList::getContent)
.orElseGet(Collections::emptyList));
}
@Override
public List<Component> components() {
return List.of(name, type);
return List.of(textFilter, type);
}
@Override
public String filter() {
return Filter.filter(
Map.of(
"name", name.getOptionalValue(),
List.of("version", "name"), textFilter.getOptionalValue().map(s -> "*" + s + "*"),
"type", type.getSelectedItems().stream().map(MgmtDistributionSetType::getKey).toList(),
"tag", tag.getSelectedItems()));
"tag", tag.getSelectedItems().stream().map(MgmtTag::getName).toList()));
}
}
@@ -157,6 +190,7 @@ public class DistributionSetView extends TableView<MgmtDistributionSet, Long> {
private final TextField createdAt = Utils.textField("Created at");
private final TextField lastModifiedBy = Utils.textField("Last modified by");
private final TextField lastModifiedAt = Utils.textField("Last modified at");
private final TextArea metadata = new TextArea("Metadata");
private final SelectionGrid<MgmtSoftwareModule, Long> softwareModulesGrid = selectSoftwareModuleGrid();
private DistributionSetDetails(final HawkbitMgmtClient hawkbitClient) {
@@ -164,9 +198,9 @@ public class DistributionSetView extends TableView<MgmtDistributionSet, Long> {
description.setMinLength(2);
Stream.of(
description,
createdBy, createdAt,
lastModifiedBy, lastModifiedAt)
description,
createdBy, createdAt,
lastModifiedBy, lastModifiedAt, metadata)
.forEach(field -> {
field.setReadOnly(true);
add(field);
@@ -181,9 +215,14 @@ public class DistributionSetView extends TableView<MgmtDistributionSet, Long> {
private void setItem(final MgmtDistributionSet distributionSet) {
description.setValue(distributionSet.getDescription());
createdBy.setValue(distributionSet.getCreatedBy());
createdAt.setValue(new Date(distributionSet.getCreatedAt()).toString());
createdAt.setValue(Utils.localDateTimeFromTs(distributionSet.getCreatedAt()));
lastModifiedBy.setValue(distributionSet.getLastModifiedBy());
lastModifiedAt.setValue(new Date(distributionSet.getLastModifiedAt()).toString());
lastModifiedAt.setValue(Utils.localDateTimeFromTs(distributionSet.getLastModifiedAt()));
metadata.setValue(Optional.ofNullable(
hawkbitClient.getDistributionSetRestApi().getMetadata(distributionSet.getId()).getBody())
.map(body -> body.getContent().stream()
.map(b -> String.format("%s: %s\n", b.getKey(), b.getValue())).collect(
Collectors.joining())).orElse(""));
softwareModulesGrid.setItems(query -> Optional.ofNullable(
hawkbitClient.getDistributionSetRestApi()
@@ -214,9 +253,9 @@ public class DistributionSetView extends TableView<MgmtDistributionSet, Long> {
"Type",
this::readyToCreate,
Optional.ofNullable(
hawkbitClient.getDistributionSetTypeRestApi()
.getDistributionSetTypes(null, 0, 30, Constants.NAME_ASC)
.getBody())
hawkbitClient.getDistributionSetTypeRestApi()
.getDistributionSetTypes(null, 0, 30, Constants.CREATED_AT_DESC)
.getBody())
.map(body -> body.getContent().toArray(new MgmtDistributionSetType[0]))
.orElseGet(() -> new MgmtDistributionSetType[0]));
type.focus();
@@ -262,15 +301,15 @@ public class DistributionSetView extends TableView<MgmtDistributionSet, Long> {
create.addClickListener(e -> {
close();
final long distributionSetId = Optional.ofNullable(
hawkbitClient.getDistributionSetRestApi()
.createDistributionSets(
List.of((MgmtDistributionSetRequestBodyPost) new MgmtDistributionSetRequestBodyPost()
.setType(type.getValue().getKey())
.setName(name.getValue())
.setVersion(version.getValue())
.setDescription(description.getValue())
.setRequiredMigrationStep(requiredMigrationStep.getValue())))
.getBody())
hawkbitClient.getDistributionSetRestApi()
.createDistributionSets(
List.of((MgmtDistributionSetRequestBodyPost) new MgmtDistributionSetRequestBodyPost()
.setType(type.getValue().getKey())
.setName(name.getValue())
.setVersion(version.getValue())
.setDescription(description.getValue())
.setRequiredMigrationStep(requiredMigrationStep.getValue())))
.getBody())
.stream()
.flatMap(Collection::stream)
.findFirst()
@@ -281,7 +320,7 @@ public class DistributionSetView extends TableView<MgmtDistributionSet, Long> {
}
}
@SuppressWarnings({"java:S1171", "java:S3599"})
@SuppressWarnings({ "java:S1171", "java:S3599" })
private static class AddSoftwareModulesDialog extends Utils.BaseDialog<Void> {
private final transient Set<MgmtSoftwareModule> softwareModules = Collections.synchronizedSet(new HashSet<>());
@@ -296,19 +335,22 @@ public class DistributionSetView extends TableView<MgmtDistributionSet, Long> {
});
final Component addRemoveControls = Utils.addRemoveControls(
v -> new Utils.BaseDialog<Void>("Add Software Modules") {{
final SoftwareModuleView softwareModulesView = new SoftwareModuleView(false, hawkbitClient);
add(softwareModulesView);
final Button addBtn = new Button("Add");
addBtn.addClickListener(e -> {
softwareModules.addAll(softwareModulesView.getSelection());
softwareModulesGrid.refreshGrid(false);
close();
});
addBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
getFooter().add(addBtn);
open();
}}.result(),
v -> new Utils.BaseDialog<Void>("Add Software Modules") {
{
final SoftwareModuleView softwareModulesView = new SoftwareModuleView(false, hawkbitClient);
add(softwareModulesView);
final Button addBtn = new Button("Add");
addBtn.addClickListener(e -> {
softwareModules.addAll(softwareModulesView.getSelection());
softwareModulesGrid.refreshGrid(false);
close();
});
addBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
getFooter().add(addBtn);
open();
}
}.result(),
v -> {
Utils.remove(softwareModulesGrid.getSelectedItems(), softwareModules, MgmtSoftwareModule::getId);
softwareModulesGrid.refreshGrid(false);

View File

@@ -10,7 +10,6 @@
package org.eclipse.hawkbit.ui.simple.view;
import java.time.ZoneOffset;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -61,7 +60,7 @@ import org.springframework.util.ObjectUtils;
@Route(value = "rollouts", layout = MainLayout.class)
@RolesAllowed({ "ROLLOUT_READ" })
@Uses(Icon.class)
@SuppressWarnings({"java:S1171", "java:S3599"})
@SuppressWarnings({ "java:S1171", "java:S3599" })
public class RolloutView extends TableView<MgmtRolloutResponseBody, Long> {
public RolloutView(final HawkbitMgmtClient hawkbitClient) {
@@ -118,33 +117,45 @@ public class RolloutView extends TableView<MgmtRolloutResponseBody, Long> {
private void init(final MgmtRolloutResponseBody rollout) {
if ("READY".equalsIgnoreCase(rollout.getStatus())) {
add(Utils.tooltip(new Button(VaadinIcon.START_COG.create()) {{
addClickListener(v -> {
hawkbitClient.getRolloutRestApi().start(rollout.getId());
refresh();
});
}}, "Start"));
add(Utils.tooltip(new Button(VaadinIcon.START_COG.create()) {
{
addClickListener(v -> {
hawkbitClient.getRolloutRestApi().start(rollout.getId());
refresh();
});
}
}, "Start"));
} else if ("RUNNING".equalsIgnoreCase(rollout.getStatus())) {
add(Utils.tooltip(new Button(VaadinIcon.PAUSE.create()) {{
addClickListener(v -> {
hawkbitClient.getRolloutRestApi().pause(rollout.getId());
refresh();
});
}}, "Pause"));
add(Utils.tooltip(new Button(VaadinIcon.PAUSE.create()) {
{
addClickListener(v -> {
hawkbitClient.getRolloutRestApi().pause(rollout.getId());
refresh();
});
}
}, "Pause"));
} else if ("PAUSED".equalsIgnoreCase(rollout.getStatus())) {
add(Utils.tooltip(new Button(VaadinIcon.START_COG.create()) {{
addClickListener(v -> {
hawkbitClient.getRolloutRestApi().resume(rollout.getId());
refresh();
});
}}, "Resume"));
add(Utils.tooltip(new Button(VaadinIcon.START_COG.create()) {
{
addClickListener(v -> {
hawkbitClient.getRolloutRestApi().resume(rollout.getId());
refresh();
});
}
}, "Resume"));
}
add(Utils.tooltip(new Button(VaadinIcon.TRASH.create()) {{
addClickListener(v -> {
hawkbitClient.getRolloutRestApi().delete(rollout.getId());
grid.getDataProvider().refreshAll();
});
}}, "Cancel and Remove"));
add(Utils.tooltip(new Button(VaadinIcon.TRASH.create()) {
{
addClickListener(v -> {
hawkbitClient.getRolloutRestApi().delete(rollout.getId());
grid.getDataProvider().refreshAll();
});
}
}, "Cancel and Remove"));
}
private void refresh() {
@@ -197,11 +208,11 @@ public class RolloutView extends TableView<MgmtRolloutResponseBody, Long> {
description.setMinLength(2);
groupGrid = createGroupGrid();
Stream.of(
description,
createdBy, createdAt,
lastModifiedBy, lastModifiedAt,
targetFilter, distributionSet,
actonType, startAt)
description,
createdBy, createdAt,
lastModifiedBy, lastModifiedAt,
targetFilter, distributionSet,
actonType, startAt)
.forEach(field -> {
field.setReadOnly(true);
add(field);
@@ -219,9 +230,9 @@ public class RolloutView extends TableView<MgmtRolloutResponseBody, Long> {
private void setItem(final MgmtRolloutResponseBody rollout) {
description.setValue(rollout.getDescription());
createdBy.setValue(rollout.getCreatedBy());
createdAt.setValue(new Date(rollout.getCreatedAt()).toString());
createdAt.setValue(Utils.localDateTimeFromTs(rollout.getCreatedAt()));
lastModifiedBy.setValue(rollout.getLastModifiedBy());
lastModifiedAt.setValue(new Date(rollout.getLastModifiedAt()).toString());
lastModifiedAt.setValue(Utils.localDateTimeFromTs(rollout.getLastModifiedAt()));
targetFilter.setValue(rollout.getTargetFilterQuery());
final MgmtDistributionSet distributionSetMgmt = hawkbitClient.getDistributionSetRestApi()
.getDistributionSet(rollout.getDistributionSetId()).getBody();
@@ -232,18 +243,18 @@ public class RolloutView extends TableView<MgmtRolloutResponseBody, Long> {
case SOFT -> Constants.SOFT;
case FORCED -> Constants.FORCED;
case DOWNLOAD_ONLY -> Constants.DOWNLOAD_ONLY;
case TIMEFORCED -> "Scheduled at " + new Date(rollout.getForcetime());
case TIMEFORCED -> "Scheduled at " + Utils.localDateTimeFromTs(rollout.getForcetime());
});
startAt.setValue(ObjectUtils.isEmpty(rollout.getStartAt()) ? "" : new Date(rollout.getStartAt()).toString());
startAt.setValue(ObjectUtils.isEmpty(rollout.getStartAt()) ? "" : Utils.localDateTimeFromTs(rollout.getStartAt()));
dynamic.setValue(rollout.isDynamic());
groupGrid.setItems(query -> Optional.ofNullable(
hawkbitClient.getRolloutRestApi()
.getRolloutGroups(
rollout.getId(),
null, query.getOffset(), query.getPageSize(),
null, "full")
.getBody())
hawkbitClient.getRolloutRestApi()
.getRolloutGroups(
rollout.getId(),
null, query.getOffset(), query.getPageSize(),
null, "full")
.getBody())
.stream().flatMap(body -> body.getContent().stream())
.skip(query.getOffset())
.limit(query.getPageSize()));
@@ -259,7 +270,8 @@ public class RolloutView extends TableView<MgmtRolloutResponseBody, Long> {
grid.addColumn(MgmtRolloutGroupResponseBody::getId).setHeader(Constants.ID).setAutoWidth(true);
grid.addColumn(MgmtRolloutGroupResponseBody::getName).setHeader(Constants.NAME).setAutoWidth(true);
grid.addColumn(MgmtRolloutGroupResponseBody::getTotalTargets).setHeader(Constants.TARGET_COUNT).setAutoWidth(true);
grid.addColumn(MgmtRolloutGroupResponseBody::getTotalTargetsPerStatus).setHeader(Constants.STATS).setAutoWidth(true);
grid.addColumn(MgmtRolloutGroupResponseBody::getTotalTargetsPerStatus).setHeader(Constants.STATS).setAutoWidth(
true);
grid.addColumn(MgmtRolloutGroupResponseBody::getStatus).setHeader(Constants.STATUS).setAutoWidth(true);
}
});
@@ -291,22 +303,21 @@ public class RolloutView extends TableView<MgmtRolloutResponseBody, Long> {
"Distribution Set",
this::readyToCreate,
Optional.ofNullable(
hawkbitClient.getDistributionSetRestApi()
.getDistributionSets(null, 0, 30, Constants.NAME_ASC)
.getBody())
hawkbitClient.getDistributionSetRestApi()
.getDistributionSets(null, 0, 30, Constants.CREATED_AT_DESC)
.getBody())
.map(body -> body.getContent().toArray(new MgmtDistributionSet[0]))
.orElseGet(() -> new MgmtDistributionSet[0]));
distributionSet.setRequiredIndicatorVisible(true);
distributionSet.setItemLabelGenerator(distributionSetO ->
distributionSetO.getName() + ":" + distributionSetO.getVersion());
distributionSet.setItemLabelGenerator(distributionSetO -> distributionSetO.getName() + ":" + distributionSetO.getVersion());
distributionSet.setWidthFull();
targetFilter = new Select<>(
"Target Filter",
this::readyToCreate,
Optional.ofNullable(
hawkbitClient.getTargetFilterQueryRestApi()
.getFilters(null, 0, 30, Constants.NAME_ASC, null)
.getBody())
hawkbitClient.getTargetFilterQueryRestApi()
.getFilters(null, 0, 30, Constants.NAME_ASC, null)
.getBody())
.map(body -> body.getContent().toArray(new MgmtTargetFilterQuery[0]))
.orElseGet(() -> new MgmtTargetFilterQuery[0]));
targetFilter.setRequiredIndicatorVisible(true);
@@ -323,20 +334,18 @@ public class RolloutView extends TableView<MgmtRolloutResponseBody, Long> {
startType.setLabel(Constants.START_TYPE);
startType.setItems(StartType.values());
startType.setValue(StartType.MANUAL);
final ComponentRenderer<Component, StartType> startTypeRenderer = new ComponentRenderer<>(startTypeO ->
switch (startTypeO) {
case MANUAL -> new Text(Constants.MANUAL);
case AUTO -> new Text(Constants.AUTO);
case SCHEDULED -> startAt;
});
final ComponentRenderer<Component, StartType> startTypeRenderer = new ComponentRenderer<>(startTypeO -> switch (startTypeO) {
case MANUAL -> new Text(Constants.MANUAL);
case AUTO -> new Text(Constants.AUTO);
case SCHEDULED -> startAt;
});
startType.setRenderer(startTypeRenderer);
startType.addValueChangeListener(e -> startType.setRenderer(startTypeRenderer));
startType.setItemLabelGenerator(startTypeO ->
switch (startTypeO) {
case MANUAL -> Constants.MANUAL;
case AUTO -> Constants.AUTO;
case SCHEDULED -> "Scheduled" + (startAt.isEmpty() ? "" : " at " + startAt.getValue());
});
startType.setItemLabelGenerator(startTypeO -> switch (startTypeO) {
case MANUAL -> Constants.MANUAL;
case AUTO -> Constants.AUTO;
case SCHEDULED -> "Scheduled" + (startAt.isEmpty() ? "" : " at " + startAt.getValue());
});
startType.setWidthFull();
final Div percentSuffix = new Div();
@@ -377,12 +386,8 @@ public class RolloutView extends TableView<MgmtRolloutResponseBody, Long> {
}
private void readyToCreate(final Object v) {
final boolean createEnabled = !name.isEmpty() &&
!distributionSet.isEmpty() &&
!targetFilter.isEmpty() &&
!groupNumber.isEmpty() &&
!triggerThreshold.isEmpty() &&
!errorThreshold.isEmpty();
final boolean createEnabled = !name.isEmpty() && !distributionSet.isEmpty() && !targetFilter.isEmpty() && !groupNumber
.isEmpty() && !triggerThreshold.isEmpty() && !errorThreshold.isEmpty();
if (create.isEnabled() != createEnabled) {
create.setEnabled(createEnabled);
}
@@ -400,16 +405,12 @@ public class RolloutView extends TableView<MgmtRolloutResponseBody, Long> {
request.setType(actionType.getValue());
if (actionType.getValue() == MgmtActionType.TIMEFORCED) {
request.setForcetime(
forceTime.isEmpty() ?
System.currentTimeMillis() :
forceTime.getValue().toEpochSecond(ZoneOffset.UTC) * 1000);
forceTime.isEmpty() ? System.currentTimeMillis() : forceTime.getValue().toEpochSecond(ZoneOffset.UTC) * 1000);
}
switch (startType.getValue()) {
case AUTO -> request.setStartAt(System.currentTimeMillis());
case SCHEDULED -> request.setStartAt(
startAt.isEmpty() ?
System.currentTimeMillis() :
startAt.getValue().toEpochSecond(ZoneOffset.UTC) * 1000);
startAt.isEmpty() ? System.currentTimeMillis() : startAt.getValue().toEpochSecond(ZoneOffset.UTC) * 1000);
case MANUAL -> {
// do nothing, will be started manually
}

View File

@@ -14,7 +14,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -93,9 +92,9 @@ public class SoftwareModuleView extends TableView<MgmtSoftwareModule, Long> {
}
},
(query, rsqlFilter) -> Optional.ofNullable(
hawkbitClient.getSoftwareModuleRestApi()
.getSoftwareModules(rsqlFilter, query.getOffset(), query.getPageSize(), Constants.NAME_ASC)
.getBody())
hawkbitClient.getSoftwareModuleRestApi()
.getSoftwareModules(rsqlFilter, query.getOffset(), query.getPageSize(), Constants.NAME_ASC)
.getBody())
.stream().map(PagedList::getContent).flatMap(List::stream),
isParent ? v -> new CreateDialog(hawkbitClient).result() : null,
isParent ? selectionGrid -> {
@@ -132,9 +131,9 @@ public class SoftwareModuleView extends TableView<MgmtSoftwareModule, Long> {
name.setPlaceholder("<name filter>");
type.setItemLabelGenerator(MgmtSoftwareModuleType::getName);
type.setItems(Optional.ofNullable(
hawkbitClient.getSoftwareModuleTypeRestApi()
.getTypes(null, 0, 20, Constants.NAME_ASC)
.getBody())
hawkbitClient.getSoftwareModuleTypeRestApi()
.getTypes(null, 0, 20, Constants.NAME_ASC)
.getBody())
.map(PagedList::getContent)
.orElseGet(Collections::emptyList));
}
@@ -184,14 +183,14 @@ public class SoftwareModuleView extends TableView<MgmtSoftwareModule, Long> {
private void setItem(final MgmtSoftwareModule softwareModule) {
description.setValue(softwareModule.getDescription());
createdBy.setValue(softwareModule.getCreatedBy());
createdAt.setValue(new Date(softwareModule.getCreatedAt()).toString());
createdAt.setValue(Utils.localDateTimeFromTs(softwareModule.getCreatedAt()));
lastModifiedBy.setValue(softwareModule.getLastModifiedBy());
lastModifiedAt.setValue(new Date(softwareModule.getLastModifiedAt()).toString());
lastModifiedAt.setValue(Utils.localDateTimeFromTs(softwareModule.getLastModifiedAt()));
artifactGrid.setItems(query -> Optional.ofNullable(
hawkbitClient.getSoftwareModuleRestApi()
.getArtifacts(softwareModule.getId(), null, null)
.getBody())
hawkbitClient.getSoftwareModuleRestApi()
.getArtifacts(softwareModule.getId(), null, null)
.getBody())
.stream()
.flatMap(Collection::stream)
.skip(query.getOffset())
@@ -220,9 +219,9 @@ public class SoftwareModuleView extends TableView<MgmtSoftwareModule, Long> {
Constants.TYPE,
this::readyToCreate,
Optional.ofNullable(
hawkbitClient.getSoftwareModuleTypeRestApi()
.getTypes(null, 0, 30, Constants.NAME_ASC)
.getBody())
hawkbitClient.getSoftwareModuleTypeRestApi()
.getTypes(null, 0, 30, Constants.NAME_ASC)
.getBody())
.map(body -> body.getContent().toArray(new MgmtSoftwareModuleType[0]))
.orElseGet(() -> new MgmtSoftwareModuleType[0]));
type.setWidthFull();
@@ -253,9 +252,9 @@ public class SoftwareModuleView extends TableView<MgmtSoftwareModule, Long> {
if (Boolean.TRUE.equals(createDistributionSet.getValue()) && distType.isEmpty()) {
distType.setItems(
Optional.ofNullable(
hawkbitClient.getDistributionSetTypeRestApi()
.getDistributionSetTypes(null, 0, 30, Constants.NAME_ASC)
.getBody())
hawkbitClient.getDistributionSetTypeRestApi()
.getDistributionSetTypes(null, 0, 30, Constants.NAME_ASC)
.getBody())
.map(body -> body.getContent().toArray(new MgmtDistributionSetType[0]))
.orElseGet(() -> new MgmtDistributionSetType[0]));
}
@@ -286,8 +285,8 @@ public class SoftwareModuleView extends TableView<MgmtSoftwareModule, Long> {
}
private void readyToCreate(final Object v) {
final boolean createEnabled = !type.isEmpty() && !name.isEmpty() && !version.isEmpty() &&
(!createDistributionSet.getValue() || !distType.isEmpty());
final boolean createEnabled = !type.isEmpty() && !name.isEmpty() && !version.isEmpty() && (!createDistributionSet
.getValue() || !distType.isEmpty());
if (create.isEnabled() != createEnabled) {
create.setEnabled(createEnabled);
}
@@ -297,30 +296,30 @@ public class SoftwareModuleView extends TableView<MgmtSoftwareModule, Long> {
create.addClickListener(e -> {
close();
final long softwareModuleId = Optional.ofNullable(
hawkbitClient.getSoftwareModuleRestApi().createSoftwareModules(
List.of(new MgmtSoftwareModuleRequestBodyPost()
.setType(type.getValue().getKey())
.setName(name.getValue())
.setVersion(version.getValue())
.setVendor(vendor.getValue())
.setDescription(description.getValue())
.setEncrypted(enableArtifactEncryption.getValue())))
.getBody())
hawkbitClient.getSoftwareModuleRestApi().createSoftwareModules(
List.of(new MgmtSoftwareModuleRequestBodyPost()
.setType(type.getValue().getKey())
.setName(name.getValue())
.setVersion(version.getValue())
.setVendor(vendor.getValue())
.setDescription(description.getValue())
.setEncrypted(enableArtifactEncryption.getValue())))
.getBody())
.stream().flatMap(Collection::stream)
.findFirst()
.orElseThrow()
.getId();
if (Boolean.TRUE.equals(createDistributionSet.getValue())) {
final long distributionSetId = Optional.ofNullable(
hawkbitClient.getDistributionSetRestApi()
.createDistributionSets(
List.of((MgmtDistributionSetRequestBodyPost) new MgmtDistributionSetRequestBodyPost()
.setType(distType.getValue().getKey())
.setName(name.getValue())
.setVersion(version.getValue())
.setDescription(description.getValue())
.setRequiredMigrationStep(distRequiredMigrationStep.getValue())))
.getBody())
hawkbitClient.getDistributionSetRestApi()
.createDistributionSets(
List.of((MgmtDistributionSetRequestBodyPost) new MgmtDistributionSetRequestBodyPost()
.setType(distType.getValue().getKey())
.setName(name.getValue())
.setVersion(version.getValue())
.setDescription(description.getValue())
.setRequiredMigrationStep(distRequiredMigrationStep.getValue())))
.getBody())
.stream()
.flatMap(Collection::stream)
.findFirst()

View File

@@ -9,11 +9,9 @@
*/
package org.eclipse.hawkbit.ui.simple.view;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -27,6 +25,8 @@ import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.vaadin.flow.data.provider.ListDataProvider;
import jakarta.annotation.security.RolesAllowed;
import com.vaadin.flow.component.AttachEvent;
@@ -39,7 +39,6 @@ import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.checkbox.CheckboxGroup;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.confirmdialog.ConfirmDialog;
import com.vaadin.flow.component.datetimepicker.DateTimePicker;
import com.vaadin.flow.component.dependency.Uses;
import com.vaadin.flow.component.formlayout.FormLayout;
@@ -60,11 +59,10 @@ import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.shared.Registration;
import com.vaadin.flow.theme.lumo.LumoUtility;
import lombok.extern.slf4j.Slf4j;
import lombok.EqualsAndHashCode;
import org.eclipse.hawkbit.mgmt.json.model.MgmtPollStatus;
import org.eclipse.hawkbit.mgmt.json.model.PagedList;
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtAction;
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtActionRequestBodyPut;
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtActionStatus;
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.MgmtTargetAssignmentRequestBody;
@@ -78,7 +76,9 @@ import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQueryReq
import org.eclipse.hawkbit.mgmt.json.model.targettype.MgmtTargetType;
import org.eclipse.hawkbit.ui.simple.HawkbitMgmtClient;
import org.eclipse.hawkbit.ui.simple.MainLayout;
import org.eclipse.hawkbit.ui.simple.component.TargetActionsHistory;
import org.eclipse.hawkbit.ui.simple.view.util.Filter;
import org.eclipse.hawkbit.ui.simple.view.util.LinkedTextArea;
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.Utils;
@@ -89,33 +89,46 @@ import org.springframework.util.ObjectUtils;
@Route(value = "targets", layout = MainLayout.class)
@RolesAllowed({ "TARGET_READ" })
@Uses(Icon.class)
public class TargetView extends TableView<MgmtTarget, String> {
public class TargetView extends TableView<TargetView.TargetWithDs, String> {
public static final String STATUS = "Status";
public static final String UPDATE = "Sync";
public static final String CONTROLLER_ID = "Controller Id";
public static final String FILTER = "Filter";
public static final String TAG = "Tag";
public TargetView(final HawkbitMgmtClient hawkbitClient) {
super(
new RawFilter(hawkbitClient), new SimpleFilter(hawkbitClient),
new SelectionGrid.EntityRepresentation<>(MgmtTarget.class, MgmtTarget::getControllerId) {
new SelectionGrid.EntityRepresentation<>(TargetWithDs.class, TargetWithDs::getControllerId) {
@Override
protected void addColumns(final Grid<MgmtTarget> grid) {
protected void addColumns(final Grid<TargetWithDs> grid) {
grid.addColumn(new ComponentRenderer<>(TargetStatusCell::new))
.setHeader(STATUS)
.setAutoWidth(true)
.setFlexGrow(0);
grid.addColumn(MgmtTarget::getControllerId).setHeader(CONTROLLER_ID).setAutoWidth(true);
grid.addColumn(MgmtTarget::getName).setHeader(Constants.NAME).setAutoWidth(true);
grid.addColumn(MgmtTarget::getTargetTypeName).setHeader(Constants.TYPE).setAutoWidth(true);
.setFlexGrow(0).setKey("lastControllerRequestAt").setSortable(true);
grid.addColumn(new ComponentRenderer<>(TargetUpdateStatusCell::new))
.setHeader(UPDATE)
.setAutoWidth(true)
.setFlexGrow(0).setKey("updateStatus").setSortable(true);
grid.addColumn(MgmtTarget::getControllerId).setHeader(CONTROLLER_ID).setAutoWidth(true).setKey("id").setSortable(true);
grid.addColumn(Utils.localDateTimeRenderer(MgmtTarget::getLastModifiedAt)).setHeader(LAST_MODIFIED_AT).setAutoWidth(
true).setKey("lastModifiedAt").setSortable(true);
grid.addColumn(MgmtTarget::getName).setHeader(Constants.NAME).setAutoWidth(true).setKey("name").setSortable(true);
grid.addColumn(MgmtTarget::getTargetTypeName).setHeader(Constants.TYPE).setAutoWidth(true).setKey("targetType")
.setSortable(true);
grid.addColumn(TargetWithDs::getDsName).setHeader(Constants.DISTRIBUTION_SET).setAutoWidth(true);
grid.addColumn(TargetWithDs::getDsVersion).setHeader(Constants.VERSION).setAutoWidth(true).setKey("installedds")
.setSortable(true);
}
},
(query, filter) -> hawkbitClient.getTargetRestApi()
.getTargets(filter, query.getOffset(), query.getPageSize(), Constants.NAME_ASC)
.getTargets(filter, query.getOffset(), query.getPageSize(), Utils.getSortParam(query.getSortOrders(),
"lastModifiedAt:desc"))
.getBody()
.getContent()
.stream(),
.stream().map(m -> TargetWithDs.from(hawkbitClient, m)),
source -> new RegisterDialog(hawkbitClient).result(),
selectionGrid -> {
selectionGrid.getSelectedItems()
@@ -129,8 +142,8 @@ public class TargetView extends TableView<MgmtTarget, String> {
}
);
final Function<SelectionGrid<MgmtTarget, String>, CompletionStage<Void>> assignHandler =
source -> new AssignDialog(hawkbitClient, source.getSelectedItems()).result();
final Function<SelectionGrid<TargetWithDs, String>, CompletionStage<Void>> assignHandler = source -> new AssignDialog(
hawkbitClient, source.getSelectedItems()).result();
final Button assignBtn = Utils.tooltip(new Button(VaadinIcon.LINK.create()), "Assign");
assignBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
@@ -142,15 +155,15 @@ public class TargetView extends TableView<MgmtTarget, String> {
private final HawkbitMgmtClient hawkbitClient;
private final TextField controllerId;
private final TextField textFilter;
private final CheckboxGroup<MgmtTargetType> type;
private final CheckboxGroup<MgmtTag> tag;
private SimpleFilter(final HawkbitMgmtClient hawkbitClient) {
this.hawkbitClient = hawkbitClient;
controllerId = Utils.textField(CONTROLLER_ID);
controllerId.setPlaceholder("<controller id filter>");
textFilter = Utils.textField(FILTER);
textFilter.setPlaceholder("<controller id/name filter>");
type = new CheckboxGroup<>(Constants.TYPE);
type.setItemLabelGenerator(MgmtTargetType::getName);
tag = new CheckboxGroup<>(TAG);
@@ -160,13 +173,13 @@ public class TargetView extends TableView<MgmtTarget, String> {
@Override
public List<Component> components() {
final List<Component> components = new LinkedList<>();
components.add(controllerId);
components.add(textFilter);
type.setItems(hawkbitClient.getTargetTypeRestApi().getTargetTypes(null, 0, 20, Constants.NAME_ASC).getBody().getContent());
if (!type.getValue().isEmpty()) {
if (!((ListDataProvider) type.getDataProvider()).getItems().isEmpty()) {
components.add(type);
}
tag.setItems(hawkbitClient.getTargetTagRestApi().getTargetTags(null, 0, 20, Constants.NAME_ASC).getBody().getContent());
if (!tag.isEmpty()) {
if (!((ListDataProvider) tag.getDataProvider()).getItems().isEmpty()) {
components.add(tag);
}
return components;
@@ -176,15 +189,15 @@ public class TargetView extends TableView<MgmtTarget, String> {
public String filter() {
return Filter.filter(
Map.of(
"controllerid", controllerId.getOptionalValue(),
List.of("controllerid", "name"), textFilter.getOptionalValue().map(s -> "*" + s + "*"),
"targettype.name", type.getSelectedItems().stream().map(MgmtTargetType::getName)
.toList(),
"tag", tag.getSelectedItems()));
"tag", tag.getSelectedItems().stream().map(MgmtTag::getName).toList()));
}
}
@SuppressWarnings({ "java:S1171", "java:S3599" })
private static class RawFilter implements Filter.Rsql {
private static class RawFilter implements Filter.Rsql, Filter.RsqlRw {
private final TextField textFilter = new TextField("Raw Filter", "<raw filter>");
private final VerticalLayout layout = new VerticalLayout();
@@ -209,8 +222,8 @@ public class TargetView extends TableView<MgmtTarget, String> {
});
savedFilters.setEmptySelectionAllowed(true);
savedFilters.setItems(listFilters(hawkbitClient));
savedFilters.setItemLabelGenerator(query ->
Optional.ofNullable(query).map(MgmtTargetFilterQuery::getName).orElse("<select saved filter>"));
savedFilters.setItemLabelGenerator(query -> Optional.ofNullable(query).map(MgmtTargetFilterQuery::getName).orElse(
"<select saved filter>"));
savedFilters.setWidthFull();
textFilter.setWidthFull();
@@ -237,25 +250,27 @@ public class TargetView extends TableView<MgmtTarget, String> {
}
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();
}};
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) {
@@ -265,34 +280,37 @@ public class TargetView extends TableView<MgmtTarget, String> {
return;
}
new Utils.BaseDialog<Void>("Update Filter") {{
final Button finishBtn = Utils.tooltip(new Button("Update"), "Update (Enter)");
finishBtn.setEnabled(false);
new Utils.BaseDialog<Void>("Update Filter") {
final TextField name = Utils.textField(Constants.NAME, e -> finishBtn.setEnabled(!e.getHasValue().isEmpty()));
name.focus();
name.setValue(selected.getName());
{
final Button finishBtn = Utils.tooltip(new Button("Update"), "Update (Enter)");
finishBtn.setEnabled(false);
final TextArea filterValue = new TextArea("Filter Value");
filterValue.setReadOnly(true);
filterValue.setValue(textFilter.getValue());
filterValue.setWidthFull();
final TextField name = Utils.textField(Constants.NAME, e -> finishBtn.setEnabled(!e.getHasValue().isEmpty()));
name.focus();
name.setValue(selected.getName());
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);
final TextArea filterValue = new TextArea("Filter Value");
filterValue.setReadOnly(true);
filterValue.setValue(textFilter.getValue());
filterValue.setWidthFull();
add(name);
add(filterValue);
open();
}};
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();
}
};
};
}
@@ -305,6 +323,11 @@ public class TargetView extends TableView<MgmtTarget, String> {
public String filter() {
return textFilter.getOptionalValue().orElse(null);
}
@Override
public void setFilter(String filter) {
textFilter.setValue(filter);
}
}
protected static class TargetDetailedView extends TabSheet {
@@ -312,26 +335,26 @@ public class TargetView extends TableView<MgmtTarget, String> {
private final TargetDetails targetDetails;
private final TargetAssignedInstalled targetAssignedInstalled;
private final TargetTags targetTags;
private final TargetActions targetActions;
private final TargetActionsHistoryLayout targetActionsHistoryLayout;
private TargetDetailedView(final HawkbitMgmtClient hawkbitClient) {
targetDetails = new TargetDetails(hawkbitClient);
targetAssignedInstalled = new TargetAssignedInstalled(hawkbitClient);
targetTags = new TargetTags(hawkbitClient);
targetActions = new TargetActions(hawkbitClient);
targetActionsHistoryLayout = new TargetActionsHistoryLayout(hawkbitClient);
setWidthFull();
add("Details", targetDetails);
add("Assigned / Installed", targetAssignedInstalled);
add("Tags", targetTags);
add("Action History", targetActions);
add("Action History", targetActionsHistoryLayout);
}
private void setItem(final MgmtTarget target) {
this.targetDetails.setItem(target);
this.targetAssignedInstalled.setItem(target);
this.targetTags.setItem(target);
this.targetActions.setItem(target);
this.targetActionsHistoryLayout.setItem(target);
}
}
@@ -346,6 +369,7 @@ public class TargetView extends TableView<MgmtTarget, String> {
private final TextField securityToken = Utils.textField(Constants.SECURITY_TOKEN);
private final TextField lastPoll = Utils.textField(Constants.LAST_POLL);
private final TextField group = Utils.textField(Constants.GROUP);
private final TextField targetAddress = Utils.textField(Constants.ADDRESS);
private final TextArea targetAttributes = new TextArea(Constants.ATTRIBUTES);
private transient MgmtTarget target;
@@ -353,11 +377,11 @@ public class TargetView extends TableView<MgmtTarget, String> {
this.hawkbitClient = hawkbitClient;
description.setMinLength(2);
Stream.of(
description,
createdBy, createdAt,
lastModifiedBy, lastModifiedAt,
securityToken, lastPoll, targetAttributes, group
)
description,
createdBy, createdAt,
lastModifiedBy, lastModifiedAt,
securityToken, lastPoll, group, targetAddress, targetAttributes
)
.forEach(field -> {
field.setReadOnly(true);
add(field);
@@ -375,14 +399,15 @@ public class TargetView extends TableView<MgmtTarget, String> {
protected void onAttach(final AttachEvent attachEvent) {
description.setValue(target.getDescription() == null ? "N/A" : target.getDescription());
createdBy.setValue(target.getCreatedBy());
createdAt.setValue(new Date(target.getCreatedAt()).toString());
createdAt.setValue(Utils.localDateTimeFromTs(target.getCreatedAt()));
lastModifiedBy.setValue(target.getLastModifiedBy());
lastModifiedAt.setValue(new Date(target.getLastModifiedAt()).toString());
securityToken.setValue(target.getSecurityToken());
lastModifiedAt.setValue(Utils.localDateTimeFromTs(target.getLastModifiedAt()));
securityToken.setValue(Objects.requireNonNullElse(target.getSecurityToken(), ""));
group.setValue(target.getGroup() != null ? target.getGroup() : "");
targetAddress.setValue(target.getAddress() != null ? target.getAddress() : "");
final MgmtPollStatus pollStatus = target.getPollStatus();
lastPoll.setValue(pollStatus == null ? NOT_AVAILABLE_NULL : new Date(pollStatus.getLastRequestAt()).toString());
lastPoll.setValue(pollStatus == null ? NOT_AVAILABLE_NULL : Utils.localDateTimeFromTs(pollStatus.getLastRequestAt()));
final ResponseEntity<MgmtTargetAttributes> response = hawkbitClient.getTargetRestApi().getAttributes(target.getControllerId());
if (response.getStatusCode().is2xxSuccessful()) {
targetAttributes.setValue(Objects.requireNonNullElse(response.getBody(), Collections.emptyMap()).entrySet().stream()
@@ -397,14 +422,12 @@ public class TargetView extends TableView<MgmtTarget, String> {
private static class TargetAssignedInstalled extends FormLayout {
private final transient HawkbitMgmtClient hawkbitClient;
private final TextArea assigned = new TextArea("Assigned Distribution Set");
private final TextArea installed = new TextArea("Installed Distribution Set");
private final LinkedTextArea assigned = new LinkedTextArea("Assigned Distribution Set", "/distribution_sets?");
private final LinkedTextArea installed = new LinkedTextArea("Installed Distribution Set", "/distribution_sets?");
private transient MgmtTarget target;
private TargetAssignedInstalled(HawkbitMgmtClient hawkbitClient) {
this.hawkbitClient = hawkbitClient;
assigned.setReadOnly(true);
installed.setReadOnly(true);
assigned.setWidthFull();
installed.setWidthFull();
add(assigned, installed);
@@ -421,22 +444,23 @@ public class TargetView extends TableView<MgmtTarget, String> {
updateDistributionSetInfo(() -> hawkbitClient.getTargetRestApi().getAssignedDistributionSet(target.getControllerId()), assigned);
}
private void updateDistributionSetInfo(Supplier<ResponseEntity<MgmtDistributionSet>> supplier, TextArea textArea) {
private void updateDistributionSetInfo(Supplier<ResponseEntity<MgmtDistributionSet>> supplier, LinkedTextArea textArea) {
Optional.ofNullable(supplier.get())
.map(ResponseEntity<MgmtDistributionSet>::getBody)
.ifPresent(value -> {
.ifPresentOrElse(value -> {
final String description = """
Name: %s
Version: %s
%s
""".replace("\n", System.lineSeparator());
textArea.setValue(description.formatted(
textArea.setValueWithLink(description.formatted(
value.getName(),
value.getVersion(),
value.getModules().stream().map(module -> module.getTypeName() + ": " + module.getVersion())
.collect(Collectors.joining(System.lineSeparator()))
));
});
), "q=id%3D%3D" + value.getId().toString());
},
() -> textArea.setValueWithLink("", null));
}
}
@@ -466,8 +490,8 @@ public class TargetView extends TableView<MgmtTarget, String> {
private HorizontalLayout buildTagSelectionLayout(HawkbitMgmtClient hawkbitClient) {
final Button createTagButton = new Button("Create Tag");
createTagButton.addClickListener(event ->
new CreateTagDialog(hawkbitClient, () -> tagSelector.setItems(fetchAvailableTags())).result());
createTagButton.addClickListener(event -> new CreateTagDialog(hawkbitClient, () -> tagSelector.setItems(fetchAvailableTags()))
.result());
tagSelector.setWidthFull();
tagSelector.setItemLabelGenerator(MgmtTag::getName);
@@ -541,7 +565,7 @@ public class TargetView extends TableView<MgmtTarget, String> {
int offset = 0;
do {
List<MgmtTag> page = Optional.ofNullable(
hawkbitClient.getTargetTagRestApi().getTargetTags(null, offset, 50, Constants.NAME_ASC).getBody())
hawkbitClient.getTargetTagRestApi().getTargetTags(null, offset, 50, Constants.NAME_ASC).getBody())
.map(PagedList::getContent)
.orElse(Collections.emptyList());
tags.addAll(page);
@@ -552,198 +576,105 @@ public class TargetView extends TableView<MgmtTarget, String> {
}
}
@Slf4j
private static class TargetActions extends Grid<TargetActions.ActionStatusEntry> {
public static class TargetActionsHistoryLayout extends VerticalLayout {
private final transient HawkbitMgmtClient hawkbitClient;
private transient MgmtTarget target;
private final TargetActionsHistory targetActionsHistory;
private TargetActions(final HawkbitMgmtClient hawkbitClient) {
this.hawkbitClient = hawkbitClient;
setWidthFull();
addColumn(new ComponentRenderer<>(ActionStatusEntry::getStatusIcon)).setHeader(STATUS).setAutoWidth(true).setFlexGrow(0);
addColumn(ActionStatusEntry::getDistributionSetName).setHeader("Distribution Set").setAutoWidth(true);
addColumn(ActionStatusEntry::getLastModifiedAt)
.setHeader("Last Modified")
.setAutoWidth(true)
.setFlexGrow(0)
.setComparator(ActionStatusEntry::getLastModifiedAt);
addColumn(new ComponentRenderer<>(ActionStatusEntry::getForceTypeIcon)).setHeader("Type").setAutoWidth(true).setFlexGrow(0);
addColumn(new ComponentRenderer<>(ActionStatusEntry::getActionsLayout)).setHeader("Actions").setAutoWidth(true).setFlexGrow(0);
addColumn(new ComponentRenderer<>(ActionStatusEntry::getForceQuitLayout)).setHeader("Force Quit").setAutoWidth(true).setFlexGrow(0);
public TargetActionsHistoryLayout(HawkbitMgmtClient hawkbitMgmtClient) {
ActionStepsGrid actionStepsGrid = new ActionStepsGrid(hawkbitMgmtClient);
targetActionsHistory = new TargetActionsHistory(hawkbitMgmtClient, actionStepsGrid);
add(targetActionsHistory);
add(actionStepsGrid);
}
private void setItem(final MgmtTarget target) {
this.target = target;
public void setItem(MgmtTarget target) {
targetActionsHistory.setItem(target);
}
private List<ActionStatusEntry> fetchActions() {
return hawkbitClient.getTargetRestApi().getActionHistory(target.getControllerId(), null, 0, 30, null)
.getBody()
.getContent()
.stream()
.map(action -> new ActionStatusEntry(action, () -> setItems(fetchActions())))
.filter(value -> value.action != null)
.toList();
}
public static class ActionStepsGrid extends Grid<ActionStepsGrid.ActionStepEntry> {
@Override
protected void onAttach(AttachEvent attachEvent) {
setItems(fetchActions());
}
private final transient HawkbitMgmtClient hawkbitClient;
private transient MgmtTarget target;
private transient Long actionId;
private class ActionStatusEntry {
private ActionStepsGrid(final HawkbitMgmtClient hawkbitClient) {
final MgmtAction action;
final Runnable onUpdate;
MgmtDistributionSet distributionSet;
this.hawkbitClient = hawkbitClient;
setWidthFull();
addColumn(new ComponentRenderer<>(ActionStepEntry::getStatusIcon)).setHeader(STATUS).setAutoWidth(true)
.setFlexGrow(0);
addColumn(Utils.localDateTimeRenderer(ActionStepEntry::getLastModifiedAt)).setHeader("Time")
.setAutoWidth(true).setFlexGrow(0).setComparator(ActionStepEntry::getLastModifiedAt);
addColumn(new ComponentRenderer<>(ActionStepEntry::getMessage)).setHeader("Message").setAutoWidth(true).setFlexGrow(0);
}
public ActionStatusEntry(final MgmtAction mgmtAction, final Runnable onUpdate) {
this.action = hawkbitClient.getActionRestApi().getAction(mgmtAction.getId()).getBody();
this.onUpdate = onUpdate;
if (action == null) {
log.error("Unable to fetch the action with id : {}", mgmtAction.getId());
return;
private List<ActionStepEntry> fetchActionSteps() {
if (actionId == null) {
return new ArrayList<>();
}
this.action.getLink("distributionset").ifPresent(link -> {
try {
Long dsId = Long.parseLong(link.getHref().substring(link.getHref().lastIndexOf("/") + 1));
this.distributionSet = hawkbitClient.getDistributionSetRestApi().getDistributionSet(dsId).getBody();
} catch (NumberFormatException e) {
log.error("Error parsing distribution set ID", e);
return hawkbitClient.getTargetRestApi()
.getActionStatusList(target.getControllerId(), actionId, 0, 30, null).getBody().getContent()
.stream().map(ActionStepEntry::new)
.toList();
}
@Override
protected void onAttach(AttachEvent attachEvent) {
setItems(fetchActionSteps());
}
public void setActionId(Long id) {
actionId = id;
setItems(fetchActionSteps());
}
public void setTarget(MgmtTarget target) {
this.target = target;
actionId = null;
}
private static class ActionStepEntry extends Object {
final MgmtActionStatus status;
public ActionStepEntry(final MgmtActionStatus status) {
this.status = status;
}
public Long getLastModifiedAt() {
return status.getReportedAt();
}
public Component getStatusIcon() {
final HorizontalLayout layout = new HorizontalLayout();
final Icon icon;
switch (status.getType()) {
case FINISHED -> icon = Utils.iconColored(VaadinIcon.CHECK_CIRCLE, "Finished", "green");
case ERROR -> icon = Utils.iconColored(VaadinIcon.CLOSE_CIRCLE, "Error", "red");
case WARNING -> icon = Utils.iconColored(VaadinIcon.WARNING, "Warning", "orange");
case RUNNING -> icon = Utils.iconColored(VaadinIcon.ADJUST, "Running", "green");
case RETRIEVED -> icon = Utils.iconColored(VaadinIcon.CIRCLE_THIN, "Retrieved", "green");
case CANCELED -> icon = Utils.iconColored(VaadinIcon.CLOSE_CIRCLE_O, "Canceled", "gray");
case CANCELING -> icon = Utils.iconColored(VaadinIcon.CLOSE_CIRCLE, "Cancelling", "brown");
case DOWNLOAD -> icon = Utils.iconColored(VaadinIcon.CLOUD_DOWNLOAD_O, "Download", "teal");
case DOWNLOADED -> icon = Utils.iconColored(VaadinIcon.CLOUD_DOWNLOAD, "Downloaded", "purple");
case WAIT_FOR_CONFIRMATION ->
icon = Utils.iconColored(VaadinIcon.QUESTION_CIRCLE, "Wait for confirmation", "coral");
default -> icon = Utils.iconColored(VaadinIcon.CIRCLE_THIN, status.getType().getName().toLowerCase(),
"black");
}
});
}
private boolean isActive() {
return action.getStatus().equals(MgmtAction.ACTION_PENDING);
}
private boolean isCancelingOrCanceled() {
return action.getType().equals(MgmtAction.ACTION_CANCEL);
}
public Component getStatusIcon() {
final HorizontalLayout layout = new HorizontalLayout();
final Icon icon;
if (isActive()) {
if (isCancelingOrCanceled()) {
icon = Utils.tooltip(VaadinIcon.ADJUST.create(), "Pending Cancellation");
icon.setColor("red");
} else {
icon = Utils.tooltip(VaadinIcon.ADJUST.create(), "Pending Update");
icon.setColor("orange");
}
} else if (action.getType().equals(MgmtAction.ACTION_UPDATE)) {
icon = Utils.tooltip(VaadinIcon.CHECK_CIRCLE.create(), "Updated");
icon.setColor("green");
} else {
icon = Utils.tooltip(VaadinIcon.CLOSE_CIRCLE.create(), "Canceled");
icon.setColor("red");
icon.addClassNames(LumoUtility.IconSize.SMALL);
layout.add(icon);
layout.setWidth(50, Unit.PIXELS);
layout.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
return layout;
}
icon.addClassNames(LumoUtility.IconSize.SMALL);
layout.add(icon);
layout.setWidth(50, Unit.PIXELS);
layout.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
return layout;
}
public String getDistributionSetName() {
return Optional.ofNullable(distributionSet).map(MgmtDistributionSet::getName).orElse("Distribution Set not found");
}
public Instant getLastModifiedAt() {
return Instant.ofEpochMilli(action.getLastModifiedAt());
}
public Icon getForceTypeIcon() {
Icon icon = switch (action.getForceType()) {
case FORCED -> VaadinIcon.BOLT.create();
case TIMEFORCED -> VaadinIcon.USER_CLOCK.create();
case SOFT -> VaadinIcon.USER_CHECK.create();
case DOWNLOAD_ONLY -> VaadinIcon.DOWNLOAD.create();
};
return Utils.tooltip(icon, action.getForceType().getName());
}
public HorizontalLayout getActionsLayout() {
final HorizontalLayout actionsLayout = new HorizontalLayout();
actionsLayout.setSpacing(true);
final Button cancelButton = Utils.tooltip(new Button(VaadinIcon.CLOSE.create()), "Cancel Action");
if (isActive() && !isCancelingOrCanceled()) {
cancelButton.addClickListener(e -> {
String message = "Are you sure you want to cancel the action ?";
promptForConfirmAction(
message, onUpdate,
() -> hawkbitClient.getTargetRestApi().cancelAction(target.getControllerId(), action.getId(), false)).open();
});
} else {
cancelButton.setEnabled(false);
public VerticalLayout getMessage() {
return new VerticalLayout(status.getMessages().stream().map(Span::new).toArray(Span[]::new));
}
final Button forceButton = Utils.tooltip(new Button(VaadinIcon.BOLT.create()), "Force Action");
if (isActive() && !isCancelingOrCanceled() && action.getForceType() != MgmtActionType.FORCED) {
forceButton.addClickListener(e -> {
String message = "Are you sure you want to force the action ?";
promptForConfirmAction(
message, onUpdate, () -> {
MgmtActionRequestBodyPut setForced = new MgmtActionRequestBodyPut();
setForced.setForceType(MgmtActionType.FORCED);
hawkbitClient.getTargetRestApi().updateAction(target.getControllerId(), action.getId(), setForced);
}
).open();
});
} else {
forceButton.setEnabled(false);
}
actionsLayout.add(cancelButton, forceButton);
return actionsLayout;
}
public HorizontalLayout getForceQuitLayout() {
final HorizontalLayout forceQuitLayout = new HorizontalLayout();
forceQuitLayout.setSpacing(true);
forceQuitLayout.setPadding(true);
forceQuitLayout.setWidthFull();
forceQuitLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
final Button forceQuitButton = Utils.tooltip(new Button(VaadinIcon.CLOSE.create()), "Force Cancel");
forceQuitButton.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY_INLINE);
if (isActive() && isCancelingOrCanceled()) {
forceQuitButton.addClickListener(e -> {
String message = "Are you sure you want to force cancel the action ?";
promptForConfirmAction(
message, onUpdate,
() -> hawkbitClient.getTargetRestApi().cancelAction(target.getControllerId(), action.getId(), true)).open();
});
} else {
forceQuitButton.setEnabled(false);
}
forceQuitLayout.add(forceQuitButton);
return forceQuitLayout;
}
private static ConfirmDialog promptForConfirmAction(String message, Runnable refreshActions, Runnable actionConsumer) {
final ConfirmDialog dialog = new ConfirmDialog();
dialog.setHeader("Confirm Action");
dialog.setText(message);
dialog.setCancelable(true);
dialog.addCancelListener(event -> dialog.close());
dialog.setConfirmButtonTheme(ButtonVariant.LUMO_ERROR.getVariantName());
dialog.setConfirmText("Confirm");
dialog.addConfirmListener(event -> {
actionConsumer.run();
refreshActions.run();
dialog.close();
});
return dialog;
}
}
}
@@ -772,7 +703,7 @@ public class TargetView extends TableView<MgmtTarget, String> {
type.setWidthFull();
type.setEmptySelectionAllowed(true);
type.setItemLabelGenerator(item -> item == null ? "" : item.getName());
controllerId = Utils.textField(CONTROLLER_ID,e -> register.setEnabled(!e.getHasValue().isEmpty()));
controllerId = Utils.textField(FILTER, e -> register.setEnabled(!e.getHasValue().isEmpty()));
controllerId.focus();
name = Utils.textField(Constants.NAME);
name.setWidthFull();
@@ -812,7 +743,7 @@ public class TargetView extends TableView<MgmtTarget, String> {
request.setTargetType(type.getValue().getId());
}
hawkbitClient.getTargetRestApi().createTargets(
List.of(request))
List.of(request))
.getBody()
.stream()
.findFirst()
@@ -830,22 +761,21 @@ public class TargetView extends TableView<MgmtTarget, String> {
private final DateTimePicker forceTime = new DateTimePicker("Force Time");
private final Button assign = new Button("Assign");
private AssignDialog(final HawkbitMgmtClient hawkbitClient, Set<MgmtTarget> selectedTargets) {
private AssignDialog(final HawkbitMgmtClient hawkbitClient, Set<TargetWithDs> selectedTargets) {
super("Assign Distribution Set");
distributionSet = new Select<>(
"Distribution Set",
this::readyToAssign,
Optional.ofNullable(
hawkbitClient.getDistributionSetRestApi()
.getDistributionSets(null, 0, 30, Constants.NAME_ASC)
.getBody())
hawkbitClient.getDistributionSetRestApi()
.getDistributionSets(null, 0, 500, Constants.CREATED_AT_DESC)
.getBody())
.map(body -> body.getContent().toArray(new MgmtDistributionSet[0]))
.orElseGet(() -> new MgmtDistributionSet[0])
);
distributionSet.setRequiredIndicatorVisible(true);
distributionSet.setItemLabelGenerator(distributionSetO ->
distributionSetO.getName() + ":" + distributionSetO.getVersion());
distributionSet.setItemLabelGenerator(distributionSetO -> distributionSetO.getName() + ":" + distributionSetO.getVersion());
distributionSet.setWidthFull();
actionType = Utils.actionTypeControls(forceTime);
@@ -874,7 +804,7 @@ public class TargetView extends TableView<MgmtTarget, String> {
}
}
private void addAssignClickListener(final HawkbitMgmtClient hawkbitClient, final Set<MgmtTarget> selectedTargets) {
private void addAssignClickListener(final HawkbitMgmtClient hawkbitClient, final Set<TargetWithDs> selectedTargets) {
assign.addClickListener(e -> {
close();
@@ -885,9 +815,7 @@ public class TargetView extends TableView<MgmtTarget, String> {
request.setType(actionType.getValue());
if (actionType.getValue() == MgmtActionType.TIMEFORCED) {
request.setForcetime(
forceTime.isEmpty() ?
System.currentTimeMillis() :
forceTime.getValue().toEpochSecond(ZoneOffset.UTC) * 1000);
forceTime.isEmpty() ? System.currentTimeMillis() : forceTime.getValue().toEpochSecond(ZoneOffset.UTC) * 1000);
}
requests.add(request);
@@ -945,9 +873,31 @@ public class TargetView extends TableView<MgmtTarget, String> {
private TargetStatusCell(final MgmtTarget target) {
final MgmtPollStatus pollStatus = target.getPollStatus();
add(pollStatusIconMapper(pollStatus));
setWidth(25, Unit.PIXELS);
}
private Icon pollStatusIconMapper(MgmtPollStatus pollStatus) {
final 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 " + Utils.durationFromMillis(pollStatus
.getLastRequestAt()));
} else {
pollIcon = Utils.tooltip(VaadinIcon.CLOCK.create(), "In Time " + Utils.durationFromMillis(pollStatus.getLastRequestAt()));
}
pollIcon.addClassNames(LumoUtility.IconSize.SMALL);
return pollIcon;
}
}
private static class TargetUpdateStatusCell extends HorizontalLayout {
private TargetUpdateStatusCell(final MgmtTarget target) {
final String targetUpdateStatus = Optional.ofNullable(target.getUpdateStatus()).orElse("unknown");
add(pollStatusIconMapper(pollStatus), targetUpdateStatusMapper(targetUpdateStatus));
setWidth(50, Unit.PIXELS);
add(targetUpdateStatusMapper(targetUpdateStatus));
setWidth(25, Unit.PIXELS);
}
private Icon targetUpdateStatusMapper(final String targetUpdateStatus) {
@@ -967,23 +917,39 @@ public class TargetView extends TableView<MgmtTarget, String> {
default -> "blue";
};
final Icon statusIcon = Utils.tooltip(icon.create(), targetUpdateStatus);
final Icon statusIcon = Utils.tooltip(icon.create(), targetUpdateStatus.replace("_", " "));
statusIcon.setColor(color);
statusIcon.addClassNames(LumoUtility.IconSize.SMALL);
return statusIcon;
}
}
private Icon pollStatusIconMapper(MgmtPollStatus pollStatus) {
final 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;
// todo change /targets api to reduce api calls ?
@EqualsAndHashCode(callSuper = true)
public static class TargetWithDs extends MgmtTarget {
TargetWithDs() {
super();
}
Optional<MgmtDistributionSet> ds;
static ObjectMapper objectMapper = new ObjectMapper();
public static TargetWithDs from(final HawkbitMgmtClient hawkbitClient, MgmtTarget target) {
TargetWithDs targetWithDs = objectMapper.convertValue(target, TargetWithDs.class);
targetWithDs.ds = Optional.ofNullable(hawkbitClient.getTargetRestApi().getInstalledDistributionSet(targetWithDs
.getControllerId())
.getBody());
return targetWithDs;
}
public String getDsVersion() {
return ds.map(MgmtDistributionSet::getVersion).orElse("");
}
public String getDsName() {
return ds.map(MgmtDistributionSet::getName).orElse("");
}
}
}

View File

@@ -28,13 +28,19 @@ import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.theme.lumo.LumoUtility;
import org.springframework.util.ObjectUtils;
public class Filter extends Div {
private transient Rsql rsql;
private final transient Rsql secondaryRsql;
private final transient Rsql primaryRsql;
private final transient Div filtersDiv;
public Filter(final Consumer<String> changeListener, final Rsql primaryRsql, final Rsql secondaryOptionalRsql) {
rsql = primaryRsql;
this.primaryRsql = primaryRsql;
secondaryRsql = secondaryOptionalRsql;
final HorizontalLayout layout = new HorizontalLayout();
@@ -42,7 +48,7 @@ public class Filter extends Div {
addClassNames(LumoUtility.Padding.Horizontal.NONE, LumoUtility.Padding.Vertical.SMALL,
LumoUtility.BoxSizing.BORDER);
final Div filtersDiv = new Div();
filtersDiv = new Div();
filtersDiv.setWidthFull();
filtersDiv.add(primaryRsql.components());
filtersDiv.addClassName(LumoUtility.Gap.SMALL);
@@ -70,11 +76,7 @@ public class Filter extends Div {
final Button toggleBtn = Utils.tooltip(new Button(VaadinIcon.FLIP_V.create()), "Toggle Search");
toggleBtn.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
toggleBtn.addClickListener(e -> {
filtersDiv.removeAll();
synchronized (this) { // toggle
rsql = rsql == primaryRsql ? secondaryOptionalRsql : primaryRsql;
}
filtersDiv.add(rsql.components());
toggle();
changeListener.accept(primaryRsql.filter());
});
layout.add(toggleBtn);
@@ -84,34 +86,59 @@ public class Filter extends Div {
changeListener.accept(primaryRsql.filter());
}
public static String filter(final Map<String, Object> keyToValues) {
final Map<String, Object> normalized =
new HashMap<>(keyToValues)
.entrySet()
.stream()
.filter(e -> {
if (e.getValue() instanceof Optional<?> opt) {
return opt.isPresent();
} else {
return e.getValue() != null;
}
})
.filter(e -> !(e.getValue() instanceof Collection<?> coll && coll.isEmpty()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
private void toggle() {
// toggle
filtersDiv.removeAll();
synchronized (this) {
rsql = rsql == primaryRsql ? secondaryRsql : primaryRsql;
}
filtersDiv.add(rsql.components());
}
public void setFilter(String string, boolean allowToggle) {
var otherFilter = rsql == primaryRsql ? secondaryRsql : primaryRsql;
Stream<Filter.Rsql> rsqlFIlter;
// logic to find the filter to use
if (allowToggle) {
rsqlFIlter = Stream.of(this.rsql);
} else {
rsqlFIlter = Stream.of(this.rsql, otherFilter);
}
rsqlFIlter.filter(RsqlRw.class::isInstance).findFirst().map(RsqlRw.class::cast).ifPresent(f -> {
if (f == otherFilter) {
toggle();
}
f.setFilter(string);
});
}
public static String filter(final Map<Object, Object> keyToValues) {
final Map<Object, Object> normalized = new HashMap<>(keyToValues)
.entrySet()
.stream()
.map(e -> {
if (e.getValue() instanceof Optional<?> opt) {
e.setValue(opt.orElse(null));
}
return e;
})
.filter(e -> !ObjectUtils.isEmpty(e))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
if (normalized.isEmpty()) {
return null;
} else if (normalized.size() == 1) {
return normalized.entrySet().stream()
.findFirst().map(e -> filter(e.getKey(), e.getValue())).orElse(null); // never return null!
} else {
final StringBuilder sb = new StringBuilder();
normalized.forEach((k, v) -> {
if (v instanceof Collection<?>) {
sb.append('(').append(filter(k, v)).append(')');
} else if (v instanceof Optional<?> opt) {
sb.append(filter(k, opt.get()));
} else {
sb.append(filter(k, v));
if (k instanceof Collection<?> keyList) {
sb.append('(').append(
keyList.stream().map(subKey -> filter((String) subKey, v))
.collect(Collectors.joining(" or "))).append(")");
} else if (k instanceof String key) {
if (v instanceof Collection<?>) {
sb.append('(').append(filter(key, v)).append(')');
} else {
sb.append(filter(key, v));
}
}
sb.append(';');
});
@@ -158,4 +185,9 @@ public class Filter extends Div {
String filter();
}
public interface RsqlRw {
void setFilter(String filter);
}
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2025 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.ui.simple.view.util;
import com.vaadin.flow.component.card.Card;
import com.vaadin.flow.component.card.CardVariant;
import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.Span;
public class LinkedTextArea extends Div {
String queryPrefix;
Card card;
public LinkedTextArea(String title, String queryPrefix) {
super();
card = new Card();
card.setTitle(title);
this.queryPrefix = queryPrefix;
}
public void setValueWithLink(String value, String query) {
var span = new Span(value);
span.setWhiteSpace(WhiteSpace.PRE_WRAP);
card.add(span);
card.addThemeVariants(CardVariant.LUMO_ELEVATED);
if (query != null) {
var a = new Anchor(queryPrefix + query, card);
a.addClassName("nocolor");
add(a);
} else {
add(card);
}
}
}

View File

@@ -10,6 +10,7 @@
package org.eclipse.hawkbit.ui.simple.view.util;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiFunction;
@@ -47,7 +48,11 @@ public class SelectionGrid<T, ID> extends Grid<T> {
final Stream<T> fetch = queryFn.apply(query, rsqlFilter);
final Set<T> selected = getSelectedItems();
if (selected == null || selected.isEmpty()) {
return fetch;
final List<T> fetchList = fetch.toList();
if (fetchList.size() == 1) {
this.setDetailsVisible(fetchList.get(0), true);
}
return fetchList.stream();
} else {
final Set<ID> selectedIds = new HashSet<>();
selected.forEach(next -> selectedIds.add(entityRepresentation.idFn.apply(next)));
@@ -61,10 +66,11 @@ public class SelectionGrid<T, ID> extends Grid<T> {
} // else externally managed
}
public void setRsqlFilter(final String rsqlFilter) {
public void setRsqlFilter(final String rsqlFilter, boolean refreshGrid) {
if (!Objects.equals(this.rsqlFilter, rsqlFilter)) {
this.rsqlFilter = rsqlFilter;
refreshGrid(true);
if (refreshGrid)
refreshGrid(true);
}
}

View File

@@ -15,6 +15,7 @@ import java.util.function.Function;
import java.util.stream.Stream;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.html.Div;
@@ -28,17 +29,21 @@ import com.vaadin.flow.component.splitlayout.SplitLayoutVariant;
import com.vaadin.flow.data.provider.Query;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.function.SerializableFunction;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.NavigationTrigger;
import com.vaadin.flow.theme.lumo.LumoUtility;
import org.eclipse.hawkbit.ui.simple.view.Constants;
@SuppressWarnings("java:S119") // better readability
public class TableView<T, ID> extends Div implements Constants {
public class TableView<T, ID> extends Div implements Constants, BeforeEnterObserver {
private static final String COLOR = "color";
private static final String VAR_LUMO_SECONDARY_TEXT_COLOR = "var(--lumo-secondary-text-color)";
private static final String VAR_LUMO_PRIMARY_COLOR = "var(--lumo-primary-color)";
private static final int DEFAULT_OPEN_POSITION_SIZE = 50;
private static final String QUERY_PARAM_FILTER = "q";
protected SelectionGrid<T, ID> selectionGrid;
private final Filter filter;
@@ -83,8 +88,14 @@ public class TableView<T, ID> extends Div implements Constants {
filter = new Filter(
(rsqlFilter) -> {
selectionGrid.setRsqlFilter(rsqlFilter);
closeDetailsPanel();
if (rsqlFilter != null) {
var queryParameters = UI.getCurrent().getActiveViewLocation()
.getQueryParameters()
.merging(QUERY_PARAM_FILTER, rsqlFilter);
UI.getCurrent().navigate(this.getClass(), queryParameters);
}
selectionGrid.setRsqlFilter(rsqlFilter, true);
}, rsql, alternativeRsql
);
gridLayout = new VerticalLayout(filter, splitLayout);
@@ -147,4 +158,16 @@ public class TableView<T, ID> extends Div implements Constants {
return button;
};
}
@Override
public void beforeEnter(BeforeEnterEvent event) {
var params = event.getLocation().getQueryParameters();
params.getSingleParameter(QUERY_PARAM_FILTER)
.ifPresent(f -> {
var newPage = event.getTrigger() == NavigationTrigger.UI_NAVIGATE;
selectionGrid.setRsqlFilter(f, newPage);
filter.setFilter(f, newPage);
});
}
}

View File

@@ -9,14 +9,31 @@
*/
package org.eclipse.hawkbit.ui.simple.view.util;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.ToLongFunction;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.IconFactory;
import com.vaadin.flow.data.provider.QuerySortOrder;
import com.vaadin.flow.data.provider.SortDirection;
import com.vaadin.flow.data.renderer.LocalDateTimeRenderer;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
import org.eclipse.hawkbit.ui.simple.view.Constants;
@@ -44,6 +61,7 @@ import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.theme.lumo.LumoUtility;
@Slf4j
public class Utils {
private Utils() {
@@ -117,7 +135,8 @@ public class Utils {
return layout;
}
private static <T, ID> ConfirmDialog promptForDeleteConfirmation(Function<SelectionGrid<T, ID>, CompletionStage<Void>> removeHandler, SelectionGrid<T, ID> selectionGrid) {
private static <T, ID> ConfirmDialog promptForDeleteConfirmation(Function<SelectionGrid<T, ID>, CompletionStage<Void>> removeHandler,
SelectionGrid<T, ID> selectionGrid) {
final ConfirmDialog dialog = new ConfirmDialog();
dialog.setHeader("Confirm Deletion");
dialog.setText("Are you sure you want to delete the selected items? This action cannot be undone.");
@@ -139,7 +158,7 @@ public class Utils {
public static <T> void remove(final Collection<T> remove, final Set<T> from, final Function<T, ?> idFn) {
remove.forEach(toRemove -> {
final Object id = idFn.apply(toRemove);
for (final Iterator<T> i = from.iterator(); i.hasNext(); ) {
for (final Iterator<T> i = from.iterator(); i.hasNext();) {
if (idFn.apply(i.next()).equals(id)) {
i.remove();
}
@@ -175,27 +194,31 @@ public class Utils {
return component;
}
public static Icon iconColored(final IconFactory component, final String text, final String color) {
var icon = tooltip(component.create(), text);
icon.setColor(color);
return icon;
}
public static Select<MgmtActionType> actionTypeControls(DateTimePicker forceTime) {
Select<MgmtActionType> actionType = new Select<>();
actionType.setLabel(Constants.ACTION_TYPE);
actionType.setItems(MgmtActionType.values());
actionType.setValue(MgmtActionType.FORCED);
final ComponentRenderer<Component, MgmtActionType> actionTypeRenderer = new ComponentRenderer<>(actionTypeO ->
switch (actionTypeO) {
case SOFT -> new Text(Constants.SOFT);
case FORCED -> new Text(Constants.FORCED);
case DOWNLOAD_ONLY -> new Text(Constants.DOWNLOAD_ONLY);
case TIMEFORCED -> forceTime;
});
final ComponentRenderer<Component, MgmtActionType> actionTypeRenderer = new ComponentRenderer<>(actionTypeO -> switch (actionTypeO) {
case SOFT -> new Text(Constants.SOFT);
case FORCED -> new Text(Constants.FORCED);
case DOWNLOAD_ONLY -> new Text(Constants.DOWNLOAD_ONLY);
case TIMEFORCED -> forceTime;
});
actionType.addValueChangeListener(e -> actionType.setRenderer(actionTypeRenderer));
actionType.setItemLabelGenerator(startTypeO ->
switch (startTypeO) {
case SOFT -> Constants.SOFT;
case FORCED -> Constants.FORCED;
case DOWNLOAD_ONLY -> Constants.DOWNLOAD_ONLY;
case TIMEFORCED -> "Time Forced at " + (forceTime.isEmpty() ? "" : " " + forceTime.getValue());
});
actionType.setItemLabelGenerator(startTypeO -> switch (startTypeO) {
case SOFT -> Constants.SOFT;
case FORCED -> Constants.FORCED;
case DOWNLOAD_ONLY -> Constants.DOWNLOAD_ONLY;
case TIMEFORCED -> "Time Forced at " + (forceTime.isEmpty() ? "" : " " + forceTime.getValue());
});
actionType.setWidthFull();
return actionType;
}
@@ -235,4 +258,58 @@ public class Utils {
super.close();
}
}
private static ZoneId getZoneId() {
CompletableFuture<ZoneId> zoneId = new CompletableFuture<>();
UI.getCurrent().getPage().retrieveExtendedClientDetails(details -> {
zoneId.complete(ZoneId.of(details.getTimeZoneId()));
});
try {
return zoneId.get(1, TimeUnit.SECONDS);
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
} catch (TimeoutException | ExecutionException ignored) {
log.warn("failed to get zone");
}
return ZoneId.systemDefault();
}
public static <G> LocalDateTimeRenderer<G> localDateTimeRenderer(ToLongFunction<G> f) {
return new LocalDateTimeRenderer<>((e) -> LocalDateTime.ofInstant(Instant.ofEpochMilli(f.applyAsLong(e)), getZoneId()),
() -> DateTimeFormatter.ofLocalizedDateTime(
FormatStyle.SHORT,
FormatStyle.MEDIUM).withLocale(UI.getCurrent().getLocale()));
}
public static String localDateTimeFromTs(long timestamp) {
return LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), getZoneId()).format(DateTimeFormatter.ofLocalizedDateTime(
FormatStyle.SHORT,
FormatStyle.MEDIUM).withLocale(UI.getCurrent().getLocale()));
}
public static String getSortParam(List<QuerySortOrder> querySortOrders) {
return getSortParam(querySortOrders, null);
}
public static String getSortParam(List<QuerySortOrder> querySortOrders, String defaultSort) {
if (!querySortOrders.isEmpty()) {
QuerySortOrder firstSort = querySortOrders.get(0);
String order = firstSort.getDirection() == SortDirection.ASCENDING ? "asc" : "desc";
return String.format("%s:%s", firstSort.getSorted(), order);
}
return defaultSort;
}
public static String durationFromMillis(Long time) {
var duration = Duration.between(Instant.ofEpochMilli(time), Instant.now());
var day = duration.toDaysPart();
if (day > 2) {
return day + "d";
}
return duration.withNanos(0).toString()
.substring(2)
.replaceFirst("(^\\d+[HMS]\\d*M*)", "$1")
.toLowerCase();
}
}

View File

@@ -26,5 +26,7 @@ spring.mustache.check-template-location=false
vaadin.launch-browser=true
# To improve the performance during development.
# For more information https://vaadin.com/docs/flow/spring/tutorial-spring-configuration.html#special-configuration-parameters
vaadin.whitelisted-packages=com.vaadin,org.vaadin,dev.hilla,org.eclipse.hawkbit
vaadin.allowed-packages=com.vaadin,org.vaadin,dev.hilla,org.eclipse.hawkbit
spring.application.name=Simple-UI
server.servlet.session.persistent=false
### Vaadin end ###