mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2025-11-18 03:26:17 +00:00
Update AppImage
This commit is contained in:
@@ -124,6 +124,12 @@ interface VMDetails extends VMData {
|
|||||||
devices?: string[]
|
devices?: string[]
|
||||||
}
|
}
|
||||||
lxc_ip?: string
|
lxc_ip?: string
|
||||||
|
lxc_ip_info?: {
|
||||||
|
all_ips: string[]
|
||||||
|
real_ips: string[]
|
||||||
|
docker_ips: string[]
|
||||||
|
primary_ip: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetcher = async (url: string) => {
|
const fetcher = async (url: string) => {
|
||||||
@@ -158,9 +164,10 @@ const formatUptime = (seconds: number) => {
|
|||||||
return `${days}d ${hours}h ${minutes}m`
|
return `${days}d ${hours}h ${minutes}m`
|
||||||
}
|
}
|
||||||
|
|
||||||
const extractIPFromConfig = (config?: VMConfig, lxcIP?: string): string => {
|
const extractIPFromConfig = (config?: VMConfig, lxcIPInfo?: VMDetails["lxc_ip_info"]): string => {
|
||||||
if (lxcIP) {
|
// Use primary IP from lxc-info if available
|
||||||
return lxcIP
|
if (lxcIPInfo?.primary_ip) {
|
||||||
|
return lxcIPInfo.primary_ip
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config) return "DHCP"
|
if (!config) return "DHCP"
|
||||||
@@ -852,6 +859,12 @@ export function VirtualMachines() {
|
|||||||
{lxcIP && (
|
{lxcIP && (
|
||||||
<span className={`text-sm ${lxcIP === "DHCP" ? "text-yellow-500" : "text-green-500"}`}>
|
<span className={`text-sm ${lxcIP === "DHCP" ? "text-yellow-500" : "text-green-500"}`}>
|
||||||
IP: {lxcIP}
|
IP: {lxcIP}
|
||||||
|
{/* Show +X more if there are multiple IPs */}
|
||||||
|
{vmDetails?.lxc_ip_info && vmDetails.lxc_ip_info.all_ips.length > 1 && (
|
||||||
|
<span className="text-muted-foreground ml-1">
|
||||||
|
+{vmDetails.lxc_ip_info.all_ips.length - 1} more
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className="text-sm text-muted-foreground ml-auto">Uptime: {formatUptime(vm.uptime)}</span>
|
<span className="text-sm text-muted-foreground ml-auto">Uptime: {formatUptime(vm.uptime)}</span>
|
||||||
@@ -1250,9 +1263,9 @@ export function VirtualMachines() {
|
|||||||
<div>
|
<div>
|
||||||
<div className="text-xs text-muted-foreground mb-1">IP Address</div>
|
<div className="text-xs text-muted-foreground mb-1">IP Address</div>
|
||||||
<div
|
<div
|
||||||
className={`font-semibold ${extractIPFromConfig(vmDetails.config, vmDetails.lxc_ip) === "DHCP" ? "text-yellow-500" : "text-green-500"}`}
|
className={`font-semibold ${extractIPFromConfig(vmDetails.config, vmDetails.lxc_ip_info) === "DHCP" ? "text-yellow-500" : "text-green-500"}`}
|
||||||
>
|
>
|
||||||
{extractIPFromConfig(vmDetails.config, vmDetails.lxc_ip)}
|
{extractIPFromConfig(vmDetails.config, vmDetails.lxc_ip_info)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -1784,6 +1797,37 @@ export function VirtualMachines() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* IPs Section */}
|
||||||
|
{selectedVM?.type === "lxc" && vmDetails?.lxc_ip_info && (
|
||||||
|
<div>
|
||||||
|
<h4 className="text-sm font-semibold text-muted-foreground mb-3 uppercase tracking-wide">
|
||||||
|
IP Addresses
|
||||||
|
</h4>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{/* Real IPs (green) */}
|
||||||
|
{vmDetails.lxc_ip_info.real_ips.map((ip, index) => (
|
||||||
|
<Badge
|
||||||
|
key={`real-${index}`}
|
||||||
|
variant="outline"
|
||||||
|
className="bg-green-500/10 text-green-500 border-green-500/20"
|
||||||
|
>
|
||||||
|
{ip} (Real)
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
{/* Docker bridge IPs (gray/yellow) */}
|
||||||
|
{vmDetails.lxc_ip_info.docker_ips.map((ip, index) => (
|
||||||
|
<Badge
|
||||||
|
key={`docker-${index}`}
|
||||||
|
variant="outline"
|
||||||
|
className="bg-yellow-500/10 text-yellow-500 border-yellow-500/20"
|
||||||
|
>
|
||||||
|
{ip} (Bridge)
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -167,7 +167,8 @@ def parse_lxc_hardware_config(vmid, node):
|
|||||||
|
|
||||||
|
|
||||||
def get_lxc_ip_from_lxc_info(vmid):
|
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:
|
try:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
['lxc-info', '-n', str(vmid), '-iH'],
|
['lxc-info', '-n', str(vmid), '-iH'],
|
||||||
@@ -176,10 +177,29 @@ def get_lxc_ip_from_lxc_info(vmid):
|
|||||||
timeout=5
|
timeout=5
|
||||||
)
|
)
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
ip = result.stdout.strip()
|
ips_str = result.stdout.strip()
|
||||||
# Return IP only if it's valid (not empty)
|
if ips_str and ips_str != '':
|
||||||
if ip and ip != '':
|
# Split multiple IPs (space-separated)
|
||||||
return ip
|
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
|
return None
|
||||||
except Exception:
|
except Exception:
|
||||||
# Silently fail if lxc-info is not available or fails
|
# 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
|
# We'll estimate utilization based on bar characters
|
||||||
engines = {}
|
engines = {}
|
||||||
engine_names = ['Render/3D', 'Blitter', 'Video', 'VideoEnhance']
|
engine_names = ['Render/3D', 'Blitter', 'Video', 'VideoEnhance']
|
||||||
|
bar_section = " ".join(parts[3:-1]) # Extract the bar section dynamically
|
||||||
|
|
||||||
bar_sections = bar_section.split('||')
|
bar_sections = bar_section.split('||')
|
||||||
|
|
||||||
for idx, engine_name in enumerate(engine_names):
|
for idx, engine_name in enumerate(engine_names):
|
||||||
@@ -2587,7 +2609,7 @@ def get_detailed_gpu_info(gpu):
|
|||||||
clients = best_json['clients']
|
clients = best_json['clients']
|
||||||
processes = []
|
processes = []
|
||||||
|
|
||||||
for client_id, client_data in clients.items():
|
for client_id, client_data in clients:
|
||||||
process_info = {
|
process_info = {
|
||||||
'name': client_data.get('name', 'Unknown'),
|
'name': client_data.get('name', 'Unknown'),
|
||||||
'pid': client_data.get('pid', 'Unknown'),
|
'pid': client_data.get('pid', 'Unknown'),
|
||||||
@@ -4366,7 +4388,6 @@ def api_network_interface_metrics(interface_name):
|
|||||||
|
|
||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/vms', methods=['GET'])
|
@app.route('/api/vms', methods=['GET'])
|
||||||
def api_vms():
|
def api_vms():
|
||||||
"""Get virtual machine information"""
|
"""Get virtual machine information"""
|
||||||
@@ -5498,78 +5519,100 @@ def api_gpu_realtime(slot):
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
# CHANGE: Modificar el endpoint para incluir la información completa de IPs
|
||||||
@app.route('/api/vms/<int:vmid>', methods=['GET'])
|
@app.route('/api/vms/<int:vmid>', methods=['GET'])
|
||||||
def api_vm_details(vmid):
|
def get_vm_config(vmid):
|
||||||
"""Get detailed information for a specific VM/LXC"""
|
"""Get detailed configuration for a specific VM/LXC"""
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(['pvesh', 'get', '/cluster/resources', '--type', 'vm', '--output-format', 'json'],
|
# Get VM/LXC configuration
|
||||||
capture_output=True, text=True, timeout=10)
|
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:
|
if result.returncode == 0:
|
||||||
resources = json.loads(result.stdout)
|
config = 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)
|
|
||||||
|
|
||||||
return jsonify({'error': f'VM/LXC {vmid} not found'}), 404
|
# Get VM/LXC status to check if it's running
|
||||||
else:
|
status_result = subprocess.run(
|
||||||
return jsonify({'error': 'Failed to get VM details'}), 500
|
['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:
|
except Exception as e:
|
||||||
# print(f"Error getting VM details: {e}")
|
# print(f"Error getting VM config: {e}")
|
||||||
pass
|
pass
|
||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
@@ -5618,8 +5661,7 @@ def api_vm_logs(vmid):
|
|||||||
else:
|
else:
|
||||||
return jsonify({'error': 'Failed to get VM logs'}), 500
|
return jsonify({'error': 'Failed to get VM logs'}), 500
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# print(f"Error getting VM logs: {e}")
|
print(f"Error getting VM logs: {e}")
|
||||||
pass
|
|
||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
@app.route('/api/vms/<int:vmid>/control', methods=['POST'])
|
@app.route('/api/vms/<int:vmid>/control', methods=['POST'])
|
||||||
@@ -5670,8 +5712,7 @@ def api_vm_control(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 controlling VM: {e}")
|
print(f"Error controlling VM: {e}")
|
||||||
pass
|
|
||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
@app.route('/api/vms/<int:vmid>/config', methods=['PUT'])
|
@app.route('/api/vms/<int:vmid>/config', methods=['PUT'])
|
||||||
|
|||||||
Reference in New Issue
Block a user