diff --git a/AppImage/components/virtual-machines.tsx b/AppImage/components/virtual-machines.tsx index 237b8849..c1396d83 100644 --- a/AppImage/components/virtual-machines.tsx +++ b/AppImage/components/virtual-machines.tsx @@ -223,13 +223,21 @@ function normalizeVmSearchValue(value: unknown): string { .toLocaleLowerCase() } -function matchesVmSearch(vm: VMData, terms: string[]): boolean { +// Type synonyms come from the active locale so a user can search by the +// word they'd naturally use ("contenedor", "machine virtuelle", …) rather +// than only the internal type token. "lxc" / "qemu" already match through +// vm.type, so the catalog entries only carry the natural-language terms. +function matchesVmSearch( + vm: VMData, + terms: string[], + typeSynonyms: { lxc: string; qemu: string }, +): boolean { if (terms.length === 0) return true const searchable = normalizeVmSearchValue([ vm.name, vm.vmid, vm.type, - vm.type === "lxc" ? "container kontajner" : "virtual machine virtualny stroj", + vm.type === "lxc" ? typeSynonyms.lxc : typeSynonyms.qemu, vm.tags, vm.description, vm.ip, @@ -1806,15 +1814,20 @@ const handleDownloadLogs = async (vmid: number, vmName: string) => { updates: safeVMData.filter(hasLxcPendingUpdates).length, }), [safeVMData]) + const vmTypeSynonyms = useMemo(() => ({ + lxc: t("vmLxc.filters.typeSynonyms.lxc"), + qemu: t("vmLxc.filters.typeSynonyms.qemu"), + }), [t]) + const filteredVMs = useMemo(() => { const statusMatched = statusFilter === "all" ? safeVMData : safeVMData.filter((vm) => vm.status === statusFilter) return statusMatched.filter((vm) => ( (!updatesOnly || hasLxcPendingUpdates(vm)) - && matchesVmSearch(vm, vmSearchTerms) + && matchesVmSearch(vm, vmSearchTerms, vmTypeSynonyms) )) - }, [safeVMData, statusFilter, updatesOnly, vmSearchTerms]) + }, [safeVMData, statusFilter, updatesOnly, vmSearchTerms, vmTypeSynonyms]) // ── LXC update apply flow (Phase 2a/b) ──────────────────────────── // Users pick a target (OS, App, both) + backup / restart options, diff --git a/AppImage/messages/de/common.json b/AppImage/messages/de/common.json index fb4a6c02..b1f1f994 100644 --- a/AppImage/messages/de/common.json +++ b/AppImage/messages/de/common.json @@ -882,7 +882,7 @@ "executable": "Ausführbar", "workingDir": "Arbeitsverzeichnis", "started": "Begonnen", - "runningFor": "Laufen für" + "runningFor": "Läuft seit" }, "states": { "running": "Läuft", @@ -1217,7 +1217,7 @@ "humanWeekly": "Wöchentlich ({day} {time})", "humanMonthly": "Monatlich (Tag {day} um {time})", "humanHourly": "Stündlich", - "weekdays": "['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag']" + "weekdays": ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"] }, "cronChip": { "detected": "Host-Cron erkannt", @@ -1567,22 +1567,26 @@ "excludeFromBadgeHelp": "Zählen Sie diese App nicht im Abzeichen „Aggregate Updates“ auf der LXC-Listenkarte.Nützlich, wenn Sie absichtlich an eine bestimmte Version gebunden sind (Tracker-Anforderung, Kompatibilitätsstopp).Hat keinen Einfluss auf den eigenen Status der App-Registerkarte oder die ausgehende Benachrichtigung." }, "statusFilter": { - "ariaLabel": "Virtuelle Maschinen und Container nach Status filtern", + "ariaLabel": "Virtuelle Maschinen und Container filtern", "all": "Alle", - "running": "Laufen", + "running": "Läuft", "stopped": "Gestoppt", "empty": "Keine virtuellen Maschinen oder Container mit dem Status „{status}“" }, "filters": { - "searchPlaceholder": "Nach Name, ID, Tag oder Notiz suchen …", - "searchAriaLabel": "Durchsuchen Sie virtuelle Maschinen und Container", + "searchPlaceholder": "Name, ID, Tag oder Notiz suchen…", + "searchAriaLabel": "Virtuelle Maschinen und Container durchsuchen", "clearSearch": "Suche löschen", - "updates": "Aktualisierungen", + "updates": "Updates", "resultCount": "{shown} von {total} Maschinen", - "noMatches": "Keine VMs oder LXCs entsprechen Ihrer Suche.", + "noMatches": "Keine VMs oder LXCs entsprechen der Suche.", "noRunning": "Keine laufenden VMs oder LXCs.", "noStopped": "Keine gestoppten VMs oder LXCs.", - "noUpdates": "Nein LXC mit verfügbaren Updates." + "noUpdates": "Kein LXC mit verfügbaren Updates.", + "typeSynonyms": { + "lxc": "container", + "qemu": "vm virtuelle maschine virtuelle maschinen" + } } }, "settings": { @@ -4065,7 +4069,7 @@ "loadingJob": "Job wird geladen...", "manualOneShot": "manuell / One-Shot", "manualOneShotDescription": "One-Shot-Backup – zum Zeitpunkt des Auslösers erfasst. Kann nicht erneut ausgeführt oder bearbeitet werden.", - "neverRun": "niemals laufen", + "neverRun": "nie ausgeführt", "newScheduledJob": "Neuer geplanter Auftrag", "newScheduledJobDescription": "Erstellen Sie einen wiederkehrenden Host-Sicherungsauftrag.", "nextRun": "Nächster Lauf", @@ -4196,7 +4200,7 @@ "inProgress": "Manuelle Sicherung läuft", "oneShotDescription": "One-Shot-Backups werden als eingefrorene Jobs aufbewahrt, sodass Sie ihr Protokoll später überprüfen können.", "reopenLogTitle": "Protokoll erneut öffnen", - "run": "Laufen", + "run": "Ausführen", "runBackup": "Führen Sie ein Backup aus", "runOneShotBackup": "Führen Sie ein One-Shot-Backup durch", "title": "Manuelle Sicherung", @@ -4352,7 +4356,7 @@ "hourlyHelpBefore": "Im Moment", "hourlyHelpMiddle": "der Timer", "hourlyOption": "Stündlich", - "howToSchedule": "Wie soll es laufen?", + "howToSchedule": "Wie soll es ausgeführt werden?", "janFirst": "Jedes Jahr am 1. Januar", "modeHelp": "Wählen Sie einen einfachen Zeitplan oder geben Sie einen systemd-OnCalendar-Ausdruck ein.", "monthlyOption": "Monatlich", diff --git a/AppImage/messages/en/common.json b/AppImage/messages/en/common.json index 7de17496..e71b00eb 100644 --- a/AppImage/messages/en/common.json +++ b/AppImage/messages/en/common.json @@ -932,7 +932,11 @@ "noMatches": "No VMs or LXCs match your search.", "noRunning": "No running VMs or LXCs.", "noStopped": "No stopped VMs or LXCs.", - "noUpdates": "No LXC with available updates." + "noUpdates": "No LXC with available updates.", + "typeSynonyms": { + "lxc": "container containers", + "qemu": "vm vms virtual machine virtual machines" + } }, "uptime": "Uptime: {uptime}", "cpuUsage": "CPU Usage", @@ -1234,7 +1238,15 @@ "humanWeekly": "Weekly ({day} {time})", "humanMonthly": "Monthly (day {day} at {time})", "humanHourly": "Hourly", - "weekdays": ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] + "weekdays": [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday" + ] }, "cronChip": { "detected": "host cron detected", diff --git a/AppImage/messages/es/common.json b/AppImage/messages/es/common.json index deafdfb1..92bdc9ba 100644 --- a/AppImage/messages/es/common.json +++ b/AppImage/messages/es/common.json @@ -882,7 +882,7 @@ "executable": "Ejecutable", "workingDir": "Directorio de trabajo", "started": "Comenzó", - "runningFor": "Corriendo por" + "runningFor": "En ejecución desde" }, "states": { "running": "En ejecución", @@ -1032,7 +1032,7 @@ "tagsPlaceholder": "Añadir etiqueta…", "tagsNone": "Sin etiquetas", "mountErrors": { - "configuredButNotMounted": "configurado pero no montado" + "configuredButNotMounted": "Configurado pero no montado" } }, "logs": { @@ -1217,7 +1217,7 @@ "humanWeekly": "Semanal ({day} {time})", "humanMonthly": "Mensual (día {day} a las {time})", "humanHourly": "cada hora", - "weekdays": "['domingo', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado']" + "weekdays": ["domingo", "lunes", "martes", "miércoles", "jueves", "viernes", "sábado"] }, "cronChip": { "detected": "cron del host detectado", @@ -1567,22 +1567,26 @@ "excludeFromBadgeHelp": "No sumar esta app al contador agregado de actualizaciones del card del LXC. Útil cuando mantienes una versión concreta a propósito (requisito de tracker, compatibilidad). No afecta al estado que se muestra en la pestaña App ni al envío de la notificación." }, "statusFilter": { - "ariaLabel": "filtrar máquinas virtuales y contenedores por estado", + "ariaLabel": "Filtrar máquinas virtuales y contenedores", "all": "Todos", - "running": "corriendo", - "stopped": "detenido", + "running": "En ejecución", + "stopped": "Detenido", "empty": "No hay máquinas virtuales ni contenedores con estado \"{status}\"" }, "filters": { - "searchPlaceholder": "buscar nombre, ID, etiqueta o nota...", - "searchAriaLabel": "buscar máquinas virtuales y contenedores", + "searchPlaceholder": "Buscar por nombre, ID, etiqueta o nota…", + "searchAriaLabel": "Buscar máquinas virtuales y contenedores", "clearSearch": "Borrar búsqueda", "updates": "Actualizaciones", "resultCount": "{shown} de {total} máquinas", - "noMatches": "Ninguna VM o LXC coincide con su búsqueda.", - "noRunning": "No se ejecutan VM ni LXC.", - "noStopped": "No hay máquinas virtuales ni LXC detenidos.", - "noUpdates": "No LXC con actualizaciones disponibles." + "noMatches": "Ninguna VM ni LXC coincide con la búsqueda.", + "noRunning": "No hay VM ni LXC en ejecución.", + "noStopped": "No hay VM ni LXC detenidas.", + "noUpdates": "Ningún LXC tiene actualizaciones disponibles.", + "typeSynonyms": { + "lxc": "contenedor contenedores", + "qemu": "vm máquina virtual máquinas virtuales" + } } }, "settings": { @@ -2717,7 +2721,7 @@ "executiveSummary": "Resumen ejecutivo", "hardeningAssessment": "Evaluación de endurecimiento", "auditOf": "Auditoría de", - "running": "corriendo", + "running": "ejecutándose en", "unknownOs": "sistema operativo desconocido", "noActionableWarnings": "Sin advertencias procesables", "and": "y", diff --git a/AppImage/messages/fr/common.json b/AppImage/messages/fr/common.json index a962859d..e4c359f7 100644 --- a/AppImage/messages/fr/common.json +++ b/AppImage/messages/fr/common.json @@ -882,7 +882,7 @@ "executable": "Exécutable", "workingDir": "Répertoire de travail", "started": "Commencé", - "runningFor": "Courir pour" + "runningFor": "En cours d'exécution depuis" }, "states": { "running": "En cours d'exécution", @@ -1032,7 +1032,7 @@ "tagsPlaceholder": "Ajouter une balise…", "tagsNone": "Aucune balise", "mountErrors": { - "configuredButNotMounted": "configuré mais non monté" + "configuredButNotMounted": "Configuré mais non monté" } }, "logs": { @@ -1217,7 +1217,7 @@ "humanWeekly": "hebdomadaire ({day} {time})", "humanMonthly": "mensuel (jour {day} à {time})", "humanHourly": "Horaire", - "weekdays": "['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi']" + "weekdays": ["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"] }, "cronChip": { "detected": "cron hôte détecté", @@ -1567,22 +1567,26 @@ "excludeFromBadgeHelp": "Ne comptez pas cette application dans le badge de mises à jour globales sur la carte de liste LXC.Utile lorsque vous êtes volontairement épinglé à une version spécifique (exigence de suivi, gel de la compatibilité).N'affecte pas l'état de l'onglet Application ni la notification sortante." }, "statusFilter": { - "ariaLabel": "filtrer les machines virtuelles et les conteneurs par statut", + "ariaLabel": "Filtrer les machines virtuelles et les conteneurs", "all": "Tous", - "running": "Courir", + "running": "En cours d'exécution", "stopped": "Arrêté", "empty": "Aucune machine virtuelle ou conteneur avec le statut \"{status}\"" }, "filters": { - "searchPlaceholder": "rechercher un nom, un identifiant, une étiquette ou une note…", + "searchPlaceholder": "Rechercher par nom, ID, étiquette ou note…", "searchAriaLabel": "Rechercher des machines virtuelles et des conteneurs", "clearSearch": "Effacer la recherche", - "updates": "mises à jour", - "resultCount": "{shown} sur {total} machines", - "noMatches": "Aucune machine virtuelle ou LXC ne correspond à votre recherche.", - "noRunning": "Aucune machine virtuelle ou LXC en cours d’exécution.", - "noStopped": "Aucune machine virtuelle ou LXC arrêtée.", - "noUpdates": "Non LXC avec les mises à jour disponibles." + "updates": "Mises à jour", + "resultCount": "{shown} sur {total} machines", + "noMatches": "Aucune VM ni LXC ne correspond à la recherche.", + "noRunning": "Aucune VM ni LXC en cours d'exécution.", + "noStopped": "Aucune VM ni LXC arrêtée.", + "noUpdates": "Aucun LXC avec des mises à jour disponibles.", + "typeSynonyms": { + "lxc": "conteneur conteneurs", + "qemu": "vm machine virtuelle machines virtuelles" + } } }, "settings": { @@ -2717,7 +2721,7 @@ "executiveSummary": "Résumé exécutif", "hardeningAssessment": "Évaluation du durcissement", "auditOf": "Vérification de", - "running": "courir sur", + "running": "exécuté sur", "unknownOs": "système d'exploitation inconnu", "noActionableWarnings": "Aucun avertissement exploitable", "and": "et", @@ -4058,14 +4062,14 @@ "jobNameHelpAnd": "et", "jobNameHelpBefore": "Utilisé dans les noms de minuterie et les journaux. Les caractères autorisés incluent également", "jobNameLocked": "Le nom du travail est verrouillé après la création.", - "lastRun": "Dernière course", - "lastRunLabel": "Dernière course", + "lastRun": "Dernière exécution", + "lastRunLabel": "Dernière exécution", "liveLogSize": "en direct · {size}", "loadFailed": "Impossible de charger les tâches", "loadingJob": "Chargement du travail...", "manualOneShot": "manuel / one-shot", "manualOneShotDescription": "Sauvegarde ponctuelle : capturée au moment du déclenchement. Ne peut pas être réexécuté ou modifié.", - "neverRun": "ne jamais courir", + "neverRun": "jamais exécuté", "newScheduledJob": "Nouveau travail planifié", "newScheduledJobDescription": "Créez une tâche de sauvegarde d'hôte récurrente.", "nextRun": "Prochaine exécution", @@ -4196,7 +4200,7 @@ "inProgress": "Sauvegarde manuelle en cours", "oneShotDescription": "Les sauvegardes ponctuelles sont conservées sous forme de tâches gelées afin que vous puissiez inspecter leur journal ultérieurement.", "reopenLogTitle": "Rouvrir le journal", - "run": "Courir", + "run": "Exécuter", "runBackup": "Exécuter une sauvegarde", "runOneShotBackup": "Exécuter une sauvegarde ponctuelle", "title": "Sauvegarde manuelle", diff --git a/AppImage/messages/it/common.json b/AppImage/messages/it/common.json index 74bf496d..32b7ef09 100644 --- a/AppImage/messages/it/common.json +++ b/AppImage/messages/it/common.json @@ -327,7 +327,7 @@ "statusValues": { "passed": "Superato", "failed": "Fallito", - "running": "Corsa", + "running": "In esecuzione", "aborted": "Abortito", "unknown": "Sconosciuto" } @@ -882,10 +882,10 @@ "executable": "Eseguibile", "workingDir": "Dir. di lavoro", "started": "Iniziato", - "runningFor": "Correre per" + "runningFor": "In esecuzione da" }, "states": { - "running": "Corsa", + "running": "In esecuzione", "sleeping": "Dormire", "diskWait": "Attendere il disco", "zombie": "Zombie", @@ -905,7 +905,7 @@ "totalCpuAllocated": "CPU totale allocata", "totalMemory": "Memoria totale", "totalDisk": "Disco totale", - "running": "corsa", + "running": "in esecuzione", "stopped": "fermato", "vms": "VM", "used": "Usato", @@ -1032,7 +1032,7 @@ "tagsPlaceholder": "aggiungi tag…", "tagsNone": "nessun tag", "mountErrors": { - "configuredButNotMounted": "configurato ma non montato" + "configuredButNotMounted": "Configurato ma non montato" } }, "logs": { @@ -1217,7 +1217,7 @@ "humanWeekly": "testo tecnico dell'interfaccia utente per una dashboard di gestione Proxmox.Traduzione: settimanale ({day} {time})", "humanMonthly": "testo tecnico dell'interfaccia utente per una dashboard di gestione Proxmox.Traduzione: mensile (giorno {day} alle {time})", "humanHourly": "ogni ora", - "weekdays": "['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato']" + "weekdays": ["Domenica", "Lunedì", "Martedì", "Mercoledì", "Giovedì", "Venerdì", "Sabato"] }, "cronChip": { "detected": "rilevato il cron dell'host", @@ -1567,22 +1567,26 @@ "excludeFromBadgeHelp": "non contare questa app nel badge degli aggiornamenti aggregati sulla scheda dell'elenco LXC.Utile quando sei bloccato di proposito su una versione specifica (requisito del tracker, blocco della compatibilità).Non influisce sullo stato della scheda App o sulla notifica in uscita." }, "statusFilter": { - "ariaLabel": "filtra macchine virtuali e contenitori in base allo stato", - "all": "tutto", - "running": "Correre", - "stopped": "fermato", - "empty": "nessuna macchina virtuale o contenitore con stato \"{status}\"" + "ariaLabel": "Filtra macchine virtuali e container", + "all": "Tutti", + "running": "In esecuzione", + "stopped": "Arrestato", + "empty": "Nessuna macchina virtuale o container con stato \"{status}\"" }, "filters": { - "searchPlaceholder": "cerca nome, ID, tag o nota...", - "searchAriaLabel": "cerca macchine virtuali e contenitori", - "clearSearch": "cancella la ricerca", + "searchPlaceholder": "Cerca per nome, ID, tag o nota…", + "searchAriaLabel": "Cerca macchine virtuali e container", + "clearSearch": "Cancella ricerca", "updates": "Aggiornamenti", "resultCount": "{shown} di {total} macchine", - "noMatches": "nessuna VM o LXC corrisponde alla tua ricerca.", - "noRunning": "nessuna VM o LXC in esecuzione.", - "noStopped": "nessuna VM o LXC interrotta.", - "noUpdates": "No LXC con aggiornamenti disponibili." + "noMatches": "Nessuna VM o LXC corrisponde alla ricerca.", + "noRunning": "Nessuna VM o LXC in esecuzione.", + "noStopped": "Nessuna VM o LXC arrestata.", + "noUpdates": "Nessun LXC con aggiornamenti disponibili.", + "typeSynonyms": { + "lxc": "contenitore contenitori container", + "qemu": "vm macchina virtuale macchine virtuali" + } } }, "settings": { @@ -2717,7 +2721,7 @@ "executiveSummary": "Sintesi", "hardeningAssessment": "Valutazione dell'indurimento", "auditOf": "Revisione di", - "running": "correndo", + "running": "in esecuzione su", "unknownOs": "sistema operativo sconosciuto", "noActionableWarnings": "Nessun avviso utilizzabile", "and": "E", @@ -3580,7 +3584,7 @@ "reinstallViaPostInstall": "Reinstallare tramite ProxMenux dopo l'installazione: {label}", "noUpdatesAvailable": "Nessun aggiornamento disponibile", "none": "nessuno", - "running": "corsa", + "running": "in esecuzione", "stopped": "fermato", "unused": "inutilizzato", "unbound": "non vincolato", @@ -3990,7 +3994,7 @@ "name": "Nome", "nameLabel": "Nome", "newRecoveryPassphrase": "Nuova passphrase di ripristino", - "nextRun": "Prossima corsa", + "nextRun": "Prossima esecuzione", "normalizedLabel": "Normalizzato", "onCalendarExpression": "Espressione OnCalendar", "ownerLabel": "Proprietario", @@ -4058,19 +4062,19 @@ "jobNameHelpAnd": "E", "jobNameHelpBefore": "Utilizzato nei nomi e nei registri dei timer. Sono inclusi anche i caratteri consentiti", "jobNameLocked": "Il nome del lavoro viene bloccato dopo la creazione.", - "lastRun": "Ultima corsa", - "lastRunLabel": "Ultima corsa", + "lastRun": "Ultima esecuzione", + "lastRunLabel": "Ultima esecuzione", "liveLogSize": "dal vivo · {size}", "loadFailed": "Impossibile caricare i lavori", "loadingJob": "Caricamento lavoro...", "manualOneShot": "manuale/one-shot", "manualOneShotDescription": "Backup one-shot: acquisito al momento dell'attivazione. Non può essere rieseguito o modificato.", - "neverRun": "non correre mai", + "neverRun": "mai eseguito", "newScheduledJob": "Nuovo lavoro programmato", "newScheduledJobDescription": "Creare un processo di backup dell'host ricorrente.", - "nextRun": "Prossima corsa", - "nextRunLabel": "Prossima corsa", - "nextRunTitle": "Prossima corsa programmata", + "nextRun": "Prossima esecuzione", + "nextRunLabel": "Prossima esecuzione", + "nextRunTitle": "Prossima esecuzione pianificata", "noCompatiblePveJob": "Nessun processo di backup PVE compatibile trovato", "noCompatiblePveJobDescriptionAfter": "Primo.", "noCompatiblePveJobDescriptionBefore": "Crea o abilita un processo di backup PVE per", @@ -4196,7 +4200,7 @@ "inProgress": "Backup manuale in corso", "oneShotDescription": "I backup one-shot vengono conservati come processi congelati in modo da poterne controllare il registro in un secondo momento.", "reopenLogTitle": "Riapri registro", - "run": "Correre", + "run": "Esegui", "runBackup": "Esegui il backup", "runOneShotBackup": "Esegui il backup one-shot", "title": "Backup manuale", @@ -4372,7 +4376,7 @@ "manual": "manuale", "manualOneShot": "manuale/one-shot", "ok": "OK", - "running": "corsa", + "running": "in esecuzione", "scheduled": "programmato" }, "taskStates": { @@ -4381,7 +4385,7 @@ "packing": "imballaggio", "queued": "in coda", "restoring": "ripristino", - "running": "corsa" + "running": "in esecuzione" }, "usb": { "description": "Monta le unità USB in modo che possano essere scelte come destinazione locale o Borg. Le unità che già disponevano di un filesystem possono essere rimontate così come sono; è possibile cancellare e formattare le unità raw (nessuna tabella delle partizioni).", diff --git a/AppImage/messages/pt/common.json b/AppImage/messages/pt/common.json index b1d59453..00fc6405 100644 --- a/AppImage/messages/pt/common.json +++ b/AppImage/messages/pt/common.json @@ -327,7 +327,7 @@ "statusValues": { "passed": "Aprovado", "failed": "Fracassado", - "running": "Correndo", + "running": "Em execução", "aborted": "Abortado", "unknown": "Desconhecido" } @@ -882,10 +882,10 @@ "executable": "Executável", "workingDir": "Diretório de trabalho", "started": "Iniciado", - "runningFor": "Correndo para" + "runningFor": "Em execução há" }, "states": { - "running": "Correndo", + "running": "Em execução", "sleeping": "Dormindo", "diskWait": "Espera de disco", "zombie": "Zumbi", @@ -905,7 +905,7 @@ "totalCpuAllocated": "Total de CPU alocada", "totalMemory": "Memória total", "totalDisk": "Disco total", - "running": "correndo", + "running": "em execução", "stopped": "parou", "vms": "VMs", "used": "Usado", @@ -1032,7 +1032,7 @@ "tagsPlaceholder": "Adicionar tag…", "tagsNone": "sem tags", "mountErrors": { - "configuredButNotMounted": "configurado mas não montado" + "configuredButNotMounted": "Configurado mas não montado" } }, "logs": { @@ -1217,7 +1217,7 @@ "humanWeekly": "Semanal ({day} {time})", "humanMonthly": "Mensalmente (dia {day} às {time})", "humanHourly": "de hora em hora", - "weekdays": "['Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado']" + "weekdays": ["Domingo", "Segunda-feira", "Terça-feira", "Quarta-feira", "Quinta-feira", "Sexta-feira", "Sábado"] }, "cronChip": { "detected": "cron do host detectado", @@ -1567,22 +1567,26 @@ "excludeFromBadgeHelp": "não conte este aplicativo no selo de atualizações agregadas no cartão de lista LXC.Útil quando você está fixado em uma versão específica propositalmente (requisito do rastreador, congelamento de compatibilidade).Não afeta o próprio estado da guia Aplicativo ou a notificação de saída." }, "statusFilter": { - "ariaLabel": "Filtre máquinas virtuais e contêineres por status", + "ariaLabel": "Filtrar máquinas virtuais e contêineres", "all": "Todos", - "running": "Correndo", + "running": "Em execução", "stopped": "Parado", "empty": "Nenhuma máquina virtual ou contêiner com status \"{status}\"" }, "filters": { - "searchPlaceholder": "pesquise nome, ID, tag ou nota…", - "searchAriaLabel": "Pesquise máquinas virtuais e contêineres", - "clearSearch": "Limpar pesquisa", + "searchPlaceholder": "Buscar por nome, ID, tag ou nota…", + "searchAriaLabel": "Buscar máquinas virtuais e contêineres", + "clearSearch": "Limpar busca", "updates": "Atualizações", "resultCount": "{shown} de {total} máquinas", - "noMatches": "Nenhuma VM ou LXC corresponde à sua pesquisa.", - "noRunning": "Não há VMs ou LXCs em execução.", - "noStopped": "Não há VMs ou LXCs parados.", - "noUpdates": "Não LXC com atualizações disponíveis." + "noMatches": "Nenhuma VM ou LXC corresponde à busca.", + "noRunning": "Nenhuma VM ou LXC em execução.", + "noStopped": "Nenhuma VM ou LXC parada.", + "noUpdates": "Nenhum LXC com atualizações disponíveis.", + "typeSynonyms": { + "lxc": "contêiner contêineres contentor contentores", + "qemu": "vm máquina virtual máquinas virtuais" + } } }, "settings": { @@ -2717,7 +2721,7 @@ "executiveSummary": "Sumário executivo", "hardeningAssessment": "Avaliação de endurecimento", "auditOf": "Auditoria de", - "running": "correndo", + "running": "em execução em", "unknownOs": "SO desconhecido", "noActionableWarnings": "Nenhum aviso acionável", "and": "e", @@ -3580,7 +3584,7 @@ "reinstallViaPostInstall": "Reinstale via ProxMenux pós-instalação: {label}", "noUpdatesAvailable": "Nenhuma atualização disponível", "none": "nenhum", - "running": "correndo", + "running": "em execução", "stopped": "parou", "unused": "não utilizado", "unbound": "não vinculado", @@ -4196,7 +4200,7 @@ "inProgress": "Backup manual em andamento", "oneShotDescription": "Os backups únicos são mantidos como trabalhos congelados para que você possa inspecionar seus logs mais tarde.", "reopenLogTitle": "Reabrir registro", - "run": "Correr", + "run": "Executar", "runBackup": "Executar backup", "runOneShotBackup": "Execute o backup único", "title": "Backup manual", @@ -4372,7 +4376,7 @@ "manual": "manual", "manualOneShot": "manual / único", "ok": "OK", - "running": "correndo", + "running": "em execução", "scheduled": "agendado" }, "taskStates": { @@ -4381,7 +4385,7 @@ "packing": "embalagem", "queued": "na fila", "restoring": "restaurando", - "running": "correndo" + "running": "em execução" }, "usb": { "description": "Monte unidades USB para que possam ser escolhidas como alvo local ou Borg. Unidades que já possuem um sistema de arquivos podem ser remontadas como estão; unidades brutas (sem tabela de partição) podem ser apagadas e formatadas para", diff --git a/AppImage/messages/sk/common.json b/AppImage/messages/sk/common.json index 40442c41..c9d87de8 100644 --- a/AppImage/messages/sk/common.json +++ b/AppImage/messages/sk/common.json @@ -932,7 +932,11 @@ "noMatches": "Žiadne VM ani LXC nezodpovedajú hľadaniu.", "noRunning": "Nie sú tu žiadne spustené VM ani LXC.", "noStopped": "Nie sú tu žiadne vypnuté VM ani LXC.", - "noUpdates": "Žiadne LXC nemá dostupné aktualizácie." + "noUpdates": "Žiadne LXC nemá dostupné aktualizácie.", + "typeSynonyms": { + "lxc": "kontajner kontajnery", + "qemu": "vm virtuálny stroj virtuálne stroje" + } }, "uptime": "Beží: {uptime}", "cpuUsage": "Využitie CPU", @@ -1234,7 +1238,15 @@ "humanWeekly": "Týždenne ({day} o {time})", "humanMonthly": "Mesačne ({day}. deň o {time})", "humanHourly": "Každú hodinu", - "weekdays": ["nedeľa", "pondelok", "utorok", "streda", "štvrtok", "piatok", "sobota"] + "weekdays": [ + "nedeľa", + "pondelok", + "utorok", + "streda", + "štvrtok", + "piatok", + "sobota" + ] }, "cronChip": { "detected": "zistený cron na serveri", diff --git a/AppImage/messages/sv/common.json b/AppImage/messages/sv/common.json index 13bd8def..983136c3 100644 --- a/AppImage/messages/sv/common.json +++ b/AppImage/messages/sv/common.json @@ -1217,7 +1217,7 @@ "humanWeekly": "Varje vecka ({day} {time})", "humanMonthly": "Varje månad (dag {day} kl. {time})", "humanHourly": "Varje timme", - "weekdays": "['söndag', 'måndag', 'tisdag', 'onsdag', 'torsdag', 'fredag', 'lördag']" + "weekdays": ["söndag", "måndag", "tisdag", "onsdag", "torsdag", "fredag", "lördag"] }, "cronChip": { "detected": "host cron upptäckt", @@ -1567,22 +1567,26 @@ "excludeFromBadgeHelp": "Räkna inte den här appen i det samlade uppdateringsmärket på LXC-listkortet.Användbart när du är fäst till en specifik version med avsikt (spårningskrav, frysning av kompatibilitet).Påverkar inte appflikens eget tillstånd eller det utgående meddelandet." }, "statusFilter": { - "ariaLabel": "Filtrera virtuella maskiner och behållare efter status", + "ariaLabel": "Filtrera virtuella maskiner och containrar", "all": "Alla", - "running": "Löpning", + "running": "Körs", "stopped": "Stoppad", - "empty": "Inga virtuella maskiner eller behållare med status \"{status}\"" + "empty": "Inga virtuella maskiner eller containrar med status \"{status}\"" }, "filters": { - "searchPlaceholder": "Sök efter namn, ID, tagg eller anteckning...", - "searchAriaLabel": "Sök i virtuella maskiner och behållare", + "searchPlaceholder": "Sök på namn, ID, tagg eller anteckning…", + "searchAriaLabel": "Sök virtuella maskiner och containrar", "clearSearch": "Rensa sökning", "updates": "Uppdateringar", "resultCount": "{shown} av {total} maskiner", - "noMatches": "Inga virtuella datorer eller LXC:er matchar din sökning.", - "noRunning": "Inga virtuella datorer eller LXC:er som körs.", - "noStopped": "Inga stoppade virtuella datorer eller LXC:er.", - "noUpdates": "Inga LXC med tillgängliga uppdateringar." + "noMatches": "Inga VM:ar eller LXC:er matchar sökningen.", + "noRunning": "Inga VM:ar eller LXC:er körs.", + "noStopped": "Inga stoppade VM:ar eller LXC:er.", + "noUpdates": "Inga LXC:er har tillgängliga uppdateringar.", + "typeSynonyms": { + "lxc": "container containrar", + "qemu": "vm virtuell maskin virtuella maskiner" + } } }, "settings": { @@ -4066,7 +4070,7 @@ "loadingJob": "Laddar jobb...", "manualOneShot": "manuell / one-shot", "manualOneShotDescription": "Säkerhetskopiering i ett skott — fångad vid tidpunkten för utlösningen. Kan inte köras om eller redigeras.", - "neverRun": "aldrig springa", + "neverRun": "aldrig körd", "newScheduledJob": "Nytt schemalagt jobb", "newScheduledJobDescription": "Skapa ett återkommande värdbackupjobb.", "nextRun": "Nästa körning", diff --git a/AppImage/scripts/notification_events.py b/AppImage/scripts/notification_events.py index fe3c8755..1394bc72 100644 --- a/AppImage/scripts/notification_events.py +++ b/AppImage/scripts/notification_events.py @@ -2586,6 +2586,11 @@ class PollingCollector: # once for each genuinely new available version. The persistent # history is stored in updates_available.json beside the scan. self._last_post_install_check = 0 + # Re-open that announcement once per service start, so a host + # carrying optimizations it never applied surfaces them again + # after a ProxMenux update or a node reboot instead of staying + # silent forever. Consumed by the first check cycle. + self._post_install_startup_reset_pending = True # Sprint 14.7: fingerprint (item_id → latest_version) of the # last managed-installs update notification, across all types # in the registry. A new notification fires when the @@ -3511,6 +3516,22 @@ class PollingCollector: # ── Post-install function updates check (Sprint 12D) ──────────── + def _reset_post_install_announcements(self): + """Re-open the optimization announcement after a service start. + + ``post_install_update`` sits behind a second gate the other + update events don't have: a per-version history that keeps a + pending optimization from being announced twice. The manager's + ``_EVENT_TYPES_RESET_ON_START`` already clears the delivery + cooldown, so only that history has to be dropped here for the + first cycle to report whatever is still pending. + """ + try: + import post_install_versions + post_install_versions.reset_notified_versions() + except Exception as e: + print(f"[PollingCollector] post-install history reset failed: {e}") + def _check_post_install_updates(self): """Notify the operator when post-install functions have new versions. @@ -3522,6 +3543,9 @@ class PollingCollector: shrinks the pending set and must not produce a second notification. """ now = time.time() + if self._post_install_startup_reset_pending: + self._post_install_startup_reset_pending = False + self._reset_post_install_announcements() if now - self._last_post_install_check < self.UPDATE_CHECK_INTERVAL: return self._last_post_install_check = now diff --git a/AppImage/scripts/notification_manager.py b/AppImage/scripts/notification_manager.py index 064ad831..9edca6f0 100644 --- a/AppImage/scripts/notification_manager.py +++ b/AppImage/scripts/notification_manager.py @@ -1608,7 +1608,7 @@ class NotificationManager: showed zero digest entries even when the schedule was firing (issue #233). """ - host = _hostname(self._config) + host = _resolve_display_hostname(self._config) summary_title = ( f"{host}: 24h summary ({now.strftime('%Y-%m-%d %H:%M')})" ) @@ -1847,23 +1847,44 @@ class NotificationManager: if not rows: return - host = _hostname(self._config) + host = _resolve_display_hostname(self._config) summary_title = ( f"{host}: {len(rows)} events buffered during Quiet Hours" ) summary_body = self._compose_digest_body(rows) + result: dict = {'success': False, 'error': ''} try: - channel.send(summary_title, summary_body, severity='INFO', - data={'_quiet_hours_summary': True, '_count': len(rows)}) + result = channel.send( + summary_title, summary_body, severity='INFO', + data={'_quiet_hours_summary': True, '_count': len(rows)}, + ) or result except Exception as e: print(f"[NotificationManager] quiet send failed for " f"{ch_name}: {e}") - return + result = {'success': False, 'error': str(e)} + + if result.get('success'): + self._stats['total_sent'] += 1 + self._stats['last_sent_at'] = datetime.now().isoformat() + else: + self._stats['total_errors'] += 1 + # Mirrors the digest path: the release is a real delivery, so it + # belongs in the history and the counters the operator reads. + self._record_history( + 'quiet_hours', ch_name, summary_title, summary_body, 'INFO', + result.get('success', False), result.get('error', '') or '', + 'quiet_scheduler', + ) # Only drop the rows after a successful send so a transient # transport failure (Telegram timeout, SMTP outage) doesn't - # lose the user's overnight context. + # lose the user's overnight context. A channel reporting failure + # without raising counts as a failure here too — otherwise the + # buffer is wiped for a summary the operator never received. + if not result.get('success'): + return + try: ids = [r[0] for r in rows] conn = sqlite3.connect(str(DB_PATH), timeout=10) @@ -2082,6 +2103,7 @@ class NotificationManager: 'secure_gateway_update_available', 'app_update_available', 'docker_stack_update_available', + 'post_install_update', # Security events that must not be silenced by stale cooldowns # following a Monitor reinstall (Pedro Rico, 19/05). 'auth_fail', diff --git a/AppImage/scripts/post_install_versions.py b/AppImage/scripts/post_install_versions.py index 79dcd605..ddd4f40d 100644 --- a/AppImage/scripts/post_install_versions.py +++ b/AppImage/scripts/post_install_versions.py @@ -434,6 +434,29 @@ def scan(persist: bool = True) -> dict[str, Any]: return snapshot +def reset_notified_versions() -> None: + """Forget which optimization versions have already been announced. + + Each version is announced once, so a host that never applies a + pending optimization would otherwise stay silent about it forever. + Clearing the history reopens that single announcement. + + The caller owns the timing: the notification collector runs this on + its first cycle after a service start, together with clearing the + matching delivery cooldown, because forgetting the history while the + cooldown still suppresses delivery would consume the announcement + without ever sending it. + """ + try: + with _cache_lock: + scanned_at = float(_cache.get("scanned_at", 0.0) or 0.0) + updates = list(_cache.get("updates", [])) + _write_persisted_snapshot(scanned_at, updates, {}) + except OSError as e: + # Read-only host: de-duplication stays best-effort, as elsewhere. + print(f"[post_install_versions] could not reset notified versions: {e}") + + def scan_at_startup() -> dict[str, Any]: """Convenience wrapper called from flask_server startup.