Simplify capacity summary and host comparison
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
<script lang="ts">
|
||||
import type { CapacityHost } from '$lib/capacity.js';
|
||||
import { hostReadinessLabel } from '$lib/hosts.js';
|
||||
import { contextHref } from '$lib/navigation-context.js';
|
||||
import { timeFormat } from '$ui/stores/time-zone.js';
|
||||
let { hosts, returnTo }: { hosts: CapacityHost[]; returnTo: string } = $props();
|
||||
let expanded = $state<string[]>([]);
|
||||
const numbers = new Intl.NumberFormat('en-US', { maximumFractionDigits: 1 });
|
||||
function count(value: number | null) { return value === null ? '—' : numbers.format(value); }
|
||||
function memory(value: number | null) { return value === null ? '—' : `${numbers.format(value / 1024 ** 3)} GiB`; }
|
||||
function restriction(host: CapacityHost) {
|
||||
if (!host.connected) return 'Offline';
|
||||
return host.readiness === 'ready' ? 'Not eligible' : hostReadinessLabel(host.readiness);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="table-wrap"><table>
|
||||
<thead><tr><th>Host</th><th>Additional instances</th><th>Limited by</th><th>CPU available</th><th>Memory available</th><th>Free ports</th><th><span class="sr-only">Details</span></th></tr></thead>
|
||||
<tbody>{#each hosts as host (host.id)}
|
||||
<tr>
|
||||
<td><a class="name" href={contextHref(`/hosts/${host.id}`, returnTo)}>{host.name}</a></td>
|
||||
<td class="estimate">{count(host.estimatedServers)}</td>
|
||||
<td class="limitation">{#if host.eligible}<strong>{host.limitingResource ?? 'Measurements pending'}</strong>{:else}<span>{restriction(host)}</span>{#if host.reasons[0]}<a class="reason" href={contextHref(`/hosts/${host.id}?tab=setup`, returnTo)}>{host.reasons[0]}</a>{/if}{/if}</td>
|
||||
<td>{count(host.cpuCoresAvailable)}{host.cpuCoresAvailable === null ? '' : ' cores'}</td>
|
||||
<td>{memory(host.memoryBytesAvailable)}</td>
|
||||
<td>{host.portsAvailable}</td>
|
||||
<td><button aria-label={`Details for ${host.name}`} aria-expanded={expanded.includes(host.id)} aria-controls={`capacity-host-${host.id}`} onclick={() => expanded = expanded.includes(host.id) ? expanded.filter(id => id !== host.id) : [...expanded, host.id]}>Details</button></td>
|
||||
</tr>
|
||||
<tr hidden={!expanded.includes(host.id)} id={`capacity-host-${host.id}`}><td colspan="7" class="detail-cell">
|
||||
<div class="detail-content">
|
||||
{#if host.reasons.length}<ul>{#each host.reasons as reason}<li>{reason}</li>{/each}</ul>{/if}
|
||||
<p>Games ready: {host.games.length ? host.games.map(game => game === 't5' ? 'Black Ops I' : 'Black Ops II').join(', ') : 'None'} · {host.portsAvailable} of {host.totalPorts} ports free.</p>
|
||||
<p>Instance limits: CPU {count(host.limits.cpu)} · Memory {count(host.limits.memory)} · Ports {host.limits.ports}.</p>
|
||||
<dl>{#each [{ label: 'Running instance', value: host.calibration.running }, { label: 'Starting instance', value: host.calibration.startup }] as allowance}<div>
|
||||
<dt>{allowance.label}</dt><dd>{count(allowance.value.cpuCores)} cores · {memory(allowance.value.memoryBytes)}</dd>
|
||||
<dd class="secondary">{allowance.value.source === 'bootstrap' ? 'Default allowance' : `${allowance.value.source === 'host' ? 'Host' : 'Fleet'} observations · ${allowance.value.samples} samples`}</dd>
|
||||
</div>{/each}</dl>
|
||||
<p>Starting instances may need more resources. Provisioning checks current capacity before reserving a host and port.</p>
|
||||
<p>{host.updatedAt ? `Reported ${$timeFormat.dateTime(host.updatedAt)}` : 'No host report yet'}</p>
|
||||
</div>
|
||||
</td></tr>
|
||||
{/each}</tbody>
|
||||
</table></div>
|
||||
|
||||
<style>
|
||||
.table-wrap { overflow-x:auto; border:1px solid var(--border-subtle); border-radius:var(--radius-md); }
|
||||
table { width:100%; min-width:800px; border-collapse:collapse; text-align:left; font-size:var(--text-sm); }
|
||||
th { padding:12px 16px; color:var(--text-secondary); font-size:var(--text-xs); font-weight:500; background:var(--surface-sunken); }
|
||||
td { padding:14px 16px; border-top:1px solid var(--border-subtle); background:var(--surface); font-variant-numeric:tabular-nums; }
|
||||
tr:hover>td { background:var(--surface-raised); }
|
||||
a { color:var(--accent-primary-hover); } .name { color:var(--text-primary); font-weight:600; text-decoration:none; }
|
||||
.name:hover { color:var(--accent-primary-hover); text-decoration:underline; }
|
||||
.estimate { font-weight:600; } .limitation { max-width:280px; } .limitation strong { font-weight:500; }
|
||||
.reason { display:block; font-size:var(--text-xs); color:var(--text-secondary); line-height:1.5; margin-top:4px; }
|
||||
button { border:0; background:none; color:var(--accent-primary-hover); font:inherit; cursor:pointer; padding:4px; }
|
||||
button:focus-visible,a:focus-visible { outline:2px solid var(--accent-primary); outline-offset:3px; }
|
||||
.detail-cell { background:var(--surface-sunken); } .detail-content { display:grid; gap:12px; font-size:var(--text-xs); color:var(--text-secondary); }
|
||||
p,dl,ul { margin:0; } ul { padding-left:18px; line-height:1.7; }
|
||||
dl { display:flex; gap:40px; flex-wrap:wrap; } dt { color:var(--text-primary); font-weight:500; } dd { margin:6px 0 0; }
|
||||
.secondary { color:var(--text-tertiary); }
|
||||
.sr-only { position:absolute; width:1px; height:1px; overflow:hidden; clip-path:inset(50%); white-space:nowrap; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user