mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2025-10-10 20:06:18 +00:00
Update AppImage
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
|
@@ -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"
|
||||
|
@@ -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}")
|
||||
|
Reference in New Issue
Block a user