feat(2.0): Phase 2 — System/OS + Connect

System: services/system.py (psutil CPU/RAM/Disk, sysfs GPU/Temp guarded),
routers/system.py GET status + restart (Whitelist) + self-update (systemd-
USER, sudo-frei). Connect: services/connect.py (Cline/OpenCode/Zed/Continue/
Claude Code/Memory-MCP → Gateway model:auto, LAN-IP-Override), routers/
connect.py. Frontend: SystemView (Metrik-Bars) + ConnectView (Tool-Tabs,
Copy, IP-Override).

Lokal verifiziert: Backend-Smoke + Frontend-Build + Browser (System-Bars,
Connect-Snippets). Docs aktualisiert (README/STATUS/Plan).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-06-25 08:08:43 +02:00
parent cff3f0b1a8
commit 6c8b6d81fe
17 changed files with 617 additions and 150 deletions
+97
View File
@@ -0,0 +1,97 @@
import { useEffect, useState } from "react"
import { Check, Copy } from "lucide-react"
import { api, type ConnectResp } from "@/lib/api"
import { cn } from "@/lib/utils"
export function ConnectView() {
const [host, setHost] = useState(localStorage.getItem("mc_host") || "192.168.178.151")
const [data, setData] = useState<ConnectResp | null>(null)
const [active, setActive] = useState("cline")
const [copied, setCopied] = useState(false)
const [error, setError] = useState("")
useEffect(() => {
api<ConnectResp>(`/api/connect?host=${encodeURIComponent(host)}`)
.then(setData)
.catch((e) => setError(String(e)))
}, [host])
function saveHost(v: string) {
setHost(v)
if (v) localStorage.setItem("mc_host", v)
}
const tool = data?.tools[active]
async function copy() {
if (!tool) return
await navigator.clipboard.writeText(tool.snippet)
setCopied(true)
setTimeout(() => setCopied(false), 1500)
}
return (
<div className="space-y-4">
<div>
<h1 className="text-xl font-semibold">Verbinden</h1>
<p className="text-sm text-muted-foreground">
Fertige Snippets für deine Tools auf dem lokalen PC alle zeigen auf den Gateway der Box
(<code>model: auto</code>) + das geteilte Gedächtnis.
</p>
</div>
<div className="flex items-center gap-2 text-sm">
<label className="text-muted-foreground">Box-LAN-IP:</label>
<input
value={host}
onChange={(e) => saveHost(e.target.value)}
className="rounded-md border border-border bg-card px-2 py-1 font-mono text-xs outline-none focus:ring-2 focus:ring-ring"
/>
{data && <span className="text-xs text-muted-foreground"> {data.gateway_url}</span>}
</div>
{error && (
<div className="rounded-md border border-border bg-card p-3 text-sm text-muted-foreground">
Snippets nicht ladbar ({error}).
</div>
)}
{data && (
<>
<div className="flex flex-wrap gap-1">
{Object.entries(data.tools).map(([key, t]) => (
<button
key={key}
onClick={() => setActive(key)}
className={cn(
"rounded-md px-3 py-1.5 text-sm transition-colors",
active === key ? "bg-primary/15 text-primary" : "text-muted-foreground hover:text-foreground",
)}
>
{t.label}
</button>
))}
</div>
{tool && (
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-xs text-muted-foreground">{tool.note}</span>
<button
onClick={copy}
className="flex items-center gap-1.5 rounded-md border border-border px-2 py-1 text-xs hover:bg-accent"
>
{copied ? <Check className="h-3.5 w-3.5 text-emerald-500" /> : <Copy className="h-3.5 w-3.5" />}
{copied ? "Kopiert" : "Kopieren"}
</button>
</div>
<pre className="overflow-x-auto rounded-xl border border-border bg-card p-4 text-xs leading-relaxed">
<code>{tool.snippet}</code>
</pre>
</div>
)}
</>
)}
</div>
)
}