From 1011652959394cca69e3a2d3a2627423fcd564f8 Mon Sep 17 00:00:00 2001 From: Adam Outler Date: Thu, 29 Jan 2026 16:18:41 +0000 Subject: [PATCH 01/15] Fix fresh install script --- .../entrypoint.d/20-first-run-config.sh | 42 +++++++++++++++---- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/install/production-filesystem/entrypoint.d/20-first-run-config.sh b/install/production-filesystem/entrypoint.d/20-first-run-config.sh index 8e37f2d6..b57a657f 100755 --- a/install/production-filesystem/entrypoint.d/20-first-run-config.sh +++ b/install/production-filesystem/entrypoint.d/20-first-run-config.sh @@ -6,17 +6,46 @@ if [ -d "${NETALERTX_CONFIG}" ]; then chmod u+rwX "${NETALERTX_CONFIG}" 2>/dev/null || true fi chmod u+rw "${NETALERTX_CONFIG}/app.conf" 2>/dev/null || true + +set -eu + +CYAN=$(printf '\033[1;36m') +RED=$(printf '\033[1;31m') +RESET=$(printf '\033[0m') + +# Ensure config folder exists +if [ ! -d "${NETALERTX_CONFIG}" ]; then + if ! mkdir -p "${NETALERTX_CONFIG}"; then + >&2 printf "%s" "${RED}" + >&2 cat <&2 printf "%s" "${RESET}" + exit 1 + fi + chmod 700 "${NETALERTX_CONFIG}" 2>/dev/null || true +fi + +# Fresh rebuild requested +if [ "${ALWAYS_FRESH_INSTALL:-false}" = "true" ] && [ -e "${NETALERTX_CONFIG}/app.conf" ]; then + >&2 echo "INFO: ALWAYS_FRESH_INSTALL enabled — removing existing config." + rm -rf "${NETALERTX_CONFIG}"/* +fi + # Check for app.conf and deploy if required if [ ! -f "${NETALERTX_CONFIG}/app.conf" ]; then - mkdir -p "${NETALERTX_CONFIG}" || { - >&2 echo "ERROR: Failed to create config directory ${NETALERTX_CONFIG}" - exit 1 - } install -m 600 /app/back/app.conf "${NETALERTX_CONFIG}/app.conf" || { >&2 echo "ERROR: Failed to deploy default config to ${NETALERTX_CONFIG}/app.conf" exit 2 } - RESET=$(printf '\033[0m') + >&2 printf "%s" "${CYAN}" >&2 cat <&2 printf "%s" "${RESET}" + >&2 printf "%s" "${RESET}" fi From 229ea770cb3e09e2bcce9fa51b5ae007d3234733 Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Fri, 30 Jan 2026 07:50:21 +1100 Subject: [PATCH 02/15] feat: authoritative plugin fields - fix devFQDN + docs + allow filters and columns on new fields Signed-off-by: jokob-sk --- docs/API_DEVICE_FIELD_LOCK.md | 33 ++---------- docs/DEVICE_FIELD_LOCK.md | 23 +++++---- docs/DEVICE_SOURCE_FIELDS.md | 67 +++++++++++++++++++++++++ front/js/sse_manager.js | 10 ++-- front/js/ui_components.js | 5 +- front/php/templates/language/ar_ar.json | 3 ++ front/php/templates/language/ca_ca.json | 3 ++ front/php/templates/language/cs_cz.json | 3 ++ front/php/templates/language/de_de.json | 3 ++ front/php/templates/language/en_us.json | 3 ++ front/php/templates/language/es_es.json | 3 ++ front/php/templates/language/fa_fa.json | 3 ++ front/php/templates/language/fr_fr.json | 5 +- front/php/templates/language/it_it.json | 5 +- front/php/templates/language/ja_jp.json | 3 ++ front/php/templates/language/nb_no.json | 3 ++ front/php/templates/language/pl_pl.json | 3 ++ front/php/templates/language/pt_br.json | 3 ++ front/php/templates/language/pt_pt.json | 3 ++ front/php/templates/language/ru_ru.json | 3 ++ front/php/templates/language/sv_sv.json | 3 ++ front/php/templates/language/tr_tr.json | 3 ++ front/php/templates/language/uk_ua.json | 5 +- front/php/templates/language/zh_cn.json | 3 ++ front/plugins/ui_settings/config.json | 7 ++- server/db/authoritative_handler.py | 1 - server/scan/device_handling.py | 1 + 27 files changed, 161 insertions(+), 49 deletions(-) create mode 100644 docs/DEVICE_SOURCE_FIELDS.md diff --git a/docs/API_DEVICE_FIELD_LOCK.md b/docs/API_DEVICE_FIELD_LOCK.md index 5819301e..61180cf7 100644 --- a/docs/API_DEVICE_FIELD_LOCK.md +++ b/docs/API_DEVICE_FIELD_LOCK.md @@ -138,36 +138,6 @@ The Device Edit form displays lock/unlock buttons for all tracked fields: 2. **Unlock Button** (🔓): Click to allow plugin overwrites again 3. **Source Indicator**: Shows current field source (USER, LOCKED, NEWDEV, or plugin name) -## UI Workflow - -### Locking a Field via UI - -1. Navigate to Device Details -2. Find the field you want to protect -3. Click the lock button (🔒) next to the field -4. Button changes to unlock (🔓) and source indicator turns red (LOCKED) -5. Field is now protected from plugin overwrites - -### Unlocking a Field via UI - -1. Find the locked field (button shows 🔓) -2. Click the unlock button -3. Button changes back to lock (🔒) and source resets to NEWDEV -4. Plugins can now update this field again - -## Authorization - -All lock/unlock operations require: -- Valid API token in `Authorization: Bearer {token}` header -- User must be authenticated to the NetAlertX instance - -## Implementation Details - -### Backend Logic -The lock/unlock feature is implemented in: -- **API Endpoint**: `/server/api_server/api_server_start.py` - `api_device_field_lock()` -- **Data Model**: `/server/models/device_instance.py` - Authorization checks in `setDeviceData()` -- **Database**: Devices table with `*Source` columns tracking field origins ### Authorization Handler @@ -179,6 +149,9 @@ The authoritative field update logic prevents plugin overwrites: 4. If source is `NEWDEV` or plugin name, plugin update is accepted ## See Also + +- [Device locking](./DEVICE_FIELD_LOCK.md) +- [Device source fields](./DEVICE_SOURCE_FIELDS.md) - [API Device Endpoints Documentation](./API_DEVICE.md) - [Authoritative Field Updates System](./PLUGINS_DEV.md#authoritative-fields) - [Plugin Configuration Reference](./PLUGINS_DEV_CONFIG.md) diff --git a/docs/DEVICE_FIELD_LOCK.md b/docs/DEVICE_FIELD_LOCK.md index fcd24960..47369c57 100644 --- a/docs/DEVICE_FIELD_LOCK.md +++ b/docs/DEVICE_FIELD_LOCK.md @@ -37,7 +37,12 @@ Each locked field has a "source" indicator that shows you why the value is prote | 📡 **NEWDEV** | Default/unset value | Yes, plugins can update | | 📡 **Plugin name** | Last updated by a plugin (e.g., UNIFIAPI) | Yes, plugins can update if field in SET_ALWAYS | -## How to Use +Overwrite rules are + +> [!TIP] +> You can bulk-unlock devices in the [Multi-edit](./DEVICES_BULK_EDITING.md) dialog. This removes all `USER` and `LOCKED` values from all `*Source` fields of selected devices. + +## Usage Examples ### Lock a Field (Prevent Plugin Changes) @@ -147,13 +152,13 @@ Each locked field has a "source" indicator that shows you why the value is prote - Check if you accidentally unlocked it - Open an issue if it persists -## For More Information +## See also -- **Technical details:** See [API_DEVICE_FIELD_LOCK.md](API_DEVICE_FIELD_LOCK.md) -- **Plugin configuration:** See [PLUGINS_DEV_CONFIG.md](PLUGINS_DEV_CONFIG.md) -- **Admin guide:** See [DEVICE_MANAGEMENT.md](DEVICE_MANAGEMENT.md) - ---- - -**Quick Start:** Find a device field you want to protect → Click the lock icon → That's it! The field won't change until you unlock it. +- [Device locking](./DEVICE_FIELD_LOCK.md) +- [Device source fields](./DEVICE_SOURCE_FIELDS.md) +- [API Device Endpoints Documentation](./API_DEVICE.md) +- [Authoritative Field Updates System](./PLUGINS_DEV.md#authoritative-fields) +- [Plugin Configuration Reference](./PLUGINS_DEV_CONFIG.md) +- [Device locking APIs](API_DEVICE_FIELD_LOCK.md) +- [Device management](DEVICE_MANAGEMENT.md) diff --git a/docs/DEVICE_SOURCE_FIELDS.md b/docs/DEVICE_SOURCE_FIELDS.md new file mode 100644 index 00000000..02ef5e46 --- /dev/null +++ b/docs/DEVICE_SOURCE_FIELDS.md @@ -0,0 +1,67 @@ +# Understanding Device Source Fields and Field Updates + +When the system scans a network, it finds various details about devices (like names, IP addresses, and manufacturers). To ensure the data remains accurate without accidentally overwriting manual changes, the system uses a set of "Source Rules." + +![Field source and locks](./img/DEVICE_MANAGEMENT/field_sources_and_locks.png) + +--- + +## The "Protection" Levels + +Every piece of information for a device has a **Source**. This source determines whether a new scan is allowed to change that value. + +| Source Status | Description | Can a Scan Overwrite it? | +| --- | --- | --- | +| **USER** | You manually entered this value. | **Never** | +| **LOCKED** | This value is pinned and protected. | **Never** | +| **NEWDEV** | This value was initialized from `NEWDEV` plugin settings. | **Always** | +| **(Plugin Name)** | The value was found by a specific scanner (e.g., `NBTSCAN`). | **Only if specific rules are met** | + +--- + +## How Scans Update Information + +If a field is **not** protected by a `USER` or `LOCKED` status, the system follows these rules to decide if it should update the info: + +### 1. The "Empty Field" Rule (Default) + +By default, the system is cautious. It will only fill in a piece of information if the current field is **empty** (showing as "unknown," "0.0.0.0," or blank). It won't change for example an existing name unless you tell it to. + +### 2. SET_ALWAYS + +Some plugins are configured to be "authoritative." If a field is in the **SET_ALWAYS** setting of a plugin: + +* The scanner will **always** overwrite the current value with the new one. +* *Note: It will still never overwrite a `USER` or `LOCKED` field.* + +### 3. SET_EMPTY + +If a field is in the **SET_EMPTY** list: + +* The scanner will **only** provide a value if the current field is currently empty. +* This is used for fields where we want to "fill in the blanks" but never change a value once it has been established by any source. + +### 4. Automatic Overrides (Live Tracking) + +Some fields, like **IP Addresses** (`devLastIP`) and **Full Domain Names** (`devFQDN`), are set to automatically update whenever they change. This ensures that if a device moves to a new IP on your network, the system reflects that change immediately without you having to do anything. + +--- + +## Summary of Field Logic + +| If the current value is... | And the Scan finds... | Does it update? | +| --- | --- | --- | +| **USER / LOCKED** | Anything | **No** | +| **Empty** | A new value | **Yes** | +| **A "Plugin" value** | A different value | **No** (Unless `SET_ALWAYS` is on) | +| **An IP Address** | A different IP | **Yes** (Updates automatically) | + +## See also: + +- [Device locking](./DEVICE_FIELD_LOCK.md) +- [Device source fields](./DEVICE_SOURCE_FIELDS.md) +- [API Device Endpoints Documentation](./API_DEVICE.md) +- [Authoritative Field Updates System](./PLUGINS_DEV.md#authoritative-fields) +- [Plugin Configuration Reference](./PLUGINS_DEV_CONFIG.md) +- [Device locking APIs](API_DEVICE_FIELD_LOCK.md) +- [Device management](DEVICE_MANAGEMENT.md) diff --git a/front/js/sse_manager.js b/front/js/sse_manager.js index 8425b306..3afda1c6 100644 --- a/front/js/sse_manager.js +++ b/front/js/sse_manager.js @@ -145,14 +145,18 @@ class NetAlertXStateManager { .attr('data-version', version); // 3. Update Build Timestamp placeholders - const buildTime = appState["buildTimestamp"] !== undefined ? appState["buildTimestamp"] : ""; - const displayTime = (buildTime === 0) ? "UNKNOWN" : buildTime; + const buildTime = appState["buildTimestamp"] || 0; + const displayTime = buildTime ? localizeTimestamp(buildTime) : "UNKNOWN"; + + $('[data-plc="build-timestamp"]') + .html(displayTime) + .attr('data-build-time', buildTime); $('[data-plc="build-timestamp"]') .html(displayTime) .attr('data-build-time', buildTime); - console.log("[NetAlertX State] UI updated via jQuery"); + // console.log("[NetAlertX State] UI updated via jQuery"); } catch (e) { console.error("[NetAlertX State] Failed to update state display:", e); } diff --git a/front/js/ui_components.js b/front/js/ui_components.js index 649531c6..80710fe1 100755 --- a/front/js/ui_components.js +++ b/front/js/ui_components.js @@ -668,7 +668,10 @@ function getColumnNameFromLangString(headStringKey) { "Device_TableHead_CustomProps": "devCustomProps", "Device_TableHead_FQDN": "devFQDN", "Device_TableHead_ParentRelType": "devParentRelType", - "Device_TableHead_ReqNicsOnline": "devReqNicsOnline" + "Device_TableHead_ReqNicsOnline": "devReqNicsOnline", + "Device_TableHead_Vlan": "devVlan", + "Device_TableHead_IPv4": "devPrimaryIPv4", + "Device_TableHead_IPv6": "devPrimaryIPv6" }; return columnNameMap[headStringKey] || ""; diff --git a/front/php/templates/language/ar_ar.json b/front/php/templates/language/ar_ar.json index e97400ba..a91e36cc 100644 --- a/front/php/templates/language/ar_ar.json +++ b/front/php/templates/language/ar_ar.json @@ -226,6 +226,8 @@ "Device_TableHead_FirstSession": "أول جلسة", "Device_TableHead_GUID": "معرف فريد", "Device_TableHead_Group": "المجموعة", + "Device_TableHead_IPv4": "", + "Device_TableHead_IPv6": "", "Device_TableHead_Icon": "الأيقونة", "Device_TableHead_LastIP": "آخر عنوان IP", "Device_TableHead_LastIPOrder": "ترتيب آخر عنوان IP", @@ -249,6 +251,7 @@ "Device_TableHead_SyncHubNodeName": "اسم عقدة المزامنة", "Device_TableHead_Type": "النوع", "Device_TableHead_Vendor": "المصنع", + "Device_TableHead_Vlan": "", "Device_Table_Not_Network_Device": "ليس جهاز شبكة", "Device_Table_info": "معلومات الجدول", "Device_Table_nav_next": "التالي", diff --git a/front/php/templates/language/ca_ca.json b/front/php/templates/language/ca_ca.json index 1103b72c..a02d4e10 100644 --- a/front/php/templates/language/ca_ca.json +++ b/front/php/templates/language/ca_ca.json @@ -226,6 +226,8 @@ "Device_TableHead_FirstSession": "Primera Sessió", "Device_TableHead_GUID": "GUID", "Device_TableHead_Group": "Grup", + "Device_TableHead_IPv4": "", + "Device_TableHead_IPv6": "", "Device_TableHead_Icon": "Icona", "Device_TableHead_LastIP": "Darrera IP", "Device_TableHead_LastIPOrder": "Últim Ordre d'IP", @@ -249,6 +251,7 @@ "Device_TableHead_SyncHubNodeName": "Node Sync", "Device_TableHead_Type": "Tipus", "Device_TableHead_Vendor": "Venedor", + "Device_TableHead_Vlan": "", "Device_Table_Not_Network_Device": "No configurat com a dispositiu de xarxa", "Device_Table_info": "Mostrant _INICI_ a_FINAL_ d'entrades_ TOTALS", "Device_Table_nav_next": "Següent", diff --git a/front/php/templates/language/cs_cz.json b/front/php/templates/language/cs_cz.json index 5bad90f8..18609abe 100644 --- a/front/php/templates/language/cs_cz.json +++ b/front/php/templates/language/cs_cz.json @@ -226,6 +226,8 @@ "Device_TableHead_FirstSession": "", "Device_TableHead_GUID": "", "Device_TableHead_Group": "", + "Device_TableHead_IPv4": "", + "Device_TableHead_IPv6": "", "Device_TableHead_Icon": "", "Device_TableHead_LastIP": "", "Device_TableHead_LastIPOrder": "", @@ -249,6 +251,7 @@ "Device_TableHead_SyncHubNodeName": "", "Device_TableHead_Type": "", "Device_TableHead_Vendor": "", + "Device_TableHead_Vlan": "", "Device_Table_Not_Network_Device": "", "Device_Table_info": "", "Device_Table_nav_next": "", diff --git a/front/php/templates/language/de_de.json b/front/php/templates/language/de_de.json index 8d59d79d..fdbf364a 100644 --- a/front/php/templates/language/de_de.json +++ b/front/php/templates/language/de_de.json @@ -230,6 +230,8 @@ "Device_TableHead_FirstSession": "Erste Sitzung", "Device_TableHead_GUID": "GUID", "Device_TableHead_Group": "Gruppe", + "Device_TableHead_IPv4": "", + "Device_TableHead_IPv6": "", "Device_TableHead_Icon": "Icon", "Device_TableHead_LastIP": "Letzte IP", "Device_TableHead_LastIPOrder": "Letzte erhaltene IP", @@ -253,6 +255,7 @@ "Device_TableHead_SyncHubNodeName": "Synchronisationsknoten", "Device_TableHead_Type": "Typ", "Device_TableHead_Vendor": "Hersteller", + "Device_TableHead_Vlan": "", "Device_Table_Not_Network_Device": "Nicht konfiguriert als Netzwerkgerät", "Device_Table_info": "Zeige _START_ bis _END_ von _TOTAL_ Einträgen", "Device_Table_nav_next": "Nächste", diff --git a/front/php/templates/language/en_us.json b/front/php/templates/language/en_us.json index 36ce9c7e..ba88789f 100755 --- a/front/php/templates/language/en_us.json +++ b/front/php/templates/language/en_us.json @@ -226,6 +226,8 @@ "Device_TableHead_FirstSession": "First Session", "Device_TableHead_GUID": "GUID", "Device_TableHead_Group": "Group", + "Device_TableHead_IPv4": "IPv4", + "Device_TableHead_IPv6": "IPv6", "Device_TableHead_Icon": "Icon", "Device_TableHead_LastIP": "Last IP", "Device_TableHead_LastIPOrder": "Last IP Order", @@ -249,6 +251,7 @@ "Device_TableHead_SyncHubNodeName": "Sync Node", "Device_TableHead_Type": "Type", "Device_TableHead_Vendor": "Vendor", + "Device_TableHead_Vlan": "VLAN", "Device_Table_Not_Network_Device": "Not configured as a network device", "Device_Table_info": "Showing _START_ to _END_ of _TOTAL_ entries", "Device_Table_nav_next": "Next", diff --git a/front/php/templates/language/es_es.json b/front/php/templates/language/es_es.json index e71e127b..e7e9bf03 100644 --- a/front/php/templates/language/es_es.json +++ b/front/php/templates/language/es_es.json @@ -228,6 +228,8 @@ "Device_TableHead_FirstSession": "1ra. sesión", "Device_TableHead_GUID": "GUID", "Device_TableHead_Group": "Grupo", + "Device_TableHead_IPv4": "", + "Device_TableHead_IPv6": "", "Device_TableHead_Icon": "Icon", "Device_TableHead_LastIP": "Última IP", "Device_TableHead_LastIPOrder": "Última orden de IP", @@ -251,6 +253,7 @@ "Device_TableHead_SyncHubNodeName": "Nodo de sincronización", "Device_TableHead_Type": "Tipo", "Device_TableHead_Vendor": "Fabricante", + "Device_TableHead_Vlan": "", "Device_Table_Not_Network_Device": "No está configurado como dispositivo de red", "Device_Table_info": "Mostrando el INICIO y el FINAL de TODAS las entradas", "Device_Table_nav_next": "Siguiente", diff --git a/front/php/templates/language/fa_fa.json b/front/php/templates/language/fa_fa.json index 8fe5e7be..3ed24919 100644 --- a/front/php/templates/language/fa_fa.json +++ b/front/php/templates/language/fa_fa.json @@ -226,6 +226,8 @@ "Device_TableHead_FirstSession": "", "Device_TableHead_GUID": "", "Device_TableHead_Group": "", + "Device_TableHead_IPv4": "", + "Device_TableHead_IPv6": "", "Device_TableHead_Icon": "", "Device_TableHead_LastIP": "", "Device_TableHead_LastIPOrder": "", @@ -249,6 +251,7 @@ "Device_TableHead_SyncHubNodeName": "", "Device_TableHead_Type": "", "Device_TableHead_Vendor": "", + "Device_TableHead_Vlan": "", "Device_Table_Not_Network_Device": "", "Device_Table_info": "", "Device_Table_nav_next": "", diff --git a/front/php/templates/language/fr_fr.json b/front/php/templates/language/fr_fr.json index ed5045a7..e32181e0 100644 --- a/front/php/templates/language/fr_fr.json +++ b/front/php/templates/language/fr_fr.json @@ -226,6 +226,8 @@ "Device_TableHead_FirstSession": "Première session", "Device_TableHead_GUID": "GUID", "Device_TableHead_Group": "Groupe", + "Device_TableHead_IPv4": "", + "Device_TableHead_IPv6": "", "Device_TableHead_Icon": "Icône", "Device_TableHead_LastIP": "Dernière IP", "Device_TableHead_LastIPOrder": "Ordre dernière IP", @@ -249,6 +251,7 @@ "Device_TableHead_SyncHubNodeName": "Noeud de synchro", "Device_TableHead_Type": "Type", "Device_TableHead_Vendor": "Fabriquant", + "Device_TableHead_Vlan": "", "Device_Table_Not_Network_Device": "Non configuré comme appareil du réseau", "Device_Table_info": "Affiche de _START_ à _END_ sur _TOTAL_ entrées", "Device_Table_nav_next": "Suivant", @@ -783,4 +786,4 @@ "settings_system_label": "Système", "settings_update_item_warning": "Mettre à jour la valeur ci-dessous. Veillez à bien suivre le même format qu'auparavant. Il n'y a pas de pas de contrôle.", "test_event_tooltip": "Enregistrer d'abord vos modifications avant de tester vôtre paramétrage." -} +} \ No newline at end of file diff --git a/front/php/templates/language/it_it.json b/front/php/templates/language/it_it.json index 9388f4c4..8ababebf 100644 --- a/front/php/templates/language/it_it.json +++ b/front/php/templates/language/it_it.json @@ -226,6 +226,8 @@ "Device_TableHead_FirstSession": "Prima sessione", "Device_TableHead_GUID": "GUID", "Device_TableHead_Group": "Gruppo", + "Device_TableHead_IPv4": "", + "Device_TableHead_IPv6": "", "Device_TableHead_Icon": "Icona", "Device_TableHead_LastIP": "Ultimo IP", "Device_TableHead_LastIPOrder": "Ordina per ultimo IP", @@ -249,6 +251,7 @@ "Device_TableHead_SyncHubNodeName": "Sincronizza nodo", "Device_TableHead_Type": "Tipo", "Device_TableHead_Vendor": "Produttore", + "Device_TableHead_Vlan": "", "Device_Table_Not_Network_Device": "Non configurato come dispositivo di rete", "Device_Table_info": "Visualizzazione da _START_ a _END_ di _TOTAL_ voci", "Device_Table_nav_next": "Successivo", @@ -783,4 +786,4 @@ "settings_system_label": "Sistema", "settings_update_item_warning": "Aggiorna il valore qui sotto. Fai attenzione a seguire il formato precedente. La convalida non viene eseguita.", "test_event_tooltip": "Salva le modifiche prima di provare le nuove impostazioni." -} +} \ No newline at end of file diff --git a/front/php/templates/language/ja_jp.json b/front/php/templates/language/ja_jp.json index e796cc39..e5dece69 100644 --- a/front/php/templates/language/ja_jp.json +++ b/front/php/templates/language/ja_jp.json @@ -226,6 +226,8 @@ "Device_TableHead_FirstSession": "初回セッション", "Device_TableHead_GUID": "GUID", "Device_TableHead_Group": "グループ", + "Device_TableHead_IPv4": "", + "Device_TableHead_IPv6": "", "Device_TableHead_Icon": "アイコン", "Device_TableHead_LastIP": "直近のIP", "Device_TableHead_LastIPOrder": "直近のIP順", @@ -249,6 +251,7 @@ "Device_TableHead_SyncHubNodeName": "同期ノード", "Device_TableHead_Type": "種別", "Device_TableHead_Vendor": "ベンダー", + "Device_TableHead_Vlan": "", "Device_Table_Not_Network_Device": "ネットワーク機器として構成されていない", "Device_Table_info": "_START_~_END_を表示 / _TOTAL_ 件中", "Device_Table_nav_next": "次", diff --git a/front/php/templates/language/nb_no.json b/front/php/templates/language/nb_no.json index aba15936..7b1d40aa 100644 --- a/front/php/templates/language/nb_no.json +++ b/front/php/templates/language/nb_no.json @@ -226,6 +226,8 @@ "Device_TableHead_FirstSession": "Første Økt", "Device_TableHead_GUID": "GUID", "Device_TableHead_Group": "Gruppe", + "Device_TableHead_IPv4": "", + "Device_TableHead_IPv6": "", "Device_TableHead_Icon": "Ikon", "Device_TableHead_LastIP": "Siste IP", "Device_TableHead_LastIPOrder": "Siste IP Rekkefølge", @@ -249,6 +251,7 @@ "Device_TableHead_SyncHubNodeName": "Synkroniser Node", "Device_TableHead_Type": "Type", "Device_TableHead_Vendor": "Leverandør", + "Device_TableHead_Vlan": "", "Device_Table_Not_Network_Device": "Ikke konfigurert som en nettverksenhet", "Device_Table_info": "Showing _START_ to _END_ of _TOTAL_ entries", "Device_Table_nav_next": "Neste", diff --git a/front/php/templates/language/pl_pl.json b/front/php/templates/language/pl_pl.json index f6896615..14d8fbe5 100644 --- a/front/php/templates/language/pl_pl.json +++ b/front/php/templates/language/pl_pl.json @@ -226,6 +226,8 @@ "Device_TableHead_FirstSession": "Pierwsza sesja", "Device_TableHead_GUID": "GUID", "Device_TableHead_Group": "Grupa", + "Device_TableHead_IPv4": "", + "Device_TableHead_IPv6": "", "Device_TableHead_Icon": "Ikona", "Device_TableHead_LastIP": "Ostatni adres IP", "Device_TableHead_LastIPOrder": "Kolejność ostatniego adresu IP", @@ -249,6 +251,7 @@ "Device_TableHead_SyncHubNodeName": "Węzeł synchronizacji", "Device_TableHead_Type": "Typ", "Device_TableHead_Vendor": "Producent", + "Device_TableHead_Vlan": "", "Device_Table_Not_Network_Device": "Nie skonfigurowano jako urządzenie sieciowe", "Device_Table_info": "Pokazuje _START_ do _END_ z _TOTAL_ wpisów", "Device_Table_nav_next": "Następna", diff --git a/front/php/templates/language/pt_br.json b/front/php/templates/language/pt_br.json index 8e3dae21..02244aea 100644 --- a/front/php/templates/language/pt_br.json +++ b/front/php/templates/language/pt_br.json @@ -226,6 +226,8 @@ "Device_TableHead_FirstSession": "Primeira sessão", "Device_TableHead_GUID": "GUID", "Device_TableHead_Group": "Grupo", + "Device_TableHead_IPv4": "", + "Device_TableHead_IPv6": "", "Device_TableHead_Icon": "Ícone", "Device_TableHead_LastIP": "Último IP", "Device_TableHead_LastIPOrder": "Último pedido de IP", @@ -249,6 +251,7 @@ "Device_TableHead_SyncHubNodeName": "Nó de sincronização", "Device_TableHead_Type": "Tipo", "Device_TableHead_Vendor": "Fornecedor", + "Device_TableHead_Vlan": "", "Device_Table_Not_Network_Device": "Não configurado como um dispositivo de rede", "Device_Table_info": "Mostrando _START_ de _END_ do _TOTAL_ entradas", "Device_Table_nav_next": "Próximo", diff --git a/front/php/templates/language/pt_pt.json b/front/php/templates/language/pt_pt.json index c3ad2246..687a9009 100644 --- a/front/php/templates/language/pt_pt.json +++ b/front/php/templates/language/pt_pt.json @@ -226,6 +226,8 @@ "Device_TableHead_FirstSession": "Primeira sessão", "Device_TableHead_GUID": "GUID", "Device_TableHead_Group": "Grupo", + "Device_TableHead_IPv4": "", + "Device_TableHead_IPv6": "", "Device_TableHead_Icon": "Ícone", "Device_TableHead_LastIP": "Último IP", "Device_TableHead_LastIPOrder": "Último pedido de IP", @@ -249,6 +251,7 @@ "Device_TableHead_SyncHubNodeName": "Nó de sincronização", "Device_TableHead_Type": "Tipo", "Device_TableHead_Vendor": "Fornecedor", + "Device_TableHead_Vlan": "", "Device_Table_Not_Network_Device": "Não configurado como um dispositivo de rede", "Device_Table_info": "A mostrar _START_ to _END_ of _TOTAL_ entradas", "Device_Table_nav_next": "Próximo", diff --git a/front/php/templates/language/ru_ru.json b/front/php/templates/language/ru_ru.json index 6d8c48e4..dd868dbe 100644 --- a/front/php/templates/language/ru_ru.json +++ b/front/php/templates/language/ru_ru.json @@ -226,6 +226,8 @@ "Device_TableHead_FirstSession": "Первый сеанс", "Device_TableHead_GUID": "GUID", "Device_TableHead_Group": "Группа", + "Device_TableHead_IPv4": "", + "Device_TableHead_IPv6": "", "Device_TableHead_Icon": "Значок", "Device_TableHead_LastIP": "Последний IP", "Device_TableHead_LastIPOrder": "Последний IP-запрос", @@ -249,6 +251,7 @@ "Device_TableHead_SyncHubNodeName": "Узел синхронизации", "Device_TableHead_Type": "Тип", "Device_TableHead_Vendor": "Поставщик", + "Device_TableHead_Vlan": "", "Device_Table_Not_Network_Device": "Не настроено как сетевое устройство", "Device_Table_info": "Показаны с _START_ по _END_ из _TOTAL_ записей", "Device_Table_nav_next": "Следующая", diff --git a/front/php/templates/language/sv_sv.json b/front/php/templates/language/sv_sv.json index f76e6729..ae8afffd 100644 --- a/front/php/templates/language/sv_sv.json +++ b/front/php/templates/language/sv_sv.json @@ -226,6 +226,8 @@ "Device_TableHead_FirstSession": "", "Device_TableHead_GUID": "", "Device_TableHead_Group": "", + "Device_TableHead_IPv4": "", + "Device_TableHead_IPv6": "", "Device_TableHead_Icon": "", "Device_TableHead_LastIP": "", "Device_TableHead_LastIPOrder": "", @@ -249,6 +251,7 @@ "Device_TableHead_SyncHubNodeName": "", "Device_TableHead_Type": "", "Device_TableHead_Vendor": "", + "Device_TableHead_Vlan": "", "Device_Table_Not_Network_Device": "", "Device_Table_info": "", "Device_Table_nav_next": "", diff --git a/front/php/templates/language/tr_tr.json b/front/php/templates/language/tr_tr.json index e96496ba..ad873c21 100644 --- a/front/php/templates/language/tr_tr.json +++ b/front/php/templates/language/tr_tr.json @@ -226,6 +226,8 @@ "Device_TableHead_FirstSession": "İlk Oturum", "Device_TableHead_GUID": "GUID", "Device_TableHead_Group": "Grup", + "Device_TableHead_IPv4": "", + "Device_TableHead_IPv6": "", "Device_TableHead_Icon": "İkon", "Device_TableHead_LastIP": "Son IP", "Device_TableHead_LastIPOrder": "Son IP Sırası", @@ -249,6 +251,7 @@ "Device_TableHead_SyncHubNodeName": "Senkronizasyon Node", "Device_TableHead_Type": "Tür", "Device_TableHead_Vendor": "Üretici", + "Device_TableHead_Vlan": "", "Device_Table_Not_Network_Device": "Ağ cihazı olarak ayarlanmadı", "Device_Table_info": "Showing _START_ to _END_ of _TOTAL_ entries", "Device_Table_nav_next": "Sonraki", diff --git a/front/php/templates/language/uk_ua.json b/front/php/templates/language/uk_ua.json index 0a3ed72e..78006829 100644 --- a/front/php/templates/language/uk_ua.json +++ b/front/php/templates/language/uk_ua.json @@ -226,6 +226,8 @@ "Device_TableHead_FirstSession": "Перша сесія", "Device_TableHead_GUID": "GUID", "Device_TableHead_Group": "Група", + "Device_TableHead_IPv4": "", + "Device_TableHead_IPv6": "", "Device_TableHead_Icon": "Значок", "Device_TableHead_LastIP": "Останній IP", "Device_TableHead_LastIPOrder": "Останнє замовлення IP", @@ -249,6 +251,7 @@ "Device_TableHead_SyncHubNodeName": "Вузол синхронізації", "Device_TableHead_Type": "Тип", "Device_TableHead_Vendor": "Продавець", + "Device_TableHead_Vlan": "", "Device_Table_Not_Network_Device": "Не налаштовано як мережевий пристрій", "Device_Table_info": "Показано від _START_ до _END_ із _TOTAL_ записів", "Device_Table_nav_next": "Далі", @@ -783,4 +786,4 @@ "settings_system_label": "Система", "settings_update_item_warning": "Оновіть значення нижче. Слідкуйте за попереднім форматом. Перевірка не виконана.", "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 dae234b5..1139be4b 100644 --- a/front/php/templates/language/zh_cn.json +++ b/front/php/templates/language/zh_cn.json @@ -226,6 +226,8 @@ "Device_TableHead_FirstSession": "加入", "Device_TableHead_GUID": "GUID", "Device_TableHead_Group": "组", + "Device_TableHead_IPv4": "", + "Device_TableHead_IPv6": "", "Device_TableHead_Icon": "图标", "Device_TableHead_LastIP": "上次 IP", "Device_TableHead_LastIPOrder": "上次 IP 排序", @@ -249,6 +251,7 @@ "Device_TableHead_SyncHubNodeName": "同步节点", "Device_TableHead_Type": "类型", "Device_TableHead_Vendor": "制造商", + "Device_TableHead_Vlan": "", "Device_Table_Not_Network_Device": "未配置为网络设备", "Device_Table_info": "显示第_START_至 END_条_共_TOTAL_条", "Device_Table_nav_next": "下一页", diff --git a/front/plugins/ui_settings/config.json b/front/plugins/ui_settings/config.json index f4f5c1de..5ca917bd 100755 --- a/front/plugins/ui_settings/config.json +++ b/front/plugins/ui_settings/config.json @@ -440,7 +440,9 @@ "Device_TableHead_CustomProps", "Device_TableHead_FQDN", "Device_TableHead_ParentRelType", - "Device_TableHead_ReqNicsOnline" + "Device_TableHead_ReqNicsOnline", + "Device_TableHead_IPv6", + "Device_TableHead_IPv4" ], "localized": ["name", "description"], "name": [ @@ -517,7 +519,8 @@ "Device_TableHead_NetworkSite", "Device_TableHead_SSID", "Device_TableHead_SourcePlugin", - "Device_TableHead_ParentRelType" + "Device_TableHead_ParentRelType", + "Device_TableHead_Vlan" ], "localized": ["name", "description"], "name": [ diff --git a/server/db/authoritative_handler.py b/server/db/authoritative_handler.py index 1c8b9e27..444170ac 100644 --- a/server/db/authoritative_handler.py +++ b/server/db/authoritative_handler.py @@ -448,4 +448,3 @@ def unlock_fields(conn, mac=None, fields=None, clear_all=False): } finally: conn.close() - diff --git a/server/scan/device_handling.py b/server/scan/device_handling.py index 797ed9ff..972ac25f 100755 --- a/server/scan/device_handling.py +++ b/server/scan/device_handling.py @@ -110,6 +110,7 @@ FIELD_SPECS = { "source_col": "devNameSource", "empty_values": ["", "null", "(unknown)", "(name not found)"], "priority": ["NSLOOKUP", "AVAHISCAN", "NBTSCAN", "DIGSCAN", "ARPSCAN", "DHCPLSS", "NEWDEV", "N/A"], + "allow_override_if_changed": True, }, # ========================================================== From 2211419c5b4a792ea10d34895c8e9b622287fd04 Mon Sep 17 00:00:00 2001 From: Anonymous Date: Wed, 28 Jan 2026 23:47:49 +0100 Subject: [PATCH 03/15] Translated using Weblate (Arabic) Currently translated at 87.3% (685 of 784 strings) Translation: NetAlertX/core Translate-URL: https://hosted.weblate.org/projects/pialert/core/ar/ --- front/php/templates/language/ar_ar.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/php/templates/language/ar_ar.json b/front/php/templates/language/ar_ar.json index e97400ba..07053382 100644 --- a/front/php/templates/language/ar_ar.json +++ b/front/php/templates/language/ar_ar.json @@ -783,4 +783,4 @@ "settings_system_label": "نظام", "settings_update_item_warning": "قم بتحديث القيمة أدناه. احرص على اتباع التنسيق السابق. لم يتم إجراء التحقق.", "test_event_tooltip": "احفظ التغييرات أولاً قبل اختبار الإعدادات." -} \ No newline at end of file +} From 17e563aa29de9693df174dfb03c672b9db25f668 Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Fri, 30 Jan 2026 08:34:55 +1100 Subject: [PATCH 04/15] BE+FE: new fields handling in views and filters Signed-off-by: jokob-sk --- front/devices.php | 13 ++++- front/plugins/newdev_template/config.json | 64 +++++++++++------------ front/plugins/ui_settings/config.json | 5 +- server/api_server/graphql_endpoint.py | 3 ++ 4 files changed, 49 insertions(+), 36 deletions(-) diff --git a/front/devices.php b/front/devices.php index f6c374b1..61d91020 100755 --- a/front/devices.php +++ b/front/devices.php @@ -543,7 +543,10 @@ function mapColumnIndexToFieldName(index, tableColumnVisible) { "devCustomProps", // 26 "devFQDN", // 27 "devParentRelType", // 28 - "devReqNicsOnline" // 29 + "devReqNicsOnline", // 29 + "devVlan", // 30 + "devPrimaryIPv4", // 31 + "devPrimaryIPv6", // 32 ]; // console.log("OrderBy: " + columnNames[tableColumnOrder[index]]); @@ -660,6 +663,9 @@ function initializeDatatable (status) { devFQDN devParentRelType devReqNicsOnline + devVlan + devPrimaryIPv4 + devPrimaryIPv6 } count } @@ -743,7 +749,10 @@ function initializeDatatable (status) { device.devCustomProps || "", device.devFQDN || "", device.devParentRelType || "", - device.devReqNicsOnline || 0 + device.devReqNicsOnline || 0, + device.devVlan || "", + device.devPrimaryIPv4 || "", + device.devPrimaryIPv6 || "", ]; const newRow = []; diff --git a/front/plugins/newdev_template/config.json b/front/plugins/newdev_template/config.json index 0f01158d..22de174f 100755 --- a/front/plugins/newdev_template/config.json +++ b/front/plugins/newdev_template/config.json @@ -1588,6 +1588,38 @@ } ] }, + { + "function": "devVlan", + "type": { + "dataType": "string", + "elements": [ + { + "elementType": "input", + "elementOptions": [], + "transformers": [] + } + ] + }, + "maxLength": 50, + "default_value": "", + "options": [], + "localized": [ + "name", + "description" + ], + "name": [ + { + "language_code": "en_us", + "string": "VLAN" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "The VLAN identifier or name the device belongs to. Database column name: devVlan." + } + ] + }, { "function": "devSyncHubNode", "type": { @@ -1901,38 +1933,6 @@ } ] }, - { - "function": "devVlan", - "type": { - "dataType": "string", - "elements": [ - { - "elementType": "input", - "elementOptions": [], - "transformers": [] - } - ] - }, - "maxLength": 50, - "default_value": "", - "options": [], - "localized": [ - "name", - "description" - ], - "name": [ - { - "language_code": "en_us", - "string": "VLAN" - } - ], - "description": [ - { - "language_code": "en_us", - "string": "The VLAN identifier or name the device belongs to. Database column name: devVlan." - } - ] - }, { "function": "devForceStatus", "type": { diff --git a/front/plugins/ui_settings/config.json b/front/plugins/ui_settings/config.json index 5ca917bd..18cee2cb 100755 --- a/front/plugins/ui_settings/config.json +++ b/front/plugins/ui_settings/config.json @@ -441,8 +441,9 @@ "Device_TableHead_FQDN", "Device_TableHead_ParentRelType", "Device_TableHead_ReqNicsOnline", - "Device_TableHead_IPv6", - "Device_TableHead_IPv4" + "Device_TableHead_Vlan", + "Device_TableHead_IPv4", + "Device_TableHead_IPv6" ], "localized": ["name", "description"], "name": [ diff --git a/server/api_server/graphql_endpoint.py b/server/api_server/graphql_endpoint.py index 94c5c624..0f99dc33 100755 --- a/server/api_server/graphql_endpoint.py +++ b/server/api_server/graphql_endpoint.py @@ -339,6 +339,9 @@ class Query(ObjectType): "devFQDN", "devParentRelType", "devParentMAC", + "devVlan", + "devPrimaryIPv4", + "devPrimaryIPv6" ] search_term = options.search.lower() From 2d6e357fe5dc6357275972d6888a55a5f66c8cdc Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Fri, 30 Jan 2026 09:09:39 +1100 Subject: [PATCH 05/15] BE+FE: new fields handling in views and skipping device heuristics for random macs Signed-off-by: jokob-sk --- front/devices.php | 14 -------------- server/helper.py | 20 -------------------- server/scan/device_heuristics.py | 9 ++++++++- 3 files changed, 8 insertions(+), 35 deletions(-) diff --git a/front/devices.php b/front/devices.php index 61d91020..1655bf28 100755 --- a/front/devices.php +++ b/front/devices.php @@ -1,17 +1,3 @@ - - bool: -# """Determine if a MAC address is random, respecting user-defined prefixes not to mark as random.""" - -# is_random = mac[1].upper() in ["2", "6", "A", "E"] - -# # Get prefixes from settings -# prefixes = get_setting_value("UI_NOT_RANDOM_MAC") - -# # If detected as random, make sure it doesn't start with a prefix the user wants to exclude -# if is_random: -# for prefix in prefixes: -# if mac.upper().startswith(prefix.upper()): -# is_random = False -# break - -# return is_random - - # ------------------------------------------------------------------------------------------- def generate_mac_links(html, deviceUrl): p = re.compile(r"(?:[0-9a-fA-F]:?){12}") diff --git a/server/scan/device_heuristics.py b/server/scan/device_heuristics.py index 15f9a0ad..2fda4ff4 100755 --- a/server/scan/device_heuristics.py +++ b/server/scan/device_heuristics.py @@ -5,6 +5,7 @@ import base64 from pathlib import Path from typing import Optional, Tuple from logger import mylog +from helpers import is_random_mac # Register NetAlertX directories INSTALL_PATH = os.getenv("NETALERTX_APP", "/app") @@ -176,6 +177,12 @@ def guess_device_attributes( name = str(name).lower().strip() if name else "(unknown)" mac_clean = mac.replace(":", "").replace("-", "").upper() + # --- Check for Random MAC --- + # If the MAC is randomized (private), skip vendor/heuristics assignment + if is_random_mac(mac): + mylog("debug", f"[guess_device_attributes] Random MAC detected ({mac}); returning defaults to avoid incorrect assignment.") + return default_icon, default_type + # # Internet shortcut # if mac == "INTERNET": # return ICONS.get("globe", default_icon), DEVICE_TYPES.get("Internet", default_type) @@ -261,4 +268,4 @@ def guess_type( # Handler for when this is run as a program instead of called as a module. if __name__ == "__main__": - mylog("error", "This module is not intended to be run directly.") + mylog("none", "This module is not intended to be run directly.") From fa40880c05d561e0478d5da7599bd87524bf93c3 Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Fri, 30 Jan 2026 09:48:08 +1100 Subject: [PATCH 06/15] heuristics fix Signed-off-by: jokob-sk --- server/scan/device_heuristics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/scan/device_heuristics.py b/server/scan/device_heuristics.py index 2fda4ff4..afce2f72 100755 --- a/server/scan/device_heuristics.py +++ b/server/scan/device_heuristics.py @@ -5,7 +5,7 @@ import base64 from pathlib import Path from typing import Optional, Tuple from logger import mylog -from helpers import is_random_mac +from helper import is_random_mac # Register NetAlertX directories INSTALL_PATH = os.getenv("NETALERTX_APP", "/app") From a868a7ed8e6252e81b7ac2d481c2107c504b69ad Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Fri, 30 Jan 2026 11:54:32 +1100 Subject: [PATCH 07/15] DOCS+FE: re-adding old copyright header Signed-off-by: jokob-sk --- front/devices.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/front/devices.php b/front/devices.php index 1655bf28..61d91020 100755 --- a/front/devices.php +++ b/front/devices.php @@ -1,3 +1,17 @@ + + Date: Fri, 30 Jan 2026 18:12:43 +1100 Subject: [PATCH 08/15] better heuristics Signed-off-by: jokob-sk --- back/device_heuristics_rules.json | 36 ++++++++++++++++++++----------- server/scan/device_heuristics.py | 20 ++++++++--------- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/back/device_heuristics_rules.json b/back/device_heuristics_rules.json index 419c3da1..3ff278cd 100755 --- a/back/device_heuristics_rules.json +++ b/back/device_heuristics_rules.json @@ -7,6 +7,24 @@ ], "name_pattern": [] }, + { + "dev_type": "Smart Switch", + "icon_html": "", + "matching_pattern": [ + { "mac_prefix": "003192", "vendor": "TP-Link" }, + { "mac_prefix": "50C7BF", "vendor": "TP-Link" }, + { "mac_prefix": "B04E26", "vendor": "TP-Link" } + ], + "name_pattern": ["hs200", "hs210", "hs220", "ks230", "smart switch", "light switch", "wall switch"] + }, + { + "dev_type": "Smart Plug", + "icon_html": "", + "matching_pattern": [ + { "mac_prefix": "2887BA", "vendor": "TP-Link" } + ], + "name_pattern": ["kp115", "hs100", "hs103", "hs105", "smart plug", "outlet"] + }, { "dev_type": "Access Point", "icon_html": "", @@ -16,15 +34,16 @@ { "mac_prefix": "F4F5D8", "vendor": "TP-Link" }, { "mac_prefix": "F88E85", "vendor": "Netgear" } ], - "name_pattern": ["router", "gateway", "ap", "access point", "access-point", "switch"] + "name_pattern": ["router", "gateway", "ap", "access point", "access-point", "switch", "sg105", "sg108", "managed switch", "unmanaged switch", "poe switch", "ethernet switch"] }, { "dev_type": "Phone", - "icon_html": "", + "icon_html": "", "matching_pattern": [ { "mac_prefix": "001A79", "vendor": "Apple" }, { "mac_prefix": "B0BE83", "vendor": "Samsung" }, - { "mac_prefix": "BC926B", "vendor": "Motorola" } + { "mac_prefix": "BC926B", "vendor": "Motorola" }, + { "mac_prefix": "", "vendor": "google" } ], "name_pattern": ["iphone", "ipad", "pixel", "galaxy", "redmi"] }, @@ -43,7 +62,7 @@ { "mac_prefix": "BC4C4C", "vendor": "Samsung" } ], "name_pattern": ["tablet", "pad"] - }, + }, { "dev_type": "IoT", "icon_html": "", @@ -164,7 +183,7 @@ "dev_type": "Smart Light", "icon_html": "", "matching_pattern": [], - "name_pattern": ["hue", "lifx", "bulb"] + "name_pattern": ["hue", "lifx", "bulb", "light"] }, { "dev_type": "Smart Home", @@ -189,12 +208,5 @@ "icon_html": "", "matching_pattern": [], "name_pattern": ["doorbell", "lock", "security"] - }, - { - "dev_type": "Smart Light", - "icon_html": "", - "matching_pattern": [ - ], - "name_pattern": ["light","bulb"] } ] diff --git a/server/scan/device_heuristics.py b/server/scan/device_heuristics.py index afce2f72..b9c14520 100755 --- a/server/scan/device_heuristics.py +++ b/server/scan/device_heuristics.py @@ -177,12 +177,6 @@ def guess_device_attributes( name = str(name).lower().strip() if name else "(unknown)" mac_clean = mac.replace(":", "").replace("-", "").upper() - # --- Check for Random MAC --- - # If the MAC is randomized (private), skip vendor/heuristics assignment - if is_random_mac(mac): - mylog("debug", f"[guess_device_attributes] Random MAC detected ({mac}); returning defaults to avoid incorrect assignment.") - return default_icon, default_type - # # Internet shortcut # if mac == "INTERNET": # return ICONS.get("globe", default_icon), DEVICE_TYPES.get("Internet", default_type) @@ -190,17 +184,21 @@ def guess_device_attributes( type_ = None icon = None - # --- Strict MAC + vendor rule matching from external file --- + # 1. Try strict MAC match first type_, icon = match_mac_and_vendor(mac_clean, vendor, default_type, default_icon) + # 2. If no strict match, try Name match BEFORE checking for random MAC + if not type_ or type_ == default_type: + type_, icon = match_name(name, default_type, default_icon) + + # 3. Only if it's STILL not found, apply the Random MAC block + if type_ == default_type and is_random_mac(mac): + return default_icon, default_type + # --- Loose Vendor-based fallback --- if not type_ or type_ == default_type: type_, icon = match_vendor(vendor, default_type, default_icon) - # --- Loose Name-based fallback --- - if not type_ or type_ == default_type: - type_, icon = match_name(name, default_type, default_icon) - # --- Loose IP-based fallback --- if (not type_ or type_ == default_type) or (not icon or icon == default_icon): type_, icon = match_ip(ip, default_type, default_icon) From e1059b69375b308f7dbd2710ac459603378abe0e Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Fri, 30 Jan 2026 18:21:48 +1100 Subject: [PATCH 09/15] DOCS: icon fix Signed-off-by: jokob-sk --- docs/img/netalertx_docs.png | Bin 8107 -> 8202 bytes front/img/svg/netalertx_blue_docs.svg | 453 ++++++++++++++++++++++++++ 2 files changed, 453 insertions(+) create mode 100644 front/img/svg/netalertx_blue_docs.svg diff --git a/docs/img/netalertx_docs.png b/docs/img/netalertx_docs.png index 27a0b6617f55e72e0e61ec0789d7fd367c4a0097..39914c59243f601036403e326886c65ad012f4c9 100755 GIT binary patch delta 8178 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 Y|8^pWf2KoQwEzGB07*qoM6N<$g4*K$RsaA1 delta 8083 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 diff --git a/front/img/svg/netalertx_blue_docs.svg b/front/img/svg/netalertx_blue_docs.svg new file mode 100644 index 00000000..77710b9b --- /dev/null +++ b/front/img/svg/netalertx_blue_docs.svg @@ -0,0 +1,453 @@ + + + + + + + + + + + + + + + A + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From ad6c3fe176a1dd2e20e077bcdd76b1456ed7d45f Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Fri, 30 Jan 2026 18:26:51 +1100 Subject: [PATCH 10/15] better heuristics Signed-off-by: jokob-sk --- back/device_heuristics_rules.json | 87 ++++++++++++++----------------- 1 file changed, 38 insertions(+), 49 deletions(-) diff --git a/back/device_heuristics_rules.json b/back/device_heuristics_rules.json index 3ff278cd..996c42d8 100755 --- a/back/device_heuristics_rules.json +++ b/back/device_heuristics_rules.json @@ -23,7 +23,33 @@ "matching_pattern": [ { "mac_prefix": "2887BA", "vendor": "TP-Link" } ], - "name_pattern": ["kp115", "hs100", "hs103", "hs105", "smart plug", "outlet"] + "name_pattern": ["kp115", "hs100", "hs103", "hs105", "smart plug", "outlet", "plug"] + }, + { + "dev_type": "Smart Speaker", + "icon_html": "", + "matching_pattern": [ + { "mac_prefix": "14C14E", "vendor": "Google" }, + { "mac_prefix": "44650D", "vendor": "Amazon" }, + { "mac_prefix": "74ACB9", "vendor": "Google" } + ], + "name_pattern": ["echo", "alexa", "dot", "nest-audio", "nest-mini", "google-home"] + }, + { + "dev_type": "Smart Home", + "icon_html": "", + "matching_pattern": [], + "name_pattern": ["google", "chromecast", "nest", "hub"] + }, + { + "dev_type": "Phone", + "icon_html": "", + "matching_pattern": [ + { "mac_prefix": "001A79", "vendor": "Apple" }, + { "mac_prefix": "B0BE83", "vendor": "Samsung" }, + { "mac_prefix": "BC926B", "vendor": "Motorola" } + ], + "name_pattern": ["iphone", "ipad", "pixel", "galaxy", "redmi", "android", "samsung"] }, { "dev_type": "Access Point", @@ -36,24 +62,6 @@ ], "name_pattern": ["router", "gateway", "ap", "access point", "access-point", "switch", "sg105", "sg108", "managed switch", "unmanaged switch", "poe switch", "ethernet switch"] }, - { - "dev_type": "Phone", - "icon_html": "", - "matching_pattern": [ - { "mac_prefix": "001A79", "vendor": "Apple" }, - { "mac_prefix": "B0BE83", "vendor": "Samsung" }, - { "mac_prefix": "BC926B", "vendor": "Motorola" }, - { "mac_prefix": "", "vendor": "google" } - ], - "name_pattern": ["iphone", "ipad", "pixel", "galaxy", "redmi"] - }, - { - "dev_type": "Phone", - "icon_html": "", - "matching_pattern": [ - ], - "name_pattern": ["android","samsung"] - }, { "dev_type": "Tablet", "icon_html": "", @@ -63,24 +71,18 @@ ], "name_pattern": ["tablet", "pad"] }, - { - "dev_type": "IoT", - "icon_html": "", - "matching_pattern": [ - { "mac_prefix": "B827EB", "vendor": "Raspberry Pi" }, - { "mac_prefix": "DCA632", "vendor": "Raspberry Pi" } - ], - "name_pattern": ["raspberry", "pi"] - }, { "dev_type": "IoT", "icon_html": "", "matching_pattern": [ + { "mac_prefix": "B827EB", "vendor": "Raspberry Pi" }, + { "mac_prefix": "DCA632", "vendor": "Raspberry Pi" }, { "mac_prefix": "840D8E", "vendor": "Espressif" }, { "mac_prefix": "ECFABC", "vendor": "Espressif" }, - { "mac_prefix": "7C9EBD", "vendor": "Espressif" } + { "mac_prefix": "7C9EBD", "vendor": "Espressif" }, + { "mac_prefix": "286DCD", "vendor": "Beijing Winner Microelectronics" } ], - "name_pattern": ["raspberry", "pi"] + "name_pattern": ["raspberry", "pi", "thingsturn", "w600", "w601"] }, { "dev_type": "Desktop", @@ -157,15 +159,6 @@ ], "name_pattern": ["camera", "cam", "webcam"] }, - { - "dev_type": "Smart Speaker", - "icon_html": "", - "matching_pattern": [ - { "mac_prefix": "44650D", "vendor": "Amazon" }, - { "mac_prefix": "74ACB9", "vendor": "Google" } - ], - "name_pattern": ["echo", "alexa", "dot"] - }, { "dev_type": "Router", "icon_html": "", @@ -173,7 +166,7 @@ { "mac_prefix": "000C29", "vendor": "Cisco" }, { "mac_prefix": "00155D", "vendor": "MikroTik" } ], - "name_pattern": ["router", "gateway", "ap", "access point", "access-point"], + "name_pattern": ["router", "gateway", "ap", "access point"], "ip_pattern": [ "^192\\.168\\.[0-1]\\.1$", "^10\\.0\\.0\\.1$" @@ -185,12 +178,6 @@ "matching_pattern": [], "name_pattern": ["hue", "lifx", "bulb", "light"] }, - { - "dev_type": "Smart Home", - "icon_html": "", - "matching_pattern": [], - "name_pattern": ["google", "chromecast", "nest"] - }, { "dev_type": "Smartwatch", "icon_html": "", @@ -206,7 +193,9 @@ { "dev_type": "Security Device", "icon_html": "", - "matching_pattern": [], - "name_pattern": ["doorbell", "lock", "security"] + "matching_pattern": [ + { "mac_prefix": "047BCB", "vendor": "Universal Global Scientific" } + ], + "name_pattern": ["doorbell", "lock", "security", "mmd-", "ring"] } -] +] \ No newline at end of file From 405c1c37cbaf01238fe785fcb040e56085daa91d Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Fri, 30 Jan 2026 19:17:38 +1100 Subject: [PATCH 11/15] better heuristics Signed-off-by: jokob-sk --- back/device_heuristics_rules.json | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/back/device_heuristics_rules.json b/back/device_heuristics_rules.json index 996c42d8..bd14df81 100755 --- a/back/device_heuristics_rules.json +++ b/back/device_heuristics_rules.json @@ -35,6 +35,14 @@ ], "name_pattern": ["echo", "alexa", "dot", "nest-audio", "nest-mini", "google-home"] }, + { + "dev_type": "Smart Appliance", + "icon_html": "", + "matching_pattern": [ + { "mac_prefix": "446FF8", "vendor": "Dyson" } + ], + "name_pattern": ["dyson", "purifier", "humidifier", "fan"] + }, { "dev_type": "Smart Home", "icon_html": "", @@ -90,9 +98,11 @@ "matching_pattern": [ { "mac_prefix": "001422", "vendor": "Dell" }, { "mac_prefix": "001874", "vendor": "Lenovo" }, - { "mac_prefix": "00E04C", "vendor": "Hewlett Packard" } + { "mac_prefix": "00E04C", "vendor": "Hewlett Packard" }, + { "mac_prefix": "F44D30", "vendor": "Elitegroup Computer Systems" }, + { "mac_prefix": "1C697A", "vendor": "Elitegroup Computer Systems" } ], - "name_pattern": ["desktop", "pc", "computer"] + "name_pattern": ["desktop", "pc", "computer", "liva", "ecs"] }, { "dev_type": "Laptop", @@ -101,9 +111,10 @@ { "mac_prefix": "3C0754", "vendor": "HP" }, { "mac_prefix": "0017A4", "vendor": "Dell" }, { "mac_prefix": "F4CE46", "vendor": "Lenovo" }, - { "mac_prefix": "409F38", "vendor": "Acer" } + { "mac_prefix": "409F38", "vendor": "Acer" }, + { "mac_prefix": "9CB6D0", "vendor": "Rivet Networks" } ], - "name_pattern": ["macbook", "imac", "laptop", "notebook"] + "name_pattern": ["macbook", "imac", "laptop", "notebook", "alienware", "razer", "msi"] }, { "dev_type": "Server", From 8640b8c28213fb35ebecaa57db27c23fa9c67aad Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Fri, 30 Jan 2026 20:25:09 +1100 Subject: [PATCH 12/15] BE: in-app notifications overwrite prevention + device huristics update Signed-off-by: jokob-sk --- back/device_heuristics_rules.json | 5 +- server/messaging/in_app.py | 105 ++++++++++++++++++------------ 2 files changed, 67 insertions(+), 43 deletions(-) diff --git a/back/device_heuristics_rules.json b/back/device_heuristics_rules.json index bd14df81..30e2ae5f 100755 --- a/back/device_heuristics_rules.json +++ b/back/device_heuristics_rules.json @@ -155,9 +155,10 @@ "matching_pattern": [ { "mac_prefix": "001FA7", "vendor": "Sony" }, { "mac_prefix": "7C04D0", "vendor": "Nintendo" }, - { "mac_prefix": "EC26CA", "vendor": "Sony" } + { "mac_prefix": "EC26CA", "vendor": "Sony" }, + { "mac_prefix": "48B02D", "vendor": "NVIDIA" } ], - "name_pattern": ["playstation", "xbox"] + "name_pattern": ["playstation", "xbox", "shield", "nvidia"] }, { "dev_type": "Camera", diff --git a/server/messaging/in_app.py b/server/messaging/in_app.py index fc47afdf..ca90962a 100755 --- a/server/messaging/in_app.py +++ b/server/messaging/in_app.py @@ -1,9 +1,9 @@ import os import sys -import _io import json import uuid import time +import fcntl from flask import jsonify @@ -20,6 +20,35 @@ from api_server.sse_broadcast import broadcast_unread_notifications_count # noq NOTIFICATION_API_FILE = apiPath + 'user_notifications.json' +def locked_notifications_file(callback): + # Ensure file exists + if not os.path.exists(NOTIFICATION_API_FILE): + with open(NOTIFICATION_API_FILE, "w") as f: + f.write("[]") + + with open(NOTIFICATION_API_FILE, "r+") as f: + fcntl.flock(f, fcntl.LOCK_EX) + try: + raw = f.read().strip() or "[]" + try: + data = json.loads(raw) + except json.JSONDecodeError: + mylog("none", "[Notification] Corrupted JSON detected, resetting.") + data = [] + + # Let caller modify data + result = callback(data) + + # Write back atomically + f.seek(0) + f.truncate() + json.dump(data, f, indent=4) + + return result + finally: + fcntl.flock(f, fcntl.LOCK_UN) + + # Show Frontend User Notification def write_notification(content, level="alert", timestamp=None): """ @@ -37,45 +66,21 @@ def write_notification(content, level="alert", timestamp=None): if timestamp is None: timestamp = timeNowDB() - # Generate GUID - guid = str(uuid.uuid4()) - - # Prepare notification dictionary notification = { "timestamp": str(timestamp), - "guid": guid, + "guid": str(uuid.uuid4()), "read": 0, "level": level, "content": content, } - # If file exists, load existing data, otherwise initialize as empty list - if os.path.exists(NOTIFICATION_API_FILE): - with open(NOTIFICATION_API_FILE, "r") as file: - # Check if the file object is of type _io.TextIOWrapper - if isinstance(file, _io.TextIOWrapper): - file_contents = file.read() # Read file contents - if file_contents == "": - file_contents = "[]" # If file is empty, initialize as empty list + def update(notifications): + notifications.append(notification) - # mylog('debug', ['[Notification] User Notifications file: ', file_contents]) - notifications = json.loads(file_contents) # Parse JSON data - else: - mylog("none", "[Notification] File is not of type _io.TextIOWrapper") - notifications = [] - else: - notifications = [] + locked_notifications_file(update) - # Append new notification - notifications.append(notification) - - # Write updated data back to file - with open(NOTIFICATION_API_FILE, "w") as file: - json.dump(notifications, file, indent=4) - - # Broadcast unread count update try: - unread_count = sum(1 for n in notifications if n.get("read", 0) == 0) + unread_count = sum(1 for n in locked_notifications_file(lambda n: n) if n.get("read", 0) == 0) broadcast_unread_notifications_count(unread_count) except Exception as e: mylog("none", [f"[Notification] Failed to broadcast unread count: {e}"]) @@ -143,24 +148,42 @@ def mark_all_notifications_read(): "error": str (optional) } """ + # If notifications file does not exist, nothing to mark if not os.path.exists(NOTIFICATION_API_FILE): return {"success": True} try: - with open(NOTIFICATION_API_FILE, "r") as f: - notifications = json.load(f) - except Exception as e: - mylog("none", f"[Notification] Failed to read notifications: {e}") - return {"success": False, "error": str(e)} + # Open file in read/write mode and acquire exclusive lock + with open(NOTIFICATION_API_FILE, "r+") as f: + fcntl.flock(f, fcntl.LOCK_EX) - for n in notifications: - n["read"] = 1 + try: + # Read file contents + file_contents = f.read().strip() + if file_contents == "": + notifications = [] + else: + try: + notifications = json.loads(file_contents) + except json.JSONDecodeError as e: + mylog("none", f"[Notification] Corrupted notifications JSON: {e}") + notifications = [] + + # Mark all notifications as read + for n in notifications: + n["read"] = 1 + + # Rewrite file safely + f.seek(0) + f.truncate() + json.dump(notifications, f, indent=4) + + finally: + # Always release file lock + fcntl.flock(f, fcntl.LOCK_UN) - try: - with open(NOTIFICATION_API_FILE, "w") as f: - json.dump(notifications, f, indent=4) except Exception as e: - mylog("none", f"[Notification] Failed to write notifications: {e}") + mylog("none", f"[Notification] Failed to read/write notifications: {e}") return {"success": False, "error": str(e)} mylog("debug", "[Notification] All notifications marked as read.") From 8dfc0e096cd78224f3836f845c3af1c5ef3a8a49 Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Fri, 30 Jan 2026 20:28:50 +1100 Subject: [PATCH 13/15] better heuristics Signed-off-by: jokob-sk --- back/device_heuristics_rules.json | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/back/device_heuristics_rules.json b/back/device_heuristics_rules.json index 30e2ae5f..783ca333 100755 --- a/back/device_heuristics_rules.json +++ b/back/device_heuristics_rules.json @@ -5,7 +5,12 @@ "matching_pattern": [ { "mac_prefix": "INTERNET", "vendor": "" } ], - "name_pattern": [] + "name_pattern": [], + "ip_pattern": [ + "^192\\.168\\.1\\.1$", + "^192\\.168\\.0\\.1$", + "^10\\.0\\.0\\.1$" + ] }, { "dev_type": "Smart Switch", @@ -178,11 +183,7 @@ { "mac_prefix": "000C29", "vendor": "Cisco" }, { "mac_prefix": "00155D", "vendor": "MikroTik" } ], - "name_pattern": ["router", "gateway", "ap", "access point"], - "ip_pattern": [ - "^192\\.168\\.[0-1]\\.1$", - "^10\\.0\\.0\\.1$" - ] + "name_pattern": ["router", "gateway", "ap", "access point"] }, { "dev_type": "Smart Light", From a6f9b56abb8c2d23082eb9826c546c4911ead749 Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Fri, 30 Jan 2026 20:48:43 +1100 Subject: [PATCH 14/15] devices view filtering fixes Signed-off-by: jokob-sk --- front/php/components/devices_filters.php | 2 +- server/const.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/front/php/components/devices_filters.php b/front/php/components/devices_filters.php index c66492ec..0a376b4f 100755 --- a/front/php/components/devices_filters.php +++ b/front/php/components/devices_filters.php @@ -18,7 +18,7 @@ function renderFilterDropdown($headerKey, $columnName, $values) { // Generate the dropdown HTML return '
- + diff --git a/server/const.py b/server/const.py index 63594d62..8725d51e 100755 --- a/server/const.py +++ b/server/const.py @@ -176,6 +176,12 @@ sql_devices_filters = """ SELECT DISTINCT 'devSyncHubNode' AS columnName, devSyncHubNode AS columnValue FROM Devices WHERE devSyncHubNode NOT IN ('', 'null') AND devSyncHubNode IS NOT NULL UNION + SELECT DISTINCT 'devVlan' AS columnName, devVlan AS columnValue + FROM Devices WHERE devVlan NOT IN ('', 'null') AND devVlan IS NOT NULL + UNION + SELECT DISTINCT 'devParentRelType' AS columnName, devParentRelType AS columnValue + FROM Devices WHERE devParentRelType NOT IN ('', 'null') AND devParentRelType IS NOT NULL + UNION SELECT DISTINCT 'devSSID' AS columnName, devSSID AS columnValue FROM Devices WHERE devSSID NOT IN ('', 'null') AND devSSID IS NOT NULL ORDER BY columnName; From 4506aa3b1f28e051e3f7e2fa4d077244d3cd4a64 Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Sat, 31 Jan 2026 11:13:35 +1100 Subject: [PATCH 15/15] =?UTF-8?q?FE:=20None=E2=9D=8C=20value=20fixes=20and?= =?UTF-8?q?=20overrides=20of=20setting=20values?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: jokob-sk --- front/deviceDetailsEdit.php | 2 +- front/js/common.js | 10 +++--- front/js/modal.js | 6 ++-- front/js/settings_utils.js | 72 ++++++++++++++++++++++++------------- front/settings.php | 6 ++-- 5 files changed, 60 insertions(+), 36 deletions(-) diff --git a/front/deviceDetailsEdit.php b/front/deviceDetailsEdit.php index 19e51351..d2689866 100755 --- a/front/deviceDetailsEdit.php +++ b/front/deviceDetailsEdit.php @@ -339,7 +339,7 @@ function getDeviceData() {
- ${generateFormHtml(settingsData, setting, fieldData.toString(), fieldOptionsOverride, null)} + ${generateFormHtml(settingsData, setting, fieldData.toString(), fieldOptionsOverride, null, mac == "new")} ${inlineControl}
`; diff --git a/front/js/common.js b/front/js/common.js index 8ff405fd..da0f2cb8 100755 --- a/front/js/common.js +++ b/front/js/common.js @@ -181,7 +181,7 @@ function getSettingOptions (key) { if (result == "") { - console.log(`Setting options with key "${key}" not found`) + // console.log(`Setting options with key "${key}" not found`) result = [] } @@ -197,10 +197,10 @@ function getSetting (key) { result = getCache(`nax_set_${key}`, true); - if (result == "") - { - console.log(`Setting with key "${key}" not found`) - } + // if (result == "") + // { + // console.log(`Setting with key "${key}" not found`) + // } return result; } diff --git a/front/js/modal.js b/front/js/modal.js index d4024d02..0c4ec1fb 100755 --- a/front/js/modal.js +++ b/front/js/modal.js @@ -170,7 +170,8 @@ function showModalPopupForm( curValue = null, popupFormJson = null, parentSettingKey = null, - triggeredBy = null + triggeredBy = null, + populateFromOverrides = true ) { // set captions prefix = "modal-form"; @@ -229,7 +230,8 @@ function showModalPopupForm( setObj, null, fieldOptionsOverride, - null + null, + populateFromOverrides // is new entry )} diff --git a/front/js/settings_utils.js b/front/js/settings_utils.js index 2f713b73..dfafde44 100755 --- a/front/js/settings_utils.js +++ b/front/js/settings_utils.js @@ -321,7 +321,8 @@ function addViaPopupForm(element) { null, // curValue popupFormJson, // popupform toId, // parentSettingKey - element // triggeredBy + element, // triggeredBy + true // initialize defaut values ); // flag something changes to prevent navigating from page @@ -475,7 +476,8 @@ function initListInteractionOptions(element) { curValue, // curValue popupFormJson, // popupform toId, // parentSettingKey - this // triggeredBy + this, // triggeredBy + true // populate overrides ); } else { // Fallback to normal field input @@ -1132,24 +1134,44 @@ function collectSetting(prefix, setCodeName, setType, settingsArray) { // ------------------------------------------------------------------------------ // Generate the form control for setting -function generateFormHtml(settingsData, set, overrideValue, overrideOptions, originalSetKey) { +/** + * Generates the HTML string for form controls based on setting configurations. + * Supports various element types including select, input, button, textarea, span, and recursive datatables. + * * @param {Object} settingsData - The global settings object containing configuration for all available settings. + * @param {Object} set - The specific configuration object for the current setting. + * @param {string} set.setKey - Unique identifier for the setting. + * @param {string} set.setType - JSON string defining the UI components (dataType, elements, etc.). + * @param {string} [set.setValue] - The default value for the setting. + * @param {Array|string} [set.setEvents] - List of event triggers to be rendered as clickable icons. + * @param {any} overrideValue - The current value to be displayed in the form control. + * @param {any} overrideOptions - Custom options to override the default setting options (used primarily for dropdowns). + * @param {string} originalSetKey - The base key name (used to maintain reference when keys are modified for uniqueness in tables). + * @param {boolean} populateFromOverrides - Flag to determine if the value should be pulled from `set['setValue']` (true) or `overrideValue` (false). + * * @returns {string} A string of HTML containing the form elements and any associated event action icons. + * * @example + * const html = generateFormHtml(allSettings, currentSet, "DefaultVal", null, "my_key", false); + * $('#container').html(html); + */ +function generateFormHtml(settingsData, set, overrideValue, overrideOptions, originalSetKey, populateFromOverrides) { let inputHtml = ''; // if override value is considered empty initialize from setting defaults - overrideValue == null || overrideValue == undefined ? inVal = set['setValue'] : inVal = overrideValue + populateFromOverrides ? inVal = set['setValue'] : inVal = overrideValue; + const setKey = set['setKey']; const setType = set['setType']; - // if (setKey == 'NEWDEV_devParentMAC') { + // if (setKey == 'UNIFIAPI_site_name') { - // console.log("==== DEBUG OUTPUT BELOW 1 ===="); - // console.log(setType); - // console.log(setKey); - // console.log(overrideValue); - // console.log(inVal); - - // } + // console.log("==== DEBUG OUTPUT BELOW 1 ===="); + // console.log("populateFromOverrides: " + populateFromOverrides); + // console.log(setType); + // console.log(setKey); + // console.log("overrideValue:" + overrideValue); + // console.log("inVal:" + inVal); + // console.log("set['setValue']:" + set['setValue']); + // } // Parse the setType JSON string // console.log(processQuotes(setType)); @@ -1189,15 +1211,14 @@ function generateFormHtml(settingsData, set, overrideValue, overrideOptions, ori // Override value let val = valRes; - // if (setKey == 'NEWDEV_devParentMAC') { + // if (setKey == 'UNIFIAPI_site_name') { // console.log("==== DEBUG OUTPUT BELOW 2 ===="); // console.log(setType); // console.log(setKey); - // console.log(overrideValue); - // console.log(inVal); - // console.log(val); - + // console.log("overrideValue:" + overrideValue); + // console.log("inVal:" + inVal); + // console.log("val:" + val); // } // Generate HTML based on elementType @@ -1227,7 +1248,7 @@ function generateFormHtml(settingsData, set, overrideValue, overrideOptions, ori break; case 'input': - const checked = val === 'True' || val === 'true' || val === '1' ? 'checked' : ''; + const checked = val === 'True' || val === 'true' || val === '1' || val == true ? 'checked' : ''; const inputClass = inputType === 'checkbox' ? 'checkbox' : 'form-control'; inputHtml += `
${cellHtml}
`; diff --git a/front/settings.php b/front/settings.php index 899cdef0..2dc298bd 100755 --- a/front/settings.php +++ b/front/settings.php @@ -511,10 +511,10 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX } // INPUT - inputFormHtml = generateFormHtml(settingsData, set, valIn, null, null); + inputFormHtml = generateFormHtml(settingsData, set, valIn, null, null, false); - // construct final HTML for the setting - setHtml += inputFormHtml + overrideHtml + ` + // construct final HTML for the setting + setHtml += inputFormHtml + overrideHtml + ` `