import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { apiPath } from "@/lib/api"; import { Activity, Box, Cpu, GitMerge, HardDrive, LayoutDashboard, Network, RefreshCw, } from "lucide-react"; import { useCallback, useEffect, useRef, useState } from "react"; type HostInfo = { cpu_percent: number; memory_total_gb: number; memory_available_gb: number; memory_percent: number; disk_path: string; disk_total_gb: number; disk_used_gb: number; disk_percent: number; network_bytes_sent_mb: number; network_bytes_recv_mb: number; boot_time: string; os_name: string; os_platform: string; window_count: number | null; }; type SystemInfoResponse = { status?: string; info?: HostInfo; timestamp?: string; }; export function Dashboard() { const [bridge, setBridge] = useState<"loading" | "ok" | "error">("loading"); const [bridgeDetail, setBridgeDetail] = useState(""); const [host, setHost] = useState(null); const [hostFetchedAt, setHostFetchedAt] = useState(""); const [hostError, setHostError] = useState(""); const [restarting, setRestarting] = useState(false); const retryRef = useRef(0); const retryTimerRef = useRef>(undefined); const restartBackend = useCallback(async () => { setRestarting(true); try { const { invoke } = await import("@tauri-apps/api/core"); await invoke("start_backend"); } catch { // not in Tauri -- HTTP poll will update } }, []); const refresh = useCallback(() => { fetch(apiPath("/api/v1/health")) .then(async (r) => { const j = await r.json().catch(() => ({})); if (r.ok && (j as { status?: string }).status === "ok") { setBridge("ok"); setBridgeDetail("REST bridge reachable"); retryRef.current = 0; } else { setBridge("error"); setBridgeDetail(`HTTP ${r.status}`); scheduleRetry(); } }) .catch(() => { setBridge("error"); setBridgeDetail("Backend not ready"); scheduleRetry(); }); fetch(apiPath("/api/v1/system/info")) .then(async (r) => { const j = (await r.json()) as SystemInfoResponse; if (!r.ok || j.status !== "success" || !j.info) { setHost(null); setHostError(`system/info HTTP ${r.status}`); return; } setHostError(""); setHost(j.info); setHostFetchedAt(j.timestamp ?? new Date().toISOString()); }) .catch(() => { setHost(null); setHostError("Backend not ready"); }); }, []); const scheduleRetry = useCallback(() => { if (retryRef.current >= 5) return; const delay = Math.min(1000 * Math.pow(2, retryRef.current), 30000); retryRef.current++; if (retryTimerRef.current) clearTimeout(retryTimerRef.current); retryTimerRef.current = setTimeout(refresh, delay); }, [refresh]); useEffect(() => { refresh(); const isTauri = typeof window !== "undefined" && "__TAURI_INTERNALS__" in window; let unlistenTauri: () => void; if (isTauri) { import("@tauri-apps/api/event").then(({ listen }) => { listen("backend-status", () => { retryRef.current = 0; refresh(); }).then((off) => { unlistenTauri = off; }); }); } const id = window.setInterval(refresh, 45000); return () => { window.clearInterval(id); if (retryTimerRef.current) clearTimeout(retryTimerRef.current); if (unlistenTauri) unlistenTauri(); }; }, [refresh]); return (

Automation Dashboard

Live host metrics from{" "} GET /api/v1/system/info{" "} (same data as MCP{" "} automation_system("info") ). Backend:{" "} {bridge === "loading" && ( checking… )} {bridge === "ok" && ( connected — {bridgeDetail} )} {bridge === "error" && ( <> unreachable — {bridgeDetail} )}

{hostFetchedAt && (

Last host snapshot: {hostFetchedAt}

)} {hostError && (

Host metrics: {hostError}

)}
CPU
{host ? `${host.cpu_percent.toFixed(1)}%` : "—"}

1s sample (psutil)

Memory
{host ? `${host.memory_percent.toFixed(1)}%` : "—"}

{host ? `${host.memory_available_gb} GB free of ${host.memory_total_gb} GB` : "—"}

Disk
{host ? `${host.disk_percent.toFixed(1)}%` : "—"}

{host ? `${host.disk_path} · ${host.disk_used_gb} / ${host.disk_total_gb} GB` : "—"}

Network
{host ? `${host.network_bytes_sent_mb} / ${host.network_bytes_recv_mb}` : "—"}

MB sent / recv (counters since boot)

Top-level windows
{host?.window_count != null ? host.window_count : "—"}

pygetwindow.getAllWindows() length

Boot
{host?.boot_time ?? "—"}

{host ? `${host.os_platform} (${host.os_name})` : "—"}

OCR / Vision
—

Tesseract on host when installed; not a live counter

Face tools
—

Opt-in via PYWINAUTO_MCP_ENABLE_FACE + face extra (Help)

Notes

Host metrics refresh on load and every 45s. The REST API runs in the same process as the MCP server when you start the ASGI app (see{" "} start.ps1).

This page does not stream MCP tool calls or automation logs; use your IDE or logging for that.

); }