import { useCallback, useEffect, useState } from "preact/hooks"; import { createClient } from "@connectrpc/connect"; import { createConnectTransport } from "@connectrpc/connect-web"; import { BookOpen, Gauge, Link as LinkIcon, Play, Server } from "lucide-preact"; import type { Item, Link, Service } from "./gen/homelab/v1/homelab_pb"; import { HomelabService } from "./gen/homelab/v1/homelab_pb"; import { MonitorCard } from "./components/MonitorCard"; import { ScriptCard } from "./components/ScriptCard"; import { ServiceCard } from "./components/ServiceCard"; import { DocsView, DocView, localDocs } from "./components/Docs"; import { syncDocs } from "./docsStore"; const client = createClient( HomelabService, createConnectTransport({ baseUrl: "" }), ); const CATEGORIES = [ { id: "links", label: "Links", icon: LinkIcon }, { id: "monitor", label: "Monitor", icon: Gauge }, { id: "services", label: "Services", icon: Server }, { id: "exec", label: "Exec", icon: Play }, { id: "docs", label: "Docs", icon: BookOpen }, ] as const; type Category = (typeof CATEGORIES)[number]["id"]; function LinkList({ links }: { links: Link[] }) { if (links.length === 0) return
No links in config
; return (
{links.map((l) => (
{l.name} {l.url}
))}
); } export function App() { const [content, setContent] = useState<{ links: Link[]; monitors: Item[]; exec: Item[]; services: Service[]; }>({ links: [], monitors: [], exec: [], services: [], }); const [category, setCategory] = useState("monitor"); const [docs, setDocs] = useState(localDocs()); const [openDoc, setOpenDoc] = useState(null); const refresh = useCallback( () => client .listContent({}) .then((r) => setContent({ links: r.links, monitors: r.monitors, exec: r.exec, services: r.services, }), ) .catch(() => {}), // offline: keep last state [], ); useEffect(() => { refresh(); const id = setInterval(refresh, 3000); return () => clearInterval(id); }, [refresh]); // Docs sync + offline dirty flush, every 5s. useEffect(() => { const tick = () => syncDocs() .then(() => setDocs(localDocs())) .catch(() => {}); tick(); const id = setInterval(tick, 5000); return () => clearInterval(id); }, []); const open = docs.find((d) => d.name === openDoc); return (
{/* Desktop only: tab row top-left */}
{category === "links" && } {category === "monitor" && (
{content.monitors.map((it) => ( ))}
)} {category === "exec" && content.exec.map((it) => )} {category === "services" && (
{content.services.map((s) => ( ))}
)} {category === "docs" && (open ? ( setOpenDoc(null)} /> ) : ( setOpenDoc(d.name)} /> ))}
{/* Mobile footer nav: daisyUI dock (v5 btm-nav removed), equal quarters */}
{CATEGORIES.map((c) => ( ))}
); }