SimpleUI: support target's tags edition, display installed & assigned distribution sets, and action history (#2409)

* simple-ui: fix flaky OAuth2TokenManager bean creation

* simple-ui: add Assigned / installed tab

This is inspired by the original hawkbit interface; however, as we've got more space it merges both Assigned and Installed into the same tab

* simple-ui: add assign and unassign to targets, and create tags

* simple-ui: add action history tab

This allows tracking the actions, see their status, and cancel / force / force cancel them; just like the previous ui

This has some limitations though; the previous UI had access to the Action's main class, whereas we're dealing with the rest dto; hence we're missing some information to make the ui simpler to create.

* use textblock with generic line-separator

* fix tags not shown due to missing badge import

* improve the action's status by combining the four statuses into one icon
This commit is contained in:
Mohamed Zenadi
2025-06-04 09:47:38 +02:00
committed by GitHub
parent 883504df84
commit d3341f7c73
7 changed files with 500 additions and 19 deletions

View File

@@ -1,5 +1,6 @@
bundles
frontend
src/main/frontend/*
!src/main/frontend/themes/
node_modules
package.json
package-lock.json

View File

@@ -0,0 +1 @@
/* Import your application global css files here or add the styles directly to this file */

View File

@@ -0,0 +1,3 @@
{
"lumoImports": ["badge"]
}

View File

