feat(ui): scope canary tokens to MazeNET topology deckies

CanaryTokens.tsx grows a Fleet/MazeNET toggle in the create modal.  In
topology mode we hydrate /topologies?status=active for the topology
picker, then GET /topologies/{id} on selection to repopulate the decky
picker — topology deckies have a different shape than fleet's /deckies
endpoint.

The tokens table gains a SCOPE column (chip: 'fleet' / 'topology'),
and a third filter dropdown alongside state.  The drawer's metadata
section shows a Scope row with a clickable jump-link back to the
MazeNET view at the right topology.

CanaryTokenRow grows a topology_id field so the drawer/list can
discriminate without re-fetching.
This commit is contained in:
2026-04-28 23:04:13 -04:00
parent 6ac8cac908
commit c942d4d333
2 changed files with 159 additions and 8 deletions

View File

@@ -8,6 +8,10 @@ export interface CanaryTokenRow {
uuid: string;
kind: 'http' | 'dns' | 'aws_passive';
decky_name: string;
// Set when the token targets a MazeNET topology decky. Null/absent
// for fleet tokens. Drives the "scope" badge in the list and the
// topology jump-link in the drawer.
topology_id: string | null;
blob_uuid: string | null;
instrumenter: string | null;
generator: string | null;
@@ -247,6 +251,19 @@ const CanaryTokenDrawer: React.FC<Props> = ({ token, onClose, onRevoked }) => {
</h3>
<Row label="UUID" value={<code>{token.uuid}</code>} />
<Row label="Decky" value={token.decky_name} />
<Row
label="Scope"
value={token.topology_id ? (
<a
href={`/mazenet?topology=${encodeURIComponent(token.topology_id)}`}
style={{ color: 'var(--accent-color, #00ff88)' }}
>
topology · {token.topology_id.slice(0, 8)}
</a>
) : (
<span style={{ opacity: 0.6 }}>fleet</span>
)}
/>
<Row label="Kind" value={KIND_LABEL[token.kind]} />
<Row label="Source" value={token.generator ? `generator: ${token.generator}` : token.instrumenter ? `instrumenter: ${token.instrumenter}` : '—'} />
<Row label="Slug" value={<code>{token.callback_token}</code>} />