165 lines
5.4 KiB
Svelte
165 lines
5.4 KiB
Svelte
<script lang="ts">
|
|
import type { Host } from '$lib/hosts.js';
|
|
import { contextHref } from '$lib/navigation-context.js';
|
|
import {
|
|
hostListStatus,
|
|
hostReadingsStale,
|
|
hostCpuUsage,
|
|
hostUsedSpace
|
|
} from '$ui/host-overview.js';
|
|
import { plural } from '$ui/plural.js';
|
|
import Badge from './primitives/Badge.svelte';
|
|
let {
|
|
hosts,
|
|
now,
|
|
returnTo = '/hosts'
|
|
}: {
|
|
hosts: (Host & { serverCount: number; playerCount: number; unreportedInstances: number })[];
|
|
now: number;
|
|
returnTo?: string;
|
|
} = $props();
|
|
</script>
|
|
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead
|
|
><tr
|
|
><th>Host</th><th>Status</th><th title="Assigned servers, including stopped servers"
|
|
>Servers</th
|
|
><th title="Players reported by online servers">Players</th><th>CPU</th><th
|
|
>Memory <span>used / total</span></th
|
|
><th>Disk <span>used / total</span></th></tr
|
|
></thead
|
|
>
|
|
<tbody
|
|
>{#each hosts as host (host.id)}
|
|
{@const status = hostListStatus(host, now)}
|
|
{@const stale = hostReadingsStale(host, now)}
|
|
<tr>
|
|
<td
|
|
><a class="name" href={contextHref(`/hosts/${host.id}`, returnTo)}
|
|
>{host.name}</a
|
|
>{#if host.hostname && host.hostname !== host.name}<span class="secondary"
|
|
>{host.hostname}</span
|
|
>{/if}
|
|
{#if stale && host.lastHeartbeat}<span class="secondary"
|
|
>Resource readings stale</span
|
|
>{/if}</td
|
|
>
|
|
<td class="status"
|
|
><Badge variant={status.tone}>{status.label}</Badge>{#if status.detail}<a
|
|
class="reason"
|
|
href={contextHref(`/hosts/${host.id}?tab=${status.tab}`, returnTo)}
|
|
>{status.detail}</a
|
|
>{/if}</td
|
|
>
|
|
<td
|
|
><a
|
|
href={contextHref(`/hosts/${host.id}?tab=servers`, returnTo)}
|
|
aria-label={`${host.serverCount} assigned ${plural(host.serverCount, 'server')} on ${host.name}`}
|
|
>{host.serverCount}</a
|
|
></td
|
|
>
|
|
<td
|
|
title={host.unreportedInstances
|
|
? `Player counts unavailable for ${host.unreportedInstances} of ${host.serverCount} ${plural(host.serverCount, 'server')}`
|
|
: 'Players reported by online servers'}
|
|
>{host.unreportedInstances === host.serverCount && host.serverCount > 0
|
|
? '—'
|
|
: `${host.playerCount}${host.unreportedInstances ? '+' : ''}`}</td
|
|
>
|
|
<td class:stale>{hostCpuUsage(host.inventory.cpuUsagePercent)}</td>
|
|
<td class:stale
|
|
>{hostUsedSpace(
|
|
host.inventory.memoryTotal,
|
|
host.inventory.memoryAvailable
|
|
)}</td
|
|
>
|
|
<td class:stale
|
|
>{hostUsedSpace(
|
|
host.inventory.storageTotal,
|
|
host.inventory.storageAvailable
|
|
)}</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: 850px;
|
|
border-collapse: collapse;
|
|
text-align: left;
|
|
font-size: var(--text-sm);
|
|
}
|
|
th {
|
|
padding: 12px 16px;
|
|
color: var(--text-secondary);
|
|
background: var(--surface-sunken);
|
|
font-weight: 500;
|
|
font-size: var(--text-xs);
|
|
}
|
|
th span {
|
|
display: block;
|
|
font-weight: 400;
|
|
margin-top: 3px;
|
|
}
|
|
td {
|
|
padding: 16px;
|
|
background: var(--surface);
|
|
border-top: 1px solid var(--border-subtle);
|
|
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;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
.name:hover {
|
|
color: var(--accent-primary-hover);
|
|
text-decoration: underline;
|
|
}
|
|
.status {
|
|
max-width: 330px;
|
|
min-width: 140px;
|
|
}
|
|
.secondary,
|
|
.reason {
|
|
display: block;
|
|
font-size: var(--text-xs);
|
|
color: var(--text-secondary);
|
|
margin-top: 6px;
|
|
line-height: 1.5;
|
|
}
|
|
.reason {
|
|
overflow-wrap: anywhere;
|
|
text-decoration: none;
|
|
}
|
|
.reason:hover {
|
|
text-decoration: underline;
|
|
color: var(--accent-primary-hover);
|
|
}
|
|
.stale {
|
|
color: var(--text-secondary);
|
|
}
|
|
a:focus-visible {
|
|
outline: 2px solid var(--accent-primary);
|
|
outline-offset: 3px;
|
|
}
|
|
</style>
|