Make UI language, timestamps and live feedback consistent
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { timeFormat } from '$ui/stores/time-zone.js';
|
||||
import { onMount } from 'svelte';
|
||||
import { TELEMETRY_REQUEST_RETENTION, type TelemetryRequestLogEntry } from '$lib/telemetry-diagnostics.js';
|
||||
import { pollVisible } from '$ui/poll.js';
|
||||
import { serverTitle } from '$ui/server-labels.js';
|
||||
import Card from './primitives/Card.svelte';
|
||||
import Badge from './primitives/Badge.svelte';
|
||||
import Button from './primitives/Button.svelte';
|
||||
import Freshness from './Freshness.svelte';
|
||||
import RelativeTime from './RelativeTime.svelte';
|
||||
import EmptyState from './primitives/EmptyState.svelte';
|
||||
import ListFooter from './primitives/ListFooter.svelte';
|
||||
|
||||
@@ -15,15 +16,18 @@
|
||||
let refreshing = $state(false);
|
||||
let loaded = $state(false);
|
||||
let error = $state('');
|
||||
let refreshedAt = $state<string | null>(null);
|
||||
let refreshedAt = $state<number | null>(null);
|
||||
let limit = $state(25);
|
||||
let active = false;
|
||||
let controller: AbortController | null = null;
|
||||
|
||||
async function refresh(): Promise<void> {
|
||||
async function refresh(signal?: AbortSignal): Promise<void | false> {
|
||||
if (!active || refreshing) return;
|
||||
const currentServer = server;
|
||||
const requestController = new AbortController();
|
||||
const abort = () => requestController.abort();
|
||||
signal?.addEventListener('abort', abort, { once: true });
|
||||
if (signal?.aborted) requestController.abort();
|
||||
controller = requestController;
|
||||
refreshing = true;
|
||||
try {
|
||||
@@ -35,17 +39,19 @@
|
||||
if (!response.ok || !body?.ok || !Array.isArray(body.requests)) {
|
||||
throw new Error(body?.error || `Could not load telemetry requests (HTTP ${response.status}).`);
|
||||
}
|
||||
if (!active || server !== currentServer) return;
|
||||
if (!active || server !== currentServer || requestController.signal.aborted) return;
|
||||
requests = body.requests;
|
||||
retention = body.retention;
|
||||
loaded = true;
|
||||
error = '';
|
||||
refreshedAt = new Date().toISOString();
|
||||
refreshedAt = Date.now();
|
||||
} catch (cause) {
|
||||
if (active && server === currentServer && !requestController.signal.aborted) {
|
||||
error = cause instanceof Error ? cause.message : 'Could not load telemetry requests.';
|
||||
}
|
||||
return false;
|
||||
} finally {
|
||||
signal?.removeEventListener('abort', abort);
|
||||
if (active && server === currentServer) refreshing = false;
|
||||
if (controller === requestController) controller = null;
|
||||
}
|
||||
@@ -61,10 +67,6 @@
|
||||
};
|
||||
});
|
||||
|
||||
function timestamp(value: string): string {
|
||||
return $timeFormat.dateTime(value);
|
||||
}
|
||||
|
||||
function outcome(status: number): string {
|
||||
return status >= 500 ? 'Failed' : status >= 400 ? 'Rejected' : 'Accepted';
|
||||
}
|
||||
@@ -74,15 +76,15 @@
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<h2>Telemetry API requests</h2>
|
||||
<p class="server-name"><code>{server}</code></p>
|
||||
<p class="server-name">{serverTitle(server)}</p>
|
||||
</div>
|
||||
<Button size="sm" onclick={refresh} disabled={refreshing} loading={refreshing}>Refresh</Button>
|
||||
<Freshness updatedAt={refreshedAt} error={error ? 'Telemetry request updates interrupted.' : ''} intervalLabel="Updates every 5 seconds" onRetry={() => void refresh()} retryDisabled={refreshing} />
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
<p class="load-error" role="alert">
|
||||
Couldn’t load telemetry requests. {#if loaded} Previously loaded requests are shown below.{/if}
|
||||
</p><Button size="sm" onclick={refresh} disabled={refreshing}>Retry</Button><details class="payload"><summary>Technical details</summary>{error}</details>
|
||||
</p><details class="payload"><summary>Technical details</summary>{error}</details>
|
||||
{/if}
|
||||
|
||||
{#if !loaded && !error}
|
||||
@@ -103,7 +105,7 @@
|
||||
</Badge>
|
||||
<code class="event">{request.event ?? 'Event not provided'}</code>
|
||||
</div>
|
||||
<time class="received" datetime={request.receivedAt}>{timestamp(request.receivedAt)}</time>
|
||||
<span class="received"><RelativeTime value={request.receivedAt} /></span>
|
||||
</div>
|
||||
<dl class="request-meta">
|
||||
<div><dt>Map</dt><dd><code>{request.map ?? 'Not provided'}</code></dd></div>
|
||||
@@ -124,13 +126,11 @@
|
||||
{/if}
|
||||
|
||||
<details class="request-help"><summary>About request logging</summary> <p class="intro">
|
||||
Incoming <code>POST /api/telemetry</code> requests with this exact server name.
|
||||
Incoming <code>POST /api/telemetry</code> requests with the exact server name <code>{server}</code>.
|
||||
Fresh game telemetry supplies player counts when available; RCON provides a fallback. The scoreboard uses telemetry.
|
||||
</p>
|
||||
<p class="retention">
|
||||
Newest first · {retention.maxPerServer ? `up to ${retention.maxPerServer} requests per server` : 'no per-server retention cap'} · {retention.maxAgeDays ? `last ${retention.maxAgeDays} days` : 'no age expiry'}. Fleet-wide limits also apply.
|
||||
Refreshes every 5 seconds while visible.
|
||||
{#if refreshedAt}<span>Last refreshed <time datetime={refreshedAt}>{timestamp(refreshedAt)}</time>.</span>{/if}
|
||||
</p>
|
||||
|
||||
<p class="attribution">
|
||||
@@ -161,7 +161,6 @@
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.retention { margin-top: var(--space-2); }
|
||||
.retention span { display: block; }
|
||||
.attribution { margin-top: var(--space-4); }
|
||||
.request-list { list-style: none; padding: 0; margin: var(--space-5) 0 0; }
|
||||
.request { padding: var(--space-4) 0; border-top: 1px solid var(--border-subtle); }
|
||||
|
||||
Reference in New Issue
Block a user