new destruction flow

This commit is contained in:
2026-09-17 16:57:08 -04:00
parent 5f8e03c693
commit a611850649
13 changed files with 349 additions and 96 deletions
+13 -11
View File
@@ -8,7 +8,7 @@
interface Deployment {
id: string; server: string; action: 'create' | 'destroy';
state: 'running' | 'done' | 'failed' | 'warning' | 'interrupted';
state: 'waiting' | 'running' | 'done' | 'failed' | 'warning' | 'interrupted';
stage: string; started_at: string; finished_at: string | null;
triggered_by: string; error: string | null; warnings: string; server_exists: number;
}
@@ -19,8 +19,8 @@
let action = $state('all');
let search = $state('');
let refreshing = false;
let active = $derived(deployments.filter(d => d.state === 'running'));
let history = $derived(deployments.filter(d => d.state !== 'running' &&
let active = $derived(deployments.filter(d => (d.state === 'running' || d.state === 'waiting')));
let history = $derived(deployments.filter(d => d.state !== 'running' && d.state !== 'waiting' &&
(action === 'all' || d.action === action) && d.server.toLowerCase().includes(search.toLowerCase())));
async function load() {
if (refreshing) return;
@@ -41,11 +41,11 @@
function fmt(value: string) { return new Date(value).toLocaleString(); }
function warnings(d: Deployment): string[] { try { return JSON.parse(d.warnings); } catch { return []; } }
function status(d: Deployment) {
return { running: 'In flight', done: 'Completed', failed: 'Failed', warning: 'Completed with warnings', interrupted: 'Interrupted' }[d.state];
return { waiting: 'Waiting for empty', running: 'In flight', done: 'Completed', failed: 'Failed', warning: 'Completed with warnings', interrupted: 'Interrupted' }[d.state];
}
</script>
<svelte:head><title>Server deployments ? Electric Cherry</title></svelte:head>
<svelte:head><title>Server deployments | Electric Cherry</title></svelte:head>
<div class="page">
<SectionHeader title="Deployments" subtitle="Server creations and destructions, from work in flight to operation history." />
<div class="toolbar">
@@ -55,7 +55,7 @@
{#if error}<p role="alert" class="error">{error} <button onclick={load}>Retry</button></p>{/if}
<section>
<h2>In flight <span>{active.length}</span></h2>
{#if loading}<p>Loading deployments?</p>
{#if loading}<p>Loading deployments...</p>
{:else if active.length === 0}<EmptyState title="No server operations in flight" description="New creations and destructions appear here automatically." />
{:else}<div class="rows">{#each active as d (d.id)}{@render row(d)}{/each}</div>{/if}
</section>
@@ -71,10 +71,10 @@
</div>
{#snippet row(d: Deployment)}
<article>
<a class="deployment-row" href="/deployments/{d.id}">
<div class="row-head">
<div><Badge variant={d.action === 'create' ? 'info' : 'neutral'}>{d.action === 'create' ? 'Creation' : 'Destruction'}</Badge>
{#if d.server_exists}<a class="server" href="/servers/{encodeURIComponent(d.server)}">{d.server}</a>{:else}<strong class="server">{d.server}</strong>{/if}
<strong class="server">{d.server}</strong>
</div>
<Badge variant={d.state === 'done' ? 'online' : d.state === 'failed' ? 'danger' : 'warning'}>{status(d)}</Badge>
</div>
@@ -82,7 +82,7 @@
<div class="meta"><span>Started {fmt(d.started_at)}</span>{#if d.finished_at}<span>Finished {fmt(d.finished_at)}</span>{/if}<span>By {d.triggered_by}</span></div>
{#if d.error}<p class="error">{d.error}</p>{/if}
{#each warnings(d) as warning}<p class="error">{warning}</p>{/each}
</article>
</a>
{/snippet}
<style>
@@ -93,9 +93,11 @@
h2 { font-size: 18px; margin: 0; } h2 span { opacity: .6; margin-left: 8px; }
p { margin: 8px 0; } a { color: var(--accent); }
.rows { display: grid; gap: 12px; }
article { padding: 20px; border: 1px solid var(--border-subtle); border-radius: 10px; background: var(--surface); overflow-wrap: anywhere; }
.deployment-row { display: block; text-decoration: none; color: inherit; padding: 20px; border: 1px solid var(--border-subtle); border-radius: 10px; background: var(--surface); overflow-wrap: anywhere; }
.deployment-row:hover { border-color: var(--border-strong); background: var(--surface-raised); }
.deployment-row:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 3px; }
.server { margin-left: 12px; font-family: var(--font-mono); font-size: 14px; }
.meta, .toolbar p { font-size: 13px; color: var(--text-secondary); }
.meta { font-size: 13px; color: var(--text-secondary); }
.error { color: var(--state-danger); }
label { display: flex; align-items: center; gap: 8px; font-size: 13px; }
input, select { background: var(--surface); color: inherit; border: 1px solid var(--border-subtle); border-radius: 6px; padding: 8px; max-width: 100%; }