Remove key entry from launch dialogs

This commit is contained in:
2026-10-07 13:24:00 -04:00
parent ec45371478
commit bd7d1ed86f
4 changed files with 48 additions and 65 deletions
+13 -17
View File
@@ -25,7 +25,6 @@
let preview = $state<LaunchPreview | null>(null);
let hostSelection = $state<NativeDeploymentSelection>({ libraryIds: {}, image: '' });
let hostReady = $state(false);
let plutoKey = $state('');
let busy = $state(false);
let loaded = $state(false);
let error = $state('');
@@ -35,7 +34,7 @@
let saving = $state(false);
let wasOpen = false;
let session = 0;
let keyNotice = $state('');
let lobbyNotice = $state('');
let selectionNotice = $state('');
let now = $state(Date.now());
let heading: HTMLHeadingElement;
@@ -50,7 +49,7 @@
async function initialize() {
const current = ++session;
preview = null; plutoKey = ''; error = ''; loaded = false; busy = false; keyNotice = ''; selectionNotice = '';
preview = null; error = ''; loaded = false; busy = false; lobbyNotice = ''; selectionNotice = '';
blueprintName = ''; description = ''; savedName = ''; saving = false;
blueprintId = initialBlueprintId;
game = initialGame;
@@ -77,7 +76,7 @@
function selectionChanged() {
if (preview) selectionNotice = 'The launch settings or game files changed. Review the launch again with the latest files.';
preview = null; plutoKey = ''; keyNotice = '';
preview = null; lobbyNotice = '';
}
async function review() {
@@ -88,7 +87,7 @@
if (!hostReady) { error = 'Wait for a host to finish preparing the latest game files and runtime.'; return; }
const current = session;
const previousName = preview?.name;
busy = true; error = ''; savedName = ''; keyNotice = ''; selectionNotice = '';
busy = true; error = ''; savedName = ''; lobbyNotice = ''; selectionNotice = '';
try {
const res = await fetch('/api/provisioning/preview', { method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...(sourceDeploymentId ? { deploymentId: sourceDeploymentId } : { game, mapSlug }),
@@ -97,8 +96,7 @@
const body = await res.json();
if (!res.ok) throw new Error(body.error ?? 'Could not prepare launch preview.');
if (current === session) {
if (previousName && previousName !== body.name) keyNotice = 'The available lobby changed. Any entered key can still be used for this game.';
if (body.hasSavedKey) plutoKey = '';
if (previousName && previousName !== body.name) lobbyNotice = 'The available lobby changed. Review the updated server before launching.';
preview = body; now = Date.now();
await tick(); heading?.focus();
}
@@ -107,12 +105,12 @@
}
async function launch() {
if (!preview || !preview.ready || !hostReady || expired || busy) return;
if (!preview || !preview.ready || !preview.hasSavedKey || !hostReady || expired || busy || saving) return;
busy = true; error = '';
try {
const res = await fetch('/api/servers', { method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ game: preview.recipe.game, gameMode: 'zombie', mapSlug: preview.recipe.mapSlug,
expectedName: preview.name, previewId: preview.id, plutoKey: plutoKey.trim() || undefined }) });
expectedName: preview.name, previewId: preview.id }) });
const body = await res.json();
if (!res.ok || !body.ok) throw new Error(body.error ?? 'Could not queue this launch.');
open = false;
@@ -147,8 +145,8 @@
<p class="muted">{error ? 'Your launch choices could not be loaded.' : 'Loading your maps and collections…'}</p>
{#if error}<Button onclick={initialize}>Try again</Button>{/if}
{:else if preview}
{#if keyNotice}<p role="status" class="muted">{keyNotice}</p>{/if}
{#if expired}<p class="error" role="status">This preview expired. Refresh it to recheck capacity. Entered keys stay with this game when you refresh.</p>{/if}
{#if lobbyNotice}<p role="status" class="muted">{lobbyNotice}</p>{/if}
{#if expired}<p class="error" role="status">This preview expired. Refresh it to recheck capacity and key availability.</p>{/if}
<section class="recipe-panel"><LaunchRecipe recipe={preview.recipe} />{#if preview.blueprintName}<p class="muted">From collection: {preview.blueprintName}</p>{/if}</section>
<section class="preflight"><div class="section-title"><h3>Before you launch</h3><button class="text-button" disabled={busy || saving} onclick={review}>Refresh preview</button></div>
<ul>{#each preview.checks as check}<li class:blocked={check.state === 'blocked'}><span class="check" aria-label={check.state}>{check.state === 'pass' ? '✓' : check.state === 'blocked' ? '!' : 'i'}</span><div><strong>{check.label}</strong><p>{check.detail}</p></div></li>{/each}</ul>
@@ -156,11 +154,9 @@
<form onsubmit={event => { event.preventDefault(); void launch(); }}>
<div class="identity"><span class="eyebrow">Your new server</span><strong>{preview.name ?? 'Waiting for an available lobby'}</strong><p>An available key from this game’s pool is assigned when you launch.</p></div>
{#if preview.hasSavedKey}<p class="key-ready"><span aria-hidden="true">✓</span> Game key available</p>
{:else}<label>Plutonium server key<input type="password" bind:value={plutoKey} disabled={busy || saving || !preview.name} maxlength="512" autocomplete="off" placeholder="Paste an unused key for this game" /></label>{/if}
<p class="muted">{preview.hasSavedKey ? 'An unused key from this game’s pool will be assigned automatically. Availability is checked again at launch.' : 'Add an unused key for this game. Launch saves it in the game pool and assigns it to this server until removal is confirmed.'} <a href={`/server-keys?game=${preview.recipe.game}`} target="_blank" rel="noopener">Manage saved keys in a new tab</a>; your launch stays open here.</p>
<div class="launch-actions"><Button variant="ghost" disabled={busy || saving} onclick={() => { preview = null; plutoKey = ''; error = ''; }}>Back</Button><Button type="submit" variant="primary" loading={busy} disabled={busy || saving || expired || !hostReady || !preview.ready || (!preview.hasSavedKey && !plutoKey.trim())}>Launch server</Button></div>
{#if !preview.hasSavedKey && !plutoKey.trim()}<p class="muted">Enter an unused key for this game to enable launch.</p>{/if}
<p class="caption">Preview valid until {$timeFormat.minuteTime(preview.expiresAt)}. Capacity is checked again at launch. The host and port are assigned automatically.</p>
{:else}<p class="muted" role="status">No unused {preview.recipe.game.toUpperCase()} keys are available. Add a key in <a href={`/server-keys?game=${preview.recipe.game}`} target="_blank" rel="noopener">Keys (opens in a new tab)</a>, then Refresh preview to enable launch.</p>{/if}
<div class="launch-actions"><Button variant="ghost" disabled={busy || saving} onclick={() => { preview = null; error = ''; }}>Back</Button><Button type="submit" variant="primary" loading={busy} disabled={busy || saving || expired || !hostReady || !preview.ready || !preview.hasSavedKey}>Launch server</Button></div>
<p class="caption">Preview valid until {$timeFormat.minuteTime(preview.expiresAt)}. Capacity and key availability are checked again at launch. The host and port are assigned automatically.</p>
</form>
<details class="save"><summary>Make this a reusable collection</summary><p class="muted">Save this game and map with a target of one lobby. Future launches use the latest promoted setup and create only missing lobbies.</p>
{#if savedName}<p class="success" role="status">“{savedName}” is ready in your collection library.</p>{:else}
@@ -172,7 +168,7 @@
{:else}
<form onsubmit={event => { event.preventDefault(); void review(); }}>
<label>Start with<select bind:value={blueprintId} disabled={busy}><option value="">Current promoted setup</option>{#each blueprints as blueprint}<option value={blueprint.id}>{blueprint.name} · {blueprint.totalServers} server{blueprint.totalServers === 1 ? '' : 's'}</option>{/each}</select></label>
{#if selectedBlueprint}<section class="recipe-panel"><h3>{selectedBlueprint.totalServers} target server{selectedBlueprint.totalServers === 1 ? '' : 's'}</h3>{#each selectedBlueprint.entries as entry}<p>{entry.count}× {entry.mapName} · {entry.game.toUpperCase()}</p>{/each}<p class="muted">Review missing servers and their keys together. Every new server uses the latest promoted scripts, plugins, and configuration.</p>{#if selectedBlueprint.description}<p class="muted">{selectedBlueprint.description}</p>{/if}</section>
{#if selectedBlueprint}<section class="recipe-panel"><h3>{selectedBlueprint.totalServers} target server{selectedBlueprint.totalServers === 1 ? '' : 's'}</h3>{#each selectedBlueprint.entries as entry}<p>{entry.count}× {entry.mapName} · {entry.game.toUpperCase()}</p>{/each}<p class="muted">Review missing servers and game key availability together. Every new server uses the latest promoted scripts, plugins, and configuration.</p>{#if selectedBlueprint.description}<p class="muted">{selectedBlueprint.description}</p>{/if}</section>
{:else}<p class="muted">Pick a map to review its current promoted scripts, plugins, and configuration. You can save the map as a collection for future launches.</p><div class="fields"><label>Game<select bind:value={game} disabled={busy} onchange={event => mapSlug = catalogs[event.currentTarget.value as ZombieGame][0]?.slug ?? ''}><option value="t5">Black Ops I (T5)</option><option value="t6">Black Ops II (T6)</option></select></label><label>Map<select bind:value={mapSlug} disabled={busy}>{#each maps as map}<option value={map.slug}>{map.friendly}</option>{/each}</select></label></div>{/if}
<div class="promise"><strong>Know what you’re launching.</strong><p>Review the current promoted setup, available lobby, and launch prerequisites before creating your server.</p></div>
<div class="launch-actions"><Button variant="ghost" disabled={busy} onclick={() => open = false}>Cancel</Button><Button type="submit" variant="primary" loading={busy} disabled={busy || (!blueprintId && (!mapSlug || !hostReady))}>Preview launch →</Button></div>