mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-04-18 10:02:16 +00:00
update virtual-machines.tsx
This commit is contained in:
@@ -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)
|
// Total allocated RAM for ALL VMs/LXCs (running + stopped)
|
||||||
const totalAllocatedMemoryGB = useMemo(() => {
|
const totalAllocatedMemoryGB = useMemo(() => {
|
||||||
|
|||||||
@@ -953,7 +953,7 @@ def proxmox_webhook():
|
|||||||
return jsonify({'accepted': False, 'error': 'internal_error', 'detail': str(e)}), 200
|
return jsonify({'accepted': False, 'error': 'internal_error', 'detail': str(e)}), 200
|
||||||
|
|
||||||
|
|
||||||
# ─── Internal Shutdown Event Endpoint ────────────<EFBFBD><EFBFBD><EFBFBD>────────────────
|
# ─── Internal Shutdown Event Endpoint ─────────────────────────────
|
||||||
|
|
||||||
@notification_bp.route('/api/internal/shutdown-event', methods=['POST'])
|
@notification_bp.route('/api/internal/shutdown-event', methods=['POST'])
|
||||||
def internal_shutdown_event():
|
def internal_shutdown_event():
|
||||||
|
|||||||
@@ -3534,24 +3534,18 @@ def get_proxmox_vms():
|
|||||||
|
|
||||||
return all_vms
|
return all_vms
|
||||||
else:
|
else:
|
||||||
return {
|
# Return empty array instead of error object - frontend expects array
|
||||||
'error': 'pvesh command not available or failed',
|
return []
|
||||||
'vms': []
|
|
||||||
}
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# print(f"[v0] Error getting VM/LXC info: {e}")
|
# print(f"[v0] Error getting VM/LXC info: {e}")
|
||||||
pass
|
pass
|
||||||
return {
|
# Return empty array instead of error object - frontend expects array
|
||||||
'error': 'Unable to access VM information: {str(e)}',
|
return []
|
||||||
'vms': []
|
|
||||||
}
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# print(f"Error getting VM info: {e}")
|
# print(f"Error getting VM info: {e}")
|
||||||
pass
|
pass
|
||||||
return {
|
# Return empty array instead of error object - frontend expects array
|
||||||
'error': f'Unable to access VM information: {str(e)}',
|
return []
|
||||||
'vms': []
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_ipmi_fans():
|
def get_ipmi_fans():
|
||||||
"""Get fan information from IPMI"""
|
"""Get fan information from IPMI"""
|
||||||
|
|||||||
@@ -2618,6 +2618,28 @@ class HealthMonitor:
|
|||||||
return True
|
return True
|
||||||
return False
|
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]:
|
def _check_vms_cts_optimized(self) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Optimized VM/CT check - detects qmp failures and startup errors from logs.
|
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)
|
# Skip if VM no longer exists (stale journal entry)
|
||||||
if not self._vm_ct_exists(vmid):
|
if not self._vm_ct_exists(vmid):
|
||||||
continue
|
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)
|
vm_name = self._resolve_vm_name(vmid)
|
||||||
display = f"VM {vmid} ({vm_name})" if vm_name else f"VM {vmid}"
|
display = f"VM {vmid} ({vm_name})" if vm_name else f"VM {vmid}"
|
||||||
key = f'vm_{vmid}'
|
key = f'vm_{vmid}'
|
||||||
@@ -2835,18 +2863,25 @@ class HealthMonitor:
|
|||||||
for line in journalctl_output.split('\n'):
|
for line in journalctl_output.split('\n'):
|
||||||
line_lower = line.lower()
|
line_lower = line.lower()
|
||||||
|
|
||||||
# VM QMP errors (skip during active backup -- normal behavior)
|
# 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)
|
vm_qmp_match = re.search(r'vm\s+(\d+)\s+qmp\s+command.*(?:failed|unable|timeout)', line_lower)
|
||||||
if vm_qmp_match:
|
if vm_qmp_match:
|
||||||
if _vzdump_running:
|
if _vzdump_running:
|
||||||
continue # Normal during backup
|
continue # Normal during backup
|
||||||
vmid = vm_qmp_match.group(1)
|
vmid = vm_qmp_match.group(1)
|
||||||
|
|
||||||
# Skip if VM no longer exists (deleted after error occurred)
|
# Skip if VM no longer exists (deleted after error occurred)
|
||||||
if not self._vm_ct_exists(vmid):
|
if not self._vm_ct_exists(vmid):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
vm_name = self._resolve_vm_name(vmid)
|
# 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}"
|
display = f"VM {vmid} ({vm_name})" if vm_name else f"VM {vmid}"
|
||||||
error_key = f'vm_{vmid}'
|
error_key = f'vm_{vmid}'
|
||||||
if error_key not in vm_details:
|
if error_key not in vm_details:
|
||||||
|
|||||||
@@ -620,7 +620,7 @@ class EmailChannel(NotificationChannel):
|
|||||||
<td colspan="2" style="padding:8px 12px;font-size:13px;color:#1f2937;border-bottom:1px solid #e5e7eb;">{value}</td>
|
<td colspan="2" style="padding:8px 12px;font-size:13px;color:#1f2937;border-bottom:1px solid #e5e7eb;">{value}</td>
|
||||||
</tr>'''
|
</tr>'''
|
||||||
|
|
||||||
# ── Reason / details block (long text, displayed separately) <EFBFBD><EFBFBD><EFBFBD>─
|
# ── Reason / details block (long text, displayed separately) ──
|
||||||
reason = data.get('reason', '')
|
reason = data.get('reason', '')
|
||||||
reason_html = ''
|
reason_html = ''
|
||||||
if reason and len(reason) > 80:
|
if reason and len(reason) > 80:
|
||||||
|
|||||||
@@ -557,7 +557,7 @@ test_vmct() {
|
|||||||
test_system() {
|
test_system() {
|
||||||
header "SYSTEM EVENT TESTS (syslog injection)"
|
header "SYSTEM EVENT TESTS (syslog injection)"
|
||||||
|
|
||||||
# ── Test S1: Authentication failures ──
|
# <EFBFBD><EFBFBD>─ Test S1: Authentication failures ──
|
||||||
log ""
|
log ""
|
||||||
log "${BOLD} Test S1: SSH auth failure injection${NC}"
|
log "${BOLD} Test S1: SSH auth failure injection${NC}"
|
||||||
info "Injecting SSH auth failure messages into syslog."
|
info "Injecting SSH auth failure messages into syslog."
|
||||||
@@ -684,7 +684,7 @@ show_menu() {
|
|||||||
echo -ne " Select: "
|
echo -ne " Select: "
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── Main ──────<EFBFBD><EFBFBD><EFBFBD>─────────────────────────────────────────────────
|
# ── Main ────────────────────────────────────────────────────────
|
||||||
main() {
|
main() {
|
||||||
local mode="${1:-menu}"
|
local mode="${1:-menu}"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user