Add confirmed admin override for provisioning player checks

This commit is contained in:
2026-10-05 18:16:02 -04:00
parent 4b0f341d64
commit 8abe71b915
20 changed files with 362 additions and 26 deletions
@@ -0,0 +1,16 @@
import { json } from '@sveltejs/kit';
import { authBody, authError } from '$lib/server/auth/http.js';
import { enableSafetyOverride } from '$lib/server/provisioning/safety-override.js';
import { kickProvisioning } from '$lib/server/pipeline/worker.js';
import type { RequestHandler } from './$types';
export const POST: RequestHandler = async ({ params, request, locals }) => {
if (!locals.user || locals.user.role !== 'admin') return json({ error: 'Administrator access is required.' }, { status: 403 });
let body: Record<string, unknown>;
try { body = await authBody(request); } catch (error) { return authError(error); }
if (body.confirmed !== true) return json({ error: 'Confirm the admin override before continuing.' }, { status: 400 });
const result = enableSafetyOverride(params.id, locals.user);
if (!result.ok) return json({ error: result.error }, { status: 409 });
void kickProvisioning();
return json({ ok: true }, { status: 202 });
};
+18 -1
View File
@@ -8,6 +8,7 @@
import { pollVisible } from '$ui/poll.js';
import { serverTitle } from '$ui/server-labels.js';
import { createProvisioningRefresh, type ProvisioningRefreshState } from '$ui/provisioning-refresh.js';
import { confirmAction } from '$ui/confirm.js';
import { elapsedTime, isProvisioningDetail, provisioningOutcome, provisioningReturn } from '$ui/provisioning-presentation.js';
import SectionHeader from '$ui/components/primitives/SectionHeader.svelte';
import Breadcrumbs from '$ui/components/primitives/Breadcrumbs.svelte';
@@ -33,6 +34,7 @@
const controller = createProvisioningRefresh<ProvisioningDetail>({ validate: isProvisioningDetail, onChange: value => { view = value; } });
const detail = $derived(view.detail);
const deployment = $derived(detail?.deployment);
const canOverride = $derived(data.user?.role === 'admin' && detail?.canOverrideSafety);
const outcome = $derived(detail ? provisioningOutcome(detail) : null);
const returnTo = $derived.by(() => {
const requested = $page.url.searchParams.get('returnTo');
@@ -56,6 +58,19 @@
onMount(() => pollVisible(() => { now = Date.now(); return controller.refresh(); }, 5000, false));
onDestroy(() => controller.dispose());
function fmt(value: string | null | undefined) { return value ? $timeFormat.dateTime(value) : 'Not recorded'; }
async function overrideSafety() {
if (!deployment || !canOverride || view.busy || detail?.safetyOverrideUnavailableReason) return;
const id = deployment.id;
const action = actionLabels[deployment.action];
const destructive = ['rebuild', 'destroy'].includes(deployment.action);
const confirmed = await confirmAction({
title: `Admin override: ${action.toLowerCase()} server?`,
message: `${action} ${serverTitle(deployment.server)} (${deployment.server}) without waiting for a fresh player count or an empty server. This can disconnect active players and interrupt their game.${destructive ? '\n\nThis action deletes the container and all instance files.' : ''}\n\nThis override applies only to this operation and records your administrator identity. Capacity, operation conflicts, file validation, and post-action verification still apply.`,
confirmLabel: `Override and ${action.toLowerCase()}`, danger: true
});
if (confirmed && view.id === id && canOverride) await controller.act('override',
'Admin override enabled for this operation. Provisioning will proceed without player safety checks when the remaining requirements are met.', { confirmed: true });
}
async function copyLink() {
copyStatus = '';
try {
@@ -89,8 +104,10 @@
{#if detail.activity.failureExcerpt && detail.activity.failureExcerpt.message !== deployment.error && ['failed', 'interrupted'].includes(deployment.state)}<p class="failure-excerpt"><span>Last failure event · {fmt(detail.activity.failureExcerpt.created_at)}</span>{detail.activity.failureExcerpt.message}</p>{/if}
{#if detail.warningMessages.length}<div class="warnings"><h3>Recorded warnings</h3><ul>{#each detail.warningMessages as warning}<li>{warning}</li>{/each}</ul></div>{/if}
{#if detail.canResumeVerification || detail.canResumeCleanup || detail.canResumeRebuild || detail.canCancel || detail.canCancelCreation}
{#if detail.safetyOverride}<div class="warnings"><h3>Admin override enabled</h3><p>Player safety checks bypassed for this operation. Confirmed by {detail.safetyOverride.actorName} · {fmt(detail.safetyOverride.confirmedAt)}.</p></div>{/if}
{#if detail.canResumeVerification || detail.canResumeCleanup || detail.canResumeRebuild || detail.canCancel || detail.canCancelCreation || canOverride}
<div class="recovery-actions">
{#if canOverride}<div><Button variant="danger" disabled={!!view.busy || !!detail.safetyOverrideUnavailableReason} loading={view.busy === 'override'} onclick={overrideSafety}>Admin override…</Button><p class="action-help">{detail.safetyOverrideUnavailableReason ?? 'Confirm to bypass player safety checks for this operation.'}</p></div>{/if}
{#if detail.canResumeVerification}<Button variant="primary" loading={view.busy === 'resume'} disabled={!!view.busy} onclick={() => controller.act('resume', 'Health verification requested. The existing instance will be observed without another creation or restart.')}>Resume health verification</Button>{/if}
{#if detail.canResumeRebuild}<Button variant="primary" loading={view.busy === 'resume'} disabled={!!view.busy} onclick={() => controller.act('resume', 'Rebuild cleanup requested. Fresh creation waits for local cleanup to finish.')}>Resume rebuild cleanup</Button>{/if}
{#if detail.canResumeCleanup}<Button variant="primary" loading={view.busy === 'resume'} disabled={!!view.busy} onclick={() => controller.act('resume', 'Local cleanup requested. Remote container deletion will not be repeated.')}>Resume cleanup</Button>{/if}