diff --git a/decnet_web/src/components/DeckyFleet.tsx b/decnet_web/src/components/DeckyFleet.tsx index ec9d90a7..d1a39449 100644 --- a/decnet_web/src/components/DeckyFleet.tsx +++ b/decnet_web/src/components/DeckyFleet.tsx @@ -113,8 +113,7 @@ const DeckyFleet: React.FC = ({ 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); diff --git a/decnet_web/src/components/DeckyFleet/DeckyCard.test.tsx b/decnet_web/src/components/DeckyFleet/DeckyCard.test.tsx index d451d7d2..31ba9113 100644 --- a/decnet_web/src/components/DeckyFleet/DeckyCard.test.tsx +++ b/decnet_web/src/components/DeckyFleet/DeckyCard.test.tsx @@ -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( + , + ); + expect(screen.getByText('TEARDOWN')).toBeInTheDocument(); + + rerender( + , + ); + expect(screen.getByText('CONFIRM')).toBeInTheDocument(); + }); + it('clicking the card body fires onInspect', async () => { const onInspect = vi.fn(); const user = userEvent.setup(); diff --git a/decnet_web/src/components/DeckyFleet/DeckyCard.tsx b/decnet_web/src/components/DeckyFleet/DeckyCard.tsx index 2398a6c4..7ced57ca 100644 --- a/decnet_web/src/components/DeckyFleet/DeckyCard.tsx +++ b/decnet_web/src/components/DeckyFleet/DeckyCard.tsx @@ -38,7 +38,7 @@ export const DeckyCard: React.FC = ({ 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 = ({ {mutating ? 'MUTATING' : 'FORCE MUTATE'} )} - {decky.swarm && isAdmin && ( + {isAdmin && (