feat(dashboard): GuruConnect v2 operator console (pass 1)
All checks were successful
All checks were successful
React + Vite + TypeScript SPA: scaffold, operations-terminal design system, Bearer-token auth, and the Machines view. - Design system: OKLCH-tinted dark theme (ink-slate + signal-cyan), Hanken Grotesk + JetBrains Mono, status-color language (online/offline/granted/pending/denied/not_required), motion with prefers-reduced-motion honored. - Auth: token in sessionStorage via ref (never React state), protected routes, 401 session teardown, admin-gated per-agent-key UI. - Machines view: data table (sticky header, keyboard-activated rows, skeleton loading, actionable empty/error states), non-blocking detail drawer, delete confirm, admin key management with copy-once reveal. - UI primitives: Modal (focus trap + inert + portal + dialogStack), Drawer, Table, Badge/StatusDot, toast, states. - Typed API client normalizing the two error-envelope shapes. Passed Code Review (no blockers), impeccable critique-and-polish, and local gates (tsc/lint/build green). Dev-only Vite proxy to :3002. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
51
dashboard/src/components/layout/Topbar.tsx
Normal file
51
dashboard/src/components/layout/Topbar.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { useAuth } from "../../auth/AuthContext";
|
||||
import { useRelayStatus } from "../../lib/useRelayStatus";
|
||||
import { Badge } from "../ui/Badge";
|
||||
import { Button } from "../ui/Button";
|
||||
import { LogoutIcon } from "./icons";
|
||||
|
||||
function roleTone(role: string | undefined): "accent" | "ok" | "neutral" {
|
||||
if (role === "admin") return "accent";
|
||||
if (role === "operator") return "ok";
|
||||
return "neutral";
|
||||
}
|
||||
|
||||
export function Topbar() {
|
||||
const { user, logout } = useAuth();
|
||||
const { live, checking } = useRelayStatus();
|
||||
|
||||
const relayClass = live ? "relay relay--live" : "relay relay--down";
|
||||
const relayLabel = checking ? "probing" : live ? "live" : "offline";
|
||||
|
||||
return (
|
||||
<header className="topbar">
|
||||
<div
|
||||
className={relayClass}
|
||||
title="GuruConnect relay connection"
|
||||
aria-label={`Relay ${relayLabel}`}
|
||||
>
|
||||
<span className="relay__pip" aria-hidden="true" />
|
||||
<span>Relay</span>
|
||||
<span className="relay__label mono">{relayLabel}</span>
|
||||
</div>
|
||||
|
||||
<div className="topbar__spacer" />
|
||||
|
||||
<div className="topbar__user">
|
||||
<div className="topbar__id">
|
||||
<span className="topbar__username">{user?.username}</span>
|
||||
</div>
|
||||
<Badge tone={roleTone(user?.role)}>{user?.role ?? "—"}</Badge>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => void logout()}
|
||||
aria-label="Log out"
|
||||
>
|
||||
<LogoutIcon width={15} height={15} />
|
||||
Logout
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user