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:
@@ -1,55 +1,73 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { openBeta, updateBeta, listBetas, listBetaScripts, listBetaServers, BetaError } from '$lib/server/fleet/beta.js';
|
||||
import { stagedFiles, findTargets } from '$lib/server/fleet/files.js';
|
||||
import {
|
||||
listBetas,
|
||||
listBetaServers,
|
||||
openBeta,
|
||||
BetaError,
|
||||
type BetaTriggerKind
|
||||
} from '$lib/server/fleet/beta.js';
|
||||
import { getScript, getVersion } from '$lib/server/fleet/scripts.js';
|
||||
import { kick } from '$lib/server/pipeline/worker.js';
|
||||
|
||||
export async function GET() {
|
||||
const staged = await stagedFiles();
|
||||
const withHints = await Promise.all(
|
||||
staged.map(async (base) => ({
|
||||
base,
|
||||
hits: await findTargets(base)
|
||||
}))
|
||||
);
|
||||
const betas = listBetas().map((b) => ({
|
||||
...b,
|
||||
servers: listBetaServers(b.slug),
|
||||
scripts: listBetaScripts(b.slug)
|
||||
}));
|
||||
return json({ staged: withHints, betas });
|
||||
export async function GET({ url }: { url: URL }) {
|
||||
const stateFilter = url.searchParams.get('state') as
|
||||
| 'running'
|
||||
| 'ready-promote'
|
||||
| 'promoted'
|
||||
| 'rolled-back'
|
||||
| null;
|
||||
const rows = listBetas(stateFilter ?? undefined).map((b) => {
|
||||
const script = getScript(b.script_id);
|
||||
const version = getVersion(b.script_version_id);
|
||||
return {
|
||||
...b,
|
||||
script: script ? { id: script.id, name: script.name, target_rel_path: script.target_rel_path } : null,
|
||||
version: version ? { id: version.id, version: version.version, sha256: version.sha256 } : null,
|
||||
servers: listBetaServers(b.id)
|
||||
};
|
||||
});
|
||||
return json({ betas: rows });
|
||||
}
|
||||
|
||||
// POST body: { scriptId, scriptVersionId, servers[], waitForHealth, waitForMatch, triggerKind, notes? }
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
const body = (await request.json().catch(() => ({}))) as {
|
||||
name?: string;
|
||||
patterns?: string[];
|
||||
dryRun?: boolean;
|
||||
create?: boolean;
|
||||
scriptId?: number;
|
||||
scriptVersionId?: number;
|
||||
servers?: string[];
|
||||
waitForHealth?: boolean;
|
||||
waitForMatch?: boolean;
|
||||
triggerKind?: BetaTriggerKind;
|
||||
notes?: string;
|
||||
};
|
||||
if (!body.name) return json({ ok: false, error: 'name is required' }, { status: 400 });
|
||||
try {
|
||||
const res = await openBeta({
|
||||
name: body.name,
|
||||
patterns: body.patterns ?? ['*'],
|
||||
dryRun: body.dryRun ?? false,
|
||||
create: body.create ?? false
|
||||
});
|
||||
if (!res.dryRun) void kick();
|
||||
return json({ ok: true, result: res });
|
||||
} catch (err) {
|
||||
if (err instanceof BetaError) {
|
||||
return json({ ok: false, error: err.message, kind: err.kind, details: err.details }, { status: 409 });
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export async function PATCH({ request }: { request: Request }) {
|
||||
const body = (await request.json().catch(() => ({}))) as { name?: string; dryRun?: boolean };
|
||||
if (!body.name) return json({ ok: false, error: 'name is required' }, { status: 400 });
|
||||
if (
|
||||
typeof body.scriptId !== 'number' ||
|
||||
typeof body.scriptVersionId !== 'number' ||
|
||||
!Array.isArray(body.servers)
|
||||
) {
|
||||
return json(
|
||||
{ ok: false, error: 'scriptId, scriptVersionId, and servers[] are required' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
if (body.servers.length === 0) {
|
||||
return json({ ok: false, error: 'at least one server is required' }, { status: 400 });
|
||||
}
|
||||
const triggerKind: BetaTriggerKind = body.triggerKind ?? 'unscoped';
|
||||
|
||||
try {
|
||||
const res = await updateBeta(body.name, body.dryRun ?? false);
|
||||
return json({ ok: true, result: res });
|
||||
const result = await openBeta({
|
||||
scriptId: body.scriptId,
|
||||
scriptVersionId: body.scriptVersionId,
|
||||
servers: body.servers,
|
||||
waitForHealth: body.waitForHealth ?? true,
|
||||
waitForMatch: body.waitForMatch ?? false,
|
||||
triggerKind,
|
||||
notes: body.notes
|
||||
});
|
||||
void kick();
|
||||
return json({ ok: true, result });
|
||||
} catch (err) {
|
||||
if (err instanceof BetaError) {
|
||||
return json({ ok: false, error: err.message, kind: err.kind, details: err.details }, { status: 409 });
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { getBeta, listBetaServers } from '$lib/server/fleet/beta.js';
|
||||
import { getScript, getVersion } from '$lib/server/fleet/scripts.js';
|
||||
|
||||
function parseId(raw: string): number | null {
|
||||
const n = Number.parseInt(raw, 10);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
}
|
||||
|
||||
export async function GET({ params }: { params: { id: string } }) {
|
||||
const id = parseId(params.id);
|
||||
if (id === null) return json({ ok: false, error: 'bad id' }, { status: 400 });
|
||||
const beta = getBeta(id);
|
||||
if (!beta) return json({ ok: false, error: 'not found' }, { status: 404 });
|
||||
|
||||
const script = getScript(beta.script_id);
|
||||
const version = getVersion(beta.script_version_id);
|
||||
const servers = listBetaServers(id);
|
||||
|
||||
return json({ beta, script, version, servers });
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { promoteBeta, BetaError } from '$lib/server/fleet/beta.js';
|
||||
|
||||
function parseId(raw: string): number | null {
|
||||
const n = Number.parseInt(raw, 10);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
}
|
||||
|
||||
export async function POST({ params }: { params: { id: string } }) {
|
||||
const id = parseId(params.id);
|
||||
if (id === null) return json({ ok: false, error: 'bad id' }, { status: 400 });
|
||||
try {
|
||||
const result = await promoteBeta(id);
|
||||
return json({ ok: true, result });
|
||||
} catch (err) {
|
||||
if (err instanceof BetaError) {
|
||||
return json({ ok: false, error: err.message, kind: err.kind, details: err.details }, { status: 409 });
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { db } from '$lib/server/db/index.js';
|
||||
import { emit, type BetaStage } from '$lib/server/pipeline/bus.js';
|
||||
|
||||
function parseId(raw: string): number | null {
|
||||
const n = Number.parseInt(raw, 10);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
}
|
||||
|
||||
// Reset one failed beta_server row back to its natural retry point. The runner
|
||||
// picks it up on the next tick. Body: { server: string }.
|
||||
export async function POST({ params, request }: { params: { id: string }; request: Request }) {
|
||||
const betaId = parseId(params.id);
|
||||
if (betaId === null) return json({ ok: false, error: 'bad id' }, { status: 400 });
|
||||
const body = (await request.json().catch(() => ({}))) as { server?: string };
|
||||
if (!body.server) return json({ ok: false, error: 'server is required' }, { status: 400 });
|
||||
|
||||
const row = db()
|
||||
.prepare('SELECT stage, restarted_at, healthy_at FROM beta_servers WHERE beta_id = ? AND server = ?')
|
||||
.get(betaId, body.server) as
|
||||
| { stage: string; restarted_at: string | null; healthy_at: string | null }
|
||||
| undefined;
|
||||
if (!row) return json({ ok: false, error: 'no such beta_server' }, { status: 404 });
|
||||
if (row.stage !== 'failed') return json({ ok: false, error: `stage is ${row.stage}, not failed` }, { status: 409 });
|
||||
|
||||
// Best-effort resume point: if we already got past restart, go back to
|
||||
// health check; otherwise back to waiting for the server to be empty.
|
||||
const nextStage: BetaStage = row.restarted_at ? 'awaiting-health' : 'awaiting-empty';
|
||||
const now = new Date().toISOString();
|
||||
db()
|
||||
.prepare('UPDATE beta_servers SET stage = ?, error = NULL, updated_at = ? WHERE beta_id = ? AND server = ?')
|
||||
.run(nextStage, now, betaId, body.server);
|
||||
emit({ type: 'beta-server-stage', betaId, server: body.server, stage: nextStage, error: null });
|
||||
return json({ ok: true, stage: nextStage });
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { rollbackBeta, BetaError } from '$lib/server/fleet/beta.js';
|
||||
|
||||
function parseId(raw: string): number | null {
|
||||
const n = Number.parseInt(raw, 10);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
}
|
||||
|
||||
export async function POST({ params }: { params: { id: string } }) {
|
||||
const id = parseId(params.id);
|
||||
if (id === null) return json({ ok: false, error: 'bad id' }, { status: 400 });
|
||||
try {
|
||||
const result = await rollbackBeta(id);
|
||||
return json({ ok: true, result });
|
||||
} catch (err) {
|
||||
if (err instanceof BetaError) {
|
||||
return json({ ok: false, error: err.message, kind: err.kind, details: err.details }, { status: 409 });
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { promoteBeta, BetaError } from '$lib/server/fleet/beta.js';
|
||||
import { kick } from '$lib/server/pipeline/worker.js';
|
||||
|
||||
export async function POST({ params, request }: { params: { slug: string }; request: Request }) {
|
||||
const body = (await request.json().catch(() => ({}))) as { dryRun?: boolean };
|
||||
try {
|
||||
const res = await promoteBeta(params.slug, body.dryRun ?? false);
|
||||
if (!res.dryRun) void kick();
|
||||
return json({ ok: true, result: res });
|
||||
} catch (err) {
|
||||
if (err instanceof BetaError) {
|
||||
return json({ ok: false, error: err.message, kind: err.kind, details: err.details }, { status: 409 });
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { rollbackBeta, BetaError } from '$lib/server/fleet/beta.js';
|
||||
import { kick } from '$lib/server/pipeline/worker.js';
|
||||
|
||||
export async function POST({ params, request }: { params: { slug: string }; request: Request }) {
|
||||
const body = (await request.json().catch(() => ({}))) as { dryRun?: boolean };
|
||||
try {
|
||||
// callers pass name; slug in URL is the slugified name — either works
|
||||
const res = await rollbackBeta(params.slug, body.dryRun ?? false);
|
||||
if (!res.dryRun) void kick();
|
||||
return json({ ok: true, result: res });
|
||||
} catch (err) {
|
||||
if (err instanceof BetaError) {
|
||||
return json({ ok: false, error: err.message, kind: err.kind, details: err.details }, { status: 409 });
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { doPush } from '$lib/server/fleet/push.js';
|
||||
import { kick } from '$lib/server/pipeline/worker.js';
|
||||
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
const body = (await request.json().catch(() => ({}))) as {
|
||||
patterns?: string[];
|
||||
dryRun?: boolean;
|
||||
delete?: boolean;
|
||||
create?: boolean;
|
||||
};
|
||||
const result = await doPush({
|
||||
patterns: body.patterns ?? ['*'],
|
||||
dryRun: body.dryRun ?? false,
|
||||
delete: body.delete ?? false,
|
||||
create: body.create ?? false,
|
||||
triggeredBy: 'ui'
|
||||
});
|
||||
if (result.deploymentId) void kick();
|
||||
return json(result);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import {
|
||||
createScript,
|
||||
listScripts,
|
||||
listVersions,
|
||||
serversMatchingScope,
|
||||
ScriptError,
|
||||
type ScopeType
|
||||
} from '$lib/server/fleet/scripts.js';
|
||||
import { findRunningBetaForScript } from '$lib/server/fleet/beta.js';
|
||||
|
||||
function scopeTypeOrNull(v: FormDataEntryValue | null): ScopeType | null {
|
||||
if (v === null) return null;
|
||||
const s = String(v);
|
||||
if (s === '' || s === 'none') return null;
|
||||
if (s === 'game' || s === 'map') return s;
|
||||
throw new ScriptError(`invalid scope_type: ${s}`, 'invalid-scope');
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
const scripts = listScripts().map((s) => {
|
||||
const versions = listVersions(s.id);
|
||||
const matched = s.scope_type && s.scope_value
|
||||
? serversMatchingScope(s.scope_type as ScopeType, s.scope_value)
|
||||
: [];
|
||||
const runningBeta = findRunningBetaForScript(s.id);
|
||||
return {
|
||||
...s,
|
||||
latestVersion: versions[0]
|
||||
? {
|
||||
id: versions[0].id,
|
||||
version: versions[0].version,
|
||||
sha256: versions[0].sha256,
|
||||
size: versions[0].size,
|
||||
uploaded_at: versions[0].uploaded_at
|
||||
}
|
||||
: null,
|
||||
versionCount: versions.length,
|
||||
servers: matched,
|
||||
runningBetaId: runningBeta?.id ?? null
|
||||
};
|
||||
});
|
||||
return json({ scripts });
|
||||
}
|
||||
|
||||
// Multipart POST — creates a script + version 1 from a single file upload.
|
||||
// Fields: name (text), targetRelPath (text), scopeType (optional: 'game'|'map'),
|
||||
// scopeValue (optional string), notes (optional), content (file).
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
const form = await request.formData();
|
||||
const name = String(form.get('name') ?? '').trim();
|
||||
const targetRelPath = String(form.get('targetRelPath') ?? '').trim();
|
||||
const notes = form.get('notes') ? String(form.get('notes')) : undefined;
|
||||
let scopeType: ScopeType | null;
|
||||
try {
|
||||
scopeType = scopeTypeOrNull(form.get('scopeType'));
|
||||
} catch (err) {
|
||||
return json({ ok: false, error: (err as Error).message }, { status: 400 });
|
||||
}
|
||||
const scopeValueRaw = form.get('scopeValue');
|
||||
const scopeValue = scopeValueRaw ? String(scopeValueRaw).trim() : null;
|
||||
const file = form.get('content');
|
||||
if (!(file instanceof File)) return json({ ok: false, error: 'content file is required' }, { status: 400 });
|
||||
|
||||
const content = Buffer.from(await file.arrayBuffer());
|
||||
|
||||
try {
|
||||
const { script, version } = createScript({
|
||||
name,
|
||||
targetRelPath,
|
||||
scopeType,
|
||||
scopeValue,
|
||||
content,
|
||||
notes
|
||||
});
|
||||
return json({ ok: true, script, versionId: version.id });
|
||||
} catch (err) {
|
||||
if (err instanceof ScriptError) {
|
||||
return json({ ok: false, error: err.message, kind: err.kind }, { status: 409 });
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import {
|
||||
getScript,
|
||||
listVersions,
|
||||
deleteScript,
|
||||
serversMatchingScope,
|
||||
ScriptError,
|
||||
type ScopeType
|
||||
} from '$lib/server/fleet/scripts.js';
|
||||
import { findRunningBetaForScript } from '$lib/server/fleet/beta.js';
|
||||
|
||||
function parseId(idRaw: string): number | null {
|
||||
const n = Number.parseInt(idRaw, 10);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
}
|
||||
|
||||
export async function GET({ params }: { params: { id: string } }) {
|
||||
const id = parseId(params.id);
|
||||
if (id === null) return json({ ok: false, error: 'bad id' }, { status: 400 });
|
||||
const script = getScript(id);
|
||||
if (!script) return json({ ok: false, error: 'not found' }, { status: 404 });
|
||||
|
||||
const versions = listVersions(id).map((v) => ({
|
||||
id: v.id,
|
||||
version: v.version,
|
||||
sha256: v.sha256,
|
||||
size: v.size,
|
||||
notes: v.notes,
|
||||
uploaded_at: v.uploaded_at
|
||||
}));
|
||||
const matched = script.scope_type && script.scope_value
|
||||
? serversMatchingScope(script.scope_type as ScopeType, script.scope_value)
|
||||
: [];
|
||||
const runningBeta = findRunningBetaForScript(id);
|
||||
|
||||
return json({
|
||||
script,
|
||||
versions,
|
||||
matchedServers: matched,
|
||||
runningBetaId: runningBeta?.id ?? null
|
||||
});
|
||||
}
|
||||
|
||||
export async function DELETE({ params }: { params: { id: string } }) {
|
||||
const id = parseId(params.id);
|
||||
if (id === null) return json({ ok: false, error: 'bad id' }, { status: 400 });
|
||||
try {
|
||||
deleteScript(id);
|
||||
return json({ ok: true });
|
||||
} catch (err) {
|
||||
if (err instanceof ScriptError) {
|
||||
return json({ ok: false, error: err.message, kind: err.kind }, { status: 409 });
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { getScript, setScope, ScriptError, type ScopeType } from '$lib/server/fleet/scripts.js';
|
||||
import { unsetScopeAndCleanup } from '$lib/server/fleet/beta.js';
|
||||
|
||||
function parseId(raw: string): number | null {
|
||||
const n = Number.parseInt(raw, 10);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
}
|
||||
|
||||
// POST body: { scopeType: 'game' | 'map' | null, scopeValue?: string }
|
||||
// Setting/changing scope on a script with a current_version_id returns a
|
||||
// 409 with kind='scope-requires-beta'; unsetting cleans the file off matching
|
||||
// servers.
|
||||
export async function POST({ params, request }: { params: { id: string }; request: Request }) {
|
||||
const scriptId = parseId(params.id);
|
||||
if (scriptId === null) return json({ ok: false, error: 'bad id' }, { status: 400 });
|
||||
const script = getScript(scriptId);
|
||||
if (!script) return json({ ok: false, error: 'not found' }, { status: 404 });
|
||||
|
||||
const body = (await request.json().catch(() => ({}))) as {
|
||||
scopeType?: ScopeType | null;
|
||||
scopeValue?: string | null;
|
||||
};
|
||||
|
||||
const scopeType = body.scopeType ?? null;
|
||||
const scopeValue = body.scopeValue ?? null;
|
||||
|
||||
try {
|
||||
if (scopeType === null) {
|
||||
const cleaned = await unsetScopeAndCleanup(scriptId);
|
||||
return json({ ok: true, cleaned });
|
||||
}
|
||||
const updated = setScope({ scriptId, scopeType, scopeValue });
|
||||
return json({ ok: true, script: updated });
|
||||
} catch (err) {
|
||||
if (err instanceof ScriptError) {
|
||||
return json({ ok: false, error: err.message, kind: err.kind }, { status: 409 });
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { uploadNewVersion, getScript, ScriptError } from '$lib/server/fleet/scripts.js';
|
||||
|
||||
function parseId(idRaw: string): number | null {
|
||||
const n = Number.parseInt(idRaw, 10);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
}
|
||||
|
||||
// Multipart POST — creates a new version from an uploaded file. Fields:
|
||||
// content (file), notes (optional). Response includes a "betaRequired" hint
|
||||
// when the script has a scope so the UI can nudge the operator to open a beta.
|
||||
export async function POST({ params, request }: { params: { id: string }; request: Request }) {
|
||||
const scriptId = parseId(params.id);
|
||||
if (scriptId === null) return json({ ok: false, error: 'bad id' }, { status: 400 });
|
||||
const script = getScript(scriptId);
|
||||
if (!script) return json({ ok: false, error: 'not found' }, { status: 404 });
|
||||
|
||||
const form = await request.formData();
|
||||
const notes = form.get('notes') ? String(form.get('notes')) : undefined;
|
||||
const file = form.get('content');
|
||||
if (!(file instanceof File)) return json({ ok: false, error: 'content file is required' }, { status: 400 });
|
||||
|
||||
const content = Buffer.from(await file.arrayBuffer());
|
||||
try {
|
||||
const version = uploadNewVersion(scriptId, content, notes);
|
||||
return json({
|
||||
ok: true,
|
||||
version: {
|
||||
id: version.id,
|
||||
version: version.version,
|
||||
sha256: version.sha256,
|
||||
size: version.size,
|
||||
notes: version.notes,
|
||||
uploaded_at: version.uploaded_at
|
||||
},
|
||||
betaRequired: script.scope_type !== null
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof ScriptError) {
|
||||
return json({ ok: false, error: err.message, kind: err.kind }, { status: 409 });
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { deleteVersion, ScriptError } from '$lib/server/fleet/scripts.js';
|
||||
|
||||
function parseId(raw: string): number | null {
|
||||
const n = Number.parseInt(raw, 10);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
}
|
||||
|
||||
export async function DELETE({ params }: { params: { id: string; versionId: string } }) {
|
||||
const versionId = parseId(params.versionId);
|
||||
if (versionId === null) return json({ ok: false, error: 'bad version id' }, { status: 400 });
|
||||
try {
|
||||
deleteVersion(versionId);
|
||||
return json({ ok: true });
|
||||
} catch (err) {
|
||||
if (err instanceof ScriptError) {
|
||||
return json({ ok: false, error: err.message, kind: err.kind }, { status: 409 });
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ export async function GET() {
|
||||
// Derived endpoint — same logic as endpointFor() but without asking
|
||||
// Komodo. If we don't have a port yet, endpoint is null.
|
||||
endpoint: s.game_port ? { host: s.rcon_host ?? rconHost, port: s.game_port } : null,
|
||||
held: [...heldByBeta(s.name).entries()].map(([target, slug]) => ({ target, slug }))
|
||||
held: [...heldByBeta(s.name).entries()].map(([target, betaId]) => ({ target, betaId }))
|
||||
}));
|
||||
return json({
|
||||
servers,
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { getServer } from '$lib/server/fleet/servers.js';
|
||||
import { listServerScripts } from '$lib/server/fleet/inventory.js';
|
||||
|
||||
export async function GET({ params }: { params: { name: string } }) {
|
||||
const server = getServer(params.name);
|
||||
if (!server) return json({ ok: false, error: 'no such server' }, { status: 404 });
|
||||
const scripts = listServerScripts(params.name).map((r) => ({
|
||||
relPath: r.rel_path,
|
||||
scriptVersionId: r.script_version_id,
|
||||
sha256: r.sha256,
|
||||
size: r.size,
|
||||
updatedAt: r.updated_at,
|
||||
source: r.source
|
||||
}));
|
||||
return json({ scripts });
|
||||
}
|
||||
Reference in New Issue
Block a user