feat: workload registry UI

This commit is contained in:
2026-08-07 09:06:36 +01:00
parent fd4c51f3db
commit 483053b9a2
7 changed files with 519 additions and 0 deletions
+73
View File
@@ -1054,6 +1054,79 @@ export const vulnerabilities = {
},
};
export type WorkloadKind = "container" | "unit";
export type WorkloadAction = "start" | "stop" | "restart";
/** One container or one systemd unit.
*
* `state` is deliberately not a shared vocabulary across the two kinds:
* containers report running/exited/paused/restarting/created, units report
* active/inactive/failed/activating. A failed unit and an exited container
* mean different things. */
export interface Workload {
kind: WorkloadKind;
id: string;
name: string;
state: string;
health?: string;
image?: string;
stack?: string;
ports?: string[];
restarts?: number;
started_at?: string;
protected: boolean;
}
export interface ServerWorkloads {
server_id: string;
hash?: string;
workloads: Workload[];
collected_at?: string;
/** false with no error means "Docker not in use here", which is not a
* fault. With an error it means installed but not responding. */
docker_ok: boolean;
docker_error?: string;
systemd_ok: boolean;
systemd_error?: string;
}
export interface WorkloadHit {
server_id: string;
workload: Workload;
}
export const workloads = {
forServer(serverId: string): Promise<ServerWorkloads> {
return request<ServerWorkloads>(`/servers/${serverId}/workloads`);
},
refresh(serverId: string): Promise<{ message: string }> {
return request<{ message: string }>(`/servers/${serverId}/workloads/refresh`, { method: "POST" });
},
control(serverId: string, kind: WorkloadKind, id: string, action: WorkloadAction): Promise<{ message: string }> {
return request<{ message: string }>(
`/servers/${serverId}/workloads/${encodeURIComponent(id)}/action`,
{ method: "POST", body: JSON.stringify({ kind, action }) },
);
},
logs(serverId: string, kind: WorkloadKind, id: string, tail = 500): Promise<{ text: string; truncated: boolean }> {
return request<{ text: string; truncated: boolean }>(
`/servers/${serverId}/workloads/${encodeURIComponent(id)}/logs?kind=${kind}&tail=${tail}`,
);
},
search(params?: { image?: string; stack?: string; state?: string }): Promise<WorkloadHit[]> {
const q = new URLSearchParams();
if (params?.image) q.set("image", params.image);
if (params?.stack) q.set("stack", params.stack);
if (params?.state) q.set("state", params.state);
const qs = q.toString();
return request<WorkloadHit[]>(`/workloads${qs ? `?${qs}` : ""}`);
},
};
// `request` already prefixes /api, so these paths do not repeat it.
export const licence = {
get(): Promise<LicenseInfo> {