Checkpoint: GoClaw Control Center v0.1 — полнофункциональный веб-интерфейс для мониторинга и управления GoClaw Swarm Cloud. Включает: Dashboard с метриками кластера, панель управления агентами, мониторинг Swarm-нод, терминальный чат с оркестратором, настройки API-ключей и провайдеров моделей, конфигурацию коннекторов (Telegram). Дизайн: Mission Control (космический центр управления), тёмная тема с неоновыми индикаторами.

This commit is contained in:
Manus
2026-03-20 15:38:12 -04:00
parent 351be6cad6
commit ac674815f2
12 changed files with 2017 additions and 130 deletions

View 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>
);
}