Let dismissed remote mount warnings clear the health status

This commit is contained in:
MacRimi
2026-10-18 12:38:40 +02:00
parent e8b8f95c5b
commit f363ef4395
7 changed files with 109 additions and 38 deletions
+6 -6
View File
@@ -1738,16 +1738,16 @@
"portCustomUrlPlaceholder": "Benutzerdefinierte URL (z. B. https://vault.example.com) überschreibt IP:port",
"stackMemberTitle": "Teil einer Multi-Container-Anwendung",
"stackMemberBody": "Dieser Container gehört zu einer Anwendung, deren Hauptcontainer {primary} ist. Die Anwendung, ihre Weblinks und ihre Version werden dort angezeigt.",
"nativeEvidencePort": "Überwachung des TCP-Ports",
"nativeEvidencePort": "Lauschender TCP-Port",
"nativeEvidenceFile": "Installierte Datei",
"nativeEvidenceProcess": "Ausführbare Datei ausführen",
"nativeEvidenceProcess": "Laufende ausführbare Datei",
"nativeEvidenceDirectory": "Arbeitsverzeichnis",
"nativeEvidenceService": "Dienstdefinition",
"nativeScanFailed": "Die Anwendungssuche konnte nicht abgeschlossen werden.Frühere Beweise könnten veraltet sein;versuchen Sie es erneut.",
"nativeScanPartial": "Die Anwendungssuche ist unvollständig.Überprüfen Sie die verfügbaren Beweise und versuchen Sie es erneut.",
"nativeScanFailed": "Die Anwendungssuche konnte nicht abgeschlossen werden. Frühere Ergebnisse sind möglicherweise veraltet; versuchen Sie es erneut.",
"nativeScanPartial": "Die Anwendungssuche ist unvollständig. Prüfen Sie die gefundenen Hinweise und versuchen Sie es erneut.",
"nativeVersionUnknown": "Installierte Version unbekannt",
"nativeEvidenceStale": "Die Beweise sind möglicherweise veraltet oder unvollständig.",
"nativeRegisterOnly": "Registrierung im Editor bestätigen.Eine bekannte Version ist ein manueller Wert;Es ist kein Updater konfiguriert."
"nativeEvidenceStale": "Die Hinweise sind möglicherweise veraltet oder unvollständig.",
"nativeRegisterOnly": "Bestätigen Sie die Registrierung im Editor. Eine bekannte Version wird als manueller Wert gespeichert; es wird kein Updater eingerichtet."
},
"statusFilter": {
"ariaLabel": "Virtuelle Maschinen und Container filtern",
+7 -7
View File
@@ -1738,16 +1738,16 @@
"portCustomUrlPlaceholder": "URL personnalisée (par exemple https://vault.example.com) — remplace IP:port",
"stackMemberTitle": "Fait partie d'une application multi-conteneurs",
"stackMemberBody": "Ce conteneur fait partie d'une application dont le conteneur principal est {primary}. L'application, ses liens web et sa version y sont affichés.",
"nativeEvidencePort": "Écoute du port TCP",
"nativeEvidencePort": "Port TCP en écoute",
"nativeEvidenceFile": "Fichier installé",
"nativeEvidenceProcess": "Exécuter un exécutable",
"nativeEvidenceProcess": "Exécutable en cours d'exécution",
"nativeEvidenceDirectory": "Répertoire de travail",
"nativeEvidenceService": "Définition du service",
"nativeScanFailed": "La recherche d'application n'a pas pu se terminer.Les preuves antérieures peuvent être obsolètes ;essayer à nouveau.",
"nativeScanPartial": "La recherche d’applications est incomplète.Examinez les preuves disponibles et réessayez.",
"nativeVersionUnknown": "version installée inconnue",
"nativeEvidenceStale": "Les preuves peuvent être obsolètes ou incomplètes.",
"nativeRegisterOnly": "Confirmez l'inscription dans l'éditeur.Une version connue est une valeur manuelle ;aucun programme de mise à jour n'est configuré."
"nativeScanFailed": "La recherche d'applications n'a pas pu se terminer. Les résultats précédents peuvent être obsolètes ; réessayez.",
"nativeScanPartial": "La recherche d'applications est incomplète. Vérifiez les éléments trouvés et réessayez.",
"nativeVersionUnknown": "Version installée inconnue",
"nativeEvidenceStale": "Les éléments trouvés peuvent être obsolètes ou incomplets.",
"nativeRegisterOnly": "Confirmez l'enregistrement dans l'éditeur. Une version connue est enregistrée comme valeur manuelle ; aucun outil de mise à jour n'est configuré."
},
"statusFilter": {
"ariaLabel": "Filtrer les machines virtuelles et les conteneurs",
+10 -10
View File
@@ -1738,16 +1738,16 @@
"portCustomUrlPlaceholder": "URL personalizzato (ad es. https://vault.example.com) — sostituisce IP:port",
"stackMemberTitle": "Parte di un'applicazione multi-container",
"stackMemberBody": "Questo container supporta un’applicazione il cui container principale è {primary}. L’applicazione, i suoi collegamenti web e la sua versione sono mostrati lì.",
"nativeEvidencePort": "porta TCP in ascolto",
"nativeEvidenceFile": "file installato",
"nativeEvidenceProcess": "esecuzione dell'eseguibile",
"nativeEvidenceDirectory": "directory di lavoro",
"nativeEvidenceService": "definizione del servizio",
"nativeScanFailed": "impossibile completare la ricerca dell'applicazione.Le prove precedenti potrebbero essere obsolete;riprova.",
"nativeScanPartial": "la ricerca dell'applicazione è incompleta.Esaminare le prove disponibili e riprovare.",
"nativeVersionUnknown": "versione installata sconosciuta",
"nativeEvidenceStale": "le prove potrebbero essere obsolete o incomplete.",
"nativeRegisterOnly": "conferma la registrazione nell'editor.Una versione nota è un valore manuale;nessun programma di aggiornamento è configurato."
"nativeEvidencePort": "Porta TCP in ascolto",
"nativeEvidenceFile": "File installato",
"nativeEvidenceProcess": "Eseguibile in esecuzione",
"nativeEvidenceDirectory": "Directory di lavoro",
"nativeEvidenceService": "Definizione del servizio",
"nativeScanFailed": "La ricerca delle applicazioni non è stata completata. I risultati precedenti potrebbero non essere aggiornati; riprova.",
"nativeScanPartial": "La ricerca delle applicazioni è incompleta. Controlla gli elementi trovati e riprova.",
"nativeVersionUnknown": "Versione installata sconosciuta",
"nativeEvidenceStale": "Gli elementi trovati potrebbero non essere aggiornati o essere incompleti.",
"nativeRegisterOnly": "Conferma la registrazione nell'editor. Una versione nota viene salvata come valore manuale; non viene configurato alcun aggiornamento automatico."
},
"statusFilter": {
"ariaLabel": "Filtra macchine virtuali e container",
+9 -9
View File
@@ -1738,16 +1738,16 @@
"portCustomUrlPlaceholder": "URL personalizada (por exemplo, https://vault.example.com) — substitui IP:port",
"stackMemberTitle": "Parte de um aplicativo multicontêiner",
"stackMemberBody": "Este contêiner faz parte de um aplicativo cujo contêiner principal é {primary}. O aplicativo, seus links da web e sua versão são mostrados lá.",
"nativeEvidencePort": "Porta TCP de escuta",
"nativeEvidenceFile": "arquivo instalado",
"nativeEvidenceProcess": "Executando executável",
"nativeEvidenceDirectory": "diretório de trabalho",
"nativeEvidenceService": "definição de serviço",
"nativeScanFailed": "a pesquisa do aplicativo não pôde ser concluída.As evidências anteriores podem estar desatualizadas;tente novamente.",
"nativeScanPartial": "a pesquisa do aplicativo está incompleta.Revise as evidências disponíveis e tente novamente.",
"nativeEvidencePort": "Porta TCP em escuta",
"nativeEvidenceFile": "Arquivo instalado",
"nativeEvidenceProcess": "Executável em execução",
"nativeEvidenceDirectory": "Diretório de trabalho",
"nativeEvidenceService": "Definição do serviço",
"nativeScanFailed": "A pesquisa de aplicativos não pôde ser concluída. Os resultados anteriores podem estar desatualizados; tente novamente.",
"nativeScanPartial": "A pesquisa de aplicativos está incompleta. Revise os itens encontrados e tente novamente.",
"nativeVersionUnknown": "Versão instalada desconhecida",
"nativeEvidenceStale": "as evidências podem estar desatualizadas ou incompletas.",
"nativeRegisterOnly": "Confirme o cadastro no editor.Uma versão conhecida é um valor manual;nenhum atualizador está configurado."
"nativeEvidenceStale": "Os itens encontrados podem estar desatualizados ou incompletos.",
"nativeRegisterOnly": "Confirme o registro no editor. Uma versão conhecida é salva como valor manual; nenhum atualizador é configurado."
},
"statusFilter": {
"ariaLabel": "Filtrar máquinas virtuais e contêineres",
+6 -6
View File
@@ -1740,14 +1740,14 @@
"stackMemberBody": "Den här behållaren stöder en applikation vars huvudbehållare är {primary}. Applikationen, dess webblänkar och dess version visas där.",
"nativeEvidencePort": "Lyssnande TCP-port",
"nativeEvidenceFile": "Installerad fil",
"nativeEvidenceProcess": "Kör körbar fil",
"nativeEvidenceProcess": "Körande programfil",
"nativeEvidenceDirectory": "Arbetskatalog",
"nativeEvidenceService": "Servicedefinition",
"nativeScanFailed": "Programsökningen kunde inte avslutas.Tidigare bevis kan vara föråldrade;försök igen.",
"nativeScanPartial": "Sökningen är ofullständig.Granska de tillgängliga bevisen och försök igen.",
"nativeEvidenceService": "Tjänstdefinition",
"nativeScanFailed": "Programsökningen kunde inte slutföras. Tidigare resultat kan vara inaktuella; försök igen.",
"nativeScanPartial": "Programsökningen är ofullständig. Granska det som hittades och försök igen.",
"nativeVersionUnknown": "Installerad version okänd",
"nativeEvidenceStale": "Bevisen kan vara föråldrade eller ofullständiga.",
"nativeRegisterOnly": "Bekräfta registreringen i editorn.En känd version är ett manuellt värde;ingen uppdatering är konfigurerad."
"nativeEvidenceStale": "Det som hittades kan vara inaktuellt eller ofullständigt.",
"nativeRegisterOnly": "Bekräfta registreringen i redigeraren. En känd version sparas som ett manuellt värde; ingen uppdatering konfigureras."
},
"statusFilter": {
"ariaLabel": "Filtrera virtuella maskiner och containrar",
+4
View File
@@ -6076,6 +6076,8 @@ class HealthMonitor:
if status == 'stale':
checks[check_label] = {
'status': 'CRITICAL',
'error_key': error_key,
'dismissable': True,
'detail': f'{fstype} mount stale: {error or "unreachable"}',
'fstype': fstype,
'source': m.get('source', ''),
@@ -6105,6 +6107,8 @@ class HealthMonitor:
elif status == 'readonly':
checks[check_label] = {
'status': 'WARNING',
'error_key': error_key,
'dismissable': True,
'detail': f'{fstype} mount unexpectedly read-only',
'fstype': fstype,
'source': m.get('source', ''),
@@ -0,0 +1,67 @@
"""Remote mount checks carry the key their dismissal is saved under."""
import sys
import unittest
from pathlib import Path
from types import ModuleType, SimpleNamespace
from unittest.mock import patch
SCRIPTS_DIR = Path(__file__).resolve().parents[1]
if str(SCRIPTS_DIR) not in sys.path:
sys.path.insert(0, str(SCRIPTS_DIR))
_health_persistence_module = ModuleType("health_persistence")
_health_persistence_module.health_persistence = SimpleNamespace(cleanup_old_errors=lambda: None)
_health_persistence_module.disk_base_name = lambda name: str(name).replace("/dev/", "")
sys.modules.setdefault("health_persistence", _health_persistence_module)
sys.modules.setdefault("psutil", ModuleType("psutil"))
sys.modules.setdefault("flask_server", SimpleNamespace(
get_proxmox_node_name=lambda: "fixture",
get_cached_pvesh_cluster_resources_vm=lambda: [],
get_cached_vm_disk=lambda _vmid: None,
))
import health_monitor # noqa: E402
class _Persistence:
def __init__(self):
self.recorded = []
def record_error(self, **kwargs):
self.recorded.append(kwargs)
def get_active_errors(self, *args, **kwargs):
return []
def clear_error(self, key):
pass
class RemoteMountDismissal(unittest.TestCase):
def check(self, host, lxc=()):
persistence = _Persistence()
mounts = SimpleNamespace(scan_remote_mounts=lambda force: list(host),
scan_lxc_mounts=lambda force: list(lxc))
with patch.object(health_monitor, "MOUNT_MONITOR_AVAILABLE", True), \
patch.object(health_monitor, "mount_monitor", mounts, create=True), \
patch.object(health_monitor, "health_persistence", persistence):
result = health_monitor.HealthMonitor._check_remote_mounts(SimpleNamespace())
return result, persistence
def test_stale_and_read_only_mounts_carry_their_saved_key(self):
result, persistence = self.check(
[{"target": "/mnt/pve/nas", "status": "stale", "fstype": "nfs4"},
{"target": "/mnt/share", "status": "ok", "fstype": "cifs"}],
[{"target": "/media", "status": "readonly", "fstype": "nfs4", "lxc_id": "120", "lxc_name": "app"}])
checks = result["checks"]
self.assertEqual(checks["/mnt/pve/nas"]["error_key"], "mount_stale_/mnt/pve/nas")
self.assertEqual(checks["CT 120 (app) /media"]["error_key"], "mount_readonly_ct120:/media")
self.assertTrue(checks["/mnt/pve/nas"]["dismissable"])
self.assertTrue(checks["CT 120 (app) /media"]["dismissable"])
self.assertNotIn("error_key", checks["/mnt/share"])
self.assertEqual({entry["error_key"] for entry in persistence.recorded},
{"mount_stale_/mnt/pve/nas", "mount_readonly_ct120:/media"})
if __name__ == "__main__":
unittest.main()