feat(web): wire local-decky teardown to DELETE /deckies/{name}
The Fleet UI only showed TEARDOWN for swarm-pinned deckies (POST
/swarm/hosts/{uuid}/teardown). Local deckies had no delete control though
the API now exposes DELETE /deckies/{name}.
teardown() branches on swarm vs local; the card's two-step arm/CONFIRM
button renders for any admin, keyed td:${host_uuid ?? 'local'}:${name}.
This commit is contained in:
@@ -113,8 +113,7 @@ const DeckyFleet: React.FC<FleetProps> = ({ searchQuery = '' }) => {
|
||||
// Two-step teardown: first click arms the button, second click within
|
||||
// 4s actually fires the POST. Keeps swarm hosts safe from misclicks.
|
||||
const handleTeardown = async (d: Decky) => {
|
||||
if (!d.swarm) return;
|
||||
const key = `td:${d.swarm.host_uuid}:${d.name}`;
|
||||
const key = `td:${d.swarm?.host_uuid ?? 'local'}:${d.name}`;
|
||||
if (armed !== key) { arm(key); return; }
|
||||
setArmed(null);
|
||||
const r = await teardown(d);
|
||||
|
||||
@@ -98,6 +98,19 @@ describe('DeckyCard', () => {
|
||||
expect(screen.getByText('CONFIRM')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows TEARDOWN for admin on a local (non-swarm) decky, keyed td:local:', () => {
|
||||
const local = makeDecky({ name: 'decoy-local' });
|
||||
const { rerender } = render(
|
||||
<DeckyCard {...baseProps} isAdmin decky={local} />,
|
||||
);
|
||||
expect(screen.getByText('TEARDOWN')).toBeInTheDocument();
|
||||
|
||||
rerender(
|
||||
<DeckyCard {...baseProps} isAdmin armed="td:local:decoy-local" decky={local} />,
|
||||
);
|
||||
expect(screen.getByText('CONFIRM')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('clicking the card body fires onInspect', async () => {
|
||||
const onInspect = vi.fn();
|
||||
const user = userEvent.setup();
|
||||
|
||||
@@ -38,7 +38,7 @@ export const DeckyCard: React.FC<Props> = ({
|
||||
const hits = hitsFor(decky);
|
||||
const hot = dot === 'hot';
|
||||
const dotClass = mutating ? 'mutating' : dot;
|
||||
const tdKey = decky.swarm ? `td:${decky.swarm.host_uuid}:${decky.name}` : '';
|
||||
const tdKey = `td:${decky.swarm?.host_uuid ?? 'local'}:${decky.name}`;
|
||||
|
||||
// Live service mutation is local-only (admin, non-swarm). Swarm
|
||||
// deckies live on a remote agent — the W3 path runs docker compose
|
||||
@@ -347,12 +347,12 @@ export const DeckyCard: React.FC<Props> = ({
|
||||
{mutating ? 'MUTATING' : 'FORCE MUTATE'}
|
||||
</button>
|
||||
)}
|
||||
{decky.swarm && isAdmin && (
|
||||
{isAdmin && (
|
||||
<button
|
||||
className="btn alert small"
|
||||
disabled={tdBusy}
|
||||
onClick={() => onTeardown(decky)}
|
||||
title="Stop this decky on its host"
|
||||
title={decky.swarm ? 'Stop this decky on its host' : 'Tear down this decky'}
|
||||
>
|
||||
<PowerOff size={10} />
|
||||
{tdBusy
|
||||
|
||||
@@ -38,7 +38,7 @@ export interface UseDeckyFleetResult {
|
||||
mutate: (name: string) => Promise<MutateResult>;
|
||||
/** Update or clear a decky's periodic mutate interval. */
|
||||
setMutateInterval: (name: string, minutes: number | null) => Promise<boolean>;
|
||||
/** Tear down a swarm-pinned decky on its host. */
|
||||
/** Tear down a decky — swarm-pinned on its host, or local via DELETE. */
|
||||
teardown: (d: Decky) => Promise<TeardownResult>;
|
||||
/** Optimistically apply a server-returned services list to a card
|
||||
* (used by DeckyCard's add/remove-service flow). */
|
||||
@@ -171,10 +171,13 @@ export function useDeckyFleet(): UseDeckyFleetResult {
|
||||
);
|
||||
|
||||
const teardown = useCallback(async (d: Decky): Promise<TeardownResult> => {
|
||||
if (!d.swarm) return { ok: false, reason: 'not a swarm decky' };
|
||||
setTearingDown((prev) => new Set(prev).add(d.name));
|
||||
try {
|
||||
await api.post(`/swarm/hosts/${d.swarm.host_uuid}/teardown`, { decky_id: d.name });
|
||||
if (d.swarm) {
|
||||
await api.post(`/swarm/hosts/${d.swarm.host_uuid}/teardown`, { decky_id: d.name });
|
||||
} else {
|
||||
await api.delete(`/deckies/${encodeURIComponent(d.name)}`);
|
||||
}
|
||||
await fetchDeckies(deployMode?.mode);
|
||||
return { ok: true };
|
||||
} catch (err: unknown) {
|
||||
|
||||
Reference in New Issue
Block a user