From e9851da12fbd813fa6dc05aafb03c07013971971 Mon Sep 17 00:00:00 2001 From: MacRimi Date: Sun, 5 Apr 2026 11:51:26 +0200 Subject: [PATCH] update virtual-machines.tsx --- AppImage/components/virtual-machines.tsx | 3 +- AppImage/scripts/flask_notification_routes.py | 2 +- AppImage/scripts/flask_server.py | 18 ++---- AppImage/scripts/health_monitor.py | 59 +++++++++++++++---- AppImage/scripts/notification_channels.py | 2 +- AppImage/scripts/test_real_events.sh | 4 +- 6 files changed, 59 insertions(+), 29 deletions(-) diff --git a/AppImage/components/virtual-machines.tsx b/AppImage/components/virtual-machines.tsx index bffd8fae..926c7236 100644 --- a/AppImage/components/virtual-machines.tsx +++ b/AppImage/components/virtual-machines.tsx @@ -621,7 +621,8 @@ const handleDownloadLogs = async (vmid: number, vmName: string) => { } } - const safeVMData = vmData || [] + // Ensure vmData is always an array (backend may return object on error) + const safeVMData = Array.isArray(vmData) ? vmData : [] // Total allocated RAM for ALL VMs/LXCs (running + stopped) const totalAllocatedMemoryGB = useMemo(() => { diff --git a/AppImage/scripts/flask_notification_routes.py b/AppImage/scripts/flask_notification_routes.py index 6b73c482..7c3294b7 100644 --- a/AppImage/scripts/flask_notification_routes.py +++ b/AppImage/scripts/flask_notification_routes.py @@ -953,7 +953,7 @@ def proxmox_webhook(): return jsonify({'accepted': False, 'error': 'internal_error', 'detail': str(e)}), 200 -# ─── Internal Shutdown Event Endpoint ────────────���──────────────── +# ─── Internal Shutdown Event Endpoint ───────────────────────────── @notification_bp.route('/api/internal/shutdown-event', methods=['POST']) def internal_shutdown_event(): diff --git a/AppImage/scripts/flask_server.py b/AppImage/scripts/flask_server.py index 3bcce177..2279827e 100644 --- a/AppImage/scripts/flask_server.py +++ b/AppImage/scripts/flask_server.py @@ -3534,24 +3534,18 @@ def get_proxmox_vms(): return all_vms else: - return { - 'error': 'pvesh command not available or failed', - 'vms': [] - } + # Return empty array instead of error object - frontend expects array + return [] except Exception as e: # print(f"[v0] Error getting VM/LXC info: {e}") pass - return { - 'error': 'Unable to access VM information: {str(e)}', - 'vms': [] - } + # Return empty array instead of error object - frontend expects array + return [] except Exception as e: # print(f"Error getting VM info: {e}") pass - return { - 'error': f'Unable to access VM information: {str(e)}', - 'vms': [] - } + # Return empty array instead of error object - frontend expects array + return [] def get_ipmi_fans(): """Get fan information from IPMI""" diff --git a/AppImage/scripts/health_monitor.py b/AppImage/scripts/health_monitor.py index f97f574e..39bb1f18 100644 --- a/AppImage/scripts/health_monitor.py +++ b/AppImage/scripts/health_monitor.py @@ -2618,6 +2618,28 @@ class HealthMonitor: return True return False + def _is_vm_running(self, vmid: str) -> bool: + """Check if a VM or CT is currently running.""" + import subprocess + try: + # Check VM status + result = subprocess.run( + ['qm', 'status', vmid], + capture_output=True, text=True, timeout=2 + ) + if result.returncode == 0 and 'running' in result.stdout.lower(): + return True + # Check CT status + result = subprocess.run( + ['pct', 'status', vmid], + capture_output=True, text=True, timeout=2 + ) + if result.returncode == 0 and 'running' in result.stdout.lower(): + return True + except Exception: + pass + return False + def _check_vms_cts_optimized(self) -> Dict[str, Any]: """ Optimized VM/CT check - detects qmp failures and startup errors from logs. @@ -2658,6 +2680,12 @@ class HealthMonitor: # Skip if VM no longer exists (stale journal entry) if not self._vm_ct_exists(vmid): continue + # Skip if VM is now running - the QMP error is stale/resolved + # This prevents re-detecting old journal entries after VM recovery + if self._is_vm_running(vmid): + # Auto-resolve any existing error for this VM + health_persistence.check_vm_running(vmid) + continue vm_name = self._resolve_vm_name(vmid) display = f"VM {vmid} ({vm_name})" if vm_name else f"VM {vmid}" key = f'vm_{vmid}' @@ -2835,18 +2863,25 @@ class HealthMonitor: for line in journalctl_output.split('\n'): line_lower = line.lower() - # VM QMP errors (skip during active backup -- normal behavior) - vm_qmp_match = re.search(r'vm\s+(\d+)\s+qmp\s+command.*(?:failed|unable|timeout)', line_lower) - if vm_qmp_match: - if _vzdump_running: - continue # Normal during backup - vmid = vm_qmp_match.group(1) - - # Skip if VM no longer exists (deleted after error occurred) - if not self._vm_ct_exists(vmid): - continue - - vm_name = self._resolve_vm_name(vmid) + # VM QMP errors (skip during active backup -- normal behavior) + vm_qmp_match = re.search(r'vm\s+(\d+)\s+qmp\s+command.*(?:failed|unable|timeout)', line_lower) + if vm_qmp_match: + if _vzdump_running: + continue # Normal during backup + vmid = vm_qmp_match.group(1) + + # Skip if VM no longer exists (deleted after error occurred) + if not self._vm_ct_exists(vmid): + continue + + # Skip if VM is now running - the QMP error is stale/resolved + # This prevents re-detecting old journal entries after VM recovery + if self._is_vm_running(vmid): + # Auto-resolve any existing error for this VM + health_persistence.check_vm_running(vmid) + continue + + vm_name = self._resolve_vm_name(vmid) display = f"VM {vmid} ({vm_name})" if vm_name else f"VM {vmid}" error_key = f'vm_{vmid}' if error_key not in vm_details: diff --git a/AppImage/scripts/notification_channels.py b/AppImage/scripts/notification_channels.py index 03acc70f..53ff059d 100644 --- a/AppImage/scripts/notification_channels.py +++ b/AppImage/scripts/notification_channels.py @@ -620,7 +620,7 @@ class EmailChannel(NotificationChannel): {value} ''' - # ── Reason / details block (long text, displayed separately) ���─ + # ── Reason / details block (long text, displayed separately) ── reason = data.get('reason', '') reason_html = '' if reason and len(reason) > 80: diff --git a/AppImage/scripts/test_real_events.sh b/AppImage/scripts/test_real_events.sh index e01bad35..b9b9a49e 100644 --- a/AppImage/scripts/test_real_events.sh +++ b/AppImage/scripts/test_real_events.sh @@ -557,7 +557,7 @@ test_vmct() { test_system() { header "SYSTEM EVENT TESTS (syslog injection)" - # ── Test S1: Authentication failures ── + # ��─ Test S1: Authentication failures ── log "" log "${BOLD} Test S1: SSH auth failure injection${NC}" info "Injecting SSH auth failure messages into syslog." @@ -684,7 +684,7 @@ show_menu() { echo -ne " Select: " } -# ── Main ──────���───────────────────────────────────────────────── +# ── Main ──────────────────────────────────────────────────────── main() { local mode="${1:-menu}"