From fa64b51d4a03cbd09ca53538fee4d0aa71454e52 Mon Sep 17 00:00:00 2001 From: MacRimi Date: Sun, 5 Oct 2025 12:03:47 +0200 Subject: [PATCH] Update AppImage --- AppImage/components/virtual-machines.tsx | 82 ++++++++++++++++++------ AppImage/scripts/flask_server.py | 75 ++++++++++++++++------ 2 files changed, 118 insertions(+), 39 deletions(-) diff --git a/AppImage/components/virtual-machines.tsx b/AppImage/components/virtual-machines.tsx index 6bf1df1..d44aac5 100644 --- a/AppImage/components/virtual-machines.tsx +++ b/AppImage/components/virtual-machines.tsx @@ -4,18 +4,23 @@ import { useState, useEffect } from "react" import { Card, CardContent, CardHeader, CardTitle } from "./ui/card" import { Badge } from "./ui/badge" import { Progress } from "./ui/progress" -import { Server, Play, Square, Monitor, Cpu, MemoryStick, AlertCircle } from "lucide-react" +import { Server, Play, Square, Monitor, Cpu, MemoryStick, AlertCircle, HardDrive, Network } from "lucide-react" interface VMData { vmid: number name: string status: string + type: string // Added type field to distinguish VM from LXC cpu: number mem: number maxmem: number disk: number maxdisk: number uptime: number + netin?: number // Added network in + netout?: number // Added network out + diskread?: number // Added disk read + diskwrite?: number // Added disk write } const fetchVMData = async (): Promise => { @@ -42,6 +47,14 @@ const fetchVMData = async (): Promise => { } } +const formatBytes = (bytes: number | undefined): string => { + if (!bytes || bytes === 0) return "0 B" + const k = 1024 + const sizes = ["B", "KB", "MB", "GB", "TB"] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}` +} + export function VirtualMachines() { const [vmData, setVmData] = useState([]) const [loading, setLoading] = useState(true) @@ -123,6 +136,13 @@ export function VirtualMachines() { } } + const getTypeBadge = (type: string) => { + if (type === "lxc") { + return { color: "bg-cyan-500/10 text-cyan-500 border-cyan-500/20", label: "LXC" } + } + return { color: "bg-purple-500/10 text-purple-500 border-purple-500/20", label: "VM" } + } + const formatUptime = (seconds: number) => { const days = Math.floor(seconds / 86400) const hours = Math.floor((seconds % 86400) / 3600) @@ -159,7 +179,7 @@ export function VirtualMachines() { -
{(totalCPU * 100).toFixed(0)}%
+
{(totalCPU * 100).toFixed(0)}%

Allocated CPU usage

@@ -170,7 +190,7 @@ export function VirtualMachines() { -
{(totalMemory / 1024 ** 3).toFixed(1)} GB
+
{(totalMemory / 1024 ** 3).toFixed(1)} GB

Allocated RAM

@@ -181,7 +201,7 @@ export function VirtualMachines() { -
+
{runningVMs > 0 ? ((totalCPU / runningVMs) * 100).toFixed(0) : 0}%

Average resource utilization

@@ -194,7 +214,7 @@ export function VirtualMachines() { - Virtual Machines + Virtual Machines & Containers @@ -207,6 +227,7 @@ export function VirtualMachines() { const memPercent = vm.maxmem > 0 ? ((vm.mem / vm.maxmem) * 100).toFixed(1) : "0" const memGB = (vm.mem / 1024 ** 3).toFixed(1) const maxMemGB = (vm.maxmem / 1024 ** 3).toFixed(1) + const typeBadge = getTypeBadge(vm.type) return (
@@ -216,11 +237,8 @@ export function VirtualMachines() {
{vm.name} - - VM + + {typeBadge.label}
ID: {vm.vmid}
@@ -235,25 +253,53 @@ export function VirtualMachines() {
-
+
CPU Usage
-
{cpuPercent}%
- +
{cpuPercent}%
+
Memory Usage
-
- {memGB} GB / {maxMemGB} GB +
+ {memGB} / {maxMemGB} GB
- +
-
Uptime
-
{formatUptime(vm.uptime)}
+
Disk I/O
+
+
+ + ↓ {formatBytes(vm.diskread)} +
+
+ + ↑ {formatBytes(vm.diskwrite)} +
+
+ +
+
Network I/O
+
+
+ + ↓ {formatBytes(vm.netin)} +
+
+ + ↑ {formatBytes(vm.netout)} +
+
+
+
+ +
+
Uptime
+
{formatUptime(vm.uptime)}
) diff --git a/AppImage/scripts/flask_server.py b/AppImage/scripts/flask_server.py index 52eef08..780ead4 100644 --- a/AppImage/scripts/flask_server.py +++ b/AppImage/scripts/flask_server.py @@ -986,7 +986,8 @@ def get_bridge_info(bridge_name): """Get detailed information about a bridge interface""" bridge_info = { 'members': [], - 'physical_interface': None + 'physical_interface': None, + 'physical_duplex': 'unknown' # Added physical_duplex field } try: @@ -1000,6 +1001,17 @@ def get_bridge_info(bridge_name): if member.startswith(('enp', 'eth', 'eno', 'ens', 'wlan', 'wlp')): bridge_info['physical_interface'] = member print(f"[v0] Bridge {bridge_name} physical interface: {member}") + + # Get duplex from physical interface + try: + net_if_stats = psutil.net_if_stats() + if member in net_if_stats: + stats = net_if_stats[member] + bridge_info['physical_duplex'] = 'full' if stats.duplex == 2 else 'half' if stats.duplex == 1 else 'unknown' + print(f"[v0] Physical interface {member} duplex: {bridge_info['physical_duplex']}") + except Exception as e: + print(f"[v0] Error getting duplex for {member}: {e}") + break print(f"[v0] Bridge {bridge_name} members: {members}") @@ -1129,6 +1141,10 @@ def get_network_info(): bridge_info = get_bridge_info(interface_name) interface_info['bridge_members'] = bridge_info['members'] interface_info['bridge_physical_interface'] = bridge_info['physical_interface'] + interface_info['bridge_physical_duplex'] = bridge_info['physical_duplex'] + # Override bridge duplex with physical interface duplex + if bridge_info['physical_duplex'] != 'unknown': + interface_info['duplex'] = bridge_info['physical_duplex'] if interface_type == 'vm_lxc': network_data['vm_lxc_interfaces'].append(interface_info) @@ -1204,32 +1220,49 @@ def get_network_info(): } def get_proxmox_vms(): - """Get Proxmox VM information (requires pvesh command)""" + """Get Proxmox VM and LXC information (requires pvesh command)""" try: - # Try to get VM list using pvesh command - result = subprocess.run(['pvesh', 'get', '/nodes/localhost/qemu', '--output-format', 'json'], - capture_output=True, text=True, timeout=10) + all_vms = [] - if result.returncode == 0: - vms = json.loads(result.stdout) - return vms - else: - # Handle LXC containers as well - result_lxc = subprocess.run(['pvesh', 'get', '/nodes/localhost/lxc', '--output-format', 'json'], - capture_output=True, text=True, timeout=10) - if result_lxc.returncode == 0: - lxc_vms = json.loads(result_lxc.stdout) - # Combine QEMU and LXC for a complete VM list - if 'vms' in locals(): # Check if vms were loaded from QEMU - vms.extend(lxc_vms) - else: - vms = lxc_vms - return vms + try: + result = subprocess.run(['pvesh', 'get', '/cluster/resources', '--type', 'vm', '--output-format', 'json'], + capture_output=True, text=True, timeout=10) + + if result.returncode == 0: + resources = json.loads(result.stdout) + for resource in resources: + vm_data = { + 'vmid': resource.get('vmid'), + 'name': resource.get('name', f"VM-{resource.get('vmid')}"), + 'status': resource.get('status', 'unknown'), + 'type': 'lxc' if resource.get('type') == 'lxc' else 'qemu', + 'cpu': resource.get('cpu', 0), + 'mem': resource.get('mem', 0), + 'maxmem': resource.get('maxmem', 0), + 'disk': resource.get('disk', 0), + 'maxdisk': resource.get('maxdisk', 0), + 'uptime': resource.get('uptime', 0), + 'netin': resource.get('netin', 0), + 'netout': resource.get('netout', 0), + 'diskread': resource.get('diskread', 0), + 'diskwrite': resource.get('diskwrite', 0) + } + all_vms.append(vm_data) + print(f"[v0] Found {vm_data['type']}: {vm_data['name']} (VMID: {vm_data['vmid']}, Status: {vm_data['status']})") + + return all_vms else: + print(f"[v0] pvesh command failed: {result.stderr}") return { - 'error': 'pvesh command not available or failed - Proxmox API not accessible for QEMU and LXC', + 'error': 'pvesh command not available or failed', 'vms': [] } + except Exception as e: + print(f"[v0] Error getting VM/LXC info: {e}") + return { + 'error': f'Unable to access VM information: {str(e)}', + 'vms': [] + } except Exception as e: print(f"Error getting VM info: {e}") return {