diff --git a/AppImage/components/network-metrics.tsx b/AppImage/components/network-metrics.tsx index d800723..88d7dff 100644 --- a/AppImage/components/network-metrics.tsx +++ b/AppImage/components/network-metrics.tsx @@ -368,8 +368,26 @@ export function NetworkMetrics() { {typeBadge.label} {interface_.bridge_physical_interface && ( -
+
→ {interface_.bridge_physical_interface} + {interface_.bridge_physical_interface.startsWith("bond") && + networkData.physical_interfaces && ( + <> + {(() => { + const bondInterface = networkData.physical_interfaces.find( + (iface) => iface.name === interface_.bridge_physical_interface, + ) + if (bondInterface?.bond_slaves && bondInterface.bond_slaves.length > 0) { + return ( + + ({bondInterface.bond_slaves.join(", ")}) + + ) + } + return null + })()} + + )}
)}
@@ -477,12 +495,12 @@ export function NetworkMetrics() { {/* Second row: Details - Responsive layout */}
-
VMID
+
VMID
{interface_.vmid ?? "N/A"}
-
Speed
+
Speed
{formatSpeed(interface_.speed)} @@ -490,7 +508,7 @@ export function NetworkMetrics() {
-
Traffic
+
Traffic
↓ {formatBytes(interface_.bytes_recv)} {" / "} @@ -500,7 +518,7 @@ export function NetworkMetrics() { {interface_.mac_address && (
-
MAC
+
MAC
{interface_.mac_address}
diff --git a/AppImage/components/virtual-machines.tsx b/AppImage/components/virtual-machines.tsx index 1ad5ee5..3689e86 100644 --- a/AppImage/components/virtual-machines.tsx +++ b/AppImage/components/virtual-machines.tsx @@ -6,7 +6,6 @@ import { Badge } from "./ui/badge" import { Progress } from "./ui/progress" import { Button } from "./ui/button" import { Dialog, DialogContent, DialogHeader, DialogTitle } from "./ui/dialog" -import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs" import { Server, Play, @@ -427,48 +426,35 @@ export function VirtualMachines() { setVMDetails(null) }} > - - + + - {selectedVM?.name} - Details + {selectedVM?.name} + {selectedVM && ( +
+ + {getTypeBadge(selectedVM.type).label} + + + {selectedVM.status.toUpperCase()} + +
+ )}
- {selectedVM && ( - - - Basic - Resources - Network - Options - - - {/* Basic Information Tab */} - +
+ {selectedVM && ( + <> + {/* Basic Information */}

Basic Information

-
-
-
Name
-
{selectedVM.name}
-
-
-
Type
- - {getTypeBadge(selectedVM.type).label} - -
+
VMID
{selectedVM.vmid}
-
-
Status
- - {selectedVM.status.toUpperCase()} - -
CPU Usage
{(selectedVM.cpu * 100).toFixed(1)}%
@@ -492,6 +478,169 @@ export function VirtualMachines() {
+ {/* Resources Configuration */} + {detailsLoading ? ( +
Loading configuration...
+ ) : vmDetails?.config ? ( + <> +
+

Resources

+
+ {vmDetails.config.cores && ( +
+
CPU Cores
+
{vmDetails.config.cores}
+
+ )} + {vmDetails.config.sockets && ( +
+
CPU Sockets
+
{vmDetails.config.sockets}
+
+ )} + {vmDetails.config.memory && ( +
+
Memory
+
{vmDetails.config.memory} MB
+
+ )} + {vmDetails.config.swap && ( +
+
Swap
+
{vmDetails.config.swap} MB
+
+ )} + {vmDetails.config.rootfs && ( +
+
Root Filesystem
+
+ {vmDetails.config.rootfs} +
+
+ )} + {vmDetails.config.scsi0 && ( +
+
SCSI Disk 0
+
+ {vmDetails.config.scsi0} +
+
+ )} + {vmDetails.config.ide0 && ( +
+
IDE Disk 0
+
{vmDetails.config.ide0}
+
+ )} +
+
+ + {/* Network Configuration */} +
+

Network

+
+ {vmDetails.config.net0 && ( +
+
Network Interface 0
+
{vmDetails.config.net0}
+
+ )} + {vmDetails.config.net1 && ( +
+
Network Interface 1
+
{vmDetails.config.net1}
+
+ )} + {vmDetails.config.net2 && ( +
+
Network Interface 2
+
{vmDetails.config.net2}
+
+ )} + {vmDetails.config.nameserver && ( +
+
DNS Nameserver
+
{vmDetails.config.nameserver}
+
+ )} + {vmDetails.config.searchdomain && ( +
+
Search Domain
+
{vmDetails.config.searchdomain}
+
+ )} + {vmDetails.config.hostname && ( +
+
Hostname
+
{vmDetails.config.hostname}
+
+ )} +
+
+ + {/* Options */} +
+

Options

+
+ {vmDetails.config.onboot !== undefined && ( +
+
Start on Boot
+ + {vmDetails.config.onboot ? "Yes" : "No"} + +
+ )} + {vmDetails.config.unprivileged !== undefined && ( +
+
Unprivileged
+ + {vmDetails.config.unprivileged ? "Yes" : "No"} + +
+ )} + {vmDetails.config.ostype && ( +
+
OS Type
+
{vmDetails.config.ostype}
+
+ )} + {vmDetails.config.arch && ( +
+
Architecture
+
{vmDetails.config.arch}
+
+ )} + {vmDetails.config.boot && ( +
+
Boot Order
+
{vmDetails.config.boot}
+
+ )} + {vmDetails.config.features && ( +
+
Features
+
{vmDetails.config.features}
+
+ )} +
+
+ + ) : null} + {/* Control Actions */}

Control Actions

@@ -546,181 +695,9 @@ export function VirtualMachines() { Download Logs
- - - {/* Resources Tab */} - - {detailsLoading ? ( -
Loading configuration...
- ) : vmDetails?.config ? ( -
-
- {vmDetails.config.cores && ( -
-
CPU Cores
-
{vmDetails.config.cores}
-
- )} - {vmDetails.config.sockets && ( -
-
CPU Sockets
-
{vmDetails.config.sockets}
-
- )} - {vmDetails.config.memory && ( -
-
Memory
-
{vmDetails.config.memory} MB
-
- )} - {vmDetails.config.swap && ( -
-
Swap
-
{vmDetails.config.swap} MB
-
- )} - {vmDetails.config.rootfs && ( -
-
Root Filesystem
-
{vmDetails.config.rootfs}
-
- )} - {vmDetails.config.scsi0 && ( -
-
SCSI Disk 0
-
{vmDetails.config.scsi0}
-
- )} - {vmDetails.config.ide0 && ( -
-
IDE Disk 0
-
{vmDetails.config.ide0}
-
- )} -
-
- ) : ( -
No configuration data available
- )} -
- - {/* Network Tab */} - - {detailsLoading ? ( -
Loading configuration...
- ) : vmDetails?.config ? ( -
-
- {vmDetails.config.net0 && ( -
-
Network Interface 0
-
{vmDetails.config.net0}
-
- )} - {vmDetails.config.net1 && ( -
-
Network Interface 1
-
{vmDetails.config.net1}
-
- )} - {vmDetails.config.net2 && ( -
-
Network Interface 2
-
{vmDetails.config.net2}
-
- )} - {vmDetails.config.nameserver && ( -
-
DNS Nameserver
-
{vmDetails.config.nameserver}
-
- )} - {vmDetails.config.searchdomain && ( -
-
Search Domain
-
{vmDetails.config.searchdomain}
-
- )} - {vmDetails.config.hostname && ( -
-
Hostname
-
{vmDetails.config.hostname}
-
- )} -
-
- ) : ( -
No network configuration available
- )} -
- - {/* Options Tab */} - - {detailsLoading ? ( -
Loading configuration...
- ) : vmDetails?.config ? ( -
-
- {vmDetails.config.onboot !== undefined && ( -
-
Start on Boot
- - {vmDetails.config.onboot ? "Yes" : "No"} - -
- )} - {vmDetails.config.unprivileged !== undefined && ( -
-
Unprivileged
- - {vmDetails.config.unprivileged ? "Yes" : "No"} - -
- )} - {vmDetails.config.ostype && ( -
-
OS Type
-
{vmDetails.config.ostype}
-
- )} - {vmDetails.config.arch && ( -
-
Architecture
-
{vmDetails.config.arch}
-
- )} - {vmDetails.config.boot && ( -
-
Boot Order
-
{vmDetails.config.boot}
-
- )} - {vmDetails.config.features && ( -
-
Features
-
{vmDetails.config.features}
-
- )} -
-
- ) : ( -
No options available
- )} -
- - )} + + )} +
diff --git a/AppImage/scripts/flask_server.py b/AppImage/scripts/flask_server.py index 75fe45c..3116710 100644 --- a/AppImage/scripts/flask_server.py +++ b/AppImage/scripts/flask_server.py @@ -1489,23 +1489,16 @@ def api_vm_logs(vmid): # Get real logs from the container/VM (last 1000 lines) log_result = subprocess.run( - ['pvesh', 'get', f'/nodes/{node}/{vm_type}/{vmid}/log', '--limit', '1000', '--output-format', 'json'], + ['pvesh', 'get', f'/nodes/{node}/{vm_type}/{vmid}/log', '--start', '0', '--limit', '1000'], capture_output=True, text=True, timeout=10 ) logs = [] if log_result.returncode == 0: - try: - log_data = json.loads(log_result.stdout) - # The API returns an array of log line objects - if isinstance(log_data, list): - logs = log_data - else: - # Fallback: parse as text - logs = [{'n': i, 't': line} for i, line in enumerate(log_result.stdout.split('\n')) if line] - except json.JSONDecodeError: - # Parse as plain text if JSON fails - logs = [{'n': i, 't': line} for i, line in enumerate(log_result.stdout.split('\n')) if line] + # Parse as plain text (each line is a log entry) + for i, line in enumerate(log_result.stdout.split('\n')): + if line.strip(): + logs.append({'n': i, 't': line}) return jsonify({ 'vmid': vmid,