From 6e194185ed279837bb215c48ee9a87144f0799d7 Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Sat, 3 Jan 2026 12:05:56 +1100 Subject: [PATCH 1/6] FE+BE: use of new events endpoint, devMAC -> devMac Signed-off-by: jokob-sk --- front/php/server/events.php | 418 -------------------------- front/plugins/_publisher_mqtt/mqtt.py | 2 +- front/presence.php | 88 ++++-- server/models/device_instance.py | 6 +- 4 files changed, 72 insertions(+), 442 deletions(-) delete mode 100755 front/php/server/events.php diff --git a/front/php/server/events.php b/front/php/server/events.php deleted file mode 100755 index 7c3f0cb3..00000000 --- a/front/php/server/events.php +++ /dev/null @@ -1,418 +0,0 @@ -= date('now', '".$periodDateSQL."')) as all_events, - (SELECT Count(*) FROM Sessions as sessions WHERE ( ses_DateTimeConnection >= date('now', '".$periodDateSQL."') OR ses_DateTimeDisconnection >= date('now', '".$periodDateSQL."') OR ses_StillConnected = 1 )) as sessions, - (SELECT Count(*) FROM Sessions WHERE ((ses_DateTimeConnection IS NULL AND ses_DateTimeDisconnection >= date('now', '".$periodDateSQL."' )) OR (ses_DateTimeDisconnection IS NULL AND ses_StillConnected = 0 AND ses_DateTimeConnection >= date('now', '".$periodDateSQL."' )))) as missing, - (SELECT Count(*) FROM Events WHERE eve_DateTime >= date('now', '".$periodDateSQL."') AND eve_EventType LIKE 'VOIDED%' ) as voided, - (SELECT Count(*) FROM Events WHERE eve_DateTime >= date('now', '".$periodDateSQL."') AND eve_EventType LIKE 'New Device' ) as new, - (SELECT Count(*) FROM Events WHERE eve_DateTime >= date('now', '".$periodDateSQL."') AND eve_EventType LIKE 'Device Down' ) as down"; - - $result = $db->query($sql); - $row = $result -> fetchArray (SQLITE3_NUM); - $resultJSON = json_encode (array ($row[0], $row[1], $row[2], $row[3], $row[4], $row[5])); - - // save JSON result to cache - setCache("getEventsTotals".$days, $resultJSON ); - } - - // Return json - echo ($resultJSON); -} - - -//------------------------------------------------------------------------------ -// Query the List of events -//------------------------------------------------------------------------------ -function getEvents() { - global $db; - - // Request Parameters - $type = $_REQUEST ['type']; - $periodDate = getDateFromPeriod(); - - // SQL - $SQL1 = 'SELECT eve_DateTime AS eve_DateTimeOrder, devName, devOwner, eve_DateTime, eve_EventType, NULL, NULL, NULL, NULL, eve_IP, NULL, eve_AdditionalInfo, NULL, devMac, eve_PendingAlertEmail - FROM Events_Devices - WHERE eve_DateTime >= '. $periodDate; - - $SQL2 = 'SELECT IFNULL (ses_DateTimeConnection, ses_DateTimeDisconnection) ses_DateTimeOrder, - devName, devOwner, Null, Null, ses_DateTimeConnection, ses_DateTimeDisconnection, NULL, NULL, ses_IP, NULL, ses_AdditionalInfo, ses_StillConnected, devMac - FROM Sessions_Devices '; - - // SQL Variations for status - switch ($type) { - case 'all': $SQL = $SQL1; break; - case 'sessions': - $SQL = $SQL2 . ' WHERE ( ses_DateTimeConnection >= '. $periodDate .' OR ses_DateTimeDisconnection >= '. $periodDate .' OR ses_StillConnected = 1 ) '; - break; - case 'missing': - $SQL = $SQL2 . ' WHERE (ses_DateTimeConnection IS NULL AND ses_DateTimeDisconnection >= '. $periodDate .' ) - OR (ses_DateTimeDisconnection IS NULL AND ses_StillConnected = 0 AND ses_DateTimeConnection >= '. $periodDate .' )'; - break; - case 'voided': $SQL = $SQL1 .' AND eve_EventType LIKE "VOIDED%" '; break; - case 'new': $SQL = $SQL1 .' AND eve_EventType = "New Device" '; break; - case 'down': $SQL = $SQL1 .' AND eve_EventType = "Device Down" '; break; - default: $SQL = $SQL1 .' AND 1==0 '; break; - } - - // Query - $result = $db->query($SQL); - - $tableData = array(); - while ($row = $result -> fetchArray (SQLITE3_NUM)) { - if ($type == 'sessions' || $type == 'missing' ) { - // Duration - if (!empty ($row[5]) && !empty($row[6]) ) { - $row[7] = formatDateDiff ($row[5], $row[6]); - $row[8] = abs(strtotime($row[6]) - strtotime($row[5])); - } elseif ($row[12] == 1) { - $row[7] = formatDateDiff ($row[5], ''); - $row[8] = abs(strtotime("now") - strtotime($row[5])); - } else { - $row[7] = '...'; - $row[8] = 0; - } - - // Connection - if (!empty ($row[5]) ) { - $row[5] = formatDate ($row[5]); - } else { - $row[5] = ''; - } - - // Disconnection - if (!empty ($row[6]) ) { - $row[6] = formatDate ($row[6]); - } elseif ($row[12] == 0) { - $row[6] = ''; - } else { - $row[6] = '...'; - } - - } else { - // Event Date - $row[3] = formatDate ($row[3]); - } - - // IP Order - $row[10] = formatIPlong ($row[9]); - $tableData['data'][] = $row; - } - - // Control no rows - if (empty($tableData['data'])) { - $tableData['data'] = ''; - } - - // Return json - echo (json_encode ($tableData)); -} - - -//------------------------------------------------------------------------------ -// Query Device Sessions -//------------------------------------------------------------------------------ -function getDeviceSessions() { - global $db; - - // Request Parameters - $mac = $_REQUEST['mac']; - $periodDate = getDateFromPeriod(); - - // SQL - $SQL = 'SELECT IFNULL (ses_DateTimeConnection, ses_DateTimeDisconnection) ses_DateTimeOrder, - ses_EventTypeConnection, ses_DateTimeConnection, - ses_EventTypeDisconnection, ses_DateTimeDisconnection, ses_StillConnected, - ses_IP, ses_AdditionalInfo - FROM Sessions - WHERE ses_MAC="' . $mac .'" - AND ( ses_DateTimeConnection >= '. $periodDate .' - OR ses_DateTimeDisconnection >= '. $periodDate .' - OR ses_StillConnected = 1 ) '; - $result = $db->query($SQL); - - // arrays of rows - $tableData = array(); - while ($row = $result -> fetchArray (SQLITE3_ASSOC)) { - // Connection DateTime - if ($row['ses_EventTypeConnection'] == '') { - $ini = $row['ses_EventTypeConnection']; - } else { - $ini = formatDate ($row['ses_DateTimeConnection']); - } - - // Disconnection DateTime - if ($row['ses_StillConnected'] == true) { - $end = '...'; - } elseif ($row['ses_EventTypeDisconnection'] == '') { - $end = $row['ses_EventTypeDisconnection']; - } else { - $end = formatDate ($row['ses_DateTimeDisconnection']); - } - - // Duration - if ($row['ses_EventTypeConnection'] == '' || $row['ses_EventTypeConnection'] == NULL || $row['ses_EventTypeDisconnection'] == '' || $row['ses_EventTypeDisconnection'] == NULL) { - $dur = '...'; - } elseif ($row['ses_StillConnected'] == true) { - $dur = formatDateDiff ($row['ses_DateTimeConnection'], ''); //*********** - } else { - $dur = formatDateDiff ($row['ses_DateTimeConnection'], $row['ses_DateTimeDisconnection']); - } - - // Additional Info - $info = $row['ses_AdditionalInfo']; - if ($row['ses_EventTypeConnection'] == 'New Device' ) { - $info = $row['ses_EventTypeConnection'] .': '. $info; - } - - // Push row data - $tableData['data'][] = array($row['ses_DateTimeOrder'], $ini, $end, $dur, $row['ses_IP'], $info); - } - - // Control no rows - if (empty($tableData['data'])) { - $tableData['data'] = ''; - } - - // Return json - echo (json_encode ($tableData)); -} - - -//------------------------------------------------------------------------------ -// Query Device Presence Calendar -//------------------------------------------------------------------------------ -function getDevicePresence() { - global $db; - - // Request Parameters - $mac = $_REQUEST['mac']; - $startDate = '"'. formatDateISO ($_REQUEST ['start']) .'"'; - $endDate = '"'. formatDateISO ($_REQUEST ['end']) .'"'; - - // SQL - $SQL = 'SELECT ses_EventTypeConnection, ses_DateTimeConnection, - ses_EventTypeDisconnection, ses_DateTimeDisconnection, ses_IP, ses_AdditionalInfo, ses_StillConnected, - - CASE - WHEN ses_EventTypeConnection = "" THEN - IFNULL ((SELECT MAX(ses_DateTimeDisconnection) FROM Sessions AS SES2 WHERE SES2.ses_MAC = SES1.ses_MAC AND SES2.ses_DateTimeDisconnection < SES1.ses_DateTimeDisconnection), DATETIME(ses_DateTimeDisconnection, "-1 hour")) - ELSE ses_DateTimeConnection - END AS ses_DateTimeConnectionCorrected, - - CASE - WHEN ses_EventTypeDisconnection = "" OR ses_EventTypeDisconnection = NULL THEN - (SELECT MIN(ses_DateTimeConnection) FROM Sessions AS SES2 WHERE SES2.ses_MAC = SES1.ses_MAC AND SES2.ses_DateTimeConnection > SES1.ses_DateTimeConnection) - ELSE ses_DateTimeDisconnection - END AS ses_DateTimeDisconnectionCorrected - - FROM Sessions AS SES1 - WHERE ses_MAC="' . $mac .'" - AND (ses_DateTimeConnectionCorrected <= date('. $endDate .') - AND (ses_DateTimeDisconnectionCorrected >= date('. $startDate .') OR ses_StillConnected = 1 )) '; - $result = $db->query($SQL); - - // arrays of rows - while ($row = $result -> fetchArray (SQLITE3_ASSOC)) { - // Event color - if ($row['ses_EventTypeConnection'] == '' || $row['ses_EventTypeDisconnection'] == '') { - $color = '#f39c12'; - } elseif ($row['ses_StillConnected'] == 1 ) { - $color = '#00a659'; - } else { - $color = '#0073b7'; - } - - - // tooltip - $tooltip = 'Connection: ' . formatEventDate ($row['ses_DateTimeConnection'], $row['ses_EventTypeConnection']) . chr(13) . - 'Disconnection: ' . formatEventDate ($row['ses_DateTimeDisconnection'], $row['ses_EventTypeDisconnection']) . chr(13) . - 'IP: ' . $row['ses_IP']; - - // Save row data - $tableData[] = array( - 'title' => '', - 'start' => formatDateISO ($row['ses_DateTimeConnectionCorrected']), - 'end' => formatDateISO ($row['ses_DateTimeDisconnectionCorrected']), - 'color' => $color, - 'tooltip' => $tooltip - ); - } - - // Control no rows - if (empty($tableData)) { - $tableData = ''; - } - - // Return json - echo (json_encode($tableData)); -} - - -//------------------------------------------------------------------------------ -// Query Presence Calendar for all Devices -//------------------------------------------------------------------------------ -function getEventsCalendar() { - global $db; - - // Request Parameters - $startDate = '"'. $_REQUEST ['start'] .'"'; - $endDate = '"'. $_REQUEST ['end'] .'"'; - - // SQL - $SQL = 'SELECT SES1.ses_MAC, SES1.ses_EventTypeConnection, SES1.ses_DateTimeConnection, - SES1.ses_EventTypeDisconnection, SES1.ses_DateTimeDisconnection, SES1.ses_IP, - SES1.ses_AdditionalInfo, SES1.ses_StillConnected, - - CASE - WHEN SES1.ses_EventTypeConnection = "" THEN - IFNULL ( - (SELECT MAX(SES2.ses_DateTimeDisconnection) - FROM Sessions AS SES2 - WHERE SES2.ses_MAC = SES1.ses_MAC - AND SES2.ses_DateTimeDisconnection < SES1.ses_DateTimeDisconnection - AND SES2.ses_DateTimeDisconnection BETWEEN Date('. $startDate .') AND Date('. $endDate .') - ), - DATETIME(SES1.ses_DateTimeDisconnection, "-1 hour") - ) - ELSE SES1.ses_DateTimeConnection - END AS ses_DateTimeConnectionCorrected, - - CASE - WHEN SES1.ses_EventTypeDisconnection = "" THEN - (SELECT MIN(SES2.ses_DateTimeConnection) - FROM Sessions AS SES2 - WHERE SES2.ses_MAC = SES1.ses_MAC - AND SES2.ses_DateTimeConnection > SES1.ses_DateTimeConnection - AND SES2.ses_DateTimeConnection BETWEEN Date('. $startDate .') AND Date('. $endDate .') - ) - ELSE SES1.ses_DateTimeDisconnection - END AS ses_DateTimeDisconnectionCorrected - - FROM Sessions AS SES1 - WHERE (SES1.ses_DateTimeConnection BETWEEN Date('. $startDate .') AND Date('. $endDate .')) - OR (SES1.ses_DateTimeDisconnection BETWEEN Date('. $startDate .') AND Date('. $endDate .')) - OR SES1.ses_StillConnected = 1'; - - $result = $db->query($SQL); - - // arrays of rows - while ($row = $result -> fetchArray (SQLITE3_ASSOC)) { - // Event color - if ($row['ses_EventTypeConnection'] == '' || $row['ses_EventTypeDisconnection'] == '') { - $color = '#f39c12'; - } elseif ($row['ses_StillConnected'] == 1 ) { - $color = '#00a659'; - } else { - $color = '#0073b7'; - } - - // tooltip - $tooltip = 'Connection: ' . formatEventDate ($row['ses_DateTimeConnection'], $row['ses_EventTypeConnection']) . chr(13) . - 'Disconnection: ' . formatEventDate ($row['ses_DateTimeDisconnection'], $row['ses_EventTypeDisconnection']) . chr(13) . - 'IP: ' . $row['ses_IP']; - - // Save row data - $tableData[] = array( - 'resourceId' => $row['ses_MAC'], - 'title' => '', - 'start' => formatDateISO ($row['ses_DateTimeConnectionCorrected']), - 'end' => formatDateISO ($row['ses_DateTimeDisconnectionCorrected']), - 'color' => $color, - 'tooltip' => $tooltip, - 'className' => 'no-border' - ); - } - - // Control no rows - if (empty($tableData)) { - $tableData = ''; - } - - // Return json - echo (json_encode($tableData)); -} - -?> diff --git a/front/plugins/_publisher_mqtt/mqtt.py b/front/plugins/_publisher_mqtt/mqtt.py index 70a884ab..6d3f77d3 100755 --- a/front/plugins/_publisher_mqtt/mqtt.py +++ b/front/plugins/_publisher_mqtt/mqtt.py @@ -510,7 +510,7 @@ def mqtt_start(db): "group": device["devGroup"], "location": device["devLocation"], "network_parent_mac": device["devParentMAC"], - "network_parent_name": next((dev["devName"] for dev in devices if dev["devMAC"] == device["devParentMAC"]), "") + "network_parent_name": next((dev["devName"] for dev in devices if dev["devMac"] == device["devParentMAC"]), "") } # bulk update device sensors in home assistant diff --git a/front/presence.php b/front/presence.php index 6052abb2..a7c0c579 100755 --- a/front/presence.php +++ b/front/presence.php @@ -1,7 +1,7 @@ - + ?> - + @@ -147,7 +147,7 @@ -
+
@@ -236,7 +236,7 @@ function initializeCalendar () { height : 'auto', firstDay : 1, allDaySlot : false, - timeFormat : 'H:mm', + timeFormat : 'H:mm', resourceLabelText : '', resourceAreaWidth : '160px', @@ -291,24 +291,24 @@ function initializeCalendar () { slotDuration : '00:30:00' } }, - + // Needed due hack partial day events 23:59:59 dayRender: function (date, cell) { if ($('#calendar').fullCalendar('getView').name == 'timelineYear') { - cell.removeClass('fc-sat'); - cell.removeClass('fc-sun'); + cell.removeClass('fc-sat'); + cell.removeClass('fc-sun'); return; - }; + }; if (date.day() == 0) { cell.addClass('fc-sun'); }; - + if (date.day() == 6) { cell.addClass('fc-sat'); }; if (date.format('YYYY-MM-DD') == moment().format('YYYY-MM-DD')) { cell.addClass ('fc-today'); }; - + if ($('#calendar').fullCalendar('getView').name == 'timelineDay') { cell.removeClass('fc-sat'); cell.removeClass('fc-sun'); @@ -318,7 +318,7 @@ function initializeCalendar () { } }; }, - + resourceRender: function (resourceObj, labelTds, bodyTds) { labelTds.find('span.fc-cell-text').html ( ''+ resourceObj.title +''); @@ -326,7 +326,7 @@ function initializeCalendar () { // Resize heihgt // $(".fc-content table tbody tr .fc-widget-content div").addClass('fc-resized-row'); }, - + eventRender: function (event, element, view) { // $(element).tooltip({container: 'body', placement: 'bottom', title: event.tooltip}); tltp = event.tooltip.replace('\n',' | ') @@ -387,7 +387,7 @@ function getDevicesPresence (status) { case 'down': tableTitle = ''; color = 'red'; break; case 'archived': tableTitle = ''; color = 'gray'; break; default: tableTitle = ''; color = 'gray'; break; - } + } period = "7 days" @@ -421,12 +421,60 @@ function getDevicesPresence (status) { $('#tableDevicesBox')[0].className = 'box box-'+ color; $('#tableDevicesTitle').html (tableTitle); - // Define new datasource URL and reload - $('#calendar').fullCalendar ('option', 'resources', 'php/server/devices.php?action=getDevicesListCalendar&status='+ deviceStatus); - $('#calendar').fullCalendar ('refetchResources'); + const protocol = window.location.protocol.replace(':', ''); + const host = window.location.hostname; + const port = getSetting("GRAPHQL_PORT"); // Or Flask server port + const apiToken = getSetting("API_TOKEN"); + + const apiBase = `${protocol}://${host}:${port}`; + + // ----------------------------- + // Load Devices as Resources + // ----------------------------- + const devicesUrl = `${apiBase}/devices/by-status?status=${deviceStatus}`; + + $.ajax({ + url: devicesUrl, + method: "GET", + headers: { + "Authorization": `Bearer ${apiToken}` + }, + success: function(devices) { + // FullCalendar expects resources array + const resources = devices.map(dev => ({ + id: dev.devMac, + title: dev.devName + })); + + $('#calendar').fullCalendar('option', 'resources', resources); + $('#calendar').fullCalendar('refetchResources'); + } + }); + + // ----------------------------- + // Load Events + // ----------------------------- + const eventsUrl = `${apiBase}/sessions/calendar?start=${startDate}&end=${endDate}`; $('#calendar').fullCalendar('removeEventSources'); - $('#calendar').fullCalendar('addEventSource', { url: `php/server/events.php?period=${period}&start=${startDate}&end=${endDate}&action=getEventsCalendar` }); + $('#calendar').fullCalendar('addEventSource', { + url: eventsUrl, + method: "GET", + headers: { + "Authorization": `Bearer ${apiToken}` + }, + success: function(response) { + // Flask returns { "sessions": [...] } → FullCalendar needs array + const events = response.sessions || []; + $('#calendar').fullCalendar('removeEvents'); + $('#calendar').fullCalendar('renderEvents', events, true); + }, + error: function(err) { + console.error('Failed to load events:', err); + } + }); }; + + diff --git a/server/models/device_instance.py b/server/models/device_instance.py index 91812075..3d3a486c 100755 --- a/server/models/device_instance.py +++ b/server/models/device_instance.py @@ -176,10 +176,10 @@ class DeviceInstance: if "*" in mac: # Wildcard matching sql_pattern = mac.replace("*", "%") - cur.execute("DELETE FROM Devices WHERE devMAC LIKE ?", (sql_pattern,)) + cur.execute("DELETE FROM Devices WHERE devMac LIKE ?", (sql_pattern,)) else: # Exact match - cur.execute("DELETE FROM Devices WHERE devMAC = ?", (mac,)) + cur.execute("DELETE FROM Devices WHERE devMac = ?", (mac,)) deleted_count += cur.rowcount conn.commit() @@ -191,7 +191,7 @@ class DeviceInstance: """Delete devices with empty MAC addresses.""" conn = get_temp_db_connection() cur = conn.cursor() - cur.execute("DELETE FROM Devices WHERE devMAC IS NULL OR devMAC = ''") + cur.execute("DELETE FROM Devices WHERE devMac IS NULL OR devMac = ''") deleted = cur.rowcount conn.commit() conn.close() From f8d8a745fea8bf99f0f3b0d44bb0de893b4e3e1c Mon Sep 17 00:00:00 2001 From: Sylvain Pichon Date: Fri, 2 Jan 2026 08:05:57 +0100 Subject: [PATCH 2/6] Translated using Weblate (French) Currently translated at 100.0% (764 of 764 strings) Translation: NetAlertX/core Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/ --- front/php/templates/language/fr_fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/front/php/templates/language/fr_fr.json b/front/php/templates/language/fr_fr.json index 0d9de195..e263bce1 100644 --- a/front/php/templates/language/fr_fr.json +++ b/front/php/templates/language/fr_fr.json @@ -290,7 +290,7 @@ "Events_Tablelenght": "Afficher _MENU_ entrées", "Events_Tablelenght_all": "Tous", "Events_Title": "Évènements", - "FakeMAC_hover": "", + "FakeMAC_hover": "Autodétecté - indique si l'appareil utilise une fausse adresse MAC (qui commence par FA:CE ou 00:1A), typiquement générée par un plugin qui ne peut pas détecter la vraie adresse MAC, ou en créant un appareil factice.", "GRAPHQL_PORT_description": "Le numéro de port du serveur GraphQL. Assurez vous sue le port est unique a l'échelle de toutes les applications sur cet hôte et vos instances NetAlertX.", "GRAPHQL_PORT_name": "Port GraphQL", "Gen_Action": "Action", @@ -763,4 +763,4 @@ "settings_system_label": "Système", "settings_update_item_warning": "Mettre à jour la valeur ci-dessous. Veillez à bien suivre le même format qu'auparavant. Il n'y a pas de pas de contrôle.", "test_event_tooltip": "Enregistrer d'abord vos modifications avant de tester vôtre paramétrage." -} \ No newline at end of file +} From 24b204612b4af078ad127727ce5552a8641811ca Mon Sep 17 00:00:00 2001 From: Massimo Pissarello Date: Fri, 2 Jan 2026 07:27:12 +0100 Subject: [PATCH 3/6] Translated using Weblate (Italian) Currently translated at 100.0% (764 of 764 strings) Translation: NetAlertX/core Translate-URL: https://hosted.weblate.org/projects/pialert/core/it/ --- front/php/templates/language/it_it.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/front/php/templates/language/it_it.json b/front/php/templates/language/it_it.json index 6f319dd7..ef2eefa4 100644 --- a/front/php/templates/language/it_it.json +++ b/front/php/templates/language/it_it.json @@ -290,7 +290,7 @@ "Events_Tablelenght": "Mostra _MENU_ elementi", "Events_Tablelenght_all": "Tutti", "Events_Title": "Eventi", - "FakeMAC_hover": "", + "FakeMAC_hover": "Rilevato automaticamente: indica se il dispositivo utilizza un indirizzo MAC FALSO (che inizia con FA:CE o 00:1A), in genere generato da un plugin che non riesce a rilevare il MAC reale o quando si crea un dispositivo fittizio.", "GRAPHQL_PORT_description": "Il numero di porta del server GraphQL. Assicurati che la porta sia univoca in tutte le tue applicazioni su questo host e nelle istanze di NetAlertX.", "GRAPHQL_PORT_name": "Porta GraphQL", "Gen_Action": "Azione", @@ -763,4 +763,4 @@ "settings_system_label": "Sistema", "settings_update_item_warning": "Aggiorna il valore qui sotto. Fai attenzione a seguire il formato precedente. La convalida non viene eseguita.", "test_event_tooltip": "Salva le modifiche prima di provare le nuove impostazioni." -} \ No newline at end of file +} From fc3178c0b3019f7a3195f730d048c2dc49aa3e14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=93=D0=BE=D1=80?= =?UTF-8?q?=D0=BF=D0=B8=D0=BD=D1=96=D1=87?= Date: Fri, 2 Jan 2026 19:25:45 +0100 Subject: [PATCH 4/6] Translated using Weblate (Ukrainian) Currently translated at 100.0% (764 of 764 strings) Translation: NetAlertX/core Translate-URL: https://hosted.weblate.org/projects/pialert/core/uk/ --- front/php/templates/language/uk_ua.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/front/php/templates/language/uk_ua.json b/front/php/templates/language/uk_ua.json index 540a22da..0c933ca9 100644 --- a/front/php/templates/language/uk_ua.json +++ b/front/php/templates/language/uk_ua.json @@ -290,7 +290,7 @@ "Events_Tablelenght": "Показати записи _МЕНЮ_", "Events_Tablelenght_all": "Все", "Events_Title": "Події", - "FakeMAC_hover": "", + "FakeMAC_hover": "Автоматично виявлено – вказує, чи пристрій використовує ПІДРОБНУ MAC-адресу (що починається з FA:CE або 00:1A), зазвичай згенеровану плагіном, який не може визначити справжню MAC-адресу, або під час створення фіктивного пристрою.", "GRAPHQL_PORT_description": "Номер порту сервера GraphQL. Переконайтеся, що порт є унікальним для всіх ваших програм на цьому хості та екземплярах NetAlertX.", "GRAPHQL_PORT_name": "Порт GraphQL", "Gen_Action": "Дія", @@ -763,4 +763,4 @@ "settings_system_label": "Система", "settings_update_item_warning": "Оновіть значення нижче. Слідкуйте за попереднім форматом. Перевірка не виконана.", "test_event_tooltip": "Перш ніж перевіряти налаштування, збережіть зміни." -} \ No newline at end of file +} From bdb9377061d3d4beafef4bbe346484fd969488d1 Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Sun, 4 Jan 2026 11:27:34 +1100 Subject: [PATCH 5/6] PLG: ICMP v2 #1331 Signed-off-by: jokob-sk --- Dockerfile | 2 +- Dockerfile.debian | 22 +- front/plugins/icmp_scan/config.json | 67 +++++- front/plugins/icmp_scan/icmp.py | 204 +++++++++++++----- .../debian12/install_dependencies.debian12.sh | 8 +- install/proxmox/proxmox-install-netalertx.sh | 24 +-- install/ubuntu24/install.sh | 30 +-- 7 files changed, 254 insertions(+), 103 deletions(-) diff --git a/Dockerfile b/Dockerfile index db48dca5..ae9af083 100755 --- a/Dockerfile +++ b/Dockerfile @@ -129,7 +129,7 @@ ENV NETALERTX_USER=netalertx NETALERTX_GROUP=netalertx ENV LANG=C.UTF-8 -RUN apk add --no-cache bash mtr libbsd zip lsblk tzdata curl arp-scan iproute2 iproute2-ss nmap \ +RUN apk add --no-cache bash mtr libbsd zip lsblk tzdata curl arp-scan iproute2 iproute2-ss nmap fping \ nmap-scripts traceroute nbtscan net-tools net-snmp-tools bind-tools awake ca-certificates \ sqlite php83 php83-fpm php83-cgi php83-curl php83-sqlite3 php83-session python3 envsubst \ nginx supercronic shadow && \ diff --git a/Dockerfile.debian b/Dockerfile.debian index 2bee1a34..d393cf9f 100755 --- a/Dockerfile.debian +++ b/Dockerfile.debian @@ -4,7 +4,7 @@ # treat a container as an operating system, which is an anti-pattern and a common source of # security issues. # -# The default Dockerfile/docker-compose image contains the following security improvements +# The default Dockerfile/docker-compose image contains the following security improvements # over the Debian image: # - read-only filesystem # - no sudo access @@ -25,7 +25,7 @@ # - minimal base image (Alpine Linux) # - minimal python environment (venv, no pip) # - minimal stripped web server -# - minimal stripped php environment +# - minimal stripped php environment # - minimal services (nginx, php-fpm, crond, no unnecessary services or service managers) # - minimal users and groups (netalertx and readonly only, no others) # - minimal permissions (read-only for most files and folders, write-only for necessary folders) @@ -36,8 +36,8 @@ # - Uses the same services as the development environment (nginx, php-fpm, crond) # - Uses the same environment variables as the development environment (only necessary ones, no others) # - Uses the same file and folder structure as the development environment (only necessary ones, no others) -# NetAlertX is designed to be run as an unattended network security monitoring appliance, which means it -# should be able to operate without human intervention. Overall, the hardened image is designed to be as +# NetAlertX is designed to be run as an unattended network security monitoring appliance, which means it +# should be able to operate without human intervention. Overall, the hardened image is designed to be as # secure as possible while still being functional and is recommended because you cannot attack a surface # that isn't there. @@ -92,7 +92,7 @@ ENV PHP_FPM_CONFIG_FILE=${SYSTEM_SERVICES_PHP_FOLDER}/php-fpm.conf #Python environment ENV PYTHONPATH=${NETALERTX_SERVER} -ENV PYTHONUNBUFFERED=1 +ENV PYTHONUNBUFFERED=1 ENV VIRTUAL_ENV=/opt/venv ENV VIRTUAL_ENV_BIN=/opt/venv/bin ENV PATH="${VIRTUAL_ENV}/bin:${PATH}:/services" @@ -107,9 +107,9 @@ ENV NETALERTX_DEBUG=0 #Container environment ENV ENVIRONMENT=debian -ENV USER=netalertx +ENV USER=netalertx ENV USER_ID=1000 -ENV USER_GID=1000 +ENV USER_GID=1000 # Todo, figure out why using a workdir instead of full paths don't work # Todo, do we still need all these packages? I can already see sudo which isn't needed @@ -127,16 +127,16 @@ RUN groupadd --gid "${USER_GID}" "${USER}" && \ usermod -a -G ${USER_GID} root && \ usermod -a -G ${USER_GID} www-data -COPY --chmod=775 --chown=${USER_ID}:${USER_GID} install/production-filesystem/ / +COPY --chmod=775 --chown=${USER_ID}:${USER_GID} install/production-filesystem/ / COPY --chmod=775 --chown=${USER_ID}:${USER_GID} . ${INSTALL_DIR}/ -# ❗ IMPORTANT - if you modify this file modify the /install/install_dependecies.debian.sh file as well ❗ +# ❗ IMPORTANT - if you modify this file modify the /install/install_dependecies.debian.sh file as well ❗ # hadolint ignore=DL3008,DL3027 RUN apt-get update && apt-get install -y --no-install-recommends \ tini snmp ca-certificates curl libwww-perl arp-scan sudo gettext-base \ - nginx-light php php-cgi php-fpm php-sqlite3 php-curl sqlite3 dnsutils net-tools \ - python3 python3-dev iproute2 nmap python3-pip zip git systemctl usbutils traceroute nbtscan openrc \ + nginx-light php php-cgi php-fpm php-sqlite3 php-curl sqlite3 dnsutils net-tools \ + python3 python3-dev iproute2 nmap fping python3-pip zip git systemctl usbutils traceroute nbtscan openrc \ busybox nginx nginx-core mtr python3-venv && \ rm -rf /var/lib/apt/lists/* diff --git a/front/plugins/icmp_scan/config.json b/front/plugins/icmp_scan/config.json index d96a4a7a..aa37c875 100755 --- a/front/plugins/icmp_scan/config.json +++ b/front/plugins/icmp_scan/config.json @@ -75,6 +75,34 @@ } ] }, + { + "function": "MODE", + "events": ["run"], + "type": { + "dataType": "string", + "elements": [ + { "elementType": "select", "elementOptions": [], "transformers": [] } + ] + }, + "default_value": "ping", + "options": [ + "ping", + "fping" + ], + "localized": ["name", "description"], + "name": [ + { + "language_code": "en_us", + "string": "Mode" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "Selects the ICMP engine to use. ping checks devices individually and works even when the ARP / neighbor cache is empty, but is slower on larger networks. fping scans IP ranges in parallel and is significantly faster, but relies on the system neighbor cache to resolve IP addresses to MAC addresses. For most networks, fping is recommended. The default command arguments ICMP_ARGS are compatible with both modes." + } + ] + }, { "function": "CMD", "type": { @@ -115,7 +143,7 @@ } ] }, - "default_value": "-i 0.5 -c 3 -W 4 -w 5", + "default_value": "-i 0.5 -c 3 -w 5", "options": [], "localized": ["name", "description"], "name": [ @@ -127,7 +155,7 @@ "description": [ { "language_code": "en_us", - "string": "Arguments passed to the ping command. Please be careful modifying these." + "string": "Arguments passed to the underlying ping or fping command. The default values are compatible with both modes and work well in most environments. Modify with care, and consult the relevant manual pages if advanced tuning is required." } ] }, @@ -159,6 +187,41 @@ } ] }, + { + "function": "FAKE_MAC", + "type": { + "dataType": "boolean", + "elements": [ + { + "elementType": "input", + "elementOptions": [ + { + "type": "checkbox" + } + ], + "transformers": [] + } + ] + }, + "default_value": false, + "options": [], + "localized": [ + "name", + "description" + ], + "name": [ + { + "language_code": "en_us", + "string": "Fake MAC" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "If enabled and the mode is set to fping, the plugin will also discover new devices not already in the database. Enabling this setting generates a fake MAC address from the IP address to track devices. This may cause inconsistencies if IPs change or devices are re-discovered with a different MAC. Static IPs are recommended. Device type and icon might not be detected correctly, and some plugins may fail if they rely on a valid MAC address. When unchecked, devices without a MAC address are skipped." + } + ] + }, { "function": "RUN_SCHD", "type": { diff --git a/front/plugins/icmp_scan/icmp.py b/front/plugins/icmp_scan/icmp.py index 3e2a2664..c98db91b 100755 --- a/front/plugins/icmp_scan/icmp.py +++ b/front/plugins/icmp_scan/icmp.py @@ -16,6 +16,7 @@ from logger import mylog, Logger # noqa: E402 [flake8 lint suppression] from helper import get_setting_value # noqa: E402 [flake8 lint suppression] from const import logPath # noqa: E402 [flake8 lint suppression] from models.device_instance import DeviceInstance # noqa: E402 [flake8 lint suppression] +from utils.crypto_utils import string_to_mac_hash # noqa: E402 [flake8 lint suppression] import conf # noqa: E402 [flake8 lint suppression] from pytz import timezone # noqa: E402 [flake8 lint suppression] @@ -32,13 +33,39 @@ LOG_FILE = os.path.join(LOG_PATH, f'script.{pluginName}.log') RESULT_FILE = os.path.join(LOG_PATH, f'last_result.{pluginName}.log') +def parse_scan_subnets(subnets): + """Extract subnet and interface from SCAN_SUBNETS""" + ranges = [] + interfaces = [] + for entry in subnets: + parts = entry.split("--interface=") + ranges.append(parts[0].strip()) + if len(parts) > 1: + interfaces.append(parts[1].strip()) + return ranges, interfaces + + +def get_device_by_ip(ip, all_devices): + """Get existing device based on IP""" + for device in all_devices: + if device["devLastIP"] == ip: + return device + + return None + + def main(): mylog('verbose', [f'[{pluginName}] In script']) timeout = get_setting_value('ICMP_RUN_TIMEOUT') args = get_setting_value('ICMP_ARGS') - in_regex = get_setting_value('ICMP_IN_REGEX') + regex = get_setting_value('ICMP_IN_REGEX') + mode = get_setting_value('ICMP_MODE') + fakeMac = get_setting_value('ICMP_FAKE_MAC') + scan_subnets = get_setting_value("SCAN_SUBNETS") + + subnets, interfaces = parse_scan_subnets(scan_subnets) # Initialize the Plugin obj output file plugin_objects = Plugin_Objects(RESULT_FILE) @@ -50,33 +77,13 @@ def main(): all_devices = device_handler.getAll() # Compile the regex for efficiency if it will be used multiple times - regex_pattern = re.compile(in_regex) + regex_pattern = re.compile(regex) - # Filter devices based on the regex match - filtered_devices = [ - device for device in all_devices - if regex_pattern.match(device['devLastIP']) - ] + if mode == "ping": + plugin_objects = execute_ping(timeout, args, all_devices, regex_pattern, plugin_objects) - mylog('verbose', [f'[{pluginName}] Devices to PING: {len(filtered_devices)}']) - - for device in filtered_devices: - is_online, output = execute_scan(device['devLastIP'], timeout, args) - - mylog('verbose', [f"[{pluginName}] ip: {device['devLastIP']} is_online: {is_online}"]) - - if is_online: - plugin_objects.add_object( - # "MAC", "IP", "Name", "Output" - primaryId = device['devMac'], - secondaryId = device['devLastIP'], - watched1 = device['devName'], - watched2 = output.replace('\n', ''), - watched3 = '', - watched4 = '', - extra = '', - foreignKey = device['devMac'] - ) + elif mode == "fping": + plugin_objects = execute_fping(timeout, args, all_devices, plugin_objects, subnets, interfaces, fakeMac) plugin_objects.write_result_file() @@ -88,27 +95,24 @@ def main(): # =============================================================================== # Execute scan # =============================================================================== -def execute_scan(ip, timeout, args): +def execute_ping(timeout, args, all_devices, regex_pattern, plugin_objects): """ - Execute the ICMP command on IP. + Execute ICMP command on filtered devices. """ - icmp_args = ['ping'] + args.split() + [ip] + # Filter devices based on the regex match + filtered_devices = [ + device for device in all_devices + if regex_pattern.match(device['devLastIP']) + ] - # Execute command - output = "" + mylog('verbose', [f'[{pluginName}] Devices to PING: {len(filtered_devices)}']) - try: - # try runnning a subprocess with a forced (timeout) in case the subprocess hangs - output = subprocess.check_output( - icmp_args, - universal_newlines=True, - stderr=subprocess.STDOUT, - timeout=(timeout), - text=True - ) + for device in filtered_devices: - mylog('verbose', [f'[{pluginName}] DEBUG OUTPUT : {output}']) + cmd = ["ping"] + args.split() + [device['devLastIP']] + + output = "" # Parse output using case-insensitive regular expressions # Synology-NAS:/# ping -i 0.5 -c 3 -W 8 -w 9 192.168.1.82 @@ -128,31 +132,115 @@ def execute_scan(ip, timeout, args): # --- 192.168.1.92 ping statistics --- # 3 packets transmitted, 0 packets received, 100% packet loss - # TODO: parse output and return True if online, False if Offline (100% packet loss, bad address) - is_online = True + try: + output = subprocess.check_output( + cmd, universal_newlines=True, stderr=subprocess.STDOUT, timeout=timeout, text=True + ) + + mylog("verbose", [f"[{pluginName}] DEBUG OUTPUT : {output}"]) - # Check for 0% packet loss in the output - if re.search(r"0% packet loss", output, re.IGNORECASE): is_online = True - elif re.search(r"bad address", output, re.IGNORECASE): - is_online = False - elif re.search(r"100% packet loss", output, re.IGNORECASE): - is_online = False + if re.search(r"0% packet loss", output, re.IGNORECASE): + is_online = True + elif re.search(r"bad address", output, re.IGNORECASE): + is_online = False + elif re.search(r"100% packet loss", output, re.IGNORECASE): + is_online = False - return is_online, output + if is_online: - except subprocess.CalledProcessError as e: - # An error occurred, handle it - mylog('verbose', [f'[{pluginName}] ⚠ ERROR - check logs']) - mylog('verbose', [f'[{pluginName}]', e.output]) + plugin_objects.add_object( + # "MAC", "IP", "Name", "Output" + primaryId = device['devMac'], + secondaryId = device['devLastIP'], + watched1 = device['devName'], + watched2 = output.replace('\n', ''), + watched3 = '', + watched4 = '', + extra = '', + foreignKey = device['devMac'] + ) - return False, output + mylog('verbose', [f"[{pluginName}] ip: {device['devLastIP']} is_online: {is_online}"]) + + except subprocess.CalledProcessError as e: + mylog("verbose", [f"[{pluginName}] ⚠ ERROR - check logs"]) + mylog("verbose", [f"[{pluginName}]", e.output]) + except subprocess.TimeoutExpired: + mylog("verbose", [f"[{pluginName}] TIMEOUT - process terminated"]) + + return plugin_objects + + +def execute_fping(timeout, args, all_devices, plugin_objects, subnets, interfaces, fakeMac): + """ + Run fping command and return alive IPs + """ + cmd = ["fping", "-a"] + + if interfaces: + cmd += ["-I", ",".join(interfaces)] + + # Build a lookup dict once + device_map = {d["devLastIP"]: d for d in all_devices if d.get("devLastIP")} + + known_ips = list(device_map.keys()) + online_ips = [] + + cmd += args.split() + cmd += subnets + cmd += known_ips + + mylog("verbose", [f"[{pluginName}] fping cmd: {' '.join(cmd)}"]) + + try: + output = subprocess.check_output( + cmd, + stderr=subprocess.DEVNULL, + timeout=timeout, + text=True + ) + online_ips = [line.strip() for line in output.splitlines() if line.strip()] + + except subprocess.CalledProcessError: + online_ips = [] except subprocess.TimeoutExpired: - mylog('verbose', [f'[{pluginName}] TIMEOUT - the process forcefully terminated as timeout reached']) - return False, output + mylog("verbose", [f"[{pluginName}] fping timeout"]) + online_ips = [] - return False, output + # process all online IPs + for onlineIp in online_ips: + if onlineIp in known_ips: + # use lookup dict instead of looping + device = device_map.get(onlineIp) + if device: + plugin_objects.add_object( + primaryId = device['devMac'], + secondaryId = device['devLastIP'], + watched1 = device['devName'], + watched2 = 'mode:fping', + watched3 = '', + watched4 = '', + extra = '', + foreignKey = device['devMac'] + ) + else: + mylog("none", [f"[{pluginName}] ERROR reverse device lookup failed unexpectedly for {onlineIp}"]) + elif fakeMac: + fakeMacFromIp = string_to_mac_hash(onlineIp) + plugin_objects.add_object( + primaryId = fakeMacFromIp, + secondaryId = onlineIp, + watched1 = "(unknown)", + watched2 = 'mode:fping', + watched3 = '', + watched4 = '', + extra = '', + foreignKey = fakeMacFromIp + ) + else: + mylog('verbose', [f"[{pluginName}] Skipping: {onlineIp}, as new IP and ICMP_FAKE_MAC setting not enabled"]) # =============================================================================== diff --git a/install/debian12/install_dependencies.debian12.sh b/install/debian12/install_dependencies.debian12.sh index 5fb09738..0f0b66d5 100755 --- a/install/debian12/install_dependencies.debian12.sh +++ b/install/debian12/install_dependencies.debian12.sh @@ -17,19 +17,19 @@ fi # Check if script is run as root if [[ $EUID -ne 0 ]]; then - echo "This script must be run as root. Please use 'sudo'." + echo "This script must be run as root. Please use 'sudo'." exit 1 fi # Install dependencies apt-get install -y \ tini snmp ca-certificates curl libwww-perl arp-scan perl apt-utils cron sudo gettext-base \ - nginx-light php php-cgi php-fpm php-sqlite3 php-curl sqlite3 dnsutils net-tools \ - python3 python3-dev iproute2 nmap python3-pip zip usbutils traceroute nbtscan avahi-daemon avahi-utils openrc build-essential git + nginx-light php php-cgi php-fpm php-sqlite3 php-curl sqlite3 dnsutils net-tools \ + python3 python3-dev iproute2 nmap fping python3-pip zip usbutils traceroute nbtscan avahi-daemon avahi-utils openrc build-essential git # alternate dependencies sudo apt-get install nginx nginx-core mtr php-fpm php8.2-fpm php-cli php8.2 php8.2-sqlite3 -y -sudo phpenmod -v 8.2 sqlite3 +sudo phpenmod -v 8.2 sqlite3 # setup virtual python environment so we can use pip3 to install packages apt-get install python3-venv -y diff --git a/install/proxmox/proxmox-install-netalertx.sh b/install/proxmox/proxmox-install-netalertx.sh index a1ed372e..64c3872e 100755 --- a/install/proxmox/proxmox-install-netalertx.sh +++ b/install/proxmox/proxmox-install-netalertx.sh @@ -9,7 +9,7 @@ set -o pipefail # Safe IFS IFS=$' \t\n' -# 🛑 Important: This is only used for the bare-metal install 🛑 +# 🛑 Important: This is only used for the bare-metal install 🛑 # Colors (guarded) if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then RESET='\e[0m' @@ -37,13 +37,13 @@ DB_FILE=app.db NGINX_CONF_FILE=netalertx.conf WEB_UI_DIR=/var/www/html/netalertx NGINX_CONFIG=/etc/nginx/conf.d/$NGINX_CONF_FILE -OUI_FILE="/usr/share/arp-scan/ieee-oui.txt" +OUI_FILE="/usr/share/arp-scan/ieee-oui.txt" FILEDB=$INSTALL_DIR/db/$DB_FILE -# DO NOT CHANGE ANYTHING ABOVE THIS LINE! +# DO NOT CHANGE ANYTHING ABOVE THIS LINE! # Check if script is run as root if [[ $EUID -ne 0 ]]; then - echo "This script must be run as root." + echo "This script must be run as root." exit 1 fi @@ -51,7 +51,7 @@ fi if [ -z "${NETALERTX_ASSUME_YES:-}" ] && [ -z "${ASSUME_YES:-}" ] && [ -z "${NETALERTX_FORCE:-}" ]; then printf "%b\n" "------------------------------------------------------------------------" printf "%b\n" "${RED}[WARNING] ${RESET}This script should be run on a fresh server" - printf "%b\n" "${RED}[WARNING] ${RESET}This script will install NetAlertX and will:" + printf "%b\n" "${RED}[WARNING] ${RESET}This script will install NetAlertX and will:" printf "%b\n" "${RED}[WARNING] ${RESET}• Update OS with apt-get update/upgrade" printf "%b\n" "${RED}[WARNING] ${RESET}• Overwrite existing files under ${INSTALL_DIR} " printf "%b\n" "${RED}[WARNING] ${RESET}• Wipe any existing database" @@ -137,7 +137,7 @@ printf "%b\n" "----------------------------------------------------------------- printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Detected OS: ${OS_ID} ${OS_VER}" printf "%b\n" "--------------------------------------------------------------------------" -if +if [ "${OS_ID}" = "ubuntu" ] && printf '%s' "${OS_VER}" | grep -q '^24'; then # Ubuntu 24.x typically ships PHP 8.3; add ondrej/php PPA and set 8.4 printf "%b\n" "--------------------------------------------------------------------------" @@ -152,15 +152,15 @@ elif printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Debian 13 detected - using built-in PHP 8.4" printf "%b\n" "--------------------------------------------------------------------------" fi - + apt-get install -y --no-install-recommends \ tini snmp ca-certificates curl libwww-perl arp-scan perl apt-utils cron sudo \ php8.4 php8.4-cgi php8.4-fpm php8.4-sqlite3 php8.4-curl sqlite3 dnsutils net-tools mtr \ - python3 python3-dev iproute2 nmap python3-pip zip usbutils traceroute nbtscan \ + python3 python3-dev iproute2 nmap fping python3-pip zip usbutils traceroute nbtscan \ avahi-daemon avahi-utils build-essential git gnupg2 lsb-release \ debian-archive-keyring python3-venv -if +if [ "${OS_ID}" = "ubuntu" ] && printf '%s' "${OS_VER}" | grep -q '^24'; then # Set PHP 8.4 as the default alternatives where applicable update-alternatives --set php /usr/bin/php8.4 || true systemctl enable php8.4-fpm || true @@ -211,7 +211,7 @@ source /opt/myenv/bin/activate python -m pip install --upgrade pip python -m pip install -r "${INSTALLER_DIR}/requirements.txt" -# Backup default NGINX site just in case +# Backup default NGINX site just in case if [ -L /etc/nginx/sites-enabled/default ] ; then rm /etc/nginx/sites-enabled/default elif [ -f /etc/nginx/sites-enabled/default ]; then @@ -350,7 +350,7 @@ printf "%b\n" "----------------------------------------------------------------- printf "%b\n" "${GREEN}[STARTING] ${RESET}Starting PHP and NGINX" printf "%b\n" "--------------------------------------------------------------------------" /etc/init.d/php8.4-fpm start -nginx -t || { +nginx -t || { printf "%b\n" "--------------------------------------------------------------------------" printf "%b\n" "${RED}[ERROR] ${RESET}NGINX config test failed!" printf "%b\n" "--------------------------------------------------------------------------"; exit 1; } @@ -405,7 +405,7 @@ systemctl daemon-reload systemctl enable netalertx.service systemctl start netalertx.service systemctl restart nginx - + # Verify service is running if systemctl is-active --quiet netalertx.service; then printf "%b\n" "--------------------------------------------------------------------------" diff --git a/install/ubuntu24/install.sh b/install/ubuntu24/install.sh index e934ee24..20eec65f 100755 --- a/install/ubuntu24/install.sh +++ b/install/ubuntu24/install.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# 🛑 Important: This is only used for the bare-metal install 🛑 +# 🛑 Important: This is only used for the bare-metal install 🛑 echo "---------------------------------------------------------" echo "[INSTALL] Starting NetAlertX installation for Ubuntu" @@ -34,7 +34,7 @@ ALWAYS_FRESH_INSTALL=false # Set to true to always reset /config and /db on eac # Check if script is run as root if [[ $EUID -ne 0 ]]; then - echo "[INSTALL] This script must be run as root. Please use 'sudo'." + echo "[INSTALL] This script must be run as root. Please use 'sudo'." exit 1 fi @@ -62,7 +62,7 @@ apt-get install -y --no-install-recommends \ # Install plugin dependencies apt-get install -y --no-install-recommends \ - dnsutils mtr arp-scan snmp iproute2 nmap zip usbutils traceroute nbtscan avahi-daemon avahi-utils + dnsutils mtr arp-scan snmp iproute2 nmap fping zip usbutils traceroute nbtscan avahi-daemon avahi-utils # nginx-core install nginx and nginx-common as dependencies apt-get install -y --no-install-recommends \ @@ -156,14 +156,14 @@ python3 -m venv "${VENV_DIR}" source "${VENV_DIR}/bin/activate" if [[ ! -f "${REQUIREMENTS_FILE}" ]]; then - echo "[INSTALL] requirements.txt not found at ${REQUIREMENTS_FILE}" - exit 1 + echo "[INSTALL] requirements.txt not found at ${REQUIREMENTS_FILE}" + exit 1 fi -pip3 install -r "${REQUIREMENTS_FILE}" || { - echo "[INSTALL] Failed to install Python dependencies" - exit 1 -} +pip3 install -r "${REQUIREMENTS_FILE}" || { + echo "[INSTALL] Failed to install Python dependencies" + exit 1 +} # We now should have all dependencies and files in place @@ -179,11 +179,11 @@ fi # if custom variables not set we do not need to do anything -if [ -n "${TZ}" ]; then - FILECONF=${INSTALL_DIR}/config/${CONF_FILE} +if [ -n "${TZ}" ]; then + FILECONF=${INSTALL_DIR}/config/${CONF_FILE} if [ -f "$FILECONF" ]; then sed -i -e "s|Europe/Berlin|${TZ}|g" "${INSTALL_DIR}/config/${CONF_FILE}" - else + else sed -i -e "s|Europe/Berlin|${TZ}|g" "${INSTALL_DIR}/back/${CONF_FILE}.bak" fi fi @@ -253,7 +253,7 @@ else if [ -f "${SYSTEM_SERVICES}/update_vendors.sh" ]; then "${SYSTEM_SERVICES}/update_vendors.sh" else - echo "[INSTALL] update_vendors.sh script not found in ${SYSTEM_SERVICES}." + echo "[INSTALL] update_vendors.sh script not found in ${SYSTEM_SERVICES}." fi fi @@ -282,12 +282,12 @@ touch "${INSTALL_DIR}"/api/user_notifications.json mkdir -p "${INSTALL_DIR}"/log/plugins -# DANGER ZONE: ALWAYS_FRESH_INSTALL +# DANGER ZONE: ALWAYS_FRESH_INSTALL if [ "${ALWAYS_FRESH_INSTALL}" = true ]; then echo "[INSTALL] ❗ ALERT /db and /config folders are cleared because the ALWAYS_FRESH_INSTALL is set to: ${ALWAYS_FRESH_INSTALL}❗" # Delete content of "/config/" rm -rf "${INSTALL_DIR}/config/"* - + # Delete content of "/db/" rm -rf "${INSTALL_DIR}/db/"* fi From 9b37e6692013c7e336bf6640d5bd83f384a63fdc Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Sun, 4 Jan 2026 11:32:45 +1100 Subject: [PATCH 6/6] PLG: ICMP v2 + incorrect import #1331 Signed-off-by: jokob-sk --- front/plugins/adguard_import/adguard_import.py | 4 ++-- front/plugins/icmp_scan/icmp.py | 4 ++-- front/plugins/nmap_dev_scan/nmap_dev.py | 4 ++-- front/plugins/pihole_api_scan/pihole_api_scan.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/front/plugins/adguard_import/adguard_import.py b/front/plugins/adguard_import/adguard_import.py index 46fc86ae..87b668d5 100644 --- a/front/plugins/adguard_import/adguard_import.py +++ b/front/plugins/adguard_import/adguard_import.py @@ -11,7 +11,7 @@ sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"]) from const import logPath # noqa: E402, E261 from plugin_helper import Plugin_Objects # noqa: E402, E261 -from utils.crypto_utils import string_to_mac_hash # noqa: E402 [flake8 lint suppression] +from utils.crypto_utils import string_to_fake_mac # noqa: E402 [flake8 lint suppression] from logger import mylog, Logger # noqa: E402, E261 from helper import get_setting_value # noqa: E402, E261 import conf # noqa: E402, E261 @@ -120,7 +120,7 @@ def main(): if not mac and fake_mac_enabled: mylog("verbose", [f"[{pluginName}] Generating FAKE MAC for ip: {ip}"]) - mac = string_to_mac_hash(ip) + mac = string_to_fake_mac(ip) if not mac: # Skip devices without MAC if fake MAC not allowed diff --git a/front/plugins/icmp_scan/icmp.py b/front/plugins/icmp_scan/icmp.py index c98db91b..7246b86d 100755 --- a/front/plugins/icmp_scan/icmp.py +++ b/front/plugins/icmp_scan/icmp.py @@ -16,7 +16,7 @@ from logger import mylog, Logger # noqa: E402 [flake8 lint suppression] from helper import get_setting_value # noqa: E402 [flake8 lint suppression] from const import logPath # noqa: E402 [flake8 lint suppression] from models.device_instance import DeviceInstance # noqa: E402 [flake8 lint suppression] -from utils.crypto_utils import string_to_mac_hash # noqa: E402 [flake8 lint suppression] +from utils.crypto_utils import string_to_fake_mac # noqa: E402 [flake8 lint suppression] import conf # noqa: E402 [flake8 lint suppression] from pytz import timezone # noqa: E402 [flake8 lint suppression] @@ -228,7 +228,7 @@ def execute_fping(timeout, args, all_devices, plugin_objects, subnets, interface else: mylog("none", [f"[{pluginName}] ERROR reverse device lookup failed unexpectedly for {onlineIp}"]) elif fakeMac: - fakeMacFromIp = string_to_mac_hash(onlineIp) + fakeMacFromIp = string_to_fake_mac(onlineIp) plugin_objects.add_object( primaryId = fakeMacFromIp, secondaryId = onlineIp, diff --git a/front/plugins/nmap_dev_scan/nmap_dev.py b/front/plugins/nmap_dev_scan/nmap_dev.py index 90d7f030..421ed3ec 100755 --- a/front/plugins/nmap_dev_scan/nmap_dev.py +++ b/front/plugins/nmap_dev_scan/nmap_dev.py @@ -16,7 +16,7 @@ from plugin_helper import Plugin_Objects # noqa: E402 [flake8 lint suppression] from logger import mylog, Logger # noqa: E402 [flake8 lint suppression] from helper import get_setting_value # noqa: E402 [flake8 lint suppression] from const import logPath # noqa: E402 [flake8 lint suppression] -from utils.crypto_utils import string_to_mac_hash # noqa: E402 [flake8 lint suppression] +from utils.crypto_utils import string_to_fake_mac # noqa: E402 [flake8 lint suppression] import conf # noqa: E402 [flake8 lint suppression] from pytz import timezone # noqa: E402 [flake8 lint suppression] @@ -159,7 +159,7 @@ def parse_nmap_xml(xml_output, interface, fakeMac): if (ip != '' and mac != '') or (ip != '' and fakeMac): if mac == '' and fakeMac: - mac = string_to_mac_hash(ip) + mac = string_to_fake_mac(ip) devices_list.append({ 'name': hostname, diff --git a/front/plugins/pihole_api_scan/pihole_api_scan.py b/front/plugins/pihole_api_scan/pihole_api_scan.py index 70a33018..c8a9aa75 100644 --- a/front/plugins/pihole_api_scan/pihole_api_scan.py +++ b/front/plugins/pihole_api_scan/pihole_api_scan.py @@ -23,7 +23,7 @@ from helper import get_setting_value # noqa: E402 [flake8 lint suppression] from const import logPath # noqa: E402 [flake8 lint suppression] import conf # noqa: E402 [flake8 lint suppression] from pytz import timezone # noqa: E402 [flake8 lint suppression] -from utils.crypto_utils import string_to_mac_hash # noqa: E402 [flake8 lint suppression] +from utils.crypto_utils import string_to_fake_mac # noqa: E402 [flake8 lint suppression] # Setup timezone & logger using standard NAX helpers conf.tz = timezone(get_setting_value('TIMEZONE')) @@ -228,7 +228,7 @@ def gather_device_entries(): # ensure fake mac if enabled if PIHOLEAPI_FAKE_MAC and is_mac(tmpMac) is False: - tmpMac = string_to_mac_hash(ip) + tmpMac = string_to_fake_mac(ip) entries.append({ 'mac': tmpMac,