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 (
+
+
+
+ {/* Status text */}
+
+ {isLxcActive ? "LXC" : isVmActive ? "VM" : "N/A"}
+
+
+ {hasChanged && (
+
+ (pending)
+
+ )}
+
+ )
+ }
+
+ return (
+
+
+
+
+ {/* 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) {