diff --git a/back/device_heuristics_rules.json b/back/device_heuristics_rules.json index 419c3da1..783ca333 100755 --- a/back/device_heuristics_rules.json +++ b/back/device_heuristics_rules.json @@ -5,7 +5,64 @@ "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", + "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", "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 Appliance", + "icon_html": "", + "matching_pattern": [ + { "mac_prefix": "446FF8", "vendor": "Dyson" } + ], + "name_pattern": ["dyson", "purifier", "humidifier", "fan"] + }, + { + "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", @@ -16,24 +73,7 @@ { "mac_prefix": "F4F5D8", "vendor": "TP-Link" }, { "mac_prefix": "F88E85", "vendor": "Netgear" } ], - "name_pattern": ["router", "gateway", "ap", "access point", "access-point", "switch"] - }, - { - "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"] - }, - { - "dev_type": "Phone", - "icon_html": "", - "matching_pattern": [ - ], - "name_pattern": ["android","samsung"] + "name_pattern": ["router", "gateway", "ap", "access point", "access-point", "switch", "sg105", "sg108", "managed switch", "unmanaged switch", "poe switch", "ethernet switch"] }, { "dev_type": "Tablet", @@ -43,25 +83,19 @@ { "mac_prefix": "BC4C4C", "vendor": "Samsung" } ], "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", @@ -69,9 +103,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", @@ -80,9 +116,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", @@ -123,9 +160,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", @@ -138,15 +176,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": "", @@ -154,23 +183,13 @@ { "mac_prefix": "000C29", "vendor": "Cisco" }, { "mac_prefix": "00155D", "vendor": "MikroTik" } ], - "name_pattern": ["router", "gateway", "ap", "access point", "access-point"], - "ip_pattern": [ - "^192\\.168\\.[0-1]\\.1$", - "^10\\.0\\.0\\.1$" - ] + "name_pattern": ["router", "gateway", "ap", "access point"] }, { "dev_type": "Smart Light", "icon_html": "", "matching_pattern": [], - "name_pattern": ["hue", "lifx", "bulb"] - }, - { - "dev_type": "Smart Home", - "icon_html": "", - "matching_pattern": [], - "name_pattern": ["google", "chromecast", "nest"] + "name_pattern": ["hue", "lifx", "bulb", "light"] }, { "dev_type": "Smartwatch", @@ -187,14 +206,9 @@ { "dev_type": "Security Device", "icon_html": "", - "matching_pattern": [], - "name_pattern": ["doorbell", "lock", "security"] - }, - { - "dev_type": "Smart Light", - "icon_html": "", "matching_pattern": [ + { "mac_prefix": "047BCB", "vendor": "Universal Global Scientific" } ], - "name_pattern": ["light","bulb"] + "name_pattern": ["doorbell", "lock", "security", "mmd-", "ring"] } -] +] \ No newline at end of file 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/docs/img/netalertx_docs.png b/docs/img/netalertx_docs.png index 27a0b661..39914c59 100755 Binary files a/docs/img/netalertx_docs.png and b/docs/img/netalertx_docs.png differ 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/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/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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/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/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/front/php/templates/language/ar_ar.json b/front/php/templates/language/ar_ar.json index e97400ba..9fb0111e 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": "التالي", @@ -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/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/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 f4f5c1de..18cee2cb 100755 --- a/front/plugins/ui_settings/config.json +++ b/front/plugins/ui_settings/config.json @@ -440,7 +440,10 @@ "Device_TableHead_CustomProps", "Device_TableHead_FQDN", "Device_TableHead_ParentRelType", - "Device_TableHead_ReqNicsOnline" + "Device_TableHead_ReqNicsOnline", + "Device_TableHead_Vlan", + "Device_TableHead_IPv4", + "Device_TableHead_IPv6" ], "localized": ["name", "description"], "name": [ @@ -517,7 +520,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/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 + `
` 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 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() 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; 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/helper.py b/server/helper.py index 4565dcd5..bfbf65c1 100755 --- a/server/helper.py +++ b/server/helper.py @@ -590,26 +590,6 @@ def normalize_string(text): # MAC and IP helper methods # ------------------------------------------------------------------------------- - -# # ------------------------------------------------------------------------------------------- -# def is_random_mac(mac: str) -> 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/messaging/in_app.py b/server/messaging/in_app.py index 97340c20..ca90962a 100755 --- a/server/messaging/in_app.py +++ b/server/messaging/in_app.py @@ -3,6 +3,7 @@ import sys import json import uuid import time +import fcntl from flask import jsonify @@ -19,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): """ @@ -36,50 +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 - try: - if os.path.exists(NOTIFICATION_API_FILE): - with open(NOTIFICATION_API_FILE, "r") as file: - file_contents = file.read().strip() - if file_contents: - notifications = json.loads(file_contents) - if not isinstance(notifications, list): - mylog("error", "[Notification] Invalid format: not a list, resetting") - notifications = [] - else: - notifications = [] - else: - notifications = [] - except Exception as e: - mylog("error", [f"[Notification] Error reading notifications file: {e}"]) - notifications = [] + def update(notifications): + notifications.append(notification) - # Append new notification - notifications.append(notification) + locked_notifications_file(update) - # Write updated data back to file try: - with open(NOTIFICATION_API_FILE, "w") as file: - json.dump(notifications, file, indent=4) - except Exception as e: - mylog("error", [f"[Notification] Error writing to notifications file: {e}"]) - # Don't re-raise, just log. This prevents the API from crashing 500. - - # 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}"]) @@ -147,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.") 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, }, # ========================================================== diff --git a/server/scan/device_heuristics.py b/server/scan/device_heuristics.py index 15f9a0ad..b9c14520 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 helper import is_random_mac # Register NetAlertX directories INSTALL_PATH = os.getenv("NETALERTX_APP", "/app") @@ -183,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) @@ -261,4 +266,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.")