Make UI language, timestamps and live feedback consistent

This commit is contained in:
2026-10-07 19:46:40 -04:00
parent 0ed3cd6b47
commit 70185eb98c
81 changed files with 656 additions and 332 deletions
+2 -9
View File
@@ -1,4 +1,5 @@
<script lang="ts">
import RelativeTime from '$ui/components/RelativeTime.svelte';
import { contextHref } from '$lib/navigation-context.js';
import { timeFormat } from '$ui/stores/time-zone.js';
import { page } from '$app/stores';
@@ -69,14 +70,6 @@
return pollVisible(loadCampaigns, 15000);
});
function ago(iso: string): string {
const elapsed = refreshedAt - new Date(iso).getTime();
if (elapsed < 60_000) return 'Just now';
if (elapsed < 3_600_000) return `${Math.floor(elapsed / 60_000)} min ago`;
if (elapsed < 86_400_000) return `${Math.floor(elapsed / 3_600_000)} hr ago`;
const days = Math.floor(elapsed / 86_400_000);
return `${days} day${days === 1 ? '' : 's'} ago`;
}
</script>
<svelte:head><title>Campaigns | Electric Cherry</title></svelte:head>
@@ -138,7 +131,7 @@
<td><div class="status"><Badge variant={outcome.tone} size="md">{outcome.label}</Badge>
{#if showProgress}<progress max={campaign.servers.length || 1} value={outcome.completed} aria-label={`${outcome.completed} of ${campaign.servers.length} servers complete`}></progress>{/if}
</div></td>
<td class="started"><button type="button" class="timestamp" aria-label={`Started ${$timeFormat.dateTime(campaign.started_at)}`}><time datetime={campaign.started_at}>{ago(campaign.started_at)}</time><span class="exact-time" aria-hidden="true">{$timeFormat.dateTime(campaign.started_at)}</span></button></td>
<td class="started"><button type="button" class="timestamp" aria-label={`Started ${$timeFormat.dateTime(campaign.started_at)}`}><RelativeTime value={campaign.started_at} /><span class="exact-time" aria-hidden="true">{$timeFormat.dateTime(campaign.started_at)}</span></button></td>
</tr>
{/each}
</tbody>
+8 -10
View File
@@ -1,6 +1,7 @@
<script lang="ts">
import { plural } from '$ui/plural.js';
import RelativeTime from '$ui/components/RelativeTime.svelte';
import { contextHref } from '$lib/navigation-context.js';
import { timeFormat } from '$ui/stores/time-zone.js';
import { onMount } from 'svelte';
import { pollVisible } from '$ui/poll.js';
import Dialog from '$ui/components/primitives/Dialog.svelte';
@@ -163,9 +164,6 @@
v === 'held' ? 'warning' :
'neutral';
}
function fmt(iso: string | null): string {
return $timeFormat.dateTime(iso);
}
</script>
<svelte:head><title>{detail ? `${detail.deployment.action} campaign · ${detail.deployment.id}` : 'Campaign'} | Electric Cherry</title></svelte:head>
@@ -191,14 +189,14 @@
</SectionHeader>
{#if rollbackError}<p class="err" role="alert">{rollbackError}</p>{/if}
<div class="metadata">
<span>Started {fmt(detail.deployment.started_at)}</span>
<span>Started <RelativeTime value={detail.deployment.started_at} /></span>
{#if detail.deployment.beta_id}<span>Source: <a href={contextHref(`/betas/${detail.deployment.beta_id}`, $page.url)}>Beta #{detail.deployment.beta_id}</a></span>{/if}
{#if detail.watchdogIncidentId}<span>Source: <a href={contextHref(`/maintenance/${encodeURIComponent(detail.watchdogIncidentId)}`, $page.url)}>Maintenance incident</a></span>{/if}
<details class="campaign-details">
<summary>Campaign details</summary>
<div class="campaign-details-body">
<div class="campaign-id"><span class="mono">{detail.deployment.id}</span><Button size="sm" onclick={copyId}>Copy ID</Button><span role="status">{copyStatus}</span></div>
{#if detail.deployment.finished_at}<p>Finished {fmt(detail.deployment.finished_at)}</p>{/if}
{#if detail.deployment.finished_at}<p>Finished <RelativeTime value={detail.deployment.finished_at} /></p>{/if}
{#if detail.deployment.triggered_by && detail.deployment.triggered_by !== 'ui'}<p>Triggered by {detail.deployment.triggered_by}</p>{/if}
<p>{detail.watchdogIncidentId ? 'Maintenance rebuilds instance files with the latest promoted setup after checking that the server is safe to update.' : 'Changes wait for an empty server and restart capacity. Rollback snapshots are captured before applying files, then servers restart and verify health. Tested servers retain their pre-beta snapshots.'}</p>
</div>
@@ -216,7 +214,7 @@
<div class="progress-counts">
{#if counts.processing}<span>{counts.processing} processing</span>{/if}
{#if counts.queued}<span>{counts.queued} queued</span>{/if}
{#if counts.attention}<span class="attention">{counts.attention} need attention</span>{/if}
{#if counts.attention}<span class="attention">{counts.attention} {plural(counts.attention, 'needs', 'need')} attention</span>{/if}
{#if counts.inactive}<span>{counts.inactive} inactive</span>{/if}
{#if detail.servers.length === 0}<span>No servers recorded</span>
{:else if counts.complete === detail.servers.length}<span>All servers activated</span>{/if}
@@ -266,9 +264,9 @@
<div><dt>Server ID</dt><dd class="mono">{s.server}</dd></div>
{#if s.players !== null}<div><dt>Players at last check</dt><dd>{s.players}</dd></div>{/if}
<div><dt>Attempts</dt><dd>{s.attempts}</dd></div>
{#if s.checked_at}<div><dt>Last checked</dt><dd>{fmt(s.checked_at)}</dd></div>{/if}
{#if s.restarted_at}<div><dt>Restart requested</dt><dd>{fmt(s.restarted_at)}</dd></div>{/if}
{#if s.server_deleted_at}<div><dt>Deleted</dt><dd>{fmt(s.server_deleted_at)}</dd></div>{/if}
{#if s.checked_at}<div><dt>Last checked</dt><dd><RelativeTime value={s.checked_at} /></dd></div>{/if}
{#if s.restarted_at}<div><dt>Restart requested</dt><dd><RelativeTime value={s.restarted_at} /></dd></div>{/if}
{#if s.server_deleted_at}<div><dt>Deleted</dt><dd><RelativeTime value={s.server_deleted_at} /></dd></div>{/if}
</dl>
<div class="detail-actions">
{#if !s.server_deleted_at}<a href={contextHref(`/servers/${encodeURIComponent(s.server)}?tab=logs`, $page.url)}>Open logs</a><a href={contextHref(`/servers/${encodeURIComponent(s.server)}?tab=diagnostics`, $page.url)}>Diagnostics</a>{/if}