@@ -13,6 +13,7 @@ import java.util.function.Supplier;
import feign.FeignException;
import lombok.Getter;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtActionRestApi;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtDistributionSetRestApi;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtDistributionSetTagRestApi;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtDistributionSetTypeRestApi;
@@ -45,6 +46,7 @@ public class HawkbitMgmtClient {
private final MgmtTargetFilterQueryRestApi targetFilterQueryRestApi;
private final MgmtRolloutRestApi rolloutRestApi;
private final MgmtTenantManagementRestApi tenantManagementRestApi;
private final MgmtActionRestApi actionRestApi;
HawkbitMgmtClient(final Tenant tenant, final HawkbitClient hawkbitClient) {
this.tenant = tenant;
@@ -61,6 +63,7 @@ public class HawkbitMgmtClient {
targetFilterQueryRestApi = service(MgmtTargetFilterQueryRestApi.class);
rolloutRestApi = service(MgmtRolloutRestApi.class);
tenantManagementRestApi = service(MgmtTenantManagementRestApi.class);
actionRestApi = service(MgmtActionRestApi.class);
}
boolean hasSoftwareModulesRead() {

View File

@@ -51,7 +51,7 @@ import java.util.function.Function;
import static feign.Util.ISO_8859_1;
import static java.util.Collections.emptyList;
@Theme(themeClass = Lumo.class)
@Theme("hawkbit")
@PWA(name = "hawkBit UI", shortName = "hawkBit UI")
@SpringBootApplication
@Import(FeignClientsConfiguration.class)

View File

@@ -9,7 +9,7 @@
*/
package org.eclipse.hawkbit.ui.simple.security;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
@@ -28,7 +28,7 @@ import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
@ConditionalOnBean(name = "hawkbitOAuth2ClientCustomizer")
@ConditionalOnProperty(prefix = "hawkbit.server.security.oauth2.client", name = "enabled")
public class OAuth2TokenManager {
private final OAuth2AuthorizedClientService clientService;

View File

@@ -9,6 +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;
@@ -17,43 +20,55 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.time.ZoneOffset;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jakarta.annotation.security.RolesAllowed;
import com.vaadin.flow.component.AttachEvent;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentEventListener;
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.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.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;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.Input;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.select.Select;
import com.vaadin.flow.component.tabs.TabSheet;
import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.shared.Registration;
import com.vaadin.flow.theme.lumo.LumoUtility;
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.distributionset.MgmtActionType;
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.tag.MgmtTag;
import org.eclipse.hawkbit.mgmt.json.model.tag.MgmtTagRequestBodyPut;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetRequestBody;
import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQuery;
@@ -65,8 +80,9 @@ import org.eclipse.hawkbit.ui.simple.view.util.Filter;
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;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.util.ObjectUtils;
import java.util.stream.Collectors;
@PageTitle("Targets")
@Route(value = "targets", layout = MainLayout.class)
@@ -92,13 +108,14 @@ public class TargetView extends TableView<MgmtTarget, String> {
grid.addColumn(MgmtTarget::getTargetTypeName).setHeader(Constants.TYPE).setAutoWidth(true);
grid.setItemDetailsRenderer(new ComponentRenderer<>(
() -> new TargetDetails(hawkbitClient), TargetDetails::setItem));
() -> new TargetDetailedView(hawkbitClient), TargetDetailedView::setItem));
}
},
(query, filter) -> hawkbitClient.getTargetRestApi()
.getTargets(
query.getOffset(), query.getPageSize(), Constants.NAME_ASC,
filter)
filter
)
.getBody()
.getContent()
.stream(),
@@ -107,7 +124,8 @@ public class TargetView extends TableView<MgmtTarget, String> {
selectionGrid.getSelectedItems().forEach(toDelete ->
hawkbitClient.getTargetRestApi().deleteTarget(toDelete.getControllerId()));
return CompletableFuture.completedFuture(null);
});
}
);
Function<SelectionGrid<MgmtTarget, String>, CompletionStage<Void>> assignHandler = source -> new AssignDialog(
hawkbitClient, source.getSelectedItems()).result();
@@ -161,7 +179,8 @@ public class TargetView extends TableView<MgmtTarget, String> {
"controllerid", controllerId.getOptionalValue(),
"targettype.name", type.getSelectedItems().stream().map(MgmtTargetType::getName)
.toList(),
"tag", tag.getSelectedItems()));
"tag", tag.getSelectedItems()
));
}
}
@@ -298,6 +317,34 @@ public class TargetView extends TableView<MgmtTarget, String> {
}
}
private static class TargetDetailedView extends TabSheet {
private final TargetDetails targetDetails;
private final TargetAssignedInstalled targetAssignedInstalled;
private final TargetTags targetTags;
private final TargetActions targetActions;
private TargetDetailedView(HawkbitMgmtClient hawkbitClient) {
targetDetails = new TargetDetails(hawkbitClient);
targetAssignedInstalled = new TargetAssignedInstalled(hawkbitClient);
targetTags = new TargetTags(hawkbitClient);
targetActions = new TargetActions(hawkbitClient);
setWidthFull();
add("Details", targetDetails);
add("Assigned / Installed", targetAssignedInstalled);
add("Tags", targetTags);
add("Action History", targetActions);
}
private void setItem(final MgmtTarget target) {
this.targetDetails.setItem(target);
this.targetAssignedInstalled.setItem(target);
this.targetTags.setItem(target);
this.targetActions.setItem(target);
}
}
private static class TargetDetails extends FormLayout {
private final transient HawkbitMgmtClient hawkbitClient;
@@ -308,6 +355,7 @@ public class TargetView extends TableView<MgmtTarget, String> {
private final TextField lastModifiedAt = Utils.textField(Constants.LAST_MODIFIED_AT);
private final TextField securityToken = Utils.textField(Constants.SECURITY_TOKEN);
private final TextArea targetAttributes = new TextArea(Constants.ATTRIBUTES);
private MgmtTarget target;
private TargetDetails(HawkbitMgmtClient hawkbitClient) {
this.hawkbitClient = hawkbitClient;
@@ -316,7 +364,8 @@ public class TargetView extends TableView<MgmtTarget, String> {
description,
createdBy, createdAt,
lastModifiedBy, lastModifiedAt,
securityToken, targetAttributes)
securityToken, targetAttributes
)
.forEach(field -> {
field.setReadOnly(true);
add(field);
@@ -327,6 +376,11 @@ public class TargetView extends TableView<MgmtTarget, String> {
}
private void setItem(final MgmtTarget target) {
this.target = target;
}
@Override
protected void onAttach(AttachEvent attachEvent) {
description.setValue(target.getDescription() == null ? "N/A" : target.getDescription());
createdBy.setValue(target.getCreatedBy());
createdAt.setValue(new Date(target.getCreatedAt()).toString());
@@ -335,14 +389,384 @@ public class TargetView extends TableView<MgmtTarget, String> {
securityToken.setValue(target.getSecurityToken());
var response = hawkbitClient.getTargetRestApi().getAttributes(target.getControllerId());
if (response.getStatusCode().is2xxSuccessful()) {
targetAttributes.setValue(Objects.requireNonNullElse(response.getBody(), Collections.emptyMap()).entrySet().stream().map(entry -> entry.getKey() + ": " +
entry.getValue()).collect(Collectors.joining("\n")));
targetAttributes.setValue(Objects.requireNonNullElse(response.getBody(), Collections.emptyMap()).entrySet().stream()
.map(entry -> entry.getKey() + ": " +
entry.getValue()).collect(Collectors.joining("\n")));
} else {
targetAttributes.setValue("Error occurred fetching attributes from server: " + response.getStatusCode());
}
}
}
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 MgmtTarget target;
private TargetAssignedInstalled(HawkbitMgmtClient hawkbitClient) {
this.hawkbitClient = hawkbitClient;
assigned.setReadOnly(true);
installed.setReadOnly(true);
assigned.setWidthFull();
installed.setWidthFull();
add(assigned, installed);
setResponsiveSteps(new FormLayout.ResponsiveStep("0", 2));
}
private void setItem(final MgmtTarget target) {
this.target = target;
}
@Override
protected void onAttach(AttachEvent attachEvent) {
updateDistributionSetInfo(
() -> hawkbitClient.getTargetRestApi().getInstalledDistributionSet(target.getControllerId()),
installed
);
updateDistributionSetInfo(
() -> hawkbitClient.getTargetRestApi().getAssignedDistributionSet(target.getControllerId()),
assigned
);
}
private void updateDistributionSetInfo(Supplier<ResponseEntity<MgmtDistributionSet>> supplier, TextArea textArea) {
Optional.ofNullable(supplier.get())
.map(ResponseEntity<MgmtDistributionSet>::getBody)
.ifPresent(value -> {
final String description = """
Name: %s
Version: %s
%s
""".replaceAll("\n", System.lineSeparator());
textArea.setValue(description.formatted(
value.getName(),
value.getVersion(),
value.getModules().stream().map(module -> module.getTypeName() + ": " + module.getVersion())
.collect(Collectors.joining(System.lineSeparator()))
));
});
}
}
private static class TargetTags extends VerticalLayout {
private final transient HawkbitMgmtClient hawkbitClient;
private final ComboBox<MgmtTag> tagSelector = new ComboBox<>(TAG);
private final HorizontalLayout tagsArea = new HorizontalLayout();
private Registration changeListener;
private MgmtTarget target;
private TargetTags(HawkbitMgmtClient hawkbitClient) {
this.hawkbitClient = hawkbitClient;
setWidthFull();
setPadding(false);
setSpacing(true);
setAlignItems(FlexComponent.Alignment.STRETCH);
setJustifyContentMode(JustifyContentMode.CENTER);
final HorizontalLayout tagSelectorLayout = buildTagSelectionLayout(hawkbitClient);
add(tagSelectorLayout);
tagsArea.setWrap(true);
tagsArea.setWidthFull();
add(tagsArea);
}
private HorizontalLayout buildTagSelectionLayout(HawkbitMgmtClient hawkbitClient) {
final Button createTagButton = new Button("Create Tag");
createTagButton.addClickListener(event -> {
new CreateTagDialog(hawkbitClient, () -> tagSelector.setItems(fetchAvailableTags())).result();
});
tagSelector.setWidthFull();
tagSelector.setItemLabelGenerator(MgmtTag::getName);
tagSelector.setClearButtonVisible(true);
tagSelector.setItems(fetchAvailableTags());
final HorizontalLayout tagSelectorLayout = new HorizontalLayout(tagSelector, createTagButton);
tagSelectorLayout.setWidthFull();
tagSelectorLayout.setSpacing(true);
tagSelectorLayout.setAlignItems(Alignment.END);
tagSelectorLayout.setJustifyContentMode(JustifyContentMode.CENTER);
return tagSelectorLayout;
}
private void setItem(final MgmtTarget target) {
this.target = target;
}
@Override
protected void onAttach(AttachEvent attachEvent) {
if (changeListener != null) {
changeListener.remove();
}
changeListener = tagSelector.addValueChangeListener(event -> {
if (event.getValue() != null) {
hawkbitClient.getTargetTagRestApi().assignTarget(event.getValue().getId(), target.getControllerId());
refreshTargetTagsList(target);
tagSelector.clear();
}
});
refreshTargetTagsList(target);
}
private void refreshTargetTagsList(MgmtTarget target) {
tagsArea.removeAll();
final List<MgmtTag> tagList = Optional.ofNullable(hawkbitClient.getTargetRestApi().getTags(target.getControllerId()).getBody())
.orElse(Collections.emptyList());
for (MgmtTag tag : tagList) {
tagsArea.add(buildTargetTagBadge(tag, target.getControllerId()));
}
}
private Span buildTargetTagBadge(MgmtTag tag, String controllerId) {
Button clearButton = new Button(VaadinIcon.CLOSE_SMALL.create());
clearButton.addThemeVariants(ButtonVariant.LUMO_CONTRAST, ButtonVariant.LUMO_TERTIARY_INLINE);
clearButton.getStyle().set("margin-inline-start", "var(--lumo-space-xs)");
clearButton.getElement().setAttribute("aria-label", "Clear filter: " + tag.getName());
clearButton.getElement().setAttribute("title", "Clear filter: " + tag.getName());
Span pill = new Span();
pill.getElement().getThemeList().add("badge pill");
pill.getStyle().set("background-color", tag.getColour());
pill.getStyle().set("margin-inline-end", "var(--lumo-space-xs)");
Span badge = new Span(pill, new Span(tag.getName()), clearButton);
badge.getElement().getThemeList().add("badge contrast pill");
clearButton.addClickListener(event -> {
hawkbitClient.getTargetTagRestApi().unassignTarget(tag.getId(), controllerId);
badge.getElement().removeFromParent();
});
return badge;
}
private List<MgmtTag> fetchAvailableTags() {
List<MgmtTag> tags = new ArrayList<>();
int fetched = 0;
int offset = 0;
do {
List<MgmtTag> page = Optional.ofNullable(
hawkbitClient.getTargetTagRestApi().getTargetTags(offset, 50, Constants.NAME_ASC, null).getBody())
.map(PagedList::getContent)
.orElse(Collections.emptyList());
tags.addAll(page);
fetched = page.size();
offset += fetched;
} while (fetched > 0);
return tags;
}
}
private static class TargetActions extends Grid<TargetActions.ActionStatusEntry> {
private final transient HawkbitMgmtClient hawkbitClient;
private MgmtTarget target;
private TargetActions(HawkbitMgmtClient hawkbitClient) {
this.hawkbitClient = hawkbitClient;
setWidthFull();
addColumn(new ComponentRenderer<Component, ActionStatusEntry>(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<Component, ActionStatusEntry>(ActionStatusEntry::getForceTypeIcon)).setHeader("Type")
.setAutoWidth(true)
.setFlexGrow(0);
addColumn(new ComponentRenderer<Component, ActionStatusEntry>(ActionStatusEntry::getActionsLayout)).setHeader("Actions")
.setAutoWidth(true)
.setFlexGrow(0);
addColumn(new ComponentRenderer<Component, ActionStatusEntry>(ActionStatusEntry::getForceQuitLayout)).setHeader("Force Quit")
.setAutoWidth(true)
.setFlexGrow(0);
}
private void setItem(final MgmtTarget target) {
this.target = target;
}
private List<ActionStatusEntry> fetchActions() {
return this.hawkbitClient.getTargetRestApi().getActionHistory(target.getControllerId(), 0, 30, null, null)
.getBody()
.getContent()
.stream()
.map(action -> new ActionStatusEntry(action, () -> setItems(fetchActions())))
.filter(value -> value.action != null)
.collect(Collectors.toList());
}
@Override
protected void onAttach(AttachEvent attachEvent) {
setItems(fetchActions());
}
private class ActionStatusEntry {
MgmtAction action;
MgmtDistributionSet distributionSet;
Runnable onUpdate;
public ActionStatusEntry(MgmtAction mgmtAction, Runnable onUpdate) {
this.action = hawkbitClient.getActionRestApi().getAction(mgmtAction.getId()).getBody();
this.onUpdate = onUpdate;
if (action == null) {
LoggerFactory.getLogger(ActionStatusEntry.class).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) {
LoggerFactory.getLogger(ActionStatusEntry.class).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() {
HorizontalLayout layout = new HorizontalLayout();
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;
}
public String getDistributionSetName() {
return distributionSet != null ? distributionSet.getName() : "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() {
HorizontalLayout actionsLayout = new HorizontalLayout();
actionsLayout.setSpacing(true);
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);
}
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() {
HorizontalLayout forceQuitLayout = new HorizontalLayout();
forceQuitLayout.setSpacing(true);
forceQuitLayout.setPadding(true);
forceQuitLayout.setWidthFull();
forceQuitLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
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;
}
}
}
private static class RegisterDialog extends Utils.BaseDialog<Void> {
private final Select<MgmtTargetType> type;
@@ -362,12 +786,15 @@ public class TargetView extends TableView<MgmtTarget, String> {
.getTargetTypes(0, 30, Constants.NAME_ASC, null)
.getBody()
.getContent()
.toArray(new MgmtTargetType[0]));
.toArray(new MgmtTargetType[0])
);
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(
CONTROLLER_ID,
e -> register.setEnabled(!e.getHasValue().isEmpty())
);
controllerId.focus();
name = Utils.textField(Constants.NAME);
name.setWidthFull();
@@ -433,7 +860,8 @@ public class TargetView extends TableView<MgmtTarget, String> {
.getDistributionSets(0, 30, Constants.NAME_ASC, null)
.getBody())
.map(body -> body.getContent().toArray(new MgmtDistributionSet[0]))
.orElseGet(() -> new MgmtDistributionSet[0]));
.orElseGet(() -> new MgmtDistributionSet[0])
);
distributionSet.setRequiredIndicatorVisible(true);
distributionSet.setItemLabelGenerator(distributionSetO ->
distributionSetO.getName() + ":" + distributionSetO.getVersion());
@@ -490,7 +918,52 @@ public class TargetView extends TableView<MgmtTarget, String> {
}
}
private static class CreateTagDialog extends Utils.BaseDialog<Void> {
private final TextField name;
private final TextArea description = new TextArea(Constants.DESCRIPTION);
private CreateTagDialog(final HawkbitMgmtClient hawkbitClient, Runnable onSuccess) {
super("Create Tag");
FormLayout formLayout = new FormLayout();
formLayout.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 1));
final Button create = Utils.tooltip(new Button("Create"), "Create (Enter)");
final Button cancel = Utils.tooltip(new Button("Cancel"), "Cancel (Esc)");
Input colorInput = new Input();
colorInput.setType("color");
name = Utils.textField("Tag Name", e -> create.setEnabled(!e.getHasValue().isEmpty()));
formLayout.add(name);
formLayout.add(description);
formLayout.addFormItem(colorInput, "Color Selection");
add(formLayout);
create.setEnabled(false);
create.addClickShortcut(Key.ENTER);
create.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
create.addClickListener(e -> {
hawkbitClient.getTargetTagRestApi().createTargetTags(List.of(
new MgmtTagRequestBodyPut()
.setName(name.getValue())
.setDescription(description.getValue())
.setColour(colorInput.getValue())
));
onSuccess.run();
close();
});
cancel.addClickListener(e -> close());
cancel.addClickShortcut(Key.ESCAPE);
getFooter().add(cancel);
getFooter().add(create);
open();
}
}
private static class TargetStatusCell extends HorizontalLayout {
private TargetStatusCell(MgmtTarget target) {
MgmtPollStatus pollStatus = target.getPollStatus();
String targetUpdateStatus = Optional.ofNullable(target.getUpdateStatus()).orElse("unknown");