mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2025-11-18 11:36:17 +00:00
Uodate AppImage
This commit is contained in:
@@ -215,15 +215,15 @@ export function NetworkMetrics() {
|
||||
<CardContent>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-muted-foreground">Received:</span>
|
||||
<span className="text-sm text-muted-foreground hidden lg:inline">Received:</span>
|
||||
<span className="text-base lg:text-xl font-bold text-green-500">↓ {trafficInFormatted}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-muted-foreground">Sent:</span>
|
||||
<span className="text-sm text-muted-foreground hidden lg:inline">Sent:</span>
|
||||
<span className="text-base lg:text-xl font-bold text-blue-500">↑ {trafficOutFormatted}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-2">Total data transferred</p>
|
||||
<p className="text-xs text-muted-foreground mt-2 hidden lg:block">Total data transferred</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -250,54 +250,6 @@ export function NetworkMetrics() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-card border-border">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">Firewall Status</CardTitle>
|
||||
<Router className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-xl lg:text-2xl font-bold text-green-500">Active</div>
|
||||
<div className="flex items-center gap-2 mt-2">
|
||||
<Badge variant="outline" className="bg-green-500/10 text-green-500 border-green-500/20 text-xs">
|
||||
Protected
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-2">System protected</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-card border-border">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">Network Health</CardTitle>
|
||||
<Activity className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-xl lg:text-2xl font-bold text-foreground mb-2">
|
||||
<Badge variant="outline" className={healthColor}>
|
||||
{healthStatus}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-muted-foreground">Packet Loss:</span>
|
||||
<span
|
||||
className={`font-medium ${Number.parseFloat(avgPacketLoss) > 1 ? "text-red-500" : "text-green-500"}`}
|
||||
>
|
||||
{avgPacketLoss}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-muted-foreground">Errors:</span>
|
||||
<span className={`font-medium ${totalErrors > 100 ? "text-red-500" : "text-green-500"}`}>
|
||||
{totalErrors}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{networkData.physical_interfaces && networkData.physical_interfaces.length > 0 && (
|
||||
<Card className="bg-card border-border">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-foreground flex items-center">
|
||||
@@ -381,7 +333,7 @@ export function NetworkMetrics() {
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{networkData.bridge_interfaces && networkData.bridge_interfaces.length > 0 && (
|
||||
<Card className="bg-card border-border">
|
||||
|
||||
@@ -3101,76 +3101,6 @@ def get_gpu_info():
|
||||
|
||||
return gpus
|
||||
|
||||
def get_disk_hardware_info(disk_name):
|
||||
"""Get detailed hardware information for a disk"""
|
||||
disk_info = {}
|
||||
|
||||
try:
|
||||
# Get disk type (HDD, SSD, NVMe)
|
||||
result = subprocess.run(['lsblk', '-d', '-n', '-o', 'NAME,ROTA,TYPE'],
|
||||
capture_output=True, text=True, timeout=5)
|
||||
if result.returncode == 0:
|
||||
for line in result.stdout.strip().split('\n'):
|
||||
parts = line.split()
|
||||
if len(parts) >= 2 and parts[0] == disk_name:
|
||||
rota = parts[1]
|
||||
disk_info['type'] = 'HDD' if rota == '1' else 'SSD'
|
||||
if disk_name.startswith('nvme'):
|
||||
disk_info['type'] = 'NVMe SSD'
|
||||
break # Found the correct disk
|
||||
|
||||
# Get driver/kernel module
|
||||
try:
|
||||
# For NVMe
|
||||
if disk_name.startswith('nvme'):
|
||||
disk_info['driver'] = 'nvme'
|
||||
disk_info['interface'] = 'PCIe/NVMe'
|
||||
# For SATA/SAS
|
||||
else:
|
||||
result = subprocess.run(['udevadm', 'info', '--query=property', f'/dev/{disk_name}'],
|
||||
capture_output=True, text=True, timeout=5)
|
||||
if result.returncode == 0:
|
||||
for line in result.stdout.split('\n'):
|
||||
if 'ID_BUS=' in line:
|
||||
bus = line.split('=')[1].strip()
|
||||
disk_info['interface'] = bus.upper()
|
||||
if 'ID_MODEL=' in line:
|
||||
model = line.split('=')[1].strip()
|
||||
disk_info['model'] = model
|
||||
if 'ID_SERIAL_SHORT=' in line:
|
||||
serial = line.split('=')[1].strip()
|
||||
disk_info['serial'] = serial
|
||||
except Exception as e:
|
||||
print(f"[v0] Error getting disk driver info: {e}")
|
||||
|
||||
# Get SMART data
|
||||
try:
|
||||
result = subprocess.run(['smartctl', '-i', f'/dev/{disk_name}'],
|
||||
capture_output=True, text=True, timeout=5)
|
||||
if result.returncode == 0:
|
||||
for line in result.stdout.split('\n'):
|
||||
if 'Model Family:' in line:
|
||||
disk_info['family'] = line.split(':', 1)[1].strip()
|
||||
elif 'Device Model:' in line or 'Model Number:' in line:
|
||||
disk_info['model'] = line.split(':', 1)[1].strip()
|
||||
elif 'Serial Number:' in line:
|
||||
disk_info['serial'] = line.split(':', 1)[1].strip()
|
||||
elif 'Firmware Version:' in line:
|
||||
disk_info['firmware'] = line.split(':', 1)[1].strip()
|
||||
elif 'Rotation Rate:' in line:
|
||||
disk_info['rotation_rate'] = line.split(':', 1)[1].strip()
|
||||
elif 'Form Factor:' in line:
|
||||
disk_info['form_factor'] = line.split(':', 1)[1].strip()
|
||||
elif 'SATA Version is:' in line:
|
||||
disk_info['sata_version'] = line.split(':', 1)[1].strip()
|
||||
except Exception as e:
|
||||
print(f"[v0] Error getting SMART info: {e}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[v0] Error getting disk hardware info: {e}")
|
||||
|
||||
return disk_info
|
||||
|
||||
def get_hardware_info():
|
||||
"""Get comprehensive hardware information"""
|
||||
try:
|
||||
@@ -3752,6 +3682,7 @@ def api_vms():
|
||||
"""Get virtual machine information"""
|
||||
return jsonify(get_proxmox_vms())
|
||||
|
||||
# Add the new api_vm_metrics endpoint here
|
||||
@app.route('/api/vms/<int:vmid>/metrics', methods=['GET'])
|
||||
def api_vm_metrics(vmid):
|
||||
"""Get historical metrics (RRD data) for a specific VM/LXC"""
|
||||
@@ -3821,6 +3752,53 @@ def api_vm_metrics(vmid):
|
||||
print(f"[v0] ===== METRICS REQUEST EXCEPTION =====")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/node/metrics', methods=['GET'])
|
||||
def api_node_metrics():
|
||||
"""Get historical metrics (RRD data) for the node"""
|
||||
try:
|
||||
timeframe = request.args.get('timeframe', 'week') # hour, day, week, month, year
|
||||
|
||||
print(f"[v0] ===== NODE METRICS REQUEST =====")
|
||||
print(f"[v0] Timeframe: {timeframe}")
|
||||
|
||||
# Validate timeframe
|
||||
valid_timeframes = ['hour', 'day', 'week', 'month', 'year']
|
||||
if timeframe not in valid_timeframes:
|
||||
print(f"[v0] ERROR: Invalid timeframe: {timeframe}")
|
||||
return jsonify({'error': f'Invalid timeframe. Must be one of: {", ".join(valid_timeframes)}'}), 400
|
||||
|
||||
# Get local node name
|
||||
local_node = socket.gethostname()
|
||||
print(f"[v0] Local node: {local_node}")
|
||||
|
||||
# Get RRD data for the node
|
||||
print(f"[v0] Fetching RRD data for node {local_node} with timeframe {timeframe}...")
|
||||
rrd_result = subprocess.run(['pvesh', 'get', f'/nodes/{local_node}/rrddata',
|
||||
'--timeframe', timeframe, '--output-format', 'json'],
|
||||
capture_output=True, text=True, timeout=10)
|
||||
|
||||
if rrd_result.returncode == 0:
|
||||
print(f"[v0] RRD data fetched successfully")
|
||||
print(f"[v0] RRD output length: {len(rrd_result.stdout)} bytes")
|
||||
rrd_data = json.loads(rrd_result.stdout)
|
||||
print(f"[v0] RRD data points: {len(rrd_data)}")
|
||||
print(f"[v0] ===== NODE METRICS REQUEST SUCCESS =====")
|
||||
return jsonify({
|
||||
'node': local_node,
|
||||
'timeframe': timeframe,
|
||||
'data': rrd_data
|
||||
})
|
||||
else:
|
||||
print(f"[v0] ERROR: Failed to get RRD data")
|
||||
print(f"[v0] stderr: {rrd_result.stderr}")
|
||||
print(f"[v0] ===== NODE METRICS REQUEST FAILED =====")
|
||||
return jsonify({'error': f'Failed to get RRD data: {rrd_result.stderr}'}), 500
|
||||
|
||||
except Exception as e:
|
||||
print(f"[v0] EXCEPTION in api_node_metrics: {e}")
|
||||
print(f"[v0] ===== NODE METRICS REQUEST EXCEPTION =====")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/logs', methods=['GET'])
|
||||
def api_logs():
|
||||
"""Get system logs"""
|
||||
@@ -3934,6 +3912,8 @@ def api_logs_download():
|
||||
if result.returncode == 0:
|
||||
import tempfile
|
||||
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.log') as f:
|
||||
f.write(f"ProxMenux Log ({log_type}, since {since_days if since_days else f'{hours}h'}) - Generated: {datetime.now().isoformat()}\n")
|
||||
f.write("=" * 80 + "\n\n")
|
||||
f.write(result.stdout)
|
||||
temp_path = f.name
|
||||
|
||||
@@ -4098,7 +4078,7 @@ def api_notifications_download():
|
||||
if result.returncode == 0:
|
||||
import tempfile
|
||||
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.log') as f:
|
||||
f.write(f"Notification Log - {timestamp}\n")
|
||||
f.write(f"ProxMenux Log - Notification around: {timestamp}\n")
|
||||
f.write(f"Time Window: {since_time} to {until_time}\n")
|
||||
f.write("=" * 80 + "\n\n")
|
||||
f.write(result.stdout)
|
||||
@@ -4689,6 +4669,7 @@ def api_info():
|
||||
'/api/network',
|
||||
'/api/vms',
|
||||
'/api/vms/<vmid>/metrics', # Added endpoint for RRD data
|
||||
'/api/node/metrics', # Added node metrics endpoint
|
||||
'/api/logs',
|
||||
'/api/health',
|
||||
'/api/hardware',
|
||||
@@ -5020,7 +5001,7 @@ def api_vm_config_update(vmid):
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
if __name__ == '__main__':
|
||||
# API endpoints available at: /api/system, /api/system-info, /api/storage, /api/proxmox-storage, /api/network, /api/vms, /api/logs, /api/health, /api/hardware, /api/prometheus
|
||||
# API endpoints available at: /api/system, /api/system-info, /api/storage, /api/proxmox-storage, /api/network, /api/vms, /api/logs, /api/health, /api/hardware, /api/prometheus, /api/node/metrics
|
||||
|
||||
import sys
|
||||
import logging
|
||||
@@ -5034,6 +5015,6 @@ if __name__ == '__main__':
|
||||
cli.show_server_banner = lambda *x: None
|
||||
|
||||
# Print only essential information
|
||||
print("API endpoints available at: /api/system, /api/system-info, /api/storage, /api/proxmox-storage, /api/network, /api/vms, /api/logs, /api/health, /api/hardware, /api/prometheus")
|
||||
print("API endpoints available at: /api/system, /api/system-info, /api/storage, /api/proxmox-storage, /api/network, /api/vms, /api/logs, /api/health, /api/hardware, /api/prometheus, /api/node/metrics")
|
||||
|
||||
app.run(host='0.0.0.0', port=8008, debug=False)
|
||||
|
||||
Reference in New Issue
Block a user