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

@@ -4,6 +4,7 @@ import {
Menu, X, Search, Activity, LayoutDashboard, Terminal, Settings, LogOut,
Server, Archive, Package, Network, ChevronDown, ChevronRight, HardDrive,
ShieldAlert, Bell, Webhook, Lock, Crosshair, Fingerprint, Zap, Cpu, Mail,
Target,
} from '../icons';
import { prefetchRoute } from '../routePrefetch';
import './Layout.css';
@@ -35,6 +36,7 @@ const ROUTE_LABELS: Record<string, string> = {
'/campaigns': 'CAMPAIGNS',
'/orchestrator': 'ORCHESTRATOR',
'/persona-generation': 'PERSONA GENERATION',
'/canary-tokens': 'CANARY TOKENS',
'/config': 'CONFIG',
'/swarm-updates': 'REMOTE UPDATES',
'/swarm/hosts': 'SWARM HOSTS',
@@ -140,6 +142,7 @@ const Layout: React.FC<LayoutProps> = ({
<NavGroup label="AUTOMATION" icon={<Zap size={20} />} open={sidebarOpen}>
<NavItem to="/orchestrator" icon={<Cpu size={18} />} label="Orchestrator" open={sidebarOpen} indent />
<NavItem to="/persona-generation" icon={<Mail size={18} />} label="Persona Generation" open={sidebarOpen} indent />
<NavItem to="/canary-tokens" icon={<Target size={18} />} label="Canary Tokens" open={sidebarOpen} indent />
</NavGroup>
<NavGroup label="SWARM" icon={<Network size={20} />} open={sidebarOpen}>
<NavItem to="/swarm/hosts" icon={<HardDrive size={18} />} label="SWARM Hosts" open={sidebarOpen} indent />