workspace: refine runtime and cleanup lists
This commit is contained in:
parent
c5aae4b234
commit
d8c0853d55
|
|
@ -50,6 +50,10 @@ impl Default for RuntimeOptions {
|
|||
}
|
||||
}
|
||||
|
||||
fn unknown_platform_component() -> String {
|
||||
"unknown".to_string()
|
||||
}
|
||||
|
||||
/// Management-plane summary for a Runtime.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct RuntimeSummary {
|
||||
|
|
@ -63,6 +67,10 @@ pub struct RuntimeSummary {
|
|||
pub cancelled_worker_count: usize,
|
||||
pub diagnostic_count: usize,
|
||||
pub limits: RuntimeLimits,
|
||||
#[serde(default = "unknown_platform_component")]
|
||||
pub os: String,
|
||||
#[serde(default = "unknown_platform_component")]
|
||||
pub arch: String,
|
||||
#[serde(default)]
|
||||
pub worker_creation_available: bool,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -162,6 +162,8 @@ impl Runtime {
|
|||
cancelled_worker_count,
|
||||
diagnostic_count: state.diagnostics.len(),
|
||||
limits: state.limits.clone(),
|
||||
os: std::env::consts::OS.to_string(),
|
||||
arch: std::env::consts::ARCH.to_string(),
|
||||
worker_creation_available: state.execution_backend.is_some(),
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1765,7 +1765,9 @@ impl RemoteRuntimeConfig {
|
|||
display_name: display_name.into(),
|
||||
base_url: base_url.into(),
|
||||
bearer_token,
|
||||
cached_capabilities: remote_runtime_capabilities(200, false, false),
|
||||
cached_capabilities: remote_runtime_capabilities(
|
||||
200, false, false, "unknown", "unknown",
|
||||
),
|
||||
cached_status: "configured".to_string(),
|
||||
timeout: Duration::from_secs(10),
|
||||
}
|
||||
|
|
@ -2033,6 +2035,8 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
|
|||
limit,
|
||||
true,
|
||||
response.runtime.worker_creation_available,
|
||||
response.runtime.os,
|
||||
response.runtime.arch,
|
||||
),
|
||||
diagnostics: Vec::new(),
|
||||
},
|
||||
|
|
@ -2066,7 +2070,7 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
|
|||
status: "configured".to_string(),
|
||||
observed_at: Utc::now().to_rfc3339(),
|
||||
last_seen_at: None,
|
||||
capabilities: remote_runtime_capabilities(limit, true, false),
|
||||
capabilities: remote_runtime_capabilities(limit, true, false, "unknown", "unknown"),
|
||||
diagnostics: Vec::new(),
|
||||
}],
|
||||
Vec::new(),
|
||||
|
|
@ -2872,6 +2876,8 @@ fn remote_runtime_capabilities(
|
|||
limit: usize,
|
||||
available: bool,
|
||||
worker_creation_available: bool,
|
||||
os: impl Into<String>,
|
||||
arch: impl Into<String>,
|
||||
) -> RuntimeCapabilitySummary {
|
||||
RuntimeCapabilitySummary {
|
||||
can_list_hosts: true,
|
||||
|
|
@ -2887,8 +2893,8 @@ fn remote_runtime_capabilities(
|
|||
supports_backend_internal_tools: false,
|
||||
workspace_scope: "remote_runtime_backend_private".to_string(),
|
||||
max_workers: limit,
|
||||
os: "remote".to_string(),
|
||||
arch: "remote".to_string(),
|
||||
os: os.into(),
|
||||
arch: arch.into(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,10 +77,6 @@
|
|||
<dt>Runtime</dt>
|
||||
<dd><code>{host.runtime_id}</code></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Scope</dt>
|
||||
<dd>{host.capabilities.workspace_scope}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Platform</dt>
|
||||
<dd>{host.capabilities.os} / {host.capabilities.arch}</dd>
|
||||
|
|
|
|||
|
|
@ -4,10 +4,6 @@
|
|||
|
||||
let { data }: PageProps = $props();
|
||||
|
||||
function runtimeScope(runtime: Runtime): string {
|
||||
return runtime.capabilities.workspace_scope;
|
||||
}
|
||||
|
||||
function runtimePlatform(runtime: Runtime): string {
|
||||
return `${runtime.capabilities.os} / ${runtime.capabilities.arch}`;
|
||||
}
|
||||
|
|
@ -40,7 +36,6 @@
|
|||
<th>Runtime</th>
|
||||
<th>Kind</th>
|
||||
<th>Status</th>
|
||||
<th>Scope</th>
|
||||
<th>Platform</th>
|
||||
<th>Capacity</th>
|
||||
<th>Workdirs</th>
|
||||
|
|
@ -55,7 +50,6 @@
|
|||
</td>
|
||||
<td>{runtime.kind}</td>
|
||||
<td>{runtime.status}</td>
|
||||
<td>{runtimeScope(runtime)}</td>
|
||||
<td>{runtimePlatform(runtime)}</td>
|
||||
<td>{runtime.capabilities.max_workers} workers</td>
|
||||
<td>
|
||||
|
|
|
|||
|
|
@ -1,19 +1,25 @@
|
|||
<script lang="ts">
|
||||
import { workspaceApiPath } from '$lib/workspace-api/http';
|
||||
import type { CleanupWorkdirCandidate, WorkingDirectorySummary } from '$lib/workspace-sidebar/types';
|
||||
import type {
|
||||
CleanupWorkdirCandidate,
|
||||
RuntimeCleanupExecutionResponse,
|
||||
RuntimeCleanupPlanResponse,
|
||||
WorkingDirectorySummary,
|
||||
} from '$lib/workspace-sidebar/types';
|
||||
import type { PageProps } from './$types';
|
||||
|
||||
let { data }: PageProps = $props();
|
||||
let selectedCleanupTargets = $state(new Set<string>());
|
||||
let selectedWorkerCleanupTargets = $state(new Set<string>());
|
||||
let confirmedDirtyTargets = $state(new Set<string>());
|
||||
let cleanupStatus = $state<string | null>(null);
|
||||
let cleanupBusy = $state(false);
|
||||
let cleanupCandidates = $derived(data.cleanupPlan?.workdirs ?? []);
|
||||
let workerCleanupCandidates = $derived(data.cleanupPlan?.workers ?? []);
|
||||
let cleanupBusyTarget = $state<string | null>(null);
|
||||
let cleanupPlan = $state<RuntimeCleanupPlanResponse | null>(null);
|
||||
let runtimeLabel = $derived(
|
||||
data.runtimes?.items.find((runtime) => runtime.runtime_id === data.runtimeId)?.label ?? data.runtimeId,
|
||||
);
|
||||
let cleanupCandidates = $derived(cleanupPlan?.workdirs ?? []);
|
||||
|
||||
$effect(() => {
|
||||
cleanupPlan = data.cleanupPlan ?? null;
|
||||
});
|
||||
|
||||
function commitLabel(workdir: WorkingDirectorySummary): string {
|
||||
return workdir.resolved_commit ? workdir.resolved_commit.slice(0, 12) : '—';
|
||||
|
|
@ -25,36 +31,23 @@
|
|||
|
||||
function cleanupLabel(candidate: CleanupWorkdirCandidate): string {
|
||||
if (candidate.action === 'workdir_dirty_discard') {
|
||||
return candidate.cleanliness === 'dirty' ? 'Discard dirty workdir' : 'Discard unknown-state workdir';
|
||||
return candidate.cleanliness === 'dirty' ? 'Discard' : 'Discard unknown';
|
||||
}
|
||||
if (candidate.action === 'workdir_record_delete') return 'Delete missing/removed record';
|
||||
return 'Clean up verified-clean workdir';
|
||||
if (candidate.action === 'workdir_record_delete') return 'Delete record';
|
||||
return 'Clean up';
|
||||
}
|
||||
|
||||
function toggleSelected(targetId: string): void {
|
||||
const next = new Set(selectedCleanupTargets);
|
||||
if (next.has(targetId)) next.delete(targetId);
|
||||
else next.add(targetId);
|
||||
selectedCleanupTargets = next;
|
||||
function cleanupCandidate(workdir: WorkingDirectorySummary): CleanupWorkdirCandidate | undefined {
|
||||
return cleanupCandidates.find((candidate) => candidate.workdir_id === workdir.working_directory_id);
|
||||
}
|
||||
|
||||
function toggleWorkerSelected(targetId: string): void {
|
||||
const next = new Set(selectedWorkerCleanupTargets);
|
||||
if (next.has(targetId)) next.delete(targetId);
|
||||
else next.add(targetId);
|
||||
selectedWorkerCleanupTargets = next;
|
||||
async function executeWorkdirCleanup(candidate: CleanupWorkdirCandidate): Promise<void> {
|
||||
if (!cleanupPlan) return;
|
||||
if (candidate.action === 'workdir_dirty_discard') {
|
||||
const confirmed = window.confirm(`${cleanupLabel(candidate)} ${candidate.workdir_id}? This explicitly discards the Workdir contents.`);
|
||||
if (!confirmed) return;
|
||||
}
|
||||
|
||||
function toggleDirtyConfirmation(targetId: string): void {
|
||||
const next = new Set(confirmedDirtyTargets);
|
||||
if (next.has(targetId)) next.delete(targetId);
|
||||
else next.add(targetId);
|
||||
confirmedDirtyTargets = next;
|
||||
}
|
||||
|
||||
async function executeCleanup(): Promise<void> {
|
||||
if (!data.cleanupPlan || (selectedCleanupTargets.size === 0 && selectedWorkerCleanupTargets.size === 0)) return;
|
||||
cleanupBusy = true;
|
||||
cleanupBusyTarget = candidate.target_id;
|
||||
cleanupStatus = null;
|
||||
try {
|
||||
const response = await fetch(
|
||||
|
|
@ -63,24 +56,22 @@
|
|||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
expected_plan_revision: data.cleanupPlan.revision,
|
||||
expected_plan_digest: data.cleanupPlan.digest,
|
||||
worker_target_ids: Array.from(selectedWorkerCleanupTargets),
|
||||
workdir_target_ids: Array.from(selectedCleanupTargets),
|
||||
confirm_dirty_discard_target_ids: Array.from(confirmedDirtyTargets),
|
||||
expected_plan_revision: cleanupPlan.revision,
|
||||
expected_plan_digest: cleanupPlan.digest,
|
||||
worker_target_ids: [],
|
||||
workdir_target_ids: [candidate.target_id],
|
||||
confirm_dirty_discard_target_ids: candidate.action === 'workdir_dirty_discard' ? [candidate.target_id] : [],
|
||||
}),
|
||||
},
|
||||
);
|
||||
const payload = await response.json().catch(() => null);
|
||||
if (!response.ok) throw new Error(payload?.message ?? payload?.error ?? response.statusText);
|
||||
cleanupStatus = `Executed ${payload?.results?.length ?? 0} cleanup action(s). Refresh to see the latest plan.`;
|
||||
selectedCleanupTargets = new Set();
|
||||
selectedWorkerCleanupTargets = new Set();
|
||||
confirmedDirtyTargets = new Set();
|
||||
const payload = (await response.json().catch(() => null)) as RuntimeCleanupExecutionResponse | { message?: string; error?: string } | null;
|
||||
if (!response.ok) throw new Error(payload && 'message' in payload ? (payload.message ?? payload.error) : response.statusText);
|
||||
if (payload && 'plan_after' in payload) cleanupPlan = payload.plan_after;
|
||||
cleanupStatus = `Executed cleanup for ${candidate.workdir_id}. Refresh to see the latest Workdir list.`;
|
||||
} catch (error) {
|
||||
cleanupStatus = error instanceof Error ? error.message : 'Cleanup failed';
|
||||
cleanupStatus = error instanceof Error ? error.message : 'Workdir cleanup failed';
|
||||
} finally {
|
||||
cleanupBusy = false;
|
||||
cleanupBusyTarget = null;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -96,6 +87,8 @@
|
|||
<p class="breadcrumb"><a href={`/w/${data.workspaceId}/runtimes`}>Runtimes</a> / {runtimeLabel}</p>
|
||||
<h1 id="workdirs-heading">Workdirs</h1>
|
||||
<p>Workdirs owned by <code>{data.runtimeId}</code>.</p>
|
||||
{#if data.cleanupPlanError}<p class="section-state error">{data.cleanupPlanError}</p>{/if}
|
||||
{#if cleanupStatus}<p>{cleanupStatus}</p>{/if}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
|
@ -106,68 +99,6 @@
|
|||
{:else if data.workdirs.items.length === 0}
|
||||
<p class="section-state">No workdirs are visible for this Runtime.</p>
|
||||
{:else}
|
||||
<section class="cleanup-panel">
|
||||
<div>
|
||||
<h2>Manual cleanup preview</h2>
|
||||
<p class="muted">Select explicit Workdir targets. Raw Runtime paths are intentionally not shown.</p>
|
||||
</div>
|
||||
{#if data.cleanupPlanError}
|
||||
<p class="section-state error">{data.cleanupPlanError}</p>
|
||||
{:else if cleanupCandidates.length === 0 && workerCleanupCandidates.length === 0}
|
||||
<p>No cleanup candidates.</p>
|
||||
{:else}
|
||||
<div class="cleanup-list">
|
||||
{#each workerCleanupCandidates as candidate (candidate.target_id)}
|
||||
<label class:blocked={!!candidate.blocking_reason}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedWorkerCleanupTargets.has(candidate.target_id)}
|
||||
disabled={!!candidate.blocking_reason}
|
||||
onchange={() => toggleWorkerSelected(candidate.target_id)}
|
||||
/>
|
||||
<span>
|
||||
<strong>Delete Worker registry row:</strong> <code>{candidate.runtime_worker_id}</code>
|
||||
<small>{candidate.retention_state}; linked Workdirs {candidate.linked_workdir_ids.length}</small>
|
||||
{#if candidate.blocking_reason}<small class="error">Blocked: {candidate.blocking_reason}</small>{/if}
|
||||
</span>
|
||||
</label>
|
||||
{/each}
|
||||
{#each cleanupCandidates as candidate (candidate.target_id)}
|
||||
<label class:blocked={!!candidate.blocking_reason} class:dirty={candidate.action === 'workdir_dirty_discard'}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedCleanupTargets.has(candidate.target_id)}
|
||||
disabled={!!candidate.blocking_reason}
|
||||
onchange={() => toggleSelected(candidate.target_id)}
|
||||
/>
|
||||
<span>
|
||||
<strong>{cleanupLabel(candidate)}:</strong> <code>{candidate.workdir_id}</code>
|
||||
<small>
|
||||
file {candidate.file_status}; {candidate.cleanliness}; linked Workers {candidate.linked_worker_ids.length}
|
||||
</small>
|
||||
{#if candidate.blocking_reason}
|
||||
<small class="error">Blocked: {candidate.blocking_reason}</small>
|
||||
{:else if candidate.action === 'workdir_dirty_discard'}
|
||||
<label class="confirm-dirty">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={confirmedDirtyTargets.has(candidate.target_id)}
|
||||
onchange={() => toggleDirtyConfirmation(candidate.target_id)}
|
||||
/>
|
||||
Confirm discard
|
||||
</label>
|
||||
{/if}
|
||||
</span>
|
||||
</label>
|
||||
{/each}
|
||||
</div>
|
||||
<button type="button" onclick={executeCleanup} disabled={cleanupBusy || (selectedCleanupTargets.size === 0 && selectedWorkerCleanupTargets.size === 0)}>
|
||||
{cleanupBusy ? 'Executing…' : 'Execute selected cleanup'}
|
||||
</button>
|
||||
{#if cleanupStatus}<p>{cleanupStatus}</p>{/if}
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<div class="table-wrap">
|
||||
<table class="workdirs-table">
|
||||
<thead>
|
||||
|
|
@ -178,10 +109,12 @@
|
|||
<th>Commit</th>
|
||||
<th>Status</th>
|
||||
<th>Policy</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each data.workdirs.items as workdir}
|
||||
{@const cleanup = cleanupCandidate(workdir)}
|
||||
<tr>
|
||||
<td><code>{workdir.working_directory_id}</code></td>
|
||||
<td>{workdir.repository_id}</td>
|
||||
|
|
@ -192,6 +125,21 @@
|
|||
<span>{workdir.dirty_state_policy}</span>
|
||||
<small>{workdir.cleanup_policy}</small>
|
||||
</td>
|
||||
<td>
|
||||
{#if cleanup}
|
||||
<button
|
||||
type="button"
|
||||
disabled={!!cleanup.blocking_reason || cleanupBusyTarget === cleanup.target_id}
|
||||
title={cleanup.blocking_reason ?? cleanup.reason}
|
||||
onclick={() => executeWorkdirCleanup(cleanup)}
|
||||
>
|
||||
{cleanupBusyTarget === cleanup.target_id ? 'Executing…' : cleanupLabel(cleanup)}
|
||||
</button>
|
||||
{#if cleanup.blocking_reason}<small class="error">{cleanup.blocking_reason}</small>{/if}
|
||||
{:else}
|
||||
<span class="muted">—</span>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
|
|
|
|||
|
|
@ -2,14 +2,20 @@
|
|||
import { workspaceApiPath } from '$lib/workspace-api/http';
|
||||
import { workerConsoleHref } from '$lib/workspace-console/model';
|
||||
import { canOpenWorkerConsole } from '$lib/workspace-sidebar/workers';
|
||||
import type { Worker } from '$lib/workspace-sidebar/types';
|
||||
import type { CleanupWorkerCandidate, RuntimeCleanupExecutionResponse, RuntimeCleanupPlanResponse, Worker } from '$lib/workspace-sidebar/types';
|
||||
import type { PageProps } from './$types';
|
||||
|
||||
let { data }: PageProps = $props();
|
||||
let retentionStatus = $state<string | null>(null);
|
||||
let statusMessage = $state<string | null>(null);
|
||||
let cleanupPlans = $state<Record<string, RuntimeCleanupPlanResponse>>({});
|
||||
let busyCleanupTarget = $state<string | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
cleanupPlans = data.cleanupPlans;
|
||||
});
|
||||
|
||||
async function setPinned(worker: Worker, pinned: boolean): Promise<void> {
|
||||
retentionStatus = null;
|
||||
statusMessage = null;
|
||||
const response = await fetch(
|
||||
workspaceApiPath(
|
||||
data.workspaceId,
|
||||
|
|
@ -19,12 +25,56 @@
|
|||
);
|
||||
const payload = await response.json().catch(() => null);
|
||||
if (!response.ok) {
|
||||
retentionStatus = payload?.message ?? payload?.error ?? response.statusText;
|
||||
statusMessage = payload?.message ?? payload?.error ?? response.statusText;
|
||||
return;
|
||||
}
|
||||
worker.pinned = Boolean(payload?.pinned);
|
||||
worker.retention_state = payload?.retention_state ?? (worker.pinned ? 'pinned' : 'normal');
|
||||
retentionStatus = `${worker.label} ${worker.pinned ? 'pinned' : 'unpinned'}.`;
|
||||
statusMessage = `${worker.label} ${worker.pinned ? 'pinned' : 'unpinned'}.`;
|
||||
}
|
||||
|
||||
function cleanupCandidate(worker: Worker): CleanupWorkerCandidate | undefined {
|
||||
return cleanupPlans?.[worker.runtime_id]?.workers.find(
|
||||
(candidate) => candidate.runtime_id === worker.runtime_id && candidate.runtime_worker_id === worker.worker_id,
|
||||
);
|
||||
}
|
||||
|
||||
async function deleteWorkerRegistryRow(worker: Worker, candidate: CleanupWorkerCandidate): Promise<void> {
|
||||
if (!cleanupPlans?.[worker.runtime_id]) return;
|
||||
statusMessage = null;
|
||||
busyCleanupTarget = candidate.target_id;
|
||||
try {
|
||||
const plan = cleanupPlans[worker.runtime_id];
|
||||
const response = await fetch(
|
||||
workspaceApiPath(data.workspaceId, `/runtimes/${encodeURIComponent(worker.runtime_id)}/cleanup-executions`),
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
expected_plan_revision: plan.revision,
|
||||
expected_plan_digest: plan.digest,
|
||||
worker_target_ids: [candidate.target_id],
|
||||
workdir_target_ids: [],
|
||||
confirm_dirty_discard_target_ids: [],
|
||||
}),
|
||||
},
|
||||
);
|
||||
const payload = (await response.json().catch(() => null)) as RuntimeCleanupExecutionResponse | { message?: string; error?: string } | null;
|
||||
if (!response.ok) throw new Error(payload && 'message' in payload ? (payload.message ?? payload.error) : response.statusText);
|
||||
if (payload && 'plan_after' in payload) {
|
||||
cleanupPlans = { ...cleanupPlans, [worker.runtime_id]: payload.plan_after };
|
||||
}
|
||||
if (data.workers) {
|
||||
data.workers.items = data.workers.items.filter(
|
||||
(item) => !(item.runtime_id === worker.runtime_id && item.worker_id === worker.worker_id),
|
||||
);
|
||||
}
|
||||
statusMessage = `Deleted Worker registry row for ${worker.label}.`;
|
||||
} catch (error) {
|
||||
statusMessage = error instanceof Error ? error.message : 'Worker cleanup failed';
|
||||
} finally {
|
||||
busyCleanupTarget = null;
|
||||
}
|
||||
}
|
||||
|
||||
function workerStatus(worker: Worker): string {
|
||||
|
|
@ -54,7 +104,7 @@
|
|||
<div>
|
||||
<h1 id="workers-heading">Workers</h1>
|
||||
<p>Workers running or persisted for this workspace. Pinning only updates Backend retention.</p>
|
||||
{#if retentionStatus}<p>{retentionStatus}</p>{/if}
|
||||
{#if statusMessage}<p>{statusMessage}</p>{/if}
|
||||
</div>
|
||||
<a class="section-action" href={`/w/${data.workspaceId}/workers/new`}>New Worker</a>
|
||||
</header>
|
||||
|
|
@ -81,6 +131,7 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
{#each data.workers.items as worker}
|
||||
{@const cleanup = cleanupCandidate(worker)}
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{worker.label}</strong>
|
||||
|
|
@ -100,6 +151,16 @@
|
|||
<button type="button" onclick={() => setPinned(worker, !worker.pinned)}>
|
||||
{worker.pinned ? 'Unpin' : 'Pin'}
|
||||
</button>
|
||||
{#if cleanup}
|
||||
<button
|
||||
type="button"
|
||||
disabled={!!cleanup.blocking_reason || busyCleanupTarget === cleanup.target_id}
|
||||
title={cleanup.blocking_reason ?? cleanup.reason}
|
||||
onclick={() => deleteWorkerRegistryRow(worker, cleanup)}
|
||||
>
|
||||
{busyCleanupTarget === cleanup.target_id ? 'Deleting…' : 'Delete row'}
|
||||
</button>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { loadJson, workspaceApiPath } from "$lib/workspace-api/http";
|
||||
import type { ListResponse, Worker } from "$lib/workspace-sidebar/types";
|
||||
import type { ListResponse, RuntimeCleanupPlanResponse, Worker } from "$lib/workspace-sidebar/types";
|
||||
import type { PageLoad } from "./$types";
|
||||
|
||||
export const load: PageLoad = async ({ fetch, params }) => {
|
||||
|
|
@ -7,10 +7,28 @@ export const load: PageLoad = async ({ fetch, params }) => {
|
|||
fetch,
|
||||
workspaceApiPath(params.workspaceId, "/workers"),
|
||||
);
|
||||
const runtimeIds = Array.from(new Set(workers.data?.items.map((worker) => worker.runtime_id) ?? []));
|
||||
const cleanupPlanEntries = await Promise.all(
|
||||
runtimeIds.map(async (runtimeId) => {
|
||||
const cleanupPlan = await loadJson<RuntimeCleanupPlanResponse>(
|
||||
fetch,
|
||||
workspaceApiPath(params.workspaceId, `/runtimes/${encodeURIComponent(runtimeId)}/cleanup-plan`),
|
||||
);
|
||||
return [runtimeId, cleanupPlan] as const;
|
||||
}),
|
||||
);
|
||||
const cleanupPlans: Record<string, RuntimeCleanupPlanResponse> = {};
|
||||
const cleanupPlanErrors: Record<string, string> = {};
|
||||
for (const [runtimeId, cleanupPlan] of cleanupPlanEntries) {
|
||||
if (cleanupPlan.data) cleanupPlans[runtimeId] = cleanupPlan.data;
|
||||
if (cleanupPlan.error) cleanupPlanErrors[runtimeId] = cleanupPlan.error;
|
||||
}
|
||||
|
||||
return {
|
||||
workspaceId: params.workspaceId,
|
||||
workers: workers.data,
|
||||
workersError: workers.error,
|
||||
cleanupPlans,
|
||||
cleanupPlanErrors,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user