From 2805c46a22d8436b48878cac7ec648449889d4ef Mon Sep 17 00:00:00 2001 From: MacRimi Date: Thu, 9 Apr 2026 15:02:25 +0200 Subject: [PATCH] update gpu-switch-mode-indicator.tsx --- .../components/gpu-switch-mode-indicator.tsx | 498 ++++++++++++++++++ AppImage/components/hardware.tsx | 143 +++++ AppImage/components/script-terminal-modal.tsx | 15 +- 3 files changed, 649 insertions(+), 7 deletions(-) create mode 100644 AppImage/components/gpu-switch-mode-indicator.tsx diff --git a/AppImage/components/gpu-switch-mode-indicator.tsx b/AppImage/components/gpu-switch-mode-indicator.tsx new file mode 100644 index 00000000..738727d0 --- /dev/null +++ b/AppImage/components/gpu-switch-mode-indicator.tsx @@ -0,0 +1,498 @@ +"use client" + +import { cn } from "@/lib/utils" + +interface GpuSwitchModeIndicatorProps { + mode: "lxc" | "vm" | "unknown" + isEditing?: boolean + pendingMode?: "lxc" | "vm" | null + onToggle?: (e: React.MouseEvent) => void + className?: string + compact?: boolean +} + +export function GpuSwitchModeIndicator({ + mode, + isEditing = false, + pendingMode = null, + onToggle, + className, + compact = false, +}: GpuSwitchModeIndicatorProps) { + const displayMode = pendingMode ?? mode + const isLxcActive = displayMode === "lxc" + const isVmActive = displayMode === "vm" + const hasChanged = pendingMode !== null && pendingMode !== mode + + const handleClick = (e: React.MouseEvent) => { + e.stopPropagation() // Prevent card click propagation + if (isEditing && onToggle) { + onToggle(e) + } + } + + if (compact) { + return ( +
+ + {/* GPU Chip Icon */} + + + {/* Chip pins */} + + + + + + + + GPU + + + + {/* Connection lines from GPU */} + + {/* Main line from GPU */} + + + {/* Switch circle */} + + + {/* LXC branch */} + + + {/* VM branch */} + + + + {/* LXC Icon - Container */} + + + + + + LXC + + + + {/* VM Icon - Monitor/PC */} + + + + + + + + {/* Status text */} + + {isLxcActive ? "LXC" : isVmActive ? "VM" : "N/A"} + + + {hasChanged && ( + + (pending) + + )} +
+ ) + } + + return ( +
+
+ + {/* GPU Chip Icon */} + + + {/* Chip pins top */} + + + + {/* Chip pins bottom */} + + + + + GPU + + + + {/* Connection lines */} + + {/* Main line from GPU to switch */} + + + {/* Switch circle */} + + + {/* Switch indicator inside circle */} + + + {/* LXC branch - top */} + + + {/* Active glow for LXC */} + {isLxcActive && ( + + )} + + {/* VM branch - bottom */} + + + {/* Active glow for VM */} + {isVmActive && ( + + )} + + + {/* LXC Icon - Container with layers */} + + + {/* Container layers */} + + + {/* Small dots on layers */} + + + + + + {/* LXC label */} + + LXC + + + {/* VM Icon - Monitor/Desktop */} + + + {/* Screen content */} + + {/* Stand */} + + + + + {/* VM label */} + + VM + + + + {/* Status text and edit hint */} +
+ + {isLxcActive ? "LXC Mode" : isVmActive ? "VM Mode" : "Unknown"} + + + {isLxcActive ? "Native Driver" : isVmActive ? "VFIO Passthrough" : ""} + + {isEditing && ( + + Click to toggle + + )} + {hasChanged && ( + + Change pending + + )} +
+
+
+ ) +} diff --git a/AppImage/components/hardware.tsx b/AppImage/components/hardware.tsx index 9b08e04d..d70a7cf8 100644 --- a/AppImage/components/hardware.tsx +++ b/AppImage/components/hardware.tsx @@ -18,6 +18,8 @@ import { } from "../types/hardware" import { fetchApi } from "@/lib/api-config" import { ScriptTerminalModal } from "./script-terminal-modal" +import { GpuSwitchModeIndicator } from "./gpu-switch-mode-indicator" +import { Pencil, Check, X } from "lucide-react" const parseLsblkSize = (sizeStr: string | undefined): number => { if (!sizeStr) return 0 @@ -230,6 +232,11 @@ export default function Hardware() { const [installingNvidiaDriver, setInstallingNvidiaDriver] = useState(false) const [showAmdInstaller, setShowAmdInstaller] = useState(false) const [showIntelInstaller, setShowIntelInstaller] = useState(false) + + // GPU Switch Mode states + const [editingSwitchModeGpu, setEditingSwitchModeGpu] = useState(null) // GPU slot being edited + const [pendingSwitchModes, setPendingSwitchModes] = useState>({}) + const [showSwitchModeModal, setShowSwitchModeModal] = useState(false) const fetcher = async (url: string) => { const data = await fetchApi(url) @@ -246,6 +253,74 @@ export default function Hardware() { revalidateOnFocus: false, }) + // Determine GPU mode based on driver (vfio-pci = VM, native driver = LXC) + const getGpuSwitchMode = (gpu: GPU): "lxc" | "vm" | "unknown" => { + const driver = gpu.pci_driver?.toLowerCase() || "" + if (driver === "vfio-pci") return "vm" + if (driver === "nvidia" || driver === "amdgpu" || driver === "radeon" || driver === "i915" || driver === "xe" || driver === "nouveau") return "lxc" + if (driver && driver !== "none") return "lxc" // Any other driver = native = LXC mode + return "unknown" + } + + const handleSwitchModeEdit = (gpuSlot: string, e: React.MouseEvent) => { + e.stopPropagation() // Prevent opening GPU modal + setEditingSwitchModeGpu(gpuSlot) + } + + const handleSwitchModeToggle = (gpu: GPU, e?: React.MouseEvent) => { + const slot = gpu.slot + const currentMode = getGpuSwitchMode(gpu) + const pendingMode = pendingSwitchModes[slot] + + // Toggle between modes + if (pendingMode) { + // Already has pending - toggle it + const newMode = pendingMode === "lxc" ? "vm" : "lxc" + if (newMode === currentMode) { + // Back to original - remove pending + const newPending = { ...pendingSwitchModes } + delete newPending[slot] + setPendingSwitchModes(newPending) + } else { + setPendingSwitchModes({ ...pendingSwitchModes, [slot]: newMode }) + } + } else { + // No pending - set opposite of current + const newMode = currentMode === "lxc" ? "vm" : "lxc" + setPendingSwitchModes({ ...pendingSwitchModes, [slot]: newMode }) + } + } + + const handleSwitchModeSave = (gpuSlot: string, e: React.MouseEvent) => { + e.stopPropagation() + const pendingMode = pendingSwitchModes[gpuSlot] + const gpu = hardwareDataSWR?.gpus?.find(g => g.slot === gpuSlot) + const currentMode = gpu ? getGpuSwitchMode(gpu) : "unknown" + + if (pendingMode && pendingMode !== currentMode) { + // Mode has changed - launch the script + setShowSwitchModeModal(true) + } + setEditingSwitchModeGpu(null) + } + + const handleSwitchModeCancel = (gpuSlot: string, e: React.MouseEvent) => { + e.stopPropagation() + // Remove pending change for this GPU + const newPending = { ...pendingSwitchModes } + delete newPending[gpuSlot] + setPendingSwitchModes(newPending) + setEditingSwitchModeGpu(null) + } + + const handleSwitchModeModalClose = () => { + setShowSwitchModeModal(false) + // Clear all pending changes after script runs + setPendingSwitchModes({}) + // Refresh hardware data + mutateHardware() + } + const handleInstallNvidiaDriver = () => { console.log("[v0] Opening NVIDIA installer terminal") setShowNvidiaInstaller(true) @@ -793,6 +868,61 @@ export default function Hardware() { )} + +{/* GPU Switch Mode Indicator */} + {getGpuSwitchMode(gpu) !== "unknown" && ( +
e.stopPropagation()} + > +
+ + Switch Mode + +
+ {editingSwitchModeGpu === fullSlot ? ( + <> + + + + ) : ( + + )} +
+
+ handleSwitchModeToggle(gpu, e)} + compact + /> +
+ )} ) })} @@ -2138,6 +2268,19 @@ title="AMD GPU Tools Installation" title="Intel GPU Tools Installation" description="Installing intel-gpu-tools for Intel GPU monitoring..." /> + + {/* GPU Switch Mode Modal */} + ) } diff --git a/AppImage/components/script-terminal-modal.tsx b/AppImage/components/script-terminal-modal.tsx index 11d891e0..296e3475 100644 --- a/AppImage/components/script-terminal-modal.tsx +++ b/AppImage/components/script-terminal-modal.tsx @@ -43,6 +43,8 @@ interface ScriptTerminalModalProps { scriptPath: string title: string description: string + scriptName?: string + params?: Record } export function ScriptTerminalModal({ @@ -51,6 +53,7 @@ export function ScriptTerminalModal({ scriptPath, title, description, + params = { EXECUTION_MODE: "web" }, }: ScriptTerminalModalProps) { const termRef = useRef(null) const wsRef = useRef(null) @@ -113,13 +116,11 @@ export function ScriptTerminalModal({ } }, 30000) - const initMessage = { - script_path: scriptPath, - params: { - EXECUTION_MODE: "web", - }, - } - ws.send(JSON.stringify(initMessage)) +const initMessage = { + script_path: scriptPath, + params, + } + ws.send(JSON.stringify(initMessage)) setTimeout(() => { if (fitAddonRef.current && termRef.current && ws.readyState === WebSocket.OPEN) {