diff --git a/README.md b/README.md
index ba0777a24..388db71d5 100755
--- a/README.md
+++ b/README.md
@@ -157,7 +157,7 @@ Check the [GitHub Issues](https://github.com/netalertx/NetAlertX/issues) for the
## Everything else
-
+
### 📧 Get notified what's new
@@ -170,6 +170,7 @@ Get notified about a new release, what new functionality you can use and about b
- [Fing](https://www.fing.com/) - Network scanner app for your Internet security (Commercial, Phone App, Proprietary hardware)
- [NetBox](https://netboxlabs.com/) - The gold standard for Network Source of Truth (NSoT) and IPAM.
- [Zabbix](https://www.zabbix.com/) or [Nagios](https://www.nagios.org/) - Strong focus on infrastructure monitoring.
+- [Domotz](https://www.domotz.com/) - Commercial network monitoring and remote management platform aimed at MSPs, IT teams, and multi-site environments.
- [NetAlertX](https://netalertx.com) - The streamlined, discovery-focused choice for real-time asset intelligence and noise-free alerting.
### 💙 Donations
diff --git a/back/app.conf b/back/app.conf
index 95fcf24f9..4add3c212 100755
--- a/back/app.conf
+++ b/back/app.conf
@@ -30,6 +30,7 @@ REPORT_DASHBOARD_URL='update_REPORT_DASHBOARD_URL_setting'
INTRNT_RUN='schedule'
ARPSCAN_RUN='schedule'
NSLOOKUP_RUN='before_name_updates'
+DIGSCAN_RUN='before_name_updates'
AVAHISCAN_RUN='before_name_updates'
NBTSCAN_RUN='before_name_updates'
diff --git a/docs/ADVISORY_EYES_ON_GLASS.md b/docs/ADVISORY_EYES_ON_GLASS.md
index ace8f0dcc..41803b762 100644
--- a/docs/ADVISORY_EYES_ON_GLASS.md
+++ b/docs/ADVISORY_EYES_ON_GLASS.md
@@ -4,6 +4,9 @@ For Managed Service Providers (MSPs) and Network Operations Centers (NOC), "Eyes

+> [!TIP]
+> If you are using Grafana check the `/metrics` endpoint that exposes **Prometheus-compatible metrics** for NetAlertX, including aggregate device counts and per-device status. See the [Metrics API endpoint](./API_METRICS.md) documentation for details.
+
---
### 1. Configure Auto-Refresh for Live Monitoring
diff --git a/docs/ADVISORY_MULTI_NETWORK.md b/docs/ADVISORY_MULTI_NETWORK.md
index 7a0dd0948..b56f58aef 100644
--- a/docs/ADVISORY_MULTI_NETWORK.md
+++ b/docs/ADVISORY_MULTI_NETWORK.md
@@ -12,20 +12,25 @@ Effective multi-network monitoring starts with understanding how NetAlertX "sees
* **Manual Entry:** For static assets where only ICMP (ping) status is needed.
> [!TIP]
-> Explore the [remote networks](./REMOTE_NETWORKS.md) documentation for more details on how to set up the approaches menationed above.
+> Explore the [remote networks](./REMOTE_NETWORKS.md) documentation for more details on how to set up the approaches mentioned above.
---
### 2. Automating IT Asset Inventory with Workflows
-[Workflows](./WORKFLOWS.md) are the "engine" of NetAlertX, reducing manual overhead as your device list grows.
+[Workflows](./WORKFLOWS.md) are the "engine" of NetAlertX, reducing manual overhead as your device list grows. See some examples below:
+
+#### A. Logical Ownership & VLAN Tagging
+
+Create a workflow triggered on **Device Creation** to:
-* **A. Logical Ownership & VLAN Tagging:** Create a workflow triggered on **Device Creation** to:
1. Inspect the IP/Subnet.
2. Set `devVlan` or `devOwner` custom fields automatically.
+#### B. Auto-Grouping:
+
+Use conditional logic to categorize devices.
-* **B. Auto-Grouping:** Use conditional logic to categorize devices.
* *Example:* If `devLastIP == 10.10.20.*`, then `Set devLocation = "BranchOffice"`.
```json
@@ -56,9 +61,9 @@ Effective multi-network monitoring starts with understanding how NetAlertX "sees
]
}
```
+#### C. Sync Node Tracking
-
-* **C. Sync Node Tracking:** When using multiple instances, ensure all synchub nodes have a descriptive `SYNC_node_name` name to distinguish between sites.
+When using multiple instances, ensure all sync hub nodes have a descriptive `SYNC_node_name` name to distinguish between sites.
> [!TIP]
> Always test new workflows in a "Staging" instance. A misconfigured workflow can trigger thousands of unintended updates across your database.
@@ -107,6 +112,7 @@ As your environment grows, tuning the underlying engine is vital to maintain a s
* **Plugin Scheduling:** Avoid "Scan Storms" by staggering plugin execution. Running intensive tasks like `NMAP` or `MASS_DNS` simultaneously can spike CPU and cause database locks.
* **Database Health:** Large-scale monitoring generates massive event logs. Use the **[DBCLNP (Database Cleanup)](https://www.google.com/search?q=https://docs.netalertx.com/PLUGINS/%23dbclnp)** plugin to prune old records and keep the SQLite database performant.
* **Resource Management:** For high-device counts, consider increasing the memory limit for the container and utilizing `tmpfs` for temporary files to reduce SD card/disk I/O bottlenecks.
+* Enable the `DEEP_SLEEP` setting.
> [!IMPORTANT]
> For a deep dive into hardware requirements, database vacuuming, and specific environment variables for high-load instances, refer to the full **[Performance Optimization Guide](https://docs.netalertx.com/PERFORMANCE/)**.
diff --git a/docs/PLUGINS_DEV_UI_COMPONENTS.md b/docs/PLUGINS_DEV_UI_COMPONENTS.md
index 663f52e0f..ef2d75fdf 100644
--- a/docs/PLUGINS_DEV_UI_COMPONENTS.md
+++ b/docs/PLUGINS_DEV_UI_COMPONENTS.md
@@ -7,6 +7,7 @@ Configure how your plugin's data is displayed in the NetAlertX web interface.
Plugin results are displayed in the UI via the **Plugins page** and **Device details tabs**. You control the appearance and functionality of these displays by defining `database_column_definitions` in your plugin's `config.json`.
Each column definition specifies:
+
- Which data field to display
- How to render it (label, link, color-coded badge, etc.)
- What CSS classes to apply
@@ -275,6 +276,7 @@ Replaces specific values with display strings or HTML.
```
**Output Examples:**
+
- `"online"` → 🟢 Online
- `"offline"` → 🔴 Offline
- `"idle"` → 🟡 Idle
diff --git a/docs/WORKFLOW_EXAMPLES.md b/docs/WORKFLOW_EXAMPLES.md
index a6c1b3f06..5f5da444b 100755
--- a/docs/WORKFLOW_EXAMPLES.md
+++ b/docs/WORKFLOW_EXAMPLES.md
@@ -45,22 +45,22 @@ Sometimes devices are manually archived (e.g., no longer expected on the network
],
"enabled": "Yes"
}
-```
+````
### 🔍 Explanation
- - Trigger: Listens for updates to device records.
- - Conditions:
- - `devIsArchived` is `1` (archived).
- - `devPresentLastScan` is `1` (device was detected in the latest scan).
- - Action: Updates the device to set `devIsArchived` to `0` (unarchived).
+* **Trigger**: Listens for updates to device records.
+* **Conditions**:
-### ✅ Result
+ * `devIsArchived` is `1` (archived).
+ * `devPresentLastScan` is `1` (device was detected in the latest scan).
+* **Action**:
-Whenever a previously archived device shows up during a network scan, it will be automatically unarchived — allowing it to reappear in your device lists and dashboards.
+ * Updates the device to set `devIsArchived` to `0` (unarchived).
+### ✅ Result
-Here is your updated version of **Example 2** and **Example 3**, fully aligned with the format and structure of **Example 1** for consistency and professionalism:
+Whenever a previously archived device shows up during a network scan, it will be automatically unarchived — allowing it to reappear in your device lists and dashboards.
---
@@ -107,7 +107,7 @@ When new devices join your network, assigning them to the correct network node i
### 🔍 Explanation
* **Trigger**: Activates when a new device is added.
-* **Condition**:
+* **Conditions**:
* `devLastIP` contains `192.168.1.` (matches subnet).
* **Action**:
@@ -173,12 +173,12 @@ You may want to automatically clear out newly detected Google devices (such as C
* **Trigger**: Runs on device updates.
* **Conditions**:
- * Vendor contains `Google`.
- * Device is marked as new (`devIsNew` is `1`).
+ * `devVendor` contains `Google`.
+ * `devIsNew` is `1` (device marked as new).
* **Actions**:
- 1. Set `devIsNew` to `0` (mark as not new).
- 2. Delete the device.
+ 1. Sets `devIsNew` to `0` (mark as not new).
+ 2. Deletes the device.
### ✅ Result
diff --git a/front/css/app.css b/front/css/app.css
index df77fc02e..24bbdf6b6 100755
--- a/front/css/app.css
+++ b/front/css/app.css
@@ -1417,6 +1417,7 @@ textarea[readonly],
grid-template-columns: repeat(auto-fit, minmax(125px, 1fr));
gap: 0.75em;
padding: 0.5em 0;
+ padding-top: 0;
}
#columnFilters::before,
diff --git a/front/deviceDetails.php b/front/deviceDetails.php
index 6c0a1180b..050672641 100755
--- a/front/deviceDetails.php
+++ b/front/deviceDetails.php
@@ -307,7 +307,10 @@ function updateChevrons(currentMac) {
pos = refreshedList.findIndex(item => item.devMac === currentMac);
if (pos === -1) {
- console.error('Still not found after re-cache:', currentMac);
+ console.warn('Device not found in device list after re-cache — hiding navigation controls:', currentMac);
+ $('#txtRecord').hide();
+ $('#btnPrevious').hide();
+ $('#btnNext').hide();
return;
}
@@ -499,26 +502,9 @@ function initializeTabs () {
}
}
-function updateDevicePageName(mac) {
- let name = getDevDataByMac(mac, "devName");
- let owner = getDevDataByMac(mac, "devOwner");
-
- // If data is missing, re-cache and retry once
- if (mac != 'new' && (name === null|| owner === null)) {
- console.warn("Device not found in cache, retrying after re-cache:", mac);
- showSpinner();
- cacheDevices(true).then(() => {
- hideSpinner();
- // Retry after successful cache
- updateDevicePageName(mac);
- }).catch((err) => {
- hideSpinner();
- console.error("Failed to refresh devices:", err);
- });
- return; // Exit early to avoid showing bad data
- }
-
- // Page title - Name
+// ----------------------------------------
+// Write device name/owner into page title DOM. Pure DOM side-effect, no data fetching.
+function applyDevicePageTitle(mac, name, owner) {
let pageTitleText;
if (mac === "new") {
@@ -530,12 +516,12 @@ function updateDevicePageName(mac) {
` ` + getString("Gen_create_new_device_info")
);
$('#devicePageInfoPlc').show();
- } else if (!owner || name.toString().includes(owner)) {
- pageTitleText = name;
+ } else if (!owner || (name && name.toString().includes(owner))) {
+ pageTitleText = encodeSpecialChars(name ?? getString("DevDetail_EveandAl_NewDevice"));
$('#pageTitle').html(pageTitleText);
$('#devicePageInfoPlc').hide();
} else {
- pageTitleText = `${name} (${owner})`;
+ pageTitleText = `${encodeSpecialChars(name ?? getString("DevDetail_EveandAl_NewDevice"))} (${encodeSpecialChars(owner)})`;
$('#pageTitle').html(pageTitleText);
$('#devicePageInfoPlc').hide();
}
@@ -544,6 +530,53 @@ function updateDevicePageName(mac) {
$('title').html(pageTitleText + ' - ' + $('title').html());
}
+// ----------------------------------------
+// Resolve device name/owner for the page title.
+// Stage 1: localStorage cache (synchronous, fast path).
+// Stage 2: one forced re-cache from table_devices.json.
+// Stage 3: REST API fallback so a direct-link visit never loops.
+async function updateDevicePageName(mac) {
+ let name = getDevDataByMac(mac, "devName");
+ let owner = getDevDataByMac(mac, "devOwner");
+
+ // Stage 2: one re-cache attempt
+ if (mac !== 'new' && name === null) {
+ console.warn("Device not in cache, attempting re-cache:", mac);
+ showSpinner();
+ try {
+ await cacheDevices(true);
+ } catch (err) {
+ console.error("Re-cache failed:", err);
+ } finally {
+ hideSpinner();
+ }
+ name = getDevDataByMac(mac, "devName");
+ owner = getDevDataByMac(mac, "devOwner");
+ }
+
+ // Stage 3: REST fallback — same endpoint renderSmallBoxes uses, always DB-direct
+ if (mac !== 'new' && name === null) {
+ console.warn("Device not found in cache after re-cache, falling back to REST API:", mac);
+ try {
+ const { apiBase, authHeader } = getAuthContext();
+ const res = await fetch(`${apiBase}/device/${encodeURIComponent(mac)}`, {
+ headers: authHeader
+ });
+ if (res.ok) {
+ const data = await res.json();
+ name = data.devName ?? null;
+ owner = data.devOwner ?? null;
+ } else {
+ console.error("REST fallback for device name returned:", res.status);
+ }
+ } catch (err) {
+ console.error("REST fallback error:", err);
+ }
+ }
+
+ applyDevicePageTitle(mac, name, owner);
+}
+
//-----------------------------------------------------------------------------------
diff --git a/front/deviceDetailsEdit.php b/front/deviceDetailsEdit.php
index 542f2a595..6c70ac4b3 100755
--- a/front/deviceDetailsEdit.php
+++ b/front/deviceDetailsEdit.php
@@ -236,6 +236,7 @@ function getDeviceData() {
// console.log(setting.setKey);
// console.log(fieldData);
+
// Additional form elements like the random MAC address button for devMac
let inlineControl = "";
// handle random mac
@@ -329,6 +330,11 @@ function getDeviceData() {
fieldOptionsOverride = fieldDataNew;
}
+ // XSS prevention - encode special characters for string fields, but not for arrays (like children dynamic)
+ // Don't move above the handle devChildrenDynamic block because it relies on the original fieldData to generate options
+ fieldData = encodeSpecialChars(fieldData);
+ // console.log(fieldData);
+
// Generate the input field HTML
const inputFormHtml = `