diff --git a/.github/workflows/checkPHP.yml b/.github/workflows/checkPHP.yml index a6cbfbe3..24a76b5e 100644 --- a/.github/workflows/checkPHP.yml +++ b/.github/workflows/checkPHP.yml @@ -12,8 +12,8 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: PHP Syntax Checker (Lint) - uses: StephaneBour/actions-php-lint@8.2 + uses: StephaneBour/actions-php-lint@8.4 with: dir: '.' diff --git a/.gitignore b/.gitignore index 8b137891..a22002a0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,55 @@ +# IDE & Editors +.vscode/* +!.vscode/extensions.json +!.vscode/cspell.json +!.vscode/settings.json +# OS Generated Files +# Windows +Thumbs.db +ehthumbs.db +Desktop.ini +$RECYCLE.BIN/ + +# Editor Temporary Files +*.swp +*.swo +*~ +*.bak +*.orig + +# PHP Temporary Files +*.tmp +*.cache + +# Environment & Configuration +.env +.env.local +.env.*.local + +# Logs +logs/ +*.log + +# Temporary Directories +tmp/ +temp/ +cache/ + +# PHP Development Tools +.php_cs.cache +.php-cs-fixer.cache + +# PHPUnit +/phpunit.xml +/.phpunit.result.cache +/coverage/ + +# Build & Distribution +/build/ +/dist/ + +# Security & Credentials +*.pem +*.key +id_rsa* diff --git a/.vscode/cspell.json b/.vscode/cspell.json new file mode 100644 index 00000000..4e41ac8a --- /dev/null +++ b/.vscode/cspell.json @@ -0,0 +1,117 @@ +{ + "version": "0.2", + "words": [ + "addressip", + "ajax", + "armv", + "asus", + "asuswrt", + "boottime", + "buffcache", + "buildno", + "buildnumber", + "CACHEDEV", + "cartereseau", + "cartereseauautre", + "cartesreseau", + "clientlist", + "colorhigh", + "colorlow", + "commandtab", + "coretemp", + "cpufreq", + "Cubie", + "dataresult", + "debian", + "dependancy", + "depinstall", + "deporte", + "distri", + "dnsmasq", + "ehthumbs", + "ELEC", + "Eqlogic", + "eqlogictab", + "equipement", + "Equipement", + "esata", + "Etat", + "Eteindre", + "Etes", + "firmver", + "Formated", + "freebsd", + "getcfg", + "getconf", + "getconfiguration", + "getsysinfo", + "gsub", + "hddesata", + "hddusb", + "hddv", + "Historized", + "htmlstate", + "hwmon", + "iface", + "ifname", + "ifnames", + "isset", + "jffs", + "loadavg", + "Localor", + "localorremote", + "localoudistant", + "maitreesclave", + "medion", + "mips", + "mmcblk", + "Monitoringcli", + "Namedashboard", + "Namemobile", + "ncpu", + "netauto", + "netautre", + "nvram", + "Odroid", + "paramaction", + "PCPVERS", + "pcpversion", + "phpunit", + "physmem", + "portssh", + "POSTSAVE", + "poweroffcmd", + "productid", + "productversion", + "PULLCUSTOM", + "PULLLOCAL", + "Rafraichir", + "realip", + "rebootcmd", + "RXTX", + "Ryzen", + "smallfixnumber", + "SSHM", + "sshmanager", + "sunxi", + "swapinfo", + "syno", + "synoinfo", + "synologyesata", + "synologyusb", + "synologyv", + "temppath", + "tempsense", + "Thresholdd", + "timeoutsrv", + "timeoutssh", + "totalsize", + "txrx", + "unconfigured", + "upnpmodelname", + "virt", + "wlan", + "Wyse" + ], + "ignoreWords": [] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index e7d6b117..9e26dfee 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,106 +1 @@ -{ - "cSpell.words": [ - "addressip", - "armv", - "asus", - "asuswrt", - "boottime", - "buffcache", - "buildno", - "buildnumber", - "CACHEDEV", - "cartereseau", - "cartereseauautre", - "cartesreseau", - "clientlist", - "colorhigh", - "colorlow", - "commandtab", - "coretemp", - "cpufreq", - "Cubie", - "dataresult", - "debian", - "dependancy", - "depinstall", - "deporte", - "distri", - "dnsmasq", - "ELEC", - "Eqlogic", - "eqlogictab", - "equipement", - "Equipement", - "Etat", - "firmver", - "Formated", - "freebsd", - "getcfg", - "getconf", - "getconfiguration", - "getsysinfo", - "gsub", - "hddesata", - "hddusb", - "hddv", - "Historized", - "hwmon", - "iface", - "ifname", - "ifnames", - "jffs", - "loadavg", - "Localor", - "localorremote", - "localoudistant", - "maitreesclave", - "medion", - "mips", - "mmcblk", - "Monitoringcli", - "Namedashboard", - "Namemobile", - "ncpu", - "netauto", - "netautre", - "nvram", - "Odroid", - "paramaction", - "PCPVERS", - "pcpversion", - "physmem", - "portssh", - "POSTSAVE", - "poweroffcmd", - "productid", - "productversion", - "PULLCUSTOM", - "PULLLOCAL", - "Rafraichir", - "realip", - "rebootcmd", - "RXTX", - "Ryzen", - "smallfixnumber", - "SSHM", - "sshmanager", - "sunxi", - "swapinfo", - "syno", - "synoinfo", - "synologyesata", - "synologyusb", - "synologyv", - "temppath", - "tempsense", - "Thresholdd", - "timeoutsrv", - "timeoutssh", - "totalsize", - "txrx", - "upnpmodelname", - "virt", - "wlan", - "Wyse" - ] -} \ No newline at end of file +{} \ No newline at end of file diff --git a/core/ajax/Monitoring.ajax.php b/core/ajax/Monitoring.ajax.php index 031ee441..f48b0b0b 100644 --- a/core/ajax/Monitoring.ajax.php +++ b/core/ajax/Monitoring.ajax.php @@ -41,6 +41,73 @@ } } + if (init('action') == 'getHealthData') { + $eqLogics = eqLogic::byType('Monitoring'); + $healthData = array(); + + foreach ($eqLogics as $eqLogic) { + $type = $eqLogic->getConfiguration('localoudistant', ''); + + // Get last refresh from uptime command collectDate + $uptimeCmd = $eqLogic->getCmd('info', 'uptime'); + $lastUptime = null; + if (is_object($uptimeCmd)) { + $lastUptime = $uptimeCmd->getCollectDate(); + } + + $eqData = array( + 'id' => $eqLogic->getId(), + 'name' => $eqLogic->getName(), + 'isEnable' => (int)$eqLogic->getIsEnable(), + 'isVisible' => (int)$eqLogic->getIsVisible(), + 'type' => $type !== '' ? $type : 'unconfigured', + 'sshHostId' => $eqLogic->getConfiguration('SSHHostId', ''), + 'sshHostName' => '', + 'lastRefresh' => $lastUptime, + 'cronCustom' => (int)$eqLogic->getConfiguration('pull_use_custom', 0), + 'commands' => array() + ); + + // Get SSH host name if distant + if ($eqData['type'] === 'distant' && $eqData['sshHostId'] !== '') { + $sshHost = eqLogic::byId($eqData['sshHostId']); + if (is_object($sshHost)) { + $eqData['sshHostName'] = $sshHost->getName(); + } + } + + // Get specific commands values by logicalId + $cmdLogicalIds = array( + 'sshStatus' => 'cnx_ssh', + 'cronStatus' => 'cron_status', + 'uptime' => 'uptime', + 'loadAvg1' => 'load_avg_1mn', + 'loadAvg5' => 'load_avg_5mn', + 'loadAvg15' => 'load_avg_15mn', + 'ip' => 'network_ip' + ); + + foreach ($cmdLogicalIds as $key => $logicalId) { + $cmd = $eqLogic->getCmd('info', $logicalId); + if (is_object($cmd)) { + $eqData['commands'][$key] = array( + 'id' => $cmd->getId(), + 'value' => $cmd->execCmd(), + 'unit' => $cmd->getUnite(), + 'collectDate' => $cmd->getCollectDate(), + 'valueDate' => $cmd->getValueDate() + ); + } else { + $eqData['commands'][$key] = null; + } + } + + $healthData[] = $eqData; + } + + ajax::success($healthData); + } + throw new Exception(__('Aucune méthode correspondante à : ', __FILE__) . init('action')); /* * *********Catch exeption*************** */ } catch (Exception $e) { diff --git a/core/class/Monitoring.class.php b/core/class/Monitoring.class.php index f1019b7d..3d3f2ef7 100644 --- a/core/class/Monitoring.class.php +++ b/core/class/Monitoring.class.php @@ -241,12 +241,14 @@ public static function pullCustom($_options) { $mem_stats = config::byKey('configStatsMemCustom', 'Monitoring', '0') == '1' ? true : false; if ($mem_stats) { $mem_start_usage = memory_get_usage(); - log::add('Monitoring', 'info', '[PULLCUSTOM] Memory Usage Start :: ' . round($mem_start_usage / 1024, 2) . ' Ko'); } /** @var Monitoring $Monitoring */ $Monitoring = Monitoring::byId($_options['Monitoring_Id']); if (is_object($Monitoring)) { + if ($mem_stats) { + log::add('Monitoring', 'info', '[' . $Monitoring->getName() .'][PULLCUSTOM] Memory Usage Start :: ' . round($mem_start_usage / 1024, 2) . ' Ko'); + } $cronState = $Monitoring->getCmd(null, 'cron_status'); if (is_object($cronState) && $cronState->execCmd() === 0) { log::add('Monitoring', 'debug', '[' . $Monitoring->getName() .'][PULLCUSTOM] Pull (Custom) :: En Pause'); @@ -270,20 +272,20 @@ public static function pullCustom($_options) { } $Monitoring->refreshWidget(); } + if ($mem_stats) { + $mem_end_usage = memory_get_usage(); + log::add('Monitoring', 'info', '[' . $Monitoring->getName() .'][PULLCUSTOM] Memory Usage End :: ' . round($mem_end_usage / 1024, 2) . ' Ko | Conso :: ' . round(($mem_end_usage - $mem_start_usage) / 1024, 2) . ' Ko'); + } } - if ($mem_stats) { - $mem_end_usage = memory_get_usage(); - log::add('Monitoring', 'info', '[PULLCUSTOM] Memory Usage End :: ' . round($mem_end_usage / 1024, 2) . ' Ko | Conso :: ' . round(($mem_end_usage - $mem_start_usage) / 1024, 2) . ' Ko'); - } } - public static function postConfig_configPullLocal($value) { - log::add('Monitoring', 'debug', '[CONFIG-SAVE] Configuration PullLocal :: '. $value); - } - + public static function postConfig_configPullLocal($value) { + log::add('Monitoring', 'debug', '[CONFIG-SAVE] Configuration PullLocal :: ' . $value); + } + public static function postConfig_configPull($value) { - log::add('Monitoring', 'debug', '[CONFIG-SAVE] Configuration Pull :: '. $value); - } + log::add('Monitoring', 'debug', '[CONFIG-SAVE] Configuration Pull :: ' . $value); + } // Fonction exécutée automatiquement avant la suppression de l'équipement public function preRemove() { @@ -2116,10 +2118,10 @@ public function postSave() { $cron->save(); } else { $cron = cron::byClassAndFunction('Monitoring', 'pullCustom', array('Monitoring_Id' => intval($this->getId()))); - if (is_object($cron)) { - log::add('Monitoring', 'debug', '['. $this->getName() .'][POSTSAVE] Remove CustomPull'); - $cron->remove(); - } + if (is_object($cron)) { + log::add('Monitoring', 'debug', '[' . $this->getName() . '][POSTSAVE] Remove CustomPull'); + $cron->remove(); + } } $this->getInformations(); @@ -2287,18 +2289,26 @@ public function toHtml($_version = 'dashboard') {
#network_' . $if_safe . '_icon#
'; } } @@ -2319,9 +2329,11 @@ public function toHtml($_version = 'dashboard') { $replace['#multi_network_cards#'] = template_replace($replace, $multi_network_html); } - // Use a specific template for AsusWRT and default for others + // Use specific templates for AsusWRT, Synology, and default for others if ($this->getConfiguration('asuswrt') == '1') { $template = 'AsusWRT'; + } elseif ($this->getConfiguration('synology') == '1') { + $template = 'Synology'; } else { $template = 'Monitoring'; } @@ -2615,7 +2627,11 @@ public function getCmdReplace(string $cmdName, array $cmdOptions, &$replace) { $replace[$cmdNamePrefix . '_averageHistory#'] = $isCmdObject ? $cmd->getConfiguration($cmdName . '_averageHistory') : '-'; $replace[$cmdNamePrefix . '_minHistory#'] = $isCmdObject ? $cmd->getConfiguration($cmdName . '_minHistory') : '-'; $replace[$cmdNamePrefix . '_maxHistory#'] = $isCmdObject ? $cmd->getConfiguration($cmdName . '_maxHistory') : '-'; - $replace[$cmdNamePrefix . '_tendance#'] = ($isCmdObject && $cmd->getConfiguration($cmdName . '_tendance', '') !== '') ? ' ' : ''; + // Utiliser htmlspecialchars pour échapper les attributs HTML afin d'éviter les conflits avec les guillemets + $tendanceIcon = ($isCmdObject && $cmd->getConfiguration($cmdName . '_tendance', '') !== '') + ? ' getConfiguration($cmdName . '_tendance') . '">' + : ''; + $replace[$cmdNamePrefix . '_tendance#'] = $tendanceIcon; } ]; diff --git a/core/i18n/de_DE.json b/core/i18n/de_DE.json index 40b84403..9126fed0 100644 --- a/core/i18n/de_DE.json +++ b/core/i18n/de_DE.json @@ -132,15 +132,47 @@ "Aucune": "Keine", "Choisir une icône": "Symbol auswählen", "Commande info liée": "Verwandte Info-Befehle", + "Fermer": "Schließen", "Historiser": "Historisieren", "Nom de la commande": "Name des Befehls", "Orange": "Orange", "Rouge": "Rot", + "Santé des équipements Monitoring": "Gesundheit der Anlagen Überwachung", "Supprimer la commande": "Befehl löschen", "Tester": "Testen", "Unité": "Einheit", "Vert": "Grün" }, + "plugins\/Monitoring\/desktop\/js\/health.monitoring.js": { + "Aucun équipement trouvé": "Keine Geräte gefunden", + "Non configuré": "Nicht konfiguriert" + }, + "plugins\/Monitoring\/desktop\/modal\/health.monitoring.php": { + "401 - Accès non autorisé": "401 - Unberechtigter Zugriff", + "Actif": "Aktiv", + "Adresse IP": "IP-Adresse", + "Autres termes : recherche par nom, IP, charge, etc.": "Weitere Begriffe: Suche nach Name, IP, Last usw.", + "Charge 15min": "Ladezeit 15 Min.", + "Charge 1min": "Ladezeit 1 Min.", + "Charge 5min": "Ladezeit 5 Min.", + "Chargement des données...": "Daten werden geladen...", + "Cron Status": "Cron Status", + "Dernière Communication": "Letzte Mitteilung", + "Effacer la recherche": "Suche löschen", + "Filtres de statut disponibles": "Verfügbare Statusfilter", + "Hôte SSH": "SSH-Host", + "Nom": "Name", + "Rechercher...": "Suchen...", + "Résumé de l'état de santé de tous vos équipements Monitoring": "Zusammenfassung des Zustands aller Ihrer Geräte Überwachung", + "SSH Status": "SSH-Status", + "Statut SSH": "SSH-Status", + "Statut du cron": "Cron-Status", + "Type": "Typ", + "Uptime": "Uptime", + "Visible": "Sichtbar", + "Équipement actif": "Aktive Geräte", + "Équipement visible": "Sichtbare Ausstattung" + }, "plugins\/Monitoring\/desktop\/php\/Monitoring.php": { "1er port Ethernet": "1. Ethernet-Anschluss", "1er port Wi-Fi": "1. Wi-Fi-Anschluss", @@ -214,6 +246,7 @@ "Rechercher": "Suche", "Saisir le nom de la carte": "Geben Sie den Namen der Karte ein", "Saisir le timeout SRV": "SRV-Timeout eingeben", + "Santé": "Gesundheit", "Sauvegarder": "Speichern", "Si cette option n'est pas cochée, le cron par défaut du plugin sera utilisé": "Wenn diese Option nicht angekreuzt ist, wird der Standard-Cron des Plugins verwendet", "Statistiques": "Statistiken", @@ -234,7 +267,8 @@ "[PLUGIN] Impossible de charger le fichier sshmanager.helper.js (Vérifiez les dépendances)": "[PLUGIN] Die Datei sshmanager.helper.js konnte nicht geladen werden (Überprüfen Sie die Abhängigkeiten)", "[Plugin :: Monitoring] Attention - Version Jeedom !": "[Plugin :: Monitoring] Achtung - Jeedom Version!", "eth1 : 2ème port Ethernet, wlan1 : 2ème port Wi-Fi...": "eth1: 2. Ethernet-Anschluss, wlan1: 2. Wi-Fi-Anschluss...", - "eth2, eth5, enp0s3": "eth2, eth5, enp0s3" + "eth2, eth5, enp0s3": "eth2, eth5, enp0s3", + "Éditer cet hôte SSH": "Diesen SSH-Host bearbeiten" }, "plugins\/Monitoring\/plugin_info\/configuration.php": { "Activer ou Désactiver l'affichage des statistiques mémoire de l'équipement local dans les logs": "Aktivieren oder Deaktivieren der Anzeige der Speicherstatistiken des lokalen Geräts in den Protokollen", diff --git a/core/i18n/en_US.json b/core/i18n/en_US.json index 632f41e5..2b29e59c 100644 --- a/core/i18n/en_US.json +++ b/core/i18n/en_US.json @@ -132,15 +132,47 @@ "Aucune": "No", "Choisir une icône": "Choose an icon", "Commande info liée": "Related info command", + "Fermer": "Close", "Historiser": "History", "Nom de la commande": "Command name", "Orange": "Orange", "Rouge": "Red", + "Santé des équipements Monitoring": "Equipment health monitoring", "Supprimer la commande": "Delete command", "Tester": "Test", "Unité": "Unit", "Vert": "Green" }, + "plugins\/Monitoring\/desktop\/js\/health.monitoring.js": { + "Aucun équipement trouvé": "No equipment found", + "Non configuré": "Not configured" + }, + "plugins\/Monitoring\/desktop\/modal\/health.monitoring.php": { + "401 - Accès non autorisé": "401 - Unauthorized access", + "Actif": "Active", + "Adresse IP": "IP address", + "Autres termes : recherche par nom, IP, charge, etc.": "Other terms: search by name, IP, load, etc.", + "Charge 15min": "Charge 15 min", + "Charge 1min": "Charge 1 min", + "Charge 5min": "Charge 5 min", + "Chargement des données...": "Loading data...", + "Cron Status": "Cron Status", + "Dernière Communication": "Latest Communication", + "Effacer la recherche": "Clear search", + "Filtres de statut disponibles": "Available status filters", + "Hôte SSH": "SSH host", + "Nom": "Name", + "Rechercher...": "Search...", + "Résumé de l'état de santé de tous vos équipements Monitoring": "Summary of the health status of all your equipment Monitoring", + "SSH Status": "SSH Status", + "Statut SSH": "SSH status", + "Statut du cron": "Cron status", + "Type": "Type", + "Uptime": "Uptime", + "Visible": "Visible", + "Équipement actif": "Active equipment", + "Équipement visible": "Visible equipment" + }, "plugins\/Monitoring\/desktop\/php\/Monitoring.php": { "1er port Ethernet": "1st Ethernet port", "1er port Wi-Fi": "1st Wi-Fi port", @@ -214,6 +246,7 @@ "Rechercher": "Search", "Saisir le nom de la carte": "Enter card name", "Saisir le timeout SRV": "Enter SRV timeout", + "Santé": "Health", "Sauvegarder": "Save", "Si cette option n'est pas cochée, le cron par défaut du plugin sera utilisé": "If this option is unchecked, the plugin's default cron will be used", "Statistiques": "Statistics", @@ -234,7 +267,8 @@ "[PLUGIN] Impossible de charger le fichier sshmanager.helper.js (Vérifiez les dépendances)": "[PLUGIN] Unable to load sshmanager.helper.js (Check dependencies)", "[Plugin :: Monitoring] Attention - Version Jeedom !": "[Plugin :: Monitoring] Warning - Jeedom version!", "eth1 : 2ème port Ethernet, wlan1 : 2ème port Wi-Fi...": "eth1: 2nd Ethernet port, wlan1: 2nd Wi-Fi port...", - "eth2, eth5, enp0s3": "eth2, eth5, enp0s3" + "eth2, eth5, enp0s3": "eth2, eth5, enp0s3", + "Éditer cet hôte SSH": "Edit this SSH host" }, "plugins\/Monitoring\/plugin_info\/configuration.php": { "Activer ou Désactiver l'affichage des statistiques mémoire de l'équipement local dans les logs": "Enable or disable the display of local device memory statistics in logs", diff --git a/core/i18n/es_ES.json b/core/i18n/es_ES.json index 7dc5c523..119a66e7 100644 --- a/core/i18n/es_ES.json +++ b/core/i18n/es_ES.json @@ -132,15 +132,47 @@ "Aucune": "No", "Choisir une icône": "Elija un icono", "Commande info liée": "Comando de información relacionado", + "Fermer": "Cerrar", "Historiser": "Historia", "Nom de la commande": "Nombre del comando", "Orange": "Naranja", "Rouge": "Red", + "Santé des équipements Monitoring": "Salud de los equipos Monitorización", "Supprimer la commande": "Eliminar comando", "Tester": "Pruebe", "Unité": "Unidad", "Vert": "Verde" }, + "plugins\/Monitoring\/desktop\/js\/health.monitoring.js": { + "Aucun équipement trouvé": "No se ha encontrado ningún equipo", + "Non configuré": "No configurado" + }, + "plugins\/Monitoring\/desktop\/modal\/health.monitoring.php": { + "401 - Accès non autorisé": "401 - Acceso no autorizado", + "Actif": "Activo", + "Adresse IP": "Dirección IP", + "Autres termes : recherche par nom, IP, charge, etc.": "Otros términos: búsqueda por nombre, IP, carga, etc.", + "Charge 15min": "Carga 15 min", + "Charge 1min": "Carga 1 min", + "Charge 5min": "Carga en 5 minutos", + "Chargement des données...": "Cargando datos...", + "Cron Status": "Estado de Cron", + "Dernière Communication": "Última comunicación", + "Effacer la recherche": "Borrar búsqueda", + "Filtres de statut disponibles": "Filtros de estado disponibles", + "Hôte SSH": "Host SSH", + "Nom": "Nombre", + "Rechercher...": "Buscar...", + "Résumé de l'état de santé de tous vos équipements Monitoring": "Resumen del estado de todos sus equipos Monitoring", + "SSH Status": "Estado de SSH", + "Statut SSH": "Estado SSH", + "Statut du cron": "Estado del cron", + "Type": "Tipo", + "Uptime": "Tiempo de actividad", + "Visible": "Visible", + "Équipement actif": "Equipos activos", + "Équipement visible": "Equipos visibles" + }, "plugins\/Monitoring\/desktop\/php\/Monitoring.php": { "1er port Ethernet": "1er puerto Ethernet", "1er port Wi-Fi": "1er puerto Wi-Fi", @@ -214,6 +246,7 @@ "Rechercher": "Buscar en", "Saisir le nom de la carte": "Introduzca el nombre de la tarjeta", "Saisir le timeout SRV": "Introducir tiempo de espera SRV", + "Santé": "Salud", "Sauvegarder": "Guardar", "Si cette option n'est pas cochée, le cron par défaut du plugin sera utilisé": "Si esta opción no está marcada, se utilizará el cron por defecto del plugin", "Statistiques": "Estadísticas", @@ -234,7 +267,8 @@ "[PLUGIN] Impossible de charger le fichier sshmanager.helper.js (Vérifiez les dépendances)": "[PLUGIN] No se puede cargar sshmanager.helper.js (Comprobar dependencias)", "[Plugin :: Monitoring] Attention - Version Jeedom !": "[Plugin :: Monitorización] Advertencia - ¡Versión Jeedom!", "eth1 : 2ème port Ethernet, wlan1 : 2ème port Wi-Fi...": "eth1: 2º puerto Ethernet, wlan1: 2º puerto Wi-Fi...", - "eth2, eth5, enp0s3": "eth2, eth5, enp0s3" + "eth2, eth5, enp0s3": "eth2, eth5, enp0s3", + "Éditer cet hôte SSH": "Editar este host SSH" }, "plugins\/Monitoring\/plugin_info\/configuration.php": { "Activer ou Désactiver l'affichage des statistiques mémoire de l'équipement local dans les logs": "Activar o desactivar la visualización de las estadísticas de memoria del equipo local en los registros.", diff --git a/core/i18n/it_IT.json b/core/i18n/it_IT.json index b999dbda..2449a783 100644 --- a/core/i18n/it_IT.json +++ b/core/i18n/it_IT.json @@ -132,15 +132,47 @@ "Aucune": "No", "Choisir une icône": "Scegliere un'icona", "Commande info liée": "Comando info correlato", + "Fermer": "Chiudi", "Historiser": "La storia", "Nom de la commande": "Nome del comando", "Orange": "Arancione", "Rouge": "Rosso", + "Santé des équipements Monitoring": "Salute delle apparecchiature Monitoraggio", "Supprimer la commande": "Comando di cancellazione", "Tester": "Test", "Unité": "Unità", "Vert": "Verde" }, + "plugins\/Monitoring\/desktop\/js\/health.monitoring.js": { + "Aucun équipement trouvé": "Nessuna apparecchiatura trovata", + "Non configuré": "Non configurato" + }, + "plugins\/Monitoring\/desktop\/modal\/health.monitoring.php": { + "401 - Accès non autorisé": "401 - Accesso non autorizzato", + "Actif": "Attivo", + "Adresse IP": "Indirizzo IP", + "Autres termes : recherche par nom, IP, charge, etc.": "Altri termini: ricerca per nome, IP, carico, ecc.", + "Charge 15min": "Ricarica 15 min", + "Charge 1min": "Carica 1 min", + "Charge 5min": "Ricarica in 5 minuti", + "Chargement des données...": "Caricamento dati in corso...", + "Cron Status": "Stato di Cron", + "Dernière Communication": "Ultima comunicazione", + "Effacer la recherche": "Cancella ricerca", + "Filtres de statut disponibles": "Filtri di stato disponibili", + "Hôte SSH": "Ospite SSH", + "Nom": "Nome", + "Rechercher...": "Cerca...", + "Résumé de l'état de santé de tous vos équipements Monitoring": "Riepilogo dello stato di salute di tutte le vostre apparecchiature Monitoraggio", + "SSH Status": "Stato SSH", + "Statut SSH": "Stato SSH", + "Statut du cron": "Stato del cron", + "Type": "Tipo", + "Uptime": "Tempo di attività", + "Visible": "Visibile", + "Équipement actif": "Apparecchiature attive", + "Équipement visible": "Apparecchiature visibili" + }, "plugins\/Monitoring\/desktop\/php\/Monitoring.php": { "1er port Ethernet": "1a porta Ethernet", "1er port Wi-Fi": "1a porta Wi-Fi", @@ -214,6 +246,7 @@ "Rechercher": "Ricerca", "Saisir le nom de la carte": "Inserire il nome della scheda", "Saisir le timeout SRV": "Inserire il timeout SRV", + "Santé": "Salute", "Sauvegarder": "Risparmiare", "Si cette option n'est pas cochée, le cron par défaut du plugin sera utilisé": "Se questa opzione non è selezionata, verrà utilizzato il cron predefinito del plugin", "Statistiques": "Statistiche", @@ -234,7 +267,8 @@ "[PLUGIN] Impossible de charger le fichier sshmanager.helper.js (Vérifiez les dépendances)": "[PLUGIN] Impossibile caricare sshmanager.helper.js (controllare le dipendenze)", "[Plugin :: Monitoring] Attention - Version Jeedom !": "[Plugin :: Monitoraggio] Attenzione - versione Jeedom!", "eth1 : 2ème port Ethernet, wlan1 : 2ème port Wi-Fi...": "eth1: seconda porta Ethernet, wlan1: seconda porta Wi-Fi...", - "eth2, eth5, enp0s3": "eth2, eth5, enp0s3" + "eth2, eth5, enp0s3": "eth2, eth5, enp0s3", + "Éditer cet hôte SSH": "Modifica questo host SSH" }, "plugins\/Monitoring\/plugin_info\/configuration.php": { "Activer ou Désactiver l'affichage des statistiques mémoire de l'équipement local dans les logs": "Attivare o disattivare la visualizzazione delle statistiche di memoria dell'apparecchiatura locale nei log", diff --git a/core/i18n/pt_PT.json b/core/i18n/pt_PT.json index e256fcfd..ef437f6a 100644 --- a/core/i18n/pt_PT.json +++ b/core/i18n/pt_PT.json @@ -132,15 +132,47 @@ "Aucune": "Não", "Choisir une icône": "Escolher um ícone", "Commande info liée": "Comando de informação relacionado", + "Fermer": "Fechar", "Historiser": "História", "Nom de la commande": "Nome do comando", "Orange": "Laranja", "Rouge": "Vermelho", + "Santé des équipements Monitoring": "Monitorização da saúde dos equipamentos", "Supprimer la commande": "Apagar comando", "Tester": "Teste", "Unité": "Unidade", "Vert": "Verde" }, + "plugins\/Monitoring\/desktop\/js\/health.monitoring.js": { + "Aucun équipement trouvé": "Nenhum equipamento encontrado", + "Non configuré": "Não configurado" + }, + "plugins\/Monitoring\/desktop\/modal\/health.monitoring.php": { + "401 - Accès non autorisé": "401 - Acesso não autorizado", + "Actif": "Ativo", + "Adresse IP": "Endereço IP", + "Autres termes : recherche par nom, IP, charge, etc.": "Outros termos: pesquisa por nome, IP, carga, etc.", + "Charge 15min": "Carregamento em 15 minutos", + "Charge 1min": "Carregamento em 1 minuto", + "Charge 5min": "Carregamento em 5 minutos", + "Chargement des données...": "Carregando dados...", + "Cron Status": "Estado Cron", + "Dernière Communication": "Última comunicação", + "Effacer la recherche": "Apagar pesquisa", + "Filtres de statut disponibles": "Filtros de estado disponíveis", + "Hôte SSH": "Anfitrião SSH", + "Nom": "Nome", + "Rechercher...": "Pesquisar...", + "Résumé de l'état de santé de tous vos équipements Monitoring": "Resumo do estado de saúde de todos os seus equipamentos Monitorização", + "SSH Status": "Estado do SSH", + "Statut SSH": "Estado SSH", + "Statut du cron": "Estado do cron", + "Type": "Tipo de produto", + "Uptime": "Tempo de funcionamento", + "Visible": "Visível", + "Équipement actif": "Equipamento ativo", + "Équipement visible": "Equipamento visível" + }, "plugins\/Monitoring\/desktop\/php\/Monitoring.php": { "1er port Ethernet": "1ª porta Ethernet", "1er port Wi-Fi": "1ª porta Wi-Fi", @@ -214,6 +246,7 @@ "Rechercher": "Pesquisar", "Saisir le nom de la carte": "Introduzir o nome do cartão", "Saisir le timeout SRV": "Introduzir o tempo limite de SRV", + "Santé": "Saúde", "Sauvegarder": "Guardar", "Si cette option n'est pas cochée, le cron par défaut du plugin sera utilisé": "Se esta opção não estiver selecionada, será utilizado o cron predefinido do plugin", "Statistiques": "Estatísticas", @@ -234,7 +267,8 @@ "[PLUGIN] Impossible de charger le fichier sshmanager.helper.js (Vérifiez les dépendances)": "[PLUGIN] Não foi possível carregar sshmanager.helper.js (Verificar dependências)", "[Plugin :: Monitoring] Attention - Version Jeedom !": "[Plugin :: Monitorização] Aviso - Versão do Jeedom!", "eth1 : 2ème port Ethernet, wlan1 : 2ème port Wi-Fi...": "eth1: 2ª porta Ethernet, wlan1: 2ª porta Wi-Fi...", - "eth2, eth5, enp0s3": "eth2, eth5, enp0s3" + "eth2, eth5, enp0s3": "eth2, eth5, enp0s3", + "Éditer cet hôte SSH": "Editar este host SSH" }, "plugins\/Monitoring\/plugin_info\/configuration.php": { "Activer ou Désactiver l'affichage des statistiques mémoire de l'équipement local dans les logs": "Ativar ou desativar a exibição das estatísticas de memória do equipamento local nos registos", diff --git a/core/template/dashboard/AsusWRT.html b/core/template/dashboard/AsusWRT.html index 0fb59438..39ad65ab 100644 --- a/core/template/dashboard/AsusWRT.html +++ b/core/template/dashboard/AsusWRT.html @@ -1,8 +1,12 @@ -
+
+
+
+ + + + + + + + +
+
+ #name_display# #object_name# + #name_display# #object_name# +
+
+ + + +
+
+
+
+ #distri_name_icon# + #distri_name# +
+
+ #uptime_icon# + +
+
+ #load_avg_icon# + +
+
+ #memory_icon# + +
+
+ #swap_icon# + +
+
+ #network_infos_icon# + +
+
+ #network_icon# + +
+ + #multi_network_cards# +
+ #hdd_icon# + +
+
+ #syno_hddv2_icon# + +
+
+ #syno_hddv3_icon# + +
+
+ #syno_hddv4_icon# + +
+
+ #syno_hddusb_icon# + +
+
+ #syno_hddesata_icon# + +
+
+ #cpu_icon# + +
+
+ #perso1_icon# + +
+
+ #perso2_icon# + +
+
+ #perso3_icon# + +
+
+ #perso4_icon# + +
+
+ #divGraphInfo# + +
\ No newline at end of file diff --git a/core/template/mobile/AsusWRT.html b/core/template/mobile/AsusWRT.html index 8e5bdacc..4c3b425c 100644 --- a/core/template/mobile/AsusWRT.html +++ b/core/template/mobile/AsusWRT.html @@ -1,4 +1,6 @@ -
+
+ + + + + + + + + + + + + + #name_display# + + + + + + + + +
+
+ #distri_name_icon# + #distri_name# +
+
+ #uptime_icon# + +
+
+ #load_avg_icon# + +
+
+ #memory_icon# + +
+
+ #swap_icon# + +
+
+ #network_infos_icon# + +
+
+ #network_icon# + +
+ + #multi_network_cards# +
+ #hdd_icon# + +
+
+ #syno_v2_icon# + +
+
+ #syno_v3_icon# + +
+
+ #syno_v4_icon# + +
+
+ #syno_usb_icon# + +
+
+ #syno_esata_icon# + +
+
+ #cpu_icon# + +
+
+ #perso1_icon# + +
+
+ #perso2_icon# + +
+
+ #perso3_icon# + +
+
+ #perso4_icon# + +
+
+ +
\ No newline at end of file diff --git a/desktop/js/Monitoring.js b/desktop/js/Monitoring.js index 4bc87368..0e64252e 100644 --- a/desktop/js/Monitoring.js +++ b/desktop/js/Monitoring.js @@ -14,179 +14,487 @@ * along with Jeedom. If not, see . */ -/* Fonction permettant l'affichage des commandes dans l'équipement */ +// Protect against multiple script loads (Jeedom SPA navigation, cache, etc.) +(() => { +'use strict' + +// DOM Selectors constants (better minification + no string repetition + immutable) +const SELECTORS = Object.freeze({ + TABLE_CMD: '#table_cmd', + EQ_ID: '.eqLogicAttr[data-l1key=id]', + SYNO_CHECKBOX: '.eqLogicAttr[data-l2key=synology]', + QNAP_CHECKBOX: '.eqLogicAttr[data-l2key=qnap]', + ASUS_CHECKBOX: '.eqLogicAttr[data-l2key=asuswrt]', + SYNO_CONF: '.syno_conf', + ASUS_CONF: '.asuswrt_conf' +}) + +// Liste des commandes pouvant être historisées (en constant pour performance) +const HISTORIZED_COMMANDS = Object.freeze([ + 'cron_status', 'uptime_sec', 'load_avg_1mn', 'load_avg_5mn', 'load_avg_15mn', + 'memory_total', 'memory_used', 'memory_free', 'memory_buffcache', 'memory_available', + 'memory_free_percent', 'memory_used_percent', 'memory_available_percent', + 'swap_free_percent', 'swap_used_percent', 'swap_total', 'swap_used', 'swap_free', + 'network_tx', 'network_rx', 'hdd_total', 'hdd_used', 'hdd_free', + 'hdd_used_percent', 'hdd_free_percent', 'cpu_temp', + 'perso1', 'perso2', 'perso3', 'perso4', + 'syno_hddv2_total', 'syno_hddv2_used', 'syno_hddv2_free', 'syno_hddv2_used_percent', 'syno_hddv2_free_percent', + 'syno_hddusb_total', 'syno_hddusb_used', 'syno_hddusb_used_percent', 'syno_hddusb_free', 'syno_hddusb_free_percent', + 'syno_hddesata_total', 'syno_hddesata_used', 'syno_hddesata_used_percent', 'syno_hddesata_free', 'syno_hddesata_free_percent', + 'asus_clients_total', 'asus_clients_wifi24', 'asus_clients_wifi5', 'asus_clients_wired', + 'asus_fw_check', 'asus_wifi2g_temp', 'asus_wifi5g_temp' +]) + +// Commandes avec seuils verts (bas) vers rouges (haut) +const GREEN_TO_RED_COMMANDS = Object.freeze([ + 'load_avg_1mn', 'load_avg_5mn', 'load_avg_15mn', + 'memory_used_percent', 'swap_used_percent', 'cpu_temp', + 'hdd_used_percent', 'syno_hddv2_used_percent', 'syno_hddusb_used_percent', 'syno_hddesata_used_percent' +]) + +// Commandes avec seuils rouges (bas) vers verts (haut) +const RED_TO_GREEN_COMMANDS = Object.freeze([ + 'memory_free_percent', 'swap_free_percent', 'memory_available_percent', + 'hdd_free_percent', 'syno_hddv2_free_percent', 'syno_hddusb_free_percent', 'syno_hddesata_free_percent' +]) + +// Commandes personnalisables +const CUSTOM_COMMANDS = Object.freeze(['perso1', 'perso2', 'perso3', 'perso4']) + +// Commandes liées (pour select value) +const LINKED_COMMANDS = Object.freeze(['cron_status', 'cron_on', 'cron_off']) + +/** + * Helper pour générer les champs de seuils de couleurs + */ +const buildColorThresholds = (logicalId, reverseColors = false) => { + if (reverseColors) { + return `[{{Rouge}}] < ≤ [{{Orange}}] ≤ < [{{Vert}}]` + } + return `[{{Vert}}] < ≤ [{{Orange}}] ≤ < [{{Rouge}}]` +} + +/** + * Fonction permettant l'affichage des commandes dans l'équipement + * @param {Object} _cmd - Commande à ajouter + */ function addCmdToTable(_cmd) { - if (!isset(_cmd)) { - var _cmd = { configuration: {} }; - } - if (!isset(_cmd.configuration)) { - _cmd.configuration = {} - } - - let tr = ''; - tr += ''; - tr += ''; - tr += ''; - tr += ''; - tr += '
' - tr += ''; - tr += ''; - tr += ''; - tr += ''; - tr += '
'; - - if (['cron_status', 'cron_on', 'cron_off'].includes(init(_cmd.logicalId))) { - tr += ''; - } - tr += ''; - tr += ''; - if (['load_avg_1mn', 'load_avg_5mn', 'load_avg_15mn', 'memory_used_percent', 'swap_used_percent', 'cpu_temp', 'hdd_used_percent', 'syno_hddv2_used_percent', 'syno_hddusb_used_percent', 'syno_hddesata_used_percent'].includes(init(_cmd.logicalId))) { - tr += '[{{Vert}}] \< \u{2264} [{{Orange}}] \u{2264} \< [{{Rouge}}]'; - } - if (['memory_free_percent', 'swap_free_percent', 'memory_available_percent', 'hdd_free_percent', 'syno_hddv2_free_percent', 'syno_hddusb_free_percent', 'syno_hddesata_free_percent'].includes(init(_cmd.logicalId))) { - tr += '[{{Rouge}}] \< \u{2264} [{{Orange}}] \u{2264} \< [{{Vert}}]'; - } - if (['perso1', 'perso2', 'perso3', 'perso4'].includes(init(_cmd.logicalId))) { - tr += ''; - tr += ' {{Unité}} : '; - tr += '
[{{Vert}}] \< \u{2264} [{{Orange}}] \u{2264} \< [{{Rouge}}]'; - } - tr += ''; - tr += ''; - tr += ''; - - // Liste des commandes de base pouvant être historisées - let historizedCommands = ['cron_status', 'uptime_sec', 'load_avg_1mn', 'load_avg_5mn', 'load_avg_15mn', 'memory_total', 'memory_used', 'memory_free', 'memory_buffcache', 'memory_available', 'memory_free_percent', 'memory_used_percent', 'memory_available_percent', 'swap_free_percent', 'swap_used_percent', 'swap_total', 'swap_used', 'swap_free', 'network_tx', 'network_rx', 'hdd_total', 'hdd_used', 'hdd_free', 'hdd_used_percent', 'hdd_free_percent', 'cpu_temp', 'perso1', 'perso2', 'perso3', 'perso4', 'syno_hddv2_total', 'syno_hddv2_used', 'syno_hddv2_free', 'syno_hddv2_used_percent', 'syno_hddv2_free_percent', 'syno_hddusb_total', 'syno_hddusb_used', 'syno_hddusb_used_percent', 'syno_hddusb_free', 'syno_hddusb_free_percent', 'syno_hddesata_total', 'syno_hddesata_used', 'syno_hddesata_used_percent', 'syno_hddesata_free', 'syno_hddesata_free_percent', 'asus_clients_total', 'asus_clients_wifi24', 'asus_clients_wifi5', 'asus_clients_wired', 'asus_fw_check', 'asus_wifi2g_temp', 'asus_wifi5g_temp']; - - // Vérifier si c'est une commande standard ou une commande de carte réseau supplémentaire - let canBeHistorized = historizedCommands.includes(init(_cmd.logicalId)) || - init(_cmd.logicalId).startsWith('network_tx_') || - init(_cmd.logicalId).startsWith('network_rx_'); - - if (canBeHistorized) { - tr += ''; - } - tr += ''; - tr += ''; - if (['perso1', 'perso2', 'perso3', 'perso4', 'cron_status'].includes(init(_cmd.logicalId))) { - tr += ''; - tr += ''; - } - tr += ''; - tr += ''; - tr += ''; - tr += ''; - - tr += ''; - if (is_numeric(_cmd.id)) { - tr += ' '; - tr += ' {{Tester}}'; - } - tr += ''; - tr += ''; - tr += ''; - - let newRow = document.createElement('tr') - newRow.innerHTML = tr - newRow.addClass('cmd') - newRow.setAttribute('data-cmd_id', init(_cmd.id)) - document.getElementById('table_cmd').querySelector('tbody').appendChild(newRow) - - jeedom.eqLogic.buildSelectCmd({ - id: document.querySelector('.eqLogicAttr[data-l1key="id"]').jeeValue(), - filter: { type: 'info' }, - error: function(error) { - jeedomUtils.showAlert({ message: error.message, level: 'danger' }) - }, - success: function(result) { - newRow.querySelector('.cmdAttr[data-l1key="value"]')?.insertAdjacentHTML('beforeend', result) - newRow.setJeeValues(_cmd, '.cmdAttr') - jeedom.cmd.changeType(newRow, init(_cmd.subType)) - } - }) + if (!isset(_cmd)) { + var _cmd = { configuration: {} } + } + if (!isset(_cmd.configuration)) { + _cmd.configuration = {} + } + + const logicalId = init(_cmd.logicalId) + + // Vérifier si c'est une commande standard ou une commande de carte réseau supplémentaire + const canBeHistorized = HISTORIZED_COMMANDS.includes(logicalId) || + logicalId.startsWith('network_tx_') || + logicalId.startsWith('network_rx_') + + // Déterminer le type de configuration + const hasGreenToRed = GREEN_TO_RED_COMMANDS.includes(logicalId) + const hasRedToGreen = RED_TO_GREEN_COMMANDS.includes(logicalId) + const isCustom = CUSTOM_COMMANDS.includes(logicalId) + const isLinked = LINKED_COMMANDS.includes(logicalId) + const hasTypeSubType = isCustom || logicalId === 'cron_status' + + // Générer les différentes parties du HTML + let configCell = '' + if (hasGreenToRed) { + configCell = buildColorThresholds(logicalId, false) + } else if (hasRedToGreen) { + configCell = buildColorThresholds(logicalId, true) + } else if (isCustom) { + configCell = ` + {{Unité}} : +
${buildColorThresholds(logicalId, false)}` + } + + // Construction du HTML avec template literals (optimal V8 performance) + const testButtons = is_numeric(_cmd.id) + ? ' {{Tester}}' + : '' + + const rowHtml = ` + + +
+ + + + +
+ ${isLinked ? '' : ''} + + ${configCell} + + + ${canBeHistorized ? '' : ''} + + + ${hasTypeSubType ? `` : ''} + + + + ${testButtons} + + ` + + // Create and configure row element (optimal: Object.assign for batch properties) + const newRow = Object.assign(document.createElement('tr'), { + className: 'cmd', + innerHTML: rowHtml + }) + newRow.setAttribute('data-cmd_id', init(_cmd.id)) + + // Cache table body for performance + const tableBody = document.querySelector(`${SELECTORS.TABLE_CMD} tbody`) + if (!tableBody) return console.error('Table body not found') + + tableBody.appendChild(newRow) + + // Cache eqLogic ID to avoid multiple DOM queries + const eqLogicIdElement = document.querySelector(SELECTORS.EQ_ID) + if (!eqLogicIdElement) return console.error('Equipment ID element not found') + + jeedom.eqLogic.buildSelectCmd({ + id: eqLogicIdElement.jeeValue(), + filter: { type: 'info' }, + error: function(error) { + jeedomUtils.showAlert({ message: error.message, level: 'danger' }) + }, + success: function(result) { + newRow.querySelector('.cmdAttr[data-l1key="value"]')?.insertAdjacentHTML('beforeend', result) + newRow.setJeeValues(_cmd, '.cmdAttr') + jeedom.cmd.changeType(newRow, init(_cmd.subType)) + } + }) +} + +// Helper functions pour une meilleure réutilisation +const updateCheckboxGroup = (checkbox, showElements = [], hideElements = [], uncheckElements = []) => { + if (!checkbox) return + + if (checkbox.checked) { + showElements.forEach(el => el?.seen()) + hideElements.forEach(el => el?.unseen()) // Masquer les blocs des autres options + uncheckElements.forEach(el => el?.jeeValue(0)) // Décocher les autres options + } else { + showElements.forEach(el => el?.unseen()) // Masquer le bloc de cette option + } } -document.querySelector(".eqLogicAttr[data-l2key='synology']").addEventListener('change', function() { - if (this.checked) { - document.querySelector(".syno_conf").style.display = "block"; - document.querySelector(".asuswrt_conf").style.display = "none"; - document.querySelector('input.eqLogicAttr[data-l2key="asuswrt"]').checked = false; - document.querySelector('input.eqLogicAttr[data-l2key="qnap"]').checked = false; - } else { - document.querySelector(".syno_conf").style.display = "none"; - } -}); - -document.querySelector(".eqLogicAttr[data-l2key='qnap']").addEventListener('change', function() { - if (this.checked) { - document.querySelector('input.eqLogicAttr[data-l2key="asuswrt"]').checked = false; - document.querySelector('input.eqLogicAttr[data-l2key="synology"]').checked = false; - document.querySelector(".syno_conf").style.display = "none"; - document.querySelector(".asuswrt_conf").style.display = "none"; - } -}); - -document.querySelector(".eqLogicAttr[data-l2key='asuswrt']").addEventListener('change', function() { - if (this.checked) { - document.querySelector('input.eqLogicAttr[data-l2key="qnap"]').checked = false; - document.querySelector('input.eqLogicAttr[data-l2key="synology"]').checked = false; - document.querySelector(".syno_conf").style.display = "none"; - document.querySelector(".asuswrt_conf").style.display = "block"; - } else { - document.querySelector(".asuswrt_conf").style.display = "none"; - } -}); - -document.querySelectorAll('.pluginAction[data-action=openLocation]').forEach(function (element) { - element.addEventListener('click', function () { - window.open(this.getAttribute("data-location"), "_blank", null); - }); -}); - -document.querySelector(".eqLogicAttr[data-l2key='syno_use_temp_path']").addEventListener('change', function () { - if(this.checked){ - document.querySelector(".syno_conf_temppath").style.display = "block"; - } else { - document.querySelector(".syno_conf_temppath").style.display = "none"; - } -}); - -document.querySelector(".eqLogicAttr[data-l2key='linux_use_temp_cmd']").addEventListener('change', function() { - if (this.checked) { - document.querySelector(".linux_class_temp_cmd").style.display = "block"; - } else { - document.querySelector(".linux_class_temp_cmd").style.display = "none"; - } -}); - -document.querySelector(".eqLogicAttr[data-l2key='pull_use_custom']").addEventListener('change', function () { - if(this.checked){ - document.querySelector(".pull_class").style.display = "block"; - } else { - document.querySelector(".pull_class").style.display = "none"; - } -}); - -document.querySelector(".eqLogicAttr[data-l2key='multi_if']").addEventListener('change', function () { - if(this.checked){ - document.querySelector(".multi_if_conf").style.display = "block"; - } else { - document.querySelector(".multi_if_conf").style.display = "none"; - } -}); - -document.querySelector(".eqLogicAttr[data-l2key='localoudistant']").addEventListener('change', function () { - if (this.selectedIndex == 1) { - document.querySelector(".distant").style.display = "block"; - document.querySelector(".local").style.display = "none"; - } else { - document.querySelector(".distant").style.display = "none"; - document.querySelector(".local").style.display = "block"; - } -}); +// Handlers nommés pour pouvoir les remove/add proprement (spécifiques à chaque équipement) +const handleSynologyChange = function(event) { + updateCheckboxGroup( + event.currentTarget, + [document.querySelector(SELECTORS.SYNO_CONF)], + [document.querySelector(SELECTORS.ASUS_CONF)], + [document.querySelector(SELECTORS.ASUS_CHECKBOX), document.querySelector(SELECTORS.QNAP_CHECKBOX)] + ) +} + +const handleQnapChange = function(event) { + if (event.currentTarget.checked) { + const asusCheckbox = document.querySelector(SELECTORS.ASUS_CHECKBOX) + const synoCheckbox = document.querySelector(SELECTORS.SYNO_CHECKBOX) + const synoConf = document.querySelector(SELECTORS.SYNO_CONF) + const asusConf = document.querySelector(SELECTORS.ASUS_CONF) + + asusCheckbox?.jeeValue(0) + synoCheckbox?.jeeValue(0) + synoConf?.unseen() + asusConf?.unseen() + } +} + +const handleAsusChange = function(event) { + updateCheckboxGroup( + event.currentTarget, + [document.querySelector(SELECTORS.ASUS_CONF)], + [document.querySelector(SELECTORS.SYNO_CONF)], + [document.querySelector(SELECTORS.QNAP_CHECKBOX), document.querySelector(SELECTORS.SYNO_CHECKBOX)] + ) +} + +const handleSynoTempPath = function(event) { + const tempPath = document.querySelector('.syno_conf_temppath') + if (event.currentTarget.checked) { + tempPath?.seen() + } else { + tempPath?.unseen() + } +} + +const handleLinuxTempCmd = function(event) { + const tempCmd = document.querySelector('.linux_class_temp_cmd') + if (event.currentTarget.checked) { + tempCmd?.seen() + } else { + tempCmd?.unseen() + } +} + +const handlePullCustom = function(event) { + const pullClass = document.querySelector('.pull_class') + if (event.currentTarget.checked) { + pullClass?.seen() + } else { + pullClass?.unseen() + } +} + +const handleMultiIf = function(event) { + const multiIfConf = document.querySelector('.multi_if_conf') + if (event.currentTarget.checked) { + multiIfConf?.seen() + } else { + multiIfConf?.unseen() + } +} + +const handleLocalDistant = function(event) { + const distantDiv = document.querySelector('.distant') + const localDiv = document.querySelector('.local') + const selectedValue = event.currentTarget.value + + if (selectedValue === 'distant') { + distantDiv?.seen() + localDiv?.unseen() + } else { + distantDiv?.unseen() + localDiv?.seen() + } +} + +// Event delegation pour openLocation (global, attaché une seule fois) +if (!window.monitoringOpenLocationAttached) { + window.monitoringOpenLocationAttached = true + + document.addEventListener('click', (event) => { + const target = event.target.closest('.pluginAction[data-action=openLocation]') + if (target) { + event.preventDefault() + window.open(target.getAttribute('data-location'), '_blank', null) + } + }) +} function printEqLogic(_eqLogic) { - buildSelectHost(_eqLogic.configuration.SSHHostId); + if (!_eqLogic) return + + // Cache DOM elements once + const elements = { + synoCheckbox: document.querySelector(SELECTORS.SYNO_CHECKBOX), + qnapCheckbox: document.querySelector(SELECTORS.QNAP_CHECKBOX), + asusCheckbox: document.querySelector(SELECTORS.ASUS_CHECKBOX), + synoConf: document.querySelector(SELECTORS.SYNO_CONF), + asusConf: document.querySelector(SELECTORS.ASUS_CONF), + synoTempPath: document.querySelector('.eqLogicAttr[data-l2key="syno_use_temp_path"]'), + synoTempPathDiv: document.querySelector('.syno_conf_temppath'), + linuxTempCmd: document.querySelector('.eqLogicAttr[data-l2key="linux_use_temp_cmd"]'), + linuxTempCmdDiv: document.querySelector('.linux_class_temp_cmd'), + pullCustom: document.querySelector('.eqLogicAttr[data-l2key="pull_use_custom"]'), + pullDiv: document.querySelector('.pull_class'), + multiIf: document.querySelector('.eqLogicAttr[data-l2key="multi_if"]'), + multiIfDiv: document.querySelector('.multi_if_conf'), + localDistant: document.querySelector('.eqLogicAttr[data-l2key="localoudistant"]'), + distantDiv: document.querySelector('.distant'), + localDiv: document.querySelector('.local') + } + + // Attach event listeners for equipment-specific checkboxes (re-attached on each equipment load) + if (elements.synoCheckbox) { + elements.synoCheckbox.removeEventListener('change', handleSynologyChange) + elements.synoCheckbox.addEventListener('change', handleSynologyChange) + // Initialiser l'affichage au chargement + if (elements.synoCheckbox.checked) { + elements.synoConf?.seen() + elements.asusConf?.unseen() + elements.asusCheckbox?.jeeValue(0) + elements.qnapCheckbox?.jeeValue(0) + } else { + elements.synoConf?.unseen() + } + } + + if (elements.qnapCheckbox) { + elements.qnapCheckbox.removeEventListener('change', handleQnapChange) + elements.qnapCheckbox.addEventListener('change', handleQnapChange) + } + + if (elements.asusCheckbox) { + elements.asusCheckbox.removeEventListener('change', handleAsusChange) + elements.asusCheckbox.addEventListener('change', handleAsusChange) + // Initialiser l'affichage au chargement + if (elements.asusCheckbox.checked) { + elements.asusConf?.seen() + elements.synoConf?.unseen() + elements.qnapCheckbox?.jeeValue(0) + elements.synoCheckbox?.jeeValue(0) + } else { + elements.asusConf?.unseen() + } + } + + if (elements.synoTempPath) { + elements.synoTempPath.removeEventListener('change', handleSynoTempPath) + elements.synoTempPath.addEventListener('change', handleSynoTempPath) + // Initialiser l'affichage au chargement + if (elements.synoTempPath.checked) { + elements.synoTempPathDiv?.seen() + } else { + elements.synoTempPathDiv?.unseen() + } + } + + if (elements.linuxTempCmd) { + elements.linuxTempCmd.removeEventListener('change', handleLinuxTempCmd) + elements.linuxTempCmd.addEventListener('change', handleLinuxTempCmd) + // Initialiser l'affichage au chargement + if (elements.linuxTempCmd.checked) { + elements.linuxTempCmdDiv?.seen() + } else { + elements.linuxTempCmdDiv?.unseen() + } + } + + if (elements.pullCustom) { + elements.pullCustom.removeEventListener('change', handlePullCustom) + elements.pullCustom.addEventListener('change', handlePullCustom) + // Initialiser l'affichage au chargement + if (elements.pullCustom.checked) { + elements.pullDiv?.seen() + } else { + elements.pullDiv?.unseen() + } + } + + if (elements.multiIf) { + elements.multiIf.removeEventListener('change', handleMultiIf) + elements.multiIf.addEventListener('change', handleMultiIf) + // Initialiser l'affichage au chargement + if (elements.multiIf.checked) { + elements.multiIfDiv?.seen() + } else { + elements.multiIfDiv?.unseen() + } + } + + if (elements.localDistant) { + elements.localDistant.removeEventListener('change', handleLocalDistant) + elements.localDistant.addEventListener('change', handleLocalDistant) + // Initialiser l'affichage au chargement + if (elements.localDistant.value === 'distant') { + elements.distantDiv?.seen() + elements.localDiv?.unseen() + } else { + elements.distantDiv?.unseen() + elements.localDiv?.seen() + } + } + + // Build SSH host select + const buildPromise = buildSelectHost(_eqLogic.configuration.SSHHostId) + + // Toggle add/edit button based on SSH host selection + const sshHostSelect = document.querySelector('.sshmanagerHelper[data-helper="list"]') + if (sshHostSelect) { + // Remove existing listener to avoid duplicates + sshHostSelect.removeEventListener('change', toggleSSHButtons) + // Attach listener + sshHostSelect.addEventListener('change', toggleSSHButtons) + + // Initialize button display - pass the value directly instead of waiting + if (buildPromise && buildPromise.then) { + buildPromise.then(() => { + toggleSSHButtons(_eqLogic.configuration.SSHHostId) + }) + } else { + // Fallback if buildSelectHost didn't return a promise + toggleSSHButtons(_eqLogic.configuration.SSHHostId) + } + } +} + +/** + * Toggle between add and edit SSH buttons based on selection + * @param {Event|string|number} eventOrValue - Either a change event or a direct value (SSHHostId) + */ +function toggleSSHButtons(eventOrValue) { + let selectedValue + + // Check if it's a direct value (string/number) or an event object + if (typeof eventOrValue === 'string' || typeof eventOrValue === 'number') { + selectedValue = eventOrValue + } else if (eventOrValue?.target || eventOrValue?.currentTarget) { + // It's an event, extract value from it + selectedValue = eventOrValue.target?.value ?? eventOrValue.currentTarget?.value ?? eventOrValue.value + } + + // If still no value, read directly from the select element as fallback + if (!selectedValue) { + const sshHostSelect = document.querySelector('.sshmanagerHelper[data-helper="list"]') + selectedValue = sshHostSelect?.value + } + + const addBtn = document.querySelector('.sshmanagerHelper[data-helper="add"]') + const editBtn = document.querySelector('.sshmanagerHelper[data-helper="edit"]') + + if (selectedValue && selectedValue !== '') { + // Host selected → show edit, hide add + if (addBtn) addBtn.style.display = 'none' + if (editBtn) editBtn.style.display = 'block' + } else { + // No host selected → show add, hide edit + if (addBtn) addBtn.style.display = 'block' + if (editBtn) editBtn.style.display = 'none' + } } + +// Health button click handler +const healthButton = document.querySelector('#bt_healthMonitoring') +if (healthButton) { + healthButton.addEventListener('click', function() { + jeeDialog.dialog({ + id: 'md_healthMonitoring', + title: '{{Santé des équipements Monitoring}}', + width: '95%', + height: '90%', + top: '5vh', + contentUrl: 'index.php?v=d&plugin=Monitoring&modal=health.monitoring', + defaultButtons: {}, + buttons: { + close: { + label: ' {{Fermer}}', + className: 'success', + callback: { + click: function(event) { + event.target.closest('div.jeeDialog')._jeeDialog.close() + } + } + } + }, + callback: function() { + if (typeof initModalHealthMonitoring === 'function') { + initModalHealthMonitoring() + } + }, + onClose: function() { + // Clean up resources when modal is closed + if (typeof cleanupHealthMonitoring === 'function') { + cleanupHealthMonitoring() + } + } + }) + }) +} + +// Expose functions globally for Jeedom to call them +window.addCmdToTable = addCmdToTable +window.printEqLogic = printEqLogic + +})() // End of IIFE protection + diff --git a/desktop/js/health.monitoring.js b/desktop/js/health.monitoring.js new file mode 100644 index 00000000..125b6d83 --- /dev/null +++ b/desktop/js/health.monitoring.js @@ -0,0 +1,476 @@ +/* This file is part of Jeedom. + * + * Jeedom is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Jeedom is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jeedom. If not, see . + */ + +(() => { +'use strict' + +// ======================================== +// === SELECTORS === +// ======================================== + +const SELECTORS = { + TABLE_BODY: '#table_healthMonitoring tbody' +} + +// Global handler reference to ensure single event listener +let healthCmdUpdateHandler = null + +// ======================================== +// === CORE FUNCTIONS === +// ======================================== + +/** + * Initialize health monitoring modal + * Called from modal PHP file + */ +const initModalHealthMonitoring = () => { + loadHealthData() +} + +/** + * Clean up resources when modal is closed + * Removes event listeners to prevent memory leaks + */ +const cleanupHealthMonitoring = () => { + // Remove WebSocket event listener + if (healthCmdUpdateHandler) { + document.body.removeEventListener('cmd::update', healthCmdUpdateHandler) + healthCmdUpdateHandler = null + } + + // Clear search input event listeners (handled by DOM removal) + // Clear button event listeners (handled by DOM removal) + // Table elements are automatically cleaned when modal DOM is removed +} + +/** + * Load health data from backend + */ +const loadHealthData = () => { + domUtils.ajax({ + type: 'POST', + url: 'plugins/Monitoring/core/ajax/Monitoring.ajax.php', + data: { action: 'getHealthData' }, + dataType: 'json', + error: (error) => { + const tbody = document.querySelector(SELECTORS.TABLE_BODY) + if (tbody) { + tbody.innerHTML = ` ${error.message}` + } + }, + success: (data) => { + displayHealthData(data.result) + } + }) +} + +/** + * Display health data in table + * @param {Array} healthData - Array of equipment health data + */ +const displayHealthData = (healthData) => { + const tbody = document.querySelector(SELECTORS.TABLE_BODY) + if (!tbody) return + + if (!healthData || healthData.length === 0) { + tbody.innerHTML = '{{Aucun équipement trouvé}}' + return + } + + const html = healthData.map(eqLogic => { + const isActive = eqLogic.isEnable === 1 + const isVisible = eqLogic.isVisible === 1 + + let typeLabel = '' + switch (eqLogic.type) { + case 'local': + typeLabel = 'Local' + break + case 'distant': + typeLabel = 'Distant' + break + case 'unconfigured': + typeLabel = '{{Non configuré}}' + break + default: + typeLabel = '-' + } + + // Prepare searchable values + const sshStatusSearch = eqLogic.commands?.sshStatus?.value === 'OK' ? 'OK' : eqLogic.commands?.sshStatus?.value === 'KO' ? 'KO' : '' + const cronValue = eqLogic.commands?.cronStatus?.value + const cronStatusSearch = cronValue ? ((cronValue === '1' || cronValue === 1 || cronValue === 'Yes') ? 'ON' : 'OFF') : '' + + // Prepare cron custom data + const cronCustomValue = eqLogic.cronCustom || 0 + const cronCustomData = { value: cronCustomValue } + + return ` + + ${eqLogic.name} + ${isActive ? '' : ''} + ${isVisible ? '' : ''} + ${typeLabel} + ${eqLogic.sshHostName || '-'} + ${formatCmdValue(eqLogic.commands?.sshStatus, 'ssh')} + ${formatCmdValue(eqLogic.commands?.cronStatus, 'cron', eqLogic.type, cronCustomData)} + ${formatCmdValue(eqLogic.commands?.uptime)} + ${formatCmdValue(eqLogic.commands?.loadAvg1)} + ${formatCmdValue(eqLogic.commands?.loadAvg5)} + ${formatCmdValue(eqLogic.commands?.loadAvg15)} + ${formatCmdValue(eqLogic.commands?.ip)} + ${formatDate(eqLogic.lastRefresh, eqLogic.type)} + + ` + }).join('') + + tbody.innerHTML = html + + // Enrich data-search with formatted text for cells with formatted content + // Exclude status cells to preserve exact match + tbody.querySelectorAll('td[data-search]:not([data-type="status"])').forEach(cell => { + const currentSearch = cell.getAttribute('data-search') + const textContent = cell.textContent.trim() + + // If textContent is different from data-search, add it + if (textContent && textContent !== currentSearch && textContent !== '-') { + cell.setAttribute('data-search', `${currentSearch} ${textContent}`) + } + }) + + // Initialize Jeedom tooltips with HTML support + initTooltips() + + // Initialize DataTables for sorting only (no search) + jeedomUtils.initDataTables('#healthMonitoringContainer', false, false, [{ select: 0, sort: "asc" }]) + + // Custom search implementation + const searchInput = document.getElementById('healthSearchInput') + const tableRows = tbody.querySelectorAll('tr') + + // Reusable search function + const performSearch = () => { + const searchTerm = searchInput ? searchInput.value.toLowerCase().trim() : '' + + tableRows.forEach(row => { + if (searchTerm === '') { + row.style.display = '' + return + } + + // List of status keywords that need exact match + const statusKeywords = ['on', 'off', 'ok', 'ko', 'actif', 'inactif', 'visible', 'invisible'] + const isStatusKeyword = statusKeywords.includes(searchTerm) + + const cells = row.querySelectorAll('td') + let found = false + + for (let cell of cells) { + const isStatusCell = cell.getAttribute('data-type') === 'status' + const searchValue = cell.getAttribute('data-search') + + if (!searchValue) continue + + const searchLower = searchValue.toLowerCase() + + // For status keywords: only search in status cells with exact match + if (isStatusKeyword) { + if (isStatusCell && searchLower === searchTerm) { + found = true + break + } + } + // For non-status keywords: contains search ONLY in non-status cells + else if (!isStatusCell) { + if (searchLower.includes(searchTerm)) { + found = true + break + } + } + } + + row.style.display = found ? '' : 'none' + }) + } + + if (searchInput) { + searchInput.addEventListener('keyup', performSearch) + } + + // Clear search button + const clearButton = document.getElementById('healthSearchClear') + if (clearButton && searchInput) { + clearButton.addEventListener('click', function() { + searchInput.value = '' + performSearch() + }) + } + + // Initialize Jeedom's automatic command update system for dynamically inserted elements + const cmdElements = tbody.querySelectorAll('.cmd[data-cmd_id]') + + // Create mappings for efficient updates + const cmdMap = new Map() // cmd_id -> element + const eqLastCommMap = new Map() // eq_id -> last comm cell + + cmdElements.forEach(element => { + const cmdId = element.getAttribute('data-cmd_id') + if (cmdId && cmdId !== '') { + cmdMap.set(cmdId, element) + } + }) + + tbody.querySelectorAll('.lastComm[data-eq-id]').forEach(cell => { + const eqId = cell.getAttribute('data-eq-id') + if (eqId) { + eqLastCommMap.set(eqId, cell) + } + }) + + if (cmdElements.length > 0) { + jeedom.cmd.refreshValue(cmdElements) + } + + // Remove previous event listener if exists to avoid duplicates + if (healthCmdUpdateHandler) { + document.body.removeEventListener('cmd::update', healthCmdUpdateHandler) + } + + // Single global event listener for cmd::update (performance optimization) + healthCmdUpdateHandler = (e) => { + if (!e.detail) return + + const updates = Array.isArray(e.detail) ? e.detail : [e.detail] + + updates.forEach(event => { + const cmdId = String(event.cmd_id || event.id) + const element = cmdMap.get(cmdId) + + if (!element) return + + const cmdType = element.getAttribute('data-cmd-type') + const value = event.display_value || event.value + + // Update data-value attribute + element.setAttribute('data-value', value) + + // Get parent cell to update data-search + const parentCell = element.closest('td') + + // Format value based on command type + if (cmdType === 'ssh') { + element.innerHTML = formatCmdValue({ value: value }, 'ssh') + // Update data-search for status cell (exact match values) + if (parentCell) { + const searchValue = value === 'OK' ? 'OK' : value === 'KO' ? 'KO' : '' + parentCell.setAttribute('data-search', searchValue) + } + } else if (cmdType === 'cron') { + const eqType = element.getAttribute('data-eq-type') + const cronCustomValue = element.getAttribute('data-cron-custom') + const cronCustomData = cronCustomValue ? { value: cronCustomValue } : null + element.innerHTML = formatCmdValue({ value: value }, 'cron', eqType, cronCustomData) + // Update data-search for status cell (exact match values) + if (parentCell) { + const searchValue = (value === '1' || value === 1 || value === 'Yes') ? 'ON' : 'OFF' + parentCell.setAttribute('data-search', searchValue) + } + } else { + element.innerHTML = formatCmdValue({ value: value }) + // Update data-search for non-status cells (include formatted text) + if (parentCell && !parentCell.getAttribute('data-type')) { + const formattedText = element.textContent.trim() + parentCell.setAttribute('data-search', `${value} ${formattedText}`) + } + } + + // Refresh search after data-search update + performSearch() + + // Add visual feedback for update + element.classList.remove('cmd-updated') + void element.offsetWidth + element.classList.add('cmd-updated') + + // Remove class after animation completes + setTimeout(() => element.classList.remove('cmd-updated'), 2000) + + // Update last communication date using direct mapping + if (event.collectDate) { + const eqId = element.getAttribute('data-eq-id') + if (eqId) { + const lastCommCell = eqLastCommMap.get(eqId) + if (lastCommCell) { + const eqType = lastCommCell.getAttribute('data-eq-type') + const formattedDate = formatDate(event.collectDate, eqType) + const lastCommSpan = lastCommCell.querySelector('span') + + if (lastCommSpan) { + lastCommSpan.innerHTML = formattedDate + + // Update data-search with formatted date text + const dateText = lastCommSpan.textContent.trim() + lastCommCell.setAttribute('data-search', dateText) + + // Refresh search after data-search update + performSearch() + + lastCommSpan.classList.remove('cmd-updated') + void lastCommSpan.offsetWidth + lastCommSpan.classList.add('cmd-updated') + + // Remove class after animation completes + setTimeout(() => lastCommSpan.classList.remove('cmd-updated'), 2000) + } + } + } + } + }) + } + + document.body.addEventListener('cmd::update', healthCmdUpdateHandler) +} + +// ======================================== +// === HELPER FUNCTIONS === +// ======================================== + +/** + * Format tooltip with command dates + * @param {string} label - Label for the command + * @param {Object} cmdData - Command data object + * @returns {string} Formatted tooltip text + */ +const formatTooltip = (label, cmdData) => { + if (!cmdData) { + return label + } + + const valueDate = cmdData.valueDate || '-' + const collectDate = cmdData.collectDate || '-' + + return `${label}
Date de valeur : ${valueDate}
Date de collecte : ${collectDate}
` +} + +/** + * Format command value for display + * @param {Object} cmdData - Command data object + * @param {string} type - Type of command (optional, e.g., 'cron') + * @param {string} eqType - Equipment type (optional, e.g., 'local', 'distant') + * @param {Object} cronCustomData - Cron custom status data (optional) + * @returns {string} Formatted HTML + */ +const formatCmdValue = (cmdData, type = null, eqType = null, cronCustomData = null) => { + if (!cmdData || cmdData.value === null || cmdData.value === undefined || cmdData.value === '') { + return '-' + } + + const value = cmdData.value + const unit = cmdData.unit || '' + + // Special handling for SSH Status + if (type === 'ssh') { + if (value === 'OK') { + return ' OK' + } else if (value === 'KO') { + return ' KO' + } else if (value === 'No') { + return '-' + } + } + + // Special handling for Cron Status + if (type === 'cron') { + const isOn = value === '1' || value === 1 || value === 'Yes' + const isCustom = cronCustomData && (cronCustomData.value === '1' || cronCustomData.value === 1) + + // Custom ON = orange badge with play icon + if (isCustom && isOn) { + return ' ON' + } + // Custom OFF = orange badge with pause icon + else if (isCustom && !isOn) { + return ' OFF' + } + // Default ON = green badge with play icon + else if (isOn) { + return ' ON' + } + // Default OFF = red badge with pause icon + else { + return ' OFF' + } + } + + // Format other special values + if (value === 'OK' || value === 'Running') { + return `${value}` + } else if (value === 'KO' || value === 'Stopped') { + return `${value}` + } + + return `${value}${unit ? ' ' + unit : ''}` +} + +/** + * Format date for display + * @param {string} dateStr - Date string to format + * @param {string} eqType - Equipment type ('local' or 'distant') + * @returns {string} Formatted date or dash if invalid + */ +const formatDate = (dateStr, eqType = null) => { + if (!dateStr || dateStr === '' || dateStr === '0000-00-00 00:00:00') { + return '-' + } + + try { + const date = new Date(dateStr) + if (isNaN(date.getTime())) { + return '-' + } + + const now = new Date() + const diffMs = now - date + const diffMins = Math.floor(diffMs / 60000) + + // Color based on age and equipment type + const formattedDate = dateStr.slice(0, 19).replace('T', ' ') + + // Green threshold varies: local <= 5min, distant <= 15min + const greenThreshold = (eqType === 'local') ? 5 : 15 + + if (diffMins <= greenThreshold) { + return `${formattedDate}` + } else if (diffMins <= 30) { + return `${formattedDate}` + } else { + return `${formattedDate}` + } + } catch (e) { + return '-' + } +} + +// ======================================== +// === GLOBAL EXPOSURE === +// ======================================== + +// Expose functions globally for modal to call +window.initModalHealthMonitoring = initModalHealthMonitoring +window.cleanupHealthMonitoring = cleanupHealthMonitoring + +})() // End of IIFE protection + diff --git a/desktop/modal/health.monitoring.php b/desktop/modal/health.monitoring.php new file mode 100644 index 00000000..96bfda9e --- /dev/null +++ b/desktop/modal/health.monitoring.php @@ -0,0 +1,152 @@ +. + */ + +if (!isConnect()) { + throw new Exception('{{401 - Accès non autorisé}}'); +} + +?> + + + + + +
+ +
+ {{Résumé de l'état de santé de tous vos équipements Monitoring}} +
+ +
+
+ + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
{{Nom}}{{Actif}}{{Visible}}{{Type}}{{Hôte SSH}}{{SSH Status}}{{Cron Status}}{{Uptime}}{{Charge 1min}}{{Charge 5min}}{{Charge 15min}}{{Adresse IP}}{{Dernière Communication}}
+ {{Chargement des données...}} +
+
+ + + + diff --git a/desktop/php/Monitoring.php b/desktop/php/Monitoring.php index 75c985da..4bcafa97 100644 --- a/desktop/php/Monitoring.php +++ b/desktop/php/Monitoring.php @@ -51,6 +51,11 @@
{{Configuration}}
+
+ +
+ {{Santé}} +

@@ -224,6 +229,9 @@ +
diff --git a/plugin_info/info.json b/plugin_info/info.json index c297e0f8..202af5de 100644 --- a/plugin_info/info.json +++ b/plugin_info/info.json @@ -1,7 +1,7 @@ { "id": "Monitoring", "name": "Monitoring", - "pluginVersion": "3.3.0", + "pluginVersion": "3.4.3", "description": { "fr_FR": "Plugin permettant le monitoring des équipements locaux et distants (via SSH). Le plugin affichera les informations systèmes d'équipements sous Linux ou Synology (Distribution, CPU, Mémoire, Disques, Swap).", "en_US": "Plugin to monitor local and remote equipments (through SSH). The plugin will display system informations from Linux or Synology (Distribution, CPU, Memory, Disks, Swap).", diff --git a/plugin_info/install.php b/plugin_info/install.php index 0fe7b36a..6db7146e 100644 --- a/plugin_info/install.php +++ b/plugin_info/install.php @@ -188,7 +188,6 @@ function Monitoring_update() { try { $dirToDelete = array( __DIR__ . '/../ressources', - __DIR__ . '/../desktop/modal', __DIR__ . '/../mobile', __DIR__ . '/../core/img', __DIR__ . '/../resources',