mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2025-11-18 11:36:17 +00:00
Update AppImage
This commit is contained in:
@@ -18,6 +18,7 @@ interface SystemData {
|
|||||||
node_id: string
|
node_id: string
|
||||||
timestamp: string
|
timestamp: string
|
||||||
cpu_cores?: number
|
cpu_cores?: number
|
||||||
|
cpu_threads?: number
|
||||||
proxmox_version?: string
|
proxmox_version?: string
|
||||||
kernel_version?: string
|
kernel_version?: string
|
||||||
available_updates?: number
|
available_updates?: number
|
||||||
@@ -271,7 +272,7 @@ export function SystemOverview() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fetchVMs()
|
fetchVMs()
|
||||||
const vmInterval = setInterval(fetchVMs, 15000)
|
const vmInterval = setInterval(fetchVMs, 60000)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(vmInterval)
|
clearInterval(vmInterval)
|
||||||
@@ -288,7 +289,7 @@ export function SystemOverview() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fetchStorage()
|
fetchStorage()
|
||||||
const storageInterval = setInterval(fetchStorage, 30000)
|
const storageInterval = setInterval(fetchStorage, 60000)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(storageInterval)
|
clearInterval(storageInterval)
|
||||||
@@ -388,6 +389,44 @@ export function SystemOverview() {
|
|||||||
(s) => s.name === "local-lvm" || s.name === "local-zfs" || s.name === "local",
|
(s) => s.name === "local-lvm" || s.name === "local-zfs" || s.name === "local",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const getLoadStatus = (load: number, cores: number) => {
|
||||||
|
if (load < cores) {
|
||||||
|
return { status: "Normal", color: "bg-green-500/10 text-green-500 border-green-500/20" }
|
||||||
|
} else if (load < cores * 1.5) {
|
||||||
|
return { status: "Moderate", color: "bg-yellow-500/10 text-yellow-500 border-yellow-500/20" }
|
||||||
|
} else {
|
||||||
|
return { status: "High", color: "bg-red-500/10 text-red-500 border-red-500/20" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const systemAlerts = []
|
||||||
|
if (systemData.available_updates && systemData.available_updates > 0) {
|
||||||
|
systemAlerts.push({
|
||||||
|
type: "warning",
|
||||||
|
message: `${systemData.available_updates} updates available`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (vmStats.stopped > 0) {
|
||||||
|
systemAlerts.push({
|
||||||
|
type: "info",
|
||||||
|
message: `${vmStats.stopped} VM${vmStats.stopped > 1 ? "s" : ""} stopped`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (systemData.temperature > 75) {
|
||||||
|
systemAlerts.push({
|
||||||
|
type: "warning",
|
||||||
|
message: "High temperature detected",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (localStorage && localStorage.percent > 90) {
|
||||||
|
systemAlerts.push({
|
||||||
|
type: "warning",
|
||||||
|
message: "System storage almost full",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadStatus = getLoadStatus(systemData.load_average[0], systemData.cpu_cores || 8)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Key Metrics Cards */}
|
{/* Key Metrics Cards */}
|
||||||
@@ -637,37 +676,56 @@ export function SystemOverview() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* System Health & Alerts */}
|
||||||
<Card className="bg-card border-border">
|
<Card className="bg-card border-border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-foreground flex items-center">
|
<CardTitle className="text-foreground flex items-center">
|
||||||
<Zap className="h-5 w-5 mr-2" />
|
<Zap className="h-5 w-5 mr-2" />
|
||||||
Performance Metrics
|
System Health & Alerts
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-3">
|
<CardContent className="space-y-4">
|
||||||
<div className="flex justify-between items-start">
|
<div className="flex justify-between items-center pb-3 border-b border-border">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="text-muted-foreground">Load Average:</span>
|
<span className="text-sm text-muted-foreground">Load Average (1m):</span>
|
||||||
<span className="text-xs text-muted-foreground">(1m, 5m, 15m)</span>
|
|
||||||
</div>
|
</div>
|
||||||
<span className="text-foreground font-mono">
|
<div className="flex items-center gap-2">
|
||||||
{systemData.load_average.map((avg) => avg.toFixed(2)).join(", ")}
|
<span className="text-lg font-semibold text-foreground font-mono">
|
||||||
|
{systemData.load_average[0].toFixed(2)}
|
||||||
</span>
|
</span>
|
||||||
|
<Badge variant="outline" className={loadStatus.color}>
|
||||||
|
{loadStatus.status}
|
||||||
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
|
||||||
<span className="text-muted-foreground">Total Memory:</span>
|
|
||||||
<span className="text-foreground">{systemData.memory_total} GB</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
|
||||||
<span className="text-muted-foreground">Available Memory:</span>
|
<div className="flex justify-between items-center pb-3 border-b border-border">
|
||||||
<span className="text-foreground">
|
<span className="text-sm text-muted-foreground">CPU Threads:</span>
|
||||||
{(systemData.memory_total - systemData.memory_used).toFixed(1)} GB
|
<span className="text-lg font-semibold text-foreground">{systemData.cpu_threads || "N/A"}</span>
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
|
||||||
<span className="text-muted-foreground">CPU Cores:</span>
|
{systemAlerts.length > 0 && (
|
||||||
<span className="text-foreground">{systemData.cpu_cores || "N/A"}</span>
|
<div className="pt-2">
|
||||||
|
<div className="text-sm text-muted-foreground mb-2">Recent Alerts:</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{systemAlerts.map((alert, index) => (
|
||||||
|
<div key={index} className="flex items-center gap-2">
|
||||||
|
<AlertCircle className="h-4 w-4 text-yellow-500" />
|
||||||
|
<span className="text-sm text-foreground">{alert.message}</span>
|
||||||
</div>
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{systemAlerts.length === 0 && (
|
||||||
|
<div className="pt-2 text-center">
|
||||||
|
<div className="text-sm text-green-500 flex items-center justify-center gap-2">
|
||||||
|
<div className="h-2 w-2 rounded-full bg-green-500"></div>
|
||||||
|
All systems operational
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import shutil # Added for shutil.which
|
|||||||
import xml.etree.ElementTree as ET # Added for XML parsing
|
import xml.etree.ElementTree as ET # Added for XML parsing
|
||||||
import math # Imported math for format_bytes function
|
import math # Imported math for format_bytes function
|
||||||
import urllib.parse # Added for URL encoding
|
import urllib.parse # Added for URL encoding
|
||||||
|
import platform # Added for platform.release()
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
CORS(app) # Enable CORS for Next.js frontend
|
CORS(app) # Enable CORS for Next.js frontend
|
||||||
@@ -38,6 +39,73 @@ def format_bytes(size_in_bytes):
|
|||||||
s = round(size_in_bytes / p, 2)
|
s = round(size_in_bytes / p, 2)
|
||||||
return f"{s} {size_name[i]}"
|
return f"{s} {size_name[i]}"
|
||||||
|
|
||||||
|
# Helper functions for system info
|
||||||
|
def get_cpu_temperature():
|
||||||
|
"""Get CPU temperature using psutil if available, otherwise return 0."""
|
||||||
|
temp = 0
|
||||||
|
try:
|
||||||
|
if hasattr(psutil, "sensors_temperatures"):
|
||||||
|
temps = psutil.sensors_temperatures()
|
||||||
|
if temps:
|
||||||
|
# Priority order for temperature sensors
|
||||||
|
sensor_priority = ['coretemp', 'cpu_thermal', 'acpi', 'thermal_zone']
|
||||||
|
for sensor_name in sensor_priority:
|
||||||
|
if sensor_name in temps and temps[sensor_name]:
|
||||||
|
temp = temps[sensor_name][0].current
|
||||||
|
break
|
||||||
|
|
||||||
|
# If no priority sensor found, use first available
|
||||||
|
if temp == 0:
|
||||||
|
for name, entries in temps.items():
|
||||||
|
if entries:
|
||||||
|
temp = entries[0].current
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: Error reading temperature sensors: {e}")
|
||||||
|
return temp
|
||||||
|
|
||||||
|
def get_uptime():
|
||||||
|
"""Get system uptime in a human-readable format."""
|
||||||
|
try:
|
||||||
|
boot_time = psutil.boot_time()
|
||||||
|
uptime_seconds = time.time() - boot_time
|
||||||
|
return str(timedelta(seconds=int(uptime_seconds)))
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: Error getting uptime: {e}")
|
||||||
|
return "N/A"
|
||||||
|
|
||||||
|
def get_proxmox_version():
|
||||||
|
"""Get Proxmox version if available."""
|
||||||
|
proxmox_version = None
|
||||||
|
try:
|
||||||
|
result = subprocess.run(['pveversion'], capture_output=True, text=True, timeout=5)
|
||||||
|
if result.returncode == 0:
|
||||||
|
# Parse output like "pve-manager/9.0.6/..."
|
||||||
|
version_line = result.stdout.strip().split('\n')[0]
|
||||||
|
if '/' in version_line:
|
||||||
|
proxmox_version = version_line.split('/')[1]
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("Warning: pveversion command not found - Proxmox may not be installed.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: Error getting Proxmox version: {e}")
|
||||||
|
return proxmox_version
|
||||||
|
|
||||||
|
def get_available_updates():
|
||||||
|
"""Get the number of available package updates."""
|
||||||
|
available_updates = 0
|
||||||
|
try:
|
||||||
|
# Use apt list --upgradable to count available updates
|
||||||
|
result = subprocess.run(['apt', 'list', '--upgradable'], capture_output=True, text=True, timeout=10)
|
||||||
|
if result.returncode == 0:
|
||||||
|
# Count lines minus the header line
|
||||||
|
lines = result.stdout.strip().split('\n')
|
||||||
|
available_updates = max(0, len(lines) - 1)
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("Warning: apt command not found - cannot check for updates.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: Error checking for updates: {e}")
|
||||||
|
return available_updates
|
||||||
|
|
||||||
# AGREGANDO FUNCIÓN PARA PARSEAR PROCESOS DE INTEL_GPU_TOP (SIN -J)
|
# AGREGANDO FUNCIÓN PARA PARSEAR PROCESOS DE INTEL_GPU_TOP (SIN -J)
|
||||||
def get_intel_gpu_processes_from_text():
|
def get_intel_gpu_processes_from_text():
|
||||||
"""Parse processes from intel_gpu_top text output (more reliable than JSON)"""
|
"""Parse processes from intel_gpu_top text output (more reliable than JSON)"""
|
||||||
@@ -385,118 +453,8 @@ def serve_images(filename):
|
|||||||
print(f"Error serving image {filename}: {e}")
|
print(f"Error serving image {filename}: {e}")
|
||||||
return '', 404
|
return '', 404
|
||||||
|
|
||||||
def get_system_info():
|
# Moved helper functions for system info up
|
||||||
"""Get basic system information"""
|
# def get_system_info(): ... (moved up)
|
||||||
try:
|
|
||||||
cpu_percent = psutil.cpu_percent(interval=0.5)
|
|
||||||
|
|
||||||
# Memory usage
|
|
||||||
memory = psutil.virtual_memory()
|
|
||||||
|
|
||||||
temp = 0
|
|
||||||
try:
|
|
||||||
if hasattr(psutil, "sensors_temperatures"):
|
|
||||||
temps = psutil.sensors_temperatures()
|
|
||||||
if temps:
|
|
||||||
# Priority order for temperature sensors
|
|
||||||
sensor_priority = ['coretemp', 'cpu_thermal', 'acpi', 'thermal_zone']
|
|
||||||
for sensor_name in sensor_priority:
|
|
||||||
if sensor_name in temps and temps[sensor_name]:
|
|
||||||
temp = temps[sensor_name][0].current
|
|
||||||
break
|
|
||||||
|
|
||||||
# If no priority sensor found, use first available
|
|
||||||
if temp == 0:
|
|
||||||
for name, entries in temps.items():
|
|
||||||
if entries:
|
|
||||||
temp = entries[0].current
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error reading temperature sensors: {e}")
|
|
||||||
temp = 0 # Use 0 to indicate no temperature available
|
|
||||||
|
|
||||||
# Uptime
|
|
||||||
boot_time = psutil.boot_time()
|
|
||||||
uptime_seconds = time.time() - boot_time
|
|
||||||
uptime_str = str(timedelta(seconds=int(uptime_seconds)))
|
|
||||||
|
|
||||||
# Load average
|
|
||||||
load_avg = os.getloadavg() if hasattr(os, 'getloadavg') else [0, 0, 0]
|
|
||||||
|
|
||||||
hostname = socket.gethostname()
|
|
||||||
node_id = f"pve-{hostname}"
|
|
||||||
|
|
||||||
proxmox_version = None
|
|
||||||
try:
|
|
||||||
result = subprocess.run(['pveversion'], capture_output=True, text=True, timeout=5)
|
|
||||||
if result.returncode == 0:
|
|
||||||
# Parse output like "pve-manager/9.0.6/..."
|
|
||||||
version_line = result.stdout.strip().split('\n')[0]
|
|
||||||
if '/' in version_line:
|
|
||||||
proxmox_version = version_line.split('/')[1]
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Note: pveversion not available: {e}")
|
|
||||||
|
|
||||||
kernel_version = None
|
|
||||||
try:
|
|
||||||
result = subprocess.run(['uname', '-r'], capture_output=True, text=True, timeout=5)
|
|
||||||
if result.returncode == 0:
|
|
||||||
kernel_version = result.stdout.strip()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Note: uname not available: {e}")
|
|
||||||
|
|
||||||
cpu_cores = psutil.cpu_count(logical=False) # Physical cores only
|
|
||||||
|
|
||||||
available_updates = 0
|
|
||||||
try:
|
|
||||||
result = subprocess.run(['apt', 'list', '--upgradable'], capture_output=True, text=True, timeout=10)
|
|
||||||
if result.returncode == 0:
|
|
||||||
# Count lines minus header
|
|
||||||
lines = result.stdout.strip().split('\n')
|
|
||||||
available_updates = max(0, len(lines) - 1)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Note: apt list not available: {e}")
|
|
||||||
|
|
||||||
# Try to get Proxmox node info if available
|
|
||||||
try:
|
|
||||||
result = subprocess.run(['pvesh', 'get', '/nodes', '--output-format', 'json'],
|
|
||||||
capture_output=True, text=True, timeout=5)
|
|
||||||
if result.returncode == 0:
|
|
||||||
nodes = json.loads(result.stdout)
|
|
||||||
if nodes and len(nodes) > 0:
|
|
||||||
node_id = nodes[0].get('node', node_id)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Note: pvesh not available or failed: {e}")
|
|
||||||
pass # Use default if pvesh not available
|
|
||||||
|
|
||||||
response = {
|
|
||||||
'cpu_usage': round(cpu_percent, 1),
|
|
||||||
'memory_usage': round(memory.percent, 1),
|
|
||||||
'memory_total': round(memory.total / (1024**3), 1), # GB
|
|
||||||
'memory_used': round(memory.used / (1024**3), 1), # GB
|
|
||||||
'temperature': temp,
|
|
||||||
'uptime': uptime_str,
|
|
||||||
'load_average': list(load_avg),
|
|
||||||
'hostname': hostname,
|
|
||||||
'node_id': node_id,
|
|
||||||
'timestamp': datetime.now().isoformat(),
|
|
||||||
'cpu_cores': cpu_cores
|
|
||||||
}
|
|
||||||
|
|
||||||
if proxmox_version:
|
|
||||||
response['proxmox_version'] = proxmox_version
|
|
||||||
if kernel_version:
|
|
||||||
response['kernel_version'] = kernel_version
|
|
||||||
if available_updates > 0:
|
|
||||||
response['available_updates'] = available_updates
|
|
||||||
|
|
||||||
return response
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Critical error getting system info: {e}")
|
|
||||||
return {
|
|
||||||
'error': f'Unable to access system information: {str(e)}',
|
|
||||||
'timestamp': datetime.now().isoformat()
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_storage_info():
|
def get_storage_info():
|
||||||
"""Get storage and disk information"""
|
"""Get storage and disk information"""
|
||||||
@@ -3311,8 +3269,58 @@ def get_hardware_info():
|
|||||||
|
|
||||||
@app.route('/api/system', methods=['GET'])
|
@app.route('/api/system', methods=['GET'])
|
||||||
def api_system():
|
def api_system():
|
||||||
"""Get system information"""
|
"""Get system information including CPU, memory, and temperature"""
|
||||||
return jsonify(get_system_info())
|
try:
|
||||||
|
cpu_usage = psutil.cpu_percent(interval=0.5)
|
||||||
|
|
||||||
|
memory = psutil.virtual_memory()
|
||||||
|
memory_used_gb = memory.used / (1024 ** 3)
|
||||||
|
memory_total_gb = memory.total / (1024 ** 3)
|
||||||
|
memory_usage_percent = memory.percent
|
||||||
|
|
||||||
|
# Get temperature
|
||||||
|
temp = get_cpu_temperature()
|
||||||
|
|
||||||
|
# Get uptime
|
||||||
|
uptime = get_uptime()
|
||||||
|
|
||||||
|
# Get load average
|
||||||
|
load_avg = os.getloadavg()
|
||||||
|
|
||||||
|
# Get CPU cores
|
||||||
|
cpu_cores = psutil.cpu_count(logical=False)
|
||||||
|
|
||||||
|
cpu_threads = psutil.cpu_count(logical=True)
|
||||||
|
|
||||||
|
# Get Proxmox version
|
||||||
|
proxmox_version = get_proxmox_version()
|
||||||
|
|
||||||
|
# Get kernel version
|
||||||
|
kernel_version = platform.release()
|
||||||
|
|
||||||
|
# Get available updates
|
||||||
|
available_updates = get_available_updates()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'cpu_usage': round(cpu_usage, 1),
|
||||||
|
'memory_usage': round(memory_usage_percent, 1),
|
||||||
|
'memory_total': round(memory_total_gb, 1),
|
||||||
|
'memory_used': round(memory_used_gb, 1),
|
||||||
|
'temperature': temp,
|
||||||
|
'uptime': uptime,
|
||||||
|
'load_average': list(load_avg),
|
||||||
|
'hostname': socket.gethostname(),
|
||||||
|
'node_id': socket.gethostname(),
|
||||||
|
'timestamp': datetime.now().isoformat(),
|
||||||
|
'cpu_cores': cpu_cores,
|
||||||
|
'cpu_threads': cpu_threads,
|
||||||
|
'proxmox_version': proxmox_version,
|
||||||
|
'kernel_version': kernel_version,
|
||||||
|
'available_updates': available_updates
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting system info: {e}")
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
@app.route('/api/storage', methods=['GET'])
|
@app.route('/api/storage', methods=['GET'])
|
||||||
def api_storage():
|
def api_storage():
|
||||||
|
|||||||
Reference in New Issue
Block a user