Add Prettier and ESLint tooling (Refs #77)

This commit is contained in:
2026-10-09 15:56:13 -04:00
parent 8ed059b8d3
commit 16469e2d43
711 changed files with 99737 additions and 28286 deletions
+175 -32
View File
@@ -15,57 +15,138 @@
$effect(() => {
if (!open) return;
const url = `/api/hosts/${encodeURIComponent(hostId)}/samples?` + new URLSearchParams({ scope, phase, page: String(page) });
const url =
`/api/hosts/${encodeURIComponent(hostId)}/samples?` +
new URLSearchParams({ scope, phase, page: String(page) });
void revision;
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 15000);
let active = true;
loading = true; error = ''; result = null;
loading = true;
error = '';
result = null;
void (async () => {
try {
const response = await fetch(url, { signal: controller.signal });
const body = await response.json();
if (!response.ok) throw new Error(body.message ?? body.error ?? 'Could not load samples.');
if (!response.ok)
throw new Error(body.message ?? body.error ?? 'Could not load samples.');
if (active) result = body;
} catch (cause) {
if (active) error = controller.signal.aborted ? 'Sample request timed out. Try refreshing.' : (cause as Error).message;
} finally { clearTimeout(timer); if (active) loading = false; }
if (active)
error = controller.signal.aborted
? 'Sample request timed out. Try refreshing.'
: (cause as Error).message;
} finally {
clearTimeout(timer);
if (active) loading = false;
}
})();
return () => { active = false; clearTimeout(timer); controller.abort(); };
return () => {
active = false;
clearTimeout(timer);
controller.abort();
};
});
</script>
<details bind:open>
<summary>View samples</summary>
{#if open}
<p>Retained minute peaks from the last 24 hours, used without extra allowance padding. CPU and memory peaks may come from different heartbeats. Choose the fleet to inspect measurements from other hosts used by fleet allowances.</p>
<p>
Retained minute peaks from the last 24 hours, used without extra allowance padding. CPU
and memory peaks may come from different heartbeats. Choose the fleet to inspect
measurements from other hosts used by fleet allowances.
</p>
<div class="controls">
<label>Source<select bind:value={scope} onchange={() => page = 1}><option value="host">This host</option><option value="fleet">Entire fleet</option></select></label>
<label>Phase<select bind:value={phase} onchange={() => page = 1}><option value="all">All phases</option><option value="startup">Starting</option><option value="running">Running</option></select></label>
<Button size="sm" disabled={loading} onclick={() => { page = 1; revision++; }}>Refresh samples</Button>
<label
>Source<select bind:value={scope} onchange={() => (page = 1)}
><option value="host">This host</option><option value="fleet"
>Entire fleet</option
></select
></label
>
<label
>Phase<select bind:value={phase} onchange={() => (page = 1)}
><option value="all">All phases</option><option value="startup">Starting</option
><option value="running">Running</option></select
></label
>
<Button
size="sm"
disabled={loading}
onclick={() => {
page = 1;
revision++;
}}>Refresh samples</Button
>
</div>
{#if error}<p class="ui-error" role="alert">{error}</p>{/if}
{#if loading}<p role="status">Loading samples…</p>
{:else if result}
{#if result.total === 0}<p role="status">No {phase === 'all' ? '' : phase === 'startup' ? 'startup ' : 'running '}samples recorded for this source in the last 24 hours.</p>
{#if result.total === 0}<p role="status">
No {phase === 'all' ? '' : phase === 'startup' ? 'startup ' : 'running '}samples
recorded for this source in the last 24 hours.
</p>
{:else}
<p>{result.total.toLocaleString()} samples · newest first. Counts can differ from the allowance above, which is recalculated periodically.</p>
<p>
{result.total.toLocaleString()} samples · newest first. Counts can differ from the
allowance above, which is recalculated periodically.
</p>
<!-- svelte-ignore a11y_no_noninteractive_tabindex (The overflow region needs keyboard focus for horizontal scrolling.) -->
<div class="table-wrap" tabindex="0" role="region" aria-label="Container resource samples">
<table><caption>CPU and memory peaks per container and minute; times are local.</caption>
<thead><tr><th scope="col">Time</th>{#if scope === 'fleet'}<th scope="col">Host</th>{/if}<th scope="col">Server</th><th scope="col">Phase</th><th scope="col">CPU cores</th><th scope="col">Memory</th></tr></thead>
<tbody>{#each result.samples as sample}
<tr><td><RelativeTime value={sample.timestamp} /></td>
{#if scope === 'fleet'}<td>{sample.hostName ?? sample.hostId}</td>{/if}
<td>{sample.server ?? sample.instanceId}<small title={sample.instanceId}>Generation {sample.generation}</small></td>
<td>{sample.phase === 'startup' ? 'Starting' : 'Running'}</td><td>{sample.cpuCores.toFixed(3)}</td><td>{(sample.memoryBytes / 1048576).toFixed(1)} MiB</td></tr>
{/each}</tbody>
<div
class="table-wrap"
tabindex="0"
role="region"
aria-label="Container resource samples"
>
<table>
<caption
>CPU and memory peaks per container and minute; times are local.</caption
>
<thead
><tr
><th scope="col">Time</th>{#if scope === 'fleet'}<th scope="col"
>Host</th
>{/if}<th scope="col">Server</th><th scope="col">Phase</th><th
scope="col">CPU cores</th
><th scope="col">Memory</th></tr
></thead
>
<tbody
>{#each result.samples as sample}
<tr
><td><RelativeTime value={sample.timestamp} /></td>
{#if scope === 'fleet'}<td
>{sample.hostName ?? sample.hostId}</td
>{/if}
<td
>{sample.server ?? sample.instanceId}<small
title={sample.instanceId}
>Generation {sample.generation}</small
></td
>
<td>{sample.phase === 'startup' ? 'Starting' : 'Running'}</td
><td>{sample.cpuCores.toFixed(3)}</td><td
>{(sample.memoryBytes / 1048576).toFixed(1)} MiB</td
></tr
>
{/each}</tbody
>
</table>
</div>
<nav aria-label="Sample pages">
<Button size="sm" disabled={result.page <= 1} onclick={() => page = result!.page - 1}>Previous</Button>
<Button
size="sm"
disabled={result.page <= 1}
onclick={() => (page = result!.page - 1)}>Previous</Button
>
<span>Page {result.page} of {Math.ceil(result.total / result.pageSize)}</span>
<Button size="sm" disabled={result.page * result.pageSize >= result.total} onclick={() => page = result!.page + 1}>Next</Button>
<Button
size="sm"
disabled={result.page * result.pageSize >= result.total}
onclick={() => (page = result!.page + 1)}>Next</Button
>
</nav>
{/if}
{/if}
@@ -73,13 +154,75 @@
</details>
<style>
details { margin-top:20px; min-width:0; } summary { cursor:pointer; color:var(--accent-primary); font-weight:600; }
p, caption, small, nav { color:var(--text-secondary); font-size:var(--text-sm); line-height:1.6; }
.controls, nav { display:flex; flex-wrap:wrap; align-items:end; gap:12px; margin:16px 0; }
nav { align-items:center; } label { display:grid; gap:6px; font-size:var(--text-sm); }
select { padding:8px; color:var(--text-primary); background:var(--surface-sunken); border:1px solid var(--border-control); border-radius:var(--radius-sm); }
.table-wrap { max-width:100%; overflow:auto; } .table-wrap:focus-visible, summary:focus-visible { outline:2px solid var(--accent-primary); outline-offset:3px; }
table { width:100%; border-collapse:collapse; text-align:left; font-size:var(--text-sm); }
caption { text-align:left; padding-bottom:8px; } td,th { padding:10px; border-bottom:1px solid var(--border-subtle); }
td { white-space:nowrap; } small { display:block; font-size:var(--text-xs); }
details {
margin-top: 20px;
min-width: 0;
}
summary {
cursor: pointer;
color: var(--accent-primary);
font-weight: 600;
}
p,
caption,
small,
nav {
color: var(--text-secondary);
font-size: var(--text-sm);
line-height: 1.6;
}
.controls,
nav {
display: flex;
flex-wrap: wrap;
align-items: end;
gap: 12px;
margin: 16px 0;
}
nav {
align-items: center;
}
label {
display: grid;
gap: 6px;
font-size: var(--text-sm);
}
select {
padding: 8px;
color: var(--text-primary);
background: var(--surface-sunken);
border: 1px solid var(--border-control);
border-radius: var(--radius-sm);
}
.table-wrap {
max-width: 100%;
overflow: auto;
}
.table-wrap:focus-visible,
summary:focus-visible {
outline: 2px solid var(--accent-primary);
outline-offset: 3px;
}
table {
width: 100%;
border-collapse: collapse;
text-align: left;
font-size: var(--text-sm);
}
caption {
text-align: left;
padding-bottom: 8px;
}
td,
th {
padding: 10px;
border-bottom: 1px solid var(--border-subtle);
}
td {
white-space: nowrap;
}
small {
display: block;
font-size: var(--text-xs);
}
</style>