Merge netalertx/main into openapi-mcp-improvements

This commit is contained in:
Adam Outler
2026-01-31 02:09:57 +01:00
44 changed files with 918 additions and 264 deletions

View File

@@ -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": "<i class=\"fa-solid fa-toggle-on\"></i>",
"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": "<i class=\"fa-solid fa-plug\"></i>",
"matching_pattern": [
{ "mac_prefix": "2887BA", "vendor": "TP-Link" }
],
"name_pattern": ["kp115", "hs100", "hs103", "hs105", "smart plug", "outlet", "plug"]
},
{
"dev_type": "Smart Speaker",
"icon_html": "<i class=\"fa fa-volume-up\"></i>",
"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": "<i class=\"fa-solid fa-wind\"></i>",
"matching_pattern": [
{ "mac_prefix": "446FF8", "vendor": "Dyson" }
],
"name_pattern": ["dyson", "purifier", "humidifier", "fan"]
},
{
"dev_type": "Smart Home",
"icon_html": "<i class=\"fa fa-house\"></i>",
"matching_pattern": [],
"name_pattern": ["google", "chromecast", "nest", "hub"]
},
{
"dev_type": "Phone",
"icon_html": "<i class=\"fa-solid fa-mobile\"></i>",
"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": "<i class=\"fa-brands fa-apple\"></i>",
"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": "<i class=\"fa-solid fa-mobile\"></i>",
"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": "<i class=\"fa-brands fa-raspberry-pi\"></i>",
"matching_pattern": [
{ "mac_prefix": "B827EB", "vendor": "Raspberry Pi" },
{ "mac_prefix": "DCA632", "vendor": "Raspberry Pi" }
],
"name_pattern": ["raspberry", "pi"]
},
{
"dev_type": "IoT",
"icon_html": "<i class=\"fa-solid fa-microchip\"></i>",
"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": "<i class=\"fa fa-volume-up\"></i>",
"matching_pattern": [
{ "mac_prefix": "44650D", "vendor": "Amazon" },
{ "mac_prefix": "74ACB9", "vendor": "Google" }
],
"name_pattern": ["echo", "alexa", "dot"]
},
{
"dev_type": "Router",
"icon_html": "<i class=\"fa fa-random\"></i>",
@@ -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": "<i class=\"fa fa-lightbulb\"></i>",
"matching_pattern": [],
"name_pattern": ["hue", "lifx", "bulb"]
},
{
"dev_type": "Smart Home",
"icon_html": "<i class=\"fa fa-house\"></i>",
"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": "<i class=\"fa fa-shield-alt\"></i>",
"matching_pattern": [],
"name_pattern": ["doorbell", "lock", "security"]
},
{
"dev_type": "Smart Light",
"icon_html": "<i class=\"fa-solid fa-lightbulb\"></i>",
"matching_pattern": [
{ "mac_prefix": "047BCB", "vendor": "Universal Global Scientific" }
],
"name_pattern": ["light","bulb"]
"name_pattern": ["doorbell", "lock", "security", "mmd-", "ring"]
}
]
]

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@@ -339,7 +339,7 @@ function getDeviceData() {
</i>
</label>
<div class="${obj.inputClasses}">
${generateFormHtml(settingsData, setting, fieldData.toString(), fieldOptionsOverride, null)}
${generateFormHtml(settingsData, setting, fieldData.toString(), fieldOptionsOverride, null, mac == "new")}
${inlineControl}
</div>
</div>`;

View File

@@ -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 = [];

View File

@@ -0,0 +1,453 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="200"
height="200"
viewBox="0 0 52.916667 52.916668"
version="1.1"
id="svg5"
inkscape:version="1.1.2 (b8e25be833, 2022-02-05)"
sodipodi:docname="netalertx_red_docs_copy3_blue.svg"
inkscape:export-filename="C:\Users\jokob\netalertx_red_docs_d_3.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="true"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="2.8284271"
inkscape:cx="132.40575"
inkscape:cy="118.44039"
inkscape:window-width="3373"
inkscape:window-height="1417"
inkscape:window-x="59"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
units="px"
width="50px" />
<defs
id="defs2">
<inkscape:path-effect
effect="powermask"
id="path-effect51283"
is_visible="true"
lpeversion="1"
uri="#mask-powermask-path-effect51283"
invert="false"
hide_mask="false"
background="true"
background_color="#ffffffff" />
<inkscape:path-effect
effect="powermask"
id="path-effect51278"
is_visible="true"
lpeversion="1"
uri="#mask-powermask-path-effect51278"
invert="false"
hide_mask="false"
background="true"
background_color="#ffffffff" />
<inkscape:path-effect
effect="powermask"
id="path-effect51273"
is_visible="true"
lpeversion="1"
uri="#mask-powermask-path-effect51273"
invert="false"
hide_mask="false"
background="true"
background_color="#ffffffff" />
<inkscape:path-effect
effect="powermask"
id="path-effect48754"
is_visible="true"
lpeversion="1"
uri="#mask-powermask-path-effect48754"
invert="false"
hide_mask="false"
background="true"
background_color="#ffffffff" />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath48972">
<path
style="fill:#000000;stroke-width:0.280643"
id="path48974"
width="56.128242"
height="56.128246"
x="-18.924671"
y="-56.198174"
transform="rotate(45.438374)"
mask="none"
sodipodi:type="rect" />
</clipPath>
<mask
maskUnits="userSpaceOnUse"
id="mask49405">
<text
xml:space="preserve"
style="font-size:60.8695px;line-height:1.25;font-family:Amiri;-inkscape-font-specification:Amiri;display:inline;stroke-width:1.52174"
x="66.930733"
y="78.642288"
id="text49409"
transform="scale(1.4861626,0.67287388)"><tspan
sodipodi:role="line"
id="tspan49407"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Tw Cen MT';-inkscape-font-specification:'Tw Cen MT';fill:#ffffff;stroke-width:1.52174"
x="66.930733"
y="78.642288">A</tspan></text>
</mask>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath50306">
<circle
style="mix-blend-mode:normal;fill:#d40000;stroke-width:0.176318"
id="circle50308"
cy="26.458334"
cx="26.458334"
r="26.458334"
clip-path="url(#clipPath48972)"
transform="matrix(1.0038771,0,0.00391255,1.0073928,-0.04603368,-0.1228191)" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath48972-7">
<path
style="fill:#000000;stroke-width:0.280643"
id="path48974-5"
width="56.128242"
height="56.128246"
x="-18.924671"
y="-56.198174"
transform="rotate(45.438374)"
mask="none"
sodipodi:type="rect" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath50306-6">
<circle
style="mix-blend-mode:normal;fill:#d40000;stroke-width:0.176318"
id="circle50308-5"
cy="26.458334"
cx="26.458334"
r="26.458334"
clip-path="url(#clipPath48972)"
transform="matrix(1.0038771,0,0.00391255,1.0073928,-0.04603368,-0.1228191)" />
</clipPath>
<mask
maskUnits="userSpaceOnUse"
id="mask-powermask-path-effect51273">
<path
id="mask-powermask-path-effect51273_box"
style="fill:#ffffff;fill-opacity:1"
d="m 71.788348,33.677177 h 2.00083 v 2.173766 h -2.00083 z" />
<path
style="fill:#000000"
id="path51263"
sodipodi:type="arc"
sodipodi:cx="66.211845"
sodipodi:cy="37.490814"
sodipodi:rx="3.9464016"
sodipodi:ry="1.4616301"
sodipodi:start="0"
sodipodi:end="0.031086059"
sodipodi:open="true"
sodipodi:arc-type="arc"
d="m 70.158247,37.490814 a 3.9464016,1.4616301 0 0 1 -0.0019,0.04543" />
</mask>
<mask
maskUnits="userSpaceOnUse"
id="mask-powermask-path-effect51278">
<path
style="fill:#000000"
id="path51267"
sodipodi:type="arc"
sodipodi:cx="66.211845"
sodipodi:cy="37.490814"
sodipodi:rx="3.9464016"
sodipodi:ry="1.4616301"
sodipodi:start="0"
sodipodi:end="0.031086059"
sodipodi:open="true"
sodipodi:arc-type="arc" />
</mask>
<mask
maskUnits="userSpaceOnUse"
id="mask-powermask-path-effect51283">
<path
style="fill:#000000"
id="path51271"
sodipodi:type="arc"
sodipodi:cx="66.211845"
sodipodi:cy="37.490814"
sodipodi:rx="3.9464016"
sodipodi:ry="1.4616301"
sodipodi:start="0"
sodipodi:end="0.031086059"
sodipodi:open="true"
sodipodi:arc-type="arc" />
</mask>
<filter
id="mask-powermask-path-effect51273_inverse"
inkscape:label="filtermask-powermask-path-effect51273"
style="color-interpolation-filters:sRGB"
height="100"
width="100"
x="-50"
y="-50">
<feColorMatrix
id="mask-powermask-path-effect51273_primitive1"
values="1"
type="saturate"
result="fbSourceGraphic" />
<feColorMatrix
id="mask-powermask-path-effect51273_primitive2"
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0 "
in="fbSourceGraphic" />
</filter>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath1481">
<rect
style="fill:#ffffff;stroke-width:0.227484"
id="rect1483"
width="26.653997"
height="52.852543"
x="62.86179"
y="-0.46772188" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath1481-1">
<rect
style="fill:#ffffff;stroke-width:0.227484"
id="rect1483-0"
width="26.653997"
height="52.852543"
x="62.86179"
y="-0.46772188" />
</clipPath>
</defs>
<g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="Red 1"
style="display:none">
<circle
style="fill:#ff2a2a;stroke-width:0.176318"
id="path31-8"
cy="26.458334"
cx="26.458334"
r="26.458334" />
</g>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="A - Layer 2"
style="display:none">
<rect
style="fill:#ffffff;stroke-width:0.328992"
id="rect48998"
width="26.0966"
height="6.0620313"
x="13.255443"
y="41.262722" />
</g>
<g
inkscape:groupmode="layer"
id="g48055"
inkscape:label="Red top"
style="display:none;mix-blend-mode:normal">
<circle
style="mix-blend-mode:normal;fill:#d40000;stroke-width:0.176318"
id="circle48752"
cy="26.458334"
cx="26.458334"
r="26.458334"
clip-path="url(#clipPath48972)"
transform="matrix(1.0038771,0,0.00391255,1.0073928,-0.04603368,-0.1228191)" />
<ellipse
style="display:inline;mix-blend-mode:normal;fill:#000000;stroke-width:0.43638"
id="path50080"
clip-path="url(#clipPath50306)"
ry="13.739323"
rx="16.735666"
cy="22.874514"
cx="26.36149"
transform="translate(0,0.09980904)" />
<path
style="fill:#000000"
id="path51325"
sodipodi:type="arc"
sodipodi:cx="16.772207"
sodipodi:cy="26.090099"
sodipodi:rx="4.1291056"
sodipodi:ry="7.6004772"
sodipodi:start="0"
sodipodi:end="0.031086059"
sodipodi:arc-type="slice"
d="m 20.901313,26.090099 a 4.1291056,7.6004772 0 0 1 -0.002,0.236231 l -4.127111,-0.236231 z" />
<path
style="fill:#d40000"
id="path51717"
sodipodi:type="arc"
sodipodi:cx="26.441042"
sodipodi:cy="-26.531424"
sodipodi:rx="10.418671"
sodipodi:ry="9.5820541"
sodipodi:start="0.82219863"
sodipodi:end="2.3054129"
sodipodi:arc-type="slice"
d="m 33.532115,-19.511189 a 10.418671,9.5820541 0 0 1 -14.074736,0.09049 l 6.983663,-7.110726 z"
transform="matrix(1,0,0.0048047,-0.99998846,0,0)" />
<path
style="fill:#ffffff;stroke-width:0.276214"
d="M 145.28835,50.354872 C 127.01317,34.62734 98.057144,30.012421 73.710372,38.947003 c -6.518003,2.391924 -14.288822,6.834002 -19.265958,11.01311 -1.198654,1.006465 -2.270358,1.829935 -2.381565,1.829935 -0.111206,0 -5.210052,-5.102002 -11.33077,-11.337781 L 29.603503,29.114489 30.822139,27.851613 c 0.670251,-0.69458 2.51592,-2.384634 4.101489,-3.755674 C 50.725112,10.43241 69.462577,2.3767456 90.736164,0.10085492 95.380582,-0.39601422 106.33043,-0.31105699 111.03786,0.25837091 133.04363,2.9202648 151.46536,11.26468 167.83762,25.986722 l 3.30701,2.97369 -2.29392,2.320103 c -1.26165,1.276057 -6.58213,6.517685 -11.82329,11.648065 l -9.52936,9.327957 z"
id="path52311"
transform="scale(0.26458333)" />
<path
style="fill:#ffffff;stroke-width:0.276214"
d="M 86.538548,86.634546 74.145111,73.25799 74.899337,72.758689 c 4.93766,-3.268754 10.138703,-6.508578 16.602198,-7.437693 5.484021,-0.788317 12.228205,-0.984814 16.377135,-0.09119 6.77689,1.459652 11.87156,4.340971 17.02452,7.792011 l 0.97468,0.652765 -1.37124,1.269268 c -0.86863,0.804036 -6.82647,6.676301 -13.34742,13.259175 L 99.423152,99.796276 Z"
id="path52350"
transform="scale(0.26458333)"
inkscape:export-filename="C:\Users\jokob\path52350.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
sodipodi:nodetypes="ccsssscsscc" />
</g>
<g
inkscape:label="Black"
inkscape:groupmode="layer"
id="layer1"
style="display:inline">
<ellipse
style="fill:#000000;stroke-width:0.176146"
id="path31"
cy="26.51001"
cx="26.458334"
rx="26.458"
ry="26.406658" />
<ellipse
style="display:inline;fill:#ffffff;stroke-width:0.176318"
id="path31-89"
mask="url(#mask49405)"
transform="translate(-99.990036,0.02979629)"
cy="26.458334"
cx="126.45834"
rx="26.458"
ry="26.458334" />
<path
style="opacity:0.98;fill:#5f5fd3;fill-opacity:1;stroke-width:3.81317;stroke-linecap:round;stroke-miterlimit:0.4"
d="M 50.734917,51.5385 C 50.317784,51.008202 45.376222,45.855755 39.753667,40.088624 L 29.530842,29.602927 32.157037,27.108298 C 37.014258,22.494413 44.043654,17.26825 51.002109,13.097503 60.785219,7.2337198 74.185013,2.5922331 86.866814,0.67450934 92.65309,-0.20048258 104.71024,-0.37258331 110.80487,0.33282367 133.37755,2.9454414 150.98136,11.201829 167.87245,27.098183 l 2.76303,2.600302 -11.44673,11.421726 -11.44672,11.421723 -2.63001,-2.20425 C 135.80913,42.540775 123.7472,37.357565 110.13188,35.306142 105.25895,34.571936 94.151456,34.473316 89.625785,35.124073 76.006414,37.082441 65.655848,41.542025 54.928431,50.073566 c -1.679878,1.336011 -3.139997,2.429113 -3.244707,2.429113 -0.104711,0 -0.531674,-0.433881 -0.948807,-0.964179 z"
id="path117144"
transform="scale(0.26458333)" />
<path
style="opacity:0.98;fill:#5f5fd3;fill-opacity:1;stroke-width:3.81317;stroke-linecap:round;stroke-miterlimit:0.4"
d="m 86.479201,86.655988 -12.859682,-12.863304 1.72756,-1.259375 c 5.937867,-4.328648 15.716974,-7.877579 22.763988,-8.261269 5.344243,-0.290978 12.593953,1.304433 19.011433,4.183761 2.41258,1.08245 8.21218,4.752269 8.21218,5.196429 0,0.224653 -16.50779,16.711429 -23.16256,23.133076 l -2.833236,2.733985 z"
id="path117183"
transform="scale(0.26458333)" />
<path
style="opacity:0.98;fill:#5f5fd3;fill-opacity:1;stroke-width:3.81317;stroke-linecap:round;stroke-miterlimit:0.4"
d="m 151.14408,181.37289 -2.63396,-2.65165 H 99.719219 50.928317 l -2.558625,2.54155 c -2.367982,2.35218 -2.618861,2.50924 -3.367071,2.10794 -1.632484,-0.87558 -7.984339,-5.82527 -11.691442,-9.11058 l -3.811927,-3.3782 34.882231,-35.14801 c 19.185224,-19.3314 34.980859,-35.144 35.101403,-35.13912 0.120544,0.005 16.129074,15.83285 35.574514,35.17326 l 35.35534,35.16438 -2.12132,1.95782 c -4.15184,3.83183 -13.51513,11.13426 -14.27653,11.13426 -0.13027,0 -1.42214,-1.19324 -2.87081,-2.65165 z M 112.69455,143.27811 99.52528,130.10884 86.35601,143.27811 73.18674,156.44738 h 26.33854 26.33854 z"
id="path117222"
transform="scale(0.26458333)" />
<path
style="opacity:0.98;fill:#5f5fd3;fill-opacity:1;stroke-width:3.81317;stroke-linecap:round;stroke-miterlimit:0.4"
d="m 43.323744,182.02493 c -3.122315,-2.21745 -8.886633,-6.91043 -11.466851,-9.33566 l -2.129855,-2.00191 31.417516,-31.60357 c 17.279634,-17.38196 32.982312,-33.19165 34.894842,-35.13266 l 3.477325,-3.5291 35.271229,35.28278 35.27123,35.28278 -2.29809,2.12417 c -3.23874,2.99361 -8.21439,6.9674 -11.21429,8.95625 l -2.55224,1.69205 -2.04396,-1.77268 c -1.12418,-0.97498 -2.34704,-2.10872 -2.71748,-2.51941 l -0.67351,-0.74673 H 99.52196 50.484308 l -2.199537,2.47487 c -1.209746,1.36118 -2.306828,2.46959 -2.437961,2.46312 -0.131132,-0.006 -1.266512,-0.7419 -2.523066,-1.6343 z m 82.364486,-25.84451 c 0,-0.14683 -5.88666,-6.15201 -13.08147,-13.34485 L 99.52528,129.75768 86.443805,142.83557 c -7.194812,7.19284 -13.081476,13.19802 -13.081476,13.34485 0,0.14683 11.773328,0.26696 26.162951,0.26696 14.38962,0 26.16295,-0.12013 26.16295,-0.26696 z"
id="path117261"
transform="scale(0.26458333)" />
</g>
<g
inkscape:groupmode="layer"
id="layer6"
inkscape:label="Circle"
style="display:none">
<path
style="fill:#000000"
id="path50026"
sodipodi:type="arc"
sodipodi:cx="71.071762"
sodipodi:cy="34.677177"
sodipodi:rx="1.7174155"
sodipodi:ry="5.5907354"
sodipodi:start="0"
sodipodi:end="0.031086059"
sodipodi:open="true"
sodipodi:arc-type="arc"
mask="url(#mask-powermask-path-effect51273)"
d="m 72.789178,34.677177 a 1.7174155,5.5907354 0 0 1 -8.3e-4,0.173766"
inkscape:path-effect="#path-effect51273" />
<path
style="fill:#ffffff;stroke-width:0.276214"
d="m 151.08883,181.46994 -2.76213,-2.60427 -48.802077,-0.009 -48.802075,-0.009 -2.292573,2.48592 c -1.260915,1.36726 -2.431589,2.48592 -2.601499,2.48592 -0.869396,0 -9.118995,-6.36599 -13.713669,-10.58246 l -2.688104,-2.46684 34.973647,-35.11455 c 19.235503,-19.313 34.922993,-35.39075 35.029879,-35.39075 0.106889,0 16.231201,16.10588 35.663001,35.45326 l 35.33055,35.17705 -2.48592,2.35505 c -3.08951,2.92687 -7.41515,6.40509 -11.09719,8.92319 -1.54594,1.05725 -2.85105,1.91728 -2.90024,1.9112 -0.0492,-0.006 -1.33242,-1.183 -2.8516,-2.61535 z m -38.4631,-38.32188 -13.050732,-13.05073 -13.050727,13.05073 -13.050725,13.05072 h 26.101452 26.101452 z"
id="path52389"
transform="scale(0.26458333)"
inkscape:export-filename="C:\Users\jokob\path52389.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
sodipodi:nodetypes="ccccssscssscsscccccccccc" />
<path
style="fill:#d40000;stroke-width:0.276214"
d="M 86.416478,86.793237 C 73.427951,73.815968 73.387119,73.801376 73.387119,73.801376 c 3.874197,-3.341721 11.025508,-6.981646 17.312424,-8.529335 2.339787,-0.576001 4.881362,-1.25628 8.810591,-1.259564 4.438736,-0.0037 8.292516,0.857843 13.253396,2.535104 4.59135,1.552325 7.8315,3.224336 11.49958,5.934101 l 1.61476,1.192897 -2.31005,2.336325 c -1.27053,1.284978 -7.22284,7.16236 -13.22736,13.060849 L 99.423152,99.796276 C 95.128284,95.409033 87.282899,87.658907 86.416478,86.793237 Z"
id="path52465"
transform="scale(0.26458333)"
sodipodi:nodetypes="sssssscsscs" />
<path
style="fill:#d40000;stroke-width:0.074168"
d="M 38.412677,13.39572 C 34.322163,9.945267 28.437517,8.4874766 22.684204,9.4993379 19.419721,10.073478 16.752307,11.410793 13.835187,13.872492 l -0.14691,0.126732 -0.587936,-0.661605 c -0.268568,-0.30222 -1.619514,-1.65761 -2.963235,-3.048642 L 7.7265561,7.8632145 7.9975963,7.5868118 C 9.8344314,5.713635 13.005888,3.476019 15.380049,2.3878744 20.659765,-0.03196726 26.24205,-0.73479764 31.856076,0.42838695 36.599757,1.4112419 40.746004,3.5106537 44.46876,7.1557672 l 0.709881,0.6950753 -0.663694,0.69037 C 44.080041,8.9935983 42.672626,10.391271 41.3963,11.655819 L 39.075708,13.955 Z"
id="path52504"
inkscape:export-filename="C:\Users\jokob\path52504.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
sodipodi:nodetypes="ssscsccsssscsscs" />
<path
style="opacity:0.98;fill:#5f5fd3;fill-opacity:1;stroke-width:3.81317;stroke-linecap:round;stroke-miterlimit:0.4"
d="M 86.655143,86.478376 73.973101,73.792663 75.700647,72.517799 c 3.888483,-2.869556 11.979097,-6.234087 17.887709,-7.438714 6.781224,-1.382532 16.632394,0.1812 23.791374,3.776537 2.53147,1.271345 7.60139,4.47823 7.60139,4.808126 0,0.217537 -18.217,18.402022 -23.34018,23.298518 l -2.303755,2.201823 z"
id="path117417"
transform="scale(0.26458333)" />
<path
style="opacity:0.98;fill:#5f5fd3;fill-opacity:1;stroke-width:3.81317;stroke-linecap:round;stroke-miterlimit:0.4"
d="M 86.653362,86.476595 74.004328,73.8239 l 1.78137,-1.307646 c 4.058289,-2.979059 11.996346,-6.266814 18.081148,-7.488783 5.742499,-1.153228 13.433334,-0.173122 20.711924,2.639491 2.64803,1.02326 7.63077,3.765523 9.69377,5.334995 l 0.88241,0.67131 -6.36248,6.41376 c -3.49937,3.527567 -9.3162,9.255172 -12.92628,12.728011 l -6.563793,6.314253 z"
id="path117456"
transform="scale(0.26458333)" />
<path
style="opacity:0.98;fill:#5f5fd3;fill-opacity:1;stroke-width:3.81317;stroke-linecap:round;stroke-miterlimit:0.4"
d="M 40.755089,40.913849 29.891381,29.698485 32.789887,26.931909 C 38.664423,21.324762 48.374309,14.517657 56.038213,10.633695 66.085649,5.5417911 79.271822,1.6347929 90.224457,0.50447904 c 5.29419,-0.54636158 20.003853,-0.24145692 24.614013,0.51020386 16.55879,2.6998184 30.27274,8.3744041 42.56518,17.6127021 3.66685,2.755798 10.38919,8.484428 12.02678,10.248962 l 0.78546,0.846346 -11.22765,11.223531 -11.22764,11.223531 -2.46252,-1.977749 C 130.84681,38.585569 112.25268,33.14502 92.666988,34.792406 78.082451,36.019136 67.49078,40.200159 55.292129,49.545997 c -1.868753,1.431721 -3.459743,2.598649 -3.535534,2.593173 -0.07579,-0.0055 -5.026468,-5.056871 -11.001506,-11.225321 z"
id="path117495"
transform="scale(0.26458333)" />
</g>
<g
inkscape:groupmode="layer"
id="layer4"
inkscape:label="half circle"
style="display:inline">
<path
style="opacity:0.98;fill:#4051b5;fill-opacity:1;stroke-width:3.81317;stroke-linecap:round;stroke-miterlimit:0.4"
d="M 50.729651,51.530407 C 50.309622,50.995658 45.365237,45.839438 39.74213,40.072138 L 29.518298,29.586142 32.436819,26.865215 C 37.858508,21.810591 46.002106,15.887672 52.91436,11.971698 62.082793,6.7775379 75.058024,2.4602175 86.866814,0.67450934 92.666822,-0.20255914 104.7089,-0.37259245 110.83899,0.33602379 133.4335,2.9478667 150.81881,11.108766 167.8709,27.107589 l 2.76147,2.590896 -11.424,11.400559 -11.42399,11.400559 -2.65118,-2.175966 C 132.57167,40.013706 117.00056,34.697228 99.348504,34.691269 c -17.588857,-0.0059 -30.84176,4.583432 -44.420073,15.382297 -1.679878,1.336011 -3.139997,2.429113 -3.244707,2.429113 -0.104711,0 -0.534044,-0.437522 -0.954073,-0.972272 z"
id="path117300"
transform="scale(0.26458333)" />
<path
style="opacity:0.98;fill:#4051b5;fill-opacity:1;stroke-width:3.81317;stroke-linecap:round;stroke-miterlimit:0.4"
d="m 86.479787,86.656574 -12.860268,-12.86389 1.72756,-1.257012 c 5.92724,-4.312793 15.575223,-7.833372 22.587211,-8.242144 5.50807,-0.321098 12.64715,1.227498 19.18821,4.162273 2.41292,1.082605 8.21218,4.752294 8.21218,5.196553 0,0.223831 -14.54007,14.745171 -22.63164,22.602487 l -3.362985,3.26562 z"
id="path117339"
transform="scale(0.26458333)" />
<path
style="opacity:0.98;fill:#4051b5;fill-opacity:1;stroke-width:3.81317;stroke-linecap:round;stroke-miterlimit:0.4"
d="m 43.323744,182.02493 c -3.01377,-2.14036 -8.648648,-6.71423 -11.329522,-9.19625 l -2.145795,-1.98662 2.929747,-3.04309 c 1.611361,-1.6737 17.298698,-17.50163 34.860748,-35.17319 l 31.931002,-32.13009 35.244626,35.24359 35.24463,35.2436 -2.29809,2.12652 c -3.22978,2.98865 -8.20792,6.96547 -11.21429,8.95861 l -2.55224,1.69205 -2.04396,-1.77268 c -1.12418,-0.97498 -2.34704,-2.10872 -2.71748,-2.51941 l -0.67351,-0.74673 H 99.52196 50.484308 l -2.199537,2.47487 c -1.209746,1.36118 -2.306828,2.46959 -2.437961,2.46312 -0.131132,-0.006 -1.266512,-0.7419 -2.523066,-1.6343 z m 82.364486,-25.84451 c 0,-0.14683 -5.88666,-6.15201 -13.08147,-13.34485 L 99.52528,129.75768 86.443805,142.83557 c -7.194812,7.19284 -13.081476,13.19802 -13.081476,13.34485 0,0.14683 11.773328,0.26696 26.162951,0.26696 14.38962,0 26.16295,-0.12013 26.16295,-0.26696 z"
id="path117378"
transform="scale(0.26458333)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -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;
}

View File

@@ -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
)}
</div>
</div>

View File

@@ -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 += `<input
@@ -1347,22 +1368,23 @@ function generateFormHtml(settingsData, set, overrideValue, overrideOptions, ori
// Extract the value for the current column
let columnOverrideValue = rowData[j] && Object.values(rowData[j])[0];
if(columnOverrideValue == undefined)
{
columnOverrideValue = ""
}
// Create unique key to prevent dropdown data duplication
const oldKey = set["setKey"];
set["setKey"] = oldKey + "_" + index;
// console.log("settingsData");
// console.log(settingsData);
// console.log(set);
// Generate the cell HTML using the extracted value
const cellHtml = generateFormHtml(
settingsData,
set,
columnOverrideValue.toString(),
columnOverrideValue,
set["setOptions"],
oldKey
oldKey,
false
);
datatableHtml += `<td> <div class="input-group"> ${cellHtml} </div></td>`;

View File

@@ -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);
}

View File

@@ -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] || "";

View File

@@ -18,7 +18,7 @@ function renderFilterDropdown($headerKey, $columnName, $values) {
// Generate the dropdown HTML
return '
<div class="filter-group input-group">
<label for="filter_' . htmlspecialchars($columnName) . '">' . lang($headerKey) . '</label>
<label for="filter_' . htmlspecialchars($columnName) . '">' . lang($headerKey) . ': </label>
<select id="filter_' . htmlspecialchars($columnName) . '" class="filter-dropdown" data-column="' . htmlspecialchars($columnName) . '">
' . $optionsHtml . '
</select>

View File

@@ -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": "قم بتحديث القيمة أدناه. احرص على اتباع التنسيق السابق. <b>لم يتم إجراء التحقق.</b>",
"test_event_tooltip": "احفظ التغييرات أولاً قبل اختبار الإعدادات."
}
}

View File

@@ -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",

View File

@@ -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": "",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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": "",

View File

@@ -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. <b>Il n'y a pas de pas de contrôle.</b>",
"test_event_tooltip": "Enregistrer d'abord vos modifications avant de tester vôtre paramétrage."
}
}

View File

@@ -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. <b>La convalida non viene eseguita.</b>",
"test_event_tooltip": "Salva le modifiche prima di provare le nuove impostazioni."
}
}

View File

@@ -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": "次",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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": "Следующая",

View File

@@ -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": "",

View File

@@ -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",

View File

@@ -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": "Оновіть значення нижче. Слідкуйте за попереднім форматом. <b>Перевірка не виконана.</b>",
"test_event_tooltip": "Перш ніж перевіряти налаштування, збережіть зміни."
}
}

View File

@@ -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": "下一页",

View File

@@ -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: <code>devVlan</code>."
}
]
},
{
"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: <code>devVlan</code>."
}
]
},
{
"function": "devForceStatus",
"type": {

View File

@@ -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": [

View File

@@ -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 + `
</div>
</div>
`

View File

@@ -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 <<EOF
══════════════════════════════════════════════════════════════════════════════
❌ Error creating config folder in: ${NETALERTX_CONFIG}
A config directory is required for proper operation, however there appear to be
insufficient permissions on this mount or it is otherwise inaccessible.
More info: https://docs.netalertx.com/FILE_PERMISSIONS
══════════════════════════════════════════════════════════════════════════════
EOF
>&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 <<EOF
══════════════════════════════════════════════════════════════════════════════
🆕 First run detected. Default configuration written to ${NETALERTX_CONFIG}/app.conf.
@@ -25,7 +54,6 @@ if [ ! -f "${NETALERTX_CONFIG}/app.conf" ]; then
this instance in production.
══════════════════════════════════════════════════════════════════════════════
EOF
>&2 printf "%s" "${RESET}"
>&2 printf "%s" "${RESET}"
fi

View File

@@ -339,6 +339,9 @@ class Query(ObjectType):
"devFQDN",
"devParentRelType",
"devParentMAC",
"devVlan",
"devPrimaryIPv4",
"devPrimaryIPv6"
]
search_term = options.search.lower()

View File

@@ -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;

View File

@@ -448,4 +448,3 @@ def unlock_fields(conn, mac=None, fields=None, clear_all=False):
}
finally:
conn.close()

View File

@@ -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}")

View File

@@ -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.")

View File

@@ -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,
},
# ==========================================================

View File

@@ -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.")