Update AppImage

This commit is contained in:
MacRimi
2025-10-06 11:02:00 +02:00
parent 7e8c69a02d
commit 84eec4655a
3 changed files with 185 additions and 65 deletions

View File

@@ -3,8 +3,10 @@
import { Card } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Progress } from "@/components/ui/progress"
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Cpu, MemoryStick, HardDrive, Network, Thermometer, Fan, Battery, Server, CpuIcon } from "lucide-react"
import useSWR from "swr"
import { useState } from "react"
const fetcher = (url: string) => fetch(url).then((res) => res.json())
@@ -91,6 +93,12 @@ interface PCIDevice {
vendor: string
device: string
class: string
driver?: string
kernel_module?: string
irq?: string
memory_address?: string
link_speed?: string
capabilities?: string[]
}
interface HardwareData {
@@ -113,6 +121,8 @@ export default function Hardware() {
refreshInterval: 5000,
})
const [selectedPCIDevice, setSelectedPCIDevice] = useState<PCIDevice | null>(null)
if (error) {
return (
<div className="p-6">
@@ -430,7 +440,8 @@ export default function Hardware() {
{hardwareData.pci_devices.map((device, index) => (
<div
key={index}
className="flex items-start justify-between rounded-lg border border-border/30 bg-background/50 p-4"
onClick={() => setSelectedPCIDevice(device)}
className="flex cursor-pointer items-start justify-between rounded-lg border border-border/30 bg-background/50 p-4 transition-colors hover:border-primary/50 hover:bg-background/80"
>
<div className="flex-1 space-y-1">
<div className="flex items-center gap-2">
@@ -550,6 +561,95 @@ export default function Hardware() {
</div>
</Card>
)}
{/* PCI Device Details Modal */}
<Dialog open={!!selectedPCIDevice} onOpenChange={() => setSelectedPCIDevice(null)}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>PCI Device Details</DialogTitle>
<DialogDescription>Detailed information about the selected PCI device</DialogDescription>
</DialogHeader>
{selectedPCIDevice && (
<div className="space-y-4">
<div className="grid gap-3">
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Device Type</span>
<Badge variant="outline">{selectedPCIDevice.type}</Badge>
</div>
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">PCI Slot</span>
<span className="font-mono text-sm">{selectedPCIDevice.slot}</span>
</div>
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Device Name</span>
<span className="text-sm text-right">{selectedPCIDevice.device}</span>
</div>
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Vendor</span>
<span className="text-sm">{selectedPCIDevice.vendor}</span>
</div>
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Class</span>
<span className="font-mono text-sm">{selectedPCIDevice.class}</span>
</div>
{selectedPCIDevice.driver && (
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Driver</span>
<span className="font-mono text-sm">{selectedPCIDevice.driver}</span>
</div>
)}
{selectedPCIDevice.kernel_module && (
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Kernel Module</span>
<span className="font-mono text-sm">{selectedPCIDevice.kernel_module}</span>
</div>
)}
{selectedPCIDevice.irq && (
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">IRQ</span>
<span className="font-mono text-sm">{selectedPCIDevice.irq}</span>
</div>
)}
{selectedPCIDevice.memory_address && (
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Memory Address</span>
<span className="font-mono text-sm">{selectedPCIDevice.memory_address}</span>
</div>
)}
{selectedPCIDevice.link_speed && (
<div className="flex justify-between border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Link Speed</span>
<span className="font-mono text-sm">{selectedPCIDevice.link_speed}</span>
</div>
)}
{selectedPCIDevice.capabilities && selectedPCIDevice.capabilities.length > 0 && (
<div className="space-y-2 border-b border-border/50 pb-2">
<span className="text-sm font-medium text-muted-foreground">Capabilities</span>
<div className="flex flex-wrap gap-2">
{selectedPCIDevice.capabilities.map((cap, idx) => (
<Badge key={idx} variant="secondary" className="text-xs">
{cap}
</Badge>
))}
</div>
</div>
)}
</div>
</div>
)}
</DialogContent>
</Dialog>
</div>
)
}

View File

@@ -14,7 +14,7 @@ APPIMAGE_ROOT="$SCRIPT_DIR/.."
VERSION=$(node -p "require('$APPIMAGE_ROOT/package.json').version")
APPIMAGE_NAME="ProxMenux-${VERSION}.AppImage"
echo "🚀 Building ProxMenux Monitor AppImage v${VERSION} with translation support..."
echo "🚀 Building ProxMenux Monitor AppImage v${VERSION} with hardware monitoring tools..."
# Clean and create work directory
rm -rf "$WORK_DIR"
@@ -317,6 +317,46 @@ def parse_header(value: str) -> Tuple[str, Dict[str, str]]:
return key, params
PYEOF
echo "🔧 Installing hardware monitoring tools..."
mkdir -p "$WORK_DIR/debs"
cd "$WORK_DIR/debs"
# Download .deb packages
echo "📥 Downloading ipmitool..."
wget -q http://deb.debian.org/debian/pool/main/i/ipmitool/ipmitool_1.8.19-4_amd64.deb -O ipmitool.deb || true
echo "📥 Downloading lm-sensors..."
wget -q http://deb.debian.org/debian/pool/main/l/lm-sensors/lm-sensors_3.6.0-7.1_amd64.deb -O lm-sensors.deb || true
echo "📥 Downloading nut-client..."
wget -q http://deb.debian.org/debian/pool/main/n/nut/nut-client_2.8.0-7_amd64.deb -O nut-client.deb || true
wget -q http://deb.debian.org/debian/pool/main/n/nut/libupsclient6_2.8.0-7_amd64.deb -O libupsclient6.deb || true
# Extract binaries from .deb packages
echo "📦 Extracting binaries..."
for deb in *.deb; do
if [ -f "$deb" ]; then
dpkg-deb -x "$deb" "$WORK_DIR/extracted"
fi
done
# Copy binaries to AppDir
if [ -d "$WORK_DIR/extracted/usr/bin" ]; then
echo "📋 Copying monitoring tools to AppDir..."
cp -r "$WORK_DIR/extracted/usr/bin"/* "$APP_DIR/usr/bin/" 2>/dev/null || true
fi
if [ -d "$WORK_DIR/extracted/usr/sbin" ]; then
cp -r "$WORK_DIR/extracted/usr/sbin"/* "$APP_DIR/usr/bin/" 2>/dev/null || true
fi
if [ -d "$WORK_DIR/extracted/usr/lib" ]; then
mkdir -p "$APP_DIR/usr/lib"
cp -r "$WORK_DIR/extracted/usr/lib"/* "$APP_DIR/usr/lib/" 2>/dev/null || true
fi
echo "✅ Hardware monitoring tools installed"
# Build AppImage
echo "🔨 Building unified AppImage v${VERSION}..."
cd "$WORK_DIR"

View File

@@ -69,40 +69,39 @@ def get_vm_lxc_names():
def serve_dashboard():
"""Serve the main dashboard page from Next.js build"""
try:
# Detectar si estamos ejecutándose desde AppImage
appimage_root = os.environ.get('APPDIR')
if not appimage_root:
# Fallback: detectar desde la ubicación del script
# Si el script está en usr/bin/, necesitamos subir 2 niveles para llegar a la raíz
# Fallback: detect from script location
base_dir = os.path.dirname(os.path.abspath(__file__))
# Verificar si estamos en usr/bin/
if base_dir.endswith('usr/bin'):
# Subir 2 niveles: usr/bin/ -> usr/ -> AppImage root
# We're in usr/bin/, go up 2 levels to AppImage root
appimage_root = os.path.dirname(os.path.dirname(base_dir))
else:
# Fallback genérico: subir 1 nivel
# Fallback: assume we're in the root
appimage_root = os.path.dirname(base_dir)
print(f"[v0] Detected AppImage root: {appimage_root}")
index_paths = [
os.path.join(appimage_root, 'web', 'index.html'), # Ruta principal para AppImage
os.path.join(appimage_root, 'usr', 'web', 'index.html'), # Fallback con usr/
os.path.join(appimage_root, 'web', 'out', 'index.html'), # Fallback si está en subcarpeta
os.path.join(appimage_root, 'usr', 'web', 'out', 'index.html'), # Fallback con usr/out/
]
index_path = os.path.join(appimage_root, 'web', 'index.html')
abs_path = os.path.abspath(index_path)
print(f"[v0] Flask server looking for index.html in:")
for path in index_paths:
abs_path = os.path.abspath(path)
exists = os.path.exists(abs_path)
print(f"[v0] {abs_path} - {'EXISTS' if exists else 'NOT FOUND'}")
if exists:
print(f"[v0] Found index.html, serving from: {abs_path}")
return send_file(abs_path)
print(f"[v0] Looking for index.html at: {abs_path}")
if os.path.exists(abs_path):
print(f"[v0] ✅ Found index.html, serving from: {abs_path}")
return send_file(abs_path)
# If not found, show detailed error
print(f"[v0] ❌ index.html NOT found at: {abs_path}")
print(f"[v0] Checking web directory contents:")
web_dir = os.path.join(appimage_root, 'web')
if os.path.exists(web_dir):
print(f"[v0] Contents of {web_dir}:")
for item in os.listdir(web_dir):
print(f"[v0] - {item}")
else:
print(f"[v0] Web directory does not exist: {web_dir}")
# If no Next.js build found, return error message with actual paths checked
actual_paths = [os.path.abspath(path) for path in index_paths]
return f'''
<!DOCTYPE html>
<html>
@@ -110,8 +109,8 @@ def serve_dashboard():
<body style="font-family: Arial; padding: 2rem; background: #0a0a0a; color: #fff;">
<h1>🚨 ProxMenux Monitor - Build Error</h1>
<p>Next.js application not found. The AppImage may not have been built correctly.</p>
<p>Expected paths checked:</p>
<ul>{''.join([f'<li>{path}</li>' for path in actual_paths])}</ul>
<p>Expected path: {abs_path}</p>
<p>APPDIR: {appimage_root}</p>
<p>API endpoints are still available:</p>
<ul>
<li><a href="/api/system" style="color: #4f46e5;">/api/system</a></li>
@@ -203,17 +202,13 @@ def serve_next_static(filename):
else:
appimage_root = os.path.dirname(base_dir)
static_paths = [
os.path.join(appimage_root, 'web', '_next'), # Ruta principal
os.path.join(appimage_root, 'usr', 'web', '_next'), # Fallback con usr/
os.path.join(appimage_root, 'web', 'out', '_next'), # Fallback con out/
os.path.join(appimage_root, 'usr', 'web', 'out', '_next'), # Fallback con usr/out/
]
static_dir = os.path.join(appimage_root, 'web', '_next')
file_path = os.path.join(static_dir, filename)
for static_dir in static_paths:
file_path = os.path.join(static_dir, filename)
if os.path.exists(file_path):
return send_file(file_path)
if os.path.exists(file_path):
return send_file(file_path)
print(f"[v0] ❌ Next.js static file not found: {file_path}")
return '', 404
except Exception as e:
print(f"Error serving Next.js static file {filename}: {e}")
@@ -231,18 +226,12 @@ def serve_static_files(filename):
else:
appimage_root = os.path.dirname(base_dir)
public_paths = [
os.path.join(appimage_root, 'web'), # Raíz web para exportación estática
os.path.join(appimage_root, 'usr', 'web'), # Fallback con usr/
os.path.join(appimage_root, 'web', 'out'), # Fallback con out/
os.path.join(appimage_root, 'usr', 'web', 'out'), # Fallback con usr/out/
]
web_dir = os.path.join(appimage_root, 'web')
file_path = os.path.join(web_dir, filename)
if os.path.exists(file_path):
return send_from_directory(web_dir, filename)
for public_dir in public_paths:
file_path = os.path.join(public_dir, filename)
if os.path.exists(file_path):
return send_from_directory(public_dir, filename)
return '', 404
except Exception as e:
print(f"Error serving static file {filename}: {e}")
@@ -260,26 +249,17 @@ def serve_images(filename):
else:
appimage_root = os.path.dirname(base_dir)
image_paths = [
os.path.join(appimage_root, 'web', 'images'), # Ruta principal para exportación estática
os.path.join(appimage_root, 'usr', 'web', 'images'), # Fallback con usr/
os.path.join(appimage_root, 'web', 'public', 'images'), # Ruta con public/
os.path.join(appimage_root, 'usr', 'web', 'public', 'images'), # Fallback usr/public/
os.path.join(appimage_root, 'public', 'images'), # Ruta directa a public
os.path.join(appimage_root, 'usr', 'public', 'images'), # Fallback usr/public
]
image_dir = os.path.join(appimage_root, 'web', 'images')
file_path = os.path.join(image_dir, filename)
abs_path = os.path.abspath(file_path)
print(f"[v0] Looking for image: {filename}")
for image_dir in image_paths:
file_path = os.path.join(image_dir, filename)
abs_path = os.path.abspath(file_path)
exists = os.path.exists(abs_path)
print(f"[v0] Checking: {abs_path} - {'FOUND' if exists else 'NOT FOUND'}")
if exists:
print(f"[v0] Serving image from: {abs_path}")
return send_from_directory(image_dir, filename)
print(f"[v0] Looking for image: {filename} at {abs_path}")
print(f"[v0] Image not found: {filename}")
if os.path.exists(abs_path):
print(f"[v0] ✅ Serving image from: {abs_path}")
return send_from_directory(image_dir, filename)
print(f"[v0] ❌ Image not found: {abs_path}")
return '', 404
except Exception as e:
print(f"Error serving image {filename}: {e}")