Rework as scripts-centric, upload-only, gated beta lifecycle
Scripts become first-class: named, versioned, uploaded via the UI, optionally scoped to a game or map. Any change that would land on scope-matched servers (marking-required or new version of a scoped script) must first pass a beta. The beta runs a per-server state machine — deploy -beta.gsc → wait empty → Komodo restart → wait healthy → optionally wait for a real match end (games_sp.log 'ExitLevel: executed') — before it's eligible for promotion. Rollback is always available and restores from .OLD. The shared/scripts mirror and its betas/ staging folder are gone. Every server now has a tracked script inventory (server_scripts table), populated only by explicit upload flows. New: /scripts, /scripts/[id], /betas/[id] UI; /api/scripts + /api/betas + /api/servers/[name]/scripts endpoints; scripts.ts, inventory.ts, beta.ts (rewritten), beta-runner.ts, match-log.ts, komodoInspectHealthy(). Removed: push.ts, /api/push, BetaPanel, legacy importer. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+9
-62
@@ -5,7 +5,7 @@
|
||||
import { statusByServer } from '$ui/stores/servers.js';
|
||||
import type { RosterPlayer, Health } from '$ui/stores/servers.js';
|
||||
|
||||
interface HeldRow { target: string; slug: string; }
|
||||
interface HeldRow { target: string; betaId: number; }
|
||||
interface ServerRow {
|
||||
name: string;
|
||||
komodo_type: string;
|
||||
@@ -24,8 +24,6 @@
|
||||
}
|
||||
|
||||
let servers = $state<ServerRow[]>([]);
|
||||
let pushing = $state(false);
|
||||
let flash = $state<string | null>(null);
|
||||
|
||||
onMount(async () => {
|
||||
const res = await fetch('/api/servers');
|
||||
@@ -33,25 +31,6 @@
|
||||
servers = body.servers;
|
||||
});
|
||||
|
||||
async function push(dryRun: boolean) {
|
||||
pushing = true;
|
||||
flash = null;
|
||||
const res = await fetch('/api/push', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ patterns: ['*'], dryRun, create: false, delete: false })
|
||||
});
|
||||
pushing = false;
|
||||
const body = (await res.json()) as { totalChangedServers?: number; totalFiles?: number; deploymentId?: string | null };
|
||||
if (!res.ok) {
|
||||
flash = `push failed: HTTP ${res.status}`;
|
||||
return;
|
||||
}
|
||||
flash = dryRun
|
||||
? `dry run: ${body.totalChangedServers ?? 0} servers would change, ${body.totalFiles ?? 0} files`
|
||||
: `pushed to ${body.totalChangedServers ?? 0} servers · ${body.totalFiles ?? 0} files`;
|
||||
}
|
||||
|
||||
// Live server view — merge SSE data over the initial GET
|
||||
function liveFor(name: string, fallback: ServerRow) {
|
||||
const live = $statusByServer.get(name);
|
||||
@@ -85,12 +64,8 @@
|
||||
<h1>Fleet</h1>
|
||||
<span class="count">{servers.length} server{servers.length === 1 ? '' : 's'}</span>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button onclick={() => push(true)} disabled={pushing}>dry run</button>
|
||||
<button class="primary" onclick={() => push(false)} disabled={pushing}>push all</button>
|
||||
</div>
|
||||
<a class="scripts-link" href="/scripts">manage scripts →</a>
|
||||
</header>
|
||||
{#if flash}<p class="flash">{flash}</p>{/if}
|
||||
{#if servers.length === 0}
|
||||
<p class="empty">no servers registered yet — check SERVERS_ROOT</p>
|
||||
{:else}
|
||||
@@ -150,35 +125,17 @@
|
||||
color: var(--ink-500);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.actions { display: flex; gap: 10px; }
|
||||
button {
|
||||
background: var(--bg-card);
|
||||
color: var(--ink-100);
|
||||
.scripts-link {
|
||||
color: var(--spark-400);
|
||||
text-decoration: none;
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-sm);
|
||||
padding: 8px 14px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--r-sm);
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-base);
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.3px;
|
||||
transition: border-color 0.15s, box-shadow 0.15s;
|
||||
}
|
||||
button:hover:not(:disabled) {
|
||||
border-color: var(--accent);
|
||||
box-shadow: var(--accent-glow);
|
||||
}
|
||||
button.primary {
|
||||
background: linear-gradient(180deg, var(--blue-500), var(--blue-600));
|
||||
color: var(--cream);
|
||||
border-color: var(--blue-400);
|
||||
font-weight: 600;
|
||||
}
|
||||
button.primary:hover:not(:disabled) {
|
||||
border-color: var(--spark-500);
|
||||
box-shadow: var(--spark-glow), 0 0 20px rgba(46, 134, 209, 0.45);
|
||||
}
|
||||
button:disabled { opacity: 0.4; cursor: default; }
|
||||
.scripts-link:hover { border-color: var(--accent); box-shadow: var(--accent-glow); }
|
||||
.cards {
|
||||
display: grid;
|
||||
gap: var(--gap-cards);
|
||||
@@ -193,14 +150,4 @@
|
||||
border: 1px dashed var(--border);
|
||||
border-radius: var(--r-md);
|
||||
}
|
||||
.flash {
|
||||
color: var(--spark-400);
|
||||
margin: 0 0 20px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-mono);
|
||||
padding: 12px 16px;
|
||||
background: rgba(124, 212, 255, 0.06);
|
||||
border: 1px solid rgba(124, 212, 255, 0.25);
|
||||
border-radius: var(--r-sm);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user