Trim provisioning job details (Refs #88)

This commit is contained in:
2026-10-09 14:55:24 -04:00
parent 15ab41d4a2
commit 967b2a1c8a
4 changed files with 61 additions and 25 deletions
+13
View File
@@ -170,6 +170,13 @@ Related work links back to the requesting campaign, beta, maintenance item, or
collection. A completed provisioning job does not imply that its requester has collection. A completed provisioning job does not imply that its requester has
finished its own verification. finished its own verification.
Optional timestamps appear only when recorded. **Launch files** appears for Create
and Rebuild, or when an older operation has a recorded recipe. Start, Restart,
and Stop use the existing instance without selecting new launch files.
While a job is active, **Controller activity** lists current work and elapsed
time. Background scans run independently of provisioning.
### Verification evidence ### Verification evidence
New operations record structured verification evidence alongside their activity: New operations record structured verification evidence alongside their activity:
@@ -201,6 +208,12 @@ the replacement in its reserved slot. Resuming the same operation preserves its
earlier failure and resume events. Uncertain commands have no generic Retry action. earlier failure and resume events. Uncertain commands have no generic Retry action.
Cancelling an operation does not stop a command already delivered to the host. Cancelling an operation does not stop a command already delivered to the host.
The confirmation explains that remaining work and health checks stop, while
changes already made remain. **Cancel creation and remove server** also deletes
the launch's container and instance files, even if players have joined or their
status is unknown. Shared game files are kept, and the key returns to its game
pool after confirmed removal.
A later rebuild waits for that command to settle. Its waiting message includes A later rebuild waits for that command to settle. Its waiting message includes
the host command's state and last reported error; **View cancelled operation** the host command's state and last reported error; **View cancelled operation**
opens the original command details. If an interrupted rebuild removed its old opens the original command details. If an interrupted rebuild removed its old
+2 -6
View File
@@ -138,7 +138,7 @@ export function provisioningOutcome(detail: ProvisioningDetail) {
: failure ? `${actionLabels[operation.action]} ${operation.state === 'interrupted' ? 'interrupted' : 'failed'}` : failure ? `${actionLabels[operation.action]} ${operation.state === 'interrupted' ? 'interrupted' : 'failed'}`
: operation.state === 'warning' ? `${actionLabels[operation.action]} completed with warnings` : operation.state === 'warning' ? `${actionLabels[operation.action]} completed with warnings`
: progress?.blocker?.message ?? operation.stage; : progress?.blocker?.message ?? operation.stage;
let description: string; let description: string | null;
if (detail.canResumeCleanup) description = 'The remote deployment was deleted. Resume the remaining local cleanup without repeating container removal.'; if (detail.canResumeCleanup) description = 'The remote deployment was deleted. Resume the remaining local cleanup without repeating container removal.';
else if (detail.canResumeRebuild) description = 'The remote deployment was deleted. Resume local cleanup, then create a fresh instance in the reserved server slot.'; else if (detail.canResumeRebuild) description = 'The remote deployment was deleted. Resume local cleanup, then create a fresh instance in the reserved server slot.';
else if (detail.canResumeVerification) description = 'Resume readiness checks for the existing instance. An unhealthy container may continue through this operation’s remaining restart and rebuild recovery steps.'; else if (detail.canResumeVerification) description = 'Resume readiness checks for the existing instance. An unhealthy container may continue through this operation’s remaining restart and rebuild recovery steps.';
@@ -152,11 +152,7 @@ export function provisioningOutcome(detail: ProvisioningDetail) {
? 'The remote response was uncertain. Inspect the recorded result and server before requesting another operation.' ? 'The remote response was uncertain. Inspect the recorded result and server before requesting another operation.'
: 'Review the recorded failure and execution evidence before submitting another operation.'; : 'Review the recorded failure and execution evidence before submitting another operation.';
else if (operation.state === 'warning') description = 'The operation finished. Review the recorded warnings below for any remaining issue.'; else if (operation.state === 'warning') description = 'The operation finished. Review the recorded warnings below for any remaining issue.';
else if (operation.state === 'done') description = !progress else if (operation.state === 'done') description = null;
? 'This operation was recorded as completed. Detailed verification evidence was not recorded.'
: operation.action === 'destroy' ? 'The instance removal completed. This record preserves its results after deletion.'
: operation.action === 'stop' ? 'The container was confirmed stopped. Its files and registration were retained.'
: 'The operation completed its readiness checks. These recorded results describe that attempt; current health is available on the server page.';
else if (active && progress?.phase === 'cleanup' && !progress.blocker) description = operation.action === 'rebuild' else if (active && progress?.phase === 'cleanup' && !progress.blocker) description = operation.action === 'rebuild'
? 'Remote deployment deletion was confirmed. Local cleanup must finish before a fresh instance is created in the reserved slot.' ? 'Remote deployment deletion was confirmed. Local cleanup must finish before a fresh instance is created in the reserved slot.'
: 'Remote deployment deletion was confirmed. The remaining local files and registration cleanup will continue without repeating container removal.'; : 'Remote deployment deletion was confirmed. The remaining local files and registration cleanup will continue without repeating container removal.';
+43 -17
View File
@@ -38,6 +38,9 @@
const deployment = $derived(detail?.deployment); const deployment = $derived(detail?.deployment);
const startedAt = $derived(timestampISO(deployment?.started_at)); const startedAt = $derived(timestampISO(deployment?.started_at));
const finishedAt = $derived(timestampISO(deployment?.finished_at)); const finishedAt = $derived(timestampISO(deployment?.finished_at));
const submittedAt = $derived(timestampISO(deployment?.submitted_at));
const observedAt = $derived(timestampISO(detail?.progress?.observedAt));
const phaseStartedAt = $derived(timestampISO(detail?.progress?.phaseStartedAt));
const canOverride = $derived(data.user?.role === 'admin' && detail?.canOverrideSafety); const canOverride = $derived(data.user?.role === 'admin' && detail?.canOverrideSafety);
const outcome = $derived(detail ? provisioningOutcome(detail) : null); const outcome = $derived(detail ? provisioningOutcome(detail) : null);
const active = $derived(outcome?.active ?? false); const active = $derived(outcome?.active ?? false);
@@ -83,6 +86,28 @@
if (confirmed && view.id === id && canOverride) await controller.act('override', 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 }); 'Admin override enabled for this operation. Provisioning will proceed without player safety checks when the remaining requirements are met.', { confirmed: true });
} }
async function cancelOperation() {
if (!deployment || !detail?.canCancel || view.busy) return;
const id = deployment.id;
const confirmed = await confirmAction({
title: 'Cancel operation?',
message: `Stop remaining work and health checks for ${serverTitle(deployment.server)} (${deployment.server}). Changes already made are not undone. Commands already sent to the host may still finish.`,
confirmLabel: 'Cancel operation', danger: true
});
if (confirmed && view.id === id && detail?.canCancel) await controller.act('cancel',
'Operation cancelled. Already-submitted commands may finish; no further steps will be started.');
}
async function cancelCreation() {
if (!deployment || !detail?.canCancelCreation || view.busy) return;
const id = deployment.id;
const confirmed = await confirmAction({
title: 'Cancel creation and remove server?',
message: `Stop this launch and delete the container and instance files for ${serverTitle(deployment.server)} (${deployment.server}), even if player status is unknown or players have joined. Shared game files are kept. The key returns to its game pool after removal is confirmed.`,
confirmLabel: 'Cancel creation and remove server', danger: true
});
if (confirmed && view.id === id && detail?.canCancelCreation) await controller.act('cancel-creation',
'Creation cancelled. Follow cleanup below until removal is confirmed.');
}
async function copyLink() { async function copyLink() {
copyStatus = ''; copyStatus = '';
try { try {
@@ -125,7 +150,7 @@
<section class="outcome" class:attention={['failed', 'interrupted', 'warning'].includes(deployment.state)} aria-labelledby="outcome-title"> <section class="outcome" class:attention={['failed', 'interrupted', 'warning'].includes(deployment.state)} aria-labelledby="outcome-title">
<div class="badges"><StatusBadge state={deployment.state} />{#if deployment.server_deleted_at}<Badge>Deleted identity</Badge>{/if}{#if detail.progress?.recovery?.state === 'held'}<Badge variant="warning">Recovery capacity held</Badge>{/if}</div> <div class="badges"><StatusBadge state={deployment.state} />{#if deployment.server_deleted_at}<Badge>Deleted identity</Badge>{/if}{#if detail.progress?.recovery?.state === 'held'}<Badge variant="warning">Recovery capacity held</Badge>{/if}</div>
<div class="outcome-message" aria-live="polite" aria-atomic="true"> <div class="outcome-message" aria-live="polite" aria-atomic="true">
<h2 id="outcome-title">{outcome.headline}</h2><p>{outcome.description}</p> <h2 id="outcome-title">{outcome.headline}</h2>{#if outcome.description}<p>{outcome.description}</p>{/if}
{#if outcome.checksSummary}<p><span aria-hidden="true">✓</span>{' '}{outcome.checksSummary}</p>{/if} {#if outcome.checksSummary}<p><span aria-hidden="true">✓</span>{' '}{outcome.checksSummary}</p>{/if}
</div> </div>
{#if deployment.error}<div class="failure" role="alert"><strong>Recorded error</strong><p>{deployment.error}</p></div>{/if} {#if deployment.error}<div class="failure" role="alert"><strong>Recorded error</strong><p>{deployment.error}</p></div>{/if}
@@ -135,13 +160,13 @@
{#if detail.safetyOverride}<div class="warnings"><h3>Admin override enabled</h3><p>Player safety checks bypassed for this operation. Confirmed by {detail.safetyOverride.actorName} · {@render dateTime(detail.safetyOverride.confirmedAt)}.</p></div>{/if} {#if detail.safetyOverride}<div class="warnings"><h3>Admin override enabled</h3><p>Player safety checks bypassed for this operation. Confirmed by {detail.safetyOverride.actorName} · {@render dateTime(detail.safetyOverride.confirmedAt)}.</p></div>{/if}
{#if detail.canResumeVerification || detail.canResumeCleanup || detail.canResumeRebuild || detail.canRetryWithRebuild || detail.canCancel || detail.canCancelCreation || canOverride} {#if detail.canResumeVerification || detail.canResumeCleanup || detail.canResumeRebuild || detail.canRetryWithRebuild || detail.canCancel || detail.canCancelCreation || canOverride}
<div class="recovery-actions"> <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 canOverride}<div><Button variant="danger" disabled={!!view.busy || !!detail.safetyOverrideUnavailableReason} loading={view.busy === 'override'} onclick={overrideSafety}>Admin override…</Button>{#if detail.safetyOverrideUnavailableReason}<p class="action-help">{detail.safetyOverrideUnavailableReason}</p>{/if}</div>{/if}
{#if detail.canResumeVerification}<Button variant="primary" loading={view.busy === 'resume'} disabled={!!view.busy} onclick={() => controller.act('resume', 'Health verification requested. An unhealthy container may continue through this operation’s remaining recovery steps.')}>Resume health verification</Button>{/if} {#if detail.canResumeVerification}<Button variant="primary" loading={view.busy === 'resume'} disabled={!!view.busy} onclick={() => controller.act('resume', 'Health verification requested. An unhealthy container may continue through this operation’s remaining recovery steps.')}>Resume health verification</Button>{/if}
{#if detail.canRetryWithRebuild}<div><Button variant="primary" loading={view.busy === 'retry-rebuild'} disabled={!!view.busy} onclick={retryWithRebuild}>Retry with rebuild</Button><p class="action-help">Starts a new rebuild for the same lobby and port. Deletes instance files and uses the latest promoted setup.</p></div>{/if} {#if detail.canRetryWithRebuild}<Button variant="primary" loading={view.busy === 'retry-rebuild'} disabled={!!view.busy} onclick={retryWithRebuild}>Retry with rebuild</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.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} {#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}
{#if detail.canCancel}<div><Button disabled={!!view.busy} loading={view.busy === 'cancel'} onclick={() => controller.act('cancel', 'Operation cancelled. Already-submitted commands may finish; no further steps will be started.')}>Cancel operation</Button><p class="action-help">Stops remaining work and health checks. Changes already made are not undone.</p></div>{/if} {#if detail.canCancel}<Button disabled={!!view.busy} loading={view.busy === 'cancel'} onclick={cancelOperation}>Cancel operation</Button>{/if}
{#if detail.canCancelCreation}<div><Button variant="danger" disabled={!!view.busy} loading={view.busy === 'cancel-creation'} onclick={() => controller.act('cancel-creation', 'Creation cancelled. Follow cleanup below until removal is confirmed.')}>Cancel creation and remove server</Button><p class="action-help">Stops this launch and deletes its container and instance files, even if player status is unknown or players have joined. Shared game files are kept. The key returns to its game pool after removal is confirmed.</p></div>{/if} {#if detail.canCancelCreation}<Button variant="danger" disabled={!!view.busy} loading={view.busy === 'cancel-creation'} onclick={cancelCreation}>Cancel creation and remove server</Button>{/if}
</div> </div>
{/if} {/if}
{#if detail.cancellation}<div class="recovery-note"><strong>{detail.cancellation.state === 'done' ? 'Cancellation cleanup completed' : 'Cancellation cleanup'}</strong><p>{detail.cancellation.error ?? detail.cancellation.stage}</p><a href={`/provisioning/${encodeURIComponent(detail.cancellation.cleanupId)}`}>View cleanup progress →</a></div>{/if} {#if detail.cancellation}<div class="recovery-note"><strong>{detail.cancellation.state === 'done' ? 'Cancellation cleanup completed' : 'Cancellation cleanup'}</strong><p>{detail.cancellation.error ?? detail.cancellation.stage}</p><a href={`/provisioning/${encodeURIComponent(detail.cancellation.cleanupId)}`}>View cleanup progress →</a></div>{/if}
@@ -150,10 +175,12 @@
{#if detail.progress?.blocker?.href}<a class="dependency" href={detail.progress.blocker.href}>{detail.progress.blocker.label ?? 'View blocking work'} →</a>{/if} {#if detail.progress?.blocker?.href}<a class="dependency" href={detail.progress.blocker.href}>{detail.progress.blocker.label ?? 'View blocking work'} →</a>{/if}
{#if detail.progress?.recovery}<div class="recovery-note"><strong>{detail.progress.recovery.state === 'held' ? 'Recovery remains in progress' : detail.progress.recovery.state === 'released' ? 'Recovery capacity released' : detail.progress.recovery.state === 'transferred' ? 'Recovery transferred' : 'Recovery state not confirmed'}</strong><p>{detail.progress.recovery.message}</p><span>Recorded {@render dateTime(detail.progress.recovery.at)}</span>{#if detail.progress.recovery.operationId && detail.progress.recovery.operationId !== deployment.id}<a href={`/provisioning/${encodeURIComponent(detail.progress.recovery.operationId)}`}>View recovery operation →</a>{/if}</div>{/if} {#if detail.progress?.recovery}<div class="recovery-note"><strong>{detail.progress.recovery.state === 'held' ? 'Recovery remains in progress' : detail.progress.recovery.state === 'released' ? 'Recovery capacity released' : detail.progress.recovery.state === 'transferred' ? 'Recovery transferred' : 'Recovery state not confirmed'}</strong><p>{detail.progress.recovery.message}</p><span>Recorded {@render dateTime(detail.progress.recovery.at)}</span>{#if detail.progress.recovery.operationId && detail.progress.recovery.operationId !== deployment.id}<a href={`/provisioning/${encodeURIComponent(detail.progress.recovery.operationId)}`}>View recovery operation →</a>{/if}</div>{/if}
{#if serverHref && !['done', 'cancelled'].includes(deployment.state)}<div class="diagnostics"><a href={`${serverHref}?tab=logs`}>Open server logs</a><a href={`${serverHref}?tab=diagnostics`}>Diagnose connection</a>{#if detail.progress?.phase === 'verify-map' || detail.progress?.checks.some(check => check.key === 'map' && check.result !== 'pass')}<a href={`${serverHref}?tab=config`}>Review map configuration</a>{/if}</div>{/if} {#if serverHref && !['done', 'cancelled'].includes(deployment.state)}<div class="diagnostics"><a href={`${serverHref}?tab=logs`}>Open server logs</a><a href={`${serverHref}?tab=diagnostics`}>Diagnose connection</a>{#if detail.progress?.phase === 'verify-map' || detail.progress?.checks.some(check => check.key === 'map' && check.result !== 'pass')}<a href={`${serverHref}?tab=config`}>Review map configuration</a>{/if}</div>{/if}
<div class="timing"> {#if (active && phaseStartedAt) || observedAt}
{#if outcome.active}<div><span>Time in phase</span><strong>{elapsedTime(timestampISO(detail.progress?.phaseStartedAt), now)}</strong></div>{/if} <div class="timing">
<div><span>Last evaluated</span>{@render dateTime(detail.progress?.observedAt)}</div> {#if active && phaseStartedAt}<div><span>Time in phase</span><strong>{elapsedTime(phaseStartedAt, now)}</strong></div>{/if}
</div> {#if observedAt}<div><span>Last evaluated</span>{@render dateTime(observedAt)}</div>{/if}
</div>
{/if}
</section> </section>
{#key deployment.id} {#key deployment.id}
@@ -166,37 +193,36 @@
{#if outcome.active && detail.workerActivity} {#if outcome.active && detail.workerActivity}
<Card as="section" padding="md" ariaLabel="Controller activity"><div class="context-section"> <Card as="section" padding="md" ariaLabel="Controller activity"><div class="context-section">
<h2>Controller activity</h2> <h2>Controller activity</h2>
<p>Current work and elapsed time. Background scans run independently of provisioning.</p>
{#if detail.workerActivity.length}<ul class="related"> {#if detail.workerActivity.length}<ul class="related">
{#each detail.workerActivity as work (work.key)}<li><strong>{work.label}</strong><span>{elapsedTime(work.startedAt, Date.parse(detail.generatedAt))}</span></li>{/each} {#each detail.workerActivity as work (work.key)}<li><strong>{work.label}</strong><span>{elapsedTime(work.startedAt, Date.parse(detail.generatedAt))}</span></li>{/each}
</ul>{:else}<p>Waiting for the next provisioning pass.</p>{/if} </ul>{:else}<p>Waiting for the next provisioning pass.</p>{/if}
</div></Card> </div></Card>
{/if} {/if}
{#if detail.sources.length || detail.collection}<Card as="section" padding="md" ariaLabel="Related work"><div class="context-section"><h2>Related work</h2><p>The requesting work may have further checks after this operation finishes.</p><ul class="related"> {#if detail.sources.length || detail.collection}<Card as="section" padding="md" ariaLabel="Related work"><div class="context-section"><h2>Related work</h2><ul class="related">
{#if detail.collection}<li><a href={`/collections/launches/${encodeURIComponent(detail.collection.id)}`}>{detail.collection.name}</a><span>Collection launch</span></li>{/if} {#if detail.collection}<li><a href={`/collections/launches/${encodeURIComponent(detail.collection.id)}`}>{detail.collection.name}</a><span>Collection launch</span></li>{/if}
{#each detail.sources as source (`${source.kind}:${source.source_id}`)}<li>{#if source.href}<a href={source.href}>{source.label}</a>{:else}<strong>{source.label}</strong><span>Source record no longer available</span>{/if}</li>{/each} {#each detail.sources as source (`${source.kind}:${source.source_id}`)}<li>{#if source.href}<a href={source.href}>{source.label}</a>{:else}<strong>{source.label}</strong><span>Source record no longer available</span>{/if}</li>{/each}
</ul></div></Card>{/if} </ul></div></Card>{/if}
{#if detail.capacity.pools.length}<Card as="section" padding="md" ariaLabel="Capacity"><div class="context-section"><h2>Capacity</h2> {#if detail.capacity.pools.length}<Card as="section" padding="md" ariaLabel="Capacity"><div class="context-section"><h2>Capacity</h2>
{#each detail.capacity.pools as pool (pool.kind)}<div class="capacity-pool"><div class="capacity-heading"><strong>{pool.kind === 'startup' ? 'Startup slots' : 'Restart recovery slots'}</strong><span>{pool.used} / {pool.limit} in use</span></div> {#each detail.capacity.pools as pool (pool.kind)}<div class="capacity-pool"><div class="capacity-heading"><strong>{pool.kind === 'startup' ? 'Startup slots' : 'Restart recovery slots'}</strong><span>{pool.used} / {pool.limit} in use</span></div>
{#if pool.ownsSlot}<p>This operation holds a slot.</p>{/if} {#if pool.ownsSlot}<p>This operation holds a slot.</p>{/if}
{#if pool.requestOrder !== null}<p>Request order: {pool.requestOrder}. Eligible requests may pass blocked work; this is not a guaranteed start order.</p>{/if} {#if pool.requestOrder !== null}<p title="Eligible requests may pass blocked work; this is not a guaranteed start order.">Request order: {pool.requestOrder}</p>{/if}
{#if pool.holders.length}<details><summary>Current slot holders</summary><ul>{#each pool.holders as holder}<li>{#if holder.id}<a href={`/provisioning/${encodeURIComponent(holder.id)}`}>{serverTitle(holder.server)}</a>{:else}<span>{serverTitle(holder.server)} · operation not recorded</span>{/if}</li>{/each}</ul></details>{/if} {#if pool.holders.length}<details><summary>Current slot holders</summary><ul>{#each pool.holders as holder}<li>{#if holder.id}<a href={`/provisioning/${encodeURIComponent(holder.id)}`}>{serverTitle(holder.server)}</a>{:else}<span>{serverTitle(holder.server)} · operation not recorded</span>{/if}</li>{/each}</ul></details>{/if}
</div>{/each}<p class="small">Capacity snapshot <RelativeTime value={detail.capacity.evaluatedAt} missing="Not recorded" /></p> </div>{/each}<p class="small">Capacity snapshot <RelativeTime value={detail.capacity.evaluatedAt} missing="Not recorded" /></p>
</div></Card>{/if} </div></Card>{/if}
<Card as="section" padding="md" ariaLabel="Launch files"><div class="context-section recipe"><h2>Launch files</h2> {#if detail.recipe || ['create', 'rebuild'].includes(deployment.action)}<Card as="section" padding="md" ariaLabel="Launch files"><div class="context-section recipe"><h2>Launch files</h2>
{#if detail.recipe} {#if detail.recipe}
{#if detail.blueprintName}<p>From {detail.blueprintName}</p>{/if} {#if detail.blueprintName}<p>From {detail.blueprintName}</p>{/if}
<p class="small">{recipeSelection} · <RelativeTime value={detail.recipe.capturedAt} missing="Capture time not recorded" /></p> <p class="small">{recipeSelection} · <RelativeTime value={detail.recipe.capturedAt} missing="Capture time not recorded" /></p>
<LaunchRecipe recipe={detail.recipe} historical /> <LaunchRecipe recipe={detail.recipe} historical />
{:else}<p>{['create', 'rebuild'].includes(deployment.action) ? 'No launch recipe was recorded for this operation.' : 'This operation uses the existing instance. No new launch files were selected.'}</p>{/if} {:else}<p>No launch recipe was recorded for this operation.</p>{/if}
</div></Card> </div></Card>{/if}
<details class="technical-details"> <details class="technical-details">
<summary>Technical details</summary> <summary>Technical details</summary>
<div class="technical-content"> <div class="technical-content">
<dl class="metadata"> <dl class="metadata">
<div><dt>Operation ID</dt><dd><CopyValue value={deployment.id} label="operation ID" /></dd></div> <div><dt>Operation ID</dt><dd><CopyValue value={deployment.id} label="operation ID" /></dd></div>
{#if !active || finishedAt}<div><dt>Finished</dt><dd>{#if finishedAt}<time datetime={finishedAt}>{$timeFormat.dateTime(finishedAt)}</time>{:else}Not recorded{/if}</dd></div>{/if} {#if finishedAt}<div><dt>Finished</dt><dd>{@render dateTime(finishedAt)}</dd></div>{/if}
<div><dt>Submission attempted</dt><dd>{@render dateTime(deployment.submitted_at)}</dd></div> {#if submittedAt}<div><dt>Submission attempted</dt><dd>{@render dateTime(submittedAt)}</dd></div>{/if}
{#if deployment.server_deleted_at}<div><dt>Identity deleted</dt><dd>{@render dateTime(deployment.server_deleted_at)}</dd></div>{/if} {#if deployment.server_deleted_at}<div><dt>Identity deleted</dt><dd>{@render dateTime(deployment.server_deleted_at)}</dd></div>{/if}
{#if detail.agentOperation}<div><dt>Agent command ID</dt><dd><CopyValue value={detail.agentOperation.id} label="agent command ID" /></dd></div>{/if} {#if detail.agentOperation}<div><dt>Agent command ID</dt><dd><CopyValue value={detail.agentOperation.id} label="agent command ID" /></dd></div>{/if}
{#if detail.collection}<div><dt>Collection launch ID</dt><dd><CopyValue value={detail.collection.id} label="collection launch ID" /></dd></div>{/if} {#if detail.collection}<div><dt>Collection launch ID</dt><dd><CopyValue value={detail.collection.id} label="collection launch ID" /></dd></div>{/if}
+3 -2
View File
@@ -145,7 +145,8 @@ describe('provisioning outcome and evidence presentation', () => {
expect(provisioningGroups(detail).flatMap(group => group.steps).every(step => step.state === 'unknown')).toBe(true); expect(provisioningGroups(detail).flatMap(group => group.steps).every(step => step.state === 'unknown')).toBe(true);
detail.progress = null; detail.progress = null;
expect(provisioningGroups(detail)).toEqual([]); expect(provisioningGroups(detail)).toEqual([]);
expect(provisioningOutcome(detail).description).toContain('evidence was not recorded'); expect(provisioningOutcome(detail).description).toBeNull();
expect(render(ProvisioningProgressView, { props: { detail } }).body).toContain('Detailed checkpoints were not recorded');
}); });
it('shows passed container and telemetry game checks while waiting on the engine map', () => { it('shows passed container and telemetry game checks while waiting on the engine map', () => {
@@ -161,7 +162,7 @@ describe('provisioning outcome and evidence presentation', () => {
expect(steps.find(step => step.key === 'verify-map')?.state).toBe('waiting'); expect(steps.find(step => step.key === 'verify-map')?.state).toBe('waiting');
expect(steps.find(step => step.key === 'verify-game')?.label).not.toContain('RCON'); expect(steps.find(step => step.key === 'verify-game')?.label).not.toContain('RCON');
detail.deployment.state = 'done'; detail.deployment.state = 'done';
expect(provisioningOutcome(detail).description).not.toContain('RCON'); expect(provisioningOutcome(detail).description).toBeNull();
}); });
it('newer unknown readiness evidence supersedes an old completion checkpoint', () => { it('newer unknown readiness evidence supersedes an old completion checkpoint', () => {