Consolidate provisioning progress and evidence (Refs #82)
This commit is contained in:
@@ -2,17 +2,16 @@
|
||||
import OperationTime from '$ui/components/OperationTime.svelte';
|
||||
import { onDestroy } from 'svelte';
|
||||
import type { ProvisioningDetail, ProvisioningEvent } from '$lib/provisioning-detail.js';
|
||||
import { countLabel } from '$ui/plural.js';
|
||||
import { isProvisioningEventPage } from '$ui/provisioning-presentation.js';
|
||||
import Button from './primitives/Button.svelte';
|
||||
|
||||
let { detail }: { detail: ProvisioningDetail } = $props();
|
||||
let open = $state(false);
|
||||
let query = $state('');
|
||||
let order = $state<'newest' | 'oldest'>('newest');
|
||||
let appliedQuery = $state('');
|
||||
let appliedOrder = $state<'newest' | 'oldest'>('newest');
|
||||
let entries = $state<ProvisioningEvent[]>([]);
|
||||
let frozenRecent = $state<ProvisioningEvent[] | null>(null);
|
||||
let total = $state(0);
|
||||
let baselineTotal = $state<number | null>(null);
|
||||
let watermark = $state<number | null>(null);
|
||||
@@ -23,7 +22,6 @@
|
||||
let requestNumber = 0;
|
||||
let controller: AbortController | null = null;
|
||||
let disposed = false;
|
||||
const recent = $derived((frozenRecent ?? detail.events).slice().sort((a, b) => b.id - a.id).slice(0, 5));
|
||||
const newEvents = $derived(baselineTotal === null ? 0 : Math.max(0, detail.activity.total - baselineTotal));
|
||||
|
||||
async function load(more = false) {
|
||||
@@ -63,29 +61,19 @@
|
||||
}
|
||||
function toggle(event: Event) {
|
||||
const expanded = (event.currentTarget as HTMLDetailsElement).open;
|
||||
open = expanded;
|
||||
if (expanded) {
|
||||
frozenRecent = detail.events.slice();
|
||||
if (!loaded && !loading) void load();
|
||||
} else frozenRecent = null;
|
||||
if (expanded && !loaded && !loading) void load();
|
||||
}
|
||||
function jumpToLatest() {
|
||||
query = appliedQuery; order = 'newest';
|
||||
frozenRecent = detail.events.slice();
|
||||
void load();
|
||||
}
|
||||
onDestroy(() => { disposed = true; requestNumber++; controller?.abort(); });
|
||||
</script>
|
||||
|
||||
<section aria-labelledby="activity-title" class="activity">
|
||||
<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"><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}
|
||||
|
||||
<h2 id="activity-title">Activity log</h2>
|
||||
<details class="history" ontoggle={toggle}>
|
||||
<summary>Full activity history <span>{detail.activity.total} events</span></summary>
|
||||
<summary>Full activity history <span>{countLabel(detail.activity.total, 'event')}</span></summary>
|
||||
<div class="history-content">
|
||||
{#if newEvents > 0}<div class="new-events" role="status"><span>{newEvents} new event{newEvents === 1 ? '' : 's'} since this view was loaded.</span><Button size="sm" onclick={jumpToLatest}>Jump to latest</Button></div>{/if}
|
||||
<form class="tools" onsubmit={event => { event.preventDefault(); void load(); }}>
|
||||
@@ -107,16 +95,14 @@
|
||||
|
||||
<style>
|
||||
.activity { display: grid; gap: var(--space-4); min-width: 0; }
|
||||
.heading { display: flex; justify-content: space-between; gap: var(--space-4); align-items: end; flex-wrap: wrap; }
|
||||
.eyebrow { display: block; font-size: var(--text-xs); color: var(--text-tertiary); margin-bottom: var(--space-2); }
|
||||
h2 { margin: 0; font-size: var(--text-lg); letter-spacing: var(--tracking-tight); }
|
||||
p { margin: 0; line-height: 1.65; overflow-wrap: anywhere; font-size: var(--text-sm); }
|
||||
.muted, .count, .time, .footer { font-size: var(--text-xs); color: var(--text-secondary); }
|
||||
.muted, .time, .footer { font-size: var(--text-xs); color: var(--text-secondary); }
|
||||
.empty { padding: var(--space-4); border: 1px solid var(--border-subtle); border-radius: var(--radius-md); color: var(--text-secondary); }
|
||||
.timeline { list-style: none; margin: 0 0 0 var(--space-1); padding: 0 0 0 var(--space-5); border-left: 1px solid var(--border-strong); }
|
||||
.timeline li { padding: 0 0 var(--space-5); position: relative; }.timeline li:last-child { padding-bottom: 0; }
|
||||
.timeline li::before { content: ''; position: absolute; top: 6px; left: -24px; width: 7px; height: 7px; background: var(--border-control); border-radius: var(--radius-pill); }
|
||||
.recent li:first-child::before { background: var(--accent-primary); }.timeline p { margin-top: var(--space-1); }
|
||||
.timeline p { margin-top: var(--space-1); }
|
||||
.history { border: 1px solid var(--border-subtle); border-radius: var(--radius-md); background: var(--surface); }
|
||||
summary { cursor: pointer; padding: var(--space-5); font-size: var(--text-sm); font-weight: var(--weight-semibold); overflow-wrap: anywhere; }
|
||||
summary span { margin-left: var(--space-2); color: var(--text-tertiary); font-size: var(--text-xs); font-weight: var(--weight-regular); }
|
||||
|
||||
Reference in New Issue
Block a user