mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2025-11-18 03:26:17 +00:00
Update AppImage
This commit is contained in:
@@ -118,6 +118,11 @@ interface VMDetails extends VMData {
|
|||||||
name?: string
|
name?: string
|
||||||
pretty_name?: string
|
pretty_name?: string
|
||||||
}
|
}
|
||||||
|
hardware_info?: {
|
||||||
|
privileged?: boolean | null
|
||||||
|
gpu_passthrough?: string[]
|
||||||
|
devices?: string[]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetcher = async (url: string) => {
|
const fetcher = async (url: string) => {
|
||||||
@@ -1321,6 +1326,71 @@ export function VirtualMachines() {
|
|||||||
|
|
||||||
{showAdditionalInfo && (
|
{showAdditionalInfo && (
|
||||||
<div className="mt-6 pt-6 border-t border-border space-y-6">
|
<div className="mt-6 pt-6 border-t border-border space-y-6">
|
||||||
|
{selectedVM?.type === "lxc" && vmDetails?.hardware_info && (
|
||||||
|
<div>
|
||||||
|
<h4 className="text-sm font-semibold text-muted-foreground mb-3 uppercase tracking-wide">
|
||||||
|
Container Configuration
|
||||||
|
</h4>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* Privileged Status */}
|
||||||
|
{vmDetails.hardware_info.privileged !== null &&
|
||||||
|
vmDetails.hardware_info.privileged !== undefined && (
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-muted-foreground mb-2">Privilege Level</div>
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className={
|
||||||
|
vmDetails.hardware_info.privileged
|
||||||
|
? "bg-yellow-500/10 text-yellow-500 border-yellow-500/20"
|
||||||
|
: "bg-green-500/10 text-green-500 border-green-500/20"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{vmDetails.hardware_info.privileged ? "Privileged" : "Unprivileged"}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* GPU Passthrough */}
|
||||||
|
{vmDetails.hardware_info.gpu_passthrough &&
|
||||||
|
vmDetails.hardware_info.gpu_passthrough.length > 0 && (
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-muted-foreground mb-2">GPU Passthrough</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{vmDetails.hardware_info.gpu_passthrough.map((gpu, index) => (
|
||||||
|
<Badge
|
||||||
|
key={index}
|
||||||
|
variant="outline"
|
||||||
|
className="bg-purple-500/10 text-purple-500 border-purple-500/20"
|
||||||
|
>
|
||||||
|
{gpu}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Other Hardware Devices */}
|
||||||
|
{vmDetails.hardware_info.devices &&
|
||||||
|
vmDetails.hardware_info.devices.length > 0 && (
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-muted-foreground mb-2">Hardware Devices</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{vmDetails.hardware_info.devices.map((device, index) => (
|
||||||
|
<Badge
|
||||||
|
key={index}
|
||||||
|
variant="outline"
|
||||||
|
className="bg-blue-500/10 text-blue-500 border-blue-500/20"
|
||||||
|
>
|
||||||
|
{device}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Hardware Section */}
|
{/* Hardware Section */}
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-sm font-semibold text-muted-foreground mb-3 uppercase tracking-wide">
|
<h4 className="text-sm font-semibold text-muted-foreground mb-3 uppercase tracking-wide">
|
||||||
|
|||||||
@@ -84,6 +84,93 @@ def identify_gpu_type(name, vendor=None, bus=None, driver=None):
|
|||||||
return 'PCI'
|
return 'PCI'
|
||||||
|
|
||||||
|
|
||||||
|
def parse_lxc_hardware_config(vmid, node):
|
||||||
|
"""Parse LXC configuration file to detect hardware passthrough"""
|
||||||
|
hardware_info = {
|
||||||
|
'privileged': None,
|
||||||
|
'gpu_passthrough': [],
|
||||||
|
'devices': []
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
config_path = f'/etc/pve/lxc/{vmid}.conf'
|
||||||
|
|
||||||
|
if not os.path.exists(config_path):
|
||||||
|
return hardware_info
|
||||||
|
|
||||||
|
with open(config_path, 'r') as f:
|
||||||
|
config_content = f.read()
|
||||||
|
|
||||||
|
# Check if privileged or unprivileged
|
||||||
|
if 'unprivileged: 1' in config_content:
|
||||||
|
hardware_info['privileged'] = False
|
||||||
|
elif 'unprivileged: 0' in config_content:
|
||||||
|
hardware_info['privileged'] = True
|
||||||
|
else:
|
||||||
|
# Check for lxc.cap.drop (empty means privileged)
|
||||||
|
if 'lxc.cap.drop:' in config_content and 'lxc.cap.drop: \n' in config_content:
|
||||||
|
hardware_info['privileged'] = True
|
||||||
|
elif 'lxc.cgroup2.devices.allow: a' in config_content:
|
||||||
|
hardware_info['privileged'] = True
|
||||||
|
|
||||||
|
# Detect GPU passthrough
|
||||||
|
gpu_types = []
|
||||||
|
|
||||||
|
# Intel iGPU detection
|
||||||
|
if '/dev/dri' in config_content or 'renderD128' in config_content:
|
||||||
|
if 'Intel' not in gpu_types:
|
||||||
|
gpu_types.append('Intel iGPU')
|
||||||
|
|
||||||
|
# NVIDIA GPU detection
|
||||||
|
if 'nvidia' in config_content.lower():
|
||||||
|
if any(x in config_content for x in ['nvidia0', 'nvidiactl', 'nvidia-uvm']):
|
||||||
|
if 'NVIDIA GPU' not in gpu_types:
|
||||||
|
gpu_types.append('NVIDIA GPU')
|
||||||
|
|
||||||
|
# AMD GPU detection
|
||||||
|
if 'amdgpu' in config_content.lower() or ('dev/dri' in config_content and 'card' in config_content):
|
||||||
|
if 'AMD GPU' not in gpu_types:
|
||||||
|
gpu_types.append('AMD GPU')
|
||||||
|
|
||||||
|
hardware_info['gpu_passthrough'] = gpu_types
|
||||||
|
|
||||||
|
# Detect other hardware devices
|
||||||
|
devices = []
|
||||||
|
|
||||||
|
# Coral TPU detection
|
||||||
|
if 'apex' in config_content.lower() or 'coral' in config_content.lower():
|
||||||
|
devices.append('Coral TPU')
|
||||||
|
|
||||||
|
# USB devices detection
|
||||||
|
if 'ttyUSB' in config_content or 'ttyACM' in config_content:
|
||||||
|
devices.append('USB Serial Devices')
|
||||||
|
|
||||||
|
if '/dev/bus/usb' in config_content:
|
||||||
|
devices.append('USB Passthrough')
|
||||||
|
|
||||||
|
# Framebuffer detection
|
||||||
|
if '/dev/fb0' in config_content:
|
||||||
|
devices.append('Framebuffer')
|
||||||
|
|
||||||
|
# Audio devices detection
|
||||||
|
if '/dev/snd' in config_content:
|
||||||
|
devices.append('Audio Devices')
|
||||||
|
|
||||||
|
# Input devices detection
|
||||||
|
if '/dev/input' in config_content:
|
||||||
|
devices.append('Input Devices')
|
||||||
|
|
||||||
|
# TTY detection
|
||||||
|
if 'tty7' in config_content:
|
||||||
|
devices.append('TTY Console')
|
||||||
|
|
||||||
|
hardware_info['devices'] = devices
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return hardware_info
|
||||||
|
|
||||||
# Helper function to format bytes into human-readable string
|
# Helper function to format bytes into human-readable string
|
||||||
def format_bytes(size_in_bytes):
|
def format_bytes(size_in_bytes):
|
||||||
"""Converts bytes to a human-readable string (KB, MB, GB, TB)."""
|
"""Converts bytes to a human-readable string (KB, MB, GB, TB)."""
|
||||||
@@ -767,7 +854,7 @@ def get_storage_info():
|
|||||||
|
|
||||||
# print(f"[v0] Total storage used: {storage_data['used']}GB (including ZFS pools)")
|
# print(f"[v0] Total storage used: {storage_data['used']}GB (including ZFS pools)")
|
||||||
pass
|
pass
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# print(f"Error getting partition info: {e}")
|
# print(f"Error getting partition info: {e}")
|
||||||
pass
|
pass
|
||||||
@@ -1674,7 +1761,7 @@ def get_network_info():
|
|||||||
bridge_total_count += 1
|
bridge_total_count += 1
|
||||||
if stats.isup:
|
if stats.isup:
|
||||||
bridge_active_count += 1
|
bridge_active_count += 1
|
||||||
|
|
||||||
interface_info = {
|
interface_info = {
|
||||||
'name': interface_name,
|
'name': interface_name,
|
||||||
'type': interface_type,
|
'type': interface_type,
|
||||||
@@ -2407,7 +2494,7 @@ def get_detailed_gpu_info(gpu):
|
|||||||
if 'clients' in json_data:
|
if 'clients' in json_data:
|
||||||
client_count = len(json_data['clients'])
|
client_count = len(json_data['clients'])
|
||||||
|
|
||||||
for client_id, client_data in json_data['clients'].items():
|
for client_id, client_data in json_data['clients']:
|
||||||
client_name = client_data.get('name', 'Unknown')
|
client_name = client_data.get('name', 'Unknown')
|
||||||
client_pid = client_data.get('pid', 'Unknown')
|
client_pid = client_data.get('pid', 'Unknown')
|
||||||
|
|
||||||
@@ -2950,22 +3037,22 @@ def get_detailed_gpu_info(gpu):
|
|||||||
# print(f"[v0] Temperature: {detailed_info['temperature']}°C", flush=True)
|
# print(f"[v0] Temperature: {detailed_info['temperature']}°C", flush=True)
|
||||||
pass
|
pass
|
||||||
data_retrieved = True
|
data_retrieved = True
|
||||||
|
|
||||||
# Parse power draw (GFX Power or average_socket_power)
|
# Parse power draw (GFX Power or average_socket_power)
|
||||||
if 'GFX Power' in sensors:
|
if 'GFX Power' in sensors:
|
||||||
gfx_power = sensors['GFX Power']
|
gfx_power = sensors['GFX Power']
|
||||||
if 'value' in gfx_power:
|
if 'value' in gfx_power:
|
||||||
detailed_info['power_draw'] = f"{gfx_power['value']:.2f} W"
|
detailed_info['power_draw'] = f"{gfx_power['value']:.2f} W"
|
||||||
# print(f"[v0] Power Draw: {detailed_info['power_draw']}", flush=True)
|
# print(f"[v0] Power Draw: {detailed_info['power_draw']}", flush=True)
|
||||||
pass
|
pass
|
||||||
data_retrieved = True
|
data_retrieved = True
|
||||||
elif 'average_socket_power' in sensors:
|
elif 'average_socket_power' in sensors:
|
||||||
socket_power = sensors['average_socket_power']
|
socket_power = sensors['average_socket_power']
|
||||||
if 'value' in socket_power:
|
if 'value' in socket_power:
|
||||||
detailed_info['power_draw'] = f"{socket_power['value']:.2f} W"
|
detailed_info['power_draw'] = f"{socket_power['value']:.2f} W"
|
||||||
# print(f"[v0] Power Draw: {detailed_info['power_draw']}", flush=True)
|
# print(f"[v0] Power Draw: {detailed_info['power_draw']}", flush=True)
|
||||||
pass
|
pass
|
||||||
data_retrieved = True
|
data_retrieved = True
|
||||||
|
|
||||||
# Parse clocks (GFX_SCLK for graphics, GFX_MCLK for memory)
|
# Parse clocks (GFX_SCLK for graphics, GFX_MCLK for memory)
|
||||||
if 'Clocks' in device:
|
if 'Clocks' in device:
|
||||||
@@ -2982,7 +3069,7 @@ def get_detailed_gpu_info(gpu):
|
|||||||
mem_clock = clocks['GFX_MCLK']
|
mem_clock = clocks['GFX_MCLK']
|
||||||
if 'value' in mem_clock:
|
if 'value' in mem_clock:
|
||||||
detailed_info['clock_memory'] = f"{mem_clock['value']} MHz"
|
detailed_info['clock_memory'] = f"{mem_clock['value']} MHz"
|
||||||
# print(f"[v0] Memory Clock: {detailed_info['clock_memory']}", flush=True)
|
# print(f"[v0] Memory Clock: {detailed_info['clock_memory']} MHz", flush=True)
|
||||||
pass
|
pass
|
||||||
data_retrieved = True
|
data_retrieved = True
|
||||||
|
|
||||||
@@ -3219,7 +3306,6 @@ def get_detailed_gpu_info(gpu):
|
|||||||
else:
|
else:
|
||||||
# print(f"[v0] No fdinfo section found in device data", flush=True)
|
# print(f"[v0] No fdinfo section found in device data", flush=True)
|
||||||
pass
|
pass
|
||||||
detailed_info['processes'] = []
|
|
||||||
|
|
||||||
if data_retrieved:
|
if data_retrieved:
|
||||||
detailed_info['has_monitoring_tool'] = True
|
detailed_info['has_monitoring_tool'] = True
|
||||||
@@ -4217,7 +4303,7 @@ def api_network_interface_metrics(interface_name):
|
|||||||
vmid, vm_type = extract_vmid_from_interface(interface_name)
|
vmid, vm_type = extract_vmid_from_interface(interface_name)
|
||||||
if vmid:
|
if vmid:
|
||||||
|
|
||||||
rrd_result = subprocess.run(['pvesh', 'get', f'/nodes/{local_node}/{vm_type}/{vmid}/rrddata',
|
rrd_result = subprocess.run(['pvesh', 'get', f'/nodes/{local_node}/{vm_type}/{vmid}/rrddata',
|
||||||
'--timeframe', timeframe, '--output-format', 'json'],
|
'--timeframe', timeframe, '--output-format', 'json'],
|
||||||
capture_output=True, text=True, timeout=10)
|
capture_output=True, text=True, timeout=10)
|
||||||
|
|
||||||
@@ -5465,6 +5551,10 @@ def api_vm_details(vmid):
|
|||||||
'vm_type': vm_type
|
'vm_type': vm_type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if vm_type == 'lxc':
|
||||||
|
hardware_info = parse_lxc_hardware_config(vmid, node)
|
||||||
|
response_data['hardware_info'] = hardware_info
|
||||||
|
|
||||||
# Add OS info if available
|
# Add OS info if available
|
||||||
if os_info:
|
if os_info:
|
||||||
response_data['os_info'] = os_info
|
response_data['os_info'] = os_info
|
||||||
@@ -5475,7 +5565,6 @@ def api_vm_details(vmid):
|
|||||||
else:
|
else:
|
||||||
return jsonify({'error': 'Failed to get VM details'}), 500
|
return jsonify({'error': 'Failed to get VM details'}), 500
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# print(f"Error getting VM details: {e}")
|
|
||||||
pass
|
pass
|
||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
@@ -5646,4 +5735,4 @@ if __name__ == '__main__':
|
|||||||
# 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")
|
# 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)
|
app.run(host='0.0.0.0', port=8008, debug=False)
|
||||||
|
|||||||
Reference in New Issue
Block a user