Update AppImage

This commit is contained in:
MacRimi
2025-11-30 19:15:07 +01:00
parent f60bfe8c54
commit 9639dd422a
3 changed files with 143 additions and 18 deletions

View File

@@ -0,0 +1,40 @@
import { NextResponse } from "next/server"
import { executeScript } from "@/lib/script-executor"
export async function POST() {
try {
// Execute the NVIDIA installer script
const result = await executeScript("/usr/local/share/proxmenux/scripts/gpu_tpu/nvidia_installer.sh", {
env: {
EXECUTION_MODE: "web",
WEB_LOG: "/tmp/nvidia_web_install.log",
},
})
if (result.exitCode === 0) {
return NextResponse.json({
success: true,
message: "NVIDIA drivers installed successfully",
output: result.stdout,
})
} else {
return NextResponse.json(
{
success: false,
error: "Installation failed",
output: result.stderr || result.stdout,
},
{ status: 500 },
)
}
} catch (error) {
console.error("NVIDIA installation error:", error)
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : "Unknown error",
},
{ status: 500 },
)
}
}

View File

@@ -5,19 +5,21 @@ import { Badge } from "@/components/ui/badge"
import { Progress } from "@/components/ui/progress"
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import {
Thermometer,
CpuIcon,
Zap,
HardDrive,
Network,
FanIcon,
PowerIcon,
Battery,
Cpu,
MemoryStick,
Cpu as Gpu,
HardDrive,
Thermometer,
Zap,
Loader2,
CpuIcon,
Cpu as Gpu,
Network,
MemoryStick,
PowerIcon,
FanIcon,
Battery,
} from "lucide-react"
import { Download } from "lucide-react"
import { Button } from "@/components/ui/button"
import useSWR from "swr"
import { useState, useEffect } from "react"
import {
@@ -236,6 +238,7 @@ export default function Hardware() {
const [selectedDisk, setSelectedDisk] = useState<StorageDevice | null>(null)
const [selectedNetwork, setSelectedNetwork] = useState<PCIDevice | null>(null)
const [selectedUPS, setSelectedUPS] = useState<any>(null)
const [installingNvidiaDriver, setInstallingNvidiaDriver] = useState(false)
const fetcher = async (url: string) => {
const data = await fetchApi(url)
@@ -246,7 +249,7 @@ export default function Hardware() {
data: hardwareDataSWR,
error: swrError,
isLoading: swrLoading,
mutate,
mutate: mutateHardware,
} = useSWR<HardwareData>("/api/hardware", fetcher, {
refreshInterval: 30000,
revalidateOnFocus: false,
@@ -311,6 +314,34 @@ export default function Hardware() {
return pciDevice || null
}
const handleInstallNvidiaDriver = async () => {
setInstallingNvidiaDriver(true)
try {
const response = await fetch("/api/gpu/nvidia/install", {
method: "POST",
})
if (!response.ok) {
throw new Error("Failed to start NVIDIA driver installation")
}
const data = await response.json()
// Show success message (you might want to add a toast notification here)
alert("NVIDIA driver installation started. Please check the terminal for progress.")
// Refresh GPU data after installation
setTimeout(() => {
mutateHardware()
}, 2000)
} catch (error) {
console.error("Error installing NVIDIA driver:", error)
alert("Failed to start NVIDIA driver installation. Please try manually.")
} finally {
setInstallingNvidiaDriver(false)
}
}
const hasRealtimeData = (): boolean => {
if (!realtimeGPUData) return false
@@ -1090,11 +1121,31 @@ export default function Hardware() {
/>
</svg>
</div>
<div>
<div className="flex-1">
<h4 className="text-sm font-semibold text-blue-500 mb-1">Extended Monitoring Not Available</h4>
<p className="text-sm text-muted-foreground">
<p className="text-sm text-muted-foreground mb-3">
{getMonitoringToolRecommendation(selectedGPU.vendor)}
</p>
{selectedGPU.vendor.toLowerCase().includes("nvidia") && (
<Button
onClick={handleInstallNvidiaDriver}
disabled={installingNvidiaDriver}
size="sm"
className="bg-blue-500 hover:bg-blue-600 text-white"
>
{installingNvidiaDriver ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Installing...
</>
) : (
<>
<Download className="mr-2 h-4 w-4" />
Install NVIDIA Drivers
</>
)}
</Button>
)}
</div>
</div>
</div>
@@ -1105,7 +1156,6 @@ export default function Hardware() {
</DialogContent>
</Dialog>
{/* Power Consumption */}
{hardwareDataSWR?.power_meter && (
<Card className="border-border/50 bg-card/50 p-6">
@@ -1476,8 +1526,6 @@ export default function Hardware() {
</DialogContent>
</Dialog>
{/* PCI Devices - Changed to modal */}
{hardwareDataSWR?.pci_devices && hardwareDataSWR.pci_devices.length > 0 && (
<Card className="border-border/50 bg-card/50 p-6">
@@ -1564,8 +1612,6 @@ export default function Hardware() {
</DialogContent>
</Dialog>
{/* Network Summary - Clickable */}
{hardwareDataSWR?.pci_devices &&
hardwareDataSWR.pci_devices.filter((d) => d.type.toLowerCase().includes("network")).length > 0 && (

View File

@@ -0,0 +1,39 @@
import { exec } from "child_process"
import { promisify } from "util"
const execAsync = promisify(exec)
interface ScriptExecutorOptions {
env?: Record<string, string>
timeout?: number
}
interface ScriptResult {
stdout: string
stderr: string
exitCode: number
}
export async function executeScript(scriptPath: string, options: ScriptExecutorOptions = {}): Promise<ScriptResult> {
const { env = {}, timeout = 300000 } = options // 5 minutes default timeout
try {
const { stdout, stderr } = await execAsync(`bash ${scriptPath}`, {
env: { ...process.env, ...env },
timeout,
maxBuffer: 1024 * 1024 * 10, // 10MB buffer
})
return {
stdout,
stderr,
exitCode: 0,
}
} catch (error: any) {
return {
stdout: error.stdout || "",
stderr: error.stderr || error.message || "Unknown error",
exitCode: error.code || 1,
}
}
}