feat: add provisioning lifecycle actions

Rename Deployments to Provisioning with compatible redirects and APIs. Add durable Start, Restart, Stop, and Rebuild operations alongside Create and Destroy; rebuild wipes the instance and recreates it in the same slot and port.

Add lifecycle, migration, recovery, and Komodo command tests. Type checks and production build pass; the full suite retains a telemetry assertion failure reproduced on the unchanged branch.

Refs #39
This commit is contained in:
2026-09-27 14:20:04 -04:00
parent 0b0e5b5bdb
commit cfed584614
42 changed files with 886 additions and 108 deletions
@@ -73,7 +73,7 @@
const previousName = preview?.name;
busy = true; error = ''; savedName = ''; keyNotice = '';
try {
const res = await fetch('/api/deployments/preview', { method: 'POST', headers: { 'Content-Type': 'application/json' },
const res = await fetch('/api/provisioning/preview', { method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(sourceDeploymentId ? { deploymentId: sourceDeploymentId } : blueprintId ? { blueprintId } : { game, mapSlug }) });
const body = await res.json();
if (!res.ok) throw new Error(body.error ?? 'Could not prepare launch preview.');
@@ -97,7 +97,7 @@
const body = await res.json();
if (!res.ok || !body.ok) throw new Error(body.error ?? 'Could not queue this launch.');
open = false;
await goto(`/deployments/${encodeURIComponent(body.deploymentId)}`);
await goto(`/provisioning/${encodeURIComponent(body.deploymentId)}`);
} catch (err) { error = (err as Error).message; }
finally { busy = false; }
}
@@ -119,7 +119,7 @@
<dialog bind:this={dialog} aria-label="Create server" onclick={event => { if (isDialogBackdrop(event, dialog) && !busy && !saving) open = false; }} oncancel={event => { if (busy || saving) event.preventDefault(); }} onclose={() => open = false}>
<div class="composer">
<header><div><h2 bind:this={heading} tabindex="-1">{preview ? 'Review server launch' : 'Create server'}</h2></div><button class="close" disabled={busy || saving} onclick={() => open = false} aria-label="Close deployment composer">×</button></header>
<header><div><h2 bind:this={heading} tabindex="-1">{preview ? 'Review server launch' : 'Create server'}</h2></div><button class="close" disabled={busy || saving} onclick={() => open = false} aria-label="Close server creation dialog">×</button></header>
<ol class="steps" aria-label="Launch steps"><li class:active={!preview} aria-current={!preview ? 'step' : undefined}>01 <span>Choose a starting point</span></li><li class:active={!!preview} aria-current={preview ? 'step' : undefined}>02 <span>Review & launch</span></li></ol>
{#if error}<div class="error" role="alert">{error}</div>{/if}
{#if !loaded}