misc changes

This commit is contained in:
lda
2026-05-15 12:11:45 +07:00 Verified
parent 3074fbb41a
commit 6fc3dee12d
18 changed files with 115 additions and 90 deletions
+14
View File
@@ -4,6 +4,20 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wakey Operator UI</title>
<script>
(function () {
try {
var t = localStorage.getItem("wakey-ui-theme");
if (
t === "dark" ||
((t === "system" || !t) &&
window.matchMedia("(prefers-color-scheme: dark)").matches)
) {
document.documentElement.classList.add("dark");
}
} catch (_) {}
})();
</script>
</head>
<body>
<div id="root"></div>
+1
View File
@@ -144,6 +144,7 @@ export function App() {
selectedAgentId={selectedAgentId}
onSelectAgent={setSelectedAgentId}
onAfterWake={loadAll}
onRefresh={loadAll}
/>
}
/>
+7 -1
View File
@@ -93,7 +93,7 @@ export function AppLayout() {
className={[
"sidebar",
collapsed ? "sidebar--collapsed" : "",
"fixed inset-y-0 left-0 z-50 shadow-xl transition-all duration-300 ease-in-out",
"fixed inset-y-0 left-0 z-50 shadow-xl transition-all duration-100 ease-in-out",
"md:sticky md:top-0 md:z-30 md:shadow-none",
collapsed ? "-translate-x-full md:translate-x-0" : "translate-x-0",
]
@@ -219,8 +219,14 @@ export function AppLayout() {
{/* Mobile backdrop */}
{!collapsed && (
<div
role="button"
tabIndex={0}
className="fixed inset-0 z-40 bg-black/20 md:hidden animate-in fade-in duration-200"
onClick={() => setCollapsed(true)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") setCollapsed(true);
}}
aria-label="Close menu"
/>
)}
+8 -7
View File
@@ -72,7 +72,7 @@ export function AlertsPage({ alerts, transitions, onRefresh }: Props) {
</CardHeader>
<CardContent className="space-y-3">
<div className="grid gap-2 sm:grid-cols-3">
<label className="grid gap-1 text-sm text-muted-foreground">
<label htmlFor="alert-severity" className="grid gap-1 text-sm text-muted-foreground">
Severity
<Select
value={severity}
@@ -80,7 +80,7 @@ export function AlertsPage({ alerts, transitions, onRefresh }: Props) {
if (value) setSeverity(value);
}}
>
<SelectTrigger className="w-full">
<SelectTrigger id="alert-severity" className="w-full">
<SelectValue placeholder="Severity" />
</SelectTrigger>
<SelectContent>
@@ -93,7 +93,7 @@ export function AlertsPage({ alerts, transitions, onRefresh }: Props) {
</Select>
</label>
<label className="grid gap-1 text-sm text-muted-foreground">
<label htmlFor="alert-status" className="grid gap-1 text-sm text-muted-foreground">
Status
<Select
value={status}
@@ -101,7 +101,7 @@ export function AlertsPage({ alerts, transitions, onRefresh }: Props) {
if (value) setStatus(value);
}}
>
<SelectTrigger className="w-full">
<SelectTrigger id="alert-status" className="w-full">
<SelectValue placeholder="Status" />
</SelectTrigger>
<SelectContent>
@@ -114,7 +114,7 @@ export function AlertsPage({ alerts, transitions, onRefresh }: Props) {
</Select>
</label>
<label className="grid gap-1 text-sm text-muted-foreground">
<label htmlFor="alert-kind" className="grid gap-1 text-sm text-muted-foreground">
Kind
<Select
value={kind}
@@ -122,7 +122,7 @@ export function AlertsPage({ alerts, transitions, onRefresh }: Props) {
if (value) setKind(value);
}}
>
<SelectTrigger className="w-full">
<SelectTrigger id="alert-kind" className="w-full">
<SelectValue placeholder="Kind" />
</SelectTrigger>
<SelectContent>
@@ -170,9 +170,10 @@ export function AlertsPage({ alerts, transitions, onRefresh }: Props) {
</span>
</CardHeader>
<CardContent className="space-y-2">
<label className="grid gap-1 text-sm text-muted-foreground">
<label htmlFor="alert-transition-search" className="grid gap-1 text-sm text-muted-foreground">
Search
<Input
id="alert-transition-search"
value={transitionQ}
onChange={(e) => setTransitionQ(e.target.value)}
placeholder="kind, status, message, agent"
+6 -5
View File
@@ -70,7 +70,7 @@ export function AuditPage({ events, onRefresh }: Props) {
<CardContent className="space-y-3">
<div className="grid gap-2 sm:grid-cols-3">
<label className="grid gap-1 text-sm text-muted-foreground">
<label htmlFor="audit-event-type" className="grid gap-1 text-sm text-muted-foreground">
Event type
<Select
value={eventType}
@@ -78,7 +78,7 @@ export function AuditPage({ events, onRefresh }: Props) {
if (value) setEventType(value);
}}
>
<SelectTrigger className="w-full">
<SelectTrigger id="audit-event-type" className="w-full">
<SelectValue placeholder="Event type" />
</SelectTrigger>
<SelectContent>
@@ -91,7 +91,7 @@ export function AuditPage({ events, onRefresh }: Props) {
</Select>
</label>
<label className="grid gap-1 text-sm text-muted-foreground">
<label htmlFor="audit-outcome" className="grid gap-1 text-sm text-muted-foreground">
Outcome
<Select
value={outcome}
@@ -99,7 +99,7 @@ export function AuditPage({ events, onRefresh }: Props) {
if (value) setOutcome(value);
}}
>
<SelectTrigger className="w-full">
<SelectTrigger id="audit-outcome" className="w-full">
<SelectValue placeholder="Outcome" />
</SelectTrigger>
<SelectContent>
@@ -112,9 +112,10 @@ export function AuditPage({ events, onRefresh }: Props) {
</Select>
</label>
<label className="grid gap-1 text-sm text-muted-foreground">
<label htmlFor="audit-search" className="grid gap-1 text-sm text-muted-foreground">
Search
<Input
id="audit-search"
value={needle}
onChange={(e) => setNeedle(e.target.value)}
placeholder="message, agent, actor"
+6 -5
View File
@@ -66,12 +66,12 @@ export function CommandsPage({
Command Runner
</CardTitle>
<CardDescription>
Debug tool send raw commands to agents and inspect JSON responses
Debug tool: send raw commands to agents and inspect JSON responses
</CardDescription>
</CardHeader>
<CardContent>
<form className="grid gap-3" onSubmit={submit}>
<label className="grid gap-1 text-sm text-muted-foreground">
<label htmlFor="cmd-agent" className="grid gap-1 text-sm text-muted-foreground">
<span>Agent</span>
<AgentSelector
agents={agents}
@@ -80,13 +80,13 @@ export function CommandsPage({
/>
</label>
<label className="grid gap-1 text-sm text-muted-foreground">
<label htmlFor="cmd-kind" className="grid gap-1 text-sm text-muted-foreground">
<span>Command</span>
<Select
value={kind}
onValueChange={(value) => setKind(value as CommandKind)}
>
<SelectTrigger className="w-full">
<SelectTrigger id="cmd-kind" className="w-full">
<SelectValue placeholder="Pick command" />
</SelectTrigger>
<SelectContent>
@@ -98,9 +98,10 @@ export function CommandsPage({
</Select>
</label>
<label className="grid gap-1 text-sm text-muted-foreground">
<label htmlFor="cmd-query" className="grid gap-1 text-sm text-muted-foreground">
<span>Query</span>
<Input
id="cmd-query"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="optional"
+5 -5
View File
@@ -115,7 +115,7 @@ export function DashboardPage({
<section className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
{/* Fleet Status */}
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardHeader className="flex flex-row items-center justify-between gap-y-0 pb-2">
<CardDescription className="flex items-center gap-1.5">
<Bot className="size-3.5" />
Fleet Status
@@ -152,7 +152,7 @@ export function DashboardPage({
{/* Active Alerts */}
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardHeader className="flex flex-row items-center justify-between gap-y-0 pb-2">
<CardDescription className="flex items-center gap-1.5">
<Bell className="size-3.5" />
Active Alerts
@@ -188,7 +188,7 @@ export function DashboardPage({
{/* Transitions */}
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardHeader className="flex flex-row items-center justify-between gap-y-0 pb-2">
<CardDescription className="flex items-center gap-1.5">
<Activity className="size-3.5" />
Transitions
@@ -206,7 +206,7 @@ export function DashboardPage({
{/* Token Health */}
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardHeader className="flex flex-row items-center justify-between gap-y-0 pb-2">
<CardDescription className="flex items-center gap-1.5">
<Key className="size-3.5" />
Enroll Tokens
@@ -228,7 +228,7 @@ export function DashboardPage({
{/* Recent Activity Feed */}
<Card className="sm:col-span-2 lg:col-span-4">
<CardHeader className="flex flex-row items-start justify-between gap-3 space-y-0">
<CardHeader className="flex flex-row items-start justify-between gap-3 gap-y-0">
<div>
<CardTitle>Recent Activity</CardTitle>
<CardDescription className="mt-1">
+11 -6
View File
@@ -51,9 +51,10 @@ type Props = {
selectedAgentId: string;
onSelectAgent: (agentId: string) => void;
onAfterWake?: () => Promise<void>;
onRefresh?: () => Promise<void>;
};
export function DevicesPage({ agents, onAfterWake }: Props) {
export function DevicesPage({ agents, onAfterWake, onRefresh }: Props) {
const [devices, setDevices] = useState<FleetDevice[]>([]);
const [knownDevices, setKnownDevices] = useState<KnownDevice[]>([]);
const [query, setQuery] = useState("");
@@ -137,8 +138,7 @@ export function DevicesPage({ agents, onAfterWake }: Props) {
} catch {
const area = document.createElement("textarea");
area.value = text;
area.style.position = "fixed";
area.style.opacity = "0";
area.style.cssText = "position:fixed;opacity:0";
document.body.appendChild(area);
area.select();
document.execCommand("copy");
@@ -201,7 +201,10 @@ export function DevicesPage({ agents, onAfterWake }: Props) {
<Button
variant="outline"
size="sm"
onClick={() => void loadFleet()}
onClick={() => {
void loadFleet();
if (onRefresh) void onRefresh();
}}
disabled={loading}
>
<RefreshCw className="size-4" aria-hidden />
@@ -222,11 +225,12 @@ export function DevicesPage({ agents, onAfterWake }: Props) {
</CardHeader>
<CardContent className="space-y-3">
<div className="grid gap-2 lg:grid-cols-[minmax(16rem,1fr)_11rem_10rem_14rem_10rem]">
<label className="grid gap-1 text-sm text-muted-foreground">
<label htmlFor="fleet-search" className="grid gap-1 text-sm text-muted-foreground">
<span>Search</span>
<div className="relative">
<Search className="absolute left-2 top-2.5 size-4 text-muted-foreground" />
<Input
id="fleet-search"
className="pl-8"
value={query}
onChange={(event) => setQuery(event.target.value)}
@@ -246,7 +250,7 @@ export function DevicesPage({ agents, onAfterWake }: Props) {
values={knownFilters}
onChange={(value) => setKnown(value as KnownFilter)}
/>
<label className="grid gap-1 text-sm text-muted-foreground">
<label htmlFor="fleet-agent" className="grid gap-1 text-sm text-muted-foreground">
<span>Agent</span>
<Select
value={agentId}
@@ -334,6 +338,7 @@ export function DevicesPage({ agents, onAfterWake }: Props) {
</Card>
<FleetDeviceDetailsDialog
key={details?.device_key ?? "__none"}
device={details}
knownDevices={knownDevices}
open={Boolean(details)}
+3 -2
View File
@@ -118,7 +118,7 @@ export function TokensPage() {
return (
<section className="grid gap-4 xl:grid-cols-2">
<Card>
<CardHeader className="flex flex-row items-start justify-between gap-3 space-y-0">
<CardHeader className="flex flex-row items-start justify-between gap-3 gap-y-0">
<div>
<CardTitle className="flex items-center gap-2">
<Key className="size-5 text-primary" />
@@ -219,9 +219,10 @@ export function TokensPage() {
</Button>
</div>
{useCustom && (
<label className="mt-2 grid gap-1 text-sm text-muted-foreground">
<label htmlFor="token-custom-ttl" className="mt-2 grid gap-1 text-sm text-muted-foreground">
<span>TTL (seconds)</span>
<Input
id="token-custom-ttl"
type="number"
min={1}
value={customTtl}
+7 -7
View File
@@ -101,7 +101,7 @@ export function WakeToolsPage({
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<label className="grid gap-1.5 text-sm text-muted-foreground">
<label htmlFor="wake-agent" className="grid gap-1.5 text-sm text-muted-foreground">
<span>Agent</span>
<AgentSelector
agents={agents}
@@ -111,14 +111,14 @@ export function WakeToolsPage({
</label>
<form onSubmit={handleWake} className="grid gap-3">
<label className="grid gap-1.5 text-sm text-muted-foreground">
<label htmlFor="wake-target" className="grid gap-1.5 text-sm text-muted-foreground">
<span>Target</span>
<Input
id="wake-target"
value={target}
onChange={(e) => setTarget(e.target.value)}
placeholder="bedroom-pc, 192.168.1.100, or aa:bb:cc:dd:ee:ff"
disabled={busy}
autoFocus
/>
</label>
<Button
@@ -134,7 +134,7 @@ export function WakeToolsPage({
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between gap-2 space-y-0">
<CardHeader className="flex flex-row items-center justify-between gap-2 gap-y-0">
<CardTitle>Recent Wakes</CardTitle>
<Button
variant="outline"
@@ -147,10 +147,10 @@ export function WakeToolsPage({
<CardContent>
{recentWakes.length > 0 ? (
<div className="grid gap-0">
{recentWakes.map((event, idx) => (
{recentWakes.map((event, i) => (
<div
className={`flex items-start justify-between gap-3 py-2.5 ${idx < recentWakes.length - 1 ? "border-b border-border/50" : ""}`}
key={`${event.ts}-${idx}`}
className={`flex items-start justify-between gap-3 py-2.5 ${i < recentWakes.length - 1 ? "border-b border-border/50" : ""}`}
key={`${event.ts}-${event.target}-${event.agentId}`}
>
<div className="min-w-0">
<div className="flex items-center gap-2">
@@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from "react";
import { useMemo, useState } from "react";
import { Minus, Plus, Zap } from "lucide-react";
import { toast } from "sonner";
@@ -52,24 +52,14 @@ export function FleetDeviceDetailsDialog({
onCopy,
onChanged,
}: Props) {
const [displayName, setDisplayName] = useState("");
const [displayName, setDisplayName] = useState(device?.display_name ?? "");
const [targetDeviceId, setTargetDeviceId] = useState("");
const [routeId, setRouteId] = useState("");
const [routeId, setRouteId] = useState(device?.recommended_route?.route_id ?? "");
const [addKind, setAddKind] = useState<"mac" | "ip">("mac");
const [addValue, setAddValue] = useState("");
const [error, setError] = useState("");
const [actionBusy, setActionBusy] = useState(false);
useEffect(() => {
setDisplayName(device?.display_name ?? "");
setTargetDeviceId("");
setRouteId(device?.recommended_route?.route_id ?? "");
setAddKind("mac");
setAddValue("");
setError("");
setActionBusy(false);
}, [device]);
const rowIdentifiers = device ? identifiersFor(device) : [];
// Resolve the full KnownDevice object if this fleet row is known
@@ -279,7 +269,7 @@ export function FleetDeviceDetailsDialog({
<span className="font-medium">{fullKnown.display_name}</span>
{fullKnown.notes && (
<span className="text-muted-foreground">
{fullKnown.notes}
: {fullKnown.notes}
</span>
)}
</div>
+2 -1
View File
@@ -135,7 +135,7 @@
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
@apply bg-background text-foreground transition-colors duration-100 ease-in-out;
margin: 0;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@@ -189,6 +189,7 @@
background-size: 100% 100%;
overflow-y: auto;
max-width: 100%;
transition: background 100ms ease-in-out;
}
/* ── Sidebar internals ────────────────────────────────────────────────── */