This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
import { page } from '$app/stores';
|
||||
import SectionHeader from '$ui/components/primitives/SectionHeader.svelte';
|
||||
import Breadcrumbs from '$ui/components/primitives/Breadcrumbs.svelte';
|
||||
import Badge from '$ui/components/primitives/Badge.svelte';
|
||||
import ProvisioningStatusBadge from '$ui/components/ProvisioningStatusBadge.svelte';
|
||||
import Button from '$ui/components/primitives/Button.svelte';
|
||||
import type { CollectionDetail } from '$lib/deployment-plan.js';
|
||||
|
||||
@@ -64,28 +64,10 @@
|
||||
const firstQueued = $derived(
|
||||
detail?.servers.find((server) => !server.server_deleted_at && server.state === 'queued')
|
||||
);
|
||||
const labels: Record<string, string> = {
|
||||
queued: 'Queued',
|
||||
running: 'Launching',
|
||||
'awaiting-health': 'Checking readiness',
|
||||
done: 'Ready',
|
||||
warning: 'Needs review',
|
||||
failed: 'Failed',
|
||||
interrupted: 'Interrupted',
|
||||
waiting: 'Waiting',
|
||||
cancelled: 'Cancelled'
|
||||
};
|
||||
const waitingForPort = (server: CollectionServer) =>
|
||||
server.state === 'queued' &&
|
||||
(server.stage.startsWith('Waiting for removal') ||
|
||||
server.stage.startsWith('Waiting for port'));
|
||||
function stateLabel(server: CollectionServer) {
|
||||
if (waitingForPort(server)) return 'Waiting for removal';
|
||||
if (server.action === 'destroy' && server.state === 'done') return 'Removed';
|
||||
if (server.action === 'destroy' && server.state === 'running') return 'Removing';
|
||||
return labels[server.state] ?? server.state;
|
||||
}
|
||||
|
||||
function queueHint(server: CollectionServer) {
|
||||
const reason = server.stage || 'Waiting for a startup slot and available host capacity.';
|
||||
return server.queuePosition && server.queuePosition > 1
|
||||
@@ -382,15 +364,7 @@
|
||||
name={server.server}
|
||||
deletedAt={server.server_deleted_at}
|
||||
/></strong
|
||||
><Badge
|
||||
variant={needsAttention(server)
|
||||
? 'danger'
|
||||
: server.state === 'done'
|
||||
? 'online'
|
||||
: launching(server)
|
||||
? 'info'
|
||||
: 'neutral'}>{stateLabel(server)}</Badge
|
||||
>
|
||||
><ProvisioningStatusBadge state={server.state} />
|
||||
</div>
|
||||
<p class:error={!!server.error}>
|
||||
{server.error ??
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
import EmptyState from '$ui/components/primitives/EmptyState.svelte';
|
||||
import ListFooter from '$ui/components/primitives/ListFooter.svelte';
|
||||
import SectionHeader from '$ui/components/primitives/SectionHeader.svelte';
|
||||
import StatusBadge from '$ui/components/primitives/StatusBadge.svelte';
|
||||
import ProvisioningStatusBadge from '$ui/components/ProvisioningStatusBadge.svelte';
|
||||
|
||||
let detail = $state<WatchdogDetail | null>(null);
|
||||
let loadError = $state('');
|
||||
@@ -270,7 +270,7 @@
|
||||
? 'Restart server'
|
||||
: 'Rebuild server'}
|
||||
</h3>
|
||||
{#if action.operation_state}<StatusBadge
|
||||
{#if action.operation_state}<ProvisioningStatusBadge
|
||||
state={action.operation_state}
|
||||
/>{/if}
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import Tabs from '$ui/components/primitives/Tabs.svelte';
|
||||
import HistoricalServerIdentity from '$ui/components/HistoricalServerIdentity.svelte';
|
||||
import SectionHeader from '$ui/components/primitives/SectionHeader.svelte';
|
||||
import Badge from '$ui/components/primitives/Badge.svelte';
|
||||
import ProvisioningStatusBadge from '$ui/components/ProvisioningStatusBadge.svelte';
|
||||
import Button from '$ui/components/primitives/Button.svelte';
|
||||
import Dialog from '$ui/components/primitives/Dialog.svelte';
|
||||
import EmptyState from '$ui/components/primitives/EmptyState.svelte';
|
||||
@@ -34,13 +34,22 @@
|
||||
type ProvisioningListRow as Deployment,
|
||||
type ProvisioningListPage
|
||||
} from '$lib/provisioning-list.js';
|
||||
import { provisioningActions, actionLabels } from '$lib/provisioning.js';
|
||||
import {
|
||||
provisioningActions,
|
||||
provisioningStates,
|
||||
activeProvisioningStates,
|
||||
actionLabels
|
||||
} from '$lib/provisioning.js';
|
||||
|
||||
import { contextHref } from '$lib/navigation-context.js';
|
||||
import { confirmAction } from '$ui/confirm.js';
|
||||
import { serverTitle } from '$ui/server-labels.js';
|
||||
import { elapsedTime, provisioningRequester } from '$ui/provisioning-presentation.js';
|
||||
import { provisioningQueueStatus, provisioningWarnings } from '$ui/provisioning-queue.js';
|
||||
import {
|
||||
elapsedTime,
|
||||
provisioningContext,
|
||||
provisioningRequester
|
||||
} from '$ui/provisioning-presentation.js';
|
||||
import { provisioningStatus, provisioningWarnings } from '$ui/provisioning-queue.js';
|
||||
|
||||
let lifecycleOpen = $state(false);
|
||||
let filtersOpen = $state(false);
|
||||
@@ -257,17 +266,13 @@
|
||||
})
|
||||
);
|
||||
const queueSummary = $derived.by(() => {
|
||||
const groups = { queued: 0, waiting: 0, verifying: 0, running: 0 };
|
||||
const counts = new Map(activeProvisioningStates.map((state) => [state, 0]));
|
||||
for (const row of visibleActive) {
|
||||
const group = provisioningQueueStatus(row).group;
|
||||
if (group in groups) groups[group as keyof typeof groups]++;
|
||||
if (counts.has(row.state)) counts.set(row.state, counts.get(row.state)! + 1);
|
||||
}
|
||||
return Object.entries(groups)
|
||||
return [...counts]
|
||||
.filter(([, count]) => count)
|
||||
.map(
|
||||
([label, count]) =>
|
||||
`${count} ${label === 'verifying' ? 'verifying health' : label === 'running' ? 'in progress' : label}`
|
||||
)
|
||||
.map(([state, count]) => `${count} ${provisioningStatus(state).label.toLowerCase()}`)
|
||||
.join(' · ');
|
||||
});
|
||||
function clearFilters() {
|
||||
@@ -336,18 +341,14 @@
|
||||
>
|
||||
<Field label="Status">
|
||||
<Select bind:value={historyState} onchange={() => saveContext(true)}
|
||||
><option value="all">All statuses</option><option value="pending"
|
||||
>Waiting / queued</option
|
||||
><option value="running">In progress</option><option value="awaiting-health"
|
||||
>Verifying health</option
|
||||
><option value="done">Complete</option><option value="attention"
|
||||
>Needs attention</option
|
||||
><option value="failed">Failed</option><option value="interrupted"
|
||||
>Interrupted</option
|
||||
><option value="warning">Warnings</option><option value="cancelled"
|
||||
>Cancelled</option
|
||||
></Select
|
||||
></Field
|
||||
><option value="all">All statuses</option>
|
||||
{#each provisioningStates as state}<option value={state}
|
||||
>{provisioningStatus(state).label}</option
|
||||
>{/each}
|
||||
<option value="attention">Needs attention</option>
|
||||
{#if historyState === 'pending'}<option value="pending">Waiting / queued</option
|
||||
>{/if}
|
||||
</Select></Field
|
||||
>
|
||||
<Field label="Source">
|
||||
<Select bind:value={source} onchange={() => saveContext(true)}
|
||||
@@ -491,7 +492,7 @@
|
||||
>
|
||||
<tbody
|
||||
>{#each rows as row (row.id)}
|
||||
{@const status = provisioningQueueStatus(row)}
|
||||
{@const context = provisioningContext(row)}
|
||||
{@const primarySource = row.sources?.[0]}
|
||||
{@const operationHref = contextHref(
|
||||
`/provisioning/${encodeURIComponent(row.id)}`,
|
||||
@@ -512,9 +513,14 @@
|
||||
/></td
|
||||
>
|
||||
<td><a href={operationHref}>{actionLabels[row.action]}</a></td>
|
||||
<td
|
||||
><Badge variant={status.tone} size="md">{status.label}</Badge
|
||||
>{#if ['queued', 'waiting'].includes(status.group) && row.phaseStartedAt && now - Date.parse(row.phaseStartedAt) >= 600000}<small
|
||||
<td class="status-cell"
|
||||
><ProvisioningStatusBadge state={row.state} size="md" />
|
||||
{#if context}<small>Current step: {context.step}</small>
|
||||
{#if context.detail}<small class="status-detail"
|
||||
>{context.detail}</small
|
||||
>{/if}
|
||||
{/if}
|
||||
{#if ['queued', 'waiting'].includes(row.state) && row.phaseStartedAt && now - Date.parse(row.phaseStartedAt) >= 600000}<small
|
||||
>In this stage {elapsedTime(row.phaseStartedAt, now)}</small
|
||||
>{/if}</td
|
||||
>
|
||||
@@ -716,6 +722,14 @@
|
||||
max-width: 240px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.status-cell {
|
||||
min-width: 200px;
|
||||
max-width: 320px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.status-detail {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.requester-cell {
|
||||
max-width: 180px;
|
||||
overflow-wrap: anywhere;
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
import {
|
||||
elapsedTime,
|
||||
isProvisioningDetail,
|
||||
provisioningContext,
|
||||
provisioningNeedsRefresh,
|
||||
provisioningOutcome,
|
||||
provisioningRequester
|
||||
} from '$ui/provisioning-presentation.js';
|
||||
import SectionHeader from '$ui/components/primitives/SectionHeader.svelte';
|
||||
import Breadcrumbs from '$ui/components/primitives/Breadcrumbs.svelte';
|
||||
import StatusBadge from '$ui/components/primitives/StatusBadge.svelte';
|
||||
import ProvisioningStatusBadge from '$ui/components/ProvisioningStatusBadge.svelte';
|
||||
import Badge from '$ui/components/primitives/Badge.svelte';
|
||||
import Button from '$ui/components/primitives/Button.svelte';
|
||||
import Card from '$ui/components/primitives/Card.svelte';
|
||||
@@ -65,6 +66,15 @@
|
||||
const phaseStartedAt = $derived(timestampISO(detail?.progress?.phaseStartedAt));
|
||||
const canOverride = $derived(data.user?.role === 'admin' && detail?.canOverrideSafety);
|
||||
const outcome = $derived(detail ? provisioningOutcome(detail) : null);
|
||||
const context = $derived(
|
||||
deployment
|
||||
? provisioningContext({
|
||||
...deployment,
|
||||
phase: detail?.progress?.phase,
|
||||
waitMessage: detail?.progress?.blocker?.message
|
||||
})
|
||||
: null
|
||||
);
|
||||
const active = $derived(outcome?.active ?? false);
|
||||
const needsRefresh = $derived(detail ? provisioningNeedsRefresh(detail) : false);
|
||||
const duration = $derived(
|
||||
@@ -268,14 +278,18 @@
|
||||
aria-labelledby="outcome-title"
|
||||
>
|
||||
<div class="badges">
|
||||
<StatusBadge state={deployment.state} />{#if deployment.server_deleted_at}<Badge
|
||||
>Deleted identity</Badge
|
||||
<ProvisioningStatusBadge
|
||||
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">
|
||||
<h2 id="outcome-title">{outcome.headline}</h2>
|
||||
<h2 id="outcome-title">
|
||||
{context ? `Current step: ${context.step}` : outcome.headline}
|
||||
</h2>
|
||||
{#if context?.detail}<p>{context.detail}</p>{/if}
|
||||
{#if outcome.description}<p>{outcome.description}</p>{/if}
|
||||
{#if outcome.checksSummary}<p>
|
||||
<span aria-hidden="true">✓</span>{' '}{outcome.checksSummary}
|
||||
|
||||
Reference in New Issue
Block a user