140 lines
5.2 KiB
Svelte
140 lines
5.2 KiB
Svelte
<script lang="ts">
|
|
import type { HostLibraryCache } from '$lib/game-files.js';
|
|
import type { Host } from '$lib/hosts.js';
|
|
import GameFileTransferProgress from './GameFileTransferProgress.svelte';
|
|
import Badge from './primitives/Badge.svelte';
|
|
let {
|
|
caches,
|
|
hosts,
|
|
latest = false
|
|
}: { caches: HostLibraryCache[]; hosts: Host[]; latest?: boolean } = $props();
|
|
const healthy = $derived(
|
|
caches.filter(
|
|
(cache) =>
|
|
cache.status === 'ready' &&
|
|
hosts.some((host) => host.id === cache.hostId && host.connected) &&
|
|
!cache.error
|
|
)
|
|
);
|
|
const attention = $derived(caches.filter((cache) => !healthy.includes(cache)));
|
|
function automaticDownloadDetail(host: Host | undefined): string {
|
|
if (host?.decommission) return 'Automatic setup is paused during host deletion.';
|
|
if (!host?.enrolled) return 'Waiting for host enrollment.';
|
|
if (host.mode !== 'active') return 'Automatic setup resumes when the host is active.';
|
|
if (!host.credentialActive || !host.connected)
|
|
return 'Automatic setup resumes when the host reconnects.';
|
|
if (host.readiness === 'degraded')
|
|
return (
|
|
host.readinessReasons[0] ??
|
|
'The host needs attention before automatic setup can continue.'
|
|
);
|
|
return 'Download starts automatically after runtime preparation and earlier work finish.';
|
|
}
|
|
</script>
|
|
|
|
{#if !hosts.length}<p class="muted">
|
|
<a href="/hosts">Add a host</a> to prepare these files automatically.
|
|
</p>{/if}
|
|
{#if attention.length}{@render hostTable(attention)}{/if}
|
|
{#if healthy.length}<details>
|
|
<summary>Ready hosts ({healthy.length})</summary>{@render hostTable(healthy)}
|
|
</details>{/if}
|
|
|
|
{#snippet hostTable(rows: HostLibraryCache[])}
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead><tr><th>Host</th><th>File status</th><th>Details</th></tr></thead><tbody>
|
|
{#each rows as cache (cache.hostId)}
|
|
{@const host = hosts.find((host) => host.id === cache.hostId)}
|
|
{@const waiting = latest && cache.status === 'missing'}
|
|
<tr>
|
|
<td
|
|
><a href={`/hosts/${cache.hostId}`}>{host?.name ?? cache.hostId}</a
|
|
>{#if !host?.connected}<span class="muted">{' · Offline'}</span
|
|
>{/if}</td
|
|
>
|
|
<td
|
|
><Badge
|
|
variant={cache.status === 'failed'
|
|
? 'danger'
|
|
: cache.status === 'ready'
|
|
? 'online'
|
|
: 'neutral'}
|
|
>{waiting
|
|
? 'Waiting'
|
|
: cache.status[0].toUpperCase() + cache.status.slice(1)}</Badge
|
|
></td
|
|
>
|
|
<td
|
|
>{#if cache.status === 'ready'}<details>
|
|
<summary>Transfer details</summary><GameFileTransferProgress
|
|
{cache}
|
|
connected={host?.connected ?? false}
|
|
/>
|
|
</details>
|
|
{:else if cache.status !== 'missing'}<GameFileTransferProgress
|
|
{cache}
|
|
connected={host?.connected ?? false}
|
|
/>{/if}
|
|
{#if cache.error}<p class="error">{cache.error}</p>{:else if waiting}<p
|
|
class="muted"
|
|
>
|
|
{automaticDownloadDetail(host)}
|
|
</p>{/if}
|
|
{#if waiting || cache.status === 'failed'}<a
|
|
href={`/hosts/${cache.hostId}`}>View host setup</a
|
|
>{/if}
|
|
</td>
|
|
</tr>
|
|
{/each}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{/snippet}
|
|
|
|
<style>
|
|
.table-wrap {
|
|
overflow-x: auto;
|
|
margin-top: 10px;
|
|
}
|
|
table {
|
|
width: 100%;
|
|
min-width: 520px;
|
|
border-collapse: collapse;
|
|
text-align: left;
|
|
font-size: var(--text-sm);
|
|
}
|
|
th,
|
|
td {
|
|
padding: 10px;
|
|
border-bottom: 1px solid var(--border-subtle);
|
|
}
|
|
th {
|
|
color: var(--text-secondary);
|
|
font-weight: 500;
|
|
font-size: var(--text-xs);
|
|
}
|
|
summary {
|
|
cursor: pointer;
|
|
font-size: var(--text-sm);
|
|
}
|
|
a {
|
|
color: var(--accent-primary-hover);
|
|
}
|
|
.muted {
|
|
color: var(--text-secondary);
|
|
font-size: var(--text-sm);
|
|
}
|
|
p {
|
|
margin: 6px 0;
|
|
}
|
|
.error {
|
|
color: var(--state-danger);
|
|
}
|
|
summary:focus-visible,
|
|
a:focus-visible {
|
|
outline: 2px solid var(--accent-primary);
|
|
outline-offset: 3px;
|
|
}
|
|
</style>
|