mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2025-12-14 16:16:21 +00:00
Update AppImage
This commit is contained in:
@@ -87,6 +87,7 @@ cp "$SCRIPT_DIR/flask_health_routes.py" "$APP_DIR/usr/bin/" 2>/dev/null || echo
|
|||||||
cp "$SCRIPT_DIR/flask_proxmenux_routes.py" "$APP_DIR/usr/bin/" 2>/dev/null || echo "⚠️ flask_proxmenux_routes.py not found"
|
cp "$SCRIPT_DIR/flask_proxmenux_routes.py" "$APP_DIR/usr/bin/" 2>/dev/null || echo "⚠️ flask_proxmenux_routes.py not found"
|
||||||
cp "$SCRIPT_DIR/flask_terminal_routes.py" "$APP_DIR/usr/bin/" 2>/dev/null || echo "⚠️ flask_terminal_routes.py not found"
|
cp "$SCRIPT_DIR/flask_terminal_routes.py" "$APP_DIR/usr/bin/" 2>/dev/null || echo "⚠️ flask_terminal_routes.py not found"
|
||||||
cp "$SCRIPT_DIR/hardware_monitor.py" "$APP_DIR/usr/bin/" 2>/dev/null || echo "⚠️ hardware_monitor.py not found"
|
cp "$SCRIPT_DIR/hardware_monitor.py" "$APP_DIR/usr/bin/" 2>/dev/null || echo "⚠️ hardware_monitor.py not found"
|
||||||
|
cp "$SCRIPT_DIR/proxmox_storage_monitor.py" "$APP_DIR/usr/bin/" 2>/dev/null || echo "⚠️ proxmox_storage_monitor.py not found"
|
||||||
|
|
||||||
echo "📋 Adding translation support..."
|
echo "📋 Adding translation support..."
|
||||||
cat > "$APP_DIR/usr/bin/translate_cli.py" << 'PYEOF'
|
cat > "$APP_DIR/usr/bin/translate_cli.py" << 'PYEOF'
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|||||||
if BASE_DIR not in sys.path:
|
if BASE_DIR not in sys.path:
|
||||||
sys.path.insert(0, BASE_DIR)
|
sys.path.insert(0, BASE_DIR)
|
||||||
|
|
||||||
|
from proxmox_storage_monitor import proxmox_storage_monitor
|
||||||
from flask_terminal_routes import terminal_bp, init_terminal_routes # noqa: E402
|
from flask_terminal_routes import terminal_bp, init_terminal_routes # noqa: E402
|
||||||
from flask_health_routes import health_bp # noqa: E402
|
from flask_health_routes import health_bp # noqa: E402
|
||||||
from flask_auth_routes import auth_bp # noqa: E402
|
from flask_auth_routes import auth_bp # noqa: E402
|
||||||
@@ -1758,18 +1759,7 @@ def get_proxmox_storage():
|
|||||||
pass
|
pass
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Si total es 0, significa que hay un error de conexión o el datastore no está disponible
|
# No filtrar storages no disponibles - mantenerlos para mostrar errores
|
||||||
if total == 0:
|
|
||||||
# print(f"[v0] Skipping storage {name} - invalid data (total=0, likely connection error)")
|
|
||||||
pass
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Si el status es "inactive", también lo omitimos
|
|
||||||
if status.lower() != "available":
|
|
||||||
# print(f"[v0] Skipping storage {name} - status is not available: {status}")
|
|
||||||
pass
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Calcular porcentaje
|
# Calcular porcentaje
|
||||||
percent = (used / total * 100) if total > 0 else 0.0
|
percent = (used / total * 100) if total > 0 else 0.0
|
||||||
|
|
||||||
@@ -1778,10 +1768,18 @@ def get_proxmox_storage():
|
|||||||
used_gb = round(used / (1024**3), 2)
|
used_gb = round(used / (1024**3), 2)
|
||||||
available_gb = round(available / (1024**3), 2)
|
available_gb = round(available / (1024**3), 2)
|
||||||
|
|
||||||
|
# Determine storage status
|
||||||
|
if total == 0:
|
||||||
|
storage_status = 'error'
|
||||||
|
elif status.lower() != "available":
|
||||||
|
storage_status = 'error'
|
||||||
|
else:
|
||||||
|
storage_status = 'active'
|
||||||
|
|
||||||
storage_info = {
|
storage_info = {
|
||||||
'name': name,
|
'name': name,
|
||||||
'type': storage_type,
|
'type': storage_type,
|
||||||
'status': 'active', # Normalizar status para compatibilidad con frontend
|
'status': storage_status, # Usar el status determinado (active o error)
|
||||||
'total': total_gb,
|
'total': total_gb,
|
||||||
'used': used_gb,
|
'used': used_gb,
|
||||||
'available': available_gb,
|
'available': available_gb,
|
||||||
@@ -1792,6 +1790,12 @@ def get_proxmox_storage():
|
|||||||
|
|
||||||
storage_list.append(storage_info)
|
storage_list.append(storage_info)
|
||||||
|
|
||||||
|
# Get unavailable storages from monitor
|
||||||
|
storage_status_data = proxmox_storage_monitor.get_storage_status()
|
||||||
|
unavailable_storages = storage_status_data.get('unavailable', [])
|
||||||
|
|
||||||
|
# Add unavailable storages to the list
|
||||||
|
storage_list.extend(unavailable_storages)
|
||||||
|
|
||||||
return {'storage': storage_list}
|
return {'storage': storage_list}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
202
AppImage/scripts/proxmox_storage_monitor.py
Normal file
202
AppImage/scripts/proxmox_storage_monitor.py
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
ProxMenux - Proxmox Storage Monitor
|
||||||
|
Monitors configured Proxmox storages and tracks unavailable storages
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import socket
|
||||||
|
from typing import Dict, List, Any, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class ProxmoxStorageMonitor:
|
||||||
|
"""Monitor Proxmox storage configuration and status"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.configured_storages: Dict[str, Dict[str, Any]] = {}
|
||||||
|
self._load_configured_storages()
|
||||||
|
|
||||||
|
def _get_node_name(self) -> str:
|
||||||
|
"""Get current Proxmox node name"""
|
||||||
|
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)
|
||||||
|
hostname = socket.gethostname()
|
||||||
|
for node in nodes:
|
||||||
|
if node.get('node') == hostname:
|
||||||
|
return hostname
|
||||||
|
if nodes:
|
||||||
|
return nodes[0].get('node', hostname)
|
||||||
|
return socket.gethostname()
|
||||||
|
except Exception:
|
||||||
|
return socket.gethostname()
|
||||||
|
|
||||||
|
def _load_configured_storages(self) -> None:
|
||||||
|
"""Load configured storages from Proxmox configuration"""
|
||||||
|
try:
|
||||||
|
local_node = self._get_node_name()
|
||||||
|
|
||||||
|
# Read storage configuration from pvesh
|
||||||
|
result = subprocess.run(
|
||||||
|
['pvesh', 'get', '/storage', '--output-format', 'json'],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
storages = json.loads(result.stdout)
|
||||||
|
|
||||||
|
for storage in storages:
|
||||||
|
storage_id = storage.get('storage')
|
||||||
|
if not storage_id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check if storage is enabled for this node
|
||||||
|
nodes = storage.get('nodes')
|
||||||
|
if nodes and local_node not in nodes.split(','):
|
||||||
|
continue
|
||||||
|
|
||||||
|
disabled = storage.get('disable', 0)
|
||||||
|
if disabled == 1:
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.configured_storages[storage_id] = {
|
||||||
|
'name': storage_id,
|
||||||
|
'type': storage.get('type', 'unknown'),
|
||||||
|
'content': storage.get('content', ''),
|
||||||
|
'path': storage.get('path', ''),
|
||||||
|
'enabled': True
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_storage_status(self) -> Dict[str, List[Dict[str, Any]]]:
|
||||||
|
"""
|
||||||
|
Get storage status, including unavailable storages
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
{
|
||||||
|
'available': [...],
|
||||||
|
'unavailable': [...]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
local_node = self._get_node_name()
|
||||||
|
|
||||||
|
# Get current storage status from pvesh
|
||||||
|
result = subprocess.run(
|
||||||
|
['pvesh', 'get', '/cluster/resources', '--type', 'storage', '--output-format', 'json'],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
return {'available': [], 'unavailable': list(self.configured_storages.values())}
|
||||||
|
|
||||||
|
resources = json.loads(result.stdout)
|
||||||
|
|
||||||
|
# Track which configured storages are available
|
||||||
|
available_storages = []
|
||||||
|
unavailable_storages = []
|
||||||
|
seen_storage_names = set()
|
||||||
|
|
||||||
|
for resource in resources:
|
||||||
|
node = resource.get('node', '')
|
||||||
|
|
||||||
|
# Filter only local node storages
|
||||||
|
if node != local_node:
|
||||||
|
continue
|
||||||
|
|
||||||
|
name = resource.get('storage', 'unknown')
|
||||||
|
seen_storage_names.add(name)
|
||||||
|
storage_type = resource.get('plugintype', 'unknown')
|
||||||
|
status = resource.get('status', 'unknown')
|
||||||
|
|
||||||
|
try:
|
||||||
|
total = int(resource.get('maxdisk', 0))
|
||||||
|
used = int(resource.get('disk', 0))
|
||||||
|
available = total - used if total > 0 else 0
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
total = 0
|
||||||
|
used = 0
|
||||||
|
available = 0
|
||||||
|
|
||||||
|
# Calculate percentage
|
||||||
|
percent = (used / total * 100) if total > 0 else 0.0
|
||||||
|
|
||||||
|
# Convert bytes to GB
|
||||||
|
total_gb = round(total / (1024**3), 2)
|
||||||
|
used_gb = round(used / (1024**3), 2)
|
||||||
|
available_gb = round(available / (1024**3), 2)
|
||||||
|
|
||||||
|
storage_info = {
|
||||||
|
'name': name,
|
||||||
|
'type': storage_type,
|
||||||
|
'total': total_gb,
|
||||||
|
'used': used_gb,
|
||||||
|
'available': available_gb,
|
||||||
|
'percent': round(percent, 2),
|
||||||
|
'node': node
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if storage is available
|
||||||
|
if total == 0 or status.lower() != "available":
|
||||||
|
storage_info['status'] = 'error'
|
||||||
|
storage_info['status_detail'] = 'unavailable' if total == 0 else status
|
||||||
|
unavailable_storages.append(storage_info)
|
||||||
|
else:
|
||||||
|
storage_info['status'] = 'active'
|
||||||
|
available_storages.append(storage_info)
|
||||||
|
|
||||||
|
# Check for configured storages that are completely missing
|
||||||
|
for storage_name, storage_config in self.configured_storages.items():
|
||||||
|
if storage_name not in seen_storage_names:
|
||||||
|
unavailable_storages.append({
|
||||||
|
'name': storage_name,
|
||||||
|
'type': storage_config['type'],
|
||||||
|
'status': 'error',
|
||||||
|
'status_detail': 'not_found',
|
||||||
|
'total': 0,
|
||||||
|
'used': 0,
|
||||||
|
'available': 0,
|
||||||
|
'percent': 0,
|
||||||
|
'node': local_node
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'available': available_storages,
|
||||||
|
'unavailable': unavailable_storages
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
return {
|
||||||
|
'available': [],
|
||||||
|
'unavailable': list(self.configured_storages.values())
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_unavailable_count(self) -> int:
|
||||||
|
"""Get count of unavailable storages"""
|
||||||
|
status = self.get_storage_status()
|
||||||
|
return len(status['unavailable'])
|
||||||
|
|
||||||
|
def reload_configuration(self) -> None:
|
||||||
|
"""Reload storage configuration from Proxmox"""
|
||||||
|
self.configured_storages.clear()
|
||||||
|
self._load_configured_storages()
|
||||||
|
|
||||||
|
|
||||||
|
# Global instance
|
||||||
|
proxmox_storage_monitor = ProxmoxStorageMonitor()
|
||||||
Reference in New Issue
Block a user