Improve application responsiveness and background processing
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
import { listHosts } from '$lib/server/hosts/index.js';
|
||||
import { presentHostPreparation } from '$lib/server/hosts/preparation.js';
|
||||
|
||||
export function load() { return { hosts: listHosts().map(presentHostPreparation) }; }
|
||||
@@ -1,22 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { onMount, untrack } from 'svelte';
|
||||
import { pollVisible } from '$ui/poll.js';
|
||||
let { data }: { data: import('./$types').PageData } = $props();
|
||||
import { page } from '$app/stores';
|
||||
import type { Host } from '$lib/hosts.js';
|
||||
import { hostAgentUpdateLabel, hostDeletionMessage, hostReadinessLabel } from '$lib/hosts.js';
|
||||
import SectionHeader from '$ui/components/primitives/SectionHeader.svelte';
|
||||
import Button from '$ui/components/primitives/Button.svelte';
|
||||
import DeleteHostDialog from '$ui/components/DeleteHostDialog.svelte';
|
||||
let hosts = $state<Host[]>([]);
|
||||
let loading = $state(true); let busy = $state(false); let error = $state('');
|
||||
let hosts = $state<Host[]>(untrack(() => data.hosts));
|
||||
let loading = $state(false); let busy = $state(false); let error = $state('');
|
||||
let notice = $state(''); let deleteOpen = $state(false); let deletingHost = $state<Host | null>(null);
|
||||
let loadSequence = 0;
|
||||
let name = $state(''); let portMin = $state(28960); let portMax = $state(28980);
|
||||
let enrollment = $state<{ command: string; expiresAt: string; hostId: string } | null>(null);
|
||||
const canManage = $derived($page.data.user?.role === 'admin');
|
||||
async function load() {
|
||||
async function load(signal?: AbortSignal) {
|
||||
const sequence = ++loadSequence;
|
||||
try { const response = await fetch('/api/hosts'); const body = await response.json(); if (!response.ok) throw new Error(body.error ?? 'Could not load hosts.'); if (sequence === loadSequence) { hosts = body.hosts; error = ''; } }
|
||||
catch (cause) { if (sequence === loadSequence) error = (cause as Error).message; } finally { if (sequence === loadSequence) loading = false; }
|
||||
try { const response = await fetch('/api/hosts', { signal: signal ?? AbortSignal.timeout(15000) }); const body = await response.json(); if (!response.ok) throw new Error(body.error ?? 'Could not load hosts.'); if (sequence === loadSequence) { hosts = body.hosts; error = ''; } }
|
||||
catch (cause) { if (!signal?.aborted && sequence === loadSequence) error = (cause as Error).message; return false; } finally { if (sequence === loadSequence) loading = false; }
|
||||
}
|
||||
async function deleted(result: { deleted: boolean }) {
|
||||
notice = result.deleted ? `${deletingHost?.name ?? 'Host'} deleted.` : `Cleanup requested for ${deletingHost?.name ?? 'host'}. It remains listed until its files and agent are removed.`;
|
||||
@@ -32,7 +34,7 @@
|
||||
enrollment = body.enrollment; name = ''; await load();
|
||||
} catch (cause) { error = (cause as Error).message; } finally { busy = false; }
|
||||
}
|
||||
onMount(() => { void load(); const timer = setInterval(() => { void load(); }, 15000); return () => clearInterval(timer); });
|
||||
onMount(() => pollVisible(load, 30000, false));
|
||||
</script>
|
||||
|
||||
<svelte:head><title>Hosts · Electric Cherry</title></svelte:head>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { untrack } from 'svelte';
|
||||
import { pollVisible } from '$ui/poll.js';
|
||||
let requestSequence = 0;
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import type { Host, HostOperation, HostMode } from '$lib/hosts.js';
|
||||
@@ -17,11 +19,12 @@
|
||||
const canManage = $derived($page.data.user?.role === 'admin');
|
||||
const cleaning = $derived(!!host?.decommission);
|
||||
function bytes(value: unknown) { return typeof value === 'number' ? `${(value / 1073741824).toFixed(1)} GiB` : 'Unknown'; }
|
||||
async function load(initialize = false) {
|
||||
async function load(initialize = false, signal?: AbortSignal) {
|
||||
const target = endpoint; const sequence = ++requestSequence;
|
||||
try {
|
||||
const response = await fetch(endpoint); const body = await response.json(); if (response.status === 404 && host?.decommission) { await goto('/hosts'); return; } if (!response.ok) throw new Error(body.error ?? body.message ?? 'Could not load host.'); host = body.host; operations = body.operations;
|
||||
const response = await fetch(target, { signal: signal ?? AbortSignal.timeout(15000) }); const body = await response.json(); if (signal?.aborted || target !== endpoint || sequence !== requestSequence) return; if (response.status === 404 && host?.decommission) { await goto('/hosts'); return; } if (!response.ok) throw new Error(body.error ?? body.message ?? 'Could not load host.'); host = body.host; operations = body.operations; error = '';
|
||||
if (initialize && host) { name = host.name; portMin = host.portMin; portMax = host.portMax; bindAddress = host.bindAddress; publicAddress = host.publicAddress; minimumFreeGB = host.minimumFreeBytes / 1073741824; runtimeImage = body.defaultRuntimeImage ?? runtimeImage; }
|
||||
} catch (cause) { error = (cause as Error).message; } finally { loading = false; }
|
||||
} catch (cause) { if (!signal?.aborted && sequence === requestSequence) error = (cause as Error).message; return false; } finally { if (sequence === requestSequence) loading = false; }
|
||||
}
|
||||
async function action(path: string, method: string, body?: unknown, message?: string) {
|
||||
busy = true; error = ''; notice = '';
|
||||
@@ -30,7 +33,13 @@
|
||||
}
|
||||
async function save(event: SubmitEvent) { event.preventDefault(); await action('', 'PATCH', { name, portMin, portMax, bindAddress, publicAddress, minimumFreeBytes: Math.round(minimumFreeGB * 1073741824) }, 'Host settings saved.'); }
|
||||
async function mode(value: HostMode) { await action('', 'PATCH', { mode: value }, value === 'draining' ? 'Drain enabled. Existing games continue until safe retirement.' : `Host ${value === 'active' ? 'enabled' : 'disabled'}.`); }
|
||||
onMount(() => { void load(true); const timer = setInterval(() => { void load(); }, 10000); return () => clearInterval(timer); });
|
||||
$effect(() => {
|
||||
void endpoint;
|
||||
return untrack(() => {
|
||||
let initialize = true;
|
||||
return pollVisible(async signal => { const result = await load(initialize, signal); if (result !== false) initialize = false; return result; }, 15000);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head><title>{host?.name ?? 'Host'} · Electric Cherry</title></svelte:head>
|
||||
|
||||
Reference in New Issue
Block a user