From cc0a7941eae13d2ccf5a7c69400d4cbc7cacc637 Mon Sep 17 00:00:00 2001 From: MacRimi Date: Wed, 29 Oct 2025 18:14:09 +0100 Subject: [PATCH] Update AppImage --- AppImage/components/virtual-machines.tsx | 54 ++++++- AppImage/scripts/flask_server.py | 195 ++++++++++++++--------- 2 files changed, 167 insertions(+), 82 deletions(-) diff --git a/AppImage/components/virtual-machines.tsx b/AppImage/components/virtual-machines.tsx index 14efe33..8de5cee 100644 --- a/AppImage/components/virtual-machines.tsx +++ b/AppImage/components/virtual-machines.tsx @@ -124,6 +124,12 @@ interface VMDetails extends VMData { devices?: string[] } lxc_ip?: string + lxc_ip_info?: { + all_ips: string[] + real_ips: string[] + docker_ips: string[] + primary_ip: string + } } const fetcher = async (url: string) => { @@ -158,9 +164,10 @@ const formatUptime = (seconds: number) => { return `${days}d ${hours}h ${minutes}m` } -const extractIPFromConfig = (config?: VMConfig, lxcIP?: string): string => { - if (lxcIP) { - return lxcIP +const extractIPFromConfig = (config?: VMConfig, lxcIPInfo?: VMDetails["lxc_ip_info"]): string => { + // Use primary IP from lxc-info if available + if (lxcIPInfo?.primary_ip) { + return lxcIPInfo.primary_ip } if (!config) return "DHCP" @@ -852,6 +859,12 @@ export function VirtualMachines() { {lxcIP && ( IP: {lxcIP} + {/* Show +X more if there are multiple IPs */} + {vmDetails?.lxc_ip_info && vmDetails.lxc_ip_info.all_ips.length > 1 && ( + + +{vmDetails.lxc_ip_info.all_ips.length - 1} more + + )} )} Uptime: {formatUptime(vm.uptime)} @@ -1250,9 +1263,9 @@ export function VirtualMachines() {
IP Address
- {extractIPFromConfig(vmDetails.config, vmDetails.lxc_ip)} + {extractIPFromConfig(vmDetails.config, vmDetails.lxc_ip_info)}
)} @@ -1784,6 +1797,37 @@ export function VirtualMachines() { )} )} + + {/* IPs Section */} + {selectedVM?.type === "lxc" && vmDetails?.lxc_ip_info && ( +
+

+ IP Addresses +

+
+ {/* Real IPs (green) */} + {vmDetails.lxc_ip_info.real_ips.map((ip, index) => ( + + {ip} (Real) + + ))} + {/* Docker bridge IPs (gray/yellow) */} + {vmDetails.lxc_ip_info.docker_ips.map((ip, index) => ( + + {ip} (Bridge) + + ))} +
+
+ )} diff --git a/AppImage/scripts/flask_server.py b/AppImage/scripts/flask_server.py index d98147a..1370f29 100644 --- a/AppImage/scripts/flask_server.py +++ b/AppImage/scripts/flask_server.py @@ -167,7 +167,8 @@ def parse_lxc_hardware_config(vmid, node): def get_lxc_ip_from_lxc_info(vmid): - """Get LXC IP address using lxc-info command (for DHCP containers)""" + """Get LXC IP addresses using lxc-info command (for DHCP containers) + Returns a dict with all IPs and classification""" try: result = subprocess.run( ['lxc-info', '-n', str(vmid), '-iH'], @@ -176,10 +177,29 @@ def get_lxc_ip_from_lxc_info(vmid): timeout=5 ) if result.returncode == 0: - ip = result.stdout.strip() - # Return IP only if it's valid (not empty) - if ip and ip != '': - return ip + ips_str = result.stdout.strip() + if ips_str and ips_str != '': + # Split multiple IPs (space-separated) + ips = ips_str.split() + + # Classify IPs + real_ips = [] + docker_ips = [] + + for ip in ips: + # Docker bridge IPs typically start with 172. + if ip.startswith('172.'): + docker_ips.append(ip) + else: + # Real network IPs (192.168.x.x, 10.x.x.x, etc.) + real_ips.append(ip) + + return { + 'all_ips': ips, + 'real_ips': real_ips, + 'docker_ips': docker_ips, + 'primary_ip': real_ips[0] if real_ips else (docker_ips[0] if docker_ips else ips[0]) + } return None except Exception: # Silently fail if lxc-info is not available or fails @@ -338,6 +358,8 @@ def get_intel_gpu_processes_from_text(): # We'll estimate utilization based on bar characters engines = {} engine_names = ['Render/3D', 'Blitter', 'Video', 'VideoEnhance'] + bar_section = " ".join(parts[3:-1]) # Extract the bar section dynamically + bar_sections = bar_section.split('||') for idx, engine_name in enumerate(engine_names): @@ -2587,7 +2609,7 @@ def get_detailed_gpu_info(gpu): clients = best_json['clients'] processes = [] - for client_id, client_data in clients.items(): + for client_id, client_data in clients: process_info = { 'name': client_data.get('name', 'Unknown'), 'pid': client_data.get('pid', 'Unknown'), @@ -4366,7 +4388,6 @@ def api_network_interface_metrics(interface_name): return jsonify({'error': str(e)}), 500 - @app.route('/api/vms', methods=['GET']) def api_vms(): """Get virtual machine information""" @@ -5498,78 +5519,100 @@ def api_gpu_realtime(slot): traceback.print_exc() return jsonify({'error': str(e)}), 500 +# CHANGE: Modificar el endpoint para incluir la información completa de IPs @app.route('/api/vms/', methods=['GET']) -def api_vm_details(vmid): - """Get detailed information for a specific VM/LXC""" +def get_vm_config(vmid): + """Get detailed configuration for a specific VM/LXC""" try: - result = subprocess.run(['pvesh', 'get', '/cluster/resources', '--type', 'vm', '--output-format', 'json'], - capture_output=True, text=True, timeout=10) + # Get VM/LXC configuration + node = socket.gethostname() # Get node name + + result = subprocess.run( + ['pvesh', 'get', f'/nodes/{node}/qemu/{vmid}/config', '--output-format', 'json'], + capture_output=True, + text=True, + timeout=10 + ) + + vm_type = 'qemu' + if result.returncode != 0: + # Try LXC + result = subprocess.run( + ['pvesh', 'get', f'/nodes/{node}/lxc/{vmid}/config', '--output-format', 'json'], + capture_output=True, + text=True, + timeout=10 + ) + vm_type = 'lxc' if result.returncode == 0: - resources = json.loads(result.stdout) - for resource in resources: - if resource.get('vmid') == vmid: - vm_type = 'lxc' if resource.get('type') == 'lxc' else 'qemu' - node = resource.get('node', 'pve') - - # Get detailed config - config_result = subprocess.run( - ['pvesh', 'get', f'/nodes/{node}/{vm_type}/{vmid}/config', '--output-format', 'json'], - capture_output=True, text=True, timeout=10) - - config = {} - if config_result.returncode == 0: - config = json.loads(config_result.stdout) - - os_info = {} - if vm_type == 'lxc' and resource.get('status') == 'running': - try: - os_release_result = subprocess.run( - ['pct', 'exec', str(vmid), '--', 'cat', '/etc/os-release'], - capture_output=True, text=True, timeout=5) - - if os_release_result.returncode == 0: - # Parse /etc/os-release content - for line in os_release_result.stdout.split('\n'): - line = line.strip() - if line.startswith('ID='): - os_info['id'] = line.split('=', 1)[1].strip('"').strip("'") - elif line.startswith('VERSION_ID='): - os_info['version_id'] = line.split('=', 1)[1].strip('"').strip("'") - elif line.startswith('NAME='): - os_info['name'] = line.split('=', 1)[1].strip('"').strip("'") - elif line.startswith('PRETTY_NAME='): - os_info['pretty_name'] = line.split('=', 1)[1].strip('"').strip("'") - except Exception as e: - pass # Silently handle errors - - response_data = { - **resource, - 'config': config, - 'node': node, - 'vm_type': vm_type - } - - if vm_type == 'lxc': - hardware_info = parse_lxc_hardware_config(vmid, node) - response_data['hardware_info'] = hardware_info - - if resource.get('status') == 'running': - lxc_ip = get_lxc_ip_from_lxc_info(vmid) - if lxc_ip: - response_data['lxc_ip'] = lxc_ip - - # Add OS info if available - if os_info: - response_data['os_info'] = os_info - - return jsonify(response_data) + config = json.loads(result.stdout) - return jsonify({'error': f'VM/LXC {vmid} not found'}), 404 - else: - return jsonify({'error': 'Failed to get VM details'}), 500 + # Get VM/LXC status to check if it's running + status_result = subprocess.run( + ['pvesh', 'get', f'/nodes/{node}/{vm_type}/{vmid}/status/current', '--output-format', 'json'], + capture_output=True, + text=True, + timeout=10 + ) + + status = 'stopped' + if status_result.returncode == 0: + status_data = json.loads(status_result.stdout) + status = status_data.get('status', 'stopped') + + response_data = { + 'vmid': vmid, + 'config': config, + 'node': node, + 'vm_type': vm_type + } + + # For LXC, try to get IP from lxc-info if running + if vm_type == 'lxc' and status == 'running': + lxc_ip_info = get_lxc_ip_from_lxc_info(vmid) + if lxc_ip_info: + response_data['lxc_ip_info'] = lxc_ip_info + + # Get OS information for LXC + os_info = {} + if vm_type == 'lxc' and status == 'running': + try: + os_release_result = subprocess.run( + ['pct', 'exec', str(vmid), '--', 'cat', '/etc/os-release'], + capture_output=True, text=True, timeout=5) + + if os_release_result.returncode == 0: + for line in os_release_result.stdout.split('\n'): + line = line.strip() + if line.startswith('ID='): + os_info['id'] = line.split('=', 1)[1].strip('"').strip("'") + elif line.startswith('VERSION_ID='): + os_info['version_id'] = line.split('=', 1)[1].strip('"').strip("'") + elif line.startswith('NAME='): + os_info['name'] = line.split('=', 1)[1].strip('"').strip("'") + elif line.startswith('PRETTY_NAME='): + os_info['pretty_name'] = line.split('=', 1)[1].strip('"').strip("'") + except Exception as e: + pass # Silently handle errors + + # Get hardware information for LXC + hardware_info = {} + if vm_type == 'lxc': + hardware_info = parse_lxc_hardware_config(vmid, node) + + # Add OS info and hardware info to response + if os_info: + response_data['os_info'] = os_info + if hardware_info: + response_data['hardware_info'] = hardware_info + + return jsonify(response_data) + + return jsonify({'error': 'VM/LXC not found'}), 404 + except Exception as e: - # print(f"Error getting VM details: {e}") + # print(f"Error getting VM config: {e}") pass return jsonify({'error': str(e)}), 500 @@ -5618,8 +5661,7 @@ def api_vm_logs(vmid): else: return jsonify({'error': 'Failed to get VM logs'}), 500 except Exception as e: - # print(f"Error getting VM logs: {e}") - pass + print(f"Error getting VM logs: {e}") return jsonify({'error': str(e)}), 500 @app.route('/api/vms//control', methods=['POST']) @@ -5670,8 +5712,7 @@ def api_vm_control(vmid): else: return jsonify({'error': 'Failed to get VM details'}), 500 except Exception as e: - # print(f"Error controlling VM: {e}") - pass + print(f"Error controlling VM: {e}") return jsonify({'error': str(e)}), 500 @app.route('/api/vms//config', methods=['PUT'])