Show provisioning timestamps and step offsets (Refs #89)
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { timestampISO, type Timestamp } from '$lib/time-zone.js';
|
||||
import { formatTimeOffset } from '$ui/duration.js';
|
||||
import { timeFormat } from '$ui/stores/time-zone.js';
|
||||
|
||||
let { value, start, missing = 'Not recorded' }: { value: Timestamp; start: Timestamp; missing?: string } = $props();
|
||||
const iso = $derived(timestampISO(value));
|
||||
const offset = $derived(formatTimeOffset(start, value));
|
||||
const absolute = $derived($timeFormat.dateTime(value));
|
||||
</script>
|
||||
|
||||
{#if iso}<time datetime={iso} title={absolute} aria-label={offset ? `${offset} from operation start · ${absolute}` : undefined}>{offset ?? absolute}</time>{:else}<span>{missing}</span>{/if}
|
||||
|
||||
<style>
|
||||
time { font-variant-numeric: tabular-nums; }
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import RelativeTime from '$ui/components/RelativeTime.svelte';
|
||||
import OperationTime from '$ui/components/OperationTime.svelte';
|
||||
import { onDestroy } from 'svelte';
|
||||
import type { ProvisioningDetail, ProvisioningEvent } from '$lib/provisioning-detail.js';
|
||||
import { isProvisioningEventPage } from '$ui/provisioning-presentation.js';
|
||||
@@ -81,7 +81,7 @@
|
||||
<div class="heading"><div><span class="eyebrow">Activity</span><h2 id="activity-title">Recent updates</h2></div><span class="count">{detail.activity.total} event{detail.activity.total === 1 ? '' : 's'}</span></div>
|
||||
<p class="muted">{open ? 'Recent updates stay in place while you read the full history.' : 'Newest first · updates automatically'}</p>
|
||||
{#if recent.length}<ol class="timeline recent">
|
||||
{#each recent as event (event.id)}<li><span class="time"><RelativeTime value={event.created_at} /></span><p>{event.message}</p></li>{/each}
|
||||
{#each recent as event (event.id)}<li><span class="time"><OperationTime start={detail.deployment.started_at} value={event.created_at} /></span><p>{event.message}</p></li>{/each}
|
||||
</ol>{:else}<p class="empty">No detailed activity was recorded. The recorded outcome and timestamps are preserved above.</p>{/if}
|
||||
|
||||
<details class="history" ontoggle={toggle}>
|
||||
@@ -98,7 +98,7 @@
|
||||
{#if loading && !loaded}<p class="muted" role="status">Loading activity…</p>
|
||||
{:else if loaded && !entries.length}<p class="empty">{appliedQuery ? 'No activity matches this search.' : 'No detailed activity recorded.'}</p>
|
||||
{:else}<ol class="timeline">
|
||||
{#each entries as event (event.id)}<li><span class="time"><RelativeTime value={event.created_at} /></span><p>{event.message}</p></li>{/each}
|
||||
{#each entries as event (event.id)}<li><span class="time"><OperationTime start={detail.deployment.started_at} value={event.created_at} /></span><p>{event.message}</p></li>{/each}
|
||||
</ol>{/if}
|
||||
{#if loaded}<div class="footer"><span>Showing {entries.length} of {total} matching events</span>{#if cursor !== null}<Button size="sm" loading={loading} disabled={loading} onclick={() => load(true)}>Show more activity</Button>{/if}</div>{/if}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import RelativeTime from '$ui/components/RelativeTime.svelte';
|
||||
import OperationTime from '$ui/components/OperationTime.svelte';
|
||||
import type { ProvisioningDetail } from '$lib/provisioning-detail.js';
|
||||
import { provisioningCheckValue, provisioningGroups, progressIcons, progressStateLabels } from '$ui/provisioning-presentation.js';
|
||||
import Badge from './primitives/Badge.svelte';
|
||||
@@ -37,7 +37,7 @@
|
||||
{#each group.steps as step (step.key)}
|
||||
<li aria-current={step.state === 'current' || step.state === 'waiting' ? 'step' : undefined}>
|
||||
<span class="step-icon state-{step.state}" aria-hidden="true">{progressIcons[step.state]}</span>
|
||||
<div><strong>{step.label}</strong><span class="step-meta state-{step.state}">{progressStateLabels[step.state]}{#if step.at} · <span class="time"><RelativeTime value={step.at} /></span>{/if}</span></div>
|
||||
<div><strong>{step.label}</strong><span class="step-meta state-{step.state}">{progressStateLabels[step.state]}{#if step.at}{' · '}<span class="time"><OperationTime start={detail.deployment.started_at} value={step.at} /></span>{/if}</span></div>
|
||||
</li>
|
||||
{/each}
|
||||
</ol>
|
||||
@@ -49,17 +49,17 @@
|
||||
<div class="command" class:uncertain={progress.command.state === 'uncertain'}>
|
||||
<h3>{progress.command.state === 'attempted' ? 'Command submission attempted' : progress.command.state === 'accepted' ? 'Remote request accepted' : progress.command.state === 'rejected' ? 'Remote request rejected' : 'Remote response uncertain'}</h3>
|
||||
<p>{progress.command.message}</p>
|
||||
<span class="time"><RelativeTime value={progress.command.at} /></span>
|
||||
<span class="time"><OperationTime start={detail.deployment.started_at} value={progress.command.at} /></span>
|
||||
</div>
|
||||
{/if}
|
||||
{#if progress.checkpoints.some(checkpoint => !groups.some(group => group.steps.some(step => step.key === checkpoint.key)))}
|
||||
<details class="milestones"><summary>Recorded checkpoints</summary><ul>
|
||||
{#each progress.checkpoints.filter(checkpoint => !groups.some(group => group.steps.some(step => step.key === checkpoint.key))) as checkpoint (checkpoint.key)}
|
||||
<li><strong>{checkpoint.label}</strong><span class="time"><RelativeTime value={checkpoint.at} /></span></li>
|
||||
<li><strong>{checkpoint.label}</strong><span class="time"><OperationTime start={detail.deployment.started_at} value={checkpoint.at} /></span></li>
|
||||
{/each}
|
||||
</ul></details>
|
||||
{/if}
|
||||
<div class="evidence-heading"><h3>Latest recorded checks</h3>{#if progress.observedAt}<span>Evaluated <span class="time"><RelativeTime value={progress.observedAt} /></span></span>{/if}</div>
|
||||
<div class="evidence-heading"><h3>Latest recorded checks</h3>{#if progress.observedAt}<span>Evaluated <span class="time"><OperationTime start={detail.deployment.started_at} value={progress.observedAt} /></span></span>{/if}</div>
|
||||
{#if progress.checks.length}
|
||||
<div class="checks">
|
||||
{#each progress.checks as check (check.key)}
|
||||
@@ -70,7 +70,7 @@
|
||||
<div><dt>Observed</dt><dd>{provisioningCheckValue(check.key, check.actual)}</dd></div>
|
||||
</dl>{/if}
|
||||
{#if check.message}<p>{check.message}</p>{/if}
|
||||
<div class="evidence-meta"><span>{check.source ? sources[check.source] : 'Source not recorded'}</span><span>Checked <span class="time"><RelativeTime value={check.checkedAt} /></span></span>{#if check.receivedAt}<span>Evidence received <span class="time"><RelativeTime value={check.receivedAt} /></span></span>{/if}</div>
|
||||
<div class="evidence-meta"><span>{check.source ? sources[check.source] : 'Source not recorded'}</span><span>Checked <span class="time"><OperationTime start={detail.deployment.started_at} value={check.checkedAt} /></span></span>{#if check.receivedAt}<span>Evidence received <span class="time"><OperationTime start={detail.deployment.started_at} value={check.receivedAt} /></span></span>{/if}</div>
|
||||
</article>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
import { timestampISO, type Timestamp } from '$lib/time-zone.js';
|
||||
|
||||
/** Signed time from an operation's start, preserving seconds even after an hour. */
|
||||
export function formatTimeOffset(start: Timestamp, value: Timestamp): string | null {
|
||||
const startISO = timestampISO(start);
|
||||
const valueISO = timestampISO(value);
|
||||
if (!startISO || !valueISO) return null;
|
||||
const difference = Date.parse(valueISO) - Date.parse(startISO);
|
||||
const seconds = Math.floor(Math.abs(difference) / 1000);
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const clock = minutes < 60 ? `${minutes}:${String(seconds % 60).padStart(2, '0')}`
|
||||
: `${Math.floor(minutes / 60)}:${String(minutes % 60).padStart(2, '0')}:${String(seconds % 60).padStart(2, '0')}`;
|
||||
return `${difference < 0 && seconds > 0 ? '−' : '+'}${clock}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an elapsed millisecond count as a compact human string:
|
||||
* < 60s -> "12s"
|
||||
|
||||
Reference in New Issue
Block a user