修复Action history 为空的问题
Some checks failed
Mark & close stale issues / stale (push) Has been cancelled
Vulnerability Scan / trivy-scan (1.0) (push) Has been cancelled
Vulnerability Scan / trivy-scan (master) (push) Has been cancelled
CodeQL Advanced / Analyze (java-kotlin) (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled

This commit is contained in:
2026-06-22 16:06:08 +08:00
parent 2a425196e6
commit a3eb952931
16 changed files with 4560 additions and 67 deletions

View File

@@ -52,6 +52,8 @@ public final class TargetActionsHistory extends Grid<TargetActionsHistory.Action
public TargetActionsHistory(final HawkbitMgmtClient hawkbitClient, TargetView.TargetActionsHistoryLayout.ActionStepsGrid actionStepsGrid) {
this.hawkbitClient = hawkbitClient;
setWidthFull();
setMinHeight("200px");
setAllRowsVisible(true);
addColumn(new ComponentRenderer<>(ActionStatusEntry::getStatusIcon)).setHeader(STATUS).setAutoWidth(true).setFlexGrow(0);
addColumn(ActionStatusEntry::getDistributionSetName).setHeader("Distribution Set").setAutoWidth(true);
addColumn(Utils.localDateTimeRenderer(ActionStatusEntry::getLastModifiedAt))
@@ -73,25 +75,60 @@ public final class TargetActionsHistory extends Grid<TargetActionsHistory.Action
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
public void loadData() {
if (target == null) return;
List<ActionStatusEntry> items;
try {
items = fetchActions();
} catch (Exception e) {
log.error("loadData: failed to fetch actions for target {}", target.getControllerId(), e);
setItems(List.of());
actionStepsGrid.setActionId(null);
return;
}
log.info("loadData: {} action entries for target {}", items.size(), target.getControllerId());
setItems(items);
items.stream().findFirst().ifPresentOrElse(e -> {
asSingleSelect().setValue(e);
actionStepsGrid.setActionId(e.action.getId());
}, () -> actionStepsGrid.setActionId(null));
}, () -> {
log.warn("loadData: no action entries to display");
actionStepsGrid.setActionId(null);
});
}
/**
* Called from parent's onAttach or tab-selection listener.
* Only triggers if a target has been set.
*/
public void loadIfReady() {
if (target != null) {
loadData();
}
}
private List<ActionStatusEntry> fetchActions() {
final var response = hawkbitClient.getTargetRestApi().getActionHistory(target.getControllerId(), null, 0, 30, null);
log.info("fetchActions: controllerId={}, httpStatus={}, body={}",
target.getControllerId(),
response.getStatusCode(),
response.getBody() != null ? "present (" + response.getBody().getContent().size() + " actions)" : "NULL");
if (response.getBody() == null) {
return List.of();
}
return response.getBody()
.getContent()
.stream()
.map(action -> {
try {
return new ActionStatusEntry(action, () -> setItems(fetchActions()));
} catch (Exception e) {
log.error("fetchActions: failed to create ActionStatusEntry for action id={}", action.getId(), e);
return null;
}
})
.filter(value -> value != null && value.action != null)
.toList();
}
protected class ActionStatusEntry {
@@ -101,20 +138,35 @@ public final class TargetActionsHistory extends Grid<TargetActionsHistory.Action
MgmtDistributionSet distributionSet;
public ActionStatusEntry(final MgmtAction mgmtAction, final Runnable onUpdate) {
this.action = hawkbitClient.getActionRestApi().getAction(mgmtAction.getId()).getBody();
log.info("ActionStatusEntry: fetching action id={}", mgmtAction.getId());
final var actionResp = hawkbitClient.getActionRestApi().getAction(mgmtAction.getId());
log.info("ActionStatusEntry: getAction(id={}) -> status={}, body={}",
mgmtAction.getId(),
actionResp.getStatusCode(),
actionResp.getBody() != null ? "present" : "NULL");
this.action = actionResp.getBody();
this.onUpdate = onUpdate;
if (action == null) {
log.error("Unable to fetch the action with id : {}", mgmtAction.getId());
return;
}
log.info("ActionStatusEntry: action id={} loaded, links={}",
action.getId(),
action.getLinks().stream().map(l -> l.getRel().value()).toList());
this.action.getLink("distributionset").ifPresent(link -> {
log.info("ActionStatusEntry: found distributionset link, href={}", link.getHref());
try {
Long dsId = Long.parseLong(link.getHref().substring(link.getHref().lastIndexOf("/") + 1));
this.distributionSet = hawkbitClient.getDistributionSetRestApi().getDistributionSet(dsId).getBody();
log.info("ActionStatusEntry: distributionSet loaded, name={}",
this.distributionSet != null ? this.distributionSet.getName() : "NULL");
} catch (NumberFormatException e) {
log.error("Error parsing distribution set ID", e);
}
});
if (this.action.getLink("distributionset").isEmpty()) {
log.warn("ActionStatusEntry: NO distributionset link found for action id={}", action.getId());
}
}
private boolean isActive() {

View File

@@ -127,6 +127,9 @@ public class DistributionSetView extends TableView<MgmtDistributionSet, Long> {
private final TextField name = Utils.textField("Name");
private static final java.util.regex.Pattern RSQL_OP =
java.util.regex.Pattern.compile("==|!=|=like=|=unlike=|=gt=|=ge=|=lt=|=le=|=in=|=out=");
private DistributionSetRawFilter() {
name.setPlaceholder("<rsql filter>");
}
@@ -138,13 +141,25 @@ public class DistributionSetView extends TableView<MgmtDistributionSet, Long> {
@Override
public String filter() {
return name.getOptionalValue().orElse(null);
return name.getOptionalValue().map(raw -> {
if (RSQL_OP.matcher(raw).find()) {
return raw;
}
// plain text: wrap as wildcard search on name and version
return "(name==*" + encodeRsqlValue(raw) + "*,version==*" + encodeRsqlValue(raw) + "*)";
}).orElse(null);
}
@Override
public void setFilter(String filter) {
name.setValue(filter);
}
private static String encodeRsqlValue(String value) {
return value.replace("\\", "\\\\")
.replace("*", "\\*")
.replace("\"", "\\\"");
}
}
private static class DistributionSetFilter implements Filter.Rsql {

View File

@@ -50,6 +50,7 @@ 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.Tab;
import com.vaadin.flow.component.tabs.TabSheet;
import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.component.textfield.TextField;
@@ -330,9 +331,24 @@ public final class TargetView extends TableView<TargetView.TargetWithDs, String>
return List.of(layout);
}
private static final java.util.regex.Pattern RSQL_OP =
java.util.regex.Pattern.compile("==|!=|=like=|=unlike=|=gt=|=ge=|=lt=|=le=|=in=|=out=");
@Override
public String filter() {
return textFilter.getOptionalValue().orElse(null);
return textFilter.getOptionalValue().map(raw -> {
if (RSQL_OP.matcher(raw).find()) {
return raw;
}
// plain text: wrap as wildcard search on controllerId and name
return "(controllerid==*" + encodeRsqlValue(raw) + "*,name==*" + encodeRsqlValue(raw) + "*)";
}).orElse(null);
}
private static String encodeRsqlValue(String value) {
return value.replace("\\", "\\\\")
.replace("*", "\\*")
.replace("\"", "\\\"");
}
@Override
@@ -352,9 +368,11 @@ public final class TargetView extends TableView<TargetView.TargetWithDs, String>
private final TargetTags targetTags;
private final TargetMetadata targetMetadata;
private final TargetActionsHistoryLayout targetActionsHistoryLayout;
private final TabSheet tabSheet;
private final Tab actionHistoryTab;
private TargetDetailedView(final HawkbitMgmtClient hawkbitClient) {
final TabSheet tabSheet = new TabSheet();
tabSheet = new TabSheet();
tabSheet.setWidthFull();
targetId = new Span();
targetDetails = new TargetDetails(hawkbitClient);
@@ -369,8 +387,16 @@ public final class TargetView extends TableView<TargetView.TargetWithDs, String>
tabSheet.add("Assigned / Installed", targetAssignedInstalled);
tabSheet.add("Tags", targetTags);
tabSheet.add("Metadata", targetMetadata);
tabSheet.add("Action History", targetActionsHistoryLayout);
actionHistoryTab = tabSheet.add("Action History", targetActionsHistoryLayout);
add(tabSheet);
// Load action history data when the tab becomes visible.
// onAttach handles the first time; this listener handles subsequent tab switches.
tabSheet.addSelectedChangeListener(e -> {
if (e.getSelectedTab() == actionHistoryTab) {
targetActionsHistoryLayout.refresh();
}
});
}
private void setItem(final MgmtTarget target) {
@@ -685,6 +711,16 @@ public final class TargetView extends TableView<TargetView.TargetWithDs, String>
targetActionsHistory.setItem(target);
}
public void refresh() {
targetActionsHistory.loadData();
}
@Override
protected void onAttach(AttachEvent attachEvent) {
super.onAttach(attachEvent);
targetActionsHistory.loadIfReady();
}
public static class ActionStepsGrid extends Grid<ActionStepsGrid.ActionStepEntry> {
@Serial
@@ -715,11 +751,6 @@ public final class TargetView extends TableView<TargetView.TargetWithDs, String>
.toList();
}
@Override
protected void onAttach(AttachEvent attachEvent) {
setItems(fetchActionSteps());
}
public void setActionId(Long id) {
actionId = id;
setItems(fetchActionSteps());