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
+79 -65
View File
@@ -5,7 +5,64 @@
"matching_pattern": [ "matching_pattern": [
{ "mac_prefix": "INTERNET", "vendor": "" } { "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", "dev_type": "Access Point",
@@ -16,24 +73,7 @@
{ "mac_prefix": "F4F5D8", "vendor": "TP-Link" }, { "mac_prefix": "F4F5D8", "vendor": "TP-Link" },
{ "mac_prefix": "F88E85", "vendor": "Netgear" } { "mac_prefix": "F88E85", "vendor": "Netgear" }
], ],
"name_pattern": ["router", "gateway", "ap", "access point", "access-point", "switch"] "name_pattern": ["router", "gateway", "ap", "access point", "access-point", "switch", "sg105", "sg108", "managed switch", "unmanaged switch", "poe switch", "ethernet switch"]
},
{
"dev_type": "Phone",
"icon_html": "<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"]
}, },
{ {
"dev_type": "Tablet", "dev_type": "Tablet",
@@ -43,25 +83,19 @@
{ "mac_prefix": "BC4C4C", "vendor": "Samsung" } { "mac_prefix": "BC4C4C", "vendor": "Samsung" }
], ],
"name_pattern": ["tablet", "pad"] "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", "dev_type": "IoT",
"icon_html": "<i class=\"fa-solid fa-microchip\"></i>", "icon_html": "<i class=\"fa-solid fa-microchip\"></i>",
"matching_pattern": [ "matching_pattern": [
{ "mac_prefix": "B827EB", "vendor": "Raspberry Pi" },
{ "mac_prefix": "DCA632", "vendor": "Raspberry Pi" },
{ "mac_prefix": "840D8E", "vendor": "Espressif" }, { "mac_prefix": "840D8E", "vendor": "Espressif" },
{ "mac_prefix": "ECFABC", "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", "dev_type": "Desktop",
@@ -69,9 +103,11 @@
"matching_pattern": [ "matching_pattern": [
{ "mac_prefix": "001422", "vendor": "Dell" }, { "mac_prefix": "001422", "vendor": "Dell" },
{ "mac_prefix": "001874", "vendor": "Lenovo" }, { "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", "dev_type": "Laptop",
@@ -80,9 +116,10 @@
{ "mac_prefix": "3C0754", "vendor": "HP" }, { "mac_prefix": "3C0754", "vendor": "HP" },
{ "mac_prefix": "0017A4", "vendor": "Dell" }, { "mac_prefix": "0017A4", "vendor": "Dell" },
{ "mac_prefix": "F4CE46", "vendor": "Lenovo" }, { "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", "dev_type": "Server",
@@ -123,9 +160,10 @@
"matching_pattern": [ "matching_pattern": [
{ "mac_prefix": "001FA7", "vendor": "Sony" }, { "mac_prefix": "001FA7", "vendor": "Sony" },
{ "mac_prefix": "7C04D0", "vendor": "Nintendo" }, { "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", "dev_type": "Camera",
@@ -138,15 +176,6 @@
], ],
"name_pattern": ["camera", "cam", "webcam"] "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", "dev_type": "Router",
"icon_html": "<i class=\"fa fa-random\"></i>", "icon_html": "<i class=\"fa fa-random\"></i>",
@@ -154,23 +183,13 @@
{ "mac_prefix": "000C29", "vendor": "Cisco" }, { "mac_prefix": "000C29", "vendor": "Cisco" },
{ "mac_prefix": "00155D", "vendor": "MikroTik" } { "mac_prefix": "00155D", "vendor": "MikroTik" }
], ],
"name_pattern": ["router", "gateway", "ap", "access point", "access-point"], "name_pattern": ["router", "gateway", "ap", "access point"]
"ip_pattern": [
"^192\\.168\\.[0-1]\\.1$",
"^10\\.0\\.0\\.1$"
]
}, },
{ {
"dev_type": "Smart Light", "dev_type": "Smart Light",
"icon_html": "<i class=\"fa fa-lightbulb\"></i>", "icon_html": "<i class=\"fa fa-lightbulb\"></i>",
"matching_pattern": [], "matching_pattern": [],
"name_pattern": ["hue", "lifx", "bulb"] "name_pattern": ["hue", "lifx", "bulb", "light"]
},
{
"dev_type": "Smart Home",
"icon_html": "<i class=\"fa fa-house\"></i>",
"matching_pattern": [],
"name_pattern": ["google", "chromecast", "nest"]
}, },
{ {
"dev_type": "Smartwatch", "dev_type": "Smartwatch",
@@ -187,14 +206,9 @@
{ {
"dev_type": "Security Device", "dev_type": "Security Device",
"icon_html": "<i class=\"fa fa-shield-alt\"></i>", "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": [ "matching_pattern": [
{ "mac_prefix": "047BCB", "vendor": "Universal Global Scientific" }
], ],
"name_pattern": ["light","bulb"] "name_pattern": ["doorbell", "lock", "security", "mmd-", "ring"]
} }
] ]
+3 -30
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 2. **Unlock Button** (🔓): Click to allow plugin overwrites again
3. **Source Indicator**: Shows current field source (USER, LOCKED, NEWDEV, or plugin name) 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 ### 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 4. If source is `NEWDEV` or plugin name, plugin update is accepted
## See Also ## See Also
- [Device locking](./DEVICE_FIELD_LOCK.md)
- [Device source fields](./DEVICE_SOURCE_FIELDS.md)
- [API Device Endpoints Documentation](./API_DEVICE.md) - [API Device Endpoints Documentation](./API_DEVICE.md)
- [Authoritative Field Updates System](./PLUGINS_DEV.md#authoritative-fields) - [Authoritative Field Updates System](./PLUGINS_DEV.md#authoritative-fields)
- [Plugin Configuration Reference](./PLUGINS_DEV_CONFIG.md) - [Plugin Configuration Reference](./PLUGINS_DEV_CONFIG.md)
+14 -9
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 | | 📡 **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 | | 📡 **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) ### 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 - Check if you accidentally unlocked it
- Open an issue if it persists - Open an issue if it persists
## For More Information ## See also
- **Technical details:** See [API_DEVICE_FIELD_LOCK.md](API_DEVICE_FIELD_LOCK.md) - [Device locking](./DEVICE_FIELD_LOCK.md)
- **Plugin configuration:** See [PLUGINS_DEV_CONFIG.md](PLUGINS_DEV_CONFIG.md) - [Device source fields](./DEVICE_SOURCE_FIELDS.md)
- **Admin guide:** See [DEVICE_MANAGEMENT.md](DEVICE_MANAGEMENT.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)
**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 management](DEVICE_MANAGEMENT.md)
+67
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

+1 -1
View File
@@ -339,7 +339,7 @@ function getDeviceData() {
</i> </i>
</label> </label>
<div class="${obj.inputClasses}"> <div class="${obj.inputClasses}">
${generateFormHtml(settingsData, setting, fieldData.toString(), fieldOptionsOverride, null)} ${generateFormHtml(settingsData, setting, fieldData.toString(), fieldOptionsOverride, null, mac == "new")}
${inlineControl} ${inlineControl}
</div> </div>
</div>`; </div>`;
+11 -2
View File
@@ -543,7 +543,10 @@ function mapColumnIndexToFieldName(index, tableColumnVisible) {
"devCustomProps", // 26 "devCustomProps", // 26
"devFQDN", // 27 "devFQDN", // 27
"devParentRelType", // 28 "devParentRelType", // 28
"devReqNicsOnline" // 29 "devReqNicsOnline", // 29
"devVlan", // 30
"devPrimaryIPv4", // 31
"devPrimaryIPv6", // 32
]; ];
// console.log("OrderBy: " + columnNames[tableColumnOrder[index]]); // console.log("OrderBy: " + columnNames[tableColumnOrder[index]]);
@@ -660,6 +663,9 @@ function initializeDatatable (status) {
devFQDN devFQDN
devParentRelType devParentRelType
devReqNicsOnline devReqNicsOnline
devVlan
devPrimaryIPv4
devPrimaryIPv6
} }
count count
} }
@@ -743,7 +749,10 @@ function initializeDatatable (status) {
device.devCustomProps || "", device.devCustomProps || "",
device.devFQDN || "", device.devFQDN || "",
device.devParentRelType || "", device.devParentRelType || "",
device.devReqNicsOnline || 0 device.devReqNicsOnline || 0,
device.devVlan || "",
device.devPrimaryIPv4 || "",
device.devPrimaryIPv6 || "",
]; ];
const newRow = []; const newRow = [];
+453
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

+5 -5
View File
@@ -181,7 +181,7 @@ function getSettingOptions (key) {
if (result == "") if (result == "")
{ {
console.log(`Setting options with key "${key}" not found`) // console.log(`Setting options with key "${key}" not found`)
result = [] result = []
} }
@@ -197,10 +197,10 @@ function getSetting (key) {
result = getCache(`nax_set_${key}`, true); result = getCache(`nax_set_${key}`, true);
if (result == "") // if (result == "")
{ // {
console.log(`Setting with key "${key}" not found`) // console.log(`Setting with key "${key}" not found`)
} // }
return result; return result;
} }
+4 -2
View File
@@ -170,7 +170,8 @@ function showModalPopupForm(
curValue = null, curValue = null,
popupFormJson = null, popupFormJson = null,
parentSettingKey = null, parentSettingKey = null,
triggeredBy = null triggeredBy = null,
populateFromOverrides = true
) { ) {
// set captions // set captions
prefix = "modal-form"; prefix = "modal-form";
@@ -229,7 +230,8 @@ function showModalPopupForm(
setObj, setObj,
null, null,
fieldOptionsOverride, fieldOptionsOverride,
null null,
populateFromOverrides // is new entry
)} )}
</div> </div>
</div> </div>
+47 -25
View File
@@ -321,7 +321,8 @@ function addViaPopupForm(element) {
null, // curValue null, // curValue
popupFormJson, // popupform popupFormJson, // popupform
toId, // parentSettingKey toId, // parentSettingKey
element // triggeredBy element, // triggeredBy
true // initialize defaut values
); );
// flag something changes to prevent navigating from page // flag something changes to prevent navigating from page
@@ -475,7 +476,8 @@ function initListInteractionOptions(element) {
curValue, // curValue curValue, // curValue
popupFormJson, // popupform popupFormJson, // popupform
toId, // parentSettingKey toId, // parentSettingKey
this // triggeredBy this, // triggeredBy
true // populate overrides
); );
} else { } else {
// Fallback to normal field input // Fallback to normal field input
@@ -1132,24 +1134,44 @@ function collectSetting(prefix, setCodeName, setType, settingsArray) {
// ------------------------------------------------------------------------------ // ------------------------------------------------------------------------------
// Generate the form control for setting // 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 = ''; let inputHtml = '';
// if override value is considered empty initialize from setting defaults // 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 setKey = set['setKey'];
const setType = set['setType']; const setType = set['setType'];
// if (setKey == 'NEWDEV_devParentMAC') { // if (setKey == 'UNIFIAPI_site_name') {
// console.log("==== DEBUG OUTPUT BELOW 1 ===="); // console.log("==== DEBUG OUTPUT BELOW 1 ====");
// console.log(setType); // console.log("populateFromOverrides: " + populateFromOverrides);
// console.log(setKey); // console.log(setType);
// console.log(overrideValue); // console.log(setKey);
// console.log(inVal); // console.log("overrideValue:" + overrideValue);
// console.log("inVal:" + inVal);
// } // console.log("set['setValue']:" + set['setValue']);
// }
// Parse the setType JSON string // Parse the setType JSON string
// console.log(processQuotes(setType)); // console.log(processQuotes(setType));
@@ -1189,15 +1211,14 @@ function generateFormHtml(settingsData, set, overrideValue, overrideOptions, ori
// Override value // Override value
let val = valRes; let val = valRes;
// if (setKey == 'NEWDEV_devParentMAC') { // if (setKey == 'UNIFIAPI_site_name') {
// console.log("==== DEBUG OUTPUT BELOW 2 ===="); // console.log("==== DEBUG OUTPUT BELOW 2 ====");
// console.log(setType); // console.log(setType);
// console.log(setKey); // console.log(setKey);
// console.log(overrideValue); // console.log("overrideValue:" + overrideValue);
// console.log(inVal); // console.log("inVal:" + inVal);
// console.log(val); // console.log("val:" + val);
// } // }
// Generate HTML based on elementType // Generate HTML based on elementType
@@ -1227,7 +1248,7 @@ function generateFormHtml(settingsData, set, overrideValue, overrideOptions, ori
break; break;
case 'input': 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'; const inputClass = inputType === 'checkbox' ? 'checkbox' : 'form-control';
inputHtml += `<input inputHtml += `<input
@@ -1347,22 +1368,23 @@ function generateFormHtml(settingsData, set, overrideValue, overrideOptions, ori
// Extract the value for the current column // Extract the value for the current column
let columnOverrideValue = rowData[j] && Object.values(rowData[j])[0]; let columnOverrideValue = rowData[j] && Object.values(rowData[j])[0];
if(columnOverrideValue == undefined)
{
columnOverrideValue = ""
}
// Create unique key to prevent dropdown data duplication // Create unique key to prevent dropdown data duplication
const oldKey = set["setKey"]; const oldKey = set["setKey"];
set["setKey"] = oldKey + "_" + index; set["setKey"] = oldKey + "_" + index;
// console.log("settingsData");
// console.log(settingsData);
// console.log(set);
// Generate the cell HTML using the extracted value // Generate the cell HTML using the extracted value
const cellHtml = generateFormHtml( const cellHtml = generateFormHtml(
settingsData, settingsData,
set, set,
columnOverrideValue.toString(), columnOverrideValue,
set["setOptions"], set["setOptions"],
oldKey oldKey,
false
); );
datatableHtml += `<td> <div class="input-group"> ${cellHtml} </div></td>`; datatableHtml += `<td> <div class="input-group"> ${cellHtml} </div></td>`;
+7 -3
View File
@@ -145,14 +145,18 @@ class NetAlertXStateManager {
.attr('data-version', version); .attr('data-version', version);
// 3. Update Build Timestamp placeholders // 3. Update Build Timestamp placeholders
const buildTime = appState["buildTimestamp"] !== undefined ? appState["buildTimestamp"] : ""; const buildTime = appState["buildTimestamp"] || 0;
const displayTime = (buildTime === 0) ? "UNKNOWN" : buildTime; const displayTime = buildTime ? localizeTimestamp(buildTime) : "UNKNOWN";
$('[data-plc="build-timestamp"]')
.html(displayTime)
.attr('data-build-time', buildTime);
$('[data-plc="build-timestamp"]') $('[data-plc="build-timestamp"]')
.html(displayTime) .html(displayTime)
.attr('data-build-time', buildTime); .attr('data-build-time', buildTime);
console.log("[NetAlertX State] UI updated via jQuery"); // console.log("[NetAlertX State] UI updated via jQuery");
} catch (e) { } catch (e) {
console.error("[NetAlertX State] Failed to update state display:", e); console.error("[NetAlertX State] Failed to update state display:", e);
} }
+4 -1
View File
@@ -668,7 +668,10 @@ function getColumnNameFromLangString(headStringKey) {
"Device_TableHead_CustomProps": "devCustomProps", "Device_TableHead_CustomProps": "devCustomProps",
"Device_TableHead_FQDN": "devFQDN", "Device_TableHead_FQDN": "devFQDN",
"Device_TableHead_ParentRelType": "devParentRelType", "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] || ""; return columnNameMap[headStringKey] || "";
+1 -1
View File
@@ -18,7 +18,7 @@ function renderFilterDropdown($headerKey, $columnName, $values) {
// Generate the dropdown HTML // Generate the dropdown HTML
return ' return '
<div class="filter-group input-group"> <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) . '"> <select id="filter_' . htmlspecialchars($columnName) . '" class="filter-dropdown" data-column="' . htmlspecialchars($columnName) . '">
' . $optionsHtml . ' ' . $optionsHtml . '
</select> </select>
+4 -1
View File
@@ -226,6 +226,8 @@
"Device_TableHead_FirstSession": "أول جلسة", "Device_TableHead_FirstSession": "أول جلسة",
"Device_TableHead_GUID": "معرف فريد", "Device_TableHead_GUID": "معرف فريد",
"Device_TableHead_Group": "المجموعة", "Device_TableHead_Group": "المجموعة",
"Device_TableHead_IPv4": "",
"Device_TableHead_IPv6": "",
"Device_TableHead_Icon": "الأيقونة", "Device_TableHead_Icon": "الأيقونة",
"Device_TableHead_LastIP": "آخر عنوان IP", "Device_TableHead_LastIP": "آخر عنوان IP",
"Device_TableHead_LastIPOrder": "ترتيب آخر عنوان IP", "Device_TableHead_LastIPOrder": "ترتيب آخر عنوان IP",
@@ -249,6 +251,7 @@
"Device_TableHead_SyncHubNodeName": "اسم عقدة المزامنة", "Device_TableHead_SyncHubNodeName": "اسم عقدة المزامنة",
"Device_TableHead_Type": "النوع", "Device_TableHead_Type": "النوع",
"Device_TableHead_Vendor": "المصنع", "Device_TableHead_Vendor": "المصنع",
"Device_TableHead_Vlan": "",
"Device_Table_Not_Network_Device": "ليس جهاز شبكة", "Device_Table_Not_Network_Device": "ليس جهاز شبكة",
"Device_Table_info": "معلومات الجدول", "Device_Table_info": "معلومات الجدول",
"Device_Table_nav_next": "التالي", "Device_Table_nav_next": "التالي",
@@ -783,4 +786,4 @@
"settings_system_label": "نظام", "settings_system_label": "نظام",
"settings_update_item_warning": "قم بتحديث القيمة أدناه. احرص على اتباع التنسيق السابق. <b>لم يتم إجراء التحقق.</b>", "settings_update_item_warning": "قم بتحديث القيمة أدناه. احرص على اتباع التنسيق السابق. <b>لم يتم إجراء التحقق.</b>",
"test_event_tooltip": "احفظ التغييرات أولاً قبل اختبار الإعدادات." "test_event_tooltip": "احفظ التغييرات أولاً قبل اختبار الإعدادات."
} }
+3
View File
@@ -226,6 +226,8 @@
"Device_TableHead_FirstSession": "Primera Sessió", "Device_TableHead_FirstSession": "Primera Sessió",
"Device_TableHead_GUID": "GUID", "Device_TableHead_GUID": "GUID",
"Device_TableHead_Group": "Grup", "Device_TableHead_Group": "Grup",
"Device_TableHead_IPv4": "",
"Device_TableHead_IPv6": "",
"Device_TableHead_Icon": "Icona", "Device_TableHead_Icon": "Icona",
"Device_TableHead_LastIP": "Darrera IP", "Device_TableHead_LastIP": "Darrera IP",
"Device_TableHead_LastIPOrder": "Últim Ordre d'IP", "Device_TableHead_LastIPOrder": "Últim Ordre d'IP",
@@ -249,6 +251,7 @@
"Device_TableHead_SyncHubNodeName": "Node Sync", "Device_TableHead_SyncHubNodeName": "Node Sync",
"Device_TableHead_Type": "Tipus", "Device_TableHead_Type": "Tipus",
"Device_TableHead_Vendor": "Venedor", "Device_TableHead_Vendor": "Venedor",
"Device_TableHead_Vlan": "",
"Device_Table_Not_Network_Device": "No configurat com a dispositiu de xarxa", "Device_Table_Not_Network_Device": "No configurat com a dispositiu de xarxa",
"Device_Table_info": "Mostrant _INICI_ a_FINAL_ d'entrades_ TOTALS", "Device_Table_info": "Mostrant _INICI_ a_FINAL_ d'entrades_ TOTALS",
"Device_Table_nav_next": "Següent", "Device_Table_nav_next": "Següent",
+3
View File
@@ -226,6 +226,8 @@
"Device_TableHead_FirstSession": "", "Device_TableHead_FirstSession": "",
"Device_TableHead_GUID": "", "Device_TableHead_GUID": "",
"Device_TableHead_Group": "", "Device_TableHead_Group": "",
"Device_TableHead_IPv4": "",
"Device_TableHead_IPv6": "",
"Device_TableHead_Icon": "", "Device_TableHead_Icon": "",
"Device_TableHead_LastIP": "", "Device_TableHead_LastIP": "",
"Device_TableHead_LastIPOrder": "", "Device_TableHead_LastIPOrder": "",
@@ -249,6 +251,7 @@
"Device_TableHead_SyncHubNodeName": "", "Device_TableHead_SyncHubNodeName": "",
"Device_TableHead_Type": "", "Device_TableHead_Type": "",
"Device_TableHead_Vendor": "", "Device_TableHead_Vendor": "",
"Device_TableHead_Vlan": "",
"Device_Table_Not_Network_Device": "", "Device_Table_Not_Network_Device": "",
"Device_Table_info": "", "Device_Table_info": "",
"Device_Table_nav_next": "", "Device_Table_nav_next": "",
+3
View File
@@ -230,6 +230,8 @@
"Device_TableHead_FirstSession": "Erste Sitzung", "Device_TableHead_FirstSession": "Erste Sitzung",
"Device_TableHead_GUID": "GUID", "Device_TableHead_GUID": "GUID",
"Device_TableHead_Group": "Gruppe", "Device_TableHead_Group": "Gruppe",
"Device_TableHead_IPv4": "",
"Device_TableHead_IPv6": "",
"Device_TableHead_Icon": "Icon", "Device_TableHead_Icon": "Icon",
"Device_TableHead_LastIP": "Letzte IP", "Device_TableHead_LastIP": "Letzte IP",
"Device_TableHead_LastIPOrder": "Letzte erhaltene IP", "Device_TableHead_LastIPOrder": "Letzte erhaltene IP",
@@ -253,6 +255,7 @@
"Device_TableHead_SyncHubNodeName": "Synchronisationsknoten", "Device_TableHead_SyncHubNodeName": "Synchronisationsknoten",
"Device_TableHead_Type": "Typ", "Device_TableHead_Type": "Typ",
"Device_TableHead_Vendor": "Hersteller", "Device_TableHead_Vendor": "Hersteller",
"Device_TableHead_Vlan": "",
"Device_Table_Not_Network_Device": "Nicht konfiguriert als Netzwerkgerät", "Device_Table_Not_Network_Device": "Nicht konfiguriert als Netzwerkgerät",
"Device_Table_info": "Zeige _START_ bis _END_ von _TOTAL_ Einträgen", "Device_Table_info": "Zeige _START_ bis _END_ von _TOTAL_ Einträgen",
"Device_Table_nav_next": "Nächste", "Device_Table_nav_next": "Nächste",
+3
View File
@@ -226,6 +226,8 @@
"Device_TableHead_FirstSession": "First Session", "Device_TableHead_FirstSession": "First Session",
"Device_TableHead_GUID": "GUID", "Device_TableHead_GUID": "GUID",
"Device_TableHead_Group": "Group", "Device_TableHead_Group": "Group",
"Device_TableHead_IPv4": "IPv4",
"Device_TableHead_IPv6": "IPv6",
"Device_TableHead_Icon": "Icon", "Device_TableHead_Icon": "Icon",
"Device_TableHead_LastIP": "Last IP", "Device_TableHead_LastIP": "Last IP",
"Device_TableHead_LastIPOrder": "Last IP Order", "Device_TableHead_LastIPOrder": "Last IP Order",
@@ -249,6 +251,7 @@
"Device_TableHead_SyncHubNodeName": "Sync Node", "Device_TableHead_SyncHubNodeName": "Sync Node",
"Device_TableHead_Type": "Type", "Device_TableHead_Type": "Type",
"Device_TableHead_Vendor": "Vendor", "Device_TableHead_Vendor": "Vendor",
"Device_TableHead_Vlan": "VLAN",
"Device_Table_Not_Network_Device": "Not configured as a network device", "Device_Table_Not_Network_Device": "Not configured as a network device",
"Device_Table_info": "Showing _START_ to _END_ of _TOTAL_ entries", "Device_Table_info": "Showing _START_ to _END_ of _TOTAL_ entries",
"Device_Table_nav_next": "Next", "Device_Table_nav_next": "Next",
+3
View File
@@ -228,6 +228,8 @@
"Device_TableHead_FirstSession": "1ra. sesión", "Device_TableHead_FirstSession": "1ra. sesión",
"Device_TableHead_GUID": "GUID", "Device_TableHead_GUID": "GUID",
"Device_TableHead_Group": "Grupo", "Device_TableHead_Group": "Grupo",
"Device_TableHead_IPv4": "",
"Device_TableHead_IPv6": "",
"Device_TableHead_Icon": "Icon", "Device_TableHead_Icon": "Icon",
"Device_TableHead_LastIP": "Última IP", "Device_TableHead_LastIP": "Última IP",
"Device_TableHead_LastIPOrder": "Última orden de IP", "Device_TableHead_LastIPOrder": "Última orden de IP",
@@ -251,6 +253,7 @@
"Device_TableHead_SyncHubNodeName": "Nodo de sincronización", "Device_TableHead_SyncHubNodeName": "Nodo de sincronización",
"Device_TableHead_Type": "Tipo", "Device_TableHead_Type": "Tipo",
"Device_TableHead_Vendor": "Fabricante", "Device_TableHead_Vendor": "Fabricante",
"Device_TableHead_Vlan": "",
"Device_Table_Not_Network_Device": "No está configurado como dispositivo de red", "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_info": "Mostrando el INICIO y el FINAL de TODAS las entradas",
"Device_Table_nav_next": "Siguiente", "Device_Table_nav_next": "Siguiente",
+3
View File
@@ -226,6 +226,8 @@
"Device_TableHead_FirstSession": "", "Device_TableHead_FirstSession": "",
"Device_TableHead_GUID": "", "Device_TableHead_GUID": "",
"Device_TableHead_Group": "", "Device_TableHead_Group": "",
"Device_TableHead_IPv4": "",
"Device_TableHead_IPv6": "",
"Device_TableHead_Icon": "", "Device_TableHead_Icon": "",
"Device_TableHead_LastIP": "", "Device_TableHead_LastIP": "",
"Device_TableHead_LastIPOrder": "", "Device_TableHead_LastIPOrder": "",
@@ -249,6 +251,7 @@
"Device_TableHead_SyncHubNodeName": "", "Device_TableHead_SyncHubNodeName": "",
"Device_TableHead_Type": "", "Device_TableHead_Type": "",
"Device_TableHead_Vendor": "", "Device_TableHead_Vendor": "",
"Device_TableHead_Vlan": "",
"Device_Table_Not_Network_Device": "", "Device_Table_Not_Network_Device": "",
"Device_Table_info": "", "Device_Table_info": "",
"Device_Table_nav_next": "", "Device_Table_nav_next": "",
+4 -1
View File
@@ -226,6 +226,8 @@
"Device_TableHead_FirstSession": "Première session", "Device_TableHead_FirstSession": "Première session",
"Device_TableHead_GUID": "GUID", "Device_TableHead_GUID": "GUID",
"Device_TableHead_Group": "Groupe", "Device_TableHead_Group": "Groupe",
"Device_TableHead_IPv4": "",
"Device_TableHead_IPv6": "",
"Device_TableHead_Icon": "Icône", "Device_TableHead_Icon": "Icône",
"Device_TableHead_LastIP": "Dernière IP", "Device_TableHead_LastIP": "Dernière IP",
"Device_TableHead_LastIPOrder": "Ordre dernière IP", "Device_TableHead_LastIPOrder": "Ordre dernière IP",
@@ -249,6 +251,7 @@
"Device_TableHead_SyncHubNodeName": "Noeud de synchro", "Device_TableHead_SyncHubNodeName": "Noeud de synchro",
"Device_TableHead_Type": "Type", "Device_TableHead_Type": "Type",
"Device_TableHead_Vendor": "Fabriquant", "Device_TableHead_Vendor": "Fabriquant",
"Device_TableHead_Vlan": "",
"Device_Table_Not_Network_Device": "Non configuré comme appareil du réseau", "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_info": "Affiche de _START_ à _END_ sur _TOTAL_ entrées",
"Device_Table_nav_next": "Suivant", "Device_Table_nav_next": "Suivant",
@@ -783,4 +786,4 @@
"settings_system_label": "Système", "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>", "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." "test_event_tooltip": "Enregistrer d'abord vos modifications avant de tester vôtre paramétrage."
} }
+4 -1
View File
@@ -226,6 +226,8 @@
"Device_TableHead_FirstSession": "Prima sessione", "Device_TableHead_FirstSession": "Prima sessione",
"Device_TableHead_GUID": "GUID", "Device_TableHead_GUID": "GUID",
"Device_TableHead_Group": "Gruppo", "Device_TableHead_Group": "Gruppo",
"Device_TableHead_IPv4": "",
"Device_TableHead_IPv6": "",
"Device_TableHead_Icon": "Icona", "Device_TableHead_Icon": "Icona",
"Device_TableHead_LastIP": "Ultimo IP", "Device_TableHead_LastIP": "Ultimo IP",
"Device_TableHead_LastIPOrder": "Ordina per ultimo IP", "Device_TableHead_LastIPOrder": "Ordina per ultimo IP",
@@ -249,6 +251,7 @@
"Device_TableHead_SyncHubNodeName": "Sincronizza nodo", "Device_TableHead_SyncHubNodeName": "Sincronizza nodo",
"Device_TableHead_Type": "Tipo", "Device_TableHead_Type": "Tipo",
"Device_TableHead_Vendor": "Produttore", "Device_TableHead_Vendor": "Produttore",
"Device_TableHead_Vlan": "",
"Device_Table_Not_Network_Device": "Non configurato come dispositivo di rete", "Device_Table_Not_Network_Device": "Non configurato come dispositivo di rete",
"Device_Table_info": "Visualizzazione da _START_ a _END_ di _TOTAL_ voci", "Device_Table_info": "Visualizzazione da _START_ a _END_ di _TOTAL_ voci",
"Device_Table_nav_next": "Successivo", "Device_Table_nav_next": "Successivo",
@@ -783,4 +786,4 @@
"settings_system_label": "Sistema", "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>", "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." "test_event_tooltip": "Salva le modifiche prima di provare le nuove impostazioni."
} }
+3
View File
@@ -226,6 +226,8 @@
"Device_TableHead_FirstSession": "初回セッション", "Device_TableHead_FirstSession": "初回セッション",
"Device_TableHead_GUID": "GUID", "Device_TableHead_GUID": "GUID",
"Device_TableHead_Group": "グループ", "Device_TableHead_Group": "グループ",
"Device_TableHead_IPv4": "",
"Device_TableHead_IPv6": "",
"Device_TableHead_Icon": "アイコン", "Device_TableHead_Icon": "アイコン",
"Device_TableHead_LastIP": "直近のIP", "Device_TableHead_LastIP": "直近のIP",
"Device_TableHead_LastIPOrder": "直近のIP順", "Device_TableHead_LastIPOrder": "直近のIP順",
@@ -249,6 +251,7 @@
"Device_TableHead_SyncHubNodeName": "同期ノード", "Device_TableHead_SyncHubNodeName": "同期ノード",
"Device_TableHead_Type": "種別", "Device_TableHead_Type": "種別",
"Device_TableHead_Vendor": "ベンダー", "Device_TableHead_Vendor": "ベンダー",
"Device_TableHead_Vlan": "",
"Device_Table_Not_Network_Device": "ネットワーク機器として構成されていない", "Device_Table_Not_Network_Device": "ネットワーク機器として構成されていない",
"Device_Table_info": "_START_~_END_を表示 / _TOTAL_ 件中", "Device_Table_info": "_START_~_END_を表示 / _TOTAL_ 件中",
"Device_Table_nav_next": "次", "Device_Table_nav_next": "次",
+3
View File
@@ -226,6 +226,8 @@
"Device_TableHead_FirstSession": "Første Økt", "Device_TableHead_FirstSession": "Første Økt",
"Device_TableHead_GUID": "GUID", "Device_TableHead_GUID": "GUID",
"Device_TableHead_Group": "Gruppe", "Device_TableHead_Group": "Gruppe",
"Device_TableHead_IPv4": "",
"Device_TableHead_IPv6": "",
"Device_TableHead_Icon": "Ikon", "Device_TableHead_Icon": "Ikon",
"Device_TableHead_LastIP": "Siste IP", "Device_TableHead_LastIP": "Siste IP",
"Device_TableHead_LastIPOrder": "Siste IP Rekkefølge", "Device_TableHead_LastIPOrder": "Siste IP Rekkefølge",
@@ -249,6 +251,7 @@
"Device_TableHead_SyncHubNodeName": "Synkroniser Node", "Device_TableHead_SyncHubNodeName": "Synkroniser Node",
"Device_TableHead_Type": "Type", "Device_TableHead_Type": "Type",
"Device_TableHead_Vendor": "Leverandør", "Device_TableHead_Vendor": "Leverandør",
"Device_TableHead_Vlan": "",
"Device_Table_Not_Network_Device": "Ikke konfigurert som en nettverksenhet", "Device_Table_Not_Network_Device": "Ikke konfigurert som en nettverksenhet",
"Device_Table_info": "Showing _START_ to _END_ of _TOTAL_ entries", "Device_Table_info": "Showing _START_ to _END_ of _TOTAL_ entries",
"Device_Table_nav_next": "Neste", "Device_Table_nav_next": "Neste",
+3
View File
@@ -226,6 +226,8 @@
"Device_TableHead_FirstSession": "Pierwsza sesja", "Device_TableHead_FirstSession": "Pierwsza sesja",
"Device_TableHead_GUID": "GUID", "Device_TableHead_GUID": "GUID",
"Device_TableHead_Group": "Grupa", "Device_TableHead_Group": "Grupa",
"Device_TableHead_IPv4": "",
"Device_TableHead_IPv6": "",
"Device_TableHead_Icon": "Ikona", "Device_TableHead_Icon": "Ikona",
"Device_TableHead_LastIP": "Ostatni adres IP", "Device_TableHead_LastIP": "Ostatni adres IP",
"Device_TableHead_LastIPOrder": "Kolejność ostatniego adresu IP", "Device_TableHead_LastIPOrder": "Kolejność ostatniego adresu IP",
@@ -249,6 +251,7 @@
"Device_TableHead_SyncHubNodeName": "Węzeł synchronizacji", "Device_TableHead_SyncHubNodeName": "Węzeł synchronizacji",
"Device_TableHead_Type": "Typ", "Device_TableHead_Type": "Typ",
"Device_TableHead_Vendor": "Producent", "Device_TableHead_Vendor": "Producent",
"Device_TableHead_Vlan": "",
"Device_Table_Not_Network_Device": "Nie skonfigurowano jako urządzenie sieciowe", "Device_Table_Not_Network_Device": "Nie skonfigurowano jako urządzenie sieciowe",
"Device_Table_info": "Pokazuje _START_ do _END_ z _TOTAL_ wpisów", "Device_Table_info": "Pokazuje _START_ do _END_ z _TOTAL_ wpisów",
"Device_Table_nav_next": "Następna", "Device_Table_nav_next": "Następna",
+3
View File
@@ -226,6 +226,8 @@
"Device_TableHead_FirstSession": "Primeira sessão", "Device_TableHead_FirstSession": "Primeira sessão",
"Device_TableHead_GUID": "GUID", "Device_TableHead_GUID": "GUID",
"Device_TableHead_Group": "Grupo", "Device_TableHead_Group": "Grupo",
"Device_TableHead_IPv4": "",
"Device_TableHead_IPv6": "",
"Device_TableHead_Icon": "Ícone", "Device_TableHead_Icon": "Ícone",
"Device_TableHead_LastIP": "Último IP", "Device_TableHead_LastIP": "Último IP",
"Device_TableHead_LastIPOrder": "Último pedido de IP", "Device_TableHead_LastIPOrder": "Último pedido de IP",
@@ -249,6 +251,7 @@
"Device_TableHead_SyncHubNodeName": "Nó de sincronização", "Device_TableHead_SyncHubNodeName": "Nó de sincronização",
"Device_TableHead_Type": "Tipo", "Device_TableHead_Type": "Tipo",
"Device_TableHead_Vendor": "Fornecedor", "Device_TableHead_Vendor": "Fornecedor",
"Device_TableHead_Vlan": "",
"Device_Table_Not_Network_Device": "Não configurado como um dispositivo de rede", "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_info": "Mostrando _START_ de _END_ do _TOTAL_ entradas",
"Device_Table_nav_next": "Próximo", "Device_Table_nav_next": "Próximo",
+3
View File
@@ -226,6 +226,8 @@
"Device_TableHead_FirstSession": "Primeira sessão", "Device_TableHead_FirstSession": "Primeira sessão",
"Device_TableHead_GUID": "GUID", "Device_TableHead_GUID": "GUID",
"Device_TableHead_Group": "Grupo", "Device_TableHead_Group": "Grupo",
"Device_TableHead_IPv4": "",
"Device_TableHead_IPv6": "",
"Device_TableHead_Icon": "Ícone", "Device_TableHead_Icon": "Ícone",
"Device_TableHead_LastIP": "Último IP", "Device_TableHead_LastIP": "Último IP",
"Device_TableHead_LastIPOrder": "Último pedido de IP", "Device_TableHead_LastIPOrder": "Último pedido de IP",
@@ -249,6 +251,7 @@
"Device_TableHead_SyncHubNodeName": "Nó de sincronização", "Device_TableHead_SyncHubNodeName": "Nó de sincronização",
"Device_TableHead_Type": "Tipo", "Device_TableHead_Type": "Tipo",
"Device_TableHead_Vendor": "Fornecedor", "Device_TableHead_Vendor": "Fornecedor",
"Device_TableHead_Vlan": "",
"Device_Table_Not_Network_Device": "Não configurado como um dispositivo de rede", "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_info": "A mostrar _START_ to _END_ of _TOTAL_ entradas",
"Device_Table_nav_next": "Próximo", "Device_Table_nav_next": "Próximo",
+3
View File
@@ -226,6 +226,8 @@
"Device_TableHead_FirstSession": "Первый сеанс", "Device_TableHead_FirstSession": "Первый сеанс",
"Device_TableHead_GUID": "GUID", "Device_TableHead_GUID": "GUID",
"Device_TableHead_Group": "Группа", "Device_TableHead_Group": "Группа",
"Device_TableHead_IPv4": "",
"Device_TableHead_IPv6": "",
"Device_TableHead_Icon": "Значок", "Device_TableHead_Icon": "Значок",
"Device_TableHead_LastIP": "Последний IP", "Device_TableHead_LastIP": "Последний IP",
"Device_TableHead_LastIPOrder": "Последний IP-запрос", "Device_TableHead_LastIPOrder": "Последний IP-запрос",
@@ -249,6 +251,7 @@
"Device_TableHead_SyncHubNodeName": "Узел синхронизации", "Device_TableHead_SyncHubNodeName": "Узел синхронизации",
"Device_TableHead_Type": "Тип", "Device_TableHead_Type": "Тип",
"Device_TableHead_Vendor": "Поставщик", "Device_TableHead_Vendor": "Поставщик",
"Device_TableHead_Vlan": "",
"Device_Table_Not_Network_Device": "Не настроено как сетевое устройство", "Device_Table_Not_Network_Device": "Не настроено как сетевое устройство",
"Device_Table_info": "Показаны с _START_ по _END_ из _TOTAL_ записей", "Device_Table_info": "Показаны с _START_ по _END_ из _TOTAL_ записей",
"Device_Table_nav_next": "Следующая", "Device_Table_nav_next": "Следующая",
+3
View File
@@ -226,6 +226,8 @@
"Device_TableHead_FirstSession": "", "Device_TableHead_FirstSession": "",
"Device_TableHead_GUID": "", "Device_TableHead_GUID": "",
"Device_TableHead_Group": "", "Device_TableHead_Group": "",
"Device_TableHead_IPv4": "",
"Device_TableHead_IPv6": "",
"Device_TableHead_Icon": "", "Device_TableHead_Icon": "",
"Device_TableHead_LastIP": "", "Device_TableHead_LastIP": "",
"Device_TableHead_LastIPOrder": "", "Device_TableHead_LastIPOrder": "",
@@ -249,6 +251,7 @@
"Device_TableHead_SyncHubNodeName": "", "Device_TableHead_SyncHubNodeName": "",
"Device_TableHead_Type": "", "Device_TableHead_Type": "",
"Device_TableHead_Vendor": "", "Device_TableHead_Vendor": "",
"Device_TableHead_Vlan": "",
"Device_Table_Not_Network_Device": "", "Device_Table_Not_Network_Device": "",
"Device_Table_info": "", "Device_Table_info": "",
"Device_Table_nav_next": "", "Device_Table_nav_next": "",
+3
View File
@@ -226,6 +226,8 @@
"Device_TableHead_FirstSession": "İlk Oturum", "Device_TableHead_FirstSession": "İlk Oturum",
"Device_TableHead_GUID": "GUID", "Device_TableHead_GUID": "GUID",
"Device_TableHead_Group": "Grup", "Device_TableHead_Group": "Grup",
"Device_TableHead_IPv4": "",
"Device_TableHead_IPv6": "",
"Device_TableHead_Icon": "İkon", "Device_TableHead_Icon": "İkon",
"Device_TableHead_LastIP": "Son IP", "Device_TableHead_LastIP": "Son IP",
"Device_TableHead_LastIPOrder": "Son IP Sırası", "Device_TableHead_LastIPOrder": "Son IP Sırası",
@@ -249,6 +251,7 @@
"Device_TableHead_SyncHubNodeName": "Senkronizasyon Node", "Device_TableHead_SyncHubNodeName": "Senkronizasyon Node",
"Device_TableHead_Type": "Tür", "Device_TableHead_Type": "Tür",
"Device_TableHead_Vendor": "Üretici", "Device_TableHead_Vendor": "Üretici",
"Device_TableHead_Vlan": "",
"Device_Table_Not_Network_Device": "Ağ cihazı olarak ayarlanmadı", "Device_Table_Not_Network_Device": "Ağ cihazı olarak ayarlanmadı",
"Device_Table_info": "Showing _START_ to _END_ of _TOTAL_ entries", "Device_Table_info": "Showing _START_ to _END_ of _TOTAL_ entries",
"Device_Table_nav_next": "Sonraki", "Device_Table_nav_next": "Sonraki",
+4 -1
View File
@@ -226,6 +226,8 @@
"Device_TableHead_FirstSession": "Перша сесія", "Device_TableHead_FirstSession": "Перша сесія",
"Device_TableHead_GUID": "GUID", "Device_TableHead_GUID": "GUID",
"Device_TableHead_Group": "Група", "Device_TableHead_Group": "Група",
"Device_TableHead_IPv4": "",
"Device_TableHead_IPv6": "",
"Device_TableHead_Icon": "Значок", "Device_TableHead_Icon": "Значок",
"Device_TableHead_LastIP": "Останній IP", "Device_TableHead_LastIP": "Останній IP",
"Device_TableHead_LastIPOrder": "Останнє замовлення IP", "Device_TableHead_LastIPOrder": "Останнє замовлення IP",
@@ -249,6 +251,7 @@
"Device_TableHead_SyncHubNodeName": "Вузол синхронізації", "Device_TableHead_SyncHubNodeName": "Вузол синхронізації",
"Device_TableHead_Type": "Тип", "Device_TableHead_Type": "Тип",
"Device_TableHead_Vendor": "Продавець", "Device_TableHead_Vendor": "Продавець",
"Device_TableHead_Vlan": "",
"Device_Table_Not_Network_Device": "Не налаштовано як мережевий пристрій", "Device_Table_Not_Network_Device": "Не налаштовано як мережевий пристрій",
"Device_Table_info": "Показано від _START_ до _END_ із _TOTAL_ записів", "Device_Table_info": "Показано від _START_ до _END_ із _TOTAL_ записів",
"Device_Table_nav_next": "Далі", "Device_Table_nav_next": "Далі",
@@ -783,4 +786,4 @@
"settings_system_label": "Система", "settings_system_label": "Система",
"settings_update_item_warning": "Оновіть значення нижче. Слідкуйте за попереднім форматом. <b>Перевірка не виконана.</b>", "settings_update_item_warning": "Оновіть значення нижче. Слідкуйте за попереднім форматом. <b>Перевірка не виконана.</b>",
"test_event_tooltip": "Перш ніж перевіряти налаштування, збережіть зміни." "test_event_tooltip": "Перш ніж перевіряти налаштування, збережіть зміни."
} }
+3
View File
@@ -226,6 +226,8 @@
"Device_TableHead_FirstSession": "加入", "Device_TableHead_FirstSession": "加入",
"Device_TableHead_GUID": "GUID", "Device_TableHead_GUID": "GUID",
"Device_TableHead_Group": "组", "Device_TableHead_Group": "组",
"Device_TableHead_IPv4": "",
"Device_TableHead_IPv6": "",
"Device_TableHead_Icon": "图标", "Device_TableHead_Icon": "图标",
"Device_TableHead_LastIP": "上次 IP", "Device_TableHead_LastIP": "上次 IP",
"Device_TableHead_LastIPOrder": "上次 IP 排序", "Device_TableHead_LastIPOrder": "上次 IP 排序",
@@ -249,6 +251,7 @@
"Device_TableHead_SyncHubNodeName": "同步节点", "Device_TableHead_SyncHubNodeName": "同步节点",
"Device_TableHead_Type": "类型", "Device_TableHead_Type": "类型",
"Device_TableHead_Vendor": "制造商", "Device_TableHead_Vendor": "制造商",
"Device_TableHead_Vlan": "",
"Device_Table_Not_Network_Device": "未配置为网络设备", "Device_Table_Not_Network_Device": "未配置为网络设备",
"Device_Table_info": "显示第_START_至 END_条_共_TOTAL_条", "Device_Table_info": "显示第_START_至 END_条_共_TOTAL_条",
"Device_Table_nav_next": "下一页", "Device_Table_nav_next": "下一页",
+32 -32
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", "function": "devSyncHubNode",
"type": { "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", "function": "devForceStatus",
"type": { "type": {
+6 -2
View File
@@ -440,7 +440,10 @@
"Device_TableHead_CustomProps", "Device_TableHead_CustomProps",
"Device_TableHead_FQDN", "Device_TableHead_FQDN",
"Device_TableHead_ParentRelType", "Device_TableHead_ParentRelType",
"Device_TableHead_ReqNicsOnline" "Device_TableHead_ReqNicsOnline",
"Device_TableHead_Vlan",
"Device_TableHead_IPv4",
"Device_TableHead_IPv6"
], ],
"localized": ["name", "description"], "localized": ["name", "description"],
"name": [ "name": [
@@ -517,7 +520,8 @@
"Device_TableHead_NetworkSite", "Device_TableHead_NetworkSite",
"Device_TableHead_SSID", "Device_TableHead_SSID",
"Device_TableHead_SourcePlugin", "Device_TableHead_SourcePlugin",
"Device_TableHead_ParentRelType" "Device_TableHead_ParentRelType",
"Device_TableHead_Vlan"
], ],
"localized": ["name", "description"], "localized": ["name", "description"],
"name": [ "name": [
+3 -3
View File
@@ -511,10 +511,10 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
} }
// INPUT // INPUT
inputFormHtml = generateFormHtml(settingsData, set, valIn, null, null); inputFormHtml = generateFormHtml(settingsData, set, valIn, null, null, false);
// construct final HTML for the setting // construct final HTML for the setting
setHtml += inputFormHtml + overrideHtml + ` setHtml += inputFormHtml + overrideHtml + `
</div> </div>
</div> </div>
` `
@@ -6,17 +6,46 @@ if [ -d "${NETALERTX_CONFIG}" ]; then
chmod u+rwX "${NETALERTX_CONFIG}" 2>/dev/null || true chmod u+rwX "${NETALERTX_CONFIG}" 2>/dev/null || true
fi fi
chmod u+rw "${NETALERTX_CONFIG}/app.conf" 2>/dev/null || true 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 # Check for app.conf and deploy if required
if [ ! -f "${NETALERTX_CONFIG}/app.conf" ]; then 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" || { install -m 600 /app/back/app.conf "${NETALERTX_CONFIG}/app.conf" || {
>&2 echo "ERROR: Failed to deploy default config to ${NETALERTX_CONFIG}/app.conf" >&2 echo "ERROR: Failed to deploy default config to ${NETALERTX_CONFIG}/app.conf"
exit 2 exit 2
} }
RESET=$(printf '\033[0m') >&2 printf "%s" "${CYAN}"
>&2 cat <<EOF >&2 cat <<EOF
══════════════════════════════════════════════════════════════════════════════ ══════════════════════════════════════════════════════════════════════════════
🆕 First run detected. Default configuration written to ${NETALERTX_CONFIG}/app.conf. 🆕 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. this instance in production.
══════════════════════════════════════════════════════════════════════════════ ══════════════════════════════════════════════════════════════════════════════
EOF EOF
>&2 printf "%s" "${RESET}"
>&2 printf "%s" "${RESET}"
fi fi
+3
View File
@@ -339,6 +339,9 @@ class Query(ObjectType):
"devFQDN", "devFQDN",
"devParentRelType", "devParentRelType",
"devParentMAC", "devParentMAC",
"devVlan",
"devPrimaryIPv4",
"devPrimaryIPv6"
] ]
search_term = options.search.lower() search_term = options.search.lower()
+6
View File
@@ -176,6 +176,12 @@ sql_devices_filters = """
SELECT DISTINCT 'devSyncHubNode' AS columnName, devSyncHubNode AS columnValue SELECT DISTINCT 'devSyncHubNode' AS columnName, devSyncHubNode AS columnValue
FROM Devices WHERE devSyncHubNode NOT IN ('', 'null') AND devSyncHubNode IS NOT NULL FROM Devices WHERE devSyncHubNode NOT IN ('', 'null') AND devSyncHubNode IS NOT NULL
UNION 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 SELECT DISTINCT 'devSSID' AS columnName, devSSID AS columnValue
FROM Devices WHERE devSSID NOT IN ('', 'null') AND devSSID IS NOT NULL FROM Devices WHERE devSSID NOT IN ('', 'null') AND devSSID IS NOT NULL
ORDER BY columnName; ORDER BY columnName;
-1
View File
@@ -448,4 +448,3 @@ def unlock_fields(conn, mac=None, fields=None, clear_all=False):
} }
finally: finally:
conn.close() conn.close()
-20
View File
@@ -590,26 +590,6 @@ def normalize_string(text):
# MAC and IP helper methods # 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): def generate_mac_links(html, deviceUrl):
p = re.compile(r"(?:[0-9a-fA-F]:?){12}") p = re.compile(r"(?:[0-9a-fA-F]:?){12}")
+64 -45
View File
@@ -3,6 +3,7 @@ import sys
import json import json
import uuid import uuid
import time import time
import fcntl
from flask import jsonify 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' 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 # Show Frontend User Notification
def write_notification(content, level="alert", timestamp=None): def write_notification(content, level="alert", timestamp=None):
""" """
@@ -36,50 +66,21 @@ def write_notification(content, level="alert", timestamp=None):
if timestamp is None: if timestamp is None:
timestamp = timeNowDB() timestamp = timeNowDB()
# Generate GUID
guid = str(uuid.uuid4())
# Prepare notification dictionary
notification = { notification = {
"timestamp": str(timestamp), "timestamp": str(timestamp),
"guid": guid, "guid": str(uuid.uuid4()),
"read": 0, "read": 0,
"level": level, "level": level,
"content": content, "content": content,
} }
# If file exists, load existing data, otherwise initialize as empty list def update(notifications):
try: notifications.append(notification)
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 = []
# Append new notification locked_notifications_file(update)
notifications.append(notification)
# Write updated data back to file
try: try:
with open(NOTIFICATION_API_FILE, "w") as file: unread_count = sum(1 for n in locked_notifications_file(lambda n: n) if n.get("read", 0) == 0)
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)
broadcast_unread_notifications_count(unread_count) broadcast_unread_notifications_count(unread_count)
except Exception as e: except Exception as e:
mylog("none", [f"[Notification] Failed to broadcast unread count: {e}"]) mylog("none", [f"[Notification] Failed to broadcast unread count: {e}"])
@@ -147,24 +148,42 @@ def mark_all_notifications_read():
"error": str (optional) "error": str (optional)
} }
""" """
# If notifications file does not exist, nothing to mark
if not os.path.exists(NOTIFICATION_API_FILE): if not os.path.exists(NOTIFICATION_API_FILE):
return {"success": True} return {"success": True}
try: try:
with open(NOTIFICATION_API_FILE, "r") as f: # Open file in read/write mode and acquire exclusive lock
notifications = json.load(f) with open(NOTIFICATION_API_FILE, "r+") as f:
except Exception as e: fcntl.flock(f, fcntl.LOCK_EX)
mylog("none", f"[Notification] Failed to read notifications: {e}")
return {"success": False, "error": str(e)}
for n in notifications: try:
n["read"] = 1 # 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: 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)} return {"success": False, "error": str(e)}
mylog("debug", "[Notification] All notifications marked as read.") mylog("debug", "[Notification] All notifications marked as read.")
+1
View File
@@ -110,6 +110,7 @@ FIELD_SPECS = {
"source_col": "devNameSource", "source_col": "devNameSource",
"empty_values": ["", "null", "(unknown)", "(name not found)"], "empty_values": ["", "null", "(unknown)", "(name not found)"],
"priority": ["NSLOOKUP", "AVAHISCAN", "NBTSCAN", "DIGSCAN", "ARPSCAN", "DHCPLSS", "NEWDEV", "N/A"], "priority": ["NSLOOKUP", "AVAHISCAN", "NBTSCAN", "DIGSCAN", "ARPSCAN", "DHCPLSS", "NEWDEV", "N/A"],
"allow_override_if_changed": True,
}, },
# ========================================================== # ==========================================================
+11 -6
View File
@@ -5,6 +5,7 @@ import base64
from pathlib import Path from pathlib import Path
from typing import Optional, Tuple from typing import Optional, Tuple
from logger import mylog from logger import mylog
from helper import is_random_mac
# Register NetAlertX directories # Register NetAlertX directories
INSTALL_PATH = os.getenv("NETALERTX_APP", "/app") INSTALL_PATH = os.getenv("NETALERTX_APP", "/app")
@@ -183,17 +184,21 @@ def guess_device_attributes(
type_ = None type_ = None
icon = 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) 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 --- # --- Loose Vendor-based fallback ---
if not type_ or type_ == default_type: if not type_ or type_ == default_type:
type_, icon = match_vendor(vendor, default_type, default_icon) 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 --- # --- Loose IP-based fallback ---
if (not type_ or type_ == default_type) or (not icon or icon == default_icon): if (not type_ or type_ == default_type) or (not icon or icon == default_icon):
type_, icon = match_ip(ip, default_type, 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. # Handler for when this is run as a program instead of called as a module.
if __name__ == "__main__": 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.")