feat(workload): global Containers tab + frontend client
Adds the user-visible piece of the Workload refactor:
- web/src/lib/types.ts — Workload, Container, ContainerView,
App, WorkloadKind, ContainerState
- web/src/lib/api.ts — listWorkloads, getWorkload,
listWorkloadContainers, setWorkloadAppID,
listContainers (with filter),
CRUD for apps
- web/src/lib/i18n/{en,ru}.json — nav.containers
- web/src/routes/+layout.svelte — "Containers" nav item between Stacks
and Deploy, IconContainer
- web/src/routes/containers/+page.svelte — global Containers table:
* filter chips for kind (project/stack/site) and state
* client-side search across workload name / role / image /
subdomain / container ID prefix
* Workload column links to the kind-specific detail page,
resolved through a one-time /api/workloads call to map
workload_id → ref_id
* existing /containers/stale route untouched
The page renders against the live database now — boot backfill
populated workload rows from existing projects/stacks/sites,
the deployer dual-writes containers on every deploy, and the
30s reconciler keeps the index in sync with `docker ps`.
This commit is contained in:
+69
-1
@@ -1,7 +1,10 @@
|
||||
import type {
|
||||
ApiEnvelope,
|
||||
App,
|
||||
Container,
|
||||
ContainerStats,
|
||||
ContainerStatsSample,
|
||||
ContainerView,
|
||||
SystemStats,
|
||||
SystemStatsSample,
|
||||
TopContainerSample,
|
||||
@@ -30,7 +33,9 @@ import type {
|
||||
BrowseResult,
|
||||
DnsZone,
|
||||
DnsRecordView,
|
||||
BackupInfo
|
||||
BackupInfo,
|
||||
Workload,
|
||||
WorkloadKind
|
||||
} from './types';
|
||||
|
||||
// ── Helpers ─────────────────────────────────────────────────────────
|
||||
@@ -1047,4 +1052,67 @@ export async function getStackLogs(
|
||||
return res.text();
|
||||
}
|
||||
|
||||
// ── Workloads ───────────────────────────────────────────────────────
|
||||
|
||||
export function listWorkloads(kind?: WorkloadKind, signal?: AbortSignal): Promise<Workload[]> {
|
||||
const path = kind ? `/api/workloads?kind=${encodeURIComponent(kind)}` : '/api/workloads';
|
||||
return get<Workload[]>(path, signal);
|
||||
}
|
||||
|
||||
export function getWorkload(id: string, signal?: AbortSignal): Promise<Workload> {
|
||||
return get<Workload>(`/api/workloads/${id}`, signal);
|
||||
}
|
||||
|
||||
export function listWorkloadContainers(id: string, signal?: AbortSignal): Promise<Container[]> {
|
||||
return get<Container[]>(`/api/workloads/${id}/containers`, signal);
|
||||
}
|
||||
|
||||
export function setWorkloadAppID(id: string, appID: string): Promise<Workload> {
|
||||
return request<Workload>(`/api/workloads/${id}/app`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ app_id: appID })
|
||||
});
|
||||
}
|
||||
|
||||
// ── Containers (global index) ───────────────────────────────────────
|
||||
|
||||
export interface ListContainersFilter {
|
||||
workload_id?: string;
|
||||
kind?: WorkloadKind;
|
||||
state?: string;
|
||||
app_id?: string;
|
||||
}
|
||||
|
||||
export function listContainers(filter: ListContainersFilter = {}, signal?: AbortSignal): Promise<ContainerView[]> {
|
||||
const params = new URLSearchParams();
|
||||
for (const [k, v] of Object.entries(filter)) {
|
||||
if (v) params.set(k, String(v));
|
||||
}
|
||||
const qs = params.toString();
|
||||
const path = qs ? `/api/containers?${qs}` : '/api/containers';
|
||||
return get<ContainerView[]>(path, signal);
|
||||
}
|
||||
|
||||
// ── Apps ────────────────────────────────────────────────────────────
|
||||
|
||||
export function listApps(signal?: AbortSignal): Promise<App[]> {
|
||||
return get<App[]>('/api/apps', signal);
|
||||
}
|
||||
|
||||
export function getApp(id: string, signal?: AbortSignal): Promise<App> {
|
||||
return get<App>(`/api/apps/${id}`, signal);
|
||||
}
|
||||
|
||||
export function createApp(data: { name: string; description?: string }): Promise<App> {
|
||||
return post<App>('/api/apps', data);
|
||||
}
|
||||
|
||||
export function updateApp(id: string, data: { name: string; description?: string }): Promise<App> {
|
||||
return put<App>(`/api/apps/${id}`, data);
|
||||
}
|
||||
|
||||
export function deleteApp(id: string): Promise<void> {
|
||||
return del<void>(`/api/apps/${id}`);
|
||||
}
|
||||
|
||||
export { ApiError };
|
||||
|
||||
Reference in New Issue
Block a user