Update AppImage 1.2.1.3

This commit is contained in:
MacRimi
2026-05-22 18:47:30 +02:00
parent 840385272c
commit f2a40b993a
8 changed files with 177 additions and 19 deletions
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
d825487696ecdf071bf9aaed58f4bcc3e5b2e44e51770b746a85a359d1d71794
1caca89b574241c9d754b9ac3bb11987c5eccc5f182d01a5c62e61623b62fda7
+1 -1
View File
@@ -271,7 +271,7 @@ export function Login({ onLogin }: LoginProps) {
</form>
</div>
<p className="text-center text-sm text-muted-foreground">ProxMenux Monitor v1.2.1.2-beta</p>
<p className="text-center text-sm text-muted-foreground">ProxMenux Monitor v1.2.1.3-beta</p>
</div>
</div>
)
+1 -1
View File
@@ -814,7 +814,7 @@ export function ProxmoxDashboard() {
</Tabs>
<footer className="mt-8 md:mt-12 pt-4 md:pt-6 border-t border-border text-center text-xs md:text-sm text-muted-foreground">
<p className="font-medium mb-2">ProxMenux Monitor v1.2.1.2-beta</p>
<p className="font-medium mb-2">ProxMenux Monitor v1.2.1.3-beta</p>
<p>
<a
href="https://ko-fi.com/macrimi"
+17 -1
View File
@@ -6,7 +6,7 @@ import { Dialog, DialogContent, DialogTitle } from "./ui/dialog"
import { X, Sparkles, Thermometer, Activity, HardDrive, Shield, Globe, Cpu, Zap, Sliders, Wrench, RefreshCw, Server } from "lucide-react"
import { Checkbox } from "./ui/checkbox"
const APP_VERSION = "1.2.1.2-beta" // Sync with AppImage/package.json
const APP_VERSION = "1.2.1.3-beta" // Sync with AppImage/package.json
interface ReleaseNote {
date: string
@@ -18,6 +18,22 @@ interface ReleaseNote {
}
export const CHANGELOG: Record<string, ReleaseNote> = {
"1.2.1.3-beta": {
date: "May 22, 2026",
changes: {
added: [
"LXC Update Detection - A new dedicated section in Settings (between Health Monitor Thresholds and Notifications) with a single toggle that gates the per-CT apt list --upgradable / apk list -u scan end-to-end. Default ON. When OFF the scan stops entirely (no pct exec calls), every type=lxc entry is purged from the managed-installs registry immediately, and the matching notification toggle in Notifications -> Services disappears from the UI while preserving its stored preference",
"LXC update checker auto-refresh - The checker now reads the mtime of the CT's package-manager metadata cache and runs apt-get update / apk update from outside via pct exec if it is older than 24h, with a 60s timeout and silent failure. Long-running appliance CTs whose caches were months stale now surface their real upstream backlog (a Debian 12 CT with a 524-day-old cache went from \"0 updates\" to \"117 (12 security)\" on lab hardware)",
],
changed: [
"AI Enhancement section in Notifications - Rewritten from a muted uppercase row that testers consistently scrolled past, to a normal-case foreground label with a leading Sparkles icon and a persistent badge (green Active when AI is enabled, neutral Optional when it isn't) so the feature is visible regardless of state",
],
fixed: [
"Terminal modals on HTTPS hosts - Every terminal modal (dashboard terminal, LXC terminal, script terminal) used to fail with WebSocket connection error on hosts with HTTPS enabled. Root cause: the gevent+SSL path stacked geventwebsocket's WebSocketHandler on top of flask-sock's protocol implementation, so the server emitted two consecutive HTTP/1.1 101 Switching Protocols headers and the browser closed the connection as a corrupt frame. Dropping handler_class=WebSocketHandler restores a single 101 response and lets the handshake complete normally",
"Health Monitor kernel updates on PVE 9.x (#208) - The System Updates -> Kernel/PVE row reported \"Kernel/PVE up to date\" on PVE 9.x hosts even when an update for the running kernel was waiting upstream. Three combined fixes: (a) the kernel-package prefix list now includes proxmox-kernel-* and proxmox-firmware-* (PVE 9.x ships kernels under proxmox-kernel-, not pve-kernel- as in 7.x/8.x), (b) the dry-run switched from apt-get upgrade --dry-run to apt-get dist-upgrade --dry-run so kernel updates packaged as new installs are visible at all, (c) the categoriser now reads uname -r and flags an update as a running-kernel update when the package matches the running release exactly or its branch meta-package (e.g. proxmox-kernel-6.14 for a host on 6.14.11-4-pve). The row text now distinguishes \"Running kernel update available (reboot required)\" from \"N kernel update(s) available (none for running kernel)\"",
],
},
},
"1.2.1.2-beta": {
date: "May 20, 2026",
changes: {
+1 -1
View File
@@ -3624,7 +3624,7 @@ ${observationsHtml}
<!-- Footer -->
<div class="rpt-footer">
<div>Report generated by ProxMenux Monitor</div>
<div>ProxMenux Monitor v1.2.1.2-beta</div>
<div>ProxMenux Monitor v1.2.1.3-beta</div>
</div>
</body>
+3 -3
View File
@@ -10435,7 +10435,7 @@ def api_health():
return jsonify({
'status': 'healthy',
'timestamp': datetime.now().isoformat(),
'version': '1.2.1.2-beta'
'version': '1.2.1.3-beta'
})
# ─── User-configurable health thresholds ─────────────────────────────────────
@@ -10872,7 +10872,7 @@ def api_info():
"""Root endpoint with API information"""
return jsonify({
'name': 'ProxMenux Monitor API',
'version': '1.2.1.2-beta',
'version': '1.2.1.3-beta',
'endpoints': [
'/api/system',
'/api/system-info',
@@ -11522,7 +11522,7 @@ if __name__ == '__main__':
try:
import sqlite3
from pathlib import Path
MONITOR_VERSION = '1.2.1.2-beta'
MONITOR_VERSION = '1.2.1.3-beta'
db_path = Path('/usr/local/share/proxmenux/health_monitor.db')
if db_path.exists():
conn = sqlite3.connect(str(db_path), timeout=10)
+153 -11
View File
@@ -4177,7 +4177,21 @@ class HealthMonitor:
'pve-', 'proxmox-', 'qemu-server', 'lxc-pve', 'ceph',
'corosync', 'libpve', 'pbs-', 'pmg-',
)
_KERNEL_PREFIXES = ('linux-image', 'pve-kernel', 'pve-firmware')
# Kernel-package prefixes for categorisation. Order matters: the matcher
# in update parsing checks KERNEL first and PVE second, so `proxmox-kernel`
# must appear here before the generic `proxmox-` falls through to
# _PVE_PREFIXES. Bug #208 — PVE 9.x ships kernels as `proxmox-kernel-*`
# (Debian 13 base), not `pve-kernel-*` as in older Proxmox; without the
# `proxmox-kernel` prefix every kernel update was mis-categorised as a
# generic PVE update and the dashboard reported "Kernel/PVE up to date"
# even with a kernel patch waiting for the running CT.
_KERNEL_PREFIXES = (
'linux-image',
'pve-kernel',
'proxmox-kernel',
'pve-firmware',
'proxmox-firmware',
)
_IMPORTANT_PKGS = {
'pve-manager', 'proxmox-ve', 'qemu-server', 'pve-container',
'pve-ha-manager', 'pve-firewall', 'ceph-common',
@@ -4232,10 +4246,18 @@ class HealthMonitor:
except Exception:
pass
# Perform a dry run of apt-get upgrade to see pending packages
# Perform a dry run of apt-get dist-upgrade to see pending
# packages. `dist-upgrade` (a.k.a. `full-upgrade`) is what the
# Proxmox VE web UI's Updates tab runs internally, and unlike
# plain `apt-get upgrade` it surfaces kernel updates packaged
# as NEW installs (e.g. `proxmox-kernel-6.14.11-9-pve-signed`
# showing up as `Inst` when the running CT is on -4). Bug #208
# — without dist-upgrade these updates never appeared in the
# `Inst` list at all, so the Kernel/PVE sub-check stayed
# green even when the running kernel had a patch waiting.
try:
result = subprocess.run(
['apt-get', 'upgrade', '--dry-run'],
['apt-get', 'dist-upgrade', '--dry-run'],
capture_output=True, text=True, timeout=30
)
except subprocess.TimeoutExpired:
@@ -4252,18 +4274,42 @@ class HealthMonitor:
security_pkgs: list = []
kernel_pkgs: list = []
pve_pkgs: list = []
running_kernel_pkgs: list = [] # {name, cur, new} — kernel(s) matching `uname -r`
important_pkgs: list = [] # {name, cur, new}
pve_manager_info = None # {cur, new} or None
sec_result = None
sec_severity = 'INFO'
sec_days_unpatched = 0
# Read the running kernel release (e.g. `7.0.2-5-pve`) so we can
# distinguish a kernel update that REPLACES the kernel the host
# is currently booted from (urgent, requires reboot) from one
# that only touches an older kernel still present in the
# bootloader or the meta-package pointers. Bug #208.
#
# `running_kernel_branch` extracts the major.minor branch from
# the running release — e.g. `7.0.2-5-pve` → `7.0`. We use it
# to detect updates to the branch META package
# (`proxmox-kernel-7.0` / `pve-kernel-7.0`), which when
# upgraded pull in the latest kernel of that branch and so
# also replace the running kernel on the next boot.
running_kernel = ''
running_kernel_branch = ''
try:
running_kernel = os.uname().release
rk_low = running_kernel.lower()
m = re.match(r'^(\d+\.\d+)', rk_low)
if m:
running_kernel_branch = m.group(1)
except Exception:
pass
if result.returncode == 0:
for line in result.stdout.strip().split('\n'):
if not line.startswith('Inst '):
continue
update_count += 1
# Parse package name, current and new versions
m = self._RE_INST.match(line)
if m:
@@ -4276,18 +4322,56 @@ class HealthMonitor:
parts = line.split()
pkg_name = parts[1] if len(parts) > 1 else 'unknown'
cur_ver, new_ver = '', ''
# Strip arch suffix (e.g. package:amd64)
pkg_name = pkg_name.split(':')[0]
name_lower = pkg_name.lower()
line_lower = line.lower()
# Categorise
if 'security' in line_lower or 'debian-security' in line_lower:
security_pkgs.append(pkg_name)
if any(name_lower.startswith(p) for p in self._KERNEL_PREFIXES):
kernel_pkgs.append(pkg_name)
# Does this package match the currently running
# kernel? Proxmox kernels are named
# `proxmox-kernel-<release>` and `pve-kernel-<release>`,
# plus their `-signed` variant. Compare against
# `uname -r` to flag updates the operator should
# reboot for. Bug #208.
if running_kernel:
stripped = name_lower
if stripped.endswith('-signed'):
stripped = stripped[:-len('-signed')]
for k_prefix in ('proxmox-kernel-', 'pve-kernel-', 'linux-image-'):
if stripped.startswith(k_prefix):
suffix = stripped[len(k_prefix):]
# Two cases both count as "this update
# will replace the running kernel":
# a) exact match — the in-place
# package of the running release
# (e.g. `proxmox-kernel-7.0.2-5-pve`
# upgrading from 7.0.2-5 to -6).
# b) branch meta — the meta package
# pointing at the latest of the
# running branch (e.g. running
# `6.14.11-4-pve`, upgradable
# `proxmox-kernel-6.14` pulls a
# newer 6.14.x on next boot).
is_exact = (suffix == running_kernel.lower())
is_branch_meta = (
running_kernel_branch
and suffix == running_kernel_branch
)
if is_exact or is_branch_meta:
running_kernel_pkgs.append({
'name': pkg_name,
'cur': cur_ver,
'new': new_ver,
'match': 'exact' if is_exact else 'branch',
})
break
elif any(name_lower.startswith(p) for p in self._PVE_PREFIXES):
pve_pkgs.append(pkg_name)
@@ -4354,6 +4438,15 @@ class HealthMonitor:
if age_result and age_result.get('type') == 'skipped_acknowledged':
status = 'INFO'
reason = None
elif running_kernel_pkgs:
# Running kernel has an update available. Bug #208.
# Reported as INFO — the Updates section never
# escalates to WARNING for pending updates; WARNING /
# CRITICAL on this section are reserved for time-based
# gates (system_age >= 365d / 548d, security_updates
# unpatched >= SECURITY_WARN_DAYS).
status = 'INFO'
reason = f'Kernel update available for running {running_kernel}'
elif kernel_pkgs or pve_pkgs:
status = 'INFO'
reason = f'{len(kernel_pkgs)} kernel + {len(pve_pkgs)} Proxmox update(s) available'
@@ -4382,11 +4475,57 @@ class HealthMonitor:
if security_pkgs and sec_days_unpatched >= self.SECURITY_WARN_DAYS:
sec_detail += f' ({sec_days_unpatched} days unpatched)'
# Kernel/PVE sub-check. Bug #208: distinguish three cases.
# All non-OK results stay at INFO — the Updates section never
# raises WARNING for pending updates; WARNING / CRITICAL on
# this section is reserved for time-based gates (system_age,
# security_updates unpatched too long).
#
# 1) Update for the running kernel → INFO with explicit
# "Running kernel update available, reboot required to
# apply" wording. Bug #208 — without this, the row read
# "Kernel/PVE up to date" even when the running kernel
# had a patch pending.
# 2) Kernel updates exist but only for non-running kernels
# (older kernels still present in the bootloader, meta-
# packages like `proxmox-kernel-7.0` pointing to the
# latest 7.0.x, signed variants of already-installed
# kernels) → INFO with explicit wording.
# 3) No kernel updates → OK.
if running_kernel_pkgs:
rk_count = len(running_kernel_pkgs)
# Keep the row text tight — on mobile the System Updates
# modal renders this string in a single line and longer
# wording wrapped awkwardly. Just `<pkg> <cur> -> <new>`
# is enough to convey "your running kernel has an update";
# the row's INFO icon already signals that no action is
# forced.
first = running_kernel_pkgs[0]
if first.get('cur') and first.get('new'):
rk_detail = f"{first['name']} {first['cur']} -> {first['new']}"
else:
rk_detail = first['name']
if rk_count > 1:
rk_detail += f" (+{rk_count - 1} more)"
kernel_status = 'INFO'
kernel_detail = rk_detail
elif kernel_pkgs:
kernel_status = 'INFO'
kernel_detail = (
f"{len(kernel_pkgs)} kernel update(s) available "
f"(none for running kernel"
+ (f" {running_kernel}" if running_kernel else "")
+ ")"
)
else:
kernel_status = 'OK'
kernel_detail = 'Kernel/PVE up to date'
checks = {
'kernel_pve': {
'status': 'INFO' if kernel_pkgs else 'OK',
'detail': f'{len(kernel_pkgs)} kernel/PVE update(s)' if kernel_pkgs else 'Kernel/PVE up to date',
'error_key': 'kernel_pve'
'status': kernel_status,
'detail': kernel_detail,
'error_key': 'kernel_pve',
},
'pending_updates': {
'status': 'INFO' if update_count > 0 else 'OK',
@@ -4437,6 +4576,9 @@ class HealthMonitor:
update_result['security_count'] = len(security_pkgs)
update_result['pve_count'] = len(pve_pkgs)
update_result['kernel_count'] = len(kernel_pkgs)
update_result['running_kernel'] = running_kernel
update_result['running_kernel_update_count'] = len(running_kernel_pkgs)
update_result['running_kernel_packages'] = running_kernel_pkgs[:5]
update_result['important_packages'] = important_pkgs[:8]
self.cached_results[cache_key] = update_result