feat: simplify Discover view into socket cards & backend upgrades
This commit is contained in:
+44
-48
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user