From f4a37178590b999f8b9264214ee435057e228c6d Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Sun, 1 Jun 2025 13:59:54 +1000 Subject: [PATCH] FQDN, Dig refactor, docs #1065 --- back/app.conf | 2 +- back/app.db | Bin 180224 -> 192512 bytes back/app_clean.db | Bin 0 -> 192512 bytes back/app_old.db | Bin 0 -> 180224 bytes docs/DATABASE.md | 1 + docs/NAME_RESOLUTION.md | 1 + docs/PLUGINS.md | 1 + front/deviceDetailsEdit.php | 24 +- front/devices.php | 9 +- front/php/server/devices.php | 3 +- front/php/server/util.php | 3 +- front/php/templates/language/ar_ar.json | 3 + front/php/templates/language/ca_ca.json | 5 +- front/php/templates/language/cs_cz.json | 5 +- front/php/templates/language/de_de.json | 5 +- front/php/templates/language/en_us.json | 3 + front/php/templates/language/es_es.json | 5 +- front/php/templates/language/fr_fr.json | 5 +- front/php/templates/language/it_it.json | 5 +- front/php/templates/language/nb_no.json | 3 + front/php/templates/language/pl_pl.json | 3 + front/php/templates/language/pt_br.json | 5 +- front/php/templates/language/ru_ru.json | 5 +- front/php/templates/language/tr_tr.json | 5 +- front/php/templates/language/uk_ua.json | 5 +- front/php/templates/language/zh_cn.json | 5 +- front/plugins/_publisher_apprise/README.md | 28 + .../_publisher_apprise/apprise_telegram.png | Bin 0 -> 142275 bytes front/plugins/avahi_scan/avahi_scan.py | 15 +- front/plugins/dig_scan/README.md | 7 + front/plugins/dig_scan/config.json | 385 +++++++++++++ front/plugins/dig_scan/digscan.py | 133 +++++ front/plugins/nbtscan_scan/config.json | 2 +- front/plugins/nbtscan_scan/nbtscan.py | 9 +- front/plugins/newdev_template/config.json | 36 ++ front/plugins/nslookup_scan/nslookup.py | 12 +- front/plugins/ui_settings/config.json | 3 +- front/workflowsCore.php | 2 +- scripts/db_empty/README.md | 19 + scripts/db_empty/db_empty.py | 26 + server/__main__.py | 2 +- server/const.py | 1 + server/database.py | 524 +----------------- server/graphql_server/graphql_schema.py | 3 +- server/helper.py | 206 +------ server/initialise.py | 41 +- server/scan/device_handling.py | 156 +++--- server/scan/name_resolution.py | 83 +++ server/workflows/app_events.py | 2 +- test/test_helper.py | 9 +- 50 files changed, 941 insertions(+), 874 deletions(-) create mode 100755 back/app_clean.db create mode 100755 back/app_old.db create mode 100755 front/plugins/_publisher_apprise/apprise_telegram.png create mode 100755 front/plugins/dig_scan/README.md create mode 100755 front/plugins/dig_scan/config.json create mode 100755 front/plugins/dig_scan/digscan.py create mode 100755 scripts/db_empty/README.md create mode 100755 scripts/db_empty/db_empty.py create mode 100755 server/scan/name_resolution.py diff --git a/back/app.conf b/back/app.conf index bb53a13e..b00fa6f2 100755 --- a/back/app.conf +++ b/back/app.conf @@ -20,7 +20,7 @@ DISCOVER_PLUGINS=True SCAN_SUBNETS=['192.168.1.0/24 --interface=eth0'] TIMEZONE='Europe/Berlin' -LOADED_PLUGINS=['ARPSCAN','CSVBCKP','DBCLNP', 'INTRNT','MAINT','NEWDEV','NSLOOKUP','NTFPRCS', 'AVAHISCAN', 'SETPWD','SMTP', 'SYNC', 'VNDRPDT', 'WORKFLOWS', 'UI'] +LOADED_PLUGINS=['ARPSCAN','CSVBCKP','DBCLNP', 'DIGSCAN', 'INTRNT','MAINT','NEWDEV', 'NBTSCAN', 'NSLOOKUP','NTFPRCS', 'AVAHISCAN', 'SETPWD','SMTP', 'SYNC', 'VNDRPDT', 'WORKFLOWS', 'UI'] DAYS_TO_KEEP_EVENTS=90 # Used for generating links in emails. Make sure not to add a trailing slash! diff --git a/back/app.db b/back/app.db index ff4e27315457b56f048e1ce792b7f6e37591bc93..a0bdc92c4e454027951a4569da85c77d8db248e1 100755 GIT binary patch literal 192512 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 literal 180224 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 diff --git a/back/app_clean.db b/back/app_clean.db new file mode 100755 index 0000000000000000000000000000000000000000..a0bdc92c4e454027951a4569da85c77d8db248e1 GIT binary patch literal 192512 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 literal 0 HcmV?d00001 diff --git a/back/app_old.db b/back/app_old.db new file mode 100755 index 0000000000000000000000000000000000000000..ff4e27315457b56f048e1ce792b7f6e37591bc93 GIT binary patch literal 180224 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 literal 0 HcmV?d00001 diff --git a/docs/DATABASE.md b/docs/DATABASE.md index e55f4303..fb1eb43b 100755 --- a/docs/DATABASE.md +++ b/docs/DATABASE.md @@ -38,6 +38,7 @@ | `devSyncHubNode` | The NetAlertX node ID used for synchronization between NetAlertX instances. | `node_1` | | `devSourcePlugin` | Source plugin that discovered the device. | `ARPSCAN` | | `devCustomProps` | [Custom properties](./CUSTOM_PROPERTIES.md) related to the device. The value is a base64-encoded JSON object. | `PHN2ZyB...` | +| `devFQDN` | Fully qualified domain name. | `raspberrypi.local` | To understand how values of these fields influuence application behavior, such as Notifications or Network topology, see also: diff --git a/docs/NAME_RESOLUTION.md b/docs/NAME_RESOLUTION.md index dd8a5fec..0f788703 100755 --- a/docs/NAME_RESOLUTION.md +++ b/docs/NAME_RESOLUTION.md @@ -9,6 +9,7 @@ For best results, ensure the following name resolution plugins are enabled: - **AVAHISCAN** – Uses mDNS/Avahi to resolve local network names. - **NBTSCAN** – Queries NetBIOS to find device names. - **NSLOOKUP** – Performs standard DNS lookups. +- **DIGSCAN** – Performs Name Resolution with the Dig utility (DNS). You can check which plugins are active in your _Settings_ section and enable any that are missing. diff --git a/docs/PLUGINS.md b/docs/PLUGINS.md index 7700a8ca..fd02fa37 100755 --- a/docs/PLUGINS.md +++ b/docs/PLUGINS.md @@ -56,6 +56,7 @@ Device-detecting plugins insert values into the `CurrentScan` database table. T | `DDNS` | ⚙ | DDNS update | | | Script | [ddns_update](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/ddns_update/) | | `DHCPLSS` | 🔍/📥/🆎| Import devices from DHCP leases | | | Script | [dhcp_leases](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/dhcp_leases/) | | `DHCPSRVS` | ♻ | DHCP servers | | | Script | [dhcp_servers](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/dhcp_servers/) | +| `DIGSCAN` | 🆎 | Dig (DNS) Name resolution | | | Script | [dig_scan](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/dig_scan/) | | `FREEBOX` | 🔍/♻/🆎| Pull data and names from Freebox/Iliadbox | | | Script | [freebox](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/freebox/) | | `ICMP` | ♻ | ICMP (ping) status checker | | | Script | [icmp_scan](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/icmp_scan/) | | `INTRNT` | 🔍 | Internet IP scanner | | | Script | [internet_ip](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/internet_ip/) | diff --git a/front/deviceDetailsEdit.php b/front/deviceDetailsEdit.php index 73cf4136..12f598d4 100755 --- a/front/deviceDetailsEdit.php +++ b/front/deviceDetailsEdit.php @@ -69,8 +69,8 @@ // columns to hide hiddenFields = ["NEWDEV_devScan", "NEWDEV_devPresentLastScan" ] - // columns to disable - conditional depending if a new dummy device is created - disabledFields = mac == "new" ? ["NEWDEV_devLastNotification", "NEWDEV_devFirstConnection", "NEWDEV_devLastConnection"] : ["NEWDEV_devLastNotification", "NEWDEV_devFirstConnection", "NEWDEV_devLastConnection", "NEWDEV_devMac", "NEWDEV_devLastIP", "NEWDEV_devSyncHubNode" ]; + // columns to disable/readonly - conditional depending if a new dummy device is created + disabledFields = mac == "new" ? ["NEWDEV_devLastNotification", "NEWDEV_devFirstConnection", "NEWDEV_devLastConnection"] : ["NEWDEV_devLastNotification", "NEWDEV_devFirstConnection", "NEWDEV_devLastConnection", "NEWDEV_devMac", "NEWDEV_devLastIP", "NEWDEV_devSyncHubNode", "NEWDEV_devFQDN" ]; // Grouping of fields into categories with associated documentation links const fieldGroups = { @@ -82,16 +82,7 @@ inputGroupClasses: "field-group main-group col-lg-4 col-sm-6 col-xs-12", labelClasses: "col-sm-4 col-xs-12 control-label", inputClasses: "col-sm-8 col-xs-12 input-group" - }, - // Group for session information - DevDetail_SessionInfo_Title: { - data: ["devStatus", "devLastConnection", "devFirstConnection"], - docs: "https://github.com/jokob-sk/NetAlertX/blob/main/docs/SESSION_INFO.md", - iconClass: "fa fa-calendar", - inputGroupClasses: "field-group session-group col-lg-4 col-sm-6 col-xs-12", - labelClasses: "col-sm-4 col-xs-12 control-label", - inputClasses: "col-sm-8 col-xs-12 input-group" - }, + }, // Group for event and alert settings DevDetail_EveandAl_Title: { data: ["devAlertEvents", "devAlertDown", "devSkipRepeated"], @@ -119,6 +110,15 @@ labelClasses: "col-sm-4 col-xs-12 control-label", inputClasses: "col-sm-8 col-xs-12 input-group" }, + // Group for session information + DevDetail_SessionInfo_Title: { + data: ["devStatus", "devLastConnection", "devFirstConnection", "devFQDN"], + docs: "https://github.com/jokob-sk/NetAlertX/blob/main/docs/SESSION_INFO.md", + iconClass: "fa fa-calendar", + inputGroupClasses: "field-group session-group col-lg-4 col-sm-6 col-xs-12", + labelClasses: "col-sm-4 col-xs-12 control-label", + inputClasses: "col-sm-8 col-xs-12 input-group" + }, // Group for Custom properties. DevDetail_CustomProperties_Title: { data: ["devCustomProps"], diff --git a/front/devices.php b/front/devices.php index a7b07aa7..e4c917c6 100755 --- a/front/devices.php +++ b/front/devices.php @@ -538,7 +538,8 @@ function mapColumnIndexToFieldName(index, tableColumnVisible) { "devSourcePlugin", "devPresentLastScan", "devAlertDown", - "devCustomProps" + "devCustomProps", + "devFQDN" ]; // console.log("OrderBy: " + columnNames[tableColumnOrder[index]]); @@ -648,6 +649,7 @@ function initializeDatatable (status) { devParentChildrenCount devIpLong devCustomProps + devFQDN } count } @@ -722,7 +724,8 @@ function initializeDatatable (status) { device.devSourcePlugin || "", device.devPresentLastScan || "", device.devAlertDown || "", - device.devCustomProps || "" + device.devCustomProps || "", + device.devFQDN || "" ]; const newRow = []; @@ -760,7 +763,7 @@ function initializeDatatable (status) { {visible: false, targets: tableColumnHide }, {className: 'text-center', targets: [mapIndx(4), mapIndx(9), mapIndx(10), mapIndx(15), mapIndx(18)] }, {className: 'iconColumn text-center', targets: [mapIndx(3)]}, - {width: '80px', targets: [mapIndx(6), mapIndx(7), mapIndx(15)] }, + {width: '80px', targets: [mapIndx(6), mapIndx(7), mapIndx(15), mapIndx(27)] }, {width: '85px', targets: [mapIndx(9)] }, {width: '30px', targets: [mapIndx(3), mapIndx(10), mapIndx(13), mapIndx(18)] }, {orderData: [mapIndx(12)], targets: mapIndx(8) }, diff --git a/front/php/server/devices.php b/front/php/server/devices.php index 193aa507..78f9bd80 100755 --- a/front/php/server/devices.php +++ b/front/php/server/devices.php @@ -112,7 +112,8 @@ function getServerDeviceData() { "devSessions" => 0, "devEvents" => 0, "devDownAlerts" => 0, - "devPresenceHours" => 0 + "devPresenceHours" => 0, + "devFQDN" => "" ]; echo json_encode($deviceData); return; diff --git a/front/php/server/util.php b/front/php/server/util.php index 84c23107..9e6d0b95 100755 --- a/front/php/server/util.php +++ b/front/php/server/util.php @@ -586,7 +586,8 @@ function getDevicesColumns(){ "devSite", "devSSID", "devSourcePlugin", - "devCustomProps" + "devCustomProps", + "devFQDN" ]; return $columns; diff --git a/front/php/templates/language/ar_ar.json b/front/php/templates/language/ar_ar.json index 8778a302..ee9e50fb 100755 --- a/front/php/templates/language/ar_ar.json +++ b/front/php/templates/language/ar_ar.json @@ -211,6 +211,7 @@ "Device_TableHead_AlertDown": "", "Device_TableHead_Connected_Devices": "", "Device_TableHead_CustomProps": "", + "Device_TableHead_FQDN": "", "Device_TableHead_Favorite": "", "Device_TableHead_FirstSession": "", "Device_TableHead_GUID": "", @@ -559,6 +560,8 @@ "Presence_Shortcut_Favorites": "", "Presence_Shortcut_NewDevices": "", "Presence_Title": "", + "REFRESH_FQDN_description": "", + "REFRESH_FQDN_name": "", "REPORT_DASHBOARD_URL_description": "", "REPORT_DASHBOARD_URL_name": "", "REPORT_ERROR": "", diff --git a/front/php/templates/language/ca_ca.json b/front/php/templates/language/ca_ca.json index 8448e3e3..ce06f848 100755 --- a/front/php/templates/language/ca_ca.json +++ b/front/php/templates/language/ca_ca.json @@ -211,6 +211,7 @@ "Device_TableHead_AlertDown": "Cancel·lar alerta", "Device_TableHead_Connected_Devices": "Connexions", "Device_TableHead_CustomProps": "Props / Accions", + "Device_TableHead_FQDN": "", "Device_TableHead_Favorite": "Favorit", "Device_TableHead_FirstSession": "Primera Sessió", "Device_TableHead_GUID": "GUID", @@ -559,6 +560,8 @@ "Presence_Shortcut_Favorites": "Favorits", "Presence_Shortcut_NewDevices": "Nous dispositius", "Presence_Title": "Detecció de dispositius", + "REFRESH_FQDN_description": "", + "REFRESH_FQDN_name": "", "REPORT_DASHBOARD_URL_description": "Aquesta URL s'utilitza com a base per generar enllaços en informes HTML (per exemple: correus electrònics). Introduïu la URL completa començant per http:// incloent el número de port (sense barra inicial /).", "REPORT_DASHBOARD_URL_name": "URL NetAlertX", "REPORT_ERROR": "Si us plau, introdueix dins de la caixa de text els caràcters que veu a la imatge de sota. Això és requerit per evitar enviaments automàtics", @@ -742,4 +745,4 @@ "settings_update_item_warning": "Actualitza el valor sota. Sigues curós de seguir el format anterior. No hi ha validació.", "test_event_icon": "fa-vial-circle-check", "test_event_tooltip": "Deseu els canvis primer abans de comprovar la configuració." -} +} \ No newline at end of file diff --git a/front/php/templates/language/cs_cz.json b/front/php/templates/language/cs_cz.json index 6efe2900..45d215b6 100755 --- a/front/php/templates/language/cs_cz.json +++ b/front/php/templates/language/cs_cz.json @@ -211,6 +211,7 @@ "Device_TableHead_AlertDown": "", "Device_TableHead_Connected_Devices": "", "Device_TableHead_CustomProps": "", + "Device_TableHead_FQDN": "", "Device_TableHead_Favorite": "", "Device_TableHead_FirstSession": "", "Device_TableHead_GUID": "", @@ -559,6 +560,8 @@ "Presence_Shortcut_Favorites": "", "Presence_Shortcut_NewDevices": "", "Presence_Title": "", + "REFRESH_FQDN_description": "", + "REFRESH_FQDN_name": "", "REPORT_DASHBOARD_URL_description": "", "REPORT_DASHBOARD_URL_name": "", "REPORT_ERROR": "", @@ -742,4 +745,4 @@ "settings_update_item_warning": "", "test_event_icon": "", "test_event_tooltip": "" -} +} \ No newline at end of file diff --git a/front/php/templates/language/de_de.json b/front/php/templates/language/de_de.json index 44366f7e..46c7cdfc 100755 --- a/front/php/templates/language/de_de.json +++ b/front/php/templates/language/de_de.json @@ -223,6 +223,7 @@ "Device_TableHead_AlertDown": "Alarm aus", "Device_TableHead_Connected_Devices": "Verbindungen", "Device_TableHead_CustomProps": "Eigenschaften / Aktionen", + "Device_TableHead_FQDN": "", "Device_TableHead_Favorite": "Favorit", "Device_TableHead_FirstSession": "Erste Sitzung", "Device_TableHead_GUID": "GUID", @@ -600,6 +601,8 @@ "Presence_Shortcut_Favorites": "Favoriten", "Presence_Shortcut_NewDevices": "Neue Geräte", "Presence_Title": "Anwesenheit pro Gerät", + "REFRESH_FQDN_description": "", + "REFRESH_FQDN_name": "", "REPORT_APPRISE_description": "Enable sending notifications via Apprise.", "REPORT_APPRISE_name": "Enable Apprise", "REPORT_DASHBOARD_URL_description": "Diese URL wird als Basis fürs Erstellen von Links in E-Mails genutzt. Geben Sie die gesamte URL startend mit http:// inklusive der genutzten Portnummer ein (keinen nachfolgenden Schrägstrich / nutzen).", @@ -823,4 +826,4 @@ "settings_update_item_warning": "", "test_event_icon": "", "test_event_tooltip": "Speichere die Änderungen, bevor Sie die Einstellungen testen." -} +} \ No newline at end of file diff --git a/front/php/templates/language/en_us.json b/front/php/templates/language/en_us.json index d6a4d584..2bf27aee 100755 --- a/front/php/templates/language/en_us.json +++ b/front/php/templates/language/en_us.json @@ -211,6 +211,7 @@ "Device_TableHead_AlertDown": "Alert Down", "Device_TableHead_Connected_Devices": "Connections", "Device_TableHead_CustomProps": "Props / Actions", + "Device_TableHead_FQDN": "FQDN", "Device_TableHead_Favorite": "Favorite", "Device_TableHead_FirstSession": "First Session", "Device_TableHead_GUID": "GUID", @@ -559,6 +560,8 @@ "Presence_Shortcut_Favorites": "Favorites", "Presence_Shortcut_NewDevices": "New Devices", "Presence_Title": "Presence by Device", + "REFRESH_FQDN_description": "Rescans all devices and refreshes their Fully Qualified Domain Name (FQDN). If disabled, only devices without a known name are scanned to improve performance. In this case, FQDN is updated only during initial device discovery.", + "REFRESH_FQDN_name": "Refresh FQDN", "REPORT_DASHBOARD_URL_description": "This URL is used as the base for generating links in HTML reports (e.g.: emails). Enter full URL starting with http:// including the port number (no trailing slash /).", "REPORT_DASHBOARD_URL_name": "NetAlertX URL", "REPORT_ERROR": "The page you are looking for is temporarily unavailable, please try again after a few seconds", diff --git a/front/php/templates/language/es_es.json b/front/php/templates/language/es_es.json index 12fdb74f..f8c980c1 100755 --- a/front/php/templates/language/es_es.json +++ b/front/php/templates/language/es_es.json @@ -221,6 +221,7 @@ "Device_TableHead_AlertDown": "Alerta desactivada", "Device_TableHead_Connected_Devices": "Conexiones", "Device_TableHead_CustomProps": "Propiedades / Acciones", + "Device_TableHead_FQDN": "", "Device_TableHead_Favorite": "Favorito", "Device_TableHead_FirstSession": "1ra. sesión", "Device_TableHead_GUID": "GUID", @@ -598,6 +599,8 @@ "Presence_Shortcut_Favorites": "Favorito(s)", "Presence_Shortcut_NewDevices": "Nuevo(s)", "Presence_Title": "Historial por dispositivo", + "REFRESH_FQDN_description": "", + "REFRESH_FQDN_name": "", "REPORT_APPRISE_description": "Habilitar el envío de notificaciones a través de Apprise.", "REPORT_APPRISE_name": "Habilitar Apprise", "REPORT_DASHBOARD_URL_description": "Esta URL se utiliza como base para generar enlaces en los correos electrónicos. Ingrese la URL completa que comienza con http://, incluido el número de puerto (sin barra inclinada al final /).", @@ -821,4 +824,4 @@ "settings_update_item_warning": "Actualice el valor a continuación. Tenga cuidado de seguir el formato anterior. O la validación no se realiza.", "test_event_icon": "fa-vial-circle-check", "test_event_tooltip": "Guarda tus cambios antes de probar nuevos ajustes." -} +} \ No newline at end of file diff --git a/front/php/templates/language/fr_fr.json b/front/php/templates/language/fr_fr.json index d6acf8c9..e64cb380 100755 --- a/front/php/templates/language/fr_fr.json +++ b/front/php/templates/language/fr_fr.json @@ -211,6 +211,7 @@ "Device_TableHead_AlertDown": "Alerter si En panne", "Device_TableHead_Connected_Devices": "Connexions", "Device_TableHead_CustomProps": "Champs / Actions", + "Device_TableHead_FQDN": "", "Device_TableHead_Favorite": "Favori", "Device_TableHead_FirstSession": "Première session", "Device_TableHead_GUID": "GUID", @@ -559,6 +560,8 @@ "Presence_Shortcut_Favorites": "Favoris", "Presence_Shortcut_NewDevices": "Nouveaux appareils", "Presence_Title": "Présence par appareil", + "REFRESH_FQDN_description": "", + "REFRESH_FQDN_name": "", "REPORT_DASHBOARD_URL_description": "Cette URL est utilisée comme base pour générer les liens des rapports HTML (par ex. les courriels). Renseignez l'adresse complète, commençant par http:// et incluznt le numero de port (sans slash / à la fin).", "REPORT_DASHBOARD_URL_name": "URL de NetAlertX", "REPORT_ERROR": "La page que vous cherchez est temporairement indisponible. Merci de réessayer dans quelques secondes", @@ -742,4 +745,4 @@ "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_icon": "fa-vial-circle-check", "test_event_tooltip": "Enregistrer d'abord vos modifications avant de tester vôtre paramétrage." -} +} \ No newline at end of file diff --git a/front/php/templates/language/it_it.json b/front/php/templates/language/it_it.json index 68995994..d7d82050 100755 --- a/front/php/templates/language/it_it.json +++ b/front/php/templates/language/it_it.json @@ -211,6 +211,7 @@ "Device_TableHead_AlertDown": "Avviso disconnessione", "Device_TableHead_Connected_Devices": "Connessioni", "Device_TableHead_CustomProps": "Proprietà/Azioni", + "Device_TableHead_FQDN": "", "Device_TableHead_Favorite": "Preferito", "Device_TableHead_FirstSession": "Prima sessione", "Device_TableHead_GUID": "GUID", @@ -559,6 +560,8 @@ "Presence_Shortcut_Favorites": "Preferiti", "Presence_Shortcut_NewDevices": "Nuovi dispositivi", "Presence_Title": "Presenza per dispositivo", + "REFRESH_FQDN_description": "", + "REFRESH_FQDN_name": "", "REPORT_DASHBOARD_URL_description": "Questo URL viene utilizzato come base per generare collegamenti nei report HTML (ad esempio: e-mail). Inserisci l'URL completo che inizia con http:// incluso il numero di porta (nessuna barra finale /).", "REPORT_DASHBOARD_URL_name": "URL NetAlertX", "REPORT_ERROR": "La pagina che stai cercando è momentaneamente non disponibile, riprova tra qualche secondo", @@ -742,4 +745,4 @@ "settings_update_item_warning": "Aggiorna il valore qui sotto. Fai attenzione a seguire il formato precedente. La convalida non viene eseguita.", "test_event_icon": "fa-vial-circle-check", "test_event_tooltip": "Salva le modifiche prima di provare le nuove impostazioni." -} +} \ No newline at end of file diff --git a/front/php/templates/language/nb_no.json b/front/php/templates/language/nb_no.json index 3b4eaa6f..1421c0e5 100755 --- a/front/php/templates/language/nb_no.json +++ b/front/php/templates/language/nb_no.json @@ -211,6 +211,7 @@ "Device_TableHead_AlertDown": "", "Device_TableHead_Connected_Devices": "Tilkoblinger", "Device_TableHead_CustomProps": "", + "Device_TableHead_FQDN": "", "Device_TableHead_Favorite": "Favoritt", "Device_TableHead_FirstSession": "Første Økt", "Device_TableHead_GUID": "GUID", @@ -559,6 +560,8 @@ "Presence_Shortcut_Favorites": "Favoritter", "Presence_Shortcut_NewDevices": "Nye enheter", "Presence_Title": "Tilstedeværelse etter enhet", + "REFRESH_FQDN_description": "", + "REFRESH_FQDN_name": "", "REPORT_DASHBOARD_URL_description": "Denne URL-en brukes som base for å generere lenker i HTML-rapporter (f.eks.: E -post). Skriv inn full URL som starter med http:// inkludert portnummeret (ingen etterfølgende slash /).", "REPORT_DASHBOARD_URL_name": "NetAlertX URL", "REPORT_ERROR": "Siden du leter etter er midlertidig utilgjengelig, prøv igjen etter noen sekunder", diff --git a/front/php/templates/language/pl_pl.json b/front/php/templates/language/pl_pl.json index 6c0376df..8e667608 100755 --- a/front/php/templates/language/pl_pl.json +++ b/front/php/templates/language/pl_pl.json @@ -211,6 +211,7 @@ "Device_TableHead_AlertDown": "", "Device_TableHead_Connected_Devices": "Połączenia", "Device_TableHead_CustomProps": "", + "Device_TableHead_FQDN": "", "Device_TableHead_Favorite": "Ulubione", "Device_TableHead_FirstSession": "Pierwsza Sesja", "Device_TableHead_GUID": "GUID", @@ -559,6 +560,8 @@ "Presence_Shortcut_Favorites": "Ulubione", "Presence_Shortcut_NewDevices": "Nowe Urządzenia", "Presence_Title": "Obecność Urządzenia", + "REFRESH_FQDN_description": "", + "REFRESH_FQDN_name": "", "REPORT_DASHBOARD_URL_description": "Link jest używany jako podstawa do generowania linków dla zgłoszeń HTML (np. e-maile). Wprowadź pełen adres zaczynając od http:// oraz dodając numer portu (bez zakańczania ukośnikiem /).", "REPORT_DASHBOARD_URL_name": "Link NetAlertX", "REPORT_ERROR": "Strona której szukasz jest tymczasowo niedostępna, spróbuj ponownie za kilka sekund", diff --git a/front/php/templates/language/pt_br.json b/front/php/templates/language/pt_br.json index 8366a56d..b7d9a72e 100755 --- a/front/php/templates/language/pt_br.json +++ b/front/php/templates/language/pt_br.json @@ -211,6 +211,7 @@ "Device_TableHead_AlertDown": "Alerta em baixo", "Device_TableHead_Connected_Devices": "Conexões", "Device_TableHead_CustomProps": "", + "Device_TableHead_FQDN": "", "Device_TableHead_Favorite": "Favorito", "Device_TableHead_FirstSession": "Primeira sessão", "Device_TableHead_GUID": "GUID", @@ -559,6 +560,8 @@ "Presence_Shortcut_Favorites": "", "Presence_Shortcut_NewDevices": "", "Presence_Title": "", + "REFRESH_FQDN_description": "", + "REFRESH_FQDN_name": "", "REPORT_DASHBOARD_URL_description": "", "REPORT_DASHBOARD_URL_name": "", "REPORT_ERROR": "", @@ -742,4 +745,4 @@ "settings_update_item_warning": "", "test_event_icon": "", "test_event_tooltip": "Guarde as alterações antes de testar as definições." -} +} \ No newline at end of file diff --git a/front/php/templates/language/ru_ru.json b/front/php/templates/language/ru_ru.json index 006f60ec..14052e80 100755 --- a/front/php/templates/language/ru_ru.json +++ b/front/php/templates/language/ru_ru.json @@ -211,6 +211,7 @@ "Device_TableHead_AlertDown": "Оповещение о сост. ВЫКЛ", "Device_TableHead_Connected_Devices": "Соединения", "Device_TableHead_CustomProps": "Свойства / Действия", + "Device_TableHead_FQDN": "", "Device_TableHead_Favorite": "Избранное", "Device_TableHead_FirstSession": "Первый сеанс", "Device_TableHead_GUID": "GUID", @@ -559,6 +560,8 @@ "Presence_Shortcut_Favorites": "Избранные", "Presence_Shortcut_NewDevices": "Новые устройства", "Presence_Title": "Присутствие по устройству", + "REFRESH_FQDN_description": "", + "REFRESH_FQDN_name": "", "REPORT_DASHBOARD_URL_description": "Этот URL-адрес используется в качестве основы для создания ссылок в отчетах HTML (например, в электронных письмах). Введите полный URL-адрес, начинающийся с http://, включая номер порта (без косой черты /).", "REPORT_DASHBOARD_URL_name": "NetAlertX URL", "REPORT_ERROR": "Страница, которую вы ищете, временно недоступна, повторите попытку через несколько секунд", @@ -742,4 +745,4 @@ "settings_update_item_warning": "Обновить значение ниже. Будьте осторожны, следуя предыдущему формату. Проверка не выполняется.", "test_event_icon": "fa-vial-circle-check", "test_event_tooltip": "Сначала сохраните изменения, прежде чем проверять настройки." -} +} \ No newline at end of file diff --git a/front/php/templates/language/tr_tr.json b/front/php/templates/language/tr_tr.json index a29a9d75..b9424fdd 100755 --- a/front/php/templates/language/tr_tr.json +++ b/front/php/templates/language/tr_tr.json @@ -211,6 +211,7 @@ "Device_TableHead_AlertDown": "Çalışmama Alarmı", "Device_TableHead_Connected_Devices": "Bağlantılar", "Device_TableHead_CustomProps": "Özellikler / Eylemler", + "Device_TableHead_FQDN": "", "Device_TableHead_Favorite": "Favori", "Device_TableHead_FirstSession": "İlk Oturum", "Device_TableHead_GUID": "GUID", @@ -559,6 +560,8 @@ "Presence_Shortcut_Favorites": "Favoriler", "Presence_Shortcut_NewDevices": "Yeni Cihazlar", "Presence_Title": "", + "REFRESH_FQDN_description": "", + "REFRESH_FQDN_name": "", "REPORT_DASHBOARD_URL_description": "", "REPORT_DASHBOARD_URL_name": "", "REPORT_ERROR": "", @@ -742,4 +745,4 @@ "settings_update_item_warning": "", "test_event_icon": "", "test_event_tooltip": "" -} +} \ No newline at end of file diff --git a/front/php/templates/language/uk_ua.json b/front/php/templates/language/uk_ua.json index e2dd3cef..0a4f5741 100755 --- a/front/php/templates/language/uk_ua.json +++ b/front/php/templates/language/uk_ua.json @@ -211,6 +211,7 @@ "Device_TableHead_AlertDown": "Сповіщення вниз", "Device_TableHead_Connected_Devices": "Зв'язки", "Device_TableHead_CustomProps": "Реквізит / дії", + "Device_TableHead_FQDN": "", "Device_TableHead_Favorite": "улюблений", "Device_TableHead_FirstSession": "Перша сесія", "Device_TableHead_GUID": "GUID", @@ -559,6 +560,8 @@ "Presence_Shortcut_Favorites": "Вибране", "Presence_Shortcut_NewDevices": "Нові пристрої", "Presence_Title": "Присутність за пристроєм", + "REFRESH_FQDN_description": "", + "REFRESH_FQDN_name": "", "REPORT_DASHBOARD_URL_description": "Ця URL-адреса використовується як основа для створення посилань у звітах HTML (наприклад, електронних листах). Введіть повну URL-адресу, починаючи з http://, включаючи номер порту (без скісної риски /).", "REPORT_DASHBOARD_URL_name": "URL-адреса NetAlertX", "REPORT_ERROR": "Сторінка, яку ви шукаєте, тимчасово недоступна, спробуйте ще раз через кілька секунд", @@ -742,4 +745,4 @@ "settings_update_item_warning": "Оновіть значення нижче. Слідкуйте за попереднім форматом. Перевірка не виконана.", "test_event_icon": "fa-vial-circle- check", "test_event_tooltip": "Перш ніж перевіряти налаштування, збережіть зміни." -} +} \ No newline at end of file diff --git a/front/php/templates/language/zh_cn.json b/front/php/templates/language/zh_cn.json index ee9e2da4..761ea688 100755 --- a/front/php/templates/language/zh_cn.json +++ b/front/php/templates/language/zh_cn.json @@ -211,6 +211,7 @@ "Device_TableHead_AlertDown": "", "Device_TableHead_Connected_Devices": "链接", "Device_TableHead_CustomProps": "", + "Device_TableHead_FQDN": "", "Device_TableHead_Favorite": "收藏", "Device_TableHead_FirstSession": "加入", "Device_TableHead_GUID": "GUID", @@ -559,6 +560,8 @@ "Presence_Shortcut_Favorites": "收藏夹", "Presence_Shortcut_NewDevices": "新设备", "Presence_Title": "按设备显示状态", + "REFRESH_FQDN_description": "", + "REFRESH_FQDN_name": "", "REPORT_DASHBOARD_URL_description": "此 URL 用作生成 HTML 报告(例如电子邮件)中链接的基础。输入以 http:// 开头的完整 URL,包括端口号(无尾部斜杠 /)。", "REPORT_DASHBOARD_URL_name": "NetAlertX 网址", "REPORT_ERROR": "您正在浏览的页面暂时不可用,请稍后重试", @@ -742,4 +745,4 @@ "settings_update_item_warning": "更新下面的值。请注意遵循先前的格式。未执行验证。", "test_event_icon": "", "test_event_tooltip": "在测试设置之前,请先保存更改。" -} +} \ No newline at end of file diff --git a/front/plugins/_publisher_apprise/README.md b/front/plugins/_publisher_apprise/README.md index 769be945..8db547b4 100755 --- a/front/plugins/_publisher_apprise/README.md +++ b/front/plugins/_publisher_apprise/README.md @@ -11,3 +11,31 @@ You need to bring your own separate Apprise instance to use this publisher gatew - Go to settings and fill in relevant details. - Use the Apprise container's URL in the `APPRISE_HOST` setting. +## Examples + +### Telegram + +![Telegram config](apprise_telegram.png) + +#### Troubleshooting + +1. Replace `` and `` with your values. + +2. Test telegram notification in browser + +``` +https://api.telegram.org/bot/sendMessage?chat_id=&text=%40%40TEXT%40%40 +``` +3. Test apprise notification in console (replace `192.168.1.2:9999` with your apprise ip and port) + +``` +curl -X POST -d '{"urls":"tgram:///","body":"test body from curl","title":"test title from curl"}' -H "Content-Type: application/json" "http://192.168.1.2:9999/notify/" +``` + +4. Test from the docker apprise container console +``` +apprise -vv -t "Test Message from apprise console" -b "Test Message from apprise console" \ + tgram://// +``` + + diff --git a/front/plugins/_publisher_apprise/apprise_telegram.png b/front/plugins/_publisher_apprise/apprise_telegram.png new file mode 100755 index 0000000000000000000000000000000000000000..62ad0b6a866eee28561acd5f9b5f9ffc96a5910b GIT binary patch literal 142275 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�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 0: + if len(devices) > 0: # ensure service is running ensure_avahi_running() - for device in unknown_devices: + for device in devices: domain_name = execute_name_lookup(device['devLastIP'], timeout) # check if found and not a timeout ('to') diff --git a/front/plugins/dig_scan/README.md b/front/plugins/dig_scan/README.md new file mode 100755 index 00000000..d20121e1 --- /dev/null +++ b/front/plugins/dig_scan/README.md @@ -0,0 +1,7 @@ +## Overview + +Plugin for device name discovery via the [nbtscan](https://linuxcommandlibrary.com/man/nbtscan) network utility supporting NetBIOS. + +### Usage + +- Check the Settings page for details. diff --git a/front/plugins/dig_scan/config.json b/front/plugins/dig_scan/config.json new file mode 100755 index 00000000..8e64cf3a --- /dev/null +++ b/front/plugins/dig_scan/config.json @@ -0,0 +1,385 @@ +{ + "code_name": "dig_scan", + "unique_prefix": "DIGSCAN", + "plugin_type": "other", + "enabled": true, + "data_source": "script", + "execution_order" : "Layer_7", + "show_ui": true, + "data_filters": [ + { + "compare_column": "Object_PrimaryID", + "compare_operator": "==", + "compare_field_id": "txtMacFilter", + "compare_js_template": "'{value}'.toString()", + "compare_use_quotes": true + } + ], + "localized": ["display_name", "description", "icon"], + "display_name": [ + { + "language_code": "en_us", + "string": "Dig (Name resolution)" + } + ], + "icon": [ + { + "language_code": "en_us", + "string": "" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "A plugin to resolve device names via Dig." + } + ], + "params": [ + { + "name": "ips", + "type": "sql", + "value": "SELECT devLastIP from DEVICES order by devMac", + "timeoutMultiplier": true + } + ], + "settings": [ + { + "function": "RUN", + "events": ["run"], + "type": { + "dataType": "string", + "elements": [ + { "elementType": "select", "elementOptions": [], "transformers": [] } + ] + }, + "default_value": "before_name_updates", + "options": [ + "disabled", + "before_name_updates", + "on_new_device", + "once", + "schedule", + "always_after_scan" + ], + "localized": ["name", "description"], + "name": [ + { + "language_code": "en_us", + "string": "When to run" + }, + { + "language_code": "es_es", + "string": "Cuándo ejecutar" + }, + { + "language_code": "de_de", + "string": "Wann laufen" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "When the plugin should be executed. If enabled this will execute the scan until there are no (unknown) or (name not found) devices. Setting this to before_name_updates is recommended.

Depends on the
SCAN_SUBNETS setting." + } + ] + }, + { + "function": "CMD", + "type": { + "dataType": "string", + "elements": [ + { + "elementType": "input", + "elementOptions": [{ "readonly": "true" }], + "transformers": [] + } + ] + }, + "default_value": "python3 /app/front/plugins/dig_scan/digscan.py", + "options": [], + "localized": ["name", "description"], + "name": [ + { + "language_code": "en_us", + "string": "Command" + }, + { + "language_code": "es_es", + "string": "Comando" + }, + { + "language_code": "de_de", + "string": "Befehl" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "Command to run. This can not be changed" + }, + { + "language_code": "es_es", + "string": "Comando a ejecutar. Esto no se puede cambiar" + }, + { + "language_code": "de_de", + "string": "Befehl zum Ausführen. Dies kann nicht geändert werden" + } + ] + }, + { + "function": "RUN_SCHD", + "type": { + "dataType": "string", + "elements": [ + { + "elementType": "span", + "elementOptions": [ + { + "cssClasses": "input-group-addon validityCheck" + }, + { + "getStringKey": "Gen_ValidIcon" + } + ], + "transformers": [] + }, + { + "elementType": "input", + "elementOptions": [ + { + "onChange": "validateRegex(this)" + }, + { + "base64Regex": "Xig/OlwqfCg/OlswLTldfFsxLTVdWzAtOV18WzAtOV0rLVswLTldK3xcKi9bMC05XSspKVxzKyg/OlwqfCg/OlswLTldfDFbMC05XXwyWzAtM118WzAtOV0rLVswLTldK3xcKi9bMC05XSspKVxzKyg/OlwqfCg/OlsxLTldfFsxMl1bMC05XXwzWzAxXXxbMC05XSstWzAtOV0rfFwqL1swLTldKykpXHMrKD86XCp8KD86WzEtOV18MVswLTJdfFswLTldKy1bMC05XSt8XCovWzAtOV0rKSlccysoPzpcKnwoPzpbMC02XXxbMC02XS1bMC02XXxcKi9bMC05XSspKSQ=" + } + ], + "transformers": [] + } + ] + }, + "default_value": "*/30 * * * *", + "options": [], + "localized": ["name", "description"], + "name": [ + { + "language_code": "en_us", + "string": "Schedule" + }, + { + "language_code": "es_es", + "string": "Schedule" + }, + { + "language_code": "de_de", + "string": "Schedule" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "Only enabled if you select schedule in the NBTSCAN_RUN setting. Make sure you enter the schedule in the correct cron-like format (e.g. validate at crontab.guru). For example entering 0 4 * * * will run the scan after 4 am in the TIMEZONE you set above. Will be run NEXT time the time passes." + }, + { + "language_code": "es_es", + "string": "Solo está habilitado si selecciona schedule en la configuración NBTSCAN_RUN. Asegúrese de ingresar la programación en el formato similar a cron correcto (por ejemplo, valide en crontab.guru). Por ejemplo, ingresar 0 4 * * * ejecutará el escaneo después de las 4 a.m. en el TIMEZONE que configuró arriba. Se ejecutará la PRÓXIMA vez que pase el tiempo." + }, + { + "language_code": "de_de", + "string": "Nur aktiviert, wenn Sie schedule in der NBTSCAN_RUN-Einstellung auswählen. Stellen Sie sicher, dass Sie den Zeitplan im richtigen Cron-ähnlichen Format eingeben (z. B. validieren unter crontab.guru). Wenn Sie beispielsweise 0 4 * * * eingeben, wird der Scan nach 4 Uhr morgens in der TIMEZONE den Sie oben festgelegt haben. Wird das NÄCHSTE Mal ausgeführt, wenn die Zeit vergeht." + } + ] + }, + { + "function": "RUN_TIMEOUT", + "type": { + "dataType": "integer", + "elements": [ + { + "elementType": "input", + "elementOptions": [{ "type": "number" }], + "transformers": [] + } + ] + }, + "default_value": 5, + "options": [], + "localized": ["name", "description"], + "name": [ + { + "language_code": "en_us", + "string": "Run timeout" + }, + { + "language_code": "es_es", + "string": "Tiempo límite de ejecución" + }, + { + "language_code": "de_de", + "string": "Zeitüberschreitung" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "Maximum time in seconds to wait for the script to finish. If this time is exceeded the script is aborted." + }, + { + "language_code": "es_es", + "string": "Tiempo máximo en segundos para esperar a que finalice el script. Si se supera este tiempo, el script se cancela." + }, + { + "language_code": "de_de", + "string": "Maximale Zeit in Sekunden, die auf den Abschluss des Skripts gewartet werden soll. Bei Überschreitung dieser Zeit wird das Skript abgebrochen." + } + ] + } + ], + "database_column_definitions": [ + { + "column": "Index", + "css_classes": "col-sm-2", + "show": true, + "type": "none", + "default_value": "", + "options": [], + "localized": ["name"], + "name": [ + { + "language_code": "en_us", + "string": "Index" + } + ] + }, + { + "column": "Object_PrimaryID", + "css_classes": "col-sm-2", + "show": true, + "type": "device_name_mac", + "default_value": "", + "options": [], + "localized": ["name"], + "name": [ + { + "language_code": "en_us", + "string": "MAC (name)" + }, + { + "language_code": "es_es", + "string": "MAC" + } + ] + }, + { + "column": "Object_SecondaryID", + "css_classes": "col-sm-2", + "show": true, + "type": "label", + "default_value": "", + "options": [], + "localized": ["name"], + "name": [ + { + "language_code": "en_us", + "string": "IP" + }, + { + "language_code": "es_es", + "string": "IP" + } + ] + }, + { + "column": "Watched_Value1", + "css_classes": "col-sm-2", + "show": true, + "type": "label", + "default_value": "", + "options": [], + "localized": ["name"], + "name": [ + { + "language_code": "en_us", + "string": "Server" + } + ] + }, + { + "column": "Watched_Value2", + "css_classes": "col-sm-2", + "show": true, + "type": "label", + "default_value": "", + "options": [], + "localized": ["name"], + "name": [ + { + "language_code": "en_us", + "string": "Name" + } + ] + }, + { + "column": "DateTimeCreated", + "css_classes": "col-sm-2", + "show": true, + "type": "label", + "default_value": "", + "options": [], + "localized": ["name"], + "name": [ + { + "language_code": "en_us", + "string": "Created" + } + ] + }, + { + "column": "DateTimeChanged", + "css_classes": "col-sm-2", + "show": true, + "type": "label", + "default_value": "", + "options": [], + "localized": ["name"], + "name": [ + { + "language_code": "en_us", + "string": "Changed" + } + ] + }, + { + "column": "Status", + "css_classes": "col-sm-1", + "show": true, + "type": "replace", + "default_value": "", + "options": [ + { + "equals": "watched-not-changed", + "replacement": "
" + }, + { + "equals": "watched-changed", + "replacement": "
" + }, + { + "equals": "new", + "replacement": "
" + }, + { + "equals": "missing-in-last-scan", + "replacement": "
" + } + ], + "localized": ["name"], + "name": [ + { + "language_code": "en_us", + "string": "Status" + } + ] + } + ] +} diff --git a/front/plugins/dig_scan/digscan.py b/front/plugins/dig_scan/digscan.py new file mode 100755 index 00000000..46266282 --- /dev/null +++ b/front/plugins/dig_scan/digscan.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python + +import os +import pathlib +import sys +import json +import sqlite3 +import subprocess + +# Define the installation path and extend the system path for plugin imports +INSTALL_PATH = "/app" +sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"]) + +from plugin_helper import Plugin_Object, Plugin_Objects, decodeBase64 +from plugin_utils import get_plugins_configs +from logger import mylog, Logger +from const import pluginsPath, fullDbPath, logPath +from helper import timeNowTZ, get_setting_value +from messaging.in_app import write_notification +from database import DB +from models.device_instance import DeviceInstance +import conf +from pytz import timezone + +# Make sure the TIMEZONE for logging is correct +conf.tz = timezone(get_setting_value('TIMEZONE')) + +# Make sure log level is initialized correctly +Logger(get_setting_value('LOG_LEVEL')) + +pluginName = 'DIGSCAN' + +# Define the current path and log file paths +LOG_PATH = logPath + '/plugins' +LOG_FILE = os.path.join(LOG_PATH, f'script.{pluginName}.log') +RESULT_FILE = os.path.join(LOG_PATH, f'last_result.{pluginName}.log') + +# Initialize the Plugin obj output file +plugin_objects = Plugin_Objects(RESULT_FILE) + + +def main(): + mylog('verbose', [f'[{pluginName}] In script']) + + timeout = get_setting_value('DIGSCAN_RUN_TIMEOUT') + + # Create a database connection + db = DB() # instance of class DB + db.open() + + # Initialize the Plugin obj output file + plugin_objects = Plugin_Objects(RESULT_FILE) + + # Create a DeviceInstance instance + device_handler = DeviceInstance(db) + + # Retrieve devices + if get_setting_value("REFRESH_FQDN"): + devices = device_handler.getUnknown() + else: + devices = device_handler.getAll() + + mylog('verbose', [f'[{pluginName}] Devices count: {len(devices)}']) + + # TEST - below is a WINDOWS host IP + # execute_name_lookup('192.168.1.121', timeout) + + for device in devices: + domain_name, dns_server = execute_name_lookup(device['devLastIP'], timeout) + + if domain_name != '': + plugin_objects.add_object( + # "MAC", "IP", "Server", "Name" + primaryId = device['devMac'], + secondaryId = device['devLastIP'], + watched1 = dns_server, + watched2 = domain_name, + watched3 = '', + watched4 = '', + extra = '', + foreignKey = device['devMac']) + + plugin_objects.write_result_file() + + + mylog('verbose', [f'[{pluginName}] Script finished']) + + return 0 + +#=============================================================================== +# Execute scan +#=============================================================================== +def execute_name_lookup (ip, timeout): + """ + Execute the DIG command on IP. + """ + + args = ['dig', '+short', '-x', ip] + + # Execute command + output = "" + + try: + mylog('verbose', [f'[{pluginName}] DEBUG CMD :', args]) + + # try runnning a subprocess with a forced (timeout) in case the subprocess hangs + output = subprocess.check_output (args, universal_newlines=True, stderr=subprocess.STDOUT, timeout=(timeout), text=True).strip() + + mylog('verbose', [f'[{pluginName}] DEBUG OUTPUT : {output}']) + + domain_name = output + dns_server = '' + + mylog('verbose', [f'[{pluginName}] Domain Name: {domain_name}']) + + return domain_name, dns_server + + except subprocess.CalledProcessError as e: + mylog('verbose', [f'[{pluginName}] ⚠ ERROR - {e.output}']) + + except subprocess.TimeoutExpired as timeErr: + mylog('verbose', [f'[{pluginName}] TIMEOUT - the process forcefully terminated as timeout reached']) + + if output == "": # check if the subprocess failed + mylog('verbose', [f'[{pluginName}] Scan: FAIL - check logs']) + else: + mylog('verbose', [f'[{pluginName}] Scan: SUCCESS']) + + return '', '' + +if __name__ == '__main__': + main() + diff --git a/front/plugins/nbtscan_scan/config.json b/front/plugins/nbtscan_scan/config.json index 51fd3a13..50691f12 100755 --- a/front/plugins/nbtscan_scan/config.json +++ b/front/plugins/nbtscan_scan/config.json @@ -52,7 +52,7 @@ { "elementType": "select", "elementOptions": [], "transformers": [] } ] }, - "default_value": "disabled", + "default_value": "before_name_updates", "options": [ "disabled", "before_name_updates", diff --git a/front/plugins/nbtscan_scan/nbtscan.py b/front/plugins/nbtscan_scan/nbtscan.py index 60261a46..003aad33 100755 --- a/front/plugins/nbtscan_scan/nbtscan.py +++ b/front/plugins/nbtscan_scan/nbtscan.py @@ -57,14 +57,17 @@ def main(): device_handler = DeviceInstance(db) # Retrieve devices - unknown_devices = device_handler.getUnknown() + if get_setting_value("REFRESH_FQDN"): + devices = device_handler.getUnknown() + else: + devices = device_handler.getAll() - mylog('verbose', [f'[{pluginName}] Unknown devices count: {len(unknown_devices)}']) + mylog('verbose', [f'[{pluginName}] Devices count: {len(devices)}']) # TEST - below is a WINDOWS host IP # execute_name_lookup('192.168.1.121', timeout) - for device in unknown_devices: + for device in devices: domain_name, dns_server = execute_name_lookup(device['devLastIP'], timeout) if domain_name != '': diff --git a/front/plugins/newdev_template/config.json b/front/plugins/newdev_template/config.json index c0a2f609..93162224 100755 --- a/front/plugins/newdev_template/config.json +++ b/front/plugins/newdev_template/config.json @@ -1629,6 +1629,42 @@ "string": "Custom device properties to store additional data or to perform an action on the device. Check the documentation on Custom Properties for additional details." } ] + }, + { + "function": "devFQDN", + "type": { + "dataType": "string", + "elements": [ + { + "elementType": "input", + "elementOptions": [ + { + "readonly": "true" + } + ], + "transformers": [] + } + ] + }, + "maxLength": 50, + "default_value": "", + "options": [], + "localized": [ + "name", + "description" + ], + "name": [ + { + "language_code": "en_us", + "string": "FQDN" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "Fully Qualified Domain Name - Autodetected and Uneditable. Can be auto-refreshed by enabling the REFRESH_FQDN setting." + } + ] } ], "required": [], diff --git a/front/plugins/nslookup_scan/nslookup.py b/front/plugins/nslookup_scan/nslookup.py index 3ec2fe2e..dc403250 100755 --- a/front/plugins/nslookup_scan/nslookup.py +++ b/front/plugins/nslookup_scan/nslookup.py @@ -59,11 +59,17 @@ def main(): device_handler = DeviceInstance(db) # Retrieve devices - unknown_devices = device_handler.getUnknown() + if get_setting_value("REFRESH_FQDN"): + devices = device_handler.getUnknown() + else: + devices = device_handler.getAll() - mylog('verbose', [f'[{pluginName}] Unknown devices count: {len(unknown_devices)}']) + mylog('verbose', [f'[{pluginName}] Devices count: {len(devices)}']) + + # TEST - below is a WINDOWS host IP + # execute_name_lookup('192.168.1.121', timeout) - for device in unknown_devices: + for device in devices: domain_name, dns_server = execute_nslookup(device['devLastIP'], timeout) if domain_name != '': diff --git a/front/plugins/ui_settings/config.json b/front/plugins/ui_settings/config.json index c46fa376..ce5e21c9 100755 --- a/front/plugins/ui_settings/config.json +++ b/front/plugins/ui_settings/config.json @@ -377,7 +377,8 @@ "Device_TableHead_SourcePlugin", "Device_TableHead_PresentLastScan", "Device_TableHead_AlertDown", - "Device_TableHead_CustomProps" + "Device_TableHead_CustomProps", + "Device_TableHead_FQDN" ], "localized": ["name", "description"], "name": [ diff --git a/front/workflowsCore.php b/front/workflowsCore.php index b7aeed26..5cba7801 100755 --- a/front/workflowsCore.php +++ b/front/workflowsCore.php @@ -44,7 +44,7 @@ let fieldOptions = [ "devLastIP", "devStaticIP", "devScan", "devLogEvents", "devAlertEvents", "devAlertDown", "devSkipRepeated", "devLastNotification", "devPresentLastScan", "devIsNew", "devLocation", "devIsArchived", "devParentMAC", "devParentPort", - "devIcon", "devSite", "devSSID", "devSyncHubNode", "devSourcePlugin" + "devIcon", "devSite", "devSSID", "devSyncHubNode", "devSourcePlugin", "devFQDN" ]; let triggerTypes = [ diff --git a/scripts/db_empty/README.md b/scripts/db_empty/README.md new file mode 100755 index 00000000..4656d22b --- /dev/null +++ b/scripts/db_empty/README.md @@ -0,0 +1,19 @@ +# Overview + +A script for deleting all data from the database. + +# Usage + +1. **Run the Script** + +`python ./db_empty.py` + +### Other info + +- Version: 1.0 +- Release Date: 01-Jun-2025 +- Author: [jokob-sk](https://github.com/jokob-sk) + + +> [!NOTE] +> This is a community supplied script and not maintained. \ No newline at end of file diff --git a/scripts/db_empty/db_empty.py b/scripts/db_empty/db_empty.py new file mode 100755 index 00000000..95ef6da0 --- /dev/null +++ b/scripts/db_empty/db_empty.py @@ -0,0 +1,26 @@ +import sqlite3 + +# Connect to the database +conn = sqlite3.connect("/app/db/app.db") +cursor = conn.cursor() + +# Get the names of all tables (excluding SQLite internal tables) +cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';") +tables = cursor.fetchall() + +# Disable foreign key constraints temporarily +cursor.execute("PRAGMA foreign_keys = OFF;") + +# Delete all rows from each table +for (table_name,) in tables: + cursor.execute(f"DELETE FROM {table_name};") + +# Commit changes and re-enable foreign keys +conn.commit() +cursor.execute("PRAGMA foreign_keys = ON;") + +# Vacuum to shrink database file +cursor.execute("VACUUM;") + +# Close connection +conn.close() diff --git a/server/__main__.py b/server/__main__.py index 653e155b..74d10761 100755 --- a/server/__main__.py +++ b/server/__main__.py @@ -153,7 +153,7 @@ def main (): # Resolve devices names mylog('debug','[Main] Resolve devices names') - update_devices_names(db) + update_devices_names(db) # Check if new devices found sql.execute (sql_new_devices) diff --git a/server/const.py b/server/const.py index 9663c7ec..8ad0ab11 100755 --- a/server/const.py +++ b/server/const.py @@ -60,6 +60,7 @@ sql_devices_all = """ IFNULL(devSyncHubNode, '') AS devSyncHubNode, IFNULL(devSourcePlugin, '') AS devSourcePlugin, IFNULL(devCustomProps, '') AS devCustomProps, + IFNULL(devFQDN, '') AS devFQDN, CASE WHEN devIsNew = 1 THEN 'New' WHEN devPresentLastScan = 1 THEN 'On-line' diff --git a/server/database.py b/server/database.py index 0db5a05b..36576bbe 100755 --- a/server/database.py +++ b/server/database.py @@ -80,370 +80,10 @@ class DB(): """ Check the current tables in the DB and upgrade them if neccessary """ - - self.sql.execute(""" - CREATE TABLE IF NOT EXISTS "Online_History" ( - "Index" INTEGER, - "Scan_Date" TEXT, - "Online_Devices" INTEGER, - "Down_Devices" INTEGER, - "All_Devices" INTEGER, - "Archived_Devices" INTEGER, - "Offline_Devices" INTEGER, - PRIMARY KEY("Index" AUTOINCREMENT) - ); - """) - # ------------------------------------------------------------------- - # DevicesNew - cleanup after 6/6/2025 - need to update also DB in the source code! - - # check if migration already done based on devMac - devMac_missing = self.sql.execute (""" - SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Devices') WHERE name='devMac' - """).fetchone()[0] == 0 - - if devMac_missing: - - # ------------------------------------------------------------------------- - # Alter Devices table - # ------------------------------------------------------------------------- - # dev_Network_Node_MAC_ADDR column - dev_Network_Node_MAC_ADDR_missing = self.sql.execute (""" - SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Devices') WHERE name='dev_Network_Node_MAC_ADDR' - """).fetchone()[0] == 0 - - if dev_Network_Node_MAC_ADDR_missing : - mylog('verbose', ["[upgradeDB] Adding dev_Network_Node_MAC_ADDR to the Devices table"]) - self.sql.execute(""" - ALTER TABLE "Devices" ADD "dev_Network_Node_MAC_ADDR" TEXT - """) - - # dev_Network_Node_port column - dev_Network_Node_port_missing = self.sql.execute (""" - SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Devices') WHERE name='dev_Network_Node_port' - """).fetchone()[0] == 0 - - if dev_Network_Node_port_missing : - mylog('verbose', ["[upgradeDB] Adding dev_Network_Node_port to the Devices table"]) - self.sql.execute(""" - ALTER TABLE "Devices" ADD "dev_Network_Node_port" INTEGER - """) - - # dev_Icon column - dev_Icon_missing = self.sql.execute (""" - SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Devices') WHERE name='dev_Icon' - """).fetchone()[0] == 0 - - if dev_Icon_missing : - mylog('verbose', ["[upgradeDB] Adding dev_Icon to the Devices table"]) - self.sql.execute(""" - ALTER TABLE "Devices" ADD "dev_Icon" TEXT - """) - - # dev_GUID column - dev_GUID_missing = self.sql.execute (""" - SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Devices') WHERE name='dev_GUID' - """).fetchone()[0] == 0 - - if dev_GUID_missing : - mylog('verbose', ["[upgradeDB] Adding dev_GUID to the Devices table"]) - self.sql.execute(""" - ALTER TABLE "Devices" ADD "dev_GUID" TEXT - """) - - # dev_NetworkSite column - dev_NetworkSite_missing = self.sql.execute (""" - SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Devices') WHERE name='dev_NetworkSite' - """).fetchone()[0] == 0 - - if dev_NetworkSite_missing : - mylog('verbose', ["[upgradeDB] Adding dev_NetworkSite to the Devices table"]) - self.sql.execute(""" - ALTER TABLE "Devices" ADD "dev_NetworkSite" TEXT - """) - - # dev_SSID column - dev_SSID_missing = self.sql.execute (""" - SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Devices') WHERE name='dev_SSID' - """).fetchone()[0] == 0 - - if dev_SSID_missing : - mylog('verbose', ["[upgradeDB] Adding dev_SSID to the Devices table"]) - self.sql.execute(""" - ALTER TABLE "Devices" ADD "dev_SSID" TEXT - """) - - # SQL query to update missing dev_GUID - self.sql.execute(f''' - UPDATE Devices - SET dev_GUID = {sql_generateGuid} - WHERE dev_GUID IS NULL - ''') - - # dev_SyncHubNodeName column - dev_SyncHubNodeName_missing = self.sql.execute (""" - SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Devices') WHERE name='dev_SyncHubNodeName' - """).fetchone()[0] == 0 - - if dev_SyncHubNodeName_missing : - mylog('verbose', ["[upgradeDB] Adding dev_SyncHubNodeName to the Devices table"]) - self.sql.execute(""" - ALTER TABLE "Devices" ADD "dev_SyncHubNodeName" TEXT - """) - - # dev_SourcePlugin column - dev_SourcePlugin_missing = self.sql.execute (""" - SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Devices') WHERE name='dev_SourcePlugin' - """).fetchone()[0] == 0 - - if dev_SourcePlugin_missing : - mylog('verbose', ["[upgradeDB] Adding dev_SourcePlugin to the Devices table"]) - self.sql.execute(""" - ALTER TABLE "Devices" ADD "dev_SourcePlugin" TEXT - """) - - # SQL to create Devices table with indexes - sql_create_devices_new_tmp = """ - CREATE TABLE IF NOT EXISTS Devices_tmp ( - devMac STRING (50) PRIMARY KEY NOT NULL COLLATE NOCASE, - devName STRING (50) NOT NULL DEFAULT "(unknown)", - devOwner STRING (30) DEFAULT "(unknown)" NOT NULL, - devType STRING (30), - devVendor STRING (250), - devFavorite BOOLEAN CHECK (devFavorite IN (0, 1)) DEFAULT (0) NOT NULL, - devGroup STRING (10), - devComments TEXT, - devFirstConnection DATETIME NOT NULL, - devLastConnection DATETIME NOT NULL, - devLastIP STRING (50) NOT NULL COLLATE NOCASE, - devStaticIP BOOLEAN DEFAULT (0) NOT NULL CHECK (devStaticIP IN (0, 1)), - devScan INTEGER DEFAULT (1) NOT NULL, - devLogEvents BOOLEAN NOT NULL DEFAULT (1) CHECK (devLogEvents IN (0, 1)), - devAlertEvents BOOLEAN NOT NULL DEFAULT (1) CHECK (devAlertEvents IN (0, 1)), - devAlertDown BOOLEAN NOT NULL DEFAULT (0) CHECK (devAlertDown IN (0, 1)), - devSkipRepeated INTEGER DEFAULT 0 NOT NULL, - devLastNotification DATETIME, - devPresentLastScan BOOLEAN NOT NULL DEFAULT (0) CHECK (devPresentLastScan IN (0, 1)), - devIsNew BOOLEAN NOT NULL DEFAULT (1) CHECK (devIsNew IN (0, 1)), - devLocation STRING (250) COLLATE NOCASE, - devIsArchived BOOLEAN NOT NULL DEFAULT (0) CHECK (devIsArchived IN (0, 1)), - devParentMAC TEXT, - devParentPort INTEGER, - devIcon TEXT, - devGUID TEXT, - devSite TEXT, - devSSID TEXT, - devSyncHubNode TEXT, - devSourcePlugin TEXT - ); - - CREATE INDEX IF NOT EXISTS IDX_dev_PresentLastScan ON Devices_tmp (devPresentLastScan); - CREATE INDEX IF NOT EXISTS IDX_dev_FirstConnection ON Devices_tmp (devFirstConnection); - CREATE INDEX IF NOT EXISTS IDX_dev_AlertDeviceDown ON Devices_tmp (devAlertDown); - CREATE INDEX IF NOT EXISTS IDX_dev_StaticIP ON Devices_tmp (devStaticIP); - CREATE INDEX IF NOT EXISTS IDX_dev_ScanCycle ON Devices_tmp (devScan); - CREATE INDEX IF NOT EXISTS IDX_dev_Favorite ON Devices_tmp (devFavorite); - CREATE INDEX IF NOT EXISTS IDX_dev_LastIP ON Devices_tmp (devLastIP); - CREATE INDEX IF NOT EXISTS IDX_dev_NewDevice ON Devices_tmp (devIsNew); - CREATE INDEX IF NOT EXISTS IDX_dev_Archived ON Devices_tmp (devIsArchived); - """ - - # Execute the creation of the Devices table and indexes - self.sql.executescript(sql_create_devices_new_tmp) - - - # copy over data - sql_copy_from_devices = """ - INSERT OR IGNORE INTO Devices_tmp ( - devMac, - devName, - devOwner, - devType, - devVendor, - devFavorite, - devGroup, - devComments, - devFirstConnection, - devLastConnection, - devLastIP, - devStaticIP, - devScan, - devLogEvents, - devAlertEvents, - devAlertDown, - devSkipRepeated, - devLastNotification, - devPresentLastScan, - devIsNew, - devLocation, - devIsArchived, - devParentMAC, - devParentPort, - devIcon, - devGUID, - devSite, - devSSID, - devSyncHubNode, - devSourcePlugin - ) - SELECT - dev_MAC AS devMac, - dev_Name AS devName, - dev_Owner AS devOwner, - dev_DeviceType AS devType, - dev_Vendor AS devVendor, - dev_Favorite AS devFavorite, - dev_Group AS devGroup, - dev_Comments AS devComments, - dev_FirstConnection AS devFirstConnection, - dev_LastConnection AS devLastConnection, - dev_LastIP AS devLastIP, - dev_StaticIP AS devStaticIP, - dev_ScanCycle AS devScan, - dev_LogEvents AS devLogEvents, - dev_AlertEvents AS devAlertEvents, - dev_AlertDeviceDown AS devAlertDown, - dev_SkipRepeated AS devSkipRepeated, - dev_LastNotification AS devLastNotification, - dev_PresentLastScan AS devPresentLastScan, - dev_NewDevice AS devIsNew, - dev_Location AS devLocation, - dev_Archived AS devIsArchived, - dev_Network_Node_MAC_ADDR AS devParentMAC, - dev_Network_Node_port AS devParentPort, - dev_Icon AS devIcon, - dev_GUID AS devGUID, - dev_NetworkSite AS devSite, - dev_SSID AS devSSID, - dev_SyncHubNodeName AS devSyncHubNode, - dev_SourcePlugin AS devSourcePlugin - FROM Devices; - """ - - self.sql.execute(sql_copy_from_devices) - - - self.sql.execute(""" DROP TABLE Devices;""") - # SQL to create Devices table with indexes - sql_create_devices_new = """ - CREATE TABLE IF NOT EXISTS Devices ( - devMac STRING (50) PRIMARY KEY NOT NULL COLLATE NOCASE, - devName STRING (50) NOT NULL DEFAULT "(unknown)", - devOwner STRING (30) DEFAULT "(unknown)" NOT NULL, - devType STRING (30), - devVendor STRING (250), - devFavorite BOOLEAN CHECK (devFavorite IN (0, 1)) DEFAULT (0) NOT NULL, - devGroup STRING (10), - devComments TEXT, - devFirstConnection DATETIME NOT NULL, - devLastConnection DATETIME NOT NULL, - devLastIP STRING (50) NOT NULL COLLATE NOCASE, - devStaticIP BOOLEAN DEFAULT (0) NOT NULL CHECK (devStaticIP IN (0, 1)), - devScan INTEGER DEFAULT (1) NOT NULL, - devLogEvents BOOLEAN NOT NULL DEFAULT (1) CHECK (devLogEvents IN (0, 1)), - devAlertEvents BOOLEAN NOT NULL DEFAULT (1) CHECK (devAlertEvents IN (0, 1)), - devAlertDown BOOLEAN NOT NULL DEFAULT (0) CHECK (devAlertDown IN (0, 1)), - devSkipRepeated INTEGER DEFAULT 0 NOT NULL, - devLastNotification DATETIME, - devPresentLastScan BOOLEAN NOT NULL DEFAULT (0) CHECK (devPresentLastScan IN (0, 1)), - devIsNew BOOLEAN NOT NULL DEFAULT (1) CHECK (devIsNew IN (0, 1)), - devLocation STRING (250) COLLATE NOCASE, - devIsArchived BOOLEAN NOT NULL DEFAULT (0) CHECK (devIsArchived IN (0, 1)), - devParentMAC TEXT, - devParentPort INTEGER, - devIcon TEXT, - devGUID TEXT, - devSite TEXT, - devSSID TEXT, - devSyncHubNode TEXT, - devSourcePlugin TEXT - ); - - CREATE INDEX IF NOT EXISTS IDX_dev_PresentLastScan ON Devices (devPresentLastScan); - CREATE INDEX IF NOT EXISTS IDX_dev_FirstConnection ON Devices (devFirstConnection); - CREATE INDEX IF NOT EXISTS IDX_dev_AlertDeviceDown ON Devices (devAlertDown); - CREATE INDEX IF NOT EXISTS IDX_dev_StaticIP ON Devices (devStaticIP); - CREATE INDEX IF NOT EXISTS IDX_dev_ScanCycle ON Devices (devScan); - CREATE INDEX IF NOT EXISTS IDX_dev_Favorite ON Devices (devFavorite); - CREATE INDEX IF NOT EXISTS IDX_dev_LastIP ON Devices (devLastIP); - CREATE INDEX IF NOT EXISTS IDX_dev_NewDevice ON Devices (devIsNew); - CREATE INDEX IF NOT EXISTS IDX_dev_Archived ON Devices (devIsArchived); - """ - - # Execute the creation of the Devices table and indexes - self.sql.executescript(sql_create_devices_new) - - # copy over data - sql_copy_from_devices_tmp = """ - INSERT OR IGNORE INTO Devices ( - devMac, - devName, - devOwner, - devType, - devVendor, - devFavorite, - devGroup, - devComments, - devFirstConnection, - devLastConnection, - devLastIP, - devStaticIP, - devScan, - devLogEvents, - devAlertEvents, - devAlertDown, - devSkipRepeated, - devLastNotification, - devPresentLastScan, - devIsNew, - devLocation, - devIsArchived, - devParentMAC, - devParentPort, - devIcon, - devGUID, - devSite, - devSSID, - devSyncHubNode, - devSourcePlugin - ) - SELECT - devMac, - devName, - devOwner, - devType, - devVendor, - devFavorite, - devGroup, - devComments, - devFirstConnection, - devLastConnection, - devLastIP, - devStaticIP, - devScan, - devLogEvents, - devAlertEvents, - devAlertDown, - devSkipRepeated, - devLastNotification, - devPresentLastScan, - devIsNew, - devLocation, - devIsArchived, - devParentMAC, - devParentPort, - devIcon, - devGUID, - devSite, - devSSID, - devSyncHubNode, - devSourcePlugin - FROM Devices_tmp; - """ - - self.sql.execute(sql_copy_from_devices_tmp) - self.sql.execute(""" DROP TABLE Devices_tmp;""") - + # ------------------------------------------------------------------------- + # Alter Devices table + # ------------------------------------------------------------------------- # VIEWS @@ -477,17 +117,19 @@ class DB(): # add fields if missing - - # devCustomProps column - devCustomProps_missing = self.sql.execute (""" - SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Devices') WHERE name='devCustomProps' + + # devFQDN missing? + devFQDN_missing = self.sql.execute (""" + SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Devices') WHERE name='devFQDN' """).fetchone()[0] == 0 + + if devFQDN_missing: - if devCustomProps_missing : - mylog('verbose', ["[upgradeDB] Adding devCustomProps to the Devices table"]) + mylog('verbose', ["[upgradeDB] Adding devFQDN to the Devices table"]) self.sql.execute(""" - ALTER TABLE "Devices" ADD "devCustomProps" TEXT + ALTER TABLE "Devices" ADD "devFQDN" TEXT """) + # ------------------------------------------------------------------------- # Settings table setup @@ -564,48 +206,6 @@ class DB(): ); """ self.sql.execute(sql_Plugins_Objects) - # ----------------------------------------- - # REMOVE after 6/6/2025 - START - # ----------------------------------------- - # syncHubNodeName column - plug_SyncHubNodeName_missing = self.sql.execute (""" - SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Plugins_Objects') WHERE name='SyncHubNodeName' - """).fetchone()[0] == 0 - - if plug_SyncHubNodeName_missing : - mylog('verbose', ["[upgradeDB] Adding SyncHubNodeName to the Plugins_Objects table"]) - self.sql.execute(""" - ALTER TABLE "Plugins_Objects" ADD "SyncHubNodeName" TEXT - """) - - # helper columns HelpVal1-4 - plug_HelpValues_missing = self.sql.execute (""" - SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Plugins_Objects') WHERE name='HelpVal1' - """).fetchone()[0] == 0 - - if plug_HelpValues_missing : - mylog('verbose', ["[upgradeDB] Adding HelpVal1-4 to the Plugins_Objects table"]) - self.sql.execute('ALTER TABLE "Plugins_Objects" ADD COLUMN "HelpVal1" TEXT') - self.sql.execute('ALTER TABLE "Plugins_Objects" ADD COLUMN "HelpVal2" TEXT') - self.sql.execute('ALTER TABLE "Plugins_Objects" ADD COLUMN "HelpVal3" TEXT') - self.sql.execute('ALTER TABLE "Plugins_Objects" ADD COLUMN "HelpVal4" TEXT') - - # plug_ObjectGUID_missing column - plug_ObjectGUID_missing = self.sql.execute (""" - SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Plugins_Objects') WHERE name='ObjectGUID' - """).fetchone()[0] == 0 - - if plug_ObjectGUID_missing : - mylog('verbose', ["[upgradeDB] Adding ObjectGUID to the Plugins_Objects table"]) - self.sql.execute(""" - ALTER TABLE "Plugins_Objects" ADD "ObjectGUID" TEXT - """) - - - # ----------------------------------------- - # REMOVE after 6/6/2025 - END - # ----------------------------------------- - # Plugin execution results sql_Plugins_Events = """ CREATE TABLE IF NOT EXISTS Plugins_Events( "Index" INTEGER, @@ -631,49 +231,6 @@ class DB(): ); """ self.sql.execute(sql_Plugins_Events) - # ----------------------------------------- - # REMOVE after 6/6/2025 - START - # ----------------------------------------- - - # syncHubNodeName column - plug_SyncHubNodeName_missing = self.sql.execute (""" - SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Plugins_Events') WHERE name='SyncHubNodeName' - """).fetchone()[0] == 0 - - if plug_SyncHubNodeName_missing : - mylog('verbose', ["[upgradeDB] Adding SyncHubNodeName to the Plugins_Events table"]) - self.sql.execute(""" - ALTER TABLE "Plugins_Events" ADD "SyncHubNodeName" TEXT - """) - - # helper columns HelpVal1-4 - plug_HelpValues_missing = self.sql.execute (""" - SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Plugins_Events') WHERE name='HelpVal1' - """).fetchone()[0] == 0 - - if plug_HelpValues_missing : - mylog('verbose', ["[upgradeDB] Adding HelpVal1-4 to the Plugins_Events table"]) - self.sql.execute('ALTER TABLE "Plugins_Events" ADD COLUMN "HelpVal1" TEXT') - self.sql.execute('ALTER TABLE "Plugins_Events" ADD COLUMN "HelpVal2" TEXT') - self.sql.execute('ALTER TABLE "Plugins_Events" ADD COLUMN "HelpVal3" TEXT') - self.sql.execute('ALTER TABLE "Plugins_Events" ADD COLUMN "HelpVal4" TEXT') - - # plug_ObjectGUID_missing column - plug_ObjectGUID_missing = self.sql.execute (""" - SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Plugins_Events') WHERE name='ObjectGUID' - """).fetchone()[0] == 0 - - if plug_ObjectGUID_missing : - mylog('verbose', ["[upgradeDB] Adding ObjectGUID to the Plugins_Events table"]) - self.sql.execute(""" - ALTER TABLE "Plugins_Events" ADD "ObjectGUID" TEXT - """) - - # ----------------------------------------- - # REMOVE after 6/6/2025 - END - # ----------------------------------------- - - # Plugin execution history sql_Plugins_History = """ CREATE TABLE IF NOT EXISTS Plugins_History( "Index" INTEGER, @@ -699,48 +256,6 @@ class DB(): ); """ self.sql.execute(sql_Plugins_History) - # ----------------------------------------- - # REMOVE after 6/6/2025 - START - # ----------------------------------------- - - # syncHubNodeName column - plug_SyncHubNodeName_missing = self.sql.execute (""" - SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Plugins_History') WHERE name='SyncHubNodeName' - """).fetchone()[0] == 0 - - if plug_SyncHubNodeName_missing : - mylog('verbose', ["[upgradeDB] Adding SyncHubNodeName to the Plugins_History table"]) - self.sql.execute(""" - ALTER TABLE "Plugins_History" ADD "SyncHubNodeName" TEXT - """) - - # helper columns HelpVal1-4 - plug_HelpValues_missing = self.sql.execute (""" - SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Plugins_History') WHERE name='HelpVal1' - """).fetchone()[0] == 0 - - if plug_HelpValues_missing : - mylog('verbose', ["[upgradeDB] Adding HelpVal1-4 to the Plugins_History table"]) - self.sql.execute('ALTER TABLE "Plugins_History" ADD COLUMN "HelpVal1" TEXT') - self.sql.execute('ALTER TABLE "Plugins_History" ADD COLUMN "HelpVal2" TEXT') - self.sql.execute('ALTER TABLE "Plugins_History" ADD COLUMN "HelpVal3" TEXT') - self.sql.execute('ALTER TABLE "Plugins_History" ADD COLUMN "HelpVal4" TEXT') - - - # plug_ObjectGUID_missing column - plug_ObjectGUID_missing = self.sql.execute (""" - SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Plugins_History') WHERE name='ObjectGUID' - """).fetchone()[0] == 0 - - if plug_ObjectGUID_missing : - mylog('verbose', ["[upgradeDB] Adding ObjectGUID to the Plugins_History table"]) - self.sql.execute(""" - ALTER TABLE "Plugins_History" ADD "ObjectGUID" TEXT - """) - - # ----------------------------------------- - # REMOVE after 6/6/2025 - END - # ----------------------------------------- # ------------------------------------------------------------------------- # Plugins_Language_Strings table setup @@ -845,21 +360,6 @@ class DB(): # Init the AppEvent database table AppEvent_obj(self) - # ------------------------------------------------------------------------- - # DELETING OBSOLETE TABLES - to remove with updated db file after 9/9/2024 - # ------------------------------------------------------------------------- - - # Deletes obsolete ScanCycles - self.sql.execute(""" DROP TABLE IF EXISTS ScanCycles;""") - self.sql.execute(""" DROP TABLE IF EXISTS DHCP_Leases;""") - self.sql.execute(""" DROP TABLE IF EXISTS PiHole_Network;""") - - self.commitDB() - - # ------------------------------------------------------------------------- - # DELETING OBSOLETE TABLES - to remove with updated db file after 9/9/2024 - # ------------------------------------------------------------------------- - #------------------------------------------------------------------------------- def get_table_as_json(self, sqlQuery): diff --git a/server/graphql_server/graphql_schema.py b/server/graphql_server/graphql_schema.py index 83bff5a6..d5c93a6e 100755 --- a/server/graphql_server/graphql_schema.py +++ b/server/graphql_server/graphql_schema.py @@ -73,6 +73,7 @@ class Device(ObjectType): devParentChildrenCount = Int() devIpLong = Int() devFilterStatus = String() + devFQDN = String() class DeviceResult(ObjectType): @@ -180,7 +181,7 @@ class Query(ObjectType): searchable_fields = [ "devName", "devMac", "devOwner", "devType", "devVendor", "devLastIP", "devGroup", "devComments", "devLocation", "devStatus", - "devSSID", "devSite", "devSourcePlugin", "devSyncHubNode" + "devSSID", "devSite", "devSourcePlugin", "devSyncHubNode", "devFQDN" ] search_term = options.search.lower() diff --git a/server/helper.py b/server/helper.py index 6b402b2b..e8c570a9 100755 --- a/server/helper.py +++ b/server/helper.py @@ -18,6 +18,7 @@ import hashlib import random import string import ipaddress +import dns.resolver import conf from const import * @@ -427,211 +428,6 @@ def check_IP_format (pIP): # Return IP return IP.group(0) -#------------------------------------------------------------------------------- -def get_device_name_mdns(db, pMAC, pIP): - - nameNotFound = "(name not found)" - - sql = db.sql - - name = nameNotFound - - # get names from the AVAHISCAN plugin entries vased on MAC - sql.execute( - f""" - SELECT Watched_Value2 FROM Plugins_Objects - WHERE - Plugin = 'AVAHISCAN' AND - Object_PrimaryID = '{pMAC}' - """ - ) - nameEntry = sql.fetchall() - db.commitDB() - - if len(nameEntry) != 0: - name = cleanDeviceName(nameEntry[0][0], False) - - return name - - # get names from the AVAHISCAN plugin entries based on IP - sql.execute( - f""" - SELECT Watched_Value2 FROM Plugins_Objects - WHERE - Plugin = 'AVAHISCAN' AND - Object_SecondaryID = '{pIP}' - """ - ) - nameEntry = sql.fetchall() - db.commitDB() - - if len(nameEntry) != 0: - name = cleanDeviceName(nameEntry[0][0], True) - - return name - - return name - -#------------------------------------------------------------------------------- -def get_device_name_nslookup(db, pMAC, pIP): - - nameNotFound = "(name not found)" - - sql = db.sql - - name = nameNotFound - - # get names from the NSLOOKUP plugin entries vased on MAC - sql.execute( - f""" - SELECT Watched_Value2 FROM Plugins_Objects - WHERE - Plugin = 'NSLOOKUP' AND - Object_PrimaryID = '{pMAC}' - """ - ) - nameEntry = sql.fetchall() - db.commitDB() - - if len(nameEntry) != 0: - name = cleanDeviceName(nameEntry[0][0], False) - - return name - - # get names from the NSLOOKUP plugin entries based on IP - sql.execute( - f""" - SELECT Watched_Value2 FROM Plugins_Objects - WHERE - Plugin = 'NSLOOKUP' AND - Object_SecondaryID = '{pIP}' - """ - ) - nameEntry = sql.fetchall() - db.commitDB() - - if len(nameEntry) != 0: - name = cleanDeviceName(nameEntry[0][0], True) - - return name - - return name - -#------------------------------------------------------------------------------- -def get_device_name_nbtlookup(db, pMAC, pIP): - - nameNotFound = "(name not found)" - - sql = db.sql - - name = nameNotFound - - # get names from the NBTSCAN plugin entries vased on MAC - sql.execute( - f""" - SELECT Watched_Value2 FROM Plugins_Objects - WHERE - Plugin = 'NBTSCAN' AND - Object_PrimaryID = '{pMAC}' - """ - ) - nameEntry = sql.fetchall() - db.commitDB() - - if len(nameEntry) != 0: - name = cleanDeviceName(nameEntry[0][0], False) - - return name - - # get names from the NSLOOKUP plugin entries based on IP - sql.execute( - f""" - SELECT Watched_Value2 FROM Plugins_Objects - WHERE - Plugin = 'NBTSCAN' AND - Object_SecondaryID = '{pIP}' - """ - ) - nameEntry = sql.fetchall() - db.commitDB() - - if len(nameEntry) != 0: - name = cleanDeviceName(nameEntry[0][0], True) - - return name - - return name - - -#------------------------------------------------------------------------------- -def resolve_device_name_dig (pMAC, pIP): - - nameNotFound = "(name not found)" - - dig_args = ['dig', '+short', '-x', pIP] - - # Execute command - try: - # try runnning a subprocess - newName = subprocess.check_output (dig_args, universal_newlines=True) - - # Check returns - newName = newName.strip() - - if len(newName) == 0 : - return nameNotFound - - # Cleanup - newName = cleanDeviceName(newName, True) - - if newName == "" or len(newName) == 0 or newName == '-1' or newName == -1 or "communications error" in newName or 'malformed message packet' in newName : - return nameNotFound - - # all checks passed - mylog('debug', [f'[resolve_device_name_dig] Found a new name: "{newName}"']) - - return newName - - except subprocess.CalledProcessError as e: - # An error occured, handle it - mylog('none', ['[resolve_device_name_dig] ⚠ ERROR: ', e.output]) - # newName = "Error - check logs" - return nameNotFound - - -#------------------------------------------------------------------------------- -# DNS record (Name resolution) cleanup methods -#------------------------------------------------------------------------------- - -import dns.resolver - -def cleanDeviceName(str, match_IP): - - mylog('debug', ["[cleanDeviceName] input: " + str]) - - # add matching info - if match_IP: - str = str + " (IP match)" - - # Applying cleanup REGEXEs - mylog('debug', ["[Name cleanup] Using old cleanDeviceName(" + str + ")"]) - - regexes = get_setting_value('NEWDEV_NAME_CLEANUP_REGEX') - - for rgx in regexes: - mylog('trace', ["[cleanDeviceName] applying regex : " + rgx]) - mylog('trace', ["[cleanDeviceName] name before regex : " + str]) - str = re.sub(rgx, "", str) - mylog('trace', ["[cleanDeviceName] name after regex : " + str]) - - # str = re.sub(r'\.\b', '', str) # trailing dot after words - str = re.sub(r'\.$', '', str) # trailing dot at the end of the string - str = str.replace(". (IP match)", " (IP match)") # Remove dot if (IP match) is added - - mylog('debug', ["[cleanDeviceName] output: " + str]) - - return str - #------------------------------------------------------------------------------- # String manipulation methods #------------------------------------------------------------------------------- diff --git a/server/initialise.py b/server/initialise.py index fac42004..3a321f02 100755 --- a/server/initialise.py +++ b/server/initialise.py @@ -168,6 +168,7 @@ def importConfigs (db, all_plugins): conf.HRS_TO_KEEP_NEWDEV = ccd('HRS_TO_KEEP_NEWDEV', 0 , c_d, 'Keep new devices for', '{"dataType":"integer", "elements": [{"elementType" : "input", "elementOptions" : [{"type": "number"}] ,"transformers": []}]}', "[]", 'General') conf.HRS_TO_KEEP_OFFDEV = ccd('HRS_TO_KEEP_OFFDEV', 0 , c_d, 'Keep offline devices for', '{"dataType":"integer", "elements": [{"elementType" : "input", "elementOptions" : [{"type": "number"}] ,"transformers": []}]}', "[]", 'General') conf.CLEAR_NEW_FLAG = ccd('CLEAR_NEW_FLAG', 0 , c_d, 'Clear new flag', '{"dataType":"integer", "elements": [{"elementType" : "input", "elementOptions" : [{"type": "number"}] ,"transformers": []}]}', "[]", 'General') + conf.REFRESH_FQDN = ccd('REFRESH_FQDN', False , c_d, 'Refresh FQDN', """{"dataType": "boolean","elements": [{"elementType": "input","elementOptions": [{ "type": "checkbox" }],"transformers": []}]}""", '[]', 'General') conf.API_CUSTOM_SQL = ccd('API_CUSTOM_SQL', 'SELECT * FROM Devices WHERE devPresentLastScan = 0' , c_d, 'Custom endpoint', '{"dataType":"string", "elements": [{"elementType" : "input", "elementOptions" : [] ,"transformers": []}]}', '[]', 'General') conf.VERSION = ccd('VERSION', '' , c_d, 'Version', '{"dataType":"string", "elements": [{"elementType" : "input", "elementOptions" : [{ "readonly": "true" }] ,"transformers": []}]}', '', 'General') conf.NETWORK_DEVICE_TYPES = ccd('NETWORK_DEVICE_TYPES', ['AP', 'Gateway', 'Firewall', 'Hypervisor', 'Powerline', 'Switch', 'WLAN', 'PLC', 'Router','USB LAN Adapter', 'USB WIFI Adapter', 'Internet'] , c_d, 'Network device types', '{"dataType":"array","elements":[{"elementType":"input","elementOptions":[{"placeholder":"Enter value"},{"suffix":"_in"},{"cssClasses":"col-sm-10"},{"prefillValue":"null"}],"transformers":[]},{"elementType":"button","elementOptions":[{"sourceSuffixes":["_in"]},{"separator":""},{"cssClasses":"col-xs-12"},{"onClick":"addList(this,false)"},{"getStringKey":"Gen_Add"}],"transformers":[]},{"elementType":"select", "elementHasInputValue":1,"elementOptions":[{"multiple":"true"},{"readonly":"true"},{"editable":"true"}],"transformers":[]},{"elementType":"button","elementOptions":[{"sourceSuffixes":[]},{"separator":""},{"cssClasses":"col-xs-6"},{"onClick":"removeAllOptions(this)"},{"getStringKey":"Gen_Remove_All"}],"transformers":[]},{"elementType":"button","elementOptions":[{"sourceSuffixes":[]},{"separator":""},{"cssClasses":"col-xs-6"},{"onClick":"removeFromList(this)"},{"getStringKey":"Gen_Remove_Last"}],"transformers":[]}]}', '[]', 'General') @@ -445,46 +446,6 @@ replacements = { r'\bREPORT_TO\b': 'SMTP_REPORT_TO', r'\bSYNC_api_token\b': 'API_TOKEN', r'\bAPI_TOKEN=\'\'': f'API_TOKEN=\'t_{generate_random_string(20)}\'', - r'\bREPORT_FROM\b': 'SMTP_REPORT_FROM', - r'\bPIALERT_WEB_PROTECTION\b': 'SETPWD_enable_password', - r'\bPIALERT_WEB_PASSWORD\b': 'SETPWD_password', - r'REPORT_MAIL=True': "SMTP_RUN='on_notification'", - r'REPORT_APPRISE=True': "APPRISE_RUN='on_notification'", - r'REPORT_NTFY=True': "NTFY_RUN='on_notification'", - r'REPORT_WEBHOOK=True': "WEBHOOK_RUN='on_notification'", - r'REPORT_PUSHSAFER=True': "PUSHSAFER_RUN='on_notification'", - r'REPORT_MQTT=True': "MQTT_RUN='on_notification'", - # r'PIHOLE_CMD=': 'PIHOLE_CMD_OLD=', - r'\bINCLUDED_SECTIONS\b': 'NTFPRCS_INCLUDED_SECTIONS', - r'\bDIG_GET_IP_ARG\b': 'INTRNT_DIG_GET_IP_ARG', - r'dev_MAC': 'devMac', - r'dev_Name': 'devName', - r'dev_Favorite': 'devFavorite', - r'dev_Group': 'devGroup', - r'dev_Comments': 'devComments', - r'dev_FirstConnection': 'devFirstConnection', - r'dev_LastConnection': 'devLastConnection', - r'dev_LastIP': 'devLastIP', - r'dev_StaticIP': 'devStaticIP', - r'dev_ScanCycle': 'devScan', - r'dev_LogEvents': 'devLogEvents', - r'dev_AlertEvents': 'devAlertEvents', - r'dev_AlertDeviceDown': 'devAlertDown', - r'dev_SkipRepeated': 'devSkipRepeated', - r'dev_LastNotification': 'devLastNotification', - r'dev_PresentLastScan': 'devPresentLastScan', - r'dev_NewDevice': 'devIsNew', - r'dev_Location': 'devLocation', - r'dev_Archived': 'devIsArchived', - r'dev_Network_Node_MAC_ADDR': 'devParentMAC', - r'dev_Network_Node_port': 'devParentPort', - r'dev_Icon': 'devIcon', - r'dev_GUID': 'devGUID', - r'dev_NetworkSite': 'devSite', - r'dev_SSID': 'devSSID', - r'dev_SyncHubNodeName': 'devSyncHubNode', - r'dev_SourcePlugin': 'devSourcePlugin', - r'/home/pi/pialert\b': '/app' } diff --git a/server/scan/device_handling.py b/server/scan/device_handling.py index 948ab52d..636b47ce 100755 --- a/server/scan/device_handling.py +++ b/server/scan/device_handling.py @@ -8,10 +8,11 @@ import subprocess import conf import os import re -from helper import timeNowTZ, get_setting, get_setting_value, list_to_where, resolve_device_name_dig, get_device_name_nbtlookup, get_device_name_nslookup, get_device_name_mdns, check_IP_format, sanitize_SQL_input +from helper import timeNowTZ, get_setting, get_setting_value, list_to_where, check_IP_format, sanitize_SQL_input from logger import mylog from const import vendorsPath, vendorsPathNewest, sql_generateGuid from models.device_instance import DeviceInstance +from scan.name_resolution import NameResolver #------------------------------------------------------------------------------- # Removing devices from the CurrentScan DB table which the user chose to ignore by MAC or IP @@ -481,88 +482,109 @@ def update_devices_data_from_scan (db): mylog('debug','[Update Devices] Update devices end') #------------------------------------------------------------------------------- -def update_devices_names (db): - sql = db.sql #TO-DO - # Initialize variables - recordsToUpdate = [] - recordsNotFound = [] +def update_devices_names(db): + sql = db.sql + resolver = NameResolver(db) + device_handler = DeviceInstance(db) nameNotFound = "(name not found)" - ignored = 0 - notFound = 0 + # Define resolution strategies in priority order + strategies = [ + (resolver.resolve_dig, 'dig'), + (resolver.resolve_mdns, 'mdns'), + (resolver.resolve_nslookup, 'nslookup'), + (resolver.resolve_nbtlookup, 'nbtlookup') + ] - foundDig = 0 - foundmDNSLookup = 0 - foundNsLookup = 0 - foundNbtLookup = 0 + def resolve_devices(devices, resolve_both_name_and_fqdn=True): + """ + Attempts to resolve device names and/or FQDNs using available strategies. + + Parameters: + devices (list): List of devices to resolve. + resolve_both_name_and_fqdn (bool): If True, resolves both name and FQDN. + If False, resolves only FQDN. + + Returns: + recordsToUpdate (list): List of [newName, newFQDN, devMac] or [newFQDN, devMac] for DB update. + recordsNotFound (list): List of [nameNotFound, devMac] for DB update. + foundStats (dict): Number of successes per strategy. + notFound (int): Number of devices not resolved. + """ + recordsToUpdate = [] + recordsNotFound = [] + foundStats = {label: 0 for _, label in strategies} + notFound = 0 - # Gen unknown devices - device_handler = DeviceInstance(db) - # Retrieve devices + for device in devices: + newName = nameNotFound + newFQDN = '' + + # Attempt each resolution strategy in order + for resolve_fn, label in strategies: + resolved = resolve_fn(device['devMac'], device['devLastIP']) + + # Only use name if resolving both name and FQDN + newName = resolved.cleaned if resolve_both_name_and_fqdn else None + newFQDN = resolved.raw + + # If a valid result is found, record it and stop further attempts + if newFQDN not in [nameNotFound, '', 'localhost.'] and ' communications error to ' not in newFQDN: + foundStats[label] += 1 + + if resolve_both_name_and_fqdn: + recordsToUpdate.append([newName, newFQDN, device['devMac']]) + else: + recordsToUpdate.append([newFQDN, device['devMac']]) + break + + # If no name was resolved, queue device for "(name not found)" update + if resolve_both_name_and_fqdn and newName == nameNotFound: + notFound += 1 + if device['devName'] != nameNotFound: + recordsNotFound.append([nameNotFound, device['devMac']]) + + return recordsToUpdate, recordsNotFound, foundStats, notFound + + # --- Step 1: Update device names for unknown devices --- unknownDevices = device_handler.getUnknown() + if unknownDevices: + mylog('verbose', f'[Update Device Name] Trying to resolve devices without name. Unknown devices count: {len(unknownDevices)}') - # skip checks if no unknown devices - if len(unknownDevices) == 0: - return + # Try resolving both name and FQDN + recordsToUpdate, recordsNotFound, foundStats, notFound = resolve_devices(unknownDevices) - # Devices without name - mylog('verbose', f'[Update Device Name] Trying to resolve devices without name. Unknown devices count: {len(unknownDevices)}') + # Log summary + mylog('verbose', f"[Update Device Name] Names Found (DiG/mDNS/NSLOOKUP/NBTSCAN): {len(recordsToUpdate)} ({foundStats['dig']}/{foundStats['mdns']}/{foundStats['nslookup']}/{foundStats['nbtlookup']})") + mylog('verbose', f'[Update Device Name] Names Not Found : {notFound}') - for device in unknownDevices: - newName = nameNotFound - - # Resolve device name with DiG - newName = resolve_device_name_dig (device['devMac'], device['devLastIP']) - - # count - if newName != nameNotFound: - foundDig += 1 - - # Resolve device name with AVAHISCAN plugin data - if newName == nameNotFound: - newName = get_device_name_mdns(db, device['devMac'], device['devLastIP']) + # Apply updates to database + sql.executemany("UPDATE Devices SET devName = ? WHERE devMac = ?", recordsNotFound) + sql.executemany("UPDATE Devices SET devName = ?, devFQDN = ? WHERE devMac = ?", recordsToUpdate) - if newName != nameNotFound: - foundmDNSLookup += 1 + # --- Step 2: Optionally refresh FQDN for all devices --- + if get_setting_value("REFRESH_FQDN"): + allDevices = device_handler.getAll() + if allDevices: + mylog('verbose', f'[Update FQDN] Trying to resolve FQDN. Devices count: {len(allDevices)}') - # Resolve device name with NSLOOKUP plugin data - if newName == nameNotFound: - newName = get_device_name_nslookup(db, device['devMac'], device['devLastIP']) + # Try resolving only FQDN + recordsToUpdate, _, foundStats, notFound = resolve_devices(allDevices, resolve_both_name_and_fqdn=False) - if newName != nameNotFound: - foundNsLookup += 1 - - # Resolve device name with NBTLOOKUP plugin data - if newName == nameNotFound: - newName = get_device_name_nbtlookup(db, device['devMac'], device['devLastIP']) + # Log summary + mylog('verbose', f"[Update FQDN] Names Found (DiG/mDNS/NSLOOKUP/NBTSCAN): {len(recordsToUpdate)} ({foundStats['dig']}/{foundStats['mdns']}/{foundStats['nslookup']}/{foundStats['nbtlookup']})") + mylog('verbose', f'[Update FQDN] Names Not Found : {notFound}') - if newName != nameNotFound: - foundNbtLookup += 1 - - # if still not found update name so we can distinguish the devices where we tried already - if newName == nameNotFound : + # Apply FQDN-only updates + sql.executemany("UPDATE Devices SET devFQDN = ? WHERE devMac = ?", recordsToUpdate) - notFound += 1 - - # if devName is the same as what we will change it to, take no action - # this mitigates a race condition which would overwrite a users edits that occured since the select earlier - if device['devName'] != nameNotFound: - recordsNotFound.append (["(name not found)", device['devMac']]) - else: - # name was found - recordsToUpdate.append ([newName, device['devMac']]) - - # Print log - mylog('verbose', [f'[Update Device Name] Names Found (DiG/mDNS/NSLOOKUP/NBTSCAN): {len(recordsToUpdate)} ({foundDig}/{foundmDNSLookup}/{foundNsLookup}/{foundNbtLookup})'] ) - mylog('verbose', [f'[Update Device Name] Names Not Found : {notFound}'] ) - - # update not found devices with (name not found) - sql.executemany ("UPDATE Devices SET devName = ? WHERE devMac = ? ", recordsNotFound ) - # update names of devices which we were bale to resolve - sql.executemany ("UPDATE Devices SET devName = ? WHERE devMac = ? ", recordsToUpdate ) + # Commit all database changes db.commitDB() + + + #------------------------------------------------------------------------------- # Check if the variable contains a valid MAC address or "Internet" def check_mac_or_internet(input_str): diff --git a/server/scan/name_resolution.py b/server/scan/name_resolution.py new file mode 100755 index 00000000..4e057696 --- /dev/null +++ b/server/scan/name_resolution.py @@ -0,0 +1,83 @@ +import sys +import re +import subprocess +import socket +import dns.resolver + +# Register NetAlertX directories +INSTALL_PATH = "/app" +sys.path.extend([f"{INSTALL_PATH}/server"]) + +import conf +from const import * +from logger import mylog +from helper import get_setting_value + +class ResolvedName: + def __init__(self, raw: str = "(name not found)", cleaned: str = "(name not found)"): + self.raw = raw + self.cleaned = cleaned + + def __str__(self): + return self.cleaned + +class NameResolver: + def __init__(self, db): + self.db = db + + def resolve_from_plugin(self, plugin: str, pMAC: str, pIP: str) -> ResolvedName: + sql = self.db.sql + nameNotFound = ResolvedName() + + # Check by MAC + sql.execute(f""" + SELECT Watched_Value2 FROM Plugins_Objects + WHERE Plugin = '{plugin}' AND Object_PrimaryID = '{pMAC}' + """) + result = sql.fetchall() + self.db.commitDB() + if result: + raw = result[0][0] + return ResolvedName(raw, self.clean_device_name(raw, False)) + + # Check by IP + sql.execute(f""" + SELECT Watched_Value2 FROM Plugins_Objects + WHERE Plugin = '{plugin}' AND Object_SecondaryID = '{pIP}' + """) + result = sql.fetchall() + self.db.commitDB() + if result: + raw = result[0][0] + return ResolvedName(raw, self.clean_device_name(raw, True)) + + return nameNotFound + + def resolve_mdns(self, pMAC, pIP) -> ResolvedName: + return self.resolve_from_plugin("AVAHISCAN", pMAC, pIP) + + def resolve_nslookup(self, pMAC, pIP) -> ResolvedName: + return self.resolve_from_plugin("NSLOOKUP", pMAC, pIP) + + def resolve_nbtlookup(self, pMAC, pIP) -> ResolvedName: + return self.resolve_from_plugin("NBTSCAN", pMAC, pIP) + + def resolve_dig(self, pMAC, pIP) -> ResolvedName: + return self.resolve_from_plugin("DIGSCAN", pMAC, pIP) + + def clean_device_name(self, name: str, match_ip: bool) -> str: + mylog('debug', [f"[cleanDeviceName] input: {name}"]) + + if match_ip: + name += " (IP match)" + + regexes = get_setting_value('NEWDEV_NAME_CLEANUP_REGEX') or [] + for rgx in regexes: + mylog('trace', [f"[cleanDeviceName] applying regex: {rgx}"]) + name = re.sub(rgx, "", name) + + name = re.sub(r'\.$', '', name) + name = name.replace(". (IP match)", " (IP match)") + + mylog('debug', [f"[cleanDeviceName] output: {name}"]) + return name diff --git a/server/workflows/app_events.py b/server/workflows/app_events.py index 364fb791..2d89fe98 100755 --- a/server/workflows/app_events.py +++ b/server/workflows/app_events.py @@ -170,7 +170,7 @@ class AppEvent_obj: END; """ - mylog("verbose", [query]) + # mylog("verbose", [query]) self.db.sql.execute(query) diff --git a/test/test_helper.py b/test/test_helper.py index 576b8601..f1493acf 100755 --- a/test/test_helper.py +++ b/test/test_helper.py @@ -86,9 +86,10 @@ def insert_devices(db_path, num_entries=1): devSSID, devSyncHubNode, devSourcePlugin, - devCustomProps + devCustomProps, + devFQDN ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); """ # List of device types, vendors, groups, locations @@ -130,6 +131,7 @@ def insert_devices(db_path, num_entries=1): dev_sync_hub_node = "" # Left as NULL dev_source_plugin = "" # Left as NULL dev_devCustomProps = "" # Left as NULL + dev_devFQDN = "" # Left as NULL # Execute the insert query cursor.execute(insert_query, ( @@ -163,7 +165,8 @@ def insert_devices(db_path, num_entries=1): dev_ssid, dev_sync_hub_node, dev_source_plugin, - dev_devCustomProps + dev_devCustomProps, + dev_devFQDN )) # Commit after every 1000 rows to improve performance