diff --git a/AppImage/messages/de/common.json b/AppImage/messages/de/common.json index 6d9f2ee7..4cc5b7e0 100644 --- a/AppImage/messages/de/common.json +++ b/AppImage/messages/de/common.json @@ -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", diff --git a/AppImage/messages/fr/common.json b/AppImage/messages/fr/common.json index 475ba188..7d542fe5 100644 --- a/AppImage/messages/fr/common.json +++ b/AppImage/messages/fr/common.json @@ -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", diff --git a/AppImage/messages/it/common.json b/AppImage/messages/it/common.json index 542ca677..0846c5c0 100644 --- a/AppImage/messages/it/common.json +++ b/AppImage/messages/it/common.json @@ -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", diff --git a/AppImage/messages/pt/common.json b/AppImage/messages/pt/common.json index 3a08fe72..c8fa95c5 100644 --- a/AppImage/messages/pt/common.json +++ b/AppImage/messages/pt/common.json @@ -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", diff --git a/AppImage/messages/sv/common.json b/AppImage/messages/sv/common.json index de890ee1..c74cd1c1 100644 --- a/AppImage/messages/sv/common.json +++ b/AppImage/messages/sv/common.json @@ -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", diff --git a/AppImage/scripts/health_monitor.py b/AppImage/scripts/health_monitor.py index 8b6168a9..8f52bed0 100644 --- a/AppImage/scripts/health_monitor.py +++ b/AppImage/scripts/health_monitor.py @@ -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', ''), diff --git a/AppImage/scripts/tests/test_remote_mount_dismissal.py b/AppImage/scripts/tests/test_remote_mount_dismissal.py new file mode 100644 index 00000000..c66ccf17 --- /dev/null +++ b/AppImage/scripts/tests/test_remote_mount_dismissal.py @@ -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()