Checkpoint: GoClaw Control Center v0.1 — полнофункциональный веб-интерфейс для мониторинга и управления GoClaw Swarm Cloud. Включает: Dashboard с метриками кластера, панель управления агентами, мониторинг Swarm-нод, терминальный чат с оркестратором, настройки API-ключей и провайдеров моделей, конфигурацию коннекторов (Telegram). Дизайн: Mission Control (космический центр управления), тёмная тема с неоновыми индикаторами.
This commit is contained in:
192
client/src/components/DashboardLayout.tsx
Normal file
192
client/src/components/DashboardLayout.tsx
Normal file
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* DashboardLayout — Mission Control style
|
||||
* Design: Dark space theme, narrow icon sidebar, top status bar with cluster metrics
|
||||
* Colors: Space deep (#0A0E1A), Cyan glow (#00D4FF), status indicators
|
||||
* Typography: Inter for nav, JetBrains Mono for data
|
||||
*/
|
||||
import { ReactNode, useState } from "react";
|
||||
import { useLocation, Link } from "wouter";
|
||||
import {
|
||||
LayoutDashboard,
|
||||
Bot,
|
||||
Server,
|
||||
MessageSquare,
|
||||
Settings,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Activity,
|
||||
Cpu,
|
||||
HardDrive,
|
||||
Wifi,
|
||||
} from "lucide-react";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
|
||||
const NAV_ITEMS = [
|
||||
{ path: "/", icon: LayoutDashboard, label: "Дашборд" },
|
||||
{ path: "/agents", icon: Bot, label: "Агенты" },
|
||||
{ path: "/nodes", icon: Server, label: "Ноды" },
|
||||
{ path: "/chat", icon: MessageSquare, label: "Чат" },
|
||||
{ path: "/settings", icon: Settings, label: "Настройки" },
|
||||
];
|
||||
|
||||
export default function DashboardLayout({ children }: { children: ReactNode }) {
|
||||
const [location] = useLocation();
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden bg-background">
|
||||
{/* Sidebar */}
|
||||
<motion.aside
|
||||
initial={false}
|
||||
animate={{ width: collapsed ? 64 : 220 }}
|
||||
transition={{ duration: 0.2, ease: "easeInOut" }}
|
||||
className="relative flex flex-col border-r border-border/50 bg-sidebar"
|
||||
>
|
||||
{/* Logo */}
|
||||
<div className="flex items-center gap-3 px-4 h-14 border-b border-border/50">
|
||||
<div className="w-8 h-8 rounded-md bg-primary/20 flex items-center justify-center glow-cyan shrink-0">
|
||||
<Cpu className="w-4 h-4 text-primary" />
|
||||
</div>
|
||||
<AnimatePresence>
|
||||
{!collapsed && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, width: 0 }}
|
||||
animate={{ opacity: 1, width: "auto" }}
|
||||
exit={{ opacity: 0, width: 0 }}
|
||||
className="overflow-hidden whitespace-nowrap"
|
||||
>
|
||||
<span className="font-mono font-bold text-sm text-primary tracking-wider">
|
||||
GoClaw
|
||||
</span>
|
||||
<span className="font-mono text-xs text-muted-foreground ml-1">
|
||||
v0.1
|
||||
</span>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
<nav className="flex-1 py-4 px-2 space-y-1">
|
||||
{NAV_ITEMS.map((item) => {
|
||||
const isActive = location === item.path;
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<Tooltip key={item.path} delayDuration={0}>
|
||||
<TooltipTrigger asChild>
|
||||
<Link href={item.path}>
|
||||
<div
|
||||
className={`flex items-center gap-3 px-3 py-2.5 rounded-md transition-all duration-150 group ${
|
||||
isActive
|
||||
? "bg-primary/15 text-primary glow-cyan"
|
||||
: "text-muted-foreground hover:text-foreground hover:bg-accent/50"
|
||||
}`}
|
||||
>
|
||||
<Icon className={`w-5 h-5 shrink-0 ${isActive ? "text-primary" : ""}`} />
|
||||
<AnimatePresence>
|
||||
{!collapsed && (
|
||||
<motion.span
|
||||
initial={{ opacity: 0, width: 0 }}
|
||||
animate={{ opacity: 1, width: "auto" }}
|
||||
exit={{ opacity: 0, width: 0 }}
|
||||
className="text-sm font-medium overflow-hidden whitespace-nowrap"
|
||||
>
|
||||
{item.label}
|
||||
</motion.span>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
{collapsed && (
|
||||
<TooltipContent side="right" className="bg-popover text-popover-foreground">
|
||||
{item.label}
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
{/* Collapse toggle */}
|
||||
<button
|
||||
onClick={() => setCollapsed(!collapsed)}
|
||||
className="absolute -right-3 top-20 w-6 h-6 rounded-full bg-secondary border border-border flex items-center justify-center text-muted-foreground hover:text-foreground hover:bg-accent transition-colors z-10"
|
||||
>
|
||||
{collapsed ? <ChevronRight className="w-3 h-3" /> : <ChevronLeft className="w-3 h-3" />}
|
||||
</button>
|
||||
|
||||
{/* Connection status */}
|
||||
<div className="px-3 py-3 border-t border-border/50">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-neon-green pulse-indicator" />
|
||||
<AnimatePresence>
|
||||
{!collapsed && (
|
||||
<motion.span
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="font-mono text-[11px] text-neon-green"
|
||||
>
|
||||
GATEWAY ONLINE
|
||||
</motion.span>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
</motion.aside>
|
||||
|
||||
{/* Main content area */}
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
{/* Top status bar */}
|
||||
<header className="h-14 border-b border-border/50 bg-sidebar flex items-center justify-between px-6">
|
||||
<div className="flex items-center gap-6">
|
||||
<StatusMetric icon={Activity} label="UPTIME" value="14d 7h 23m" color="text-neon-green" />
|
||||
<StatusMetric icon={Server} label="NODES" value="4 / 4" color="text-primary" />
|
||||
<StatusMetric icon={Bot} label="AGENTS" value="7 active" color="text-primary" />
|
||||
<StatusMetric icon={Cpu} label="CPU" value="34%" color="text-neon-green" />
|
||||
<StatusMetric icon={HardDrive} label="MEM" value="6.2 GB" color="text-neon-amber" />
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-2 px-3 py-1.5 rounded-md bg-secondary/50 border border-border/50">
|
||||
<Wifi className="w-3.5 h-3.5 text-neon-green" />
|
||||
<span className="font-mono text-[11px] text-muted-foreground">
|
||||
goclaw-swarm:18789
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Page content */}
|
||||
<main className="flex-1 overflow-auto p-6">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StatusMetric({
|
||||
icon: Icon,
|
||||
label,
|
||||
value,
|
||||
color,
|
||||
}: {
|
||||
icon: typeof Activity;
|
||||
label: string;
|
||||
value: string;
|
||||
color: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Icon className={`w-3.5 h-3.5 ${color}`} />
|
||||
<div className="flex items-baseline gap-1.5">
|
||||
<span className="font-mono text-[10px] text-muted-foreground tracking-wider uppercase">
|
||||
{label}
|
||||
</span>
|
||||
<span className={`font-mono text-xs font-semibold ${color}`}>{value}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user