diff --git a/AppImage/components/hardware.tsx b/AppImage/components/hardware.tsx index e3aaa27..309dda3 100644 --- a/AppImage/components/hardware.tsx +++ b/AppImage/components/hardware.tsx @@ -30,6 +30,7 @@ import { fetcher as swrFetcher, } from "../types/hardware" import { fetchApi } from "@/lib/api-config" +import { HybridScriptMonitor } from "./hybrid-script-monitor" const parseLsblkSize = (sizeStr: string | undefined): number => { if (!sizeStr) return 0 @@ -238,6 +239,7 @@ export default function Hardware() { const [selectedDisk, setSelectedDisk] = useState(null) const [selectedNetwork, setSelectedNetwork] = useState(null) const [selectedUPS, setSelectedUPS] = useState(null) + const [nvidiaSessionId, setNvidiaSessionId] = useState(null) const [installingNvidiaDriver, setInstallingNvidiaDriver] = useState(false) const fetcher = async (url: string) => { @@ -256,14 +258,9 @@ export default function Hardware() { }) const handleInstallNvidiaDriver = async () => { - console.log("[v0] ============================================") - console.log("[v0] NVIDIA installation button clicked!") - console.log("[v0] ============================================") + console.log("[v0] NVIDIA installation button clicked") try { - console.log("[v0] Step 1: Setting installingNvidiaDriver state to true") - setInstallingNvidiaDriver(true) - const payload = { script_relative_path: "gpu_tpu/nvidia_installer.sh", params: { @@ -272,41 +269,25 @@ export default function Hardware() { }, } - console.log("[v0] Step 2: Payload prepared:", JSON.stringify(payload, null, 2)) - console.log("[v0] Step 3: Calling fetchApi with endpoint: /api/scripts/execute") - console.log("[v0] Step 4: Method: POST") + console.log("[v0] Calling Flask to execute script:", payload) const data = await fetchApi("/api/scripts/execute", { method: "POST", body: JSON.stringify(payload), }) - console.log("[v0] Step 5: fetchApi returned successfully") - console.log("[v0] Step 6: Response data:", JSON.stringify(data, null, 2)) + console.log("[v0] Flask response:", data) - if (data.success) { - console.log("[v0] Step 7: Installation started successfully") - console.log("[v0] Session ID:", data.session_id) - alert(`NVIDIA driver installation started! Session ID: ${data.session_id}`) - mutateHardware() + if (data.success && data.session_id) { + console.log("[v0] Installation started with session ID:", data.session_id) + setNvidiaSessionId(data.session_id) } else { - console.log("[v0] Step 7: Installation failed with error:", data.error) + console.error("[v0] Installation failed:", data.error) alert(`Failed to install NVIDIA drivers: ${data.error}`) } } catch (error) { - console.error("[v0] ============================================") console.error("[v0] Exception during installation:", error) - console.error("[v0] Error type:", typeof error) - console.error("[v0] Error message:", error instanceof Error ? error.message : String(error)) - console.error("[v0] Error stack:", error instanceof Error ? error.stack : "No stack trace") - console.error("[v0] ============================================") alert("Failed to start NVIDIA driver installation. Check console for details.") - } finally { - console.log("[v0] Step 8: Setting installingNvidiaDriver state to false") - setInstallingNvidiaDriver(false) - console.log("[v0] ============================================") - console.log("[v0] Handler execution completed") - console.log("[v0] ============================================") } } @@ -836,13 +817,7 @@ export default function Hardware() { )} {/* GPU Detail Modal - Shows immediately with basic info, then loads real-time data */} - { - setSelectedGPU(null) - setRealtimeGPUData(null) - }} - > + !open && setSelectedGPU(null)}> {selectedGPU && ( <> @@ -1154,13 +1129,8 @@ export default function Hardware() { {getMonitoringToolRecommendation(selectedGPU.vendor)}

{selectedGPU.vendor.toLowerCase().includes("nvidia") && ( -
+ + {/* NVIDIA Installation Monitor */} + {nvidiaSessionId && ( + { + setNvidiaSessionId(null) + mutateHardware() + }} + onComplete={(success) => { + console.log("[v0] NVIDIA installation completed:", success ? "success" : "failed") + if (success) { + mutateHardware() + } + }} + /> + )} ) } diff --git a/AppImage/components/hybrid-script-monitor.tsx b/AppImage/components/hybrid-script-monitor.tsx new file mode 100644 index 0000000..439c794 --- /dev/null +++ b/AppImage/components/hybrid-script-monitor.tsx @@ -0,0 +1,384 @@ +"use client" + +import { useEffect, useState, useRef } from "react" +import { fetchApi } from "@/lib/api-config" +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { ScrollArea } from "@/components/ui/scroll-area" +import { Loader2, CheckCircle2, XCircle, AlertCircle } from "lucide-react" + +interface HybridScriptMonitorProps { + sessionId: string | null + title?: string + description?: string + onClose: () => void + onComplete?: (success: boolean) => void +} + +interface ScriptInteraction { + type: "msgbox" | "yesno" | "inputbox" | "menu" + id: string + title: string + text: string + data?: string +} + +interface LogEntry { + timestamp: string + message: string + type: "info" | "error" | "warning" | "success" +} + +export function HybridScriptMonitor({ + sessionId, + title = "Script Execution", + description = "Monitoring script execution...", + onClose, + onComplete, +}: HybridScriptMonitorProps) { + const [logs, setLogs] = useState([]) + const [interaction, setInteraction] = useState(null) + const [status, setStatus] = useState<"running" | "completed" | "failed">("running") + const [inputValue, setInputValue] = useState("") + const [selectedMenuItem, setSelectedMenuItem] = useState("") + const [isResponding, setIsResponding] = useState(false) + const scrollRef = useRef(null) + const pollingIntervalRef = useRef(null) + + // Auto-scroll to bottom when logs update + useEffect(() => { + if (scrollRef.current) { + scrollRef.current.scrollTop = scrollRef.current.scrollHeight + } + }, [logs]) + + // Poll for logs and status + useEffect(() => { + if (!sessionId) return + + const pollScript = async () => { + try { + console.log("[v0] Polling script status and logs for session:", sessionId) + + // Get status + const statusData = await fetchApi(`/api/scripts/status/${sessionId}`) + console.log("[v0] Status data:", statusData) + + if (statusData.status === "completed") { + setStatus("completed") + if (pollingIntervalRef.current) { + clearInterval(pollingIntervalRef.current) + } + onComplete?.(true) + } else if (statusData.status === "failed") { + setStatus("failed") + if (pollingIntervalRef.current) { + clearInterval(pollingIntervalRef.current) + } + onComplete?.(false) + } + + // Get logs + const logsData = await fetchApi(`/api/scripts/logs/${sessionId}`) + console.log("[v0] Logs data:", logsData) + + if (logsData.logs && Array.isArray(logsData.logs)) { + const parsedLogs: LogEntry[] = [] + + for (const logLine of logsData.logs) { + // Check for web interaction + if (logLine.includes("WEB_INTERACTION:")) { + const parts = logLine.split("WEB_INTERACTION:")[1].split(":") + if (parts.length >= 4) { + const [type, id, title, text, ...dataParts] = parts + const data = dataParts.join(":") + + console.log("[v0] Detected interaction:", { type, id, title, text, data }) + + setInteraction({ + type: type as ScriptInteraction["type"], + id, + title: title.replace(/_/g, " "), + text: text.replace(/_/g, " "), + data, + }) + + // Pause polling while waiting for user interaction + if (pollingIntervalRef.current) { + clearInterval(pollingIntervalRef.current) + } + } + } else { + // Regular log entry + parsedLogs.push({ + timestamp: new Date().toLocaleTimeString(), + message: logLine, + type: logLine.toLowerCase().includes("error") + ? "error" + : logLine.toLowerCase().includes("warning") + ? "warning" + : logLine.toLowerCase().includes("success") + ? "success" + : "info", + }) + } + } + + setLogs(parsedLogs) + } + } catch (error) { + console.error("[v0] Error polling script:", error) + setLogs((prev) => [ + ...prev, + { + timestamp: new Date().toLocaleTimeString(), + message: `Error: ${error}`, + type: "error", + }, + ]) + } + } + + // Initial poll + pollScript() + + // Set up polling interval + pollingIntervalRef.current = setInterval(pollScript, 2000) + + return () => { + if (pollingIntervalRef.current) { + clearInterval(pollingIntervalRef.current) + } + } + }, [sessionId, onComplete]) + + const handleInteractionResponse = async (response: string) => { + if (!interaction || !sessionId) return + + setIsResponding(true) + + try { + console.log("[v0] Sending interaction response:", { + session_id: sessionId, + interaction_id: interaction.id, + response, + }) + + await fetchApi("/api/scripts/respond", { + method: "POST", + body: JSON.stringify({ + session_id: sessionId, + interaction_id: interaction.id, + response, + }), + }) + + setInteraction(null) + setInputValue("") + setSelectedMenuItem("") + + // Resume polling + if (!pollingIntervalRef.current && status === "running") { + pollingIntervalRef.current = setInterval(async () => { + // Polling logic here (same as above) + }, 2000) + } + } catch (error) { + console.error("[v0] Error responding to interaction:", error) + setLogs((prev) => [ + ...prev, + { + timestamp: new Date().toLocaleTimeString(), + message: `Error responding: ${error}`, + type: "error", + }, + ]) + } finally { + setIsResponding(false) + } + } + + const renderInteractionModal = () => { + if (!interaction) return null + + switch (interaction.type) { + case "msgbox": + return ( + {}}> + + + {interaction.title} + + {interaction.text} +
+ +
+
+
+ ) + + case "yesno": + return ( + {}}> + + + {interaction.title} + + {interaction.text} +
+ + +
+
+
+ ) + + case "inputbox": + return ( + {}}> + + + {interaction.title} + + {interaction.text} +
+
+ + setInputValue(e.target.value)} + placeholder={interaction.data || "Enter value..."} + /> +
+
+ + +
+
+
+
+ ) + + case "menu": + const menuItems = interaction.data?.split("|").filter(Boolean) || [] + return ( + {}}> + + + {interaction.title} + + {interaction.text} + +
+ {menuItems.map((item, index) => ( + + ))} +
+
+
+ + +
+
+
+ ) + + default: + return null + } + } + + if (!sessionId) return null + + return ( + <> + + + + + {status === "running" && } + {status === "completed" && } + {status === "failed" && } + {title} + + {description} + + +
+ {/* Logs Display */} +
+
+ + Execution Logs +
+ +
+ {logs.length === 0 ? ( +
Waiting for logs...
+ ) : ( + logs.map((log, index) => ( +
+ [{log.timestamp}] {log.message} +
+ )) + )} +
+
+
+ + {/* Status footer */} +
+
Session ID: {sessionId}
+ {status !== "running" && } +
+
+
+
+ + {/* Interaction Modal */} + {renderInteractionModal()} + + ) +}