feat: simplify Discover view into socket cards & backend upgrades

This commit is contained in:
Hitonabi
2026-06-26 07:53:41 +02:00
parent b90cef3968
commit bc3abb127a
21 changed files with 2019 additions and 715 deletions
+44 -48
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react"
import { Command as CommandIcon, Moon, Sun, ChevronLeft, ChevronRight, Cpu } from "lucide-react"
import { Command as CommandIcon, ChevronLeft, ChevronRight } from "lucide-react"
import { NAV, type ViewId } from "@/nav"
import { CommandPalette } from "@/components/CommandPalette"
import { DashboardView } from "@/views/DashboardView"
@@ -11,16 +11,16 @@ import { AgentView } from "@/views/AgentView"
import { GuideView } from "@/views/GuideView"
import { Placeholder } from "@/views/Placeholder"
import { SystemDrawer } from "@/components/SystemDrawer"
import { api, type Health, type UpdatesResp } from "@/lib/api"
import { api, type Health } from "@/lib/api"
import { cn } from "@/lib/utils"
export default function App() {
const [view, setView] = useState<ViewId>("dashboard")
const [health, setHealth] = useState<Health | null>(null)
const [dark, setDark] = useState(() => localStorage.getItem("mc_theme") !== "light")
const [sidebarCollapsed, setSidebarCollapsed] = useState(() => localStorage.getItem("mc_sidebar_collapsed") === "true")
const [updates, setUpdates] = useState<UpdatesResp | null>(null)
const [sysStatus, setSysStatus] = useState<any | null>(null)
const [drawerOpen, setDrawerOpen] = useState(false)
const [drawerTab, setDrawerTab] = useState<"maintenance" | "logs">("maintenance")
useEffect(() => {
const load = () => api<Health>("/api/health").then(setHealth).catch(() => setHealth(null))
@@ -30,25 +30,27 @@ export default function App() {
}, [])
useEffect(() => {
const loadUpdates = () => api<UpdatesResp>("/api/maintenance/updates").then(setUpdates).catch(() => {})
loadUpdates()
const t = setInterval(loadUpdates, 20000)
const loadSys = () => api<any>("/api/system/status").then(setSysStatus).catch(() => {})
loadSys()
const t = setInterval(loadSys, 20000)
return () => clearInterval(t)
}, [])
useEffect(() => {
document.documentElement.classList.toggle("dark", dark)
localStorage.setItem("mc_theme", dark ? "dark" : "light")
}, [dark])
document.documentElement.classList.add("dark")
}, [])
useEffect(() => {
const handleOpen = () => setDrawerOpen(true)
const handleOpen = (e: Event) => {
const customEvent = e as CustomEvent
setDrawerTab(customEvent.detail?.tab || "maintenance")
setDrawerOpen(true)
}
window.addEventListener("open-system-drawer", handleOpen)
return () => window.removeEventListener("open-system-drawer", handleOpen)
}, [])
const active = NAV.find((n) => n.id === view)!
const totalUpdates = updates ? (updates.os + updates.engine + updates.models) : 0
return (
<div className="flex h-full relative">
@@ -61,7 +63,7 @@ export default function App() {
</div>
<CommandPalette onNavigate={setView} />
<SystemDrawer open={drawerOpen} onClose={() => setDrawerOpen(false)} />
<SystemDrawer open={drawerOpen} onClose={() => setDrawerOpen(false)} defaultTab={drawerTab} />
{/* Sidebar */}
<aside className={cn(
@@ -122,16 +124,35 @@ export default function App() {
)} title={health ? `Engine ${health.engine_reachable ? "online" : "offline"}` : "Backend offline"} />
</div>
) : (
health ? (
<span className="flex items-center gap-2">
<span className={cn("h-2 w-2 rounded-full", health.engine_reachable ? "bg-emerald-500" : "bg-amber-500")} />
<span className="truncate">Engine {health.engine_reachable ? "online" : "offline"} · v{health.version}</span>
</span>
) : (
<span className="flex items-center gap-2">
<span className="h-2 w-2 rounded-full bg-red-500" /> <span className="truncate">Backend offline</span>
</span>
)
<div className="space-y-2 text-left">
{health ? (
<span className="flex items-center gap-2">
<span className={cn("h-2 w-2 rounded-full animate-pulse", health.engine_reachable ? "bg-emerald-500" : "bg-amber-500")} />
<span className="truncate">Engine {health.engine_reachable ? "online" : "offline"}</span>
</span>
) : (
<span className="flex items-center gap-2">
<span className="h-2 w-2 rounded-full bg-red-500" /> <span className="truncate">Backend offline</span>
</span>
)}
{sysStatus?.versions && (
<div className="space-y-1 text-[9px] text-muted-foreground/60 mt-2 pt-2 border-t border-border/30">
<div className="truncate" title={sysStatus.versions.mc2 ? `${sysStatus.versions.mc2.branch}-${sysStatus.versions.mc2.hash}${sysStatus.versions.mc2.dirty ? "*" : ""} (${sysStatus.versions.mc2.date})` : "nicht gefunden"}>
<strong>MC2:</strong> {sysStatus.versions.mc2 ? `${sysStatus.versions.mc2.hash}${sysStatus.versions.mc2.dirty ? "*" : ""}` : "—"}
</div>
<div className="truncate" title={sysStatus.versions.engine?.type === "git" ? `${sysStatus.versions.engine.branch}-${sysStatus.versions.engine.hash}${sysStatus.versions.engine.dirty ? "*" : ""} (${sysStatus.versions.engine.date})` : sysStatus.versions.engine?.version_text || "unbekannt"}>
<strong>Engine:</strong> {sysStatus.versions.engine?.type === "git" ? `${sysStatus.versions.engine.hash}${sysStatus.versions.engine.dirty ? "*" : ""}` : (sysStatus.versions.engine?.version_text?.split(" ").pop() || "—")}
</div>
<div className="truncate" title={sysStatus.versions.hermes_ui ? `${sysStatus.versions.hermes_ui.branch}-${sysStatus.versions.hermes_ui.hash}${sysStatus.versions.hermes_ui.dirty ? "*" : ""} (${sysStatus.versions.hermes_ui.date})` : "nicht gefunden"}>
<strong>Hermes UI:</strong> {sysStatus.versions.hermes_ui ? `${sysStatus.versions.hermes_ui.hash}${sysStatus.versions.hermes_ui.dirty ? "*" : ""}` : "—"}
</div>
<div className="truncate" title={sysStatus.versions.hermes_agent ? `${sysStatus.versions.hermes_agent.branch}-${sysStatus.versions.hermes_agent.hash}${sysStatus.versions.hermes_agent.dirty ? "*" : ""} (${sysStatus.versions.hermes_agent.date})` : "nicht gefunden"}>
<strong>Hermes Agent:</strong> {sysStatus.versions.hermes_agent ? `${sysStatus.versions.hermes_agent.hash}${sysStatus.versions.hermes_agent.dirty ? "*" : ""}` : "—"}
</div>
</div>
)}
</div>
)}
</div>
</aside>
@@ -141,21 +162,6 @@ export default function App() {
<header className="flex h-14 shrink-0 items-center justify-between border-b border-border/40 px-6 bg-card/20 backdrop-blur-sm">
<div className="text-xs font-medium text-muted-foreground tracking-wide uppercase font-sans">{active.hint}</div>
<div className="flex items-center gap-2">
<button
onClick={() => setDrawerOpen(true)}
className="relative flex h-8 items-center rounded-md border border-border/40 bg-background/40 px-3 text-xs text-muted-foreground hover:bg-accent hover:text-accent-foreground gap-1.5 transition-all cursor-pointer font-semibold"
title="System-Pflege öffnen"
>
<Cpu className="h-3.5 w-3.5" />
<span>OS & Updates</span>
{totalUpdates > 0 && (
<span className="flex h-1.5 w-1.5 relative">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-amber-400 opacity-75"></span>
<span className="relative inline-flex rounded-full h-1.5 w-1.5 bg-amber-500"></span>
</span>
)}
</button>
<a
href="https://git.tobisniceshomelab.ddnsfree.com/Hitonabi/mission-control-v2/src/branch/main/docs/BEDIENUNG.md"
target="_blank"
@@ -166,14 +172,6 @@ export default function App() {
Hilfe
</a>
<button
onClick={() => setDark((d) => !d)}
className="flex h-8 w-8 items-center justify-center rounded-md border border-border/40 bg-background/40 text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-all cursor-pointer"
title="Hell/Dunkel"
>
{dark ? <Sun className="h-3.5 w-3.5" /> : <Moon className="h-3.5 w-3.5" />}
</button>
<button
onClick={() => {
const ev = new KeyboardEvent("keydown", { key: "k", metaKey: true })
@@ -201,8 +199,6 @@ export default function App() {
)}
</main>
</div>
</div>
)
}