Simplify host overview and move enrollment into drawer

This commit is contained in:
2026-10-06 17:18:31 -04:00
parent a991198d69
commit ef290ae0ee
11 changed files with 310 additions and 61 deletions
@@ -0,0 +1,61 @@
<script lang="ts">
import Dialog from './primitives/Dialog.svelte';
import Button from './primitives/Button.svelte';
let { open = $bindable(false), onadded }: { open?: boolean; onadded: () => Promise<unknown> } = $props();
let name = $state('');
let portMin = $state(28960); let portMax = $state(28980);
let busy = $state(false); let error = $state(''); let copied = $state(false);
let enrollment = $state<{ command: string; expiresAt: string; hostId: string } | null>(null);
async function add(event: SubmitEvent) {
event.preventDefault(); if (busy) return;
busy = true; error = ''; copied = false;
try {
const response = await fetch('/api/hosts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, portMin, portMax }) });
const body = await response.json(); if (!response.ok) throw new Error(body.error ?? 'Could not add host.');
enrollment = body.enrollment;
await onadded();
} catch (cause) { error = (cause as Error).message; } finally { busy = false; }
}
async function copy() {
if (!enrollment) return;
try { await navigator.clipboard.writeText(enrollment.command); copied = true; error = ''; }
catch { error = 'Could not copy automatically. Select and copy the command below.'; }
}
</script>
<Dialog bind:open title={enrollment ? 'Install the host agent' : 'Add host'} {busy} drawer>
<div class="content">
{#if error}<p role="alert" class="ui-error">{error}</p>{/if}
{#if enrollment}
<p role="status">{name} created. Run this command with administrator access on that machine.</p>
<textarea readonly aria-label="Host installation command" rows="6" value={enrollment.command}></textarea>
<div class="actions"><Button onclick={copy}>{copied ? 'Copied' : 'Copy command'}</Button><Button variant="primary" href={`/hosts/${enrollment.hostId}?tab=setup`}>Open host setup</Button></div>
<p class="muted">This single-use command expires {new Date(enrollment.expiresAt).toLocaleString()}. Generate a new installer from host settings if it expires.</p>
<Button variant="ghost" onclick={() => { enrollment = null; name = ''; copied = false; error = ''; }}>Add another host</Button>
{:else}
<p>Use a Debian 12 or 13 amd64 machine. The agent connects over HTTPS and prepares the runtime and game files automatically.</p>
<form onsubmit={add}><fieldset disabled={busy}>
<label>Friendly name<input required maxlength="200" bind:value={name} placeholder="Game host 1" /></label>
<details><summary>Advanced settings</summary><div class="ports">
<label>First game port<input type="number" required min="1" max="65535" bind:value={portMin} /></label>
<label>Last game port<input type="number" required min={portMin} max="65535" bind:value={portMax} /></label>
</div></details>
<Button type="submit" variant="primary" disabled={busy}>{busy ? 'Creating…' : 'Add host'}</Button>
</fieldset></form>
{/if}
</div>
</Dialog>
<style>
.content,fieldset { display:grid; gap:20px; }
fieldset { border:0; padding:0; margin:0; min-width:0; }
p { margin:0; color:var(--text-secondary); font-size:var(--text-sm); line-height:1.6; }
.muted { font-size:var(--text-xs); }
label { display:grid; gap:8px; font-size:var(--text-sm); }
input,textarea { box-sizing:border-box; width:100%; min-width:0; padding:10px; color:var(--text-primary); background:var(--surface-sunken); border:1px solid var(--border-control); border-radius:var(--radius-sm); }
textarea { font:var(--text-xs)/1.6 var(--font-mono); resize:vertical; }
summary { cursor:pointer; color:var(--text-secondary); font-size:var(--text-sm); }
.ports { display:grid; grid-template-columns:1fr 1fr; gap:16px; margin-top:16px; }
.actions { display:flex; flex-wrap:wrap; gap:12px; }
input:focus-visible,textarea:focus-visible,summary:focus-visible { outline:2px solid var(--accent-primary); outline-offset:3px; }
</style>