feat(web): canary tokens page (under AUTOMATION)

New /canary-tokens route, lazy-loaded and gated behind the existing
auth flow. Wired into the AUTOMATION NavGroup beside Orchestrator and
Persona Generation, using the Target icon.

Two components:

- CanaryTokens.tsx: list + filter (text + state), stats summary,
  Tokens / Blobs tab switcher, inline CreateModal + UploadModal.
  Alt+C opens the create modal (per feedback_linux_meta_key). Drag-
  drop blob upload, server-sniffed MIME drives the instrumenter.
- CanaryTokenDrawer.tsx: per-token detail panel matching the
  MailDrawer.tsx visual format (right-side drawer, --bg/--border/
  --dim/--text CSS vars, X close, focus trap + escape key, monospace
  metadata table, paginated callback history).

Backdrop close uses target===currentTarget instead of stopPropagation
on the panel (per feedback_react_stop_propagation_native_delegation).
Preview button downloads the deterministically re-derived instrumented
bytes; revoke button hits DELETE with a confirm prompt.

Type-checks clean (npx tsc --noEmit).
This commit is contained in:
2026-04-27 13:27:14 -04:00
parent 53d08e01e5
commit e2c8b77546
4 changed files with 1034 additions and 0 deletions

View File

@@ -25,6 +25,10 @@ const Campaigns = lazy(() => import('./components/Campaigns'));
const CampaignDetail = lazy(() => import('./components/CampaignDetail'));
const Orchestrator = lazy(() => import('./components/Orchestrator'));
const PersonaGeneration = lazy(() => import('./components/PersonaGeneration'));
const CanaryTokens = lazy(() => import('./components/CanaryTokens'));
const TopologyPersonaGeneration = lazy(() =>
import('./components/PersonaGeneration').then((m) => ({ default: m.TopologyPersonaGeneration })),
);
const Config = lazy(() => import('./components/Config'));
const Bounty = lazy(() => import('./components/Bounty'));
const Credentials = lazy(() => import('./components/Credentials'));
@@ -125,6 +129,8 @@ const AuthedShell: React.FC<AuthedShellProps> = ({ onLogout, onSearch, searchQue
<Route path="/campaigns/:id" element={<CampaignDetail />} />
<Route path="/orchestrator" element={<Orchestrator />} />
<Route path="/persona-generation" element={<PersonaGeneration />} />
<Route path="/canary-tokens" element={<CanaryTokens />} />
<Route path="/topologies/:id/personas" element={<TopologyPersonaGeneration />} />
<Route path="/config" element={<Config />} />
<Route path="/swarm-updates" element={<RemoteUpdates />} />
<Route path="/swarm/hosts" element={<SwarmHosts />} />