Add Prettier and ESLint tooling (Refs #77)
This commit is contained in:
@@ -2,25 +2,46 @@
|
||||
import { timeFormat } from '$ui/stores/time-zone.js';
|
||||
import Dialog from './primitives/Dialog.svelte';
|
||||
import Button from './primitives/Button.svelte';
|
||||
let { open = $bindable(false), onadded }: { open?: boolean; onadded: () => Promise<unknown> } = $props();
|
||||
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 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;
|
||||
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.');
|
||||
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; }
|
||||
} 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.'; }
|
||||
try {
|
||||
await navigator.clipboard.writeText(enrollment.command);
|
||||
copied = true;
|
||||
error = '';
|
||||
} catch {
|
||||
error = 'Could not copy automatically. Select and copy the command below.';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -28,35 +49,142 @@
|
||||
<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 {$timeFormat.dateTime(enrollment.expiresAt)}. 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>
|
||||
<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 {$timeFormat.dateTime(enrollment.expiresAt)}.
|
||||
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>
|
||||
<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; }
|
||||
.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>
|
||||
|
||||
Reference in New Issue
Block a user