Compare commits

..

21 Commits

Author SHA1 Message Date
Jokob @NetAlertX
dfd2cf9e20 Merge pull request #1321 from jokob-sk/main
sync
2025-11-30 00:23:45 +00:00
jokob-sk
61824abb9f BE: restore previous version retrieval as a test #1320
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-11-30 11:21:24 +11:00
jokob-sk
33c5548fe1 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2025-11-30 11:15:25 +11:00
jokob-sk
fd41c395ae DOCS: old link removal
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-11-30 11:15:19 +11:00
jokob-sk
1a980844f0 BE: restore previous verison retrieval as a test #1320
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-11-30 11:14:45 +11:00
jokob-sk
82e018e284 FE: more defensive network topology hierarchy check #1308
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-11-30 10:55:08 +11:00
jokob-sk
e0e1233b1c DOCS: migration docs
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-11-30 10:27:33 +11:00
jokob-sk
74677f940e FE: more defensive network topology hierarchy check #1308
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-11-30 10:27:23 +11:00
Jokob @NetAlertX
21a4d20579 Merge pull request #1317 from mmomjian/main
Fix typo in warning message for read-only mode
2025-11-29 23:17:43 +00:00
jokob-sk
9634e4e0f7 FE: YYYY-DD-MM timestamp handling #1312
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-11-30 09:36:56 +11:00
jokob-sk
00a47ab5d3 FE: config backups saved in incorrect location #1311
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
Code checks / lint (push) Has been cancelled
Code checks / docker-tests (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-11-30 07:42:11 +11:00
Matthew Momjian
59b417705e Fix typo in warning message for read-only mode 2025-11-29 11:02:42 -05:00
jokob-sk
525d082f3d DOCS: volume
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-11-29 16:53:15 +11:00
jokob-sk
ba3481759b DOCS: Migration callouts
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
Code checks / lint (push) Has been cancelled
Code checks / docker-tests (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-11-29 16:50:06 +11:00
jokob-sk
7125cea29b DOCS: DB + config -> /data
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
Code checks / lint (push) Has been cancelled
Code checks / docker-tests (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-11-29 16:19:13 +11:00
jokob-sk
8586c5a307 FE: delay UI_DEFAULT_PAGE_SIZE setting check after cahce rebuilt #1181
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-11-29 15:45:28 +11:00
jokob-sk
0d81315809 PLG: PIHOLEAPI FAKE MAC #1282
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-11-29 14:18:54 +11:00
jokob-sk
8f193f1e2c Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2025-11-29 13:52:04 +11:00
jokob-sk
b1eef8aa09 PLG: PIHOLEAPI FAKE MAC #1282
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-11-29 13:51:16 +11:00
Massimo Pissarello
2da17f272c Translated using Weblate (Italian)
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
Code checks / lint (push) Has been cancelled
Code checks / docker-tests (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Currently translated at 100.0% (763 of 763 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/it/
2025-11-28 09:00:12 +01:00
jokob-sk
7bcb4586b2 FE: regex validation for cron run schedules
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-11-27 12:21:12 +11:00
25 changed files with 672 additions and 590 deletions

View File

@@ -32,6 +32,18 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# --- Previous approach Get release version from tag
- name: Set up dynamic build ARGs
id: getargs
run: echo "version=$(cat ./stable/VERSION)" >> $GITHUB_OUTPUT
- name: Get release version
id: get_version_prev
run: echo "::set-output name=version::${GITHUB_REF#refs/tags/}"
- name: Create .VERSION file
run: echo "${{ steps.get_version.outputs.version }}" >> .VERSION_PREV
# --- Get release version from tag
- name: Get release version
id: get_version

1
.gitignore vendored
View File

@@ -11,6 +11,7 @@ nohup.out
config/*
.ash_history
.VERSION
.VERSION_PREV
config/pialert.conf
config/app.conf
db/*

View File

@@ -138,6 +138,7 @@ RUN install -d -o ${NETALERTX_USER} -g ${NETALERTX_GROUP} -m 700 ${READ_WRITE_FO
# Copy version information into the image
COPY --chown=${NETALERTX_USER}:${NETALERTX_GROUP} .[V]ERSION ${NETALERTX_APP}/.VERSION
COPY --chown=${NETALERTX_USER}:${NETALERTX_GROUP} .[V]ERSION ${NETALERTX_APP}/.VERSION_PREV
# Copy the virtualenv from the builder stage
COPY --from=builder --chown=20212:20212 ${VIRTUAL_ENV} ${VIRTUAL_ENV}
@@ -147,12 +148,12 @@ COPY --from=builder --chown=20212:20212 ${VIRTUAL_ENV} ${VIRTUAL_ENV}
# This is done after the copy of the venv to ensure the venv is in place
# although it may be quicker to do it before the copy, it keeps the image
# layers smaller to do it after.
RUN if [ -f '.VERSION' ]; then \
cp '.VERSION' "${NETALERTX_APP}/.VERSION"; \
else \
echo "DEVELOPMENT 00000000" > "${NETALERTX_APP}/.VERSION"; \
fi && \
chown 20212:20212 "${NETALERTX_APP}/.VERSION" && \
RUN for vfile in .VERSION .VERSION_PREV; do \
if [ ! -f "${NETALERTX_APP}/${vfile}" ]; then \
echo "DEVELOPMENT 00000000" > "${NETALERTX_APP}/${vfile}"; \
fi; \
chown 20212:20212 "${NETALERTX_APP}/${vfile}"; \
done && \
apk add --no-cache libcap && \
setcap cap_net_raw+ep /bin/busybox && \
setcap cap_net_raw,cap_net_admin+eip /usr/bin/nmap && \

View File

@@ -34,9 +34,7 @@ Get visibility of what's going on on your WIFI/LAN network and enable presence d
## 🚀 Quick Start
> [!WARNING]
> ⚠️ **Important:** The documentation has been recently updated and some instructions may have changed.
> If you are using the currently live production image, please follow the instructions on [Docker Hub](https://hub.docker.com/r/jokobsk/netalertx) for building and running the container.
> These docs reflect the latest development version and may differ from the production image.
> ⚠️ **Important:** The docker-compose has recently changed. Carefully read the [Migration guide](https://jokob-sk.github.io/NetAlertX/MIGRATION/?h=migrat#12-migration-from-netalertx-v25524) for detailed instructions.
Start NetAlertX in seconds with Docker:
@@ -44,8 +42,7 @@ Start NetAlertX in seconds with Docker:
docker run -d \
--network=host \
--restart unless-stopped \
-v /local_data_dir/config:/data/config \
-v /local_data_dir/db:/data/db \
-v /local_data_dir:/data \
-v /etc/localtime:/etc/localtime:ro \
--tmpfs /tmp:uid=20211,gid=20211,mode=1700 \
-e PORT=20211 \
@@ -53,6 +50,8 @@ docker run -d \
ghcr.io/jokob-sk/netalertx:latest
```
Note: Your `/local_data_dir` should contain a `config` and `db` folder.
To deploy a containerized instance directly from the source repository, execute the following BASH sequence:
```bash
git clone https://github.com/jokob-sk/NetAlertX.git

View File

@@ -16,8 +16,7 @@ Start the container via the **terminal** with a command similar to this one:
docker run \
--network=host \
--restart unless-stopped \
-v /local_data_dir/config:/data/config \
-v /local_data_dir/db:/data/db \
-v /local_data_dir:/data \
-v /etc/localtime:/etc/localtime:ro \
--tmpfs /tmp:uid=20211,gid=20211,mode=1700 \
-e PORT=20211 \
@@ -26,6 +25,8 @@ docker run \
```
Note: Your `/local_data_dir` should contain a `config` and `db` folder.
> [!NOTE]
> ⚠ The most important part is NOT to use the `-d` parameter so you see the error when the container crashes. Use this error in your issue description.

View File

@@ -1,9 +1,7 @@
# NetAlertX and Docker Compose
> [!WARNING]
> ⚠️ **Important:** The documentation has been recently updated and some instructions may have changed.
> If you are using the currently live production image, please follow the instructions on [Docker Hub](https://hub.docker.com/r/jokobsk/netalertx) for building and running the container.
> These docs reflect the latest development version and may differ from the production image.
> ⚠️ **Important:** The docker-compose has recently changed. Carefully read the [Migration guide](https://jokob-sk.github.io/NetAlertX/MIGRATION/?h=migrat#12-migration-from-netalertx-v25524) for detailed instructions.
Great care is taken to ensure NetAlertX meets the needs of everyone while being flexible enough for anyone. This document outlines how you can configure your docker-compose. There are many settings, so we recommend using the Baseline Docker Compose as-is, or modifying it for your system.Good care is taken to ensure NetAlertX meets the needs of everyone while being flexible enough for anyone. This document outlines how you can configure your docker-compose. There are many settings, so we recommend using the Baseline Docker Compose as-is, or modifying it for your system.
@@ -125,9 +123,9 @@ docker compose up
### Modification 1: Use a Local Folder (Bind Mount)
By default, the baseline compose file uses a single named volume (netalertx_data) mounted at /data. This single-volume layout is preferred because NetAlertX manages both configuration and the database under /data (for example, /data/config and /data/db) via its web UI. Using one named volume simplifies permissions and portability: Docker manages the storage and NetAlertX manages the files inside /data.
By default, the baseline compose file uses a single named volume (netalertx_data) mounted at `/data`. This single-volume layout is preferred because NetAlertX manages both configuration and the database under `/data` (for example, `/data/config` and `/data/db`) via its web UI. Using one named volume simplifies permissions and portability: Docker manages the storage and NetAlertX manages the files inside `/data`.
A two-volume layout that mounts /data/config and /data/db separately (for example, netalertx_config and netalertx_db) is supported for backward compatibility and some advanced workflows, but it is an abnormal/legacy layout and not recommended for new deployments.
A two-volume layout that mounts `/data/config` and `/data/db` separately (for example, `netalertx_config` and `netalertx_db`) is supported for backward compatibility and some advanced workflows, but it is an abnormal/legacy layout and not recommended for new deployments.
However, if you prefer to have direct, file-level access to your configuration for manual editing, a "bind mount" is a simple alternative. This tells Docker to use a specific folder from your computer (the "host") inside the container.

View File

@@ -25,8 +25,7 @@ Head to [https://netalertx.com/](https://netalertx.com/) for more gifs and scree
```bash
docker run -d --rm --network=host \
-v /local_data_dir/config:/data/config \
-v /local_data_dir/db:/data/db \
-v /local_data_dir:/data \
-v /etc/localtime:/etc/localtime \
--tmpfs /tmp:uid=20211,gid=20211,mode=1700 \
-e PORT=20211 \
@@ -62,8 +61,7 @@ See alternative [docked-compose examples](https://github.com/jokob-sk/NetAlertX/
| Required | Path | Description |
| :------------- | :------------- | :-------------|
| ✅ | `:/data/config` | Folder which will contain the `app.conf` & `devices.csv` ([read about devices.csv](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICES_BULK_EDITING.md)) files |
| ✅ | `:/data/db` | Folder which will contain the `app.db` database file |
| ✅ | `:/data` | Folder which will contain the `/db/app.db`, `/config/app.conf` & `/config/devices.csv` ([read about devices.csv](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICES_BULK_EDITING.md)) files |
| ✅ | `/etc/localtime:/etc/localtime:ro` | Ensuring the timezone is teh same as on teh server. |
| | `:/tmp/log` | Logs folder useful for debugging if you have issues setting up the container |
| | `:/tmp/api` | The [API endpoint](https://github.com/jokob-sk/NetAlertX/blob/main/docs/API.md) containing static (but regularly updated) json and other files. Path configurable via `NETALERTX_API` environment variable. |

View File

@@ -1,9 +1,7 @@
# The NetAlertX Container Operator's Guide
> [!WARNING]
> ⚠️ **Important:** The documentation has been recently updated and some instructions may have changed.
> If you are using the currently live production image, please follow the instructions on [Docker Hub](https://hub.docker.com/r/jokobsk/netalertx) for building and running the container.
> These docs reflect the latest development version and may differ from the production image.
> ⚠️ **Important:** The docker-compose has recently changed. Carefully read the [Migration guide](https://jokob-sk.github.io/NetAlertX/MIGRATION/?h=migrat#12-migration-from-netalertx-v25524) for detailed instructions.
This guide assumes you are starting with the official `docker-compose.yml` file provided with the project. We strongly recommend you start with or migrate to this file as your baseline and modify it to suit your specific needs (e.g., changing file paths). While there are many ways to configure NetAlertX, the default file is designed to meet the mandatory security baseline with layer-2 networking capabilities while operating securely and without startup warnings.

View File

@@ -78,7 +78,7 @@ In the **Environment variables** section of Portainer, add the following:
>
> `sudo chown -R 20211:20211 /local_data_dir`
>
> `sudo chmod -R a+rwx /local_data_dir1`
> `sudo chmod -R a+rwx /local_data_dir`
>

View File

@@ -46,8 +46,7 @@ NetAlertX requires certain paths to be writable at runtime. These paths should b
```bash
docker run -it --rm --name netalertx --user "0" \
-v /local_data_dir/config:/data/config \
-v /local_data_dir/db:/data/db \
-v /local_data_dir:/data \
--tmpfs /tmp:uid=20211,gid=20211,mode=1700 \
ghcr.io/jokob-sk/netalertx:latest
```
@@ -63,7 +62,7 @@ docker run -it --rm --name netalertx --user "0" \
>
> `sudo chown -R 20211:20211 /local_data_dir`
>
> `sudo chmod -R a+rwx /local_data_dir1`
> `sudo chmod -R a+rwx /local_data_dir`
>
---
@@ -84,8 +83,7 @@ services:
- NET_BIND_SERVICE # Required to bind to privileged ports (nbtscan)
restart: unless-stopped
volumes:
- /local_data_dir/config:/data/config
- /local_data_dir/db:/data/db
- /local_data_dir:/data
- /etc/localtime:/etc/localtime
environment:
- PORT=20211

View File

@@ -1,11 +1,5 @@
# Migration
> [!WARNING]
> ⚠️ **Important:** The documentation has been recently updated and some instructions may have changed.
> If you are using the currently live production image, please follow the instructions on [Docker Hub](https://hub.docker.com/r/jokobsk/netalertx) for building and running the container.
> These docs reflect the latest development version and may differ from the production image.
When upgrading from older versions of NetAlertX (or PiAlert by jokob-sk), follow the migration steps below to ensure your data and configuration are properly transferred.
> [!TIP]
@@ -259,12 +253,11 @@ docker run -it --rm --name netalertx --user "0" \
ghcr.io/jokob-sk/netalertx:latest
```
..or alternatively execute:
...or alternatively execute:
```bash
sudo chown -R 20211:20211 /local_data_dir/config
sudo chown -R 20211:20211 /local_data_dir/db
sudo chmod -R a+rwx /local_data_dir/
sudo chown -R 20211:20211 /local_data_dir
sudo chmod -R a+rwx /local_data_dir
```
7. Stop the container
@@ -284,8 +277,7 @@ services:
- NET_BIND_SERVICE # 🆕 New line
restart: unless-stopped
volumes:
- /local_data_dir/config:/data/config # 🆕 This has changed from /app to /data
- /local_data_dir/db:/data/db # 🆕 This has changed from /app to /data
- /local_data_dir:/data # 🆕 This folder contains your /db and /config directories and the parent changed from /app to /data
# Ensuring the timezone is the same as on the server - make sure also the TIMEZONE setting is configured
- /etc/localtime:/etc/localtime:ro # 🆕 New line
environment:

View File

@@ -63,7 +63,6 @@ There is also an in-app Help / FAQ section that should be answering frequently a
#### ♻ Misc
- [Version history (legacy)](./VERSIONS_HISTORY.md)
- [Reverse proxy (Nginx, Apache, SWAG)](./REVERSE_PROXY.md)
- [Installing Updates](./UPDATES.md)
- [Setting up Authelia](./AUTHELIA.md) (DRAFT)

View File

@@ -47,8 +47,7 @@ services:
- NET_ADMIN
- NET_BIND_SERVICE
volumes:
- /app_storage/netalertx/config:/data/config
- /app_storage/netalertx/db:/data/db
- /app_storage/netalertx:/data
# to sync with system time
- /etc/localtime:/etc/localtime:ro
tmpfs:
@@ -66,10 +65,7 @@ services:
```yaml
volumes:
- /volume1/app_storage/netalertx/config:/data/config
- /volume1/app_storage/netalertx/db:/data/db
# (optional) useful for debugging if you have issues setting up the container
# - local/path/logs:/tmp/log <- commented out with # ⚠
- /volume1/app_storage/netalertx:/data
```
![Adjusting docker-compose](./img/SYNOLOGY/08_Adjust_docker_compose_volumes.png)
@@ -88,5 +84,5 @@ services:
>
> `sudo chown -R 20211:20211 /local_data_dir`
>
> `sudo chmod -R a+rwx /local_data_dir1`
> `sudo chmod -R a+rwx /local_data_dir`
>

View File

@@ -72,7 +72,7 @@ a[target="_blank"] {
[data-is-valid="0"] {
/* border: 1px solid red; */
background-color: #ff4b4b;
background-color: #ff4b4b !important;
}
/* -----------------------------------------------------------------------------

View File

@@ -136,7 +136,7 @@
<!-- page script ----------------------------------------------------------- -->
<script>
var deviceStatus = 'all';
var tableRows = getCache ("nax_parTableRows") == "" ? parseInt(getSetting("UI_DEFAULT_PAGE_SIZE")) : getCache ("nax_parTableRows") ;
var tableOrder = getCache ("nax_parTableOrder") == "" ? [[3,'desc'], [0,'asc']] : JSON.parse(getCache ("nax_parTableOrder")) ;
var tableColumnHide = [];
@@ -563,6 +563,9 @@ function initializeDatatable (status) {
status = 'my_devices'
}
// retrieve page size
var tableRows = getCache ("nax_parTableRows") == "" ? parseInt(getSetting("UI_DEFAULT_PAGE_SIZE")) : getCache ("nax_parTableRows") ;
// Save status selected
deviceStatus = status;

View File

@@ -378,7 +378,7 @@ function localizeTimestamp(input) {
let tz = getSetting("TIMEZONE") || 'Europe/Berlin';
input = String(input || '').trim();
// 1. Unix timestamps (10 or 13 digits)
// 1. Unix timestamps (10 or 13 digits)
if (/^\d+$/.test(input)) {
const ms = input.length === 10 ? parseInt(input, 10) * 1000 : parseInt(input, 10);
return new Intl.DateTimeFormat('default', {
@@ -389,7 +389,7 @@ function localizeTimestamp(input) {
}).format(new Date(ms));
}
// 2. European DD/MM/YYYY
// 2. European DD/MM/YYYY
let match = input.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:[ ,]+(\d{1,2}:\d{2}(?::\d{2})?))?(.*)$/);
if (match) {
let [, d, m, y, t = "00:00:00", tzPart = ""] = match;
@@ -397,7 +397,7 @@ function localizeTimestamp(input) {
return formatSafe(iso, tz);
}
// 3. US MM/DD/YYYY
// 3. US MM/DD/YYYY
match = input.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:[ ,]+(\d{1,2}:\d{2}(?::\d{2})?))?(.*)$/);
if (match) {
let [, m, d, y, t = "00:00:00", tzPart = ""] = match;
@@ -405,23 +405,37 @@ function localizeTimestamp(input) {
return formatSafe(iso, tz);
}
// 4. ISO-style (with T, Z, offsets)
match = input.match(/^(\d{4}-\d{1,2}-\d{1,2})[ T](\d{1,2}:\d{2}(?::\d{2})?)(Z|[+-]\d{2}:?\d{2})?$/);
// 4. ISO YYYY-MM-DD with optional Z/+offset
match = input.match(/^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])[ T](\d{1,2}:\d{2}(?::\d{2})?)(Z|[+-]\d{2}:?\d{2})?$/);
if (match) {
let [ , ymd, time, offset = "" ] = match;
// normalize to YYYY-MM-DD
let [y, m, d] = ymd.split('-').map(x => x.padStart(2,'0'));
let [, y, m, d, time, offset = ""] = match;
const iso = `${y}-${m}-${d}T${time.length===5?time+":00":time}${offset}`;
return formatSafe(iso, tz);
}
// 5. RFC2822 / "25 Aug 2025 13:45:22 +0200"
// 5. RFC2822 / "25 Aug 2025 13:45:22 +0200"
match = input.match(/^\d{1,2} [A-Za-z]{3,} \d{4}/);
if (match) {
return formatSafe(input, tz);
}
// 6. Fallback (whatever Date() can parse)
// 6. DD-MM-YYYY with optional time
match = input.match(/^(\d{1,2})-(\d{1,2})-(\d{4})(?:[ T](\d{1,2}:\d{2}(?::\d{2})?))?$/);
if (match) {
let [, d, m, y, time = "00:00:00"] = match;
const iso = `${y}-${m.padStart(2,'0')}-${d.padStart(2,'0')}T${time.length===5?time+":00":time}`;
return formatSafe(iso, tz);
}
// 7. Strict YYYY-DD-MM with optional time
match = input.match(/^(\d{4})-(0[1-9]|[12]\d|3[01])-(0[1-9]|1[0-2])(?:[ T](\d{1,2}:\d{2}(?::\d{2})?))?$/);
if (match) {
let [, y, d, m, time = "00:00:00"] = match;
const iso = `${y}-${m}-${d}T${time.length === 5 ? time + ":00" : time}`;
return formatSafe(iso, tz);
}
// 8. Fallback
return formatSafe(input, tz);
function formatSafe(str, tz) {
@@ -440,6 +454,7 @@ function localizeTimestamp(input) {
}
// ----------------------------------------------------
/**
* Replaces double quotes within single-quoted strings, then converts all single quotes to double quotes,
@@ -1622,7 +1637,6 @@ async function executeOnce() {
if (!isAppInitialized()) {
try {
console.log("HERE");
await waitForGraphQLServer(); // Wait for the server to start
@@ -1630,7 +1644,7 @@ async function executeOnce() {
await cacheSettings();
await cacheStrings();
console.log("All AJAX callbacks have completed");
console.log("All AJAX callbacks have completed");
onAllCallsComplete();
} catch (error) {
console.error("Error:", error);

View File

@@ -521,12 +521,16 @@ function getChildren(node, list, path, visited = [])
// Loop through all items to find children of the current node
for (var i in list) {
if (list[i].devParentMAC.toLowerCase() == node.devMac.toLowerCase() && !hiddenMacs.includes(list[i].devParentMAC)) {
const item = list[i];
const parentMac = item.devParentMAC || ""; // null-safe
const nodeMac = node.devMac || ""; // null-safe
if (parentMac != "" && parentMac.toLowerCase() == nodeMac.toLowerCase() && !hiddenMacs.includes(parentMac)) {
visibleNodesCount++;
// Process children recursively, passing a copy of the visited list
children.push(getChildren(list[i], list, path + ((path == "") ? "" : '|') + list[i].devParentMAC, visited));
children.push(getChildren(list[i], list, path + ((path == "") ? "" : '|') + parentMac, visited));
}
}
@@ -565,14 +569,27 @@ function getChildren(node, list, path, visited = [])
// ---------------------------------------------------------------------------
function getHierarchy()
{
let internetNode = null;
for(i in deviceListGlobal)
{
if(deviceListGlobal[i].devMac == 'Internet')
{
return (getChildren(deviceListGlobal[i], deviceListGlobal, ''))
internetNode = deviceListGlobal[i];
return (getChildren(internetNode, deviceListGlobal, ''))
break;
}
}
if (!internetNode) {
showModalOk(
getString('Network_Configuration_Error'),
getString('Network_Root_Not_Configured')
);
console.error("getHierarchy(): Internet node not found");
return null;
}
}
//---------------------------------------------------------------------------
@@ -671,8 +688,6 @@ function handleNodeClick(el)
// ---------------------------------------------------------------------------
var myTree;
var emSize;
var nodeHeight;
// var sizeCoefficient = 1.4
@@ -689,10 +704,12 @@ function emToPx(em, element) {
function initTree(myHierarchy)
{
// calculate the drawing area based on teh tree width and available screen size
if(myHierarchy && myHierarchy.type !== "")
{
// calculate the drawing area based on the tree width and available screen size
let baseFontSize = parseFloat($('html').css('font-size'));
let treeAreaHeight = ($(window).height() - 155); ;
// calculate the font size of the leaf nodes to fit everything into the tree area
leafNodesCount == 0 ? 1 : leafNodesCount;
@@ -703,13 +720,6 @@ function initTree(myHierarchy)
// init the drawing area size
$("#networkTree").attr('style', `height:${treeAreaHeight}px; width:${emToPx(screenWidthEm)}px`)
if(myHierarchy.type == "")
{
showModalOk(getString('Network_Configuration_Error'), getString('Network_Root_Not_Configured'))
return;
}
// handle canvas and node size if only a few nodes
emSize > 1 ? emSize = 1 : emSize = emSize;
@@ -823,6 +833,10 @@ function initTree(myHierarchy)
// hide spinning icon
hideSpinner()
} else
{
console.error("getHierarchy() not returning expected result");
}
}

View File

@@ -303,7 +303,7 @@ function saveSettings()
// save to the file
$new_name = $config_file.'_'.$timestamp.'.backup';
$new_location = $configFolderPath.$new_name;
$new_location = $configFolderPath.'/'.$new_name;
if(file_exists( $fullConfPath) != 1)
{

2
front/php/templates/language/it_it.json Executable file → Normal file
View File

@@ -311,7 +311,7 @@
"Gen_Filter": "Filtro",
"Gen_Generate": "Genera",
"Gen_InvalidMac": "Indirizzo Mac non valido.",
"Gen_Invalid_Value": "",
"Gen_Invalid_Value": "È stato inserito un valore non valido",
"Gen_LockedDB": "ERRORE: il DB potrebbe essere bloccato, controlla F12 Strumenti di sviluppo -> Console o riprova più tardi.",
"Gen_NetworkMask": "Maschera di rete",
"Gen_Offline": "Offline",

View File

@@ -5,7 +5,6 @@
import os
import subprocess
import sys
import hashlib
import re
import nmap
@@ -17,6 +16,7 @@ from plugin_helper import Plugin_Objects # noqa: E402 [flake8 lint suppression]
from logger import mylog, Logger # noqa: E402 [flake8 lint suppression]
from helper import get_setting_value # noqa: E402 [flake8 lint suppression]
from const import logPath # noqa: E402 [flake8 lint suppression]
from utils.crypto_utils import string_to_mac_hash # noqa: E402 [flake8 lint suppression]
import conf # noqa: E402 [flake8 lint suppression]
from pytz import timezone # noqa: E402 [flake8 lint suppression]
@@ -177,16 +177,6 @@ def parse_nmap_xml(xml_output, interface, fakeMac):
return devices_list
def string_to_mac_hash(input_string):
# Calculate a hash using SHA-256
sha256_hash = hashlib.sha256(input_string.encode()).hexdigest()
# Take the first 12 characters of the hash and format as a MAC address
mac_hash = ':'.join(sha256_hash[i:i + 2] for i in range(0, 12, 2))
return mac_hash
# ===============================================================================
# BEGIN
# ===============================================================================

View File

@@ -13,9 +13,6 @@ The plugin connects to your Pi-holes API and retrieves:
NetAlertX then uses this information to match or create devices in your system.
> [!TIP]
> Some tip.
### Quick setup guide
* You are running **Pi-hole v6** or newer.
@@ -35,16 +32,8 @@ No additional Pi-hole configuration is required.
| **PIHOLEAPI_SSL_VERIFY** | Whether to verify HTTPS certificates. Disable only for self-signed certificates. |
| **PIHOLEAPI_RUN_TIMEOUT** | Request timeout in seconds. |
| **PIHOLEAPI_API_MAXCLIENTS** | Maximum number of devices to request from Pi-hole. Defaults are usually fine. |
| **PIHOLEAPI_FAKE_MAC** | Generate FAKE MAC from IP. |
### Example Configuration
| Setting Key | Sample Value |
| ---------------------------- | -------------------------------------------------- |
| **PIHOLEAPI_URL** | `http://pi.hole/` |
| **PIHOLEAPI_PASSWORD** | `passw0rd` |
| **PIHOLEAPI_SSL_VERIFY** | `true` |
| **PIHOLEAPI_RUN_TIMEOUT** | `30` |
| **PIHOLEAPI_API_MAXCLIENTS** | `500` |
### ⚠️ Troubleshooting
@@ -110,6 +99,32 @@ Then re-run the plugin.
---
#### ❌ Some devices are missing
Check:
* Pi-hole shows devices under **Settings → Network**
* NetAlertX logs contain:
```
[PIHOLEAPI] Skipping invalid MAC (see PIHOLEAPI_FAKE_MAC setting) ...
```
If devices are missing:
* The app skipps devices with invalid MACs
* Enable PIHOLEAPI_FAKE_MAC if you want to import these devices with a fake mac and you are not concerned with data inconsistencies later on
Try enabling PIHOLEAPI_FAKE_MAC:
```
PIHOLEAPI_FAKE_MAC = 1
```
Then re-run the plugin.
---
#### ❌ Wrong or missing hostnames
Pi-hole only reports names it knows from:

View File

@@ -279,6 +279,41 @@
"string": "Maximum time in seconds to wait for the script to finish. If this time is exceeded the script is aborted."
}
]
},
{
"function": "FAKE_MAC",
"type": {
"dataType": "boolean",
"elements": [
{
"elementType": "input",
"elementOptions": [
{
"type": "checkbox"
}
],
"transformers": []
}
]
},
"default_value": false,
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Fake MAC if empty"
}
],
"description": [
{
"language_code": "en_us",
"string": "Some PiHole devices don't have a MAC assigned. Enabling the FAKE_MAC setting generates a fake MAC address from the IP address to track devices, but it may cause inconsistencies if IPs change or devices are re-discovered with a different MAC. Static IPs are recommended. Device type and icon might not be detected correctly and some plugins might fail if they depend on a valid MAC address. When unchecked, devices with empty MAC addresses are skipped."
}
]
}
],
"database_column_definitions": [

View File

@@ -23,6 +23,7 @@ from helper import get_setting_value # noqa: E402 [flake8 lint suppression]
from const import logPath # noqa: E402 [flake8 lint suppression]
import conf # noqa: E402 [flake8 lint suppression]
from pytz import timezone # noqa: E402 [flake8 lint suppression]
from utils.crypto_utils import string_to_mac_hash # noqa: E402 [flake8 lint suppression]
# Setup timezone & logger using standard NAX helpers
conf.tz = timezone(get_setting_value('TIMEZONE'))
@@ -42,6 +43,7 @@ PIHOLEAPI_SES_CSRF = None
PIHOLEAPI_API_MAXCLIENTS = None
PIHOLEAPI_VERIFY_SSL = True
PIHOLEAPI_RUN_TIMEOUT = 10
PIHOLEAPI_FAKE_MAC = get_setting_value('PIHOLEAPI_FAKE_MAC')
VERSION_DATE = "NAX-PIHOLEAPI-1.0"
@@ -222,8 +224,14 @@ def gather_device_entries():
if ip in iplist:
lastQuery = str(now_ts)
tmpMac = hwaddr.lower()
# ensure fake mac if enabled
if PIHOLEAPI_FAKE_MAC and is_mac(tmpMac) is False:
tmpMac = string_to_mac_hash(ip)
entries.append({
'mac': hwaddr.lower(),
'mac': tmpMac,
'ip': ip,
'name': name,
'macVendor': macVendor,
@@ -281,7 +289,7 @@ def main():
foreignKey=str(entry['mac'])
)
else:
mylog('verbose', [f"[{pluginName}] Skipping invalid MAC: {entry['name']}|{entry['mac']}|{entry['ip']}"])
mylog('verbose', [f"[{pluginName}] Skipping invalid MAC (see PIHOLEAPI_FAKE_MAC setting): {entry['name']}|{entry['mac']}|{entry['ip']}"])
# Write result file for NetAlertX to ingest
plugin_objects.write_result_file()

View File

@@ -14,7 +14,7 @@ if ! awk '$2 == "/" && $4 ~ /ro/ {found=1} END {exit !found}' /proc/mounts; then
══════════════════════════════════════════════════════════════════════════════
⚠️ Warning: Container is running as read-write, not in read-only mode.
Please mount the root filesystem as --read-only or use read-only: true
Please mount the root filesystem as --read-only or use read_only: true
https://github.com/jokob-sk/NetAlertX/blob/main/docs/docker-troubleshooting/read-only-filesystem.md
══════════════════════════════════════════════════════════════════════════════
EOF

View File

@@ -70,3 +70,13 @@ def generate_deterministic_guid(plugin, primary_id, secondary_id):
"""Generates a deterministic GUID based on plugin, primary ID, and secondary ID."""
data = f"{plugin}-{primary_id}-{secondary_id}".encode("utf-8")
return str(uuid.UUID(hashlib.md5(data).hexdigest()))
def string_to_mac_hash(input_string):
# Calculate a hash using SHA-256
sha256_hash = hashlib.sha256(input_string.encode()).hexdigest()
# Take the first 12 characters of the hash and format as a MAC address
mac_hash = ':'.join(sha256_hash[i:i + 2] for i in range(0, 12, 2))
return mac_hash