Add clear host deletion controls with workload safeguards
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
<script lang="ts">
|
||||
import type { Host, HostDeletion } from '$lib/hosts.js';
|
||||
import Button from './primitives/Button.svelte';
|
||||
import Dialog from './primitives/Dialog.svelte';
|
||||
|
||||
let { open = $bindable(false), host, ondeleted }:
|
||||
{ open?: boolean; host: Pick<Host, 'id' | 'name'> | null; ondeleted: () => void | Promise<void> } = $props();
|
||||
let deletion = $state<HostDeletion | null>(null);
|
||||
let checking = $state(false); let deleting = $state(false); let error = $state('');
|
||||
|
||||
async function inspect(id: string, signal?: AbortSignal) {
|
||||
checking = true; deletion = null; error = '';
|
||||
try {
|
||||
const response = await fetch(`/api/hosts/${encodeURIComponent(id)}`, { signal });
|
||||
const body = await response.json();
|
||||
if (!response.ok) throw new Error(body.error ?? body.message ?? 'Could not check this host.');
|
||||
if (!signal?.aborted) deletion = body.deletion;
|
||||
} catch (cause) { if (!signal?.aborted) error = (cause as Error).message; }
|
||||
finally { if (!signal?.aborted) checking = false; }
|
||||
}
|
||||
$effect(() => {
|
||||
if (!open || !host) return;
|
||||
const controller = new AbortController();
|
||||
void inspect(host.id, controller.signal);
|
||||
return () => controller.abort();
|
||||
});
|
||||
async function remove() {
|
||||
if (!host || deleting || checking || !deletion?.allowed) return;
|
||||
const id = host.id;
|
||||
deleting = true; error = '';
|
||||
try {
|
||||
const response = await fetch(`/api/hosts/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
||||
const body = await response.json();
|
||||
if (!response.ok) {
|
||||
await inspect(id);
|
||||
throw new Error(body.error ?? body.message ?? 'Could not delete this host.');
|
||||
}
|
||||
open = false;
|
||||
await ondeleted();
|
||||
} catch (cause) { error = (cause as Error).message; }
|
||||
finally { deleting = false; }
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog bind:open title={`Delete ${host?.name ?? 'host'}?`} busy={deleting}
|
||||
description="Remove this host from Electric Cherry and revoke its agent access and unused installer links.">
|
||||
<div class="content">
|
||||
<p>The agent and game files stay on the machine. Published game libraries and operation history are kept. Commands that have never been sent to the host will be cancelled.</p>
|
||||
{#if checking}<p role="status">Checking servers and unfinished operations…</p>
|
||||
{:else if deletion && !deletion.allowed}<div role="status" class="blocked"><strong>This host still has work to finish:</strong><ul>{#each deletion.blockers as blocker}<li>{blocker}</li>{/each}</ul><p>Open <a href={`/hosts/${host?.id}`} onclick={() => open = false}>host details</a> to review its operations or drain its servers.</p></div>
|
||||
{:else if deletion?.allowed}<p>This host is ready to delete. Adding it again requires a new enrollment.</p>{/if}
|
||||
{#if error}<p role="alert" class="ui-error">{error}</p>{/if}
|
||||
</div>
|
||||
{#snippet actions()}
|
||||
<Button variant="ghost" disabled={deleting} onclick={() => open = false}>Cancel</Button>
|
||||
{#if !deletion?.allowed}<Button disabled={checking || deleting || !host} onclick={() => { if (host) void inspect(host.id); }}>Check again</Button>{/if}
|
||||
<Button variant="danger" loading={deleting} disabled={checking || !deletion?.allowed} onclick={remove}>Delete host</Button>
|
||||
{/snippet}
|
||||
</Dialog>
|
||||
|
||||
<style>
|
||||
.content { display:grid; gap:16px; } p,li { margin:0; color:var(--text-secondary); font-size:var(--text-sm); line-height:1.65; }
|
||||
.blocked { display:grid; gap:12px; } strong { font-size:var(--text-sm); } ul { margin:0; padding-left:20px; }
|
||||
a { color:var(--accent-primary-hover); } .ui-error { color:var(--state-danger); }
|
||||
</style>
|
||||
Reference in New Issue
Block a user