mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2025-11-18 03:26:17 +00:00
Update AppImage
This commit is contained in:
@@ -965,7 +965,12 @@ export default function Hardware() {
|
|||||||
<div className="rounded-lg border border-border/30 bg-background/50 p-4">
|
<div className="rounded-lg border border-border/30 bg-background/50 p-4">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<span className="text-sm font-medium">{hardwareData.ups.model}</span>
|
<span className="text-sm font-medium">{hardwareData.ups.model}</span>
|
||||||
<Badge variant={hardwareData.ups.status === "OL" ? "default" : "destructive"}>
|
<Badge
|
||||||
|
variant={hardwareData.ups.status === "OL" ? "default" : "destructive"}
|
||||||
|
className={
|
||||||
|
hardwareData.ups.status === "OL" ? "bg-green-500/10 text-green-500 border-green-500/20" : ""
|
||||||
|
}
|
||||||
|
>
|
||||||
{hardwareData.ups.status}
|
{hardwareData.ups.status}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
@@ -975,11 +980,11 @@ export default function Hardware() {
|
|||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-xs text-muted-foreground">Battery Charge</span>
|
<span className="text-xs text-muted-foreground">Battery Charge</span>
|
||||||
<span className="text-sm font-semibold">{hardwareData.ups.battery_charge}</span>
|
<span className="text-sm font-semibold text-green-500">{hardwareData.ups.battery_charge}</span>
|
||||||
</div>
|
</div>
|
||||||
<Progress
|
<Progress
|
||||||
value={Number.parseInt(hardwareData.ups.battery_charge.replace("%", ""))}
|
value={Number.parseInt(hardwareData.ups.battery_charge.replace("%", ""))}
|
||||||
className="h-2"
|
className="h-2 [&>div]:bg-blue-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -988,23 +993,34 @@ export default function Hardware() {
|
|||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-xs text-muted-foreground">Load</span>
|
<span className="text-xs text-muted-foreground">Load</span>
|
||||||
<span className="text-sm font-semibold">{hardwareData.ups.load_percent}</span>
|
<span className="text-sm font-semibold text-green-500">{hardwareData.ups.load_percent}</span>
|
||||||
</div>
|
</div>
|
||||||
<Progress value={Number.parseInt(hardwareData.ups.load_percent.replace("%", ""))} className="h-2" />
|
<Progress
|
||||||
|
value={Number.parseInt(hardwareData.ups.load_percent.replace("%", ""))}
|
||||||
|
className="h-2 [&>div]:bg-blue-500"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{hardwareData.ups.time_left && (
|
{hardwareData.ups.time_left && (
|
||||||
<div>
|
<div>
|
||||||
<span className="text-xs text-muted-foreground">Runtime</span>
|
<span className="text-xs text-muted-foreground">Runtime</span>
|
||||||
<p className="text-sm font-semibold">{hardwareData.ups.time_left}</p>
|
<div className="mt-1">
|
||||||
|
<Badge className="bg-green-500/10 text-green-500 border-green-500/20">
|
||||||
|
{hardwareData.ups.time_left}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{hardwareData.ups.line_voltage && (
|
{hardwareData.ups.line_voltage && (
|
||||||
<div>
|
<div>
|
||||||
<span className="text-xs text-muted-foreground">Input Voltage</span>
|
<span className="text-xs text-muted-foreground">Input Voltage</span>
|
||||||
<p className="text-sm font-semibold">{hardwareData.ups.line_voltage}</p>
|
<div className="mt-1">
|
||||||
|
<Badge className="bg-green-500/10 text-green-500 border-green-500/20">
|
||||||
|
{hardwareData.ups.line_voltage}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3912,6 +3912,199 @@ def api_health():
|
|||||||
'version': '1.0.0'
|
'version': '1.0.0'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@app.route('/api/prometheus', methods=['GET'])
|
||||||
|
def api_prometheus():
|
||||||
|
"""Export metrics in Prometheus format"""
|
||||||
|
try:
|
||||||
|
metrics = []
|
||||||
|
timestamp = int(datetime.now().timestamp() * 1000)
|
||||||
|
node = socket.gethostname()
|
||||||
|
|
||||||
|
# Get system data
|
||||||
|
cpu_usage = psutil.cpu_percent(interval=0.5)
|
||||||
|
memory = psutil.virtual_memory()
|
||||||
|
load_avg = os.getloadavg()
|
||||||
|
uptime_seconds = time.time() - psutil.boot_time()
|
||||||
|
|
||||||
|
# System metrics
|
||||||
|
metrics.append(f'# HELP proxmox_cpu_usage CPU usage percentage')
|
||||||
|
metrics.append(f'# TYPE proxmox_cpu_usage gauge')
|
||||||
|
metrics.append(f'proxmox_cpu_usage{{node="{node}"}} {cpu_usage} {timestamp}')
|
||||||
|
|
||||||
|
metrics.append(f'# HELP proxmox_memory_total_bytes Total memory in bytes')
|
||||||
|
metrics.append(f'# TYPE proxmox_memory_total_bytes gauge')
|
||||||
|
metrics.append(f'proxmox_memory_total_bytes{{node="{node}"}} {memory.total} {timestamp}')
|
||||||
|
|
||||||
|
metrics.append(f'# HELP proxmox_memory_used_bytes Used memory in bytes')
|
||||||
|
metrics.append(f'# TYPE proxmox_memory_used_bytes gauge')
|
||||||
|
metrics.append(f'proxmox_memory_used_bytes{{node="{node}"}} {memory.used} {timestamp}')
|
||||||
|
|
||||||
|
metrics.append(f'# HELP proxmox_memory_usage_percent Memory usage percentage')
|
||||||
|
metrics.append(f'# TYPE proxmox_memory_usage_percent gauge')
|
||||||
|
metrics.append(f'proxmox_memory_usage_percent{{node="{node}"}} {memory.percent} {timestamp}')
|
||||||
|
|
||||||
|
metrics.append(f'# HELP proxmox_load_average System load average')
|
||||||
|
metrics.append(f'# TYPE proxmox_load_average gauge')
|
||||||
|
metrics.append(f'proxmox_load_average{{node="{node}",period="1m"}} {load_avg[0]} {timestamp}')
|
||||||
|
metrics.append(f'proxmox_load_average{{node="{node}",period="5m"}} {load_avg[1]} {timestamp}')
|
||||||
|
metrics.append(f'proxmox_load_average{{node="{node}",period="15m"}} {load_avg[2]} {timestamp}')
|
||||||
|
|
||||||
|
metrics.append(f'# HELP proxmox_uptime_seconds System uptime in seconds')
|
||||||
|
metrics.append(f'# TYPE proxmox_uptime_seconds counter')
|
||||||
|
metrics.append(f'proxmox_uptime_seconds{{node="{node}"}} {uptime_seconds} {timestamp}')
|
||||||
|
|
||||||
|
# Temperature
|
||||||
|
temp = get_cpu_temperature()
|
||||||
|
if temp:
|
||||||
|
metrics.append(f'# HELP proxmox_cpu_temperature_celsius CPU temperature in Celsius')
|
||||||
|
metrics.append(f'# TYPE proxmox_cpu_temperature_celsius gauge')
|
||||||
|
metrics.append(f'proxmox_cpu_temperature_celsius{{node="{node}"}} {temp} {timestamp}')
|
||||||
|
|
||||||
|
# Storage metrics
|
||||||
|
storage_info = get_storage_info()
|
||||||
|
for disk in storage_info.get('disks', []):
|
||||||
|
disk_name = disk.get('name', 'unknown')
|
||||||
|
metrics.append(f'# HELP proxmox_disk_total_bytes Total disk space in bytes')
|
||||||
|
metrics.append(f'# TYPE proxmox_disk_total_bytes gauge')
|
||||||
|
metrics.append(f'proxmox_disk_total_bytes{{node="{node}",disk="{disk_name}"}} {disk.get("total", 0)} {timestamp}')
|
||||||
|
|
||||||
|
metrics.append(f'# HELP proxmox_disk_used_bytes Used disk space in bytes')
|
||||||
|
metrics.append(f'# TYPE proxmox_disk_used_bytes gauge')
|
||||||
|
metrics.append(f'proxmox_disk_used_bytes{{node="{node}",disk="{disk_name}"}} {disk.get("used", 0)} {timestamp}')
|
||||||
|
|
||||||
|
metrics.append(f'# HELP proxmox_disk_usage_percent Disk usage percentage')
|
||||||
|
metrics.append(f'# TYPE proxmox_disk_usage_percent gauge')
|
||||||
|
metrics.append(f'proxmox_disk_usage_percent{{node="{node}",disk="{disk_name}"}} {disk.get("percent", 0)} {timestamp}')
|
||||||
|
|
||||||
|
# Network metrics
|
||||||
|
network_info = get_network_info()
|
||||||
|
if 'traffic' in network_info:
|
||||||
|
metrics.append(f'# HELP proxmox_network_bytes_sent_total Total bytes sent')
|
||||||
|
metrics.append(f'# TYPE proxmox_network_bytes_sent_total counter')
|
||||||
|
metrics.append(f'proxmox_network_bytes_sent_total{{node="{node}"}} {network_info["traffic"].get("bytes_sent", 0)} {timestamp}')
|
||||||
|
|
||||||
|
metrics.append(f'# HELP proxmox_network_bytes_received_total Total bytes received')
|
||||||
|
metrics.append(f'# TYPE proxmox_network_bytes_received_total counter')
|
||||||
|
metrics.append(f'proxmox_network_bytes_received_total{{node="{node}"}} {network_info["traffic"].get("bytes_recv", 0)} {timestamp}')
|
||||||
|
|
||||||
|
# Per-interface network metrics
|
||||||
|
for interface in network_info.get('interfaces', []):
|
||||||
|
iface_name = interface.get('name', 'unknown')
|
||||||
|
if interface.get('status') == 'up':
|
||||||
|
metrics.append(f'# HELP proxmox_interface_bytes_sent_total Bytes sent per interface')
|
||||||
|
metrics.append(f'# TYPE proxmox_interface_bytes_sent_total counter')
|
||||||
|
metrics.append(f'proxmox_interface_bytes_sent_total{{node="{node}",interface="{iface_name}"}} {interface.get("bytes_sent", 0)} {timestamp}')
|
||||||
|
|
||||||
|
metrics.append(f'# HELP proxmox_interface_bytes_received_total Bytes received per interface')
|
||||||
|
metrics.append(f'# TYPE proxmox_interface_bytes_received_total counter')
|
||||||
|
metrics.append(f'proxmox_interface_bytes_received_total{{node="{node}",interface="{iface_name}"}} {interface.get("bytes_recv", 0)} {timestamp}')
|
||||||
|
|
||||||
|
# VM metrics
|
||||||
|
vms_data = get_proxmox_vms()
|
||||||
|
if isinstance(vms_data, list):
|
||||||
|
vms = vms_data
|
||||||
|
total_vms = len(vms)
|
||||||
|
running_vms = sum(1 for vm in vms if vm.get('status') == 'running')
|
||||||
|
stopped_vms = sum(1 for vm in vms if vm.get('status') == 'stopped')
|
||||||
|
|
||||||
|
metrics.append(f'# HELP proxmox_vms_total Total number of VMs and LXCs')
|
||||||
|
metrics.append(f'# TYPE proxmox_vms_total gauge')
|
||||||
|
metrics.append(f'proxmox_vms_total{{node="{node}"}} {total_vms} {timestamp}')
|
||||||
|
|
||||||
|
metrics.append(f'# HELP proxmox_vms_running Number of running VMs and LXCs')
|
||||||
|
metrics.append(f'# TYPE proxmox_vms_running gauge')
|
||||||
|
metrics.append(f'proxmox_vms_running{{node="{node}"}} {running_vms} {timestamp}')
|
||||||
|
|
||||||
|
metrics.append(f'# HELP proxmox_vms_stopped Number of stopped VMs and LXCs')
|
||||||
|
metrics.append(f'# TYPE proxmox_vms_stopped gauge')
|
||||||
|
metrics.append(f'proxmox_vms_stopped{{node="{node}"}} {stopped_vms} {timestamp}')
|
||||||
|
|
||||||
|
# Per-VM metrics
|
||||||
|
for vm in vms:
|
||||||
|
vmid = vm.get('vmid', 'unknown')
|
||||||
|
vm_name = vm.get('name', f'vm-{vmid}')
|
||||||
|
vm_status = 1 if vm.get('status') == 'running' else 0
|
||||||
|
|
||||||
|
metrics.append(f'# HELP proxmox_vm_status VM status (1=running, 0=stopped)')
|
||||||
|
metrics.append(f'# TYPE proxmox_vm_status gauge')
|
||||||
|
metrics.append(f'proxmox_vm_status{{node="{node}",vmid="{vmid}",name="{vm_name}"}} {vm_status} {timestamp}')
|
||||||
|
|
||||||
|
if vm.get('status') == 'running':
|
||||||
|
metrics.append(f'# HELP proxmox_vm_cpu_usage VM CPU usage')
|
||||||
|
metrics.append(f'# TYPE proxmox_vm_cpu_usage gauge')
|
||||||
|
metrics.append(f'proxmox_vm_cpu_usage{{node="{node}",vmid="{vmid}",name="{vm_name}"}} {vm.get("cpu", 0)} {timestamp}')
|
||||||
|
|
||||||
|
metrics.append(f'# HELP proxmox_vm_memory_used_bytes VM memory used in bytes')
|
||||||
|
metrics.append(f'# TYPE proxmox_vm_memory_used_bytes gauge')
|
||||||
|
metrics.append(f'proxmox_vm_memory_used_bytes{{node="{node}",vmid="{vmid}",name="{vm_name}"}} {vm.get("mem", 0)} {timestamp}')
|
||||||
|
|
||||||
|
metrics.append(f'# HELP proxmox_vm_memory_max_bytes VM memory max in bytes')
|
||||||
|
metrics.append(f'# TYPE proxmox_vm_memory_max_bytes gauge')
|
||||||
|
metrics.append(f'proxmox_vm_memory_max_bytes{{node="{node}",vmid="{vmid}",name="{vm_name}"}} {vm.get("maxmem", 0)} {timestamp}')
|
||||||
|
|
||||||
|
# Hardware metrics (temperature, fans, UPS)
|
||||||
|
try:
|
||||||
|
hardware_info = get_hardware_info()
|
||||||
|
|
||||||
|
# Disk temperatures
|
||||||
|
for device in hardware_info.get('storage_devices', []):
|
||||||
|
if device.get('temperature'):
|
||||||
|
disk_name = device.get('name', 'unknown')
|
||||||
|
metrics.append(f'# HELP proxmox_disk_temperature_celsius Disk temperature in Celsius')
|
||||||
|
metrics.append(f'# TYPE proxmox_disk_temperature_celsius gauge')
|
||||||
|
metrics.append(f'proxmox_disk_temperature_celsius{{node="{node}",disk="{disk_name}"}} {device["temperature"]} {timestamp}')
|
||||||
|
|
||||||
|
# Fan speeds
|
||||||
|
all_fans = hardware_info.get('sensors', {}).get('fans', [])
|
||||||
|
all_fans.extend(hardware_info.get('ipmi_fans', []))
|
||||||
|
for fan in all_fans:
|
||||||
|
fan_name = fan.get('name', 'unknown').replace(' ', '_')
|
||||||
|
if fan.get('speed') is not None:
|
||||||
|
metrics.append(f'# HELP proxmox_fan_speed_rpm Fan speed in RPM')
|
||||||
|
metrics.append(f'# TYPE proxmox_fan_speed_rpm gauge')
|
||||||
|
metrics.append(f'proxmox_fan_speed_rpm{{node="{node}",fan="{fan_name}"}} {fan["speed"]} {timestamp}')
|
||||||
|
|
||||||
|
# UPS metrics
|
||||||
|
ups = hardware_info.get('ups')
|
||||||
|
if ups:
|
||||||
|
ups_name = ups.get('name', 'ups').replace(' ', '_')
|
||||||
|
|
||||||
|
if ups.get('battery_charge') is not None:
|
||||||
|
metrics.append(f'# HELP proxmox_ups_battery_charge_percent UPS battery charge percentage')
|
||||||
|
metrics.append(f'# TYPE proxmox_ups_battery_charge_percent gauge')
|
||||||
|
metrics.append(f'proxmox_ups_battery_charge_percent{{node="{node}",ups="{ups_name}"}} {ups["battery_charge"]} {timestamp}')
|
||||||
|
|
||||||
|
if ups.get('load') is not None:
|
||||||
|
metrics.append(f'# HELP proxmox_ups_load_percent UPS load percentage')
|
||||||
|
metrics.append(f'# TYPE proxmox_ups_load_percent gauge')
|
||||||
|
metrics.append(f'proxmox_ups_load_percent{{node="{node}",ups="{ups_name}"}} {ups["load"]} {timestamp}')
|
||||||
|
|
||||||
|
if ups.get('runtime'):
|
||||||
|
# Convert runtime to seconds
|
||||||
|
runtime_str = ups['runtime']
|
||||||
|
runtime_seconds = 0
|
||||||
|
if 'minutes' in runtime_str:
|
||||||
|
runtime_seconds = int(runtime_str.split()[0]) * 60
|
||||||
|
metrics.append(f'# HELP proxmox_ups_runtime_seconds UPS runtime in seconds')
|
||||||
|
metrics.append(f'# TYPE proxmox_ups_runtime_seconds gauge')
|
||||||
|
metrics.append(f'proxmox_ups_runtime_seconds{{node="{node}",ups="{ups_name}"}} {runtime_seconds} {timestamp}')
|
||||||
|
|
||||||
|
if ups.get('input_voltage') is not None:
|
||||||
|
metrics.append(f'# HELP proxmox_ups_input_voltage_volts UPS input voltage in volts')
|
||||||
|
metrics.append(f'# TYPE proxmox_ups_input_voltage_volts gauge')
|
||||||
|
metrics.append(f'proxmox_ups_input_voltage_volts{{node="{node}",ups="{ups_name}"}} {ups["input_voltage"]} {timestamp}')
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[v0] Error getting hardware metrics for Prometheus: {e}")
|
||||||
|
|
||||||
|
# Return metrics in Prometheus format
|
||||||
|
return '\n'.join(metrics) + '\n', 200, {'Content-Type': 'text/plain; version=0.0.4; charset=utf-8'}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error generating Prometheus metrics: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
return f'# Error generating metrics: {str(e)}\n', 500, {'Content-Type': 'text/plain; charset=utf-8'}
|
||||||
|
|
||||||
@app.route('/api/system-info', methods=['GET'])
|
@app.route('/api/system-info', methods=['GET'])
|
||||||
def api_system_info():
|
def api_system_info():
|
||||||
"""Get system and node information for dashboard header"""
|
"""Get system and node information for dashboard header"""
|
||||||
@@ -3983,7 +4176,8 @@ def api_info():
|
|||||||
'/api/backups', # Added backup endpoint
|
'/api/backups', # Added backup endpoint
|
||||||
'/api/events', # Added events endpoint
|
'/api/events', # Added events endpoint
|
||||||
'/api/notifications', # Added notifications endpoint
|
'/api/notifications', # Added notifications endpoint
|
||||||
'/api/task-log/<upid>' # Added task log endpoint
|
'/api/task-log/<upid>', # Added task log endpoint
|
||||||
|
'/api/prometheus' # Added prometheus endpoint
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -4231,7 +4425,7 @@ def api_vm_control(vmid):
|
|||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
if __name__ == '__main__':
|
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 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
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
@@ -4245,6 +4439,6 @@ if __name__ == '__main__':
|
|||||||
cli.show_server_banner = lambda *x: None
|
cli.show_server_banner = lambda *x: None
|
||||||
|
|
||||||
# Print only essential information
|
# 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")
|
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")
|
||||||
|
|
||||||
app.run(host='0.0.0.0', port=8008, debug=False)
|
app.run(host='0.0.0.0', port=8008, debug=False)
|
||||||
|
|||||||
Reference in New Issue
Block a user