Compare commits
196 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f772b3e0f | ||
|
|
7015ba2f86 | ||
|
|
8485f6fe48 | ||
|
|
e3327d8718 | ||
|
|
af986aa540 | ||
|
|
06c38322ed | ||
|
|
3ece89379f | ||
|
|
d9fedddae2 | ||
|
|
1fc015fe2d | ||
|
|
5395524511 | ||
|
|
4fef4a7dd4 | ||
|
|
2c8fa55edb | ||
|
|
246777a290 | ||
|
|
1823a8139b | ||
|
|
3dd5c4bfcc | ||
|
|
d843fd4443 | ||
|
|
9dda02d430 | ||
|
|
47f23fcc4f | ||
|
|
75ef310e9b | ||
|
|
b78758976e | ||
|
|
6a17edc694 | ||
|
|
e88374e246 | ||
|
|
2c940b3422 | ||
|
|
739cc0e639 | ||
|
|
a7fa58151a | ||
|
|
a6df61e22c | ||
|
|
a981c9eec1 | ||
|
|
c62b9c5848 | ||
|
|
be5931f439 | ||
|
|
b1b6ce3c5c | ||
|
|
25d739fc67 | ||
|
|
f83a909a94 | ||
|
|
4ed1b6e8e6 | ||
|
|
c5610f11e0 | ||
|
|
ddb70ba5d4 | ||
|
|
83aa1a961e | ||
|
|
2d1a9da046 | ||
|
|
599bedf908 | ||
|
|
041e97d741 | ||
|
|
c3dc04c1e5 | ||
|
|
9fb2377e9e | ||
|
|
c663afdce0 | ||
|
|
1d91b17dee | ||
|
|
b66e370672 | ||
|
|
1ee82f37ba | ||
|
|
6831c9e0f4 | ||
|
|
773580e51b | ||
|
|
d3770373d4 | ||
|
|
dfc06d1419 | ||
|
|
9adcd4c5ee | ||
|
|
5ffb6f26e5 | ||
|
|
a7f5eebd26 | ||
|
|
75904848f5 | ||
|
|
874b9b070e | ||
|
|
d58471f713 | ||
|
|
a51d0e72c7 | ||
|
|
94254a14eb | ||
|
|
ddfa69a3ae | ||
|
|
14f40099c3 | ||
|
|
e492ba27a4 | ||
|
|
a478ab69e6 | ||
|
|
8cbfd04db6 | ||
|
|
750fb33e1c | ||
|
|
a20058a884 | ||
|
|
f8eaec091c | ||
|
|
67e89b55a7 | ||
|
|
aee93c0e24 | ||
|
|
3a4235a661 | ||
|
|
2762e8a30d | ||
|
|
e6daa33bca | ||
|
|
9482e7a720 | ||
|
|
8f00a28454 | ||
|
|
e00f26658b | ||
|
|
9943c98055 | ||
|
|
1601c10025 | ||
|
|
3298f79c44 | ||
|
|
6c79c04e9c | ||
|
|
ad9babd349 | ||
|
|
e0ffe8b424 | ||
|
|
db42d7f577 | ||
|
|
786ae9305d | ||
|
|
a823301862 | ||
|
|
de20a2621c | ||
|
|
1874a5e641 | ||
|
|
3653d2efd0 | ||
|
|
f1e9ca2540 | ||
|
|
3390384ce3 | ||
|
|
cb63dd1765 | ||
|
|
ccec89f419 | ||
|
|
7f7b0a328f | ||
|
|
24eaf1e143 | ||
|
|
99981754c9 | ||
|
|
d31af28f08 | ||
|
|
2836996a21 | ||
|
|
db43ab9cf6 | ||
|
|
a94c6a291e | ||
|
|
c6f0614570 | ||
|
|
f64cd9ea28 | ||
|
|
2482289ad6 | ||
|
|
7863ab3b03 | ||
|
|
b0d117c3b8 | ||
|
|
1399e3881a | ||
|
|
2b2ae516da | ||
|
|
2df7d143d3 | ||
|
|
1688d029b9 | ||
|
|
6d8f451be1 | ||
|
|
840e1e50a9 | ||
|
|
164fe504a4 | ||
|
|
9040e49e16 | ||
|
|
629736ad39 | ||
|
|
ebc41ada45 | ||
|
|
4fea786e16 | ||
|
|
0edd20c82c | ||
|
|
296dd0d0df | ||
|
|
f2151cd9e8 | ||
|
|
60876b14ce | ||
|
|
9231ba742c | ||
|
|
8a538102da | ||
|
|
31f901da35 | ||
|
|
c5b731fcb2 | ||
|
|
b2c7945513 | ||
|
|
6bf5c1f535 | ||
|
|
3da50fe83d | ||
|
|
b46bdb9b60 | ||
|
|
00c7bb65e1 | ||
|
|
9946f9affd | ||
|
|
46a11b1cca | ||
|
|
8a003ad805 | ||
|
|
7dd860b2ab | ||
|
|
a9d7ca8809 | ||
|
|
5695f4f3e7 | ||
|
|
1d74398337 | ||
|
|
e8f17346ff | ||
|
|
bb1e00301c | ||
|
|
883786ec91 | ||
|
|
3a023a675f | ||
|
|
8c895864da | ||
|
|
90474a6b92 | ||
|
|
f7cf8a0b1d | ||
|
|
98fdccb58f | ||
|
|
6f606f34d1 | ||
|
|
fd3f1fc929 | ||
|
|
36ea3e62fd | ||
|
|
7c9b37d827 | ||
|
|
3fc0787b84 | ||
|
|
5ba50f6d80 | ||
|
|
c0c685c561 | ||
|
|
64a0fd0446 | ||
|
|
b1b67c268f | ||
|
|
ae12195439 | ||
|
|
3106b39566 | ||
|
|
d88aa9d6eb | ||
|
|
9f9f2ff58c | ||
|
|
ce887968b7 | ||
|
|
40e9fbdb3f | ||
|
|
3227cbbfa4 | ||
|
|
df9a17ed85 | ||
|
|
3ad7b59c84 | ||
|
|
b94da568a9 | ||
|
|
0146ae7c30 | ||
|
|
afbcf5985f | ||
|
|
af879ec84d | ||
|
|
f78c84d9a8 | ||
|
|
2d11d3dd3e | ||
|
|
39c556576c | ||
|
|
73fd094cfc | ||
|
|
cbf2cd0ee8 | ||
|
|
915bb523d6 | ||
|
|
3dc87d2adb | ||
|
|
9155303674 | ||
|
|
0777824d96 | ||
|
|
b170ca3e18 | ||
|
|
5fd30fe3c8 | ||
|
|
2fa181ffbc | ||
|
|
a2bccdfb8e | ||
|
|
f3b159116f | ||
|
|
03b9a9cf0d | ||
|
|
bf2fae6e1a | ||
|
|
086fa54035 | ||
|
|
962bbaa5a1 | ||
|
|
9c71a8ecab | ||
|
|
deff5a4ed0 | ||
|
|
e10c1c9c8d | ||
|
|
b155fe2b06 | ||
|
|
840bfe32d2 | ||
|
|
f33ef9861b | ||
|
|
cbe71cc203 | ||
|
|
beaf8131ae | ||
|
|
99bfbb56de | ||
|
|
e73c8e830a | ||
|
|
1c4e6c7e38 | ||
|
|
1319c3380d | ||
|
|
dce8c34064 | ||
|
|
9502ee0cd0 | ||
|
|
8eb4ffe3ed | ||
|
|
4be59807e5 |
112
.devcontainer/Dockerfile
Executable file
@@ -0,0 +1,112 @@
|
||||
# DO NOT MODIFY THIS FILE DIRECTLY. IT IS AUTO-GENERATED BY .devcontainer/scripts/generate-dockerfile.sh
|
||||
|
||||
# ---/Dockerfile---
|
||||
FROM alpine:3.22 AS builder
|
||||
|
||||
ARG INSTALL_DIR=/app
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
# Install build dependencies
|
||||
RUN apk add --no-cache bash shadow python3 python3-dev gcc musl-dev libffi-dev openssl-dev git \
|
||||
&& python -m venv /opt/venv
|
||||
|
||||
# Enable venv
|
||||
ENV PATH="/opt/venv/bin:$PATH"
|
||||
|
||||
|
||||
RUN pip install openwrt-luci-rpc asusrouter asyncio aiohttp graphene flask flask-cors unifi-sm-api tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros yattag git+https://github.com/foreign-sub/aiofreepybox.git
|
||||
|
||||
# Append Iliadbox certificate to aiofreepybox
|
||||
|
||||
# second stage
|
||||
FROM alpine:3.22 AS runner
|
||||
|
||||
ARG INSTALL_DIR=/app
|
||||
|
||||
COPY --from=builder /opt/venv /opt/venv
|
||||
COPY --from=builder /usr/sbin/usermod /usr/sbin/groupmod /usr/sbin/
|
||||
|
||||
# Enable venv
|
||||
ENV PATH="/opt/venv/bin:$PATH"
|
||||
|
||||
# default port and listen address
|
||||
ENV PORT=20211 LISTEN_ADDR=0.0.0.0
|
||||
|
||||
# needed for s6-overlay
|
||||
ENV S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0
|
||||
|
||||
# ❗ IMPORTANT - if you modify this file modify the /install/install_dependecies.sh file as well ❗
|
||||
|
||||
RUN apk update --no-cache \
|
||||
&& apk add --no-cache bash libbsd zip lsblk gettext-envsubst sudo mtr tzdata s6-overlay \
|
||||
&& apk add --no-cache curl arp-scan iproute2 iproute2-ss nmap nmap-scripts traceroute nbtscan avahi avahi-tools openrc dbus net-tools net-snmp-tools bind-tools awake ca-certificates \
|
||||
&& apk add --no-cache sqlite php83 php83-fpm php83-cgi php83-curl php83-sqlite3 php83-session \
|
||||
&& apk add --no-cache python3 nginx \
|
||||
&& ln -s /usr/bin/awake /usr/bin/wakeonlan \
|
||||
&& rm -f /etc/nginx/http.d/default.conf
|
||||
|
||||
|
||||
# Add crontab file
|
||||
COPY --chmod=600 --chown=root:root install/crontab /etc/crontabs/root
|
||||
|
||||
# Start all required services
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=2 \
|
||||
CMD curl -sf -o /dev/null ${LISTEN_ADDR}:${PORT}/php/server/query_json.php?file=app_state.json
|
||||
|
||||
ENTRYPOINT ["/init"]
|
||||
|
||||
# ---/resources/devcontainer-Dockerfile---
|
||||
|
||||
# Devcontainer build stage (do not build directly)
|
||||
# This file is combined with the root /Dockerfile by
|
||||
# .devcontainer/scripts/generate-dockerfile.sh
|
||||
# The generator appends this stage to produce .devcontainer/Dockerfile.
|
||||
# Prefer to place dev-only setup here; use setup.sh only for runtime fixes.
|
||||
|
||||
FROM runner AS devcontainer
|
||||
ENV INSTALL_DIR=/app
|
||||
ENV PYTHONPATH=/workspaces/NetAlertX/test:/workspaces/NetAlertX/server:/app:/app/server:/opt/venv/lib/python3.12/site-packages
|
||||
|
||||
# Install common tools, create user, and set up sudo
|
||||
RUN apk add --no-cache git nano vim jq php83-pecl-xdebug py3-pip nodejs sudo gpgconf pytest pytest-cov && \
|
||||
adduser -D -s /bin/sh netalertx && \
|
||||
addgroup netalertx nginx && \
|
||||
addgroup netalertx www-data && \
|
||||
echo "netalertx ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/90-netalertx && \
|
||||
chmod 440 /etc/sudoers.d/90-netalertx
|
||||
# Install debugpy in the virtualenv if present, otherwise into system python3
|
||||
RUN /bin/sh -c '(/opt/venv/bin/python3 -m pip install --no-cache-dir debugpy) || (python3 -m pip install --no-cache-dir debugpy) || true'
|
||||
# setup nginx
|
||||
COPY .devcontainer/resources/netalertx-devcontainer.conf /etc/nginx/http.d/netalert-frontend.conf
|
||||
RUN set -e; \
|
||||
chown netalertx:nginx /etc/nginx/http.d/netalert-frontend.conf; \
|
||||
install -d -o netalertx -g www-data -m 775 /app; \
|
||||
install -d -o netalertx -g www-data -m 755 /run/nginx; \
|
||||
install -d -o netalertx -g www-data -m 755 /var/lib/nginx/logs; \
|
||||
rm -f /var/lib/nginx/logs/* || true; \
|
||||
for f in error access; do : > /var/lib/nginx/logs/$f.log; done; \
|
||||
install -d -o netalertx -g www-data -m 777 /run/php; \
|
||||
install -d -o netalertx -g www-data -m 775 /var/log/php; \
|
||||
chown -R netalertx:www-data /etc/nginx/http.d; \
|
||||
chmod -R 775 /etc/nginx/http.d; \
|
||||
chown -R netalertx:www-data /var/lib/nginx; \
|
||||
chmod -R 755 /var/lib/nginx && \
|
||||
chown -R netalertx:www-data /var/log/nginx/ && \
|
||||
sed -i '/^user /d' /etc/nginx/nginx.conf; \
|
||||
sed -i 's|^error_log .*|error_log /dev/stderr warn;|' /etc/nginx/nginx.conf; \
|
||||
sed -i 's|^access_log .*|access_log /dev/stdout main;|' /etc/nginx/nginx.conf; \
|
||||
sed -i 's|error_log .*|error_log /dev/stderr warn;|g' /etc/nginx/http.d/*.conf 2>/dev/null || true; \
|
||||
sed -i 's|access_log .*|access_log /dev/stdout main;|g' /etc/nginx/http.d/*.conf 2>/dev/null || true; \
|
||||
mkdir -p /run/openrc; \
|
||||
chown netalertx:nginx /run/openrc/; \
|
||||
rm -Rf /run/openrc/*;
|
||||
|
||||
# setup pytest
|
||||
RUN sudo /opt/venv/bin/python -m pip install -U pytest pytest-cov
|
||||
|
||||
WORKDIR /workspaces/NetAlertX
|
||||
|
||||
|
||||
ENTRYPOINT ["/bin/sh","-c","sleep infinity"]
|
||||
30
.devcontainer/README.md
Executable file
@@ -0,0 +1,30 @@
|
||||
# NetAlertX Devcontainer Notes
|
||||
|
||||
This devcontainer replicates the production container as closely as practical, with a few development-oriented differences.
|
||||
|
||||
Key behavior
|
||||
- No init process: Services are managed by shell scripts using killall, setsid, and nohup. Startup and restarts are script-driven rather than supervised by an init system.
|
||||
- Autogenerated Dockerfile: The effective devcontainer Dockerfile is generated on demand by `.devcontainer/scripts/generate-dockerfile.sh`. It combines the root `Dockerfile` (with certain COPY instructions removed) and an extra "devcontainer" stage from `.devcontainer/resources/devcontainer-Dockerfile`. When you change the resource Dockerfile, re-run the generator to refresh `.devcontainer/Dockerfile`.
|
||||
- Where to put setup: Prefer baking setup into `.devcontainer/resources/devcontainer-Dockerfile`. Use `.devcontainer/scripts/setup.sh` only for steps that must happen at container start (e.g., cleaning up nginx/php ownership, creating directories, touching runtime files) or depend on runtime paths.
|
||||
|
||||
Debugging (F5)
|
||||
The Frontend and backend run in debug mode always. You can attach your debugger at any time.
|
||||
- Python Backend Debug: Attach - The backend runs with a debugger on port 5678. Set breakpoints in the code and press F5 to begin triggering them.
|
||||
- PHP Frontend (XDebug) Xdebug listens on 9003. Start listening and use an Xdebug extension in your browser to debug PHP.
|
||||
|
||||
Common workflows (F1->Tasks: Run Task)
|
||||
- Regenerate the devcontainer Dockerfile: Run the VS Code task "Generate Dockerfile" or execute `.devcontainer/scripts/generate-dockerfile.sh`. The result is `.devcontainer/Dockerfile`.
|
||||
- Re-run startup provisioning: Use the task "Re-Run Startup Script" to execute `.devcontainer/scripts/setup.sh` in the container.
|
||||
- Start services:
|
||||
- Backend (GraphQL/Flask): `.devcontainer/scripts/restart-backend.sh` starts it under debugpy and logs to `/app/log/app.log`
|
||||
- Frontend (nginx + PHP-FPM): Started via setup.sh; can be restarted by the task "Start Frontend (nginx and PHP-FPM)".
|
||||
|
||||
Testing
|
||||
- pytest is installed via Alpine packages (py3-pytest, py3-pytest-cov).
|
||||
- PYTHONPATH includes workspace and venv site-packages so tests can import `server/*` modules and third-party libs.
|
||||
- Run tests via VS Code Pytest Runner or `pytest -q` from the workspace root.
|
||||
|
||||
Conventions
|
||||
- Don’t edit `.devcontainer/Dockerfile` directly; edit `.devcontainer/resources/devcontainer-Dockerfile` and regenerate.
|
||||
- Keep setup in the resource Dockerfile when possible; reserve `setup.sh` for runtime fixes.
|
||||
- Avoid hardcoding ports/secrets; prefer existing settings and helpers (see `.github/copilot-instructions.md`).
|
||||
79
.devcontainer/devcontainer.json
Executable file
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"name": "NetAlertX DevContainer",
|
||||
"remoteUser": "netalertx",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile",
|
||||
"context": "..",
|
||||
"target": "devcontainer"
|
||||
},
|
||||
"workspaceFolder": "/workspaces/NetAlertX",
|
||||
"runArgs": [
|
||||
"--add-host=host.docker.internal:host-gateway",
|
||||
"--security-opt", "apparmor=unconfined" // for alowing ramdisk mounts
|
||||
],
|
||||
|
||||
"capAdd": [
|
||||
"SYS_ADMIN", // For mounting ramdisks
|
||||
"NET_ADMIN", // For network interface configuration
|
||||
"NET_RAW" // For raw packet manipulation
|
||||
],
|
||||
|
||||
|
||||
|
||||
"postStartCommand": "${containerWorkspaceFolder}/.devcontainer/scripts/setup.sh",
|
||||
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"ms-azuretools.vscode-docker",
|
||||
"felixfbecker.php-debug",
|
||||
"bmewburn.vscode-intelephense-client",
|
||||
"xdebug.php-debug",
|
||||
"ms-python.vscode-pylance",
|
||||
"pamaron.pytest-runner",
|
||||
"coderabbit.coderabbit-vscode",
|
||||
"ms-python.black-formatter"
|
||||
]
|
||||
,
|
||||
"settings": {
|
||||
"terminal.integrated.cwd": "${containerWorkspaceFolder}",
|
||||
// Python testing configuration
|
||||
"python.testing.pytestEnabled": true,
|
||||
"python.testing.unittestEnabled": false,
|
||||
"python.testing.pytestArgs": [
|
||||
"test"
|
||||
],
|
||||
// Make sure we discover tests and import server correctly
|
||||
"python.analysis.extraPaths": [
|
||||
"/workspaces/NetAlertX",
|
||||
"/workspaces/NetAlertX/server",
|
||||
"/app",
|
||||
"/app/server"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"forwardPorts": [5678, 9000, 9003, 20211, 20212],
|
||||
|
||||
"portsAttributes": {
|
||||
"20211": {
|
||||
"label": "Frontend:Nginx+PHP"
|
||||
},
|
||||
"20212": {
|
||||
"label": "Backend:GraphQL"
|
||||
},
|
||||
"9003": {
|
||||
"label": "PHP Debug:Xdebug"
|
||||
},
|
||||
"9000": {
|
||||
"label": "PHP-FPM:FastCGI"
|
||||
},
|
||||
"5678": {
|
||||
"label": "Python Debug:debugpy"
|
||||
}
|
||||
},
|
||||
|
||||
// Optional: ensures compose services are stopped when you close the window
|
||||
"shutdownAction": "stopContainer"
|
||||
}
|
||||
8
.devcontainer/resources/99-xdebug.ini
Executable file
@@ -0,0 +1,8 @@
|
||||
zend_extension="xdebug.so"
|
||||
[xdebug]
|
||||
xdebug.mode=develop,debug
|
||||
xdebug.log_level=0
|
||||
xdebug.client_host=host.docker.internal
|
||||
xdebug.client_port=9003
|
||||
xdebug.start_with_request=yes
|
||||
xdebug.discover_client_host=1
|
||||
51
.devcontainer/resources/devcontainer-Dockerfile
Executable file
@@ -0,0 +1,51 @@
|
||||
# Devcontainer build stage (do not build directly)
|
||||
# This file is combined with the root /Dockerfile by
|
||||
# .devcontainer/scripts/generate-dockerfile.sh
|
||||
# The generator appends this stage to produce .devcontainer/Dockerfile.
|
||||
# Prefer to place dev-only setup here; use setup.sh only for runtime fixes.
|
||||
|
||||
FROM runner AS devcontainer
|
||||
ENV INSTALL_DIR=/app
|
||||
ENV PYTHONPATH=/workspaces/NetAlertX/test:/workspaces/NetAlertX/server:/app:/app/server:/opt/venv/lib/python3.12/site-packages
|
||||
|
||||
# Install common tools, create user, and set up sudo
|
||||
RUN apk add --no-cache git nano vim jq php83-pecl-xdebug py3-pip nodejs sudo gpgconf pytest pytest-cov && \
|
||||
adduser -D -s /bin/sh netalertx && \
|
||||
addgroup netalertx nginx && \
|
||||
addgroup netalertx www-data && \
|
||||
echo "netalertx ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/90-netalertx && \
|
||||
chmod 440 /etc/sudoers.d/90-netalertx
|
||||
# Install debugpy in the virtualenv if present, otherwise into system python3
|
||||
RUN /bin/sh -c '(/opt/venv/bin/python3 -m pip install --no-cache-dir debugpy) || (python3 -m pip install --no-cache-dir debugpy) || true'
|
||||
# setup nginx
|
||||
COPY .devcontainer/resources/netalertx-devcontainer.conf /etc/nginx/http.d/netalert-frontend.conf
|
||||
RUN set -e; \
|
||||
chown netalertx:nginx /etc/nginx/http.d/netalert-frontend.conf; \
|
||||
install -d -o netalertx -g www-data -m 775 /app; \
|
||||
install -d -o netalertx -g www-data -m 755 /run/nginx; \
|
||||
install -d -o netalertx -g www-data -m 755 /var/lib/nginx/logs; \
|
||||
rm -f /var/lib/nginx/logs/* || true; \
|
||||
for f in error access; do : > /var/lib/nginx/logs/$f.log; done; \
|
||||
install -d -o netalertx -g www-data -m 777 /run/php; \
|
||||
install -d -o netalertx -g www-data -m 775 /var/log/php; \
|
||||
chown -R netalertx:www-data /etc/nginx/http.d; \
|
||||
chmod -R 775 /etc/nginx/http.d; \
|
||||
chown -R netalertx:www-data /var/lib/nginx; \
|
||||
chmod -R 755 /var/lib/nginx && \
|
||||
chown -R netalertx:www-data /var/log/nginx/ && \
|
||||
sed -i '/^user /d' /etc/nginx/nginx.conf; \
|
||||
sed -i 's|^error_log .*|error_log /dev/stderr warn;|' /etc/nginx/nginx.conf; \
|
||||
sed -i 's|^access_log .*|access_log /dev/stdout main;|' /etc/nginx/nginx.conf; \
|
||||
sed -i 's|error_log .*|error_log /dev/stderr warn;|g' /etc/nginx/http.d/*.conf 2>/dev/null || true; \
|
||||
sed -i 's|access_log .*|access_log /dev/stdout main;|g' /etc/nginx/http.d/*.conf 2>/dev/null || true; \
|
||||
mkdir -p /run/openrc; \
|
||||
chown netalertx:nginx /run/openrc/; \
|
||||
rm -Rf /run/openrc/*;
|
||||
|
||||
# setup pytest
|
||||
RUN sudo /opt/venv/bin/python -m pip install -U pytest pytest-cov
|
||||
|
||||
WORKDIR /workspaces/NetAlertX
|
||||
|
||||
|
||||
ENTRYPOINT ["/bin/sh","-c","sleep infinity"]
|
||||
26
.devcontainer/resources/netalertx-devcontainer.conf
Executable file
@@ -0,0 +1,26 @@
|
||||
log_format netalertx '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
access_log /var/log/nginx/access.log netalertx flush=1s;
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
|
||||
server {
|
||||
listen 20211 default_server;
|
||||
root /app/front;
|
||||
index index.php;
|
||||
|
||||
add_header X-Forwarded-Prefix "/netalertx" always;
|
||||
proxy_set_header X-Forwarded-Prefix "/netalertx";
|
||||
|
||||
location ~* \.php$ {
|
||||
add_header Cache-Control "no-store";
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
|
||||
fastcgi_param PHP_VALUE "xdebug.remote_enable=1";
|
||||
fastcgi_connect_timeout 75;
|
||||
fastcgi_send_timeout 600;
|
||||
fastcgi_read_timeout 600;
|
||||
}
|
||||
}
|
||||
38
.devcontainer/scripts/generate-dockerfile.sh
Executable file
@@ -0,0 +1,38 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Generator for .devcontainer/Dockerfile
|
||||
# Combines the root /Dockerfile (with some COPY lines removed) and
|
||||
# the dev-only stage in .devcontainer/resources/devcontainer-Dockerfile.
|
||||
# Run this script after modifying the resource Dockerfile to refresh
|
||||
# the final .devcontainer/Dockerfile used by the devcontainer.
|
||||
|
||||
# Make a copy of the original Dockerfile to the .devcontainer folder
|
||||
# but remove the COPY . ${INSTALL_DIR}/ command from it. This avoids
|
||||
# overwriting /app (which uses symlinks to the workspace) and preserves
|
||||
# debugging capabilities inside the devcontainer.
|
||||
|
||||
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
||||
DEVCONTAINER_DIR="${SCRIPT_DIR%/scripts}"
|
||||
ROOT_DIR="${DEVCONTAINER_DIR%/.devcontainer}"
|
||||
|
||||
OUT_FILE="${DEVCONTAINER_DIR}/Dockerfile"
|
||||
|
||||
echo "# DO NOT MODIFY THIS FILE DIRECTLY. IT IS AUTO-GENERATED BY .devcontainer/scripts/generate-dockerfile.sh" > "$OUT_FILE"
|
||||
echo "" >> "$OUT_FILE"
|
||||
echo "# ---/Dockerfile---" >> "$OUT_FILE"
|
||||
|
||||
sed '/${INSTALL_DIR}/d' "${ROOT_DIR}/Dockerfile" >> "$OUT_FILE"
|
||||
|
||||
# sed the line https://github.com/foreign-sub/aiofreepybox.git \\ to remove trailing backslash
|
||||
sed -i '/aiofreepybox.git/ s/ \\$//' "$OUT_FILE"
|
||||
|
||||
# don't cat the file, just copy it in because it doesn't exist at build time
|
||||
sed -i 's|^ RUN cat ${INSTALL_DIR}/install/freebox_certificate.pem >> /opt/venv/lib/python3.12/site-packages/aiofreepybox/freebox_certificates.pem$| COPY install/freebox_certificate.pem /opt/venv/lib/python3.12/site-packages/aiofreepybox/freebox_certificates.pem |' "$OUT_FILE"
|
||||
|
||||
echo "" >> "$OUT_FILE"
|
||||
echo "# ---/resources/devcontainer-Dockerfile---" >> "$OUT_FILE"
|
||||
echo "" >> "$OUT_FILE"
|
||||
|
||||
cat "${DEVCONTAINER_DIR}/resources/devcontainer-Dockerfile" >> "$OUT_FILE"
|
||||
|
||||
echo "Generated $OUT_FILE using root dir $ROOT_DIR" >&2
|
||||
26
.devcontainer/scripts/restart-backend.sh
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/bin/sh
|
||||
# Start (or restart) the NetAlertX Python backend under debugpy in background.
|
||||
# This script is invoked by the VS Code task "Restart GraphQL".
|
||||
# It exists to avoid complex inline command chains that were being mangled by the task runner.
|
||||
|
||||
set -e
|
||||
|
||||
LOG_DIR=/app/log
|
||||
APP_DIR=/app/server
|
||||
PY=python3
|
||||
PORT_DEBUG=5678
|
||||
|
||||
# Kill any prior debug/run instances
|
||||
sudo killall python3 2>/dev/null || true
|
||||
sleep 2
|
||||
|
||||
echo ''|tee $LOG_DIR/stdout.log $LOG_DIR/stderr.log $LOG_DIR/app.log
|
||||
|
||||
cd "$APP_DIR"
|
||||
|
||||
# Launch using absolute module path for clarity; rely on cwd for local imports
|
||||
setsid nohup "${PY}" -m debugpy --listen "0.0.0.0:${PORT_DEBUG}" /app/server/__main__.py \
|
||||
1>>"$LOG_DIR/stdout.log" \
|
||||
2>>"$LOG_DIR/stderr.log" &
|
||||
PID=$!
|
||||
sleep 2
|
||||
13
.devcontainer/scripts/run-tests.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
# shellcheck shell=sh
|
||||
# Simple helper to run pytest inside the devcontainer with correct paths
|
||||
set -eu
|
||||
|
||||
# Ensure we run from the workspace root
|
||||
cd /workspaces/NetAlertX
|
||||
|
||||
# Make sure PYTHONPATH includes server and workspace
|
||||
export PYTHONPATH="/workspaces/NetAlertX:/workspaces/NetAlertX/server:/app:/app/server:${PYTHONPATH:-}"
|
||||
|
||||
# Default to running the full test suite under /workspaces/NetAlertX/test
|
||||
pytest -q --maxfail=1 --disable-warnings test "$@"
|
||||
200
.devcontainer/scripts/setup.sh
Executable file
@@ -0,0 +1,200 @@
|
||||
#! /bin/bash
|
||||
# Runtime setup for devcontainer (executed after container starts).
|
||||
# Prefer building setup into resources/devcontainer-Dockerfile when possible.
|
||||
# Use this script for runtime-only adjustments (permissions, sockets, ownership,
|
||||
# and services managed without init) that are difficult at build time.
|
||||
id
|
||||
|
||||
# Define variables (paths, ports, environment)
|
||||
|
||||
export APP_DIR="/app"
|
||||
export APP_COMMAND="/workspaces/NetAlertX/.devcontainer/scripts/restart-backend.sh"
|
||||
export PHP_FPM_BIN="/usr/sbin/php-fpm83"
|
||||
export NGINX_BIN="/usr/sbin/nginx"
|
||||
export CROND_BIN="/usr/sbin/crond -f"
|
||||
|
||||
|
||||
export ALWAYS_FRESH_INSTALL=false
|
||||
export INSTALL_DIR=/app
|
||||
export APP_DATA_LOCATION=/app/config
|
||||
export APP_CONFIG_LOCATION=/app/config
|
||||
export LOGS_LOCATION=/app/logs
|
||||
export CONF_FILE="app.conf"
|
||||
export NGINX_CONF_FILE=netalertx.conf
|
||||
export DB_FILE="app.db"
|
||||
export FULL_FILEDB_PATH="${INSTALL_DIR}/db/${DB_FILE}"
|
||||
export NGINX_CONFIG_FILE="/etc/nginx/http.d/${NGINX_CONF_FILE}"
|
||||
export OUI_FILE="/usr/share/arp-scan/ieee-oui.txt" # Define the path to ieee-oui.txt and ieee-iab.txt
|
||||
export TZ=Europe/Paris
|
||||
export PORT=20211
|
||||
export SOURCE_DIR="/workspaces/NetAlertX"
|
||||
|
||||
|
||||
|
||||
main() {
|
||||
echo "=== NetAlertX Development Container Setup ==="
|
||||
echo "Setting up ${SOURCE_DIR}..."
|
||||
configure_source
|
||||
|
||||
echo "--- Starting Development Services ---"
|
||||
configure_php
|
||||
|
||||
|
||||
start_services
|
||||
}
|
||||
|
||||
# safe_link: create a symlink from source to target, removing existing target if necessary
|
||||
# bypassing the default behavior of symlinking the directory into the target directory if it is a directory
|
||||
safe_link() {
|
||||
# usage: safe_link <source> <target>
|
||||
local src="$1"
|
||||
local dst="$2"
|
||||
|
||||
# Ensure parent directory exists
|
||||
install -d -m 775 "$(dirname "$dst")" >/dev/null 2>&1 || true
|
||||
|
||||
# If target exists, remove it without dereferencing symlinks
|
||||
if [ -L "$dst" ] || [ -e "$dst" ]; then
|
||||
rm -rf "$dst"
|
||||
fi
|
||||
|
||||
# Create link; -n prevents deref, -f replaces if somehow still exists
|
||||
ln -sfn "$src" "$dst"
|
||||
}
|
||||
|
||||
# Setup source directory
|
||||
configure_source() {
|
||||
echo "[1/3] Configuring Source..."
|
||||
echo " -> Linking source to ${INSTALL_DIR}"
|
||||
echo "Dev">${INSTALL_DIR}/.VERSION
|
||||
|
||||
echo " -> Mounting ramdisks for /log and /api"
|
||||
sudo mount -t tmpfs -o size=256M tmpfs "${SOURCE_DIR}/log"
|
||||
sudo mount -t tmpfs -o size=512M tmpfs "${SOURCE_DIR}/api"
|
||||
safe_link ${SOURCE_DIR}/api ${INSTALL_DIR}/api
|
||||
safe_link ${SOURCE_DIR}/back ${INSTALL_DIR}/back
|
||||
safe_link "${SOURCE_DIR}/config" "${INSTALL_DIR}/config"
|
||||
safe_link "${SOURCE_DIR}/db" "${INSTALL_DIR}/db"
|
||||
if [ ! -f "${SOURCE_DIR}/config/app.conf" ]; then
|
||||
cp ${SOURCE_DIR}/back/app.conf ${INSTALL_DIR}/config/
|
||||
cp ${SOURCE_DIR}/back/app.db ${INSTALL_DIR}/db/
|
||||
fi
|
||||
|
||||
safe_link "${SOURCE_DIR}/docs" "${INSTALL_DIR}/docs"
|
||||
safe_link "${SOURCE_DIR}/front" "${INSTALL_DIR}/front"
|
||||
safe_link "${SOURCE_DIR}/install" "${INSTALL_DIR}/install"
|
||||
safe_link "${SOURCE_DIR}/scripts" "${INSTALL_DIR}/scripts"
|
||||
safe_link "${SOURCE_DIR}/server" "${INSTALL_DIR}/server"
|
||||
safe_link "${SOURCE_DIR}/test" "${INSTALL_DIR}/test"
|
||||
safe_link "${SOURCE_DIR}/log" "${INSTALL_DIR}/log"
|
||||
safe_link "${SOURCE_DIR}/mkdocs.yml" "${INSTALL_DIR}/mkdocs.yml"
|
||||
|
||||
echo " -> Copying static files to ${INSTALL_DIR}"
|
||||
cp -R ${SOURCE_DIR}/CODE_OF_CONDUCT.md ${INSTALL_DIR}/
|
||||
cp -R ${SOURCE_DIR}/dockerfiles ${INSTALL_DIR}/dockerfiles
|
||||
sudo cp -na "${INSTALL_DIR}/back/${CONF_FILE}" "${INSTALL_DIR}/config/${CONF_FILE}"
|
||||
sudo cp -na "${INSTALL_DIR}/back/${DB_FILE}" "${FULL_FILEDB_PATH}"
|
||||
if [ -e "${INSTALL_DIR}/api/user_notifications.json" ]; then
|
||||
echo " -> Removing existing user_notifications.json"
|
||||
sudo rm "${INSTALL_DIR}"/api/user_notifications.json
|
||||
fi
|
||||
|
||||
|
||||
|
||||
echo " -> Setting ownership and permissions"
|
||||
sudo find ${INSTALL_DIR}/ -type d -exec chmod 775 {} \;
|
||||
sudo find ${INSTALL_DIR}/ -type f -exec chmod 664 {} \;
|
||||
sudo date +%s > "${INSTALL_DIR}/front/buildtimestamp.txt"
|
||||
sudo chmod 640 "${INSTALL_DIR}/config/${CONF_FILE}" || true
|
||||
|
||||
|
||||
|
||||
echo " -> Setting up log directory"
|
||||
install -d -o netalertx -g www-data -m 777 ${INSTALL_DIR}/log/plugins
|
||||
|
||||
echo " -> Empty log"|tee ${INSTALL_DIR}/log/app.log \
|
||||
${INSTALL_DIR}/log/app_front.log \
|
||||
${INSTALL_DIR}/log/stdout.log
|
||||
touch ${INSTALL_DIR}/log/stderr.log \
|
||||
${INSTALL_DIR}/log/execution_queue.log
|
||||
echo 0>${INSTALL_DIR}/log/db_is_locked.log
|
||||
|
||||
date +%s > /app/front/buildtimestamp.txt
|
||||
|
||||
killall python &>/dev/null
|
||||
sleep 1
|
||||
}
|
||||
|
||||
#
|
||||
|
||||
# start_services: start crond, PHP-FPM, nginx and the application
|
||||
start_services() {
|
||||
echo "[3/3] Starting services..."
|
||||
|
||||
killall nohup &>/dev/null || true
|
||||
|
||||
killall php-fpm83 &>/dev/null || true
|
||||
killall crond &>/dev/null || true
|
||||
# Give the OS a moment to release the php-fpm socket
|
||||
sleep 0.3
|
||||
echo " -> Starting CronD"
|
||||
setsid nohup $CROND_BIN &>/dev/null &
|
||||
|
||||
echo " -> Starting PHP-FPM"
|
||||
setsid nohup $PHP_FPM_BIN &>/dev/null &
|
||||
|
||||
sudo killall nginx &>/dev/null || true
|
||||
# Wait for the previous nginx processes to exit and for the port to free up
|
||||
tries=0
|
||||
while ss -ltn | grep -q ":${PORT}[[:space:]]" && [ $tries -lt 10 ]; do
|
||||
echo " -> Waiting for port ${PORT} to free..."
|
||||
sleep 0.2
|
||||
tries=$((tries+1))
|
||||
done
|
||||
sleep 0.2
|
||||
echo " -> Starting Nginx"
|
||||
setsid nohup $NGINX_BIN &>/dev/null &
|
||||
echo " -> Starting Backend ${APP_DIR}/server..."
|
||||
$APP_COMMAND
|
||||
sleep 2
|
||||
}
|
||||
|
||||
# configure_php: configure PHP-FPM and enable dev debug options
|
||||
configure_php() {
|
||||
echo "[2/3] Configuring PHP-FPM..."
|
||||
sudo killall php-fpm83 &>/dev/null || true
|
||||
install -d -o nginx -g www-data /run/php/ &>/dev/null
|
||||
sudo sed -i "/^;pid/c\pid = /run/php/php8.3-fpm.pid" /etc/php83/php-fpm.conf
|
||||
sudo sed -i 's|^listen = .*|listen = 127.0.0.1:9000|' /etc/php83/php-fpm.d/www.conf
|
||||
sudo sed -i 's|fastcgi_pass .*|fastcgi_pass 127.0.0.1:9000;|' /etc/nginx/http.d/*.conf
|
||||
|
||||
#increase max child process count to 10
|
||||
sudo sed -i -e 's/pm.max_children = 5/pm.max_children = 10/' /etc/php83/php-fpm.d/www.conf
|
||||
|
||||
# find any line in php-fmp that starts with either ;error_log or error_log = and replace it with error_log = /app/log/app.php_errors.log
|
||||
sudo sed -i '/^;*error_log\s*=/c\error_log = /app/log/app.php_errors.log' /etc/php83/php-fpm.conf
|
||||
# If the line was not found, append it to the end of the file
|
||||
if ! grep -q '^error_log\s*=' /etc/php83/php-fpm.conf; then
|
||||
echo 'error_log = /app/log/app.php_errors.log' | sudo tee -a /etc/php83/php-fpm.conf
|
||||
fi
|
||||
|
||||
sudo mkdir -p /etc/php83/conf.d
|
||||
sudo cp /workspaces/NetAlertX/.devcontainer/resources/99-xdebug.ini /etc/php83/conf.d/99-xdebug.ini
|
||||
|
||||
sudo rm -R /var/log/php83 &>/dev/null || true
|
||||
install -d -o netalertx -g www-data -m 755 var/log/php83;
|
||||
|
||||
sudo chmod 644 /etc/php83/conf.d/99-xdebug.ini || true
|
||||
|
||||
}
|
||||
|
||||
# (duplicate start_services removed)
|
||||
|
||||
|
||||
|
||||
echo "$(git rev-parse --short=8 HEAD)">/app/.VERSION
|
||||
# Run the main function
|
||||
main
|
||||
|
||||
|
||||
|
||||
40
.devcontainer/scripts/stream-logs.sh
Executable file
@@ -0,0 +1,40 @@
|
||||
#!/bin/sh
|
||||
# Stream NetAlertX logs to stdout so the Dev Containers output channel shows them.
|
||||
# This script waits briefly for the files to appear and then tails them with -F.
|
||||
|
||||
LOG_FILES="/app/log/app.log /app/log/db_is_locked.log /app/log/execution_queue.log /app/log/app_front.log /app/log/app.php_errors.log /app/log/IP_changes.log /app/stderr.log /app/stdout.log"
|
||||
|
||||
wait_for_files() {
|
||||
# Wait up to ~10s for at least one of the files to exist
|
||||
attempts=0
|
||||
while [ $attempts -lt 20 ]; do
|
||||
for f in $LOG_FILES; do
|
||||
if [ -f "$f" ]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
attempts=$((attempts+1))
|
||||
sleep 0.5
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
if wait_for_files; then
|
||||
echo "Starting log stream for:"
|
||||
for f in $LOG_FILES; do
|
||||
[ -f "$f" ] && echo " $f"
|
||||
done
|
||||
|
||||
# Use tail -F where available. If tail -F isn't supported, tail -f is used as fallback.
|
||||
# Some minimal images may have busybox tail without -F; this handles both.
|
||||
if tail --version >/dev/null 2>&1; then
|
||||
# GNU tail supports -F
|
||||
tail -n +1 -F $LOG_FILES
|
||||
else
|
||||
# Fallback to -f for busybox; will exit if files rotate or do not exist initially
|
||||
tail -n +1 -f $LOG_FILES
|
||||
fi
|
||||
else
|
||||
echo "No log files appeared after wait; exiting stream script."
|
||||
exit 0
|
||||
fi
|
||||
11
.devcontainer/xdebug-trigger.ini
Executable file
@@ -0,0 +1,11 @@
|
||||
zend_extension=xdebug.so
|
||||
xdebug.mode=debug
|
||||
xdebug.start_with_request=trigger
|
||||
xdebug.trigger_value=VSCODE
|
||||
xdebug.client_host=host.docker.internal
|
||||
xdebug.client_port=9003
|
||||
xdebug.log=/var/log/xdebug.log
|
||||
xdebug.log_level=7
|
||||
xdebug.idekey=VSCODE
|
||||
xdebug.discover_client_host=true
|
||||
xdebug.max_nesting_level=512
|
||||
2
.github/FUNDING.yml
vendored
@@ -1,3 +1,3 @@
|
||||
github: jokob-sk
|
||||
patreon: 84385063
|
||||
patreon: netalertx
|
||||
buy_me_a_coffee: jokobsk
|
||||
|
||||
62
.github/copilot-instructions.md
vendored
Executable file
@@ -0,0 +1,62 @@
|
||||
This is NetAlertX — network monitoring & alerting.
|
||||
|
||||
Purpose: Guide AI assistants to follow NetAlertX architecture, conventions, and safety practices. Be concise, opinionated, and prefer existing helpers/settings over new code or hardcoded values.
|
||||
|
||||
## Architecture (what runs where)
|
||||
- Backend (Python): main loop + GraphQL/REST endpoints orchestrate scans, plugins, workflows, notifications, and JSON export.
|
||||
- Key: `server/__main__.py`, `server/plugin.py`, `server/initialise.py`, `server/api_server/api_server_start.py`
|
||||
- Data (SQLite): persistent state in `db/app.db`; helpers in `server/database.py` and `server/db/*`.
|
||||
- Frontend (Nginx + PHP + JS): UI reads JSON, triggers execution queue events.
|
||||
- Key: `front/`, `front/js/common.js`, `front/php/server/*.php`
|
||||
- Plugins (Python): acquisition/enrichment/publishers under `front/plugins/*` with `config.json` manifests.
|
||||
- Messaging/Workflows: `server/messaging/*`, `server/workflows/*`
|
||||
- API JSON Cache for UI: generated under `api/*.json`
|
||||
|
||||
Backend loop phases (see `server/__main__.py` and `server/plugin.py`): `once`, `schedule`, `always_after_scan`, `before_name_updates`, `on_new_device`, `on_notification`, plus ad‑hoc `run` via execution queue. Plugins execute as scripts that write result logs for ingestion.
|
||||
|
||||
## Plugin patterns that matter
|
||||
- Manifest lives at `front/plugins/<code_name>/config.json`; `code_name` == folder, `unique_prefix` drives settings and filenames (e.g., `ARPSCAN`).
|
||||
- Control via settings: `<PREF>_RUN` (phase), `<PREF>_RUN_SCHD` (cron-like), `<PREF>_CMD` (script path), `<PREF>_RUN_TIMEOUT`, `<PREF>_WATCH` (diff columns).
|
||||
- Data contract: scripts write `/app/log/plugins/last_result.<PREF>.log` (pipe‑delimited: 9 required cols + optional 4). Use `front/plugins/plugin_helper.py`’s `Plugin_Objects` to sanitize text and normalize MACs, then `write_result_file()`.
|
||||
- Device import: define `database_column_definitions` when creating/updating devices; watched fields trigger notifications.
|
||||
|
||||
### Standard Plugin Formats
|
||||
* publisher: Sends notifications to services. Runs `on_notification`. Data source: self.
|
||||
* dev scanner: Creates devices and manages online/offline status. Runs on `schedule`. Data source: self / SQLite DB.
|
||||
* name discovery: Discovers device names via various protocols. Runs `before_name_updates` or on `schedule`. Data source: self.
|
||||
* importer: Imports devices from another service. Runs on `schedule`. Data source: self / SQLite DB.
|
||||
* system: Provides core system functionality. Runs on `schedule` or is always on. Data source: self / Template.
|
||||
* other: Miscellaneous plugins. Runs at various times. Data source: self / Template.
|
||||
|
||||
### Plugin logging & outputs
|
||||
- Always log via `mylog()` like other plugins do (no `print()`). Example: `mylog('verbose', [f'[{pluginName}] In script'])`.
|
||||
- Collect results with `Plugin_Objects.add_object(...)` during processing and call `plugin_objects.write_result_file()` exactly once at the end of the script.
|
||||
- Prefer to log a brief summary before writing (e.g., total objects added) to aid troubleshooting; keep logs concise at `verbose` level unless debugging.
|
||||
|
||||
- Do not write ad‑hoc files for results; the only consumable output is `last_result.<PREF>.log` generated by `Plugin_Objects`.
|
||||
## API/Endpoints quick map
|
||||
- Flask app: `server/api_server/api_server_start.py` exposes routes like `/device/<mac>`, `/devices`, `/devices/export/{csv,json}`, `/devices/import`, `/devices/totals`, `/devices/by-status`, plus `nettools`, `events`, `sessions`, `dbquery`, `metrics`, `sync`.
|
||||
- Authorization: all routes expect header `Authorization: Bearer <API_TOKEN>` via `get_setting_value('API_TOKEN')`.
|
||||
|
||||
## Conventions & helpers to reuse
|
||||
- Settings: add/modify via `ccd()` in `server/initialise.py` or per‑plugin manifest. Never hardcode ports or secrets; use `get_setting_value()`.
|
||||
- Logging: use `logger.mylog(level, [message])`; levels: none/minimal/verbose/debug/trace.
|
||||
- Time/MAC/strings: `helper.py` (`timeNowTZ`, `normalize_mac`, sanitizers). Validate MACs before DB writes.
|
||||
- DB helpers: prefer `server/db/db_helper.py` functions (e.g., `get_table_json`, device condition helpers) over raw SQL in new paths.
|
||||
|
||||
## Dev workflow (devcontainer)
|
||||
- Services: use tasks to (re)start backend and nginx/PHP-FPM. Backend runs with debugpy on 5678; attach a Python debugger if needed.
|
||||
- Run a plugin manually: `python3 front/plugins/<code_name>/script.py` (ensure `sys.path` includes `/app/front/plugins` and `/app/server` like the template).
|
||||
- Testing: pytest available via Alpine packages. Tests live in `test/`; app code is under `server/`. PYTHONPATH is preconfigured to include workspace and `/opt/venv` site‑packages.
|
||||
|
||||
## What “done right” looks like
|
||||
- When adding a plugin, start from `front/plugins/__template`, implement with `plugin_helper`, define manifest settings, and wire phase via `<PREF>_RUN`. Verify logs in `/app/log/plugins/` and data in `api/*.json`.
|
||||
- When introducing new config, define it once (core `ccd()` or plugin manifest) and read it via helpers everywhere.
|
||||
- When exposing new server functionality, add endpoints in `server/api_server/*` and keep authorization consistent; update UI by reading/writing JSON cache rather than bypassing the pipeline.
|
||||
|
||||
## Useful references
|
||||
- Docs: `docs/PLUGINS_DEV.md`, `docs/SETTINGS_SYSTEM.md`, `docs/API_*.md`, `docs/DEBUG_*.md`
|
||||
- Logs: backend `/app/log/app.log`, plugin logs under `/app/log/plugins/`, nginx/php logs under `/var/log/*`
|
||||
|
||||
Assistant expectations
|
||||
- Reference concrete files/paths. Use existing helpers/settings. Keep changes idempotent and safe. Offer a quick validation step (log line, API hit, or JSON export) for anything you add.
|
||||
81
.github/workflows/docker_rewrite.yml
vendored
Executable file
@@ -0,0 +1,81 @@
|
||||
name: docker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- rewrite
|
||||
tags:
|
||||
- '*.*.*'
|
||||
pull_request:
|
||||
branches:
|
||||
- rewrite
|
||||
|
||||
jobs:
|
||||
docker_rewrite:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
if: >
|
||||
contains(github.event.head_commit.message, 'PUSHPROD') != 'True' &&
|
||||
github.repository == 'jokob-sk/NetAlertX'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Set up dynamic build ARGs
|
||||
id: getargs
|
||||
run: echo "version=$(cat ./stable/VERSION)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Get release version
|
||||
id: get_version
|
||||
run: echo "version=Dev" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create .VERSION file
|
||||
run: echo "${{ steps.get_version.outputs.version }}" >> .VERSION
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/jokob-sk/netalertx-dev-rewrite
|
||||
jokobsk/netalertx-dev-rewrite
|
||||
tags: |
|
||||
type=raw,value=latest
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=sha
|
||||
|
||||
- name: Log in to Github Container Registry (GHCR)
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: jokob-sk
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Log in to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
34
.vscode/launch.json
vendored
Executable file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Python Backend Debug: Attach",
|
||||
"type": "debugpy",
|
||||
"request": "attach",
|
||||
"connect": {
|
||||
"host": "localhost",
|
||||
"port": 5678
|
||||
},
|
||||
"pathMappings": [
|
||||
{
|
||||
// Map workspace root to /app for PHP and other resources, plus explicit server mapping for Python.
|
||||
"localRoot": "${workspaceFolder}",
|
||||
"remoteRoot": "/app"
|
||||
},
|
||||
{
|
||||
"localRoot": "${workspaceFolder}/server",
|
||||
"remoteRoot": "/app/server"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PHP Frontend Xdebug: Listen",
|
||||
"type": "php",
|
||||
"request": "launch",
|
||||
"port": 9003,
|
||||
"pathMappings": {
|
||||
"/app": "${workspaceFolder}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
13
.vscode/settings.json
vendored
Executable file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"terminal.integrated.suggest.enabled": true,
|
||||
// Use pytest and look under the test/ folder
|
||||
"python.testing.pytestEnabled": true,
|
||||
"python.testing.unittestEnabled": false,
|
||||
"python.testing.pytestArgs": [
|
||||
"test"
|
||||
],
|
||||
// Ensure VS Code uses the devcontainer virtualenv
|
||||
"python.defaultInterpreterPath": "/opt/venv/bin/python",
|
||||
// Let the Python extension invoke pytest via the interpreter; avoid hardcoded paths
|
||||
// Removed python.testing.pytestPath and legacy pytest.command overrides
|
||||
}
|
||||
94
.vscode/tasks.json
vendored
Executable file
@@ -0,0 +1,94 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Generate Dockerfile",
|
||||
"type": "shell",
|
||||
"command": "${workspaceFolder:NetAlertX}/.devcontainer/scripts/generate-dockerfile.sh",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"panel": "shared",
|
||||
"showReuseMessage": false
|
||||
},
|
||||
"problemMatcher": [],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": false
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder:NetAlertX}"
|
||||
},
|
||||
"icon": {
|
||||
"id": "tools",
|
||||
"color": "terminal.ansiYellow"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Re-Run Startup Script",
|
||||
"type": "shell",
|
||||
"command": "${workspaceFolder:NetAlertX}/.devcontainer/scripts/setup.sh",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"panel": "shared",
|
||||
"showReuseMessage": false
|
||||
},
|
||||
"problemMatcher": [],
|
||||
"icon": {
|
||||
"id": "beaker",
|
||||
"color": "terminal.ansiBlue"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Start Backend (Python)",
|
||||
"type": "shell",
|
||||
"command": "/workspaces/NetAlertX/.devcontainer/scripts/restart-backend.sh",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"panel": "shared",
|
||||
"showReuseMessage": false,
|
||||
"clear": false
|
||||
},
|
||||
"problemMatcher": [],
|
||||
"icon": {
|
||||
"id": "debug-restart",
|
||||
"color": "terminal.ansiGreen"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Start Frontend (nginx and PHP-FPM)",
|
||||
"type": "shell",
|
||||
"command": "killall php-fpm83 nginx 2>/dev/null || true; sleep 1; php-fpm83 & nginx",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"panel": "shared",
|
||||
"showReuseMessage": false,
|
||||
"clear": false
|
||||
},
|
||||
"problemMatcher": [],
|
||||
"icon": {
|
||||
"id": "debug-restart",
|
||||
"color": "terminal.ansiGreen"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Stop Frontend & Backend Services",
|
||||
"type": "shell",
|
||||
"command": "pkill -f 'php-fpm83|nginx|crond|python3' || true",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"panel": "shared",
|
||||
"showReuseMessage": false
|
||||
},
|
||||
"problemMatcher": [],
|
||||
"icon": {
|
||||
"id": "debug-stop",
|
||||
"color": "terminal.ansiRed"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -13,7 +13,7 @@ ENV PATH="/opt/venv/bin:$PATH"
|
||||
|
||||
COPY . ${INSTALL_DIR}/
|
||||
|
||||
RUN pip install openwrt-luci-rpc asusrouter asyncio aiohttp graphene flask flask-cors tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros yattag git+https://github.com/foreign-sub/aiofreepybox.git \
|
||||
RUN pip install openwrt-luci-rpc asusrouter asyncio aiohttp graphene flask flask-cors unifi-sm-api tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros yattag git+https://github.com/foreign-sub/aiofreepybox.git \
|
||||
&& bash -c "find ${INSTALL_DIR} -type d -exec chmod 750 {} \;" \
|
||||
&& bash -c "find ${INSTALL_DIR} -type f -exec chmod 640 {} \;" \
|
||||
&& bash -c "find ${INSTALL_DIR} -type f \( -name '*.sh' -o -name '*.py' -o -name 'speedtest-cli' \) -exec chmod 750 {} \;"
|
||||
|
||||
@@ -43,7 +43,7 @@ RUN phpenmod -v 8.2 sqlite3
|
||||
RUN apt-get install -y python3-venv
|
||||
RUN python3 -m venv myenv
|
||||
|
||||
RUN /bin/bash -c "source myenv/bin/activate && update-alternatives --install /usr/bin/python python /usr/bin/python3 10 && pip3 install openwrt-luci-rpc asusrouter asyncio aiohttp graphene flask flask-cors tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros yattag "
|
||||
RUN /bin/bash -c "source myenv/bin/activate && update-alternatives --install /usr/bin/python python /usr/bin/python3 10 && pip3 install openwrt-luci-rpc asusrouter asyncio aiohttp graphene flask flask-cors unifi-sm-api tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros yattag "
|
||||
|
||||
# Create a buildtimestamp.txt to later check if a new version was released
|
||||
RUN date +%s > ${INSTALL_DIR}/front/buildtimestamp.txt
|
||||
|
||||
0
api/.git-placeholder
Executable file
2
api/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
*
|
||||
!.gitignore
|
||||
358
docs/API.md
@@ -1,6 +1,6 @@
|
||||
# API endpoints
|
||||
# NetAlertX API Documentation
|
||||
|
||||
NetAlertX comes with a couple of API endpoints. All requests need to be authorized (executed in a logged in browser session) or you have to pass the value of the `API_TOKEN` settings as authorization bearer, for example:
|
||||
This API provides programmatic access to **devices, events, sessions, metrics, network tools, and sync** in NetAlertX. It is implemented as a **REST and GraphQL server**. All requests require authentication via **API Token** (`API_TOKEN` setting) unless explicitly noted. For example, to authorize a GraphQL request, you need to use a `Authorization: Bearer API_TOKEN` header as per example below:
|
||||
|
||||
```graphql
|
||||
curl 'http://host:GRAPHQL_PORT/graphql' \
|
||||
@@ -21,347 +21,59 @@ curl 'http://host:GRAPHQL_PORT/graphql' \
|
||||
}'
|
||||
```
|
||||
|
||||
## API Endpoint: GraphQL
|
||||
The API server runs on `0.0.0.0:<graphql_port>` with **CORS enabled** for all main endpoints.
|
||||
|
||||
- Endpoint URL: `php/server/query_graphql.php`
|
||||
- Host: `same as front end (web ui)`
|
||||
- Port: `20212` or as defined by the `GRAPHQL_PORT` setting
|
||||
---
|
||||
|
||||
### Example Query to Fetch Devices
|
||||
## Authentication
|
||||
|
||||
First, let's define the GraphQL query to fetch devices with pagination and sorting options.
|
||||
All endpoints require an API token provided in the HTTP headers:
|
||||
|
||||
```graphql
|
||||
query GetDevices($options: PageQueryOptionsInput) {
|
||||
devices(options: $options) {
|
||||
devices {
|
||||
rowid
|
||||
devMac
|
||||
devName
|
||||
devOwner
|
||||
devType
|
||||
devVendor
|
||||
devLastConnection
|
||||
devStatus
|
||||
}
|
||||
count
|
||||
}
|
||||
}
|
||||
```http
|
||||
Authorization: Bearer <API_TOKEN>
|
||||
```
|
||||
|
||||
See also: [Debugging GraphQL issues](./DEBUG_GRAPHQL.md)
|
||||
|
||||
### `curl` Command
|
||||
|
||||
You can use the following `curl` command to execute the query.
|
||||
|
||||
```sh
|
||||
curl 'http://host:GRAPHQL_PORT/graphql' -X POST -H 'Authorization: Bearer API_TOKEN' -H 'Content-Type: application/json' --data '{
|
||||
"query": "query GetDevices($options: PageQueryOptionsInput) { devices(options: $options) { devices { rowid devMac devName devOwner devType devVendor devLastConnection devStatus } count } }",
|
||||
"variables": {
|
||||
"options": {
|
||||
"page": 1,
|
||||
"limit": 10,
|
||||
"sort": [{ "field": "devName", "order": "asc" }],
|
||||
"search": "",
|
||||
"status": "connected"
|
||||
}
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
### Explanation:
|
||||
|
||||
1. **GraphQL Query**:
|
||||
- The `query` parameter contains the GraphQL query as a string.
|
||||
- The `variables` parameter contains the input variables for the query.
|
||||
|
||||
2. **Query Variables**:
|
||||
- `page`: Specifies the page number of results to fetch.
|
||||
- `limit`: Specifies the number of results per page.
|
||||
- `sort`: Specifies the sorting options, with `field` being the field to sort by and `order` being the sort order (`asc` for ascending or `desc` for descending).
|
||||
- `search`: A search term to filter the devices.
|
||||
- `status`: The status filter to apply (valid values are `my_devices` (determined by the `UI_MY_DEVICES` setting), `connected`, `favorites`, `new`, `down`, `archived`, `offline`).
|
||||
|
||||
3. **`curl` Command**:
|
||||
- The `-X POST` option specifies that we are making a POST request.
|
||||
- The `-H "Content-Type: application/json"` option sets the content type of the request to JSON.
|
||||
- The `-d` option provides the request payload, which includes the GraphQL query and variables.
|
||||
|
||||
### Sample Response
|
||||
|
||||
The response will be in JSON format, similar to the following:
|
||||
If the token is missing or invalid, the server will return:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"devices": {
|
||||
"devices": [
|
||||
{
|
||||
"rowid": 1,
|
||||
"devMac": "00:11:22:33:44:55",
|
||||
"devName": "Device 1",
|
||||
"devOwner": "Owner 1",
|
||||
"devType": "Type 1",
|
||||
"devVendor": "Vendor 1",
|
||||
"devLastConnection": "2025-01-01T00:00:00Z",
|
||||
"devStatus": "connected"
|
||||
},
|
||||
{
|
||||
"rowid": 2,
|
||||
"devMac": "66:77:88:99:AA:BB",
|
||||
"devName": "Device 2",
|
||||
"devOwner": "Owner 2",
|
||||
"devType": "Type 2",
|
||||
"devVendor": "Vendor 2",
|
||||
"devLastConnection": "2025-01-02T00:00:00Z",
|
||||
"devStatus": "connected"
|
||||
}
|
||||
],
|
||||
"count": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Endpoint: JSON files
|
||||
|
||||
This API endpoint retrieves static files, that are periodically updated.
|
||||
|
||||
- Endpoint URL: `php/server/query_json.php?file=<file name>`
|
||||
- Host: `same as front end (web ui)`
|
||||
- Port: `20211` or as defined by the $PORT docker environment variable (same as the port for the web ui)
|
||||
|
||||
### When are the endpoints updated
|
||||
|
||||
The endpoints are updated when objects in the API endpoints are changed.
|
||||
|
||||
### Location of the endpoints
|
||||
|
||||
In the container, these files are located under the `/app/api/` folder. You can access them via the `/php/server/query_json.php?file=user_notifications.json` endpoint.
|
||||
|
||||
### Available endpoints
|
||||
|
||||
You can access the following files:
|
||||
|
||||
| File name | Description |
|
||||
|----------------------|----------------------|
|
||||
| `notification_json_final.json` | The json version of the last notification (e.g. used for webhooks - [sample JSON](https://github.com/jokob-sk/NetAlertX/blob/main/front/report_templates/webhook_json_sample.json)). |
|
||||
| `table_devices.json` | All of the available Devices detected by the app. |
|
||||
| `table_plugins_events.json` | The list of the unprocessed (pending) notification events (plugins_events DB table). |
|
||||
| `table_plugins_history.json` | The list of notification events history. |
|
||||
| `table_plugins_objects.json` | The content of the plugins_objects table. Find more info on the [Plugin system here](https://github.com/jokob-sk/NetAlertX/tree/main/docs/PLUGINS.md)|
|
||||
| `language_strings.json` | The content of the language_strings table, which in turn is loaded from the plugins `config.json` definitions. |
|
||||
| `table_custom_endpoint.json` | A custom endpoint generated by the SQL query specified by the `API_CUSTOM_SQL` setting. |
|
||||
| `table_settings.json` | The content of the settings table. |
|
||||
| `app_state.json` | Contains the current application state. |
|
||||
|
||||
|
||||
### JSON Data format
|
||||
|
||||
The endpoints starting with the `table_` prefix contain most, if not all, data contained in the corresponding database table. The common format for those is:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"db_column_name": "data",
|
||||
"db_column_name2": "data2"
|
||||
},
|
||||
{
|
||||
"db_column_name": "data3",
|
||||
"db_column_name2": "data4"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Example JSON of the `table_devices.json` endpoint with two Devices (database rows):
|
||||
|
||||
```JSON
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"devMac": "Internet",
|
||||
"devName": "Net - Huawei",
|
||||
"devType": "Router",
|
||||
"devVendor": null,
|
||||
"devGroup": "Always on",
|
||||
"devFirstConnection": "2021-01-01 00:00:00",
|
||||
"devLastConnection": "2021-01-28 22:22:11",
|
||||
"devLastIP": "192.168.1.24",
|
||||
"devStaticIP": 0,
|
||||
"devPresentLastScan": 1,
|
||||
"devLastNotification": "2023-01-28 22:22:28.998715",
|
||||
"devIsNew": 0,
|
||||
"devParentMAC": "",
|
||||
"devParentPort": "",
|
||||
"devIcon": "globe"
|
||||
},
|
||||
{
|
||||
"devMac": "a4:8f:ff:aa:ba:1f",
|
||||
"devName": "Net - USG",
|
||||
"devType": "Firewall",
|
||||
"devVendor": "Ubiquiti Inc",
|
||||
"devGroup": "",
|
||||
"devFirstConnection": "2021-02-12 22:05:00",
|
||||
"devLastConnection": "2021-07-17 15:40:00",
|
||||
"devLastIP": "192.168.1.1",
|
||||
"devStaticIP": 1,
|
||||
"devPresentLastScan": 1,
|
||||
"devLastNotification": "2021-07-17 15:40:10.667717",
|
||||
"devIsNew": 0,
|
||||
"devParentMAC": "Internet",
|
||||
"devParentPort": 1,
|
||||
"devIcon": "shield-halved"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## API Endpoint: Prometheus Exporter
|
||||
|
||||
* **Endpoint URL**: `/metrics`
|
||||
* **Host**: (where NetAlertX exporter is running)
|
||||
* **Port**: as configured in the `GRAPHQL_PORT` setting (`20212` by default)
|
||||
|
||||
---
|
||||
|
||||
### Example Output of the `/metrics` Endpoint
|
||||
|
||||
Below is a representative snippet of the metrics you may find when querying the `/metrics` endpoint for `netalertx`. It includes both aggregate counters and `device_status` labels per device.
|
||||
|
||||
```
|
||||
netalertx_connected_devices 31
|
||||
netalertx_offline_devices 54
|
||||
netalertx_down_devices 0
|
||||
netalertx_new_devices 0
|
||||
netalertx_archived_devices 31
|
||||
netalertx_favorite_devices 2
|
||||
netalertx_my_devices 54
|
||||
|
||||
netalertx_device_status{device="Net - Huawei", mac="Internet", ip="1111.111.111.111", vendor="None", first_connection="2021-01-01 00:00:00", last_connection="2025-08-04 17:57:00", dev_type="Router", device_status="Online"} 1
|
||||
netalertx_device_status{device="Net - USG", mac="74:ac:74:ac:74:ac", ip="192.168.1.1", vendor="Ubiquiti Networks Inc.", first_connection="2022-02-12 22:05:00", last_connection="2025-06-07 08:16:49", dev_type="Firewall", device_status="Archived"} 1
|
||||
netalertx_device_status{device="Raspberry Pi 4 LAN", mac="74:ac:74:ac:74:74", ip="192.168.1.9", vendor="Raspberry Pi Trading Ltd", first_connection="2022-02-12 22:05:00", last_connection="2025-08-04 17:57:00", dev_type="Singleboard Computer (SBC)", device_status="Online"} 1
|
||||
...
|
||||
{ "error": "Forbidden" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Metrics Explanation
|
||||
## Base URL
|
||||
|
||||
#### 1. Aggregate Device Counts
|
||||
|
||||
Metric names prefixed with `netalertx_` provide aggregated counts by device status:
|
||||
|
||||
* `netalertx_connected_devices`: number of devices currently connected
|
||||
* `netalertx_offline_devices`: devices currently offline
|
||||
* `netalertx_down_devices`: down/unreachable devices
|
||||
* `netalertx_new_devices`: devices recently detected
|
||||
* `netalertx_archived_devices`: archived devices
|
||||
* `netalertx_favorite_devices`: user-marked favorite devices
|
||||
* `netalertx_my_devices`: devices associated with the current user context
|
||||
|
||||
These numeric values give a high-level overview of device distribution.
|
||||
|
||||
#### 2. Per‑Device Status with Labels
|
||||
|
||||
Each individual device is represented by a `netalertx_device_status` metric, with descriptive labels:
|
||||
|
||||
* `device`: friendly name of the device
|
||||
* `mac`: MAC address (or placeholder)
|
||||
* `ip`: last recorded IP address
|
||||
* `vendor`: manufacturer or "None" if unknown
|
||||
* `first_connection`: timestamp when the device was first observed
|
||||
* `last_connection`: most recent contact timestamp
|
||||
* `dev_type`: device category or type
|
||||
* `device_status`: current status (Online / Offline / Archived / Down / ...)
|
||||
|
||||
The metric value is always `1` (indicating presence or active state) and the combination of labels identifies the device.
|
||||
```
|
||||
http://<server>:<GRAPHQL_PORT>/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### How to Query with `curl`
|
||||
## Endpoints
|
||||
|
||||
To fetch the metrics from the NetAlertX exporter:
|
||||
> [!TIP]
|
||||
> When retrieving devices or settings try using the GraphQL API endpoint first as it is read-optimized.
|
||||
|
||||
```sh
|
||||
curl 'http://<server_ip>:<GRAPHQL_PORT>/metrics' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Accept: text/plain'
|
||||
```
|
||||
* [Device API Endpoints](API_DEVICE.md) – Manage individual devices
|
||||
* [Devices Collection](API_DEVICES.md) – Bulk operations on multiple devices
|
||||
* [Events](API_EVENTS.md) – Device event logging and management
|
||||
* [Sessions](API_SESSIONS.md) – Connection sessions and history
|
||||
* [Settings](API_SETTINGS.md) – Settings
|
||||
* [Metrics](API_METRICS.md) – Prometheus metrics and per-device status
|
||||
* [Network Tools](API_NETTOOLS.md) – Utilities like Wake-on-LAN, traceroute, nslookup, nmap, and internet info
|
||||
* [Online History](API_ONLINEHISTORY.md) – Online/offline device records
|
||||
* [GraphQL](API_GRAPHQL.md) – Advanced queries and filtering
|
||||
* [Sync](API_SYNC.md) – Synchronization between multiple NetAlertX instances
|
||||
* [DB query](API_DBQUERY.md) (⚠ Internal) - Low level database access - use other endpoints if possible
|
||||
|
||||
Replace:
|
||||
|
||||
* `<server_ip>`: IP or hostname of the NetAlertX server
|
||||
* `<GRAPHQL_PORT>`: port specified in your `GRAPHQL_PORT` setting (default: `20212`)
|
||||
* `<API_TOKEN>` your Bearer token from the `API_TOKEN` setting
|
||||
See [Testing](API_TESTS.md) for example requests and usage.
|
||||
|
||||
---
|
||||
|
||||
### Summary
|
||||
|
||||
* **Endpoint**: `/metrics` provides both summary counters and per-device status entries.
|
||||
* **Aggregate metrics** help monitor overall device states.
|
||||
* **Detailed metrics** expose each device’s metadata via labels.
|
||||
* **Use case**: feed into Prometheus for scraping, monitoring, alerting, or charting dashboard views.
|
||||
|
||||
### Prometheus Scraping Configuration
|
||||
|
||||
```yaml
|
||||
scrape_configs:
|
||||
- job_name: 'netalertx'
|
||||
metrics_path: /metrics
|
||||
scheme: http
|
||||
scrape_interval: 60s
|
||||
static_configs:
|
||||
- targets: ['<server_ip>:<GRAPHQL_PORT>']
|
||||
authorization:
|
||||
type: Bearer
|
||||
credentials: <API_TOKEN>
|
||||
```
|
||||
|
||||
### Grafana template
|
||||
|
||||
Grafana template sample: [Download json](./samples/API/Grafana_Dashboard.json)
|
||||
|
||||
## API Endpoint: /log files
|
||||
|
||||
This API endpoint retrieves files from the `/app/log` folder.
|
||||
|
||||
- Endpoint URL: `php/server/query_logs.php?file=<file name>`
|
||||
- Host: `same as front end (web ui)`
|
||||
- Port: `20211` or as defined by the $PORT docker environment variable (same as the port for the web ui)
|
||||
|
||||
| File | Description |
|
||||
|--------------------------|---------------------------------------------------------------|
|
||||
| `IP_changes.log` | Logs of IP address changes |
|
||||
| `app.log` | Main application log |
|
||||
| `app.php_errors.log` | PHP error log |
|
||||
| `app_front.log` | Frontend application log |
|
||||
| `app_nmap.log` | Logs of Nmap scan results |
|
||||
| `db_is_locked.log` | Logs when the database is locked |
|
||||
| `execution_queue.log` | Logs of execution queue activities |
|
||||
| `plugins/` | Directory for temporary plugin-related files (not accessible) |
|
||||
| `report_output.html` | HTML report output |
|
||||
| `report_output.json` | JSON format report output |
|
||||
| `report_output.txt` | Text format report output |
|
||||
| `stderr.log` | Logs of standard error output |
|
||||
| `stdout.log` | Logs of standard output |
|
||||
|
||||
|
||||
## API Endpoint: /config files
|
||||
|
||||
To retrieve files from the `/app/config` folder.
|
||||
|
||||
- Endpoint URL: `php/server/query_config.php?file=<file name>`
|
||||
- Host: `same as front end (web ui)`
|
||||
- Port: `20211` or as defined by the $PORT docker environment variable (same as the port for the web ui)
|
||||
|
||||
| File | Description |
|
||||
|--------------------------|--------------------------------------------------|
|
||||
| `devices.csv` | Devices csv file |
|
||||
| `app.conf` | Application config file |
|
||||
## Notes
|
||||
|
||||
* All endpoints enforce **Bearer token authentication**.
|
||||
* Errors return JSON with `success: False` and an error message.
|
||||
* GraphQL is available for advanced queries, while REST endpoints cover structured use cases.
|
||||
* Endpoints run on `0.0.0.0:<GRAPHQL_PORT>` with **CORS enabled**.
|
||||
* Use consistent API tokens and node/plugin names when interacting with `/sync` to ensure data integrity.
|
||||
|
||||
183
docs/API_DBQUERY.md
Executable file
@@ -0,0 +1,183 @@
|
||||
# Database Query API
|
||||
|
||||
The **Database Query API** provides direct, low-level access to the NetAlertX database. It allows **read, write, update, and delete** operations against tables, using **base64-encoded** SQL or structured parameters.
|
||||
|
||||
> [!Warning]
|
||||
> This API is primarily used internally to generate and render the application UI. These endpoints are low-level and powerful, and should be used with caution. Wherever possible, prefer the [standard API endpoints](API.md). Invalid or unsafe queries can corrupt data.
|
||||
> If you need data in a specific format that is not already provided, please open an issue or pull request with a clear, broadly useful use case. This helps ensure new endpoints benefit the wider community rather than relying on raw database queries.
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
All `/dbquery/*` endpoints require an API token in the HTTP headers:
|
||||
|
||||
```http
|
||||
Authorization: Bearer <API_TOKEN>
|
||||
```
|
||||
|
||||
If the token is missing or invalid:
|
||||
|
||||
```json
|
||||
{ "error": "Forbidden" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Endpoints
|
||||
|
||||
### 1. `POST /dbquery/read`
|
||||
|
||||
Execute a **read-only** SQL query (e.g., `SELECT`).
|
||||
|
||||
#### Request Body
|
||||
|
||||
```json
|
||||
{
|
||||
"rawSql": "U0VMRUNUICogRlJPTSBERVZJQ0VT" // base64 encoded SQL
|
||||
}
|
||||
```
|
||||
|
||||
Decoded SQL:
|
||||
|
||||
```sql
|
||||
SELECT * FROM Devices;
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"results": [
|
||||
{ "devMac": "AA:BB:CC:DD:EE:FF", "devName": "Phone" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### `curl` Example
|
||||
|
||||
```bash
|
||||
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/dbquery/read" \
|
||||
-H "Authorization: Bearer <API_TOKEN>" \
|
||||
-H "Accept: application/json" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"rawSql": "U0VMRUNUICogRlJPTSBERVZJQ0VT"
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. `POST /dbquery/update` (safer than `/dbquery/write`)
|
||||
|
||||
Update rows in a table by `columnName` + `id`. `/dbquery/update` is parameterized to reduce the risk of SQL injection, while `/dbquery/write` executes raw SQL directly.
|
||||
|
||||
#### Request Body
|
||||
|
||||
```json
|
||||
{
|
||||
"columnName": "devMac",
|
||||
"id": ["AA:BB:CC:DD:EE:FF"],
|
||||
"dbtable": "Devices",
|
||||
"columns": ["devName", "devOwner"],
|
||||
"values": ["Laptop", "Alice"]
|
||||
}
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{ "success": true, "updated_count": 1 }
|
||||
```
|
||||
|
||||
#### `curl` Example
|
||||
|
||||
```bash
|
||||
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/dbquery/update" \
|
||||
-H "Authorization: Bearer <API_TOKEN>" \
|
||||
-H "Accept: application/json" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"columnName": "devMac",
|
||||
"id": ["AA:BB:CC:DD:EE:FF"],
|
||||
"dbtable": "Devices",
|
||||
"columns": ["devName", "devOwner"],
|
||||
"values": ["Laptop", "Alice"]
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. `POST /dbquery/write`
|
||||
|
||||
Execute a **write query** (`INSERT`, `UPDATE`, `DELETE`).
|
||||
|
||||
#### Request Body
|
||||
|
||||
```json
|
||||
{
|
||||
"rawSql": "SU5TRVJUIElOVE8gRGV2aWNlcyAoZGV2TWFjLCBkZXYgTmFtZSwgZGV2Rmlyc3RDb25uZWN0aW9uLCBkZXZMYXN0Q29ubmVjdGlvbiwgZGV2TGFzdElQKSBWQUxVRVMgKCc2QTpCQjo0Qzo1RDo2RTonLCAnVGVzdERldmljZScsICcyMDI1LTA4LTMwIDEyOjAwOjAwJywgJzIwMjUtMDgtMzAgMTI6MDA6MDAnLCAnMTAuMC4wLjEwJyk="
|
||||
}
|
||||
```
|
||||
|
||||
Decoded SQL:
|
||||
|
||||
```sql
|
||||
INSERT INTO Devices (devMac, devName, devFirstConnection, devLastConnection, devLastIP)
|
||||
VALUES ('6A:BB:4C:5D:6E', 'TestDevice', '2025-08-30 12:00:00', '2025-08-30 12:00:00', '10.0.0.10');
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{ "success": true, "affected_rows": 1 }
|
||||
```
|
||||
|
||||
#### `curl` Example
|
||||
|
||||
```bash
|
||||
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/dbquery/write" \
|
||||
-H "Authorization: Bearer <API_TOKEN>" \
|
||||
-H "Accept: application/json" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"rawSql": "SU5TRVJUIElOVE8gRGV2aWNlcyAoZGV2TWFjLCBkZXYgTmFtZSwgZGV2Rmlyc3RDb25uZWN0aW9uLCBkZXZMYXN0Q29ubmVjdGlvbiwgZGV2TGFzdElQKSBWQUxVRVMgKCc2QTpCQjo0Qzo1RDo2RTonLCAnVGVzdERldmljZScsICcyMDI1LTA4LTMwIDEyOjAwOjAwJywgJzIwMjUtMDgtMzAgMTI6MDA6MDAnLCAnMTAuMC4wLjEwJyk="
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. `POST /dbquery/delete`
|
||||
|
||||
Delete rows in a table by `columnName` + `id`.
|
||||
|
||||
#### Request Body
|
||||
|
||||
```json
|
||||
{
|
||||
"columnName": "devMac",
|
||||
"id": ["AA:BB:CC:DD:EE:FF"],
|
||||
"dbtable": "Devices"
|
||||
}
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{ "success": true, "deleted_count": 1 }
|
||||
```
|
||||
|
||||
#### `curl` Example
|
||||
|
||||
```bash
|
||||
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/dbquery/delete" \
|
||||
-H "Authorization: Bearer <API_TOKEN>" \
|
||||
-H "Accept: application/json" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"columnName": "devMac",
|
||||
"id": ["AA:BB:CC:DD:EE:FF"],
|
||||
"dbtable": "Devices"
|
||||
}'
|
||||
```
|
||||
233
docs/API_DEVICE.md
Executable file
@@ -0,0 +1,233 @@
|
||||
# Device API Endpoints
|
||||
|
||||
Manage a **single device** by its MAC address. Operations include retrieval, updates, deletion, resetting properties, and copying data between devices. All endpoints require **authorization** via Bearer token.
|
||||
|
||||
---
|
||||
|
||||
## 1. Retrieve Device Details
|
||||
|
||||
* **GET** `/device/<mac>`
|
||||
Fetch all details for a single device, including:
|
||||
|
||||
* Computed status (`devStatus`) → `On-line`, `Off-line`, or `Down`
|
||||
* Session and event counts (`devSessions`, `devEvents`, `devDownAlerts`)
|
||||
* Presence hours (`devPresenceHours`)
|
||||
* Children devices (`devChildrenDynamic`) and NIC children (`devChildrenNicsDynamic`)
|
||||
|
||||
**Special case**: `mac=new` returns a template for a new device with default values.
|
||||
|
||||
**Response** (success):
|
||||
|
||||
```json
|
||||
{
|
||||
"devMac": "AA:BB:CC:DD:EE:FF",
|
||||
"devName": "Net - Huawei",
|
||||
"devOwner": "Admin",
|
||||
"devType": "Router",
|
||||
"devVendor": "Huawei",
|
||||
"devStatus": "On-line",
|
||||
"devSessions": 12,
|
||||
"devEvents": 5,
|
||||
"devDownAlerts": 1,
|
||||
"devPresenceHours": 32,
|
||||
"devChildrenDynamic": [...],
|
||||
"devChildrenNicsDynamic": [...],
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
* Device not found → HTTP 404
|
||||
* Unauthorized → HTTP 403
|
||||
|
||||
---
|
||||
|
||||
## 2. Update Device Fields
|
||||
|
||||
* **POST** `/device/<mac>`
|
||||
Create or update a device record.
|
||||
|
||||
**Request Body**:
|
||||
|
||||
```json
|
||||
{
|
||||
"devName": "New Device",
|
||||
"devOwner": "Admin",
|
||||
"createNew": true
|
||||
}
|
||||
```
|
||||
|
||||
**Behavior**:
|
||||
|
||||
* If `createNew=true` → creates a new device
|
||||
* Otherwise → updates existing device fields
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
* Unauthorized → HTTP 403
|
||||
|
||||
---
|
||||
|
||||
## 3. Delete a Device
|
||||
|
||||
* **DELETE** `/device/<mac>/delete`
|
||||
Deletes the device with the given MAC.
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
* Unauthorized → HTTP 403
|
||||
|
||||
---
|
||||
|
||||
## 4. Delete All Events for a Device
|
||||
|
||||
* **DELETE** `/device/<mac>/events/delete`
|
||||
Removes all events associated with a device.
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Reset Device Properties
|
||||
|
||||
* **POST** `/device/<mac>/reset-props`
|
||||
Resets the device's custom properties to default values.
|
||||
|
||||
**Request Body**: Optional JSON for additional parameters.
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Copy Device Data
|
||||
|
||||
* **POST** `/device/copy`
|
||||
Copy all data from one device to another. If a device exists with `macTo`, it is replaced.
|
||||
|
||||
**Request Body**:
|
||||
|
||||
```json
|
||||
{
|
||||
"macFrom": "AA:BB:CC:DD:EE:FF",
|
||||
"macTo": "11:22:33:44:55:66"
|
||||
}
|
||||
```
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Device copied from AA:BB:CC:DD:EE:FF to 11:22:33:44:55:66"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
* Missing `macFrom` or `macTo` → HTTP 400
|
||||
* Unauthorized → HTTP 403
|
||||
|
||||
---
|
||||
|
||||
## 7. Update a Single Column
|
||||
|
||||
* **POST** `/device/<mac>/update-column`
|
||||
Update one specific column for a device.
|
||||
|
||||
**Request Body**:
|
||||
|
||||
```json
|
||||
{
|
||||
"columnName": "devName",
|
||||
"columnValue": "Updated Device Name"
|
||||
}
|
||||
```
|
||||
|
||||
**Response** (success):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
* Device not found → HTTP 404
|
||||
* Missing `columnName` or `columnValue` → HTTP 400
|
||||
* Unauthorized → HTTP 403
|
||||
|
||||
---
|
||||
|
||||
## Example `curl` Requests
|
||||
|
||||
**Get Device Details**:
|
||||
|
||||
```bash
|
||||
curl -X GET "http://<server_ip>:<GRAPHQL_PORT>/device/AA:BB:CC:DD:EE:FF" \
|
||||
-H "Authorization: Bearer <API_TOKEN>"
|
||||
```
|
||||
|
||||
**Update Device Fields**:
|
||||
|
||||
```bash
|
||||
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/device/AA:BB:CC:DD:EE:FF" \
|
||||
-H "Authorization: Bearer <API_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{"devName": "New Device Name"}'
|
||||
```
|
||||
|
||||
**Delete Device**:
|
||||
|
||||
```bash
|
||||
curl -X DELETE "http://<server_ip>:<GRAPHQL_PORT>/device/AA:BB:CC:DD:EE:FF/delete" \
|
||||
-H "Authorization: Bearer <API_TOKEN>"
|
||||
```
|
||||
|
||||
**Copy Device Data**:
|
||||
|
||||
```bash
|
||||
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/device/copy" \
|
||||
-H "Authorization: Bearer <API_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{"macFrom":"AA:BB:CC:DD:EE:FF","macTo":"11:22:33:44:55:66"}'
|
||||
```
|
||||
|
||||
**Update Single Column**:
|
||||
|
||||
```bash
|
||||
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/device/AA:BB:CC:DD:EE:FF/update-column" \
|
||||
-H "Authorization: Bearer <API_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{"columnName":"devName","columnValue":"Updated Device"}'
|
||||
```
|
||||
|
||||
249
docs/API_DEVICES.md
Executable file
@@ -0,0 +1,249 @@
|
||||
# Devices Collection API Endpoints
|
||||
|
||||
The Devices Collection API provides operations to **retrieve, manage, import/export, and filter devices** in bulk. All endpoints require **authorization** via Bearer token.
|
||||
|
||||
---
|
||||
|
||||
## Endpoints
|
||||
|
||||
### 1. Get All Devices
|
||||
|
||||
* **GET** `/devices`
|
||||
Retrieves all devices from the database.
|
||||
|
||||
**Response** (success):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"devices": [
|
||||
{
|
||||
"devName": "Net - Huawei",
|
||||
"devMAC": "AA:BB:CC:DD:EE:FF",
|
||||
"devIP": "192.168.1.1",
|
||||
"devType": "Router",
|
||||
"devFavorite": 0,
|
||||
"devStatus": "online"
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
* Unauthorized → HTTP 403
|
||||
|
||||
---
|
||||
|
||||
### 2. Delete Devices by MAC
|
||||
|
||||
* **DELETE** `/devices`
|
||||
Deletes devices by MAC address. Supports exact matches or wildcard `*`.
|
||||
|
||||
**Request Body**:
|
||||
|
||||
```json
|
||||
{
|
||||
"macs": ["AA:BB:CC:DD:EE:FF", "11:22:33:*"]
|
||||
}
|
||||
```
|
||||
|
||||
**Behavior**:
|
||||
|
||||
* If `macs` is omitted or `null` → deletes **all devices**.
|
||||
* Wildcards `*` match multiple devices.
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"deleted_count": 5
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
* Unauthorized → HTTP 403
|
||||
|
||||
---
|
||||
|
||||
### 3. Delete Devices with Empty MACs
|
||||
|
||||
* **DELETE** `/devices/empty-macs`
|
||||
Removes all devices where MAC address is null or empty.
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"deleted": 3
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Delete Unknown Devices
|
||||
|
||||
* **DELETE** `/devices/unknown`
|
||||
Deletes devices with names marked as `(unknown)` or `(name not found)`.
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"deleted": 2
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. Export Devices
|
||||
|
||||
* **GET** `/devices/export` or `/devices/export/<format>`
|
||||
Exports all devices in **CSV** (default) or **JSON** format.
|
||||
|
||||
**Query Parameter / URL Parameter**:
|
||||
|
||||
* `format` (optional) → `csv` (default) or `json`
|
||||
|
||||
**CSV Response**:
|
||||
|
||||
* Returns as a downloadable CSV file: `Content-Disposition: attachment; filename=devices.csv`
|
||||
|
||||
**JSON Response**:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{ "devName": "Net - Huawei", "devMAC": "AA:BB:CC:DD:EE:FF", ... },
|
||||
...
|
||||
],
|
||||
"columns": ["devName", "devMAC", "devIP", "devType", "devFavorite", "devStatus"]
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
* Unsupported format → HTTP 400
|
||||
|
||||
---
|
||||
|
||||
### 6. Import Devices from CSV
|
||||
|
||||
* **POST** `/devices/import`
|
||||
Imports devices from an uploaded CSV or base64-encoded CSV content.
|
||||
|
||||
**Request Body** (multipart file or JSON with `content` field):
|
||||
|
||||
```json
|
||||
{
|
||||
"content": "<base64-encoded CSV content>"
|
||||
}
|
||||
```
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"inserted": 25,
|
||||
"skipped_lines": [3, 7]
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
* Missing file or content → HTTP 400 / 404
|
||||
* CSV malformed → HTTP 400
|
||||
|
||||
---
|
||||
|
||||
### 7. Get Device Totals
|
||||
|
||||
* **GET** `/devices/totals`
|
||||
Returns counts of devices by various categories.
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
[
|
||||
120, // Total devices
|
||||
85, // Connected
|
||||
5, // Favorites
|
||||
10, // New
|
||||
8, // Down
|
||||
12 // Archived
|
||||
]
|
||||
```
|
||||
|
||||
*Order: `[all, connected, favorites, new, down, archived]`*
|
||||
|
||||
---
|
||||
|
||||
### 8. Get Devices by Status
|
||||
|
||||
* **GET** `/devices/by-status?status=<status>`
|
||||
Returns devices filtered by status.
|
||||
|
||||
**Query Parameter**:
|
||||
|
||||
* `status` → Supported values: `online`, `offline`, `down`, `archived`, `favorites`, `new`, `my`
|
||||
* If omitted, returns **all devices**.
|
||||
|
||||
**Response** (success):
|
||||
|
||||
```json
|
||||
[
|
||||
{ "id": "AA:BB:CC:DD:EE:FF", "title": "Net - Huawei", "favorite": 0 },
|
||||
{ "id": "11:22:33:44:55:66", "title": "★ USG Firewall", "favorite": 1 }
|
||||
]
|
||||
```
|
||||
|
||||
*If `devFavorite=1`, the title is prepended with a star `★`.*
|
||||
|
||||
---
|
||||
|
||||
## Example `curl` Requests
|
||||
|
||||
**Get All Devices**:
|
||||
|
||||
```sh
|
||||
curl -X GET "http://<server_ip>:<GRAPHQL_PORT>/devices" \
|
||||
-H "Authorization: Bearer <API_TOKEN>"
|
||||
```
|
||||
|
||||
**Delete Devices by MAC**:
|
||||
|
||||
```sh
|
||||
curl -X DELETE "http://<server_ip>:<GRAPHQL_PORT>/devices" \
|
||||
-H "Authorization: Bearer <API_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{"macs":["AA:BB:CC:DD:EE:FF","11:22:33:*"]}'
|
||||
```
|
||||
|
||||
**Export Devices CSV**:
|
||||
|
||||
```sh
|
||||
curl -X GET "http://<server_ip>:<GRAPHQL_PORT>/devices/export?format=csv" \
|
||||
-H "Authorization: Bearer <API_TOKEN>"
|
||||
```
|
||||
|
||||
**Import Devices from CSV**:
|
||||
|
||||
```sh
|
||||
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/devices/import" \
|
||||
-H "Authorization: Bearer <API_TOKEN>" \
|
||||
-F "file=@devices.csv"
|
||||
```
|
||||
|
||||
**Get Devices by Status**:
|
||||
|
||||
```sh
|
||||
curl -X GET "http://<server_ip>:<GRAPHQL_PORT>/devices/by-status?status=online" \
|
||||
-H "Authorization: Bearer <API_TOKEN>"
|
||||
```
|
||||
|
||||
169
docs/API_EVENTS.md
Executable file
@@ -0,0 +1,169 @@
|
||||
# Events API Endpoints
|
||||
|
||||
The Events API provides access to **device event logs**, allowing creation, retrieval, deletion, and summary of events over time.
|
||||
|
||||
---
|
||||
|
||||
## Endpoints
|
||||
|
||||
### 1. Create Event
|
||||
|
||||
* **POST** `/events/create/<mac>`
|
||||
Create an event for a device identified by its MAC address.
|
||||
|
||||
**Request Body** (JSON):
|
||||
|
||||
```json
|
||||
{
|
||||
"ip": "192.168.1.10",
|
||||
"event_type": "Device Down",
|
||||
"additional_info": "Optional info about the event",
|
||||
"pending_alert": 1,
|
||||
"event_time": "2025-08-24T12:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
* **Parameters**:
|
||||
|
||||
* `ip` (string, optional): IP address of the device
|
||||
* `event_type` (string, optional): Type of event (default `"Device Down"`)
|
||||
* `additional_info` (string, optional): Extra information
|
||||
* `pending_alert` (int, optional): 1 if alert email is pending (default 1)
|
||||
* `event_time` (ISO datetime, optional): Event timestamp; defaults to current time
|
||||
|
||||
**Response** (JSON):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Event created for 00:11:22:33:44:55"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Get Events
|
||||
|
||||
* **GET** `/events`
|
||||
Retrieve all events, optionally filtered by MAC address:
|
||||
|
||||
```
|
||||
/events?mac=<mac>
|
||||
```
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"events": [
|
||||
{
|
||||
"eve_MAC": "00:11:22:33:44:55",
|
||||
"eve_IP": "192.168.1.10",
|
||||
"eve_DateTime": "2025-08-24T12:00:00Z",
|
||||
"eve_EventType": "Device Down",
|
||||
"eve_AdditionalInfo": "",
|
||||
"eve_PendingAlertEmail": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Delete Events
|
||||
|
||||
* **DELETE** `/events/<mac>` → Delete events for a specific MAC
|
||||
* **DELETE** `/events` → Delete **all** events
|
||||
* **DELETE** `/events/<days>` → Delete events older than N days
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Deleted events older than <days> days"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Event Totals Over a Period
|
||||
|
||||
* **GET** `/sessions/totals?period=<period>`
|
||||
Return event and session totals over a given period.
|
||||
|
||||
**Query Parameters**:
|
||||
|
||||
| Parameter | Description |
|
||||
| --------- | -------------------------------------------------------------------------------- |
|
||||
| `period` | Time period for totals, e.g., `"7 days"`, `"1 month"`, `"1 year"`, `"100 years"` |
|
||||
|
||||
**Sample Response** (JSON Array):
|
||||
|
||||
```json
|
||||
[120, 85, 5, 10, 3, 7]
|
||||
```
|
||||
|
||||
**Meaning of Values**:
|
||||
|
||||
1. Total events in the period
|
||||
2. Total sessions
|
||||
3. Missing sessions
|
||||
4. Voided events (`eve_EventType LIKE 'VOIDED%'`)
|
||||
5. New device events (`eve_EventType LIKE 'New Device'`)
|
||||
6. Device down events (`eve_EventType LIKE 'Device Down'`)
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
* All endpoints require **authorization** (Bearer token). Unauthorized requests return:
|
||||
|
||||
```json
|
||||
{ "error": "Forbidden" }
|
||||
```
|
||||
|
||||
* Events are stored in the **Events table** with the following fields:
|
||||
`eve_MAC`, `eve_IP`, `eve_DateTime`, `eve_EventType`, `eve_AdditionalInfo`, `eve_PendingAlertEmail`.
|
||||
|
||||
* Event creation automatically logs activity for debugging.
|
||||
|
||||
---
|
||||
|
||||
## Example `curl` Requests
|
||||
|
||||
**Create Event**:
|
||||
|
||||
```sh
|
||||
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/events/create/00:11:22:33:44:55" \
|
||||
-H "Authorization: Bearer <API_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{
|
||||
"ip": "192.168.1.10",
|
||||
"event_type": "Device Down",
|
||||
"additional_info": "Power outage",
|
||||
"pending_alert": 1
|
||||
}'
|
||||
```
|
||||
|
||||
**Get Events for a Device**:
|
||||
|
||||
```sh
|
||||
curl "http://<server_ip>:<GRAPHQL_PORT>/events?mac=00:11:22:33:44:55" \
|
||||
-H "Authorization: Bearer <API_TOKEN>"
|
||||
```
|
||||
|
||||
**Delete Events Older Than 30 Days**:
|
||||
|
||||
```sh
|
||||
curl -X DELETE "http://<server_ip>:<GRAPHQL_PORT>/events/30" \
|
||||
-H "Authorization: Bearer <API_TOKEN>"
|
||||
```
|
||||
|
||||
**Get Event Totals for 7 Days**:
|
||||
|
||||
```sh
|
||||
curl "http://<server_ip>:<GRAPHQL_PORT>/sessions/totals?period=7 days" \
|
||||
-H "Authorization: Bearer <API_TOKEN>"
|
||||
```
|
||||
200
docs/API_GRAPHQL.md
Executable file
@@ -0,0 +1,200 @@
|
||||
# GraphQL API Endpoint
|
||||
|
||||
GraphQL queries are **read-optimized for speed**. Data may be slightly out of date until the file system cache refreshes. The GraphQL endpoints allows you to access the following objects:
|
||||
|
||||
- Devices
|
||||
- Settings
|
||||
|
||||
## Endpoints
|
||||
|
||||
* **GET** `/graphql`
|
||||
Returns a simple status message (useful for browser or debugging).
|
||||
|
||||
* **POST** `/graphql`
|
||||
Execute GraphQL queries against the `devicesSchema`.
|
||||
|
||||
---
|
||||
|
||||
## Devices Query
|
||||
|
||||
### Sample Query
|
||||
|
||||
```graphql
|
||||
query GetDevices($options: PageQueryOptionsInput) {
|
||||
devices(options: $options) {
|
||||
devices {
|
||||
rowid
|
||||
devMac
|
||||
devName
|
||||
devOwner
|
||||
devType
|
||||
devVendor
|
||||
devLastConnection
|
||||
devStatus
|
||||
}
|
||||
count
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Query Parameters
|
||||
|
||||
| Parameter | Description |
|
||||
| --------- | ------------------------------------------------------------------------------------------------------- |
|
||||
| `page` | Page number of results to fetch. |
|
||||
| `limit` | Number of results per page. |
|
||||
| `sort` | Sorting options (`field` = field name, `order` = `asc` or `desc`). |
|
||||
| `search` | Term to filter devices. |
|
||||
| `status` | Filter devices by status: `my_devices`, `connected`, `favorites`, `new`, `down`, `archived`, `offline`. |
|
||||
| `filters` | Additional filters (array of `{ filterColumn, filterValue }`). |
|
||||
|
||||
---
|
||||
|
||||
### `curl` Example
|
||||
|
||||
```sh
|
||||
curl 'http://host:GRAPHQL_PORT/graphql' \
|
||||
-X POST \
|
||||
-H 'Authorization: Bearer API_TOKEN' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"query": "query GetDevices($options: PageQueryOptionsInput) { devices(options: $options) { devices { rowid devMac devName devOwner devType devVendor devLastConnection devStatus } count } }",
|
||||
"variables": {
|
||||
"options": {
|
||||
"page": 1,
|
||||
"limit": 10,
|
||||
"sort": [{ "field": "devName", "order": "asc" }],
|
||||
"search": "",
|
||||
"status": "connected"
|
||||
}
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Sample Response
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"devices": {
|
||||
"devices": [
|
||||
{
|
||||
"rowid": 1,
|
||||
"devMac": "00:11:22:33:44:55",
|
||||
"devName": "Device 1",
|
||||
"devOwner": "Owner 1",
|
||||
"devType": "Type 1",
|
||||
"devVendor": "Vendor 1",
|
||||
"devLastConnection": "2025-01-01T00:00:00Z",
|
||||
"devStatus": "connected"
|
||||
}
|
||||
],
|
||||
"count": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Settings Query
|
||||
|
||||
The **settings query** provides access to NetAlertX configuration stored in the settings table.
|
||||
|
||||
### Sample Query
|
||||
|
||||
```graphql
|
||||
query GetSettings {
|
||||
settings {
|
||||
settings {
|
||||
setKey
|
||||
setName
|
||||
setDescription
|
||||
setType
|
||||
setOptions
|
||||
setGroup
|
||||
setValue
|
||||
setEvents
|
||||
setOverriddenByEnv
|
||||
}
|
||||
count
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Schema Fields
|
||||
|
||||
| Field | Type | Description |
|
||||
| -------------------- | ------- | ------------------------------------------------------------------------ |
|
||||
| `setKey` | String | Unique key identifier for the setting. |
|
||||
| `setName` | String | Human-readable name. |
|
||||
| `setDescription` | String | Description or documentation of the setting. |
|
||||
| `setType` | String | Data type (`string`, `int`, `bool`, `json`, etc.). |
|
||||
| `setOptions` | String | Available options (for dropdown/select-type settings). |
|
||||
| `setGroup` | String | Group/category the setting belongs to. |
|
||||
| `setValue` | String | Current value of the setting. |
|
||||
| `setEvents` | String | Events or triggers related to this setting. |
|
||||
| `setOverriddenByEnv` | Boolean | Whether the setting is overridden by an environment variable at runtime. |
|
||||
|
||||
---
|
||||
|
||||
### `curl` Example
|
||||
|
||||
```sh
|
||||
curl 'http://host:GRAPHQL_PORT/graphql' \
|
||||
-X POST \
|
||||
-H 'Authorization: Bearer API_TOKEN' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"query": "query GetSettings { settings { settings { setKey setName setDescription setType setOptions setGroup setValue setEvents setOverriddenByEnv } count } }"
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Sample Response
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"settings": {
|
||||
"settings": [
|
||||
{
|
||||
"setKey": "UI_MY_DEVICES",
|
||||
"setName": "My Devices Filter",
|
||||
"setDescription": "Defines which statuses to include in the 'My Devices' view.",
|
||||
"setType": "list",
|
||||
"setOptions": "[\"online\",\"new\",\"down\",\"offline\",\"archived\"]",
|
||||
"setGroup": "UI",
|
||||
"setValue": "[\"online\",\"new\"]",
|
||||
"setEvents": null,
|
||||
"setOverriddenByEnv": false
|
||||
},
|
||||
{
|
||||
"setKey": "NETWORK_DEVICE_TYPES",
|
||||
"setName": "Network Device Types",
|
||||
"setDescription": "Types of devices considered as network infrastructure.",
|
||||
"setType": "list",
|
||||
"setOptions": "[\"Router\",\"Switch\",\"AP\"]",
|
||||
"setGroup": "Network",
|
||||
"setValue": "[\"Router\",\"Switch\"]",
|
||||
"setEvents": null,
|
||||
"setOverriddenByEnv": true
|
||||
}
|
||||
],
|
||||
"count": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
* Device and settings queries can be combined in one request since GraphQL supports batching.
|
||||
* The `setOverriddenByEnv` flag helps identify setting values that are locked at container runtime.
|
||||
* The schema is **read-only** — updates must be performed through other APIs or configuration management. See the other [API](API.md) endpoints for details.
|
||||
|
||||
103
docs/API_METRICS.md
Executable file
@@ -0,0 +1,103 @@
|
||||
# Metrics API Endpoint
|
||||
|
||||
The `/metrics` endpoint exposes **Prometheus-compatible metrics** for NetAlertX, including aggregate device counts and per-device status.
|
||||
|
||||
---
|
||||
|
||||
## Endpoint Details
|
||||
|
||||
* **GET** `/metrics` → Returns metrics in plain text.
|
||||
* **Host**: NetAlertX server
|
||||
* **Port**: As configured in `GRAPHQL_PORT` (default: `20212`)
|
||||
|
||||
---
|
||||
|
||||
## Example Output
|
||||
|
||||
```text
|
||||
netalertx_connected_devices 31
|
||||
netalertx_offline_devices 54
|
||||
netalertx_down_devices 0
|
||||
netalertx_new_devices 0
|
||||
netalertx_archived_devices 31
|
||||
netalertx_favorite_devices 2
|
||||
netalertx_my_devices 54
|
||||
|
||||
netalertx_device_status{device="Net - Huawei", mac="Internet", ip="1111.111.111.111", vendor="None", first_connection="2021-01-01 00:00:00", last_connection="2025-08-04 17:57:00", dev_type="Router", device_status="Online"} 1
|
||||
netalertx_device_status{device="Net - USG", mac="74:ac:74:ac:74:ac", ip="192.168.1.1", vendor="Ubiquiti Networks Inc.", first_connection="2022-02-12 22:05:00", last_connection="2025-06-07 08:16:49", dev_type="Firewall", device_status="Archived"} 1
|
||||
netalertx_device_status{device="Raspberry Pi 4 LAN", mac="74:ac:74:ac:74:74", ip="192.168.1.9", vendor="Raspberry Pi Trading Ltd", first_connection="2022-02-12 22:05:00", last_connection="2025-08-04 17:57:00", dev_type="Singleboard Computer (SBC)", device_status="Online"} 1
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Metrics Overview
|
||||
|
||||
### 1. Aggregate Device Counts
|
||||
|
||||
| Metric | Description |
|
||||
| ----------------------------- | ---------------------------------------- |
|
||||
| `netalertx_connected_devices` | Devices currently connected |
|
||||
| `netalertx_offline_devices` | Devices currently offline |
|
||||
| `netalertx_down_devices` | Down/unreachable devices |
|
||||
| `netalertx_new_devices` | Recently detected devices |
|
||||
| `netalertx_archived_devices` | Archived devices |
|
||||
| `netalertx_favorite_devices` | User-marked favorites |
|
||||
| `netalertx_my_devices` | Devices associated with the current user |
|
||||
|
||||
---
|
||||
|
||||
### 2. Per-Device Status
|
||||
|
||||
Metric: `netalertx_device_status`
|
||||
Each device has labels:
|
||||
|
||||
* `device`: friendly name
|
||||
* `mac`: MAC address (or placeholder)
|
||||
* `ip`: last recorded IP
|
||||
* `vendor`: manufacturer or "None"
|
||||
* `first_connection`: timestamp of first detection
|
||||
* `last_connection`: most recent contact
|
||||
* `dev_type`: device type/category
|
||||
* `device_status`: current status (`Online`, `Offline`, `Archived`, `Down`, …)
|
||||
|
||||
Metric value is always `1` (presence indicator).
|
||||
|
||||
---
|
||||
|
||||
## Querying with `curl`
|
||||
|
||||
```sh
|
||||
curl 'http://<server_ip>:<GRAPHQL_PORT>/metrics' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Accept: text/plain'
|
||||
```
|
||||
|
||||
Replace placeholders:
|
||||
|
||||
* `<server_ip>` – NetAlertX host IP/hostname
|
||||
* `<GRAPHQL_PORT>` – configured port (default `20212`)
|
||||
* `<API_TOKEN>` – your API token
|
||||
|
||||
---
|
||||
|
||||
## Prometheus Scraping Configuration
|
||||
|
||||
```yaml
|
||||
scrape_configs:
|
||||
- job_name: 'netalertx'
|
||||
metrics_path: /metrics
|
||||
scheme: http
|
||||
scrape_interval: 60s
|
||||
static_configs:
|
||||
- targets: ['<server_ip>:<GRAPHQL_PORT>']
|
||||
authorization:
|
||||
type: Bearer
|
||||
credentials: <API_TOKEN>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Grafana Dashboard Template
|
||||
|
||||
Sample template JSON: [Download](./samples/API/Grafana_Dashboard.json)
|
||||
243
docs/API_NETTOOLS.md
Executable file
@@ -0,0 +1,243 @@
|
||||
# Net Tools API Endpoints
|
||||
|
||||
The Net Tools API provides **network diagnostic utilities**, including Wake-on-LAN, traceroute, speed testing, DNS resolution, nmap scanning, and internet connection information.
|
||||
|
||||
All endpoints require **authorization** via Bearer token.
|
||||
|
||||
---
|
||||
|
||||
## Endpoints
|
||||
|
||||
### 1. Wake-on-LAN
|
||||
|
||||
* **POST** `/nettools/wakeonlan`
|
||||
Sends a Wake-on-LAN packet to wake a device.
|
||||
|
||||
**Request Body** (JSON):
|
||||
|
||||
```json
|
||||
{
|
||||
"devMac": "AA:BB:CC:DD:EE:FF"
|
||||
}
|
||||
```
|
||||
|
||||
**Response** (success):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "WOL packet sent",
|
||||
"output": "Sent magic packet to AA:BB:CC:DD:EE:FF"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
* Invalid MAC address → HTTP 400
|
||||
* Command failure → HTTP 500
|
||||
|
||||
---
|
||||
|
||||
### 2. Traceroute
|
||||
|
||||
* **POST** `/nettools/traceroute`
|
||||
Performs a traceroute to a specified IP address.
|
||||
|
||||
**Request Body**:
|
||||
|
||||
```json
|
||||
{
|
||||
"devLastIP": "192.168.1.1"
|
||||
}
|
||||
```
|
||||
|
||||
**Response** (success):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"output": "traceroute output as string"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
* Invalid IP → HTTP 400
|
||||
* Traceroute command failure → HTTP 500
|
||||
|
||||
---
|
||||
|
||||
### 3. Speedtest
|
||||
|
||||
* **GET** `/nettools/speedtest`
|
||||
Runs an internet speed test using `speedtest-cli`.
|
||||
|
||||
**Response** (success):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"output": [
|
||||
"Ping: 15 ms",
|
||||
"Download: 120.5 Mbit/s",
|
||||
"Upload: 22.4 Mbit/s"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
* Command failure → HTTP 500
|
||||
|
||||
---
|
||||
|
||||
### 4. DNS Lookup (nslookup)
|
||||
|
||||
* **POST** `/nettools/nslookup`
|
||||
Resolves an IP address or hostname using `nslookup`.
|
||||
|
||||
**Request Body**:
|
||||
|
||||
```json
|
||||
{
|
||||
"devLastIP": "8.8.8.8"
|
||||
}
|
||||
```
|
||||
|
||||
**Response** (success):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"output": [
|
||||
"Server: 8.8.8.8",
|
||||
"Address: 8.8.8.8#53",
|
||||
"Name: google-public-dns-a.google.com"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
* Missing or invalid `devLastIP` → HTTP 400
|
||||
* Command failure → HTTP 500
|
||||
|
||||
---
|
||||
|
||||
### 5. Nmap Scan
|
||||
|
||||
* **POST** `/nettools/nmap`
|
||||
Runs an nmap scan on a target IP address or range.
|
||||
|
||||
**Request Body**:
|
||||
|
||||
```json
|
||||
{
|
||||
"scan": "192.168.1.0/24",
|
||||
"mode": "fast"
|
||||
}
|
||||
```
|
||||
|
||||
**Supported Modes**:
|
||||
|
||||
| Mode | nmap Arguments |
|
||||
| --------------- | -------------- |
|
||||
| `fast` | `-F` |
|
||||
| `normal` | default |
|
||||
| `detail` | `-A` |
|
||||
| `skipdiscovery` | `-Pn` |
|
||||
|
||||
**Response** (success):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"mode": "fast",
|
||||
"ip": "192.168.1.0/24",
|
||||
"output": [
|
||||
"Starting Nmap 7.91",
|
||||
"Host 192.168.1.1 is up",
|
||||
"... scan results ..."
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
* Invalid IP → HTTP 400
|
||||
* Invalid mode → HTTP 400
|
||||
* Command failure → HTTP 500
|
||||
|
||||
---
|
||||
|
||||
### 6. Internet Connection Info
|
||||
|
||||
* **GET** `/nettools/internetinfo`
|
||||
Fetches public internet connection information using `ipinfo.io`.
|
||||
|
||||
**Response** (success):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"output": "IP: 203.0.113.5 City: Sydney Country: AU Org: Example ISP"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
* Failed request or empty response → HTTP 500
|
||||
|
||||
---
|
||||
|
||||
## Example `curl` Requests
|
||||
|
||||
**Wake-on-LAN**:
|
||||
|
||||
```sh
|
||||
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/nettools/wakeonlan" \
|
||||
-H "Authorization: Bearer <API_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{"devMac":"AA:BB:CC:DD:EE:FF"}'
|
||||
```
|
||||
|
||||
**Traceroute**:
|
||||
|
||||
```sh
|
||||
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/nettools/traceroute" \
|
||||
-H "Authorization: Bearer <API_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{"devLastIP":"192.168.1.1"}'
|
||||
```
|
||||
|
||||
**Speedtest**:
|
||||
|
||||
```sh
|
||||
curl "http://<server_ip>:<GRAPHQL_PORT>/nettools/speedtest" \
|
||||
-H "Authorization: Bearer <API_TOKEN>"
|
||||
```
|
||||
|
||||
**Nslookup**:
|
||||
|
||||
```sh
|
||||
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/nettools/nslookup" \
|
||||
-H "Authorization: Bearer <API_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{"devLastIP":"8.8.8.8"}'
|
||||
```
|
||||
|
||||
**Nmap Scan**:
|
||||
|
||||
```sh
|
||||
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/nettools/nmap" \
|
||||
-H "Authorization: Bearer <API_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{"scan":"192.168.1.0/24","mode":"fast"}'
|
||||
```
|
||||
|
||||
**Internet Info**:
|
||||
|
||||
```sh
|
||||
curl "http://<server_ip>:<GRAPHQL_PORT>/nettools/internetinfo" \
|
||||
-H "Authorization: Bearer <API_TOKEN>"
|
||||
```
|
||||
370
docs/API_OLD.md
Executable file
@@ -0,0 +1,370 @@
|
||||
# [Deprecated] API endpoints
|
||||
|
||||
> [!WARNING]
|
||||
> Some of these endpoints will be deprecated soon. Please refere to the new [API](API.md) endpoints docs for details on the new API layer.
|
||||
|
||||
NetAlertX comes with a couple of API endpoints. All requests need to be authorized (executed in a logged in browser session) or you have to pass the value of the `API_TOKEN` settings as authorization bearer, for example:
|
||||
|
||||
```graphql
|
||||
curl 'http://host:GRAPHQL_PORT/graphql' \
|
||||
-X POST \
|
||||
-H 'Authorization: Bearer API_TOKEN' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"query": "query GetDevices($options: PageQueryOptionsInput) { devices(options: $options) { devices { rowid devMac devName devOwner devType devVendor devLastConnection devStatus } count } }",
|
||||
"variables": {
|
||||
"options": {
|
||||
"page": 1,
|
||||
"limit": 10,
|
||||
"sort": [{ "field": "devName", "order": "asc" }],
|
||||
"search": "",
|
||||
"status": "connected"
|
||||
}
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
## API Endpoint: GraphQL
|
||||
|
||||
- Endpoint URL: `php/server/query_graphql.php`
|
||||
- Host: `same as front end (web ui)`
|
||||
- Port: `20212` or as defined by the `GRAPHQL_PORT` setting
|
||||
|
||||
### Example Query to Fetch Devices
|
||||
|
||||
First, let's define the GraphQL query to fetch devices with pagination and sorting options.
|
||||
|
||||
```graphql
|
||||
query GetDevices($options: PageQueryOptionsInput) {
|
||||
devices(options: $options) {
|
||||
devices {
|
||||
rowid
|
||||
devMac
|
||||
devName
|
||||
devOwner
|
||||
devType
|
||||
devVendor
|
||||
devLastConnection
|
||||
devStatus
|
||||
}
|
||||
count
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
See also: [Debugging GraphQL issues](./DEBUG_GRAPHQL.md)
|
||||
|
||||
### `curl` Command
|
||||
|
||||
You can use the following `curl` command to execute the query.
|
||||
|
||||
```sh
|
||||
curl 'http://host:GRAPHQL_PORT/graphql' -X POST -H 'Authorization: Bearer API_TOKEN' -H 'Content-Type: application/json' --data '{
|
||||
"query": "query GetDevices($options: PageQueryOptionsInput) { devices(options: $options) { devices { rowid devMac devName devOwner devType devVendor devLastConnection devStatus } count } }",
|
||||
"variables": {
|
||||
"options": {
|
||||
"page": 1,
|
||||
"limit": 10,
|
||||
"sort": [{ "field": "devName", "order": "asc" }],
|
||||
"search": "",
|
||||
"status": "connected"
|
||||
}
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
### Explanation:
|
||||
|
||||
1. **GraphQL Query**:
|
||||
- The `query` parameter contains the GraphQL query as a string.
|
||||
- The `variables` parameter contains the input variables for the query.
|
||||
|
||||
2. **Query Variables**:
|
||||
- `page`: Specifies the page number of results to fetch.
|
||||
- `limit`: Specifies the number of results per page.
|
||||
- `sort`: Specifies the sorting options, with `field` being the field to sort by and `order` being the sort order (`asc` for ascending or `desc` for descending).
|
||||
- `search`: A search term to filter the devices.
|
||||
- `status`: The status filter to apply (valid values are `my_devices` (determined by the `UI_MY_DEVICES` setting), `connected`, `favorites`, `new`, `down`, `archived`, `offline`).
|
||||
|
||||
3. **`curl` Command**:
|
||||
- The `-X POST` option specifies that we are making a POST request.
|
||||
- The `-H "Content-Type: application/json"` option sets the content type of the request to JSON.
|
||||
- The `-d` option provides the request payload, which includes the GraphQL query and variables.
|
||||
|
||||
### Sample Response
|
||||
|
||||
The response will be in JSON format, similar to the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"devices": {
|
||||
"devices": [
|
||||
{
|
||||
"rowid": 1,
|
||||
"devMac": "00:11:22:33:44:55",
|
||||
"devName": "Device 1",
|
||||
"devOwner": "Owner 1",
|
||||
"devType": "Type 1",
|
||||
"devVendor": "Vendor 1",
|
||||
"devLastConnection": "2025-01-01T00:00:00Z",
|
||||
"devStatus": "connected"
|
||||
},
|
||||
{
|
||||
"rowid": 2,
|
||||
"devMac": "66:77:88:99:AA:BB",
|
||||
"devName": "Device 2",
|
||||
"devOwner": "Owner 2",
|
||||
"devType": "Type 2",
|
||||
"devVendor": "Vendor 2",
|
||||
"devLastConnection": "2025-01-02T00:00:00Z",
|
||||
"devStatus": "connected"
|
||||
}
|
||||
],
|
||||
"count": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Endpoint: JSON files
|
||||
|
||||
This API endpoint retrieves static files, that are periodically updated.
|
||||
|
||||
- Endpoint URL: `php/server/query_json.php?file=<file name>`
|
||||
- Host: `same as front end (web ui)`
|
||||
- Port: `20211` or as defined by the $PORT docker environment variable (same as the port for the web ui)
|
||||
|
||||
### When are the endpoints updated
|
||||
|
||||
The endpoints are updated when objects in the API endpoints are changed.
|
||||
|
||||
### Location of the endpoints
|
||||
|
||||
In the container, these files are located under the `/app/api/` folder. You can access them via the `/php/server/query_json.php?file=user_notifications.json` endpoint.
|
||||
|
||||
### Available endpoints
|
||||
|
||||
You can access the following files:
|
||||
|
||||
| File name | Description |
|
||||
|----------------------|----------------------|
|
||||
| `notification_json_final.json` | The json version of the last notification (e.g. used for webhooks - [sample JSON](https://github.com/jokob-sk/NetAlertX/blob/main/front/report_templates/webhook_json_sample.json)). |
|
||||
| `table_devices.json` | All of the available Devices detected by the app. |
|
||||
| `table_plugins_events.json` | The list of the unprocessed (pending) notification events (plugins_events DB table). |
|
||||
| `table_plugins_history.json` | The list of notification events history. |
|
||||
| `table_plugins_objects.json` | The content of the plugins_objects table. Find more info on the [Plugin system here](https://github.com/jokob-sk/NetAlertX/tree/main/docs/PLUGINS.md)|
|
||||
| `language_strings.json` | The content of the language_strings table, which in turn is loaded from the plugins `config.json` definitions. |
|
||||
| `table_custom_endpoint.json` | A custom endpoint generated by the SQL query specified by the `API_CUSTOM_SQL` setting. |
|
||||
| `table_settings.json` | The content of the settings table. |
|
||||
| `app_state.json` | Contains the current application state. |
|
||||
|
||||
|
||||
### JSON Data format
|
||||
|
||||
The endpoints starting with the `table_` prefix contain most, if not all, data contained in the corresponding database table. The common format for those is:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"db_column_name": "data",
|
||||
"db_column_name2": "data2"
|
||||
},
|
||||
{
|
||||
"db_column_name": "data3",
|
||||
"db_column_name2": "data4"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Example JSON of the `table_devices.json` endpoint with two Devices (database rows):
|
||||
|
||||
```JSON
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"devMac": "Internet",
|
||||
"devName": "Net - Huawei",
|
||||
"devType": "Router",
|
||||
"devVendor": null,
|
||||
"devGroup": "Always on",
|
||||
"devFirstConnection": "2021-01-01 00:00:00",
|
||||
"devLastConnection": "2021-01-28 22:22:11",
|
||||
"devLastIP": "192.168.1.24",
|
||||
"devStaticIP": 0,
|
||||
"devPresentLastScan": 1,
|
||||
"devLastNotification": "2023-01-28 22:22:28.998715",
|
||||
"devIsNew": 0,
|
||||
"devParentMAC": "",
|
||||
"devParentPort": "",
|
||||
"devIcon": "globe"
|
||||
},
|
||||
{
|
||||
"devMac": "a4:8f:ff:aa:ba:1f",
|
||||
"devName": "Net - USG",
|
||||
"devType": "Firewall",
|
||||
"devVendor": "Ubiquiti Inc",
|
||||
"devGroup": "",
|
||||
"devFirstConnection": "2021-02-12 22:05:00",
|
||||
"devLastConnection": "2021-07-17 15:40:00",
|
||||
"devLastIP": "192.168.1.1",
|
||||
"devStaticIP": 1,
|
||||
"devPresentLastScan": 1,
|
||||
"devLastNotification": "2021-07-17 15:40:10.667717",
|
||||
"devIsNew": 0,
|
||||
"devParentMAC": "Internet",
|
||||
"devParentPort": 1,
|
||||
"devIcon": "shield-halved"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## API Endpoint: Prometheus Exporter
|
||||
|
||||
* **Endpoint URL**: `/metrics`
|
||||
* **Host**: (where NetAlertX exporter is running)
|
||||
* **Port**: as configured in the `GRAPHQL_PORT` setting (`20212` by default)
|
||||
|
||||
---
|
||||
|
||||
### Example Output of the `/metrics` Endpoint
|
||||
|
||||
Below is a representative snippet of the metrics you may find when querying the `/metrics` endpoint for `netalertx`. It includes both aggregate counters and `device_status` labels per device.
|
||||
|
||||
```
|
||||
netalertx_connected_devices 31
|
||||
netalertx_offline_devices 54
|
||||
netalertx_down_devices 0
|
||||
netalertx_new_devices 0
|
||||
netalertx_archived_devices 31
|
||||
netalertx_favorite_devices 2
|
||||
netalertx_my_devices 54
|
||||
|
||||
netalertx_device_status{device="Net - Huawei", mac="Internet", ip="1111.111.111.111", vendor="None", first_connection="2021-01-01 00:00:00", last_connection="2025-08-04 17:57:00", dev_type="Router", device_status="Online"} 1
|
||||
netalertx_device_status{device="Net - USG", mac="74:ac:74:ac:74:ac", ip="192.168.1.1", vendor="Ubiquiti Networks Inc.", first_connection="2022-02-12 22:05:00", last_connection="2025-06-07 08:16:49", dev_type="Firewall", device_status="Archived"} 1
|
||||
netalertx_device_status{device="Raspberry Pi 4 LAN", mac="74:ac:74:ac:74:74", ip="192.168.1.9", vendor="Raspberry Pi Trading Ltd", first_connection="2022-02-12 22:05:00", last_connection="2025-08-04 17:57:00", dev_type="Singleboard Computer (SBC)", device_status="Online"} 1
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Metrics Explanation
|
||||
|
||||
#### 1. Aggregate Device Counts
|
||||
|
||||
Metric names prefixed with `netalertx_` provide aggregated counts by device status:
|
||||
|
||||
* `netalertx_connected_devices`: number of devices currently connected
|
||||
* `netalertx_offline_devices`: devices currently offline
|
||||
* `netalertx_down_devices`: down/unreachable devices
|
||||
* `netalertx_new_devices`: devices recently detected
|
||||
* `netalertx_archived_devices`: archived devices
|
||||
* `netalertx_favorite_devices`: user-marked favorite devices
|
||||
* `netalertx_my_devices`: devices associated with the current user context
|
||||
|
||||
These numeric values give a high-level overview of device distribution.
|
||||
|
||||
#### 2. Per‑Device Status with Labels
|
||||
|
||||
Each individual device is represented by a `netalertx_device_status` metric, with descriptive labels:
|
||||
|
||||
* `device`: friendly name of the device
|
||||
* `mac`: MAC address (or placeholder)
|
||||
* `ip`: last recorded IP address
|
||||
* `vendor`: manufacturer or "None" if unknown
|
||||
* `first_connection`: timestamp when the device was first observed
|
||||
* `last_connection`: most recent contact timestamp
|
||||
* `dev_type`: device category or type
|
||||
* `device_status`: current status (Online / Offline / Archived / Down / ...)
|
||||
|
||||
The metric value is always `1` (indicating presence or active state) and the combination of labels identifies the device.
|
||||
|
||||
---
|
||||
|
||||
### How to Query with `curl`
|
||||
|
||||
To fetch the metrics from the NetAlertX exporter:
|
||||
|
||||
```sh
|
||||
curl 'http://<server_ip>:<GRAPHQL_PORT>/metrics' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Accept: text/plain'
|
||||
```
|
||||
|
||||
Replace:
|
||||
|
||||
* `<server_ip>`: IP or hostname of the NetAlertX server
|
||||
* `<GRAPHQL_PORT>`: port specified in your `GRAPHQL_PORT` setting (default: `20212`)
|
||||
* `<API_TOKEN>` your Bearer token from the `API_TOKEN` setting
|
||||
|
||||
---
|
||||
|
||||
### Summary
|
||||
|
||||
* **Endpoint**: `/metrics` provides both summary counters and per-device status entries.
|
||||
* **Aggregate metrics** help monitor overall device states.
|
||||
* **Detailed metrics** expose each device’s metadata via labels.
|
||||
* **Use case**: feed into Prometheus for scraping, monitoring, alerting, or charting dashboard views.
|
||||
|
||||
### Prometheus Scraping Configuration
|
||||
|
||||
```yaml
|
||||
scrape_configs:
|
||||
- job_name: 'netalertx'
|
||||
metrics_path: /metrics
|
||||
scheme: http
|
||||
scrape_interval: 60s
|
||||
static_configs:
|
||||
- targets: ['<server_ip>:<GRAPHQL_PORT>']
|
||||
authorization:
|
||||
type: Bearer
|
||||
credentials: <API_TOKEN>
|
||||
```
|
||||
|
||||
### Grafana template
|
||||
|
||||
Grafana template sample: [Download json](./samples/API/Grafana_Dashboard.json)
|
||||
|
||||
## API Endpoint: /log files
|
||||
|
||||
This API endpoint retrieves files from the `/app/log` folder.
|
||||
|
||||
- Endpoint URL: `php/server/query_logs.php?file=<file name>`
|
||||
- Host: `same as front end (web ui)`
|
||||
- Port: `20211` or as defined by the $PORT docker environment variable (same as the port for the web ui)
|
||||
|
||||
| File | Description |
|
||||
|--------------------------|---------------------------------------------------------------|
|
||||
| `IP_changes.log` | Logs of IP address changes |
|
||||
| `app.log` | Main application log |
|
||||
| `app.php_errors.log` | PHP error log |
|
||||
| `app_front.log` | Frontend application log |
|
||||
| `app_nmap.log` | Logs of Nmap scan results |
|
||||
| `db_is_locked.log` | Logs when the database is locked |
|
||||
| `execution_queue.log` | Logs of execution queue activities |
|
||||
| `plugins/` | Directory for temporary plugin-related files (not accessible) |
|
||||
| `report_output.html` | HTML report output |
|
||||
| `report_output.json` | JSON format report output |
|
||||
| `report_output.txt` | Text format report output |
|
||||
| `stderr.log` | Logs of standard error output |
|
||||
| `stdout.log` | Logs of standard output |
|
||||
|
||||
|
||||
## API Endpoint: /config files
|
||||
|
||||
To retrieve files from the `/app/config` folder.
|
||||
|
||||
- Endpoint URL: `php/server/query_config.php?file=<file name>`
|
||||
- Host: `same as front end (web ui)`
|
||||
- Port: `20211` or as defined by the $PORT docker environment variable (same as the port for the web ui)
|
||||
|
||||
| File | Description |
|
||||
|--------------------------|--------------------------------------------------|
|
||||
| `devices.csv` | Devices csv file |
|
||||
| `app.conf` | Application config file |
|
||||
|
||||
32
docs/API_ONLINEHISTORY.md
Executable file
@@ -0,0 +1,32 @@
|
||||
# Online History API Endpoints
|
||||
|
||||
Manage the **online history records** of devices. Currently, the API supports deletion of all history entries. All endpoints require **authorization**.
|
||||
|
||||
---
|
||||
|
||||
## 1. Delete Online History
|
||||
|
||||
* **DELETE** `/history`
|
||||
Remove **all records** from the online history table (`Online_History`). This operation **cannot be undone**.
|
||||
|
||||
**Response** (success):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Deleted online history"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
* Unauthorized → HTTP 403
|
||||
|
||||
---
|
||||
|
||||
### Example `curl` Request
|
||||
|
||||
```bash
|
||||
curl -X DELETE "http://<server_ip>:<GRAPHQL_PORT>/history" \
|
||||
-H "Authorization: Bearer <API_TOKEN>"
|
||||
```
|
||||
240
docs/API_SESSIONS.md
Executable file
@@ -0,0 +1,240 @@
|
||||
# Sessions API Endpoints
|
||||
|
||||
Track and manage device connection sessions. Sessions record when a device connects or disconnects on the network.
|
||||
|
||||
### Create a Session
|
||||
|
||||
* **POST** `/sessions/create` → Create a new session for a device
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"mac": "AA:BB:CC:DD:EE:FF",
|
||||
"ip": "192.168.1.10",
|
||||
"start_time": "2025-08-01T10:00:00",
|
||||
"end_time": "2025-08-01T12:00:00", // optional
|
||||
"event_type_conn": "Connected", // optional, default "Connected"
|
||||
"event_type_disc": "Disconnected" // optional, default "Disconnected"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Session created for MAC AA:BB:CC:DD:EE:FF"
|
||||
}
|
||||
```
|
||||
|
||||
#### `curl` Example
|
||||
|
||||
```bash
|
||||
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/sessions/create" \
|
||||
-H "Authorization: Bearer <API_TOKEN>" \
|
||||
-H "Accept: application/json" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"mac": "AA:BB:CC:DD:EE:FF",
|
||||
"ip": "192.168.1.10",
|
||||
"start_time": "2025-08-01T10:00:00",
|
||||
"end_time": "2025-08-01T12:00:00",
|
||||
"event_type_conn": "Connected",
|
||||
"event_type_disc": "Disconnected"
|
||||
}'
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Delete Sessions
|
||||
|
||||
* **DELETE** `/sessions/delete` → Delete all sessions for a given MAC
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"mac": "AA:BB:CC:DD:EE:FF"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Deleted sessions for MAC AA:BB:CC:DD:EE:FF"
|
||||
}
|
||||
```
|
||||
|
||||
#### `curl` Example
|
||||
|
||||
```bash
|
||||
curl -X DELETE "http://<server_ip>:<GRAPHQL_PORT>/sessions/delete" \
|
||||
-H "Authorization: Bearer <API_TOKEN>" \
|
||||
-H "Accept: application/json" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"mac": "AA:BB:CC:DD:EE:FF"
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### List Sessions
|
||||
|
||||
* **GET** `/sessions/list` → Retrieve sessions optionally filtered by device and date range
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
* `mac` (optional) → Filter by device MAC address
|
||||
* `start_date` (optional) → Filter sessions starting from this date (`YYYY-MM-DD`)
|
||||
* `end_date` (optional) → Filter sessions ending by this date (`YYYY-MM-DD`)
|
||||
|
||||
**Example:**
|
||||
|
||||
```
|
||||
/sessions/list?mac=AA:BB:CC:DD:EE:FF&start_date=2025-08-01&end_date=2025-08-21
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"sessions": [
|
||||
{
|
||||
"ses_MAC": "AA:BB:CC:DD:EE:FF",
|
||||
"ses_Connection": "2025-08-01 10:00",
|
||||
"ses_Disconnection": "2025-08-01 12:00",
|
||||
"ses_Duration": "2h 0m",
|
||||
"ses_IP": "192.168.1.10",
|
||||
"ses_Info": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
#### `curl` Example
|
||||
|
||||
```bash
|
||||
curl -X GET "http://<server_ip>:<GRAPHQL_PORT>/sessions/list?mac=AA:BB:CC:DD:EE:FF&start_date=2025-08-01&end_date=2025-08-21" \
|
||||
-H "Authorization: Bearer <API_TOKEN>" \
|
||||
-H "Accept: application/json"
|
||||
```
|
||||
---
|
||||
|
||||
### Calendar View of Sessions
|
||||
|
||||
* **GET** `/sessions/calendar` → View sessions in calendar format
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
* `start` → Start date (`YYYY-MM-DD`)
|
||||
* `end` → End date (`YYYY-MM-DD`)
|
||||
|
||||
**Example:**
|
||||
|
||||
```
|
||||
/sessions/calendar?start=2025-08-01&end=2025-08-21
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"sessions": [
|
||||
{
|
||||
"resourceId": "AA:BB:CC:DD:EE:FF",
|
||||
"title": "",
|
||||
"start": "2025-08-01T10:00:00",
|
||||
"end": "2025-08-01T12:00:00",
|
||||
"color": "#00a659",
|
||||
"tooltip": "Connection: 2025-08-01 10:00\nDisconnection: 2025-08-01 12:00\nIP: 192.168.1.10",
|
||||
"className": "no-border"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### `curl` Example
|
||||
|
||||
```bash
|
||||
curl -X GET "http://<server_ip>:<GRAPHQL_PORT>/sessions/calendar?start=2025-08-01&end=2025-08-21" \
|
||||
-H "Authorization: Bearer <API_TOKEN>" \
|
||||
-H "Accept: application/json"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Device Sessions
|
||||
|
||||
* **GET** `/sessions/<mac>` → Retrieve sessions for a specific device
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
* `period` → Period to retrieve sessions (`1 day`, `7 days`, `1 month`, etc.)
|
||||
Default: `1 day`
|
||||
|
||||
**Example:**
|
||||
|
||||
```
|
||||
/sessions/AA:BB:CC:DD:EE:FF?period=7 days
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"sessions": [
|
||||
{
|
||||
"ses_MAC": "AA:BB:CC:DD:EE:FF",
|
||||
"ses_Connection": "2025-08-01 10:00",
|
||||
"ses_Disconnection": "2025-08-01 12:00",
|
||||
"ses_Duration": "2h 0m",
|
||||
"ses_IP": "192.168.1.10",
|
||||
"ses_Info": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### `curl` Example
|
||||
|
||||
```bash
|
||||
curl -X GET "http://<server_ip>:<GRAPHQL_PORT>/sessions/AA:BB:CC:DD:EE:FF?period=7%20days" \
|
||||
-H "Authorization: Bearer <API_TOKEN>" \
|
||||
-H "Accept: application/json"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Session Events Summary
|
||||
|
||||
* **GET** `/sessions/session-events` → Retrieve a summary of session events
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
* `type` → Event type (`all`, `sessions`, `missing`, `voided`, `new`, `down`)
|
||||
Default: `all`
|
||||
* `period` → Period to retrieve events (`7 days`, `1 month`, etc.)
|
||||
|
||||
**Example:**
|
||||
|
||||
```
|
||||
/sessions/session-events?type=all&period=7 days
|
||||
```
|
||||
|
||||
**Response:**
|
||||
Returns a list of events or sessions with formatted connection, disconnection, duration, and IP information.
|
||||
|
||||
#### `curl` Example
|
||||
|
||||
```bash
|
||||
curl -X GET "http://<server_ip>:<GRAPHQL_PORT>/sessions/session-events?type=all&period=7%20days" \
|
||||
-H "Authorization: Bearer <API_TOKEN>" \
|
||||
-H "Accept: application/json"
|
||||
```
|
||||
92
docs/API_SETTINGS.md
Executable file
@@ -0,0 +1,92 @@
|
||||
# Settings API Endpoints
|
||||
|
||||
Retrieve application settings stored in the configuration system. This endpoint is useful for quickly fetching individual settings such as `API_TOKEN` or `TIMEZONE`.
|
||||
|
||||
For bulk or structured access (all settings, schema details, or filtering), use the [GraphQL API Endpoint](API_GRAPHQL.md).
|
||||
|
||||
---
|
||||
|
||||
### Get a Setting
|
||||
|
||||
* **GET** `/settings/<key>` → Retrieve the value of a specific setting
|
||||
|
||||
**Path Parameter:**
|
||||
|
||||
* `key` → The setting key to retrieve (e.g., `API_TOKEN`, `TIMEZONE`)
|
||||
|
||||
**Authorization:**
|
||||
Requires a valid API token in the `Authorization` header.
|
||||
|
||||
---
|
||||
|
||||
#### `curl` Example (Success)
|
||||
|
||||
```sh
|
||||
curl 'http://<server_ip>:<GRAPHQL_PORT>/settings/API_TOKEN' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Accept: application/json'
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"value": "my-secret-token"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `curl` Example (Invalid Key)
|
||||
|
||||
```sh
|
||||
curl 'http://<server_ip>:<GRAPHQL_PORT>/settings/DOES_NOT_EXIST' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Accept: application/json'
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"value": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `curl` Example (Unauthorized)
|
||||
|
||||
```sh
|
||||
curl 'http://<server_ip>:<GRAPHQL_PORT>/settings/API_TOKEN' \
|
||||
-H 'Accept: application/json'
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Forbidden"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Notes
|
||||
|
||||
* This endpoint is optimized for **direct retrieval of a single setting**.
|
||||
* For **complex retrieval scenarios** (listing all settings, retrieving schema metadata like `setName`, `setDescription`, `setType`, or checking if a setting is overridden by environment variables), use the **GraphQL Settings Query**:
|
||||
|
||||
```sh
|
||||
curl 'http://<server_ip>:<GRAPHQL_PORT>/graphql' \
|
||||
-X POST \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"query": "query GetSettings { settings { settings { setKey setName setDescription setType setOptions setGroup setValue setEvents setOverriddenByEnv } count } }"
|
||||
}'
|
||||
```
|
||||
|
||||
See the [GraphQL API Endpoint](API_GRAPHQL.md) for more details.
|
||||
125
docs/API_SYNC.md
Executable file
@@ -0,0 +1,125 @@
|
||||
# Sync API Endpoint
|
||||
|
||||
---
|
||||
|
||||
The `/sync` endpoint is used by the **SYNC plugin** to synchronize data between multiple NetAlertX instances (e.g., from a node to a hub). It supports both **GET** and **POST** requests.
|
||||
|
||||
#### 9.1 GET `/sync`
|
||||
|
||||
Fetches data from a node to the hub. The data is returned as a **base64-encoded JSON file**.
|
||||
|
||||
**Example Request:**
|
||||
|
||||
```sh
|
||||
curl 'http://<server>:<GRAPHQL_PORT>/sync' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
**Response Example:**
|
||||
|
||||
```json
|
||||
{
|
||||
"node_name": "NODE-01",
|
||||
"status": 200,
|
||||
"message": "OK",
|
||||
"data_base64": "eyJkZXZpY2VzIjogW3siZGV2TWFjIjogIjAwOjExOjIyOjMzOjQ0OjU1IiwiZGV2TmFtZSI6ICJEZXZpY2UgMSJ9XSwgImNvdW50Ijog1fQ==",
|
||||
"timestamp": "2025-08-24T10:15:00+10:00"
|
||||
}
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
|
||||
* `data_base64` contains the full JSON data encoded in Base64.
|
||||
* `node_name` corresponds to the `SYNC_node_name` setting on the node.
|
||||
* Errors (e.g., missing file) return HTTP 500 with an error message.
|
||||
|
||||
---
|
||||
|
||||
#### 9.2 POST `/sync`
|
||||
|
||||
The **POST** endpoint is used by nodes to **send data to the hub**. The hub expects the data as **form-encoded fields** (application/x-www-form-urlencoded or multipart/form-data). The hub then stores the data in the plugin log folder for processing.
|
||||
|
||||
#### Required Fields
|
||||
|
||||
| Field | Type | Description |
|
||||
| ----------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `data` | string | The payload from the plugin or devices. Typically **plain text**, **JSON**, or **encrypted Base64** data. In your Python script, `encrypt_data()` is applied before sending. |
|
||||
| `node_name` | string | The name of the node sending the data. Matches the node’s `SYNC_node_name` setting. Used to generate the filename on the hub. |
|
||||
| `plugin` | string | The name of the plugin sending the data. Determines the filename prefix (`last_result.<plugin>...`). |
|
||||
| `file_path` | string (optional) | Path of the local file being sent. Used only for logging/debugging purposes on the hub; **not required for processing**. |
|
||||
|
||||
---
|
||||
|
||||
### How the Hub Processes the POST Data
|
||||
|
||||
1. **Receives the data** and validates the API token.
|
||||
2. **Stores the raw payload** in:
|
||||
|
||||
```
|
||||
INSTALL_PATH/log/plugins/last_result.<plugin>.encoded.<node_name>.<sequence>.log
|
||||
```
|
||||
|
||||
* `<plugin>` → plugin name from the POST request.
|
||||
* `<node_name>` → node name from the POST request.
|
||||
* `<sequence>` → incremented number for each submission.
|
||||
|
||||
3. **Decodes / decrypts the data** if necessary (Base64 or encrypted) before processing.
|
||||
4. **Processes JSON payloads** (e.g., device info) to:
|
||||
|
||||
* Avoid duplicates by tracking `devMac`.
|
||||
* Add metadata like `devSyncHubNode`.
|
||||
* Insert new devices into the database.
|
||||
5. **Renames files** to indicate they have been processed:
|
||||
|
||||
```
|
||||
processed_last_result.<plugin>.<node_name>.<sequence>.log
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Example POST Payload
|
||||
|
||||
If a node is sending device data:
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://<hub>:<PORT>/sync' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-F 'data={"data":[{"devMac":"00:11:22:33:44:55","devName":"Device 1","devVendor":"Vendor A","devLastIP":"192.168.1.10"}]}' \
|
||||
-F 'node_name=NODE-01' \
|
||||
-F 'plugin=SYNC'
|
||||
```
|
||||
|
||||
* The `data` field contains JSON with a **`data` array**, where each element is a **device object** or **plugin data object**.
|
||||
* The `plugin` and `node_name` fields allow the hub to **organize and store the file correctly**.
|
||||
* The data is only processed if the relevant plugins are enabled and run on the target server.
|
||||
|
||||
---
|
||||
|
||||
### Key Notes
|
||||
|
||||
* **Always use the same `plugin` and `node_name` values** for consistent storage.
|
||||
* **Encrypted data**: The Python script uses `encrypt_data()` before sending, and the hub decodes it before processing.
|
||||
* **Sequence numbers**: Every submission generates a new sequence, preventing overwriting previous data.
|
||||
* **Form-encoded**: The hub expects `multipart/form-data` (cURL `-F`) or `application/x-www-form-urlencoded`.
|
||||
|
||||
**Storage Details:**
|
||||
|
||||
* Data is stored under `INSTALL_PATH/log/plugins` with filenames following the pattern:
|
||||
|
||||
```
|
||||
last_result.<plugin>.encoded.<node_name>.<sequence>.log
|
||||
```
|
||||
|
||||
* Both encoded and decoded files are tracked, and new submissions increment the sequence number.
|
||||
* If storing fails, the API returns HTTP 500 with an error message.
|
||||
* The data is only processed if the relevant plugins are enabled and run on the target server.
|
||||
|
||||
---
|
||||
|
||||
#### 9.3 Notes and Best Practices
|
||||
|
||||
* **Authorization Required** – Both GET and POST require a valid API token.
|
||||
* **Data Integrity** – Ensure that `node_name` and `plugin` are consistent to avoid overwriting files.
|
||||
* **Monitoring** – Notifications are generated whenever data is sent or received (`write_notification`), which can be used for alerting or auditing.
|
||||
* **Use Case** – Typically used in multi-node deployments to consolidate device and event data on a central hub.
|
||||
|
||||
12
docs/API_TESTS.md
Executable file
@@ -0,0 +1,12 @@
|
||||
### Unit Tests
|
||||
|
||||
>[!WARNING]
|
||||
> Please note these test modify data in the database.
|
||||
|
||||
1. See the `/test` directory for available test cases. These are not exhaustive but cover the main API endpoints.
|
||||
2. To run a test case, SSH into the container:
|
||||
`sudo docker exec -it netalertx /bin/bash`
|
||||
3. Inside the container, install pytest (if not already installed):
|
||||
`pip install pytest`
|
||||
4. Run a specific test case:
|
||||
`pytest /app/test/TESTFILE.py`
|
||||
34
docs/DEBUG_PHP.md
Executable file
@@ -0,0 +1,34 @@
|
||||
# Debugging backend PHP issues
|
||||
|
||||
## Logs in UI
|
||||
|
||||

|
||||
|
||||
You can view recent backend PHP errors directly in the **Maintenance > Logs** section of the UI. This provides quick access to logs without needing terminal access.
|
||||
|
||||
## Accessing logs directly
|
||||
|
||||
Sometimes, the UI might not be accessible. In that case, you can access the logs directly inside the container.
|
||||
|
||||
### Step-by-step:
|
||||
|
||||
1. **Open a shell into the container:**
|
||||
|
||||
```bash
|
||||
docker exec -it netalertx /bin/sh
|
||||
```
|
||||
|
||||
2. **Check the NGINX error log:**
|
||||
|
||||
```bash
|
||||
cat /var/log/nginx/error.log
|
||||
```
|
||||
|
||||
3. **Check the PHP application error log:**
|
||||
|
||||
```bash
|
||||
cat /app/log/app.php_errors.log
|
||||
```
|
||||
|
||||
These logs will help identify syntax issues, fatal errors, or startup problems when the UI fails to load properly.
|
||||
|
||||
@@ -53,6 +53,9 @@ The function `guess_device_attributes(...)` runs a series of matching functions
|
||||
4. IP pattern → `match_ip()`
|
||||
5. Final fallback → defaults defined in the `NEWDEV_devIcon` and `NEWDEV_devType` settings.
|
||||
|
||||
> [!NOTE]
|
||||
> The app will try guessing the device type or icon if `devType` or `devIcon` are `""` or `"null"`.
|
||||
|
||||
### Use of default values
|
||||
|
||||
The guessing process runs for every device **as long as the current type or icon still matches the default values**. Even if earlier heuristics return a match, the system continues evaluating additional clues — like name or IP — to try and replace placeholders.
|
||||
|
||||
63
docs/DEV_DEVCONTAINER.md
Executable file
@@ -0,0 +1,63 @@
|
||||
# Devcontainer for NetAlertX Guide
|
||||
|
||||
This devcontainer is designed to mirror the production container environment as closely as possible, while providing a rich set of tools for development.
|
||||
|
||||
## How to Get Started
|
||||
|
||||
1. **Prerequisites:**
|
||||
* A working **Docker installation** that can be managed by your user. This can be [Docker Desktop](https://www.docker.com/products/docker-desktop/) or Docker Engine installed via other methods (like the official [get-docker script](https://get.docker.com)).
|
||||
* [Visual Studio Code](https://code.visualstudio.com/) installed.
|
||||
* The [VS Code Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) installed.
|
||||
|
||||
2. **Launch the Devcontainer:**
|
||||
* Clone this repository.
|
||||
* Open the repository folder in VS Code.
|
||||
* A notification will pop up in the bottom-right corner asking to **"Reopen in Container"**. Click it.
|
||||
* VS Code will now build the Docker image and connect your editor to the container. Your terminal, debugger, and all tools will now be running inside this isolated environment.
|
||||
|
||||
## Key Workflows & Features
|
||||
|
||||
Once you're inside the container, everything is set up for you.
|
||||
|
||||
### 1. Services (Frontend & Backend)
|
||||
|
||||

|
||||
|
||||
The container's startup script (`.devcontainer/scripts/setup.sh`) automatically starts the Nginx/PHP frontend and the Python backend. You can restart them at any time using the built-in tasks.
|
||||
|
||||
### 2. Integrated Debugging (Just Press F5!)
|
||||
|
||||

|
||||
|
||||
Debugging for both the Python backend and PHP frontend is pre-configured and ready to go.
|
||||
|
||||
* **Python Backend (debugpy):** The backend automatically starts with a debugger attached on port `5678`. Simply open a Python file (e.g., `server/__main__.py`), set a breakpoint, and press **F5** (or select "Python Backend Debug: Attach") to connect the debugger.
|
||||
* **PHP Frontend (Xdebug):** Xdebug listens on port `9003`. In VS Code, start listening for Xdebug connections and use a browser extension (like "Xdebug helper") to start a debugging session for the web UI.
|
||||
|
||||
### 3. Common Tasks (F1 -> Run Task)
|
||||
|
||||

|
||||
|
||||
We've created several VS Code Tasks to simplify common operations. Access them by pressing `F1` and typing "Tasks: Run Task".
|
||||
|
||||
* `Generate Dockerfile`: **This is important.** The actual `.devcontainer/Dockerfile` is auto-generated. If you need to change the container environment, edit `.devcontainer/resources/devcontainer-Dockerfile` and then run this task.
|
||||
* `Re-Run Startup Script`: Manually re-runs the `.devcontainer/scripts/setup.sh` script to re-link files and restart services.
|
||||
* `Start Backend (Python)` / `Start Frontend (nginx and PHP-FPM)`: Manually restart the services if needed.
|
||||
|
||||
### 4. Running Tests
|
||||
|
||||

|
||||
|
||||
The environment includes `pytest`. You can run tests directly from the VS Code Test Explorer UI or by running `pytest -q` in the integrated terminal. The necessary `PYTHONPATH` is already configured so that tests can correctly import the server modules.
|
||||
|
||||
## How to Maintain This Devcontainer
|
||||
|
||||
The setup is designed to be easy to manage. Here are the core principles:
|
||||
|
||||
* **Don't Edit `Dockerfile` Directly:** The main `.devcontainer/Dockerfile` is a combination of the project's root `Dockerfile` and a special dev-only stage. To add new tools or dependencies, **edit `.devcontainer/resources/devcontainer-Dockerfile`** and then run the `Generate Dockerfile` task.
|
||||
* **Build-Time vs. Run-Time Setup:**
|
||||
* For changes that can be baked into the image (like installing a new package with `apk add`), add them to the resource Dockerfile.
|
||||
* For changes that must happen when the container *starts* (like creating symlinks, setting permissions, or starting services), use `.devcontainer/scripts/setup.sh`.
|
||||
* **Project Conventions:** The `.github/copilot-instructions.md` file is an excellent resource to help AI and humans understand the project's architecture, conventions, and how to use existing helper functions instead of hardcoding values.
|
||||
|
||||
This setup provides a powerful and consistent foundation for all current and future contributors to NetAlertX.
|
||||
@@ -1,32 +1,45 @@
|
||||
# Development environment set up
|
||||
# Development Environment Setup
|
||||
|
||||
>[!NOTE]
|
||||
> Replace `/development` with the path where your code files will be stored. The default container name is `netalertx` so there might be a conflict with your running containers.
|
||||
I truly appreciate all contributions! To help keep this project maintainable, this guide provides an overview of project priorities, key design considerations, and overall philosophy. It also includes instructions for setting up your environment so you can start contributing right away.
|
||||
|
||||
## Development Guidelines
|
||||
|
||||
Before starting development, please scan the below development guidelines.
|
||||
Before starting development, please review the following guidelines.
|
||||
|
||||
### Priority Order (Highest to Lowest)
|
||||
|
||||
1. 🔼 Fixing core bugs that lack workarounds.
|
||||
2. 🔵 Adding core functionality that unlocks other features (e.g., plugins).
|
||||
3. 🔵 Refactoring to enable faster development.
|
||||
4. 🔽 UI improvements (PRs welcome).
|
||||
1. 🔼 Fixing core bugs that lack workarounds
|
||||
2. 🔵 Adding core functionality that unlocks other features (e.g., plugins)
|
||||
3. 🔵 Refactoring to enable faster development
|
||||
4. 🔽 UI improvements (PRs welcome, but low priority)
|
||||
|
||||
### Design Philosophy
|
||||
|
||||
Focus on core functionality and integrate with existing tools rather than reinventing the wheel.
|
||||
Examples:
|
||||
The application architecture is designed for extensibility and maintainability. It relies heavily on configuration manifests via plugins and settings to dynamically build the UI and populate the application with data from various sources.
|
||||
|
||||
- Using **Apprise** for notifications instead of implementing multiple separate gateways.
|
||||
- Implementing **regex-based validation** instead of one-off validation for each setting.
|
||||
For details, see:
|
||||
- [Plugins Development](PLUGINS_DEV.md) (includes video)
|
||||
- [Settings System](SETTINGS_SYSTEM.md)
|
||||
|
||||
Focus on **core functionality** and integrate with existing tools rather than reinventing the wheel.
|
||||
|
||||
Examples:
|
||||
- Using **Apprise** for notifications instead of implementing multiple separate gateways
|
||||
- Implementing **regex-based validation** instead of one-off validation for each setting
|
||||
|
||||
> [!NOTE]
|
||||
> UI changes have lower priority, however, PRs are welcome, but **keep them small & focused**.
|
||||
> UI changes have lower priority. PRs are welcome, but please keep them **small and focused**.
|
||||
|
||||
## Development Environment Set Up
|
||||
|
||||
>[!TIP]
|
||||
> There is also a ready to use [devcontainer](DEV_DEVCONTAINER.md) available.
|
||||
|
||||
The following steps will guide you to set up your environment for local development and to run a custom docker build on your system. For most changes the container doesn't need to be rebuild which speeds up the development significantly.
|
||||
|
||||
>[!NOTE]
|
||||
> Replace `/development` with the path where your code files will be stored. The default container name is `netalertx` so there might be a conflict with your running containers.
|
||||
|
||||
### 1. Download the code:
|
||||
|
||||
- `mkdir /development`
|
||||
@@ -84,14 +97,14 @@ Most code changes can be tested without rebuilding the container. When working o
|
||||
|
||||
1. You can usually restart the backend via _Maintenance > Logs > Restart_ server
|
||||
|
||||

|
||||

|
||||
|
||||
2. If above doesn't work, SSH into the container and kill & restart the main script loop
|
||||
|
||||
- `sudo docker exec -it netalertx /bin/bash`
|
||||
- `pkill -f "python /app/server" && python /app/server & `
|
||||
|
||||
3. If none of the above work, restart the docker caontainer.
|
||||
3. If none of the above work, restart the docker container.
|
||||
|
||||
- This is usually the last resort as sometimes the Docker engine becomes unresponsive and the whole engine needs to be restarted.
|
||||
|
||||
@@ -119,3 +132,6 @@ Most code changes can be tested without rebuilding the container. When working o
|
||||
- Updating a Device
|
||||
- Plugin functionality.
|
||||
- Error log inspection.
|
||||
|
||||
> [!NOTE]
|
||||
> Always run all available tests as per the [Testing documentation](API_TESTS.md).
|
||||
|
||||
@@ -137,3 +137,67 @@ networks:
|
||||
|
||||
|
||||
```
|
||||
|
||||
### Example 5: same as 3 but with a top-level root directory; also works in Portainer as-is
|
||||
|
||||
`docker-compose.yml`
|
||||
|
||||
```yaml
|
||||
services:
|
||||
netalertx:
|
||||
container_name: netalertx
|
||||
# use the below line if you want to test the latest dev image instead of the stable release
|
||||
# image: "ghcr.io/jokob-sk/netalertx-dev:latest"
|
||||
image: "ghcr.io/jokob-sk/netalertx:latest"
|
||||
|
||||
network_mode: "host"
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ${APP_FOLDER}/netalertx/config:/app/config
|
||||
- ${APP_FOLDER}/netalertx/db:/app/db
|
||||
# (optional) useful for debugging if you have issues setting up the container
|
||||
- ${APP_FOLDER}/netalertx/log:/app/log
|
||||
# (API: OPTION 1) default -> use for performance
|
||||
- type: tmpfs
|
||||
target: /app/api
|
||||
# (API: OPTION 2) use when debugging issues
|
||||
# - ${APP_FOLDER}/netalertx/api:/app/api
|
||||
environment:
|
||||
|
||||
- TZ=${TZ}
|
||||
- PORT=${PORT}
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
- LISTEN_ADDR=${LISTEN_ADDR}
|
||||
```
|
||||
|
||||
`.env` file
|
||||
|
||||
```yaml
|
||||
APP_FOLDER=/path/to/local/NetAlertX/location
|
||||
|
||||
#ENVIRONMENT VARIABLES
|
||||
|
||||
PUID=200
|
||||
PGID=300
|
||||
|
||||
TZ=America/New_York
|
||||
LISTEN_ADDR=0.0.0.0
|
||||
PORT=20211
|
||||
#GLOBAL PATH VARIABLE
|
||||
|
||||
# you may want to create a dedicated user and group to run the container with
|
||||
# sudo groupadd -g 300 nax-g
|
||||
# sudo useradd -u 200 -g 300 nax-u
|
||||
# mkdir -p $APP_FOLDER/{db,config,log}
|
||||
# chown -R 200:300 $APP_FOLDER
|
||||
# chmod -R 775 $APP_FOLDER
|
||||
|
||||
# DEVELOPMENT VARIABLES
|
||||
# you can create multiple env files called .env.dev1, .env.dev2 etc and use them by running:
|
||||
# docker compose --env-file .env.dev1 up -d
|
||||
# you can then clone multiple dev copies of NetAlertX just make sure to change the APP_FOLDER and PORT variables in each .env.devX file
|
||||
|
||||
```
|
||||
|
||||
To run the container execute: `sudo docker-compose --env-file /path/to/.env up`
|
||||
|
||||
97
docs/DOCKER_PORTAINER.md
Executable file
@@ -0,0 +1,97 @@
|
||||
# Deploying NetAlertX in Portainer (via Stacks)
|
||||
|
||||
This guide shows you how to set up **NetAlertX** using Portainer’s **Stacks** feature.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## 1. Prepare Your Host
|
||||
|
||||
Before deploying, make sure you have a folder on your Docker host for NetAlertX data. Replace `APP_FOLDER` with your preferred location, for example `/opt` here:
|
||||
|
||||
```bash
|
||||
mkdir -p /opt/netalertx/config
|
||||
mkdir -p /opt/netalertx/db
|
||||
mkdir -p /opt/netalertx/log
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Open Portainer Stacks
|
||||
|
||||
1. Log in to your **Portainer UI**.
|
||||
2. Navigate to **Stacks** → **Add stack**.
|
||||
3. Give your stack a name (e.g., `netalertx`).
|
||||
|
||||
---
|
||||
|
||||
## 3. Paste the Stack Configuration
|
||||
|
||||
Copy and paste the following YAML into the **Web editor**:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
netalertx:
|
||||
container_name: netalertx
|
||||
|
||||
# Use this line for stable release
|
||||
image: "ghcr.io/jokob-sk/netalertx:latest"
|
||||
|
||||
# Or, use this for the latest development build
|
||||
# image: "ghcr.io/jokob-sk/netalertx-dev:latest"
|
||||
|
||||
network_mode: "host"
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
- ${APP_FOLDER}/netalertx/config:/app/config
|
||||
- ${APP_FOLDER}/netalertx/db:/app/db
|
||||
# Optional: logs (useful for debugging setup issues, comment out for performance)
|
||||
- ${APP_FOLDER}/netalertx/log:/app/log
|
||||
|
||||
# API storage options:
|
||||
# (Option 1) tmpfs (default, best performance)
|
||||
- type: tmpfs
|
||||
target: /app/api
|
||||
|
||||
# (Option 2) bind mount (useful for debugging)
|
||||
# - ${APP_FOLDER}/netalertx/api:/app/api
|
||||
|
||||
environment:
|
||||
- TZ=${TZ}
|
||||
- PORT=${PORT}
|
||||
- APP_CONF_OVERRIDE=${APP_CONF_OVERRIDE}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Configure Environment Variables
|
||||
|
||||
In the **Environment variables** section of Portainer, add the following:
|
||||
|
||||
* `APP_FOLDER=/opt` (or wherever you created the directories in step 1)
|
||||
* `TZ=Europe/Berlin` (replace with your timezone)
|
||||
* `PORT=22022` (or another port if needed)
|
||||
* `APP_CONF_OVERRIDE={"GRAPHQL_PORT":"22023"}` (optional advanced settings)
|
||||
|
||||
---
|
||||
|
||||
## 5. Deploy the Stack
|
||||
|
||||
1. Scroll down and click **Deploy the stack**.
|
||||
2. Portainer will pull the image and start NetAlertX.
|
||||
3. Once running, access the app at:
|
||||
|
||||
```
|
||||
http://<your-docker-host-ip>:22022
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Verify and Troubleshoot
|
||||
|
||||
* Check logs via Portainer → **Containers** → `netalertx` → **Logs**.
|
||||
* Logs are stored under `${APP_FOLDER}/netalertx/log` if you enabled that volume.
|
||||
|
||||
Once the application is running, configure it by reading the [initial setup](INITIAL_SETUP.md) guide, or [troubleshoot common issues](COMMON_ISSUES.md).
|
||||
@@ -7,49 +7,69 @@ To download and install NetAlertX on the hardware/server directly use the `curl`
|
||||
>
|
||||
> 🙏 Looking for maintainers for this installation method 🙂 Current community volunteers:
|
||||
> - [slammingprogramming](https://github.com/slammingprogramming)
|
||||
> - [ingoratsdorf](https://github.com/ingoratsdorf)
|
||||
>
|
||||
> There is no guarantee that the install script or any other script will gracefully handle other installed software.
|
||||
> Data loss is a possibility, **it is recommended to install NetAlertX using the supplied Docker image**.
|
||||
|
||||
A warning to the installation method below: Piping to bash is [controversial](https://pi-hole.net/2016/07/25/curling-and-piping-to-bash) and may
|
||||
> [!WARNING]
|
||||
> A warning to the installation method below: Piping to bash is [controversial](https://pi-hole.net/2016/07/25/curling-and-piping-to-bash) and may
|
||||
be dangerous, as you cannot see the code that's about to be executed on your system.
|
||||
|
||||
Alternatively you can download the installation script `install/install.debian.sh` from the repository and check the code yourself (beware other scripts are
|
||||
downloaded too - only from this repo).
|
||||
If you trust this repo, you can download the install script via one of the methods (curl/wget) below and it will fo its best to install NetAlertX on your system.
|
||||
|
||||
Alternatively you can download the installation script from the repository and check the code yourself.
|
||||
|
||||
NetAlertX will be installed in `/app` and run on port number `20211`.
|
||||
|
||||
Some facts about what and where something will be changed/installed by the HW install setup (may not contain everything!):
|
||||
|
||||
- dependencies will be installed from the respective system repos
|
||||
- required python modules will be installed
|
||||
- `/app` directory will be deleted and newly created
|
||||
- `/app` will contain the whole repository (downloaded by `install/install.debian.sh`)
|
||||
- `/app` will contain the whole repository (downloaded by the install script)
|
||||
- The default NGINX site `/etc/nginx/sites-enabled/default` will be disabled (sym-link deleted or backed up to `sites-available`)
|
||||
- `/var/www/html/netalertx` directory will be deleted and newly created
|
||||
- `/etc/nginx/conf.d/netalertx.conf` will be sym-linked to `/app/install/netalertx.debian.conf`
|
||||
- `/etc/nginx/conf.d/netalertx.conf` will be sym-linked to the appropriate installer location (depending on your system installer script)
|
||||
- Some files (IEEE device vendors info, ...) will be created in the directory where the installation script is executed
|
||||
|
||||
## Limitations
|
||||
|
||||
- No system service is provided. NetAlertX must be started using `/app/install/start.debian.sh`.
|
||||
- No system service is provided. NetAlertX must be started using `/app/install/<system>/start.<system>.sh`.
|
||||
- No checks for other running software is done.
|
||||
- Only tested to work on Debian Bookworm (Debian 12).
|
||||
- Only tested to work on the system listed in the install directory.
|
||||
- **EXPERIMENTAL** and not recommended way to install NetAlertX.
|
||||
|
||||
## 📥 Installation via CURL
|
||||
|
||||
> [!TIP]
|
||||
> If the below fails try grabbing and installing one of the [previous releases](https://github.com/jokob-sk/NetAlertX/releases) and run the installation from the zip package.
|
||||
|
||||
```bash
|
||||
curl -o install.debian.sh https://raw.githubusercontent.com/jokob-sk/NetAlertX/main/install/install.debian.sh && sudo chmod +x install.debian.sh && sudo ./install.debian.sh
|
||||
```
|
||||
|
||||
## 📥 Installation via WGET
|
||||
|
||||
```bash
|
||||
wget https://raw.githubusercontent.com/jokob-sk/NetAlertX/main/install/install.debian.sh -O install.debian.sh && sudo chmod +x install.debian.sh && sudo ./install.debian.sh
|
||||
```
|
||||
|
||||
These commands will download the `install.debian.sh` script from the GitHub repository, make it executable with `chmod`, and then run it using `./install.debian.sh`.
|
||||
These commands will download the `install.debian12.sh` script from the GitHub repository, make it executable with `chmod`, and then run it using `./install.debian12.sh`.
|
||||
|
||||
Make sure you have the necessary permissions to execute the script.
|
||||
|
||||
|
||||
## 📥 Debian 12 (Bookworm)
|
||||
|
||||
### Installation via curl
|
||||
```bash
|
||||
curl -o install.debian12.sh https://raw.githubusercontent.com/jokob-sk/NetAlertX/main/install/debian12/install.debian12.sh && sudo chmod +x install.debian12.sh && sudo ./install.debian12.sh
|
||||
```
|
||||
|
||||
### Installation via wget
|
||||
|
||||
```bash
|
||||
wget https://raw.githubusercontent.com/jokob-sk/NetAlertX/main/install/debian12/install.debian12.sh -O install.debian12.sh && sudo chmod +x install.debian12.sh && sudo ./install.debian12.sh
|
||||
```
|
||||
|
||||
## 📥 Ubuntu 24 (Noble Numbat)
|
||||
|
||||
### Installation via curl
|
||||
```bash
|
||||
curl -o install.ubuntu24.sh https://raw.githubusercontent.com/jokob-sk/NetAlertX/main/install/ubuntu24/install.ubuntu24.sh && sudo chmod +x install.ubuntu24.sh && sudo ./install.ubuntu24.sh
|
||||
```
|
||||
|
||||
### Installation via wget
|
||||
|
||||
```bash
|
||||
wget https://raw.githubusercontent.com/jokob-sk/NetAlertX/main/install/ubuntu24/install.ubuntu24.sh -O install.ubuntu24.sh && sudo chmod +x install.ubuntu24.sh && sudo ./install.ubuntu24.sh
|
||||
```
|
||||
|
||||
@@ -91,6 +91,12 @@ You can confirm that `raspberrypi` now acts as a network device in two places:
|
||||
|
||||
> Hovering over devices in the tree reveals connection details and tooltips for quick inspection.
|
||||
|
||||
> [!NOTE]
|
||||
> Selecting certain relationship types hides the device in the default device views.
|
||||
> You can change this behavior by adjusting the `UI_hide_rel_types` setting, which by default is set to `["nic","virtual"]`.
|
||||
> This means devices with `devParentRelType` set to `nic` or `virtual` will not be shown.
|
||||
> All devices, regardless of relationship type, are always accessible in the **All devices** view.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Summary
|
||||
|
||||
@@ -79,10 +79,11 @@ Device-detecting plugins insert values into the `CurrentScan` database table. T
|
||||
| `SETPWD` | [set_password](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/set_password/) | ⚙ | Set password | | Yes |
|
||||
| `SMTP` | [_publisher_email](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/_publisher_email/) | ▶️ | Email notifications | | |
|
||||
| `SNMPDSC` | [snmp_discovery](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/snmp_discovery/) | 🔍/📥 | SNMP device import & sync | | |
|
||||
| `SYNC` | [sync](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/sync/) | 🔍/⚙/📥 | Sync & import from NetAlertX instances | 🖧 🔄 | Yes |
|
||||
| `SYNC` | [sync](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/sync/) | 🔍/⚙/📥 | Sync & import from NetAlertX instances | 🖧 🔄 | Yes |
|
||||
| `TELEGRAM` | [_publisher_telegram](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/_publisher_telegram/) | ▶️ | Telegram notifications | | |
|
||||
| `UI` | [ui_settings](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/ui_settings/) | ♻ | UI specific settings | | Yes |
|
||||
| `UNFIMP` | [unifi_import](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/unifi_import/) | 🔍/📥/🆎 | UniFi device import & sync | 🖧 | |
|
||||
| `UNIFIAPI` | [unifi_api_import](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/unifi_api_import/) | 🔍/📥/🆎 | UniFi device import (SM API, multi-site) | | |
|
||||
| `VNDRPDT` | [vendor_update](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/vendor_update/) | ⚙ | Vendor database update | | |
|
||||
| `WEBHOOK` | [_publisher_webhook](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/_publisher_webhook/) | ▶️ | Webhook notifications | | |
|
||||
| `WEBMON` | [website_monitor](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/website_monitor/) | ♻ | Website down monitoring | | |
|
||||
|
||||
@@ -10,6 +10,9 @@ NetAlertX comes with a plugin system to feed events from third-party scripts int
|
||||
|
||||
> (Currently, update/overwriting of existing objects is only supported for devices via the `CurrentScan` table.)
|
||||
|
||||
> [!NOTE]
|
||||
> For a high-level overview of how the `config.json` is used and it's lifecycle check the [config.json Lifecycle in NetAlertX Guide](PLUGINS_DEV_CONFIG.md).
|
||||
|
||||
### 🎥 Watch the video:
|
||||
|
||||
> [!TIP]
|
||||
|
||||
146
docs/PLUGINS_DEV_CONFIG.md
Executable file
@@ -0,0 +1,146 @@
|
||||
## config.json Lifecycle in NetAlertX
|
||||
|
||||
This document describes on a high level how `config.json` is read, processed, and used by the NetAlertX core and plugins. It also outlines the plugin output contract and the main plugin types.
|
||||
|
||||
> [!NOTE]
|
||||
> For a deep-dive on the specific configuration options and sections of the `config.json` plugin manifest, consult the [Plugins Development Guide](PLUGINS_DEV.md).
|
||||
|
||||
---
|
||||
|
||||
### 1. Loading
|
||||
|
||||
* On startup, the app core loads `config.json` for each plugin.
|
||||
* The `config.json` represents a plugin manifest, that contains metadata and runtime settings.
|
||||
|
||||
---
|
||||
|
||||
### 2. Validation
|
||||
|
||||
* The core checks that each required settings key (such as `RUN`) for a plugin exists.
|
||||
* Invalid or missing values may be replaced with defaults, or the plugin may be disabled.
|
||||
|
||||
---
|
||||
|
||||
### 3. Preparation
|
||||
|
||||
* The plugin’s settings (paths, commands, parameters) are prepared.
|
||||
* Database mappings (`mapped_to_table`, `database_column_definitions`) for data ingestion into the core app are parsed.
|
||||
|
||||
---
|
||||
|
||||
### 4. Execution
|
||||
|
||||
* Plugins can be run at different core app execution points, such as on schedule, once on start, after a notification, etc.
|
||||
* At runtime, the scheduler triggers plugins according to their `interval`.
|
||||
* The plugin executes its command or script.
|
||||
|
||||
---
|
||||
|
||||
### 5. Parsing
|
||||
|
||||
* Plugin output is expected in **pipe (`|`)-delimited format**.
|
||||
* The core parses lines into fields, matching the **plugin interface contract**.
|
||||
|
||||
---
|
||||
|
||||
### 6. Mapping
|
||||
|
||||
* Each parsed field is moved into the `Plugins_` database tables and can be mapped into a configured database table.
|
||||
* Controlled by `database_column_definitions` and `mapped_to_table`.
|
||||
* Example: `Object_PrimaryID → Devices.MAC`.
|
||||
|
||||
---
|
||||
|
||||
### 6a. Plugin Output Contract
|
||||
|
||||
Each plugin must output results in the **plugin interface contract format**, pipe (`|`)-delimited values, in the column order described under [Plugin Interface Contract](PLUGINS_DEV.md)
|
||||
|
||||
#### IDs
|
||||
|
||||
* `Object_PrimaryID` and `Object_SecondaryID` identify the record (e.g. `MAC|IP`).
|
||||
|
||||
#### **Watched values (`Watched_Value1–4`)**
|
||||
|
||||
* Used by the core to detect changes between runs.
|
||||
* Changes here can trigger **notifications**.
|
||||
|
||||
#### **Extra value (`Extra`)**
|
||||
|
||||
* Optional, extra field.
|
||||
* Stored in the database but **not used for alerts**.
|
||||
|
||||
#### **Helper values (`Helper_Value1–3`)**
|
||||
|
||||
* Added for cases where more than IDs + watched + extra are needed.
|
||||
* Can be made visible in the UI.
|
||||
* Stored in the database but **not used for alerts**.
|
||||
|
||||
#### **Mapping matters**
|
||||
|
||||
* While the plugin output is free-form, the `database_column_definitions` and `mapped_to_table` settings in `config.json` determine the **target columns and data types** in NetAlertX.
|
||||
|
||||
---
|
||||
|
||||
### 7. Persistence
|
||||
|
||||
* Data is upserted into the database.
|
||||
* Conflicts are resolved using `Object_PrimaryID` + `Object_SecondaryID`.
|
||||
|
||||
---
|
||||
|
||||
### 8. Plugin Types and Expected Outputs
|
||||
|
||||
Beyond the `data_source` setting, plugins fall into functional categories. Each has its own input requirements and output expectations:
|
||||
|
||||
#### **Device discovery plugins**
|
||||
|
||||
* **Inputs:** `N/A`, subnet, or API for discovery service, or similar.
|
||||
* **Outputs:** At minimum `MAC` and `IP` that results in a new or updated device records in the `Devices` table.
|
||||
* **Mapping:** Must be mapped to the `CurrentScan` table via `database_column_definitions` and `data_filters`.
|
||||
* **Examples:** ARP-scan, NMAP device discovery (e.g., `ARPSCAN`, `NMAPDEV`).
|
||||
|
||||
#### **Device-data enrichment plugins**
|
||||
|
||||
* **Inputs:** Device identifier (usually `MAC`, `IP`).
|
||||
* **Outputs:** Additional data for that device (e.g. open ports).
|
||||
* **Mapping:** Controlled via `database_column_definitions` and `data_filters`.
|
||||
* **Examples:** Ports, MQTT messages (e.g., `NMAP`, `MQTT`)
|
||||
|
||||
#### **Name resolver plugins**
|
||||
|
||||
* **Inputs:** Device identifiers (MAC, IP, or hostname).
|
||||
* **Outputs:** Updated `devName` and `devFQDN` fields.
|
||||
* **Mapping:** Not expected.
|
||||
* **Note:** Currently requires **core app modification** to add new plugins, not fully driven by the plugins’ `config.json`.
|
||||
* **Examples:** Avahiscan (e.g., `NBTSCAN`, `NSLOOKUP`).
|
||||
|
||||
#### **Generic plugins**
|
||||
|
||||
* **Inputs:** Whatever the script or query provides.
|
||||
* **Outputs:** Data shown only in **Integrations → Plugins**, not tied to devices.
|
||||
* **Mapping:** Not expected.
|
||||
* **Examples:** External monitoring data (e.g., `INTRSPD`)
|
||||
|
||||
#### **Configuration-only plugins**
|
||||
|
||||
* **Inputs/Outputs:** None at runtime.
|
||||
* **Mapping:** Not expected.
|
||||
* **Examples:** Used to provide additional settings or execute scripts (e.g., `MAINT`, `CSVBCKP`).
|
||||
|
||||
---
|
||||
|
||||
### 9. Post-Processing
|
||||
|
||||
* Notifications are generated if watched values change.
|
||||
* UI is updated with new or updated records.
|
||||
* All values that are configured to be shown in teh UI appear in the Plugins section.
|
||||
|
||||
---
|
||||
|
||||
### 10. Summary
|
||||
|
||||
The lifecycle of `config.json` entries is:
|
||||
|
||||
**Load → Validate → Prepare → Execute → Parse → Map → Persist → Post-process**
|
||||
|
||||
Plugins must follow the **output contract**, and their category (discovery, specific, resolver, generic, config-only) defines what inputs they require and what outputs are expected.
|
||||
@@ -2,9 +2,13 @@
|
||||
|
||||
> Submitted by amazing [cvc90](https://github.com/cvc90) 🙏
|
||||
|
||||
|
||||
> [!NOTE]
|
||||
> There are 2 NGINX files for NetAlertX, one for the bare-metal Debian install (`netalertx.debian.conf`), and one for the docker container (`netalertx.template.conf`). Both can be found in the [install](https://github.com/jokob-sk/NetAlertX/tree/main/install) folder. Map, or use, the one appropriate for your setup.
|
||||
> There are various NGINX config files for NetAlertX, some for the bare-metal install, currently Debian 12 and Ubuntu 24 (`netalertx.conf`), and one for the docker container (`netalertx.template.conf`).
|
||||
>
|
||||
> The first one you can find in the respective bare metal installer folder `/app/install/\<system\>/netalertx.conf`.
|
||||
> The docker one can be found in the [install](https://github.com/jokob-sk/NetAlertX/tree/main/install) folder. Map, or use, the one appropriate for your setup.
|
||||
|
||||
<br/>
|
||||
|
||||
## NGINX HTTP Configuration (Direct Path)
|
||||
|
||||
@@ -26,9 +30,11 @@
|
||||
|
||||
`nginx -s reload` or `systemctl restart nginx`
|
||||
|
||||
4. Once NGINX restarts, you should be able to access the proxy website at http://netalertx/
|
||||
4. Check your config with `nginx -t`. If there are any issues, it will tell you.
|
||||
|
||||
<br>
|
||||
5. Once NGINX restarts, you should be able to access the proxy website at http://netalertx/
|
||||
|
||||
<br/>
|
||||
|
||||
## NGINX HTTP Configuration (Sub Path)
|
||||
|
||||
@@ -50,13 +56,15 @@
|
||||
}
|
||||
```
|
||||
|
||||
3. Activate the new website by running the following command:
|
||||
3. Check your config with `nginx -t`. If there are any issues, it will tell you.
|
||||
|
||||
4. Activate the new website by running the following command:
|
||||
|
||||
`nginx -s reload` or `systemctl restart nginx`
|
||||
|
||||
4. Once NGINX restarts, you should be able to access the proxy website at http://netalertx/netalertx/
|
||||
5. Once NGINX restarts, you should be able to access the proxy website at http://netalertx/netalertx/
|
||||
|
||||
<br>
|
||||
<br/>
|
||||
|
||||
## NGINX HTTP Configuration (Sub Path) with module ngx_http_sub_module
|
||||
|
||||
@@ -86,13 +94,15 @@
|
||||
}
|
||||
```
|
||||
|
||||
3. Activate the new website by running the following command:
|
||||
3. Check your config with `nginx -t`. If there are any issues, it will tell you.
|
||||
|
||||
4. Activate the new website by running the following command:
|
||||
|
||||
`nginx -s reload` or `systemctl restart nginx`
|
||||
|
||||
4. Once NGINX restarts, you should be able to access the proxy website at http://netalertx/netalertx/
|
||||
5. Once NGINX restarts, you should be able to access the proxy website at http://netalertx/netalertx/
|
||||
|
||||
<br>
|
||||
<br/>
|
||||
|
||||
**NGINX HTTPS Configuration (Direct Path)**
|
||||
|
||||
@@ -113,13 +123,15 @@
|
||||
}
|
||||
```
|
||||
|
||||
3. Activate the new website by running the following command:
|
||||
3. Check your config with `nginx -t`. If there are any issues, it will tell you.
|
||||
|
||||
4. Activate the new website by running the following command:
|
||||
|
||||
`nginx -s reload` or `systemctl restart nginx`
|
||||
|
||||
4. Once NGINX restarts, you should be able to access the proxy website at https://netalertx/
|
||||
5. Once NGINX restarts, you should be able to access the proxy website at https://netalertx/
|
||||
|
||||
<br>
|
||||
<br/>
|
||||
|
||||
**NGINX HTTPS Configuration (Sub Path)**
|
||||
|
||||
@@ -143,13 +155,15 @@
|
||||
}
|
||||
```
|
||||
|
||||
3. Activate the new website by running the following command:
|
||||
3. Check your config with `nginx -t`. If there are any issues, it will tell you.
|
||||
|
||||
4. Activate the new website by running the following command:
|
||||
|
||||
`nginx -s reload` or `systemctl restart nginx`
|
||||
|
||||
4. Once NGINX restarts, you should be able to access the proxy website at https://netalertx/netalertx/
|
||||
5. Once NGINX restarts, you should be able to access the proxy website at https://netalertx/netalertx/
|
||||
|
||||
<br>
|
||||
<br/>
|
||||
|
||||
## NGINX HTTPS Configuration (Sub Path) with module ngx_http_sub_module
|
||||
|
||||
@@ -181,13 +195,15 @@
|
||||
}
|
||||
```
|
||||
|
||||
3. Activate the new website by running the following command:
|
||||
3. Check your config with `nginx -t`. If there are any issues, it will tell you.
|
||||
|
||||
4. Activate the new website by running the following command:
|
||||
|
||||
`nginx -s reload` or `systemctl restart nginx`
|
||||
|
||||
4. Once NGINX restarts, you should be able to access the proxy website at https://netalertx/netalertx/
|
||||
5. Once NGINX restarts, you should be able to access the proxy website at https://netalertx/netalertx/
|
||||
|
||||
<br>
|
||||
<br/>
|
||||
|
||||
## Apache HTTP Configuration (Direct Path)
|
||||
|
||||
@@ -204,13 +220,15 @@
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
3. Activate the new website by running the following command:
|
||||
3. Check your config with `httpd -t` (or `apache2ctl -t` on Debian/Ubuntu). If there are any issues, it will tell you.
|
||||
|
||||
4. Activate the new website by running the following command:
|
||||
|
||||
`a2ensite netalertx` or `service apache2 reload`
|
||||
|
||||
4. Once Apache restarts, you should be able to access the proxy website at http://netalertx/
|
||||
5. Once Apache restarts, you should be able to access the proxy website at http://netalertx/
|
||||
|
||||
<br>
|
||||
<br/>
|
||||
|
||||
## Apache HTTP Configuration (Sub Path)
|
||||
|
||||
@@ -229,13 +247,15 @@
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
3. Activate the new website by running the following command:
|
||||
3. Check your config with `httpd -t` (or `apache2ctl -t` on Debian/Ubuntu). If there are any issues, it will tell you.
|
||||
|
||||
4. Activate the new website by running the following command:
|
||||
|
||||
`a2ensite netalertx` or `service apache2 reload`
|
||||
|
||||
4. Once Apache restarts, you should be able to access the proxy website at http://netalertx/
|
||||
5. Once Apache restarts, you should be able to access the proxy website at http://netalertx/
|
||||
|
||||
<br>
|
||||
<br/>
|
||||
|
||||
## Apache HTTPS Configuration (Direct Path)
|
||||
|
||||
@@ -255,13 +275,15 @@
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
3. Activate the new website by running the following command:
|
||||
3. Check your config with `httpd -t` (or `apache2ctl -t` on Debian/Ubuntu). If there are any issues, it will tell you.
|
||||
|
||||
4. Activate the new website by running the following command:
|
||||
|
||||
`a2ensite netalertx` or `service apache2 reload`
|
||||
|
||||
4. Once Apache restarts, you should be able to access the proxy website at https://netalertx/
|
||||
5. Once Apache restarts, you should be able to access the proxy website at https://netalertx/
|
||||
|
||||
<br>
|
||||
<br/>
|
||||
|
||||
## Apache HTTPS Configuration (Sub Path)
|
||||
|
||||
@@ -283,11 +305,15 @@
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
3. Activate the new website by running the following command:
|
||||
3. Check your config with `httpd -t` (or `apache2ctl -t` on Debian/Ubuntu). If there are any issues, it will tell you.
|
||||
|
||||
4. Activate the new website by running the following command:
|
||||
|
||||
`a2ensite netalertx` or `service apache2 reload`
|
||||
|
||||
4. Once Apache restarts, you should be able to access the proxy website at https://netalertx/netalertx/
|
||||
5. Once Apache restarts, you should be able to access the proxy website at https://netalertx/netalertx/
|
||||
|
||||
<br/>
|
||||
|
||||
## Reverse proxy example by using LinuxServer's SWAG container.
|
||||
|
||||
@@ -349,12 +375,13 @@ location ^~ /netalertx/ {
|
||||
}
|
||||
```
|
||||
|
||||
<br/>
|
||||
|
||||
## Traefik
|
||||
|
||||
> Submitted by [Isegrimm](https://github.com/Isegrimm) 🙏 (based on this [discussion](https://github.com/jokob-sk/NetAlertX/discussions/449#discussioncomment-7281442))
|
||||
|
||||
Asuming the user already has a working Traefik setup, this is what's needed to make NetAlertX work at a URL like www.domain.com/netalertx/.
|
||||
Assuming the user already has a working Traefik setup, this is what's needed to make NetAlertX work at a URL like www.domain.com/netalertx/.
|
||||
|
||||
Note: Everything in these configs assumes '**www.domain.com**' as your domainname and '**section31**' as an arbitrary name for your certificate setup. You will have to substitute these with your own.
|
||||
|
||||
@@ -480,4 +507,3 @@ docker run -d --rm --network=host \
|
||||
ghcr.io/jokob-sk/netalertx:latest
|
||||
|
||||
```
|
||||
|
||||
|
||||
BIN
docs/img/DEBUG/maintenance_debug_php.png
Executable file
|
After Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
BIN
docs/img/DEV/devcontainer_1.png
Executable file
|
After Width: | Height: | Size: 11 KiB |
BIN
docs/img/DEV/devcontainer_2.png
Executable file
|
After Width: | Height: | Size: 17 KiB |
BIN
docs/img/DEV/devcontainer_3.png
Executable file
|
After Width: | Height: | Size: 14 KiB |
BIN
docs/img/DEV/devcontainer_4.png
Executable file
|
After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 7.0 KiB |
BIN
docs/img/DOCKER/DOCKER_PORTAINER.png
Executable file
|
After Width: | Height: | Size: 180 KiB |
1
front/.gitignore
vendored
Executable file
@@ -0,0 +1 @@
|
||||
buildtimestamp.txt
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
require 'php/templates/header.php';
|
||||
require 'php/templates/notification.php';
|
||||
require 'php/templates/modals.php';
|
||||
?>
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
--color-yellow: #f39c12;
|
||||
--color-red: #dd4b39;
|
||||
--color-gray: #8c8c8c;
|
||||
--color-black: #000;
|
||||
}
|
||||
|
||||
.input-group .checkbox
|
||||
@@ -604,6 +605,20 @@ body
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.btn-outline:hover
|
||||
{
|
||||
border: 1px solid var(--color-black);
|
||||
background: transparent;
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
.btn-outline
|
||||
{
|
||||
border: 1px solid var(--color-gray);
|
||||
background: transparent;
|
||||
color: var(--color-gray);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Customized Full Calendar
|
||||
----------------------------------------------------------------------------- */
|
||||
@@ -1726,6 +1741,16 @@ input[readonly] {
|
||||
width: 92%;
|
||||
}
|
||||
|
||||
#modal-ok
|
||||
{
|
||||
z-index: 1051; /*highest priority*/
|
||||
}
|
||||
|
||||
#modal-form-plc
|
||||
{
|
||||
display: grid;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------- */
|
||||
/* NETWORK page */
|
||||
/* ----------------------------------------------------------------- */
|
||||
|
||||
@@ -419,6 +419,12 @@ td.highlight {
|
||||
border: 1px solid #353c42;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
border: 1px solid var(--color-black);
|
||||
background: transparent;
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
/* Used in debug log page */
|
||||
.log-red {
|
||||
color: #ff4038;
|
||||
|
||||
@@ -422,6 +422,12 @@
|
||||
border: 1px solid #353c42;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
border: 1px solid var(--color-black);
|
||||
background: transparent;
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
/* Used in debug log page */
|
||||
.log-red {
|
||||
color: #ff4038;
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
<!-- Content header--------------------------------------------------------- -->
|
||||
<section class="content-header">
|
||||
<?php require 'php/templates/notification.php'; ?>
|
||||
<?php require 'php/templates/modals.php'; ?>
|
||||
|
||||
<h1 id="pageTitle">
|
||||
 <small>Quering device info...</small>
|
||||
@@ -497,7 +497,7 @@ function updateDevicePageName(mac) {
|
||||
let owner = getDevDataByMac(mac, "devOwner");
|
||||
|
||||
// If data is missing, re-cache and retry once
|
||||
if (mac != 'new' && (name === "Unknown" || owner === "Unknown")) {
|
||||
if (mac != 'new' && (name === null|| owner === null)) {
|
||||
console.warn("Device not found in cache, retrying after re-cache:", mac);
|
||||
showSpinner();
|
||||
cacheDevices().then(() => {
|
||||
|
||||
@@ -53,14 +53,6 @@
|
||||
|
||||
var deviceData = JSON.parse(data);
|
||||
|
||||
// // Deactivate next previous buttons
|
||||
// if (readAllData) {
|
||||
// $('#btnPrevious').attr ('disabled','');
|
||||
// $('#btnPrevious').addClass ('text-gray50');
|
||||
// $('#btnNext').attr ('disabled','');
|
||||
// $('#btnNext').addClass ('text-gray50');
|
||||
// }
|
||||
|
||||
// some race condition, need to implement delay
|
||||
setTimeout(() => {
|
||||
$.get('php/server/query_json.php', {
|
||||
@@ -256,25 +248,27 @@
|
||||
|
||||
// update readonly fields
|
||||
handleReadOnly(settingsData, disabledFields);
|
||||
};
|
||||
};
|
||||
|
||||
// console.log(relevantSettings)
|
||||
// console.log(relevantSettings)
|
||||
|
||||
generateSimpleForm(relevantSettings);
|
||||
generateSimpleForm(relevantSettings);
|
||||
|
||||
toggleNetworkConfiguration(mac == 'Internet')
|
||||
toggleNetworkConfiguration(mac == 'Internet')
|
||||
|
||||
initSelect2();
|
||||
initHoverNodeInfo();
|
||||
initSelect2();
|
||||
initHoverNodeInfo();
|
||||
|
||||
hideSpinner();
|
||||
hideSpinner();
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
}, 100);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}, 100);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------
|
||||
@@ -290,18 +284,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------
|
||||
// Show the description of a setting
|
||||
function showDescriptionPopup(e) {
|
||||
|
||||
console.log($(e).attr("my-set-key"));
|
||||
|
||||
showModalOK("Info", getString($(e).attr("my-set-key") + '_description'))
|
||||
}
|
||||
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Save device data to DB
|
||||
function setDeviceData(direction = '', refreshCallback = '') {
|
||||
|
||||
@@ -198,7 +198,7 @@
|
||||
<?= lang("DevDetail_Nmap_buttonSkipDiscovery_text") ?>
|
||||
</li>
|
||||
<li>
|
||||
<a onclick="setCache('activeMaintenanceTab', 'tab_Logging_id')" href="/maintenance.php#tab_Logging">
|
||||
<a onclick="setCache('activeMaintenanceTab', 'tab_Logging_id')" href="maintenance.php#tab_Logging">
|
||||
<?= lang("DevDetail_Nmap_resultsLink") ?>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -683,11 +683,19 @@ function initializeDatatable (status) {
|
||||
|
||||
return JSON.stringify(query); // Send the JSON request
|
||||
},
|
||||
"dataSrc": function (json) {
|
||||
// Set the total number of records for pagination
|
||||
json.recordsTotal = json.devices.count || 0;
|
||||
json.recordsFiltered = json.devices.count || 0;
|
||||
"dataSrc": function (res) {
|
||||
|
||||
console.log("Raw response:", res);
|
||||
const json = res["data"];
|
||||
|
||||
// Set the total number of records for pagination at the *root level* so DataTables sees them
|
||||
res.recordsTotal = json.devices.count || 0;
|
||||
res.recordsFiltered = json.devices.count || 0;
|
||||
|
||||
// console.log("recordsTotal:", res.recordsTotal, "recordsFiltered:", res.recordsFiltered);
|
||||
// console.log("tableRows:", tableRows);
|
||||
|
||||
// Return only the array of rows for the table
|
||||
return json.devices.devices.map(device => {
|
||||
// Convert each device record into the required DataTable row format
|
||||
// Order has to be the same as in the UI_device_columns setting options
|
||||
@@ -701,10 +709,10 @@ function initializeDatatable (status) {
|
||||
device.devFirstConnection || "",
|
||||
device.devLastConnection || "",
|
||||
device.devLastIP || "",
|
||||
device.devIsRandomMac || "", // Custom logic for randomized MAC
|
||||
device.devIsRandomMac || "",
|
||||
device.devStatus || "",
|
||||
device.devMac || "", // hidden
|
||||
device.devIpLong || "", // IP orderable
|
||||
device.devMac || "",
|
||||
device.devIpLong || "",
|
||||
device.rowid || "",
|
||||
device.devParentMAC || "",
|
||||
device.devParentChildrenCount || 0,
|
||||
@@ -729,7 +737,6 @@ function initializeDatatable (status) {
|
||||
for (let index = 0; index < tableColumnOrder.length; index++) {
|
||||
newRow.push(originalRow[tableColumnOrder[index]]);
|
||||
}
|
||||
|
||||
return newRow;
|
||||
});
|
||||
}
|
||||
@@ -1005,8 +1012,6 @@ function initializeDatatable (status) {
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"display": "standalone",
|
||||
"icons": [
|
||||
{
|
||||
"src": "",
|
||||
"src": "/img/NetAlertX_logo.png",
|
||||
"sizes": "180x180",
|
||||
"type": "image/png"
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ var timerRefreshData = ''
|
||||
|
||||
var emptyArr = ['undefined', "", undefined, null, 'null'];
|
||||
var UI_LANG = "English";
|
||||
const allLanguages = ["en_us", "es_es", "de_de", "fr_fr", "it_it", "ru_ru", "nb_no", "pl_pl", "pt_br", "tr_tr", "zh_cn", "cs_cz", "ar_ar", "ca_ca", "uk_ua"]; // needs to be same as in lang.php
|
||||
const allLanguages = ["en_us", "es_es", "de_de", "fr_fr", "it_it", "ru_ru", "nb_no", "pl_pl", "pt_br", "pt_pt", "tr_tr", "zh_cn", "cs_cz", "ar_ar", "ca_ca", "uk_ua"]; // needs to be same as in lang.php
|
||||
var settingsJSON = {}
|
||||
|
||||
|
||||
@@ -328,6 +328,9 @@ function getLangCode() {
|
||||
case 'Portuguese (pt_br)':
|
||||
lang_code = 'pt_br';
|
||||
break;
|
||||
case 'Portuguese (pt_pt)':
|
||||
lang_code = 'pt_pt';
|
||||
break;
|
||||
case 'Turkish (tr_tr)':
|
||||
lang_code = 'tr_tr';
|
||||
break;
|
||||
@@ -364,75 +367,70 @@ function getLangCode() {
|
||||
// -----------------------------------------------------------------------------
|
||||
function localizeTimestamp(input) {
|
||||
let tz = getSetting("TIMEZONE") || 'Europe/Berlin';
|
||||
|
||||
// Convert to string and trim
|
||||
input = String(input || '').trim();
|
||||
|
||||
// Normalize multiple spaces and remove commas
|
||||
const cleaned = input.replace(',', ' ').replace(/\s+/g, ' ');
|
||||
|
||||
// DD/MM/YYYY format check
|
||||
const dateTimeParts = cleaned.split(' ');
|
||||
if (dateTimeParts.length >= 2 && dateTimeParts[0].includes('/')) {
|
||||
const [day, month, year] = dateTimeParts[0].split('/');
|
||||
const timePart = dateTimeParts[1];
|
||||
|
||||
if (day && month && year && timePart) {
|
||||
const isoString = `${year}-${month}-${day}T${timePart.length === 5 ? timePart + ':00' : timePart}`;
|
||||
const date = new Date(isoString);
|
||||
if (!isFinite(date)) return 'b-';
|
||||
|
||||
return new Intl.DateTimeFormat('default', {
|
||||
timeZone: tz,
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false
|
||||
}).format(date);
|
||||
}
|
||||
}
|
||||
|
||||
// ISO style YYYY-MM-DD HH:mm(:ss)?
|
||||
const match = cleaned.match(/^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2})(:\d{2})?$/);
|
||||
if (match) {
|
||||
let iso = `${match[1]}T${match[2]}${match[3] || ':00'}`;
|
||||
|
||||
const date = new Date(iso);
|
||||
if (!isFinite(date)) return 'c-';
|
||||
|
||||
// ✅ 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', {
|
||||
timeZone: tz,
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
year: 'numeric', month: '2-digit', day: '2-digit',
|
||||
hour: '2-digit', minute: '2-digit', second: '2-digit',
|
||||
hour12: false
|
||||
}).format(new Date(ms));
|
||||
}
|
||||
|
||||
// ✅ 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;
|
||||
const iso = `${y}-${m.padStart(2,'0')}-${d.padStart(2,'0')}T${t.length===5?t+":00":t}${tzPart}`;
|
||||
return formatSafe(iso, tz);
|
||||
}
|
||||
|
||||
// ✅ 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;
|
||||
const iso = `${y}-${m.padStart(2,'0')}-${d.padStart(2,'0')}T${t.length===5?t+":00":t}${tzPart}`;
|
||||
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})?$/);
|
||||
if (match) {
|
||||
let [ , ymd, time, offset = "" ] = match;
|
||||
// normalize to YYYY-MM-DD
|
||||
let [y, m, d] = ymd.split('-').map(x => x.padStart(2,'0'));
|
||||
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"
|
||||
match = input.match(/^\d{1,2} [A-Za-z]{3,} \d{4}/);
|
||||
if (match) {
|
||||
return formatSafe(input, tz);
|
||||
}
|
||||
|
||||
// ✅ 6. Fallback (whatever Date() can parse)
|
||||
return formatSafe(input, tz);
|
||||
|
||||
function formatSafe(str, tz) {
|
||||
const date = new Date(str);
|
||||
if (!isFinite(date)) {
|
||||
console.error(`ERROR: Couldn't parse date: '${str}' with TIMEZONE ${tz}`);
|
||||
return 'Failed conversion - Check browser console';
|
||||
}
|
||||
return new Intl.DateTimeFormat('default', {
|
||||
timeZone: tz,
|
||||
year: 'numeric', month: '2-digit', day: '2-digit',
|
||||
hour: '2-digit', minute: '2-digit', second: '2-digit',
|
||||
hour12: false
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
// Fallback: try to parse any other string input
|
||||
const date = new Date(input);
|
||||
if (!isFinite(date)) return 'Failed conversion: ' + input;
|
||||
|
||||
return new Intl.DateTimeFormat('default', {
|
||||
timeZone: tz,
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ----------------------------------------------------
|
||||
/**
|
||||
* Replaces double quotes within single-quoted strings, then converts all single quotes to double quotes,
|
||||
@@ -609,7 +607,7 @@ function createDeviceLink(input)
|
||||
{
|
||||
if(checkMacOrInternet(input))
|
||||
{
|
||||
return `<span class="anonymizeMac"><a href="/deviceDetails.php?mac=${input}" target="_blank">${getNameByMacAddress(input)}</a><span>`
|
||||
return `<span class="anonymizeMac"><a href="/deviceDetails.php?mac=${input}" target="_blank">${getDevDataByMac(input, "devName")}</a><span>`
|
||||
}
|
||||
|
||||
return input;
|
||||
@@ -813,7 +811,6 @@ function forceLoadUrl(relativeUrl) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
function navigateToDeviceWithIp (ip) {
|
||||
|
||||
@@ -836,11 +833,6 @@ function navigateToDeviceWithIp (ip) {
|
||||
});
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
function getNameByMacAddress(macAddress) {
|
||||
return getDevDataByMac(macAddress, "devName")
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Check if MAC or Internet
|
||||
function checkMacOrInternet(inputStr) {
|
||||
@@ -1013,7 +1005,7 @@ function getDevDataByMac(macAddress, dbColumn) {
|
||||
|
||||
if (!devicesCache || devicesCache == "") {
|
||||
console.error(`Session variable "${sessionDataKey}" not found.`);
|
||||
return "Unknown";
|
||||
return null;
|
||||
}
|
||||
|
||||
const devices = JSON.parse(devicesCache);
|
||||
@@ -1032,7 +1024,8 @@ function getDevDataByMac(macAddress, dbColumn) {
|
||||
}
|
||||
}
|
||||
|
||||
return "Unknown"; // Return a default value if MAC address is not found
|
||||
console.error("⚠ Device with MAC not found:" + macAddress)
|
||||
return null; // Return a default value if MAC address is not found
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@@ -161,6 +161,139 @@ function showModalFieldInput(
|
||||
$(`#${prefix}`).modal("show");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
function showModalPopupForm(
|
||||
title,
|
||||
message,
|
||||
btnCancel = getString("Gen_Cancel"),
|
||||
btnOK = getString("Gen_Okay"),
|
||||
curValue = null,
|
||||
popupFormJson = null,
|
||||
parentSettingKey = null,
|
||||
triggeredBy = null
|
||||
) {
|
||||
// set captions
|
||||
prefix = "modal-form";
|
||||
console.log(popupFormJson);
|
||||
|
||||
$(`#${prefix}-title`).html(title);
|
||||
$(`#${prefix}-message`).html(message);
|
||||
$(`#${prefix}-cancel`).html(btnCancel);
|
||||
$(`#${prefix}-OK`).html(btnOK);
|
||||
|
||||
// if curValue not null
|
||||
|
||||
if (curValue)
|
||||
{
|
||||
initialValues = JSON.parse(atob(curValue));
|
||||
}
|
||||
|
||||
outputHtml = "";
|
||||
|
||||
if (Array.isArray(popupFormJson)) {
|
||||
popupFormJson.forEach((field, index) => {
|
||||
// You'll need to define these or map them from `field`
|
||||
const setKey = field.function || `field_${index}`;
|
||||
const setName = getString(`${parentSettingKey}_popupform_${setKey}_name`);
|
||||
const labelClasses = "col-sm-2"; // example, or from your obj.labelClasses
|
||||
const inputClasses = "col-sm-10"; // example, or from your obj.inputClasses
|
||||
let initialValue = '';
|
||||
if (curValue && Array.isArray(initialValues)) {
|
||||
const match = initialValues.find(
|
||||
v => v[1] == setKey
|
||||
);
|
||||
if (match) {
|
||||
initialValue = match[3];
|
||||
}
|
||||
}
|
||||
|
||||
const fieldOptionsOverride = field.type?.elements[0]?.elementOptions || [];
|
||||
const setValue = initialValue;
|
||||
const setType = JSON.stringify(field.type);
|
||||
const setEvents = field.events || []; // default to empty array if missing
|
||||
const setObj = { setKey, setValue, setType, setEvents };
|
||||
|
||||
// Generate the input field HTML
|
||||
const inputFormHtml = `
|
||||
<div class="form-group col-xs-12">
|
||||
<label id="${setKey}_label" class="${labelClasses}"> ${setName}
|
||||
<i my-set-key="${parentSettingKey}_popupform_${setKey}"
|
||||
title="${getString("Settings_Show_Description")}"
|
||||
class="fa fa-circle-info pointer helpIconSmallTopRight"
|
||||
onclick="showDescriptionPopup(this)">
|
||||
</i>
|
||||
</label>
|
||||
<div class="${inputClasses}">
|
||||
${generateFormHtml(
|
||||
null, // settingsData only required for datatables
|
||||
setObj,
|
||||
null,
|
||||
fieldOptionsOverride,
|
||||
null
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Append to result
|
||||
outputHtml += inputFormHtml;
|
||||
});
|
||||
}
|
||||
|
||||
$(`#modal-form-plc`).html(outputHtml);
|
||||
|
||||
// Bind OK button click event
|
||||
$(`#${prefix}-OK`).off("click").on("click", function() {
|
||||
let settingsArray = [];
|
||||
if (Array.isArray(popupFormJson)) {
|
||||
popupFormJson.forEach(field => {
|
||||
collectSetting(
|
||||
`${parentSettingKey}_popupform`, // prefix
|
||||
field.function, // setCodeName
|
||||
field.type, // setType (object)
|
||||
settingsArray
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Encode settings
|
||||
const jsonData = JSON.stringify(settingsArray);
|
||||
const encodedValue = btoa(jsonData);
|
||||
|
||||
// Get label from the FIRST field (value in 4th column)
|
||||
const label = settingsArray[0][3]
|
||||
|
||||
// Add new option to target select
|
||||
const selectId = parentSettingKey;
|
||||
|
||||
// If triggered by an option, update it; otherwise append new
|
||||
if (triggeredBy && $(triggeredBy).is("option")) {
|
||||
// Update existing option
|
||||
$(triggeredBy)
|
||||
.attr("value", encodedValue)
|
||||
.text(label);
|
||||
} else {
|
||||
const newOption = $("<option class='interactable-option'></option>")
|
||||
.attr("value", encodedValue)
|
||||
.text(label);
|
||||
|
||||
$("#" + selectId).append(newOption);
|
||||
initListInteractionOptions(newOption);
|
||||
}
|
||||
|
||||
console.log("Collected popup form settings:", settingsArray);
|
||||
|
||||
if (typeof modalCallbackFunction === "function") {
|
||||
modalCallbackFunction(settingsArray);
|
||||
}
|
||||
|
||||
$(`#${prefix}`).modal("hide");
|
||||
});
|
||||
|
||||
// Show modal
|
||||
$(`#${prefix}`).modal("show");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
function modalDefaultOK() {
|
||||
// Hide modal
|
||||
|
||||
@@ -67,6 +67,15 @@ function getPluginConfig(pluginsData, prefix) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// ----------------------------------------
|
||||
// Show the description of a setting
|
||||
function showDescriptionPopup(e) {
|
||||
|
||||
console.log($(e).attr("my-set-key"));
|
||||
|
||||
showModalOK("Info", getString($(e).attr("my-set-key") + '_description'))
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Generate plugin HTML card based on prefixes in an array
|
||||
function pluginCards(prefixesOfEnabledPlugins, includeSettings) {
|
||||
@@ -237,13 +246,6 @@ function settingsCollectedCorrectly(settingsArray, settingsJSON_DB) {
|
||||
// Manipulating Editable List options
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Add row to datatable
|
||||
function addDataTableRow(el)
|
||||
{
|
||||
alert("a")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Clone datatable row
|
||||
function cloneDataTableRow(el){
|
||||
@@ -299,6 +301,33 @@ function removeDataTableRow(el) {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Add item via pop up form dialog
|
||||
function addViaPopupForm(element) {
|
||||
console.log(element);
|
||||
|
||||
const toId = $(element).attr("my-input-to");
|
||||
const curValue = $(`#${toId}`).val();
|
||||
const parsed = JSON.parse(atob($(`#${toId}`).data("elementoptionsbase64")));
|
||||
const popupFormJson = parsed.find(obj => "popupForm" in obj)?.popupForm ?? null;
|
||||
|
||||
console.log(`toId | curValue: ${toId} | ${curValue}`);
|
||||
|
||||
showModalPopupForm(
|
||||
`<i class="fa-solid fa-square-plus"></i> ${getString("Gen_Add")}`, // title
|
||||
"", // message
|
||||
getString("Gen_Cancel"), // btnCancel
|
||||
getString("Gen_Add"), // btnOK
|
||||
null, // curValue
|
||||
popupFormJson, // popupform
|
||||
toId, // parentSettingKey
|
||||
element // triggeredBy
|
||||
);
|
||||
|
||||
// flag something changes to prevent navigating from page
|
||||
settingsChanged();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Add item to list
|
||||
function addList(element, clearInput = true) {
|
||||
@@ -427,18 +456,41 @@ function initListInteractionOptions(element) {
|
||||
// Perform action based on click count
|
||||
if (clickCounter === 1) {
|
||||
// Single-click action
|
||||
showModalFieldInput(
|
||||
`<i class="fa fa-pen-to-square"></i> ${getString(
|
||||
"Gen_Update_Value"
|
||||
)}`,
|
||||
getString("settings_update_item_warning"),
|
||||
getString("Gen_Cancel"),
|
||||
getString("Gen_Update"),
|
||||
$option.html(),
|
||||
function () {
|
||||
updateOptionItem($option, $(`#modal-field-input-field`).val());
|
||||
}
|
||||
);
|
||||
|
||||
const $parent = $option.parent();
|
||||
const transformers = $parent.attr("my-transformers");
|
||||
|
||||
if (transformers && transformers === "name|base64") {
|
||||
// Parent has my-transformers="name|base64"
|
||||
const toId = $parent.attr("id");
|
||||
const curValue = $option.val();
|
||||
const parsed = JSON.parse(atob($parent.data("elementoptionsbase64")));
|
||||
const popupFormJson = parsed.find(obj => "popupForm" in obj)?.popupForm ?? null;
|
||||
|
||||
showModalPopupForm(
|
||||
`<i class="fa fa-pen-to-square"></i> ${getString("Gen_Update_Value")}`, // title
|
||||
"", // message
|
||||
getString("Gen_Cancel"), // btnCancel
|
||||
getString("Gen_Update"), // btnOK
|
||||
curValue, // curValue
|
||||
popupFormJson, // popupform
|
||||
toId, // parentSettingKey
|
||||
this // triggeredBy
|
||||
);
|
||||
} else {
|
||||
// Fallback to normal field input
|
||||
showModalFieldInput(
|
||||
`<i class="fa fa-pen-to-square"></i> ${getString("Gen_Update_Value")}`,
|
||||
getString("settings_update_item_warning"),
|
||||
getString("Gen_Cancel"),
|
||||
getString("Gen_Update"),
|
||||
$option.html(),
|
||||
function () {
|
||||
updateOptionItem($option, $(`#modal-field-input-field`).val());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
} else if (clickCounter === 2) {
|
||||
// Double-click action
|
||||
removeOptionItem($option);
|
||||
@@ -622,8 +674,6 @@ function generateOptionsOrSetOptions(
|
||||
// obj.push({ id: item, name: item })
|
||||
options = arrayToObject(createArray(overrideOptions ? overrideOptions : getSettingOptions(setKey)))
|
||||
|
||||
|
||||
|
||||
// Call to render lists
|
||||
renderList(
|
||||
options,
|
||||
@@ -633,8 +683,6 @@ function generateOptionsOrSetOptions(
|
||||
targetField,
|
||||
transformers
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -655,6 +703,13 @@ function applyTransformers(val, transformers) {
|
||||
val = btoa(val);
|
||||
}
|
||||
break;
|
||||
case "name|base64":
|
||||
// // Implement base64 logic
|
||||
// if (!isBase64(val)) {
|
||||
// val = btoa(val);
|
||||
// }
|
||||
val = val; // probably TODO ⚠
|
||||
break;
|
||||
case "getString":
|
||||
// no change
|
||||
val = val;
|
||||
@@ -667,7 +722,7 @@ function applyTransformers(val, transformers) {
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Function to reverse transformers applied to a value
|
||||
// Function to reverse transformers applied to a value - returns the LABEL
|
||||
function reverseTransformers(val, transformers) {
|
||||
transformers.reverse().forEach((transformer) => {
|
||||
switch (transformer) {
|
||||
@@ -681,6 +736,13 @@ function reverseTransformers(val, transformers) {
|
||||
val = atob(val);
|
||||
}
|
||||
break;
|
||||
case "name|base64":
|
||||
// Implement base64 decoding logic
|
||||
if (isBase64(val)) {
|
||||
val = JSON.parse(atob(val))[0][3];
|
||||
}
|
||||
val = val; // probably TODO ⚠
|
||||
break;
|
||||
case "getString":
|
||||
// retrieve string
|
||||
val = getString(val);
|
||||
@@ -721,7 +783,7 @@ const handleElementOptions = (setKey, elementOptions, transformers, val) => {
|
||||
let customId = "";
|
||||
let columns = [];
|
||||
let base64Regex = "";
|
||||
|
||||
let elementOptionsBase64 = btoa(JSON.stringify(elementOptions));
|
||||
|
||||
elementOptions.forEach((option) => {
|
||||
if (option.prefillValue) {
|
||||
@@ -804,7 +866,8 @@ const handleElementOptions = (setKey, elementOptions, transformers, val) => {
|
||||
customParams,
|
||||
customId,
|
||||
columns,
|
||||
base64Regex
|
||||
base64Regex,
|
||||
elementOptionsBase64
|
||||
};
|
||||
};
|
||||
|
||||
@@ -934,13 +997,102 @@ function genListWithInputSet(options, valuesArray, targetField, transformers, pl
|
||||
$("#" + placeholder).replaceWith(listHtml);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Collects a setting based on code name
|
||||
function collectSetting(prefix, setCodeName, setType, settingsArray) {
|
||||
// Parse setType if it's a JSON string
|
||||
const setTypeObject = (typeof setType === "string")
|
||||
? JSON.parse(processQuotes(setType))
|
||||
: setType;
|
||||
|
||||
const dataType = setTypeObject.dataType;
|
||||
|
||||
// Pick element with input value
|
||||
let elements = setTypeObject.elements.filter(el => el.elementHasInputValue === 1);
|
||||
let elementWithInputValue = elements.length === 0
|
||||
? setTypeObject.elements[setTypeObject.elements.length - 1]
|
||||
: elements[0];
|
||||
|
||||
const { elementType, elementOptions = [], transformers = [] } = elementWithInputValue;
|
||||
|
||||
const opts = handleElementOptions('none', elementOptions, transformers, val = "");
|
||||
|
||||
// Map of handlers
|
||||
const handlers = {
|
||||
datatableString: () => {
|
||||
const value = collectTableData(`#${setCodeName}_table`);
|
||||
return btoa(JSON.stringify(value));
|
||||
},
|
||||
simpleValue: () => {
|
||||
let value = $(`#${setCodeName}`).val();
|
||||
return applyTransformers(value, transformers);
|
||||
},
|
||||
checkbox: () => {
|
||||
let value = $(`#${setCodeName}`).is(':checked') ? 1 : 0;
|
||||
if (dataType === "boolean") {
|
||||
value = value === 1 ? "True" : "False";
|
||||
}
|
||||
return applyTransformers(value, transformers);
|
||||
},
|
||||
array: () => {
|
||||
let temps = [];
|
||||
if (opts.isOrdeable) {
|
||||
temps = $(`#${setCodeName}`).val();
|
||||
} else {
|
||||
const sel = $(`#${setCodeName}`).attr("my-editable") === "true" ? "" : ":selected";
|
||||
$(`#${setCodeName} option${sel}`).each(function() {
|
||||
const vl = $(this).val();
|
||||
if (vl !== '') {
|
||||
temps.push(applyTransformers(vl, transformers));
|
||||
}
|
||||
});
|
||||
}
|
||||
return JSON.stringify(temps);
|
||||
},
|
||||
none: () => "",
|
||||
json: () => {
|
||||
let value = $(`#${setCodeName}`).val();
|
||||
value = applyTransformers(value, transformers);
|
||||
return JSON.stringify(value, null, 2);
|
||||
},
|
||||
fallback: () => {
|
||||
console.error(`[collectSetting] Couldn't determine how to handle (${setCodeName}|${dataType}|${opts.inputType})`);
|
||||
let value = $(`#${setCodeName}`).val();
|
||||
return applyTransformers(value, transformers);
|
||||
}
|
||||
};
|
||||
|
||||
// Select handler key
|
||||
let handlerKey;
|
||||
if (dataType === "string" && elementType === "datatable") {
|
||||
handlerKey = "datatableString";
|
||||
} else if (dataType === "string" ||
|
||||
(dataType === "integer" && (opts.inputType === "number" || opts.inputType === "text"))) {
|
||||
handlerKey = "simpleValue";
|
||||
} else if (opts.inputType === "checkbox") {
|
||||
handlerKey = "checkbox";
|
||||
} else if (dataType === "array") {
|
||||
handlerKey = "array";
|
||||
} else if (dataType === "none") {
|
||||
handlerKey = "none";
|
||||
} else if (dataType === "json") {
|
||||
handlerKey = "json";
|
||||
} else {
|
||||
handlerKey = "fallback";
|
||||
}
|
||||
|
||||
const value = handlers[handlerKey]();
|
||||
settingsArray.push([prefix, setCodeName, dataType, value]);
|
||||
|
||||
return settingsArray;
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
// Generate the form control for setting
|
||||
function generateFormHtml(settingsData, set, overrideValue, overrideOptions, originalSetKey) {
|
||||
let inputHtml = '';
|
||||
|
||||
|
||||
isEmpty(overrideValue) ? inVal = set['setValue'] : inVal = overrideValue;
|
||||
const setKey = set['setKey'];
|
||||
const setType = set['setType'];
|
||||
@@ -955,6 +1107,8 @@ function generateFormHtml(settingsData, set, overrideValue, overrideOptions, ori
|
||||
// }
|
||||
|
||||
// Parse the setType JSON string
|
||||
// console.log(processQuotes(setType));
|
||||
|
||||
const setTypeObject = JSON.parse(processQuotes(setType))
|
||||
const dataType = setTypeObject.dataType;
|
||||
const elements = setTypeObject.elements || [];
|
||||
@@ -982,7 +1136,8 @@ function generateFormHtml(settingsData, set, overrideValue, overrideOptions, ori
|
||||
customParams,
|
||||
customId,
|
||||
columns,
|
||||
base64Regex
|
||||
base64Regex,
|
||||
elementOptionsBase64
|
||||
} = handleElementOptions(setKey, elementOptions, transformers, inVal);
|
||||
|
||||
// Override value
|
||||
@@ -1014,6 +1169,7 @@ function generateFormHtml(settingsData, set, overrideValue, overrideOptions, ori
|
||||
my-customparams="${customParams}"
|
||||
my-customid="${customId}"
|
||||
my-originalSetKey="${originalSetKey}"
|
||||
data-elementoptionsbase64="${elementOptionsBase64}"
|
||||
${multi}
|
||||
${readOnly ? "disabled" : ""}>
|
||||
<option value="" id="${setKey + "_temp_"}"></option>
|
||||
@@ -1051,6 +1207,7 @@ function generateFormHtml(settingsData, set, overrideValue, overrideOptions, ori
|
||||
my-originalSetKey="${originalSetKey}"
|
||||
my-input-from="${sourceIds}"
|
||||
my-input-to="${setKey}"
|
||||
data-elementoptionsbase64="${elementOptionsBase64}"
|
||||
onclick="${onClick}">
|
||||
${getString(getStringKey)}
|
||||
</button>`;
|
||||
|
||||
@@ -782,17 +782,24 @@ function initSelect2() {
|
||||
// ------------------------------------------
|
||||
// Render a device link with hover-over functionality
|
||||
function renderDeviceLink(data, container, useName = false) {
|
||||
if (!data.id) return data.text; // default placeholder etc.
|
||||
// If no valid MAC, return placeholder text
|
||||
if (!data.id || !isValidMac(data.id)) {
|
||||
return `<span>${data.text}<span/>`;
|
||||
}
|
||||
|
||||
const device = getDevDataByMac(data.id);
|
||||
if (!device) {
|
||||
return data.text;
|
||||
}
|
||||
|
||||
// Build and return badge parts
|
||||
const badge = getStatusBadgeParts(
|
||||
device.devPresentLastScan,
|
||||
device.devAlertDown,
|
||||
device.devMac
|
||||
);
|
||||
|
||||
// Add badge class and hover-info class to container
|
||||
// badge class and hover-info class to container
|
||||
$(container)
|
||||
.addClass(`${badge.cssClass} hover-node-info`)
|
||||
.attr({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
require 'php/templates/header.php';
|
||||
require 'php/templates/notification.php';
|
||||
require 'php/templates/modals.php';
|
||||
?>
|
||||
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
|
||||
@@ -136,7 +136,8 @@
|
||||
customParams,
|
||||
customId,
|
||||
columns,
|
||||
base64Regex
|
||||
base64Regex,
|
||||
elementOptionsBase64
|
||||
} = handleElementOptions('none', elementOptions, transformers, val = "");
|
||||
|
||||
// render based on element type
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
require 'php/templates/header.php';
|
||||
require 'php/templates/notification.php';
|
||||
require 'php/templates/modals.php';
|
||||
?>
|
||||
|
||||
<script>
|
||||
@@ -197,7 +197,7 @@
|
||||
<div class="col-sm-9">
|
||||
${isRootNode ? '' : `<a class="anonymize" href="#">`}
|
||||
<span my-data-mac="${node.parent_mac}" data-mac="${node.parent_mac}" data-devIsNetworkNodeDynamic="1" onclick="handleNodeClick(this)">
|
||||
${isRootNode ? getString('Network_Root') : getNameByMacAddress(node.parent_mac)}
|
||||
${isRootNode ? getString('Network_Root') : getDevDataByMac(node.parent_mac, "devName")}
|
||||
</span>
|
||||
${isRootNode ? '' : `</a>`}
|
||||
</div>
|
||||
|
||||
@@ -62,7 +62,7 @@ function renderLogArea($params) {
|
||||
'</textarea>
|
||||
</div>
|
||||
<div class="row logs-row">
|
||||
<div class="log-file col-sm-6 col-xs-12">' . htmlspecialchars($fileName) . '
|
||||
<div class="log-file col-sm-6 col-xs-12">' . htmlspecialchars($filePath) . '
|
||||
<div class="logs-size">' . number_format((filesize($filePath) / 1000000), 2, ",", ".") . ' MB'
|
||||
. $downloadButtonHtml .
|
||||
'</div>
|
||||
|
||||
@@ -82,7 +82,7 @@ class CustomDatabaseWrapper {
|
||||
private $maxRetries;
|
||||
private $retryDelay;
|
||||
|
||||
public function __construct($filename, $flags = SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, $maxRetries = 3, $retryDelay = 1000, $encryptionKey = null) {
|
||||
public function __construct($filename, $flags = SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, $maxRetries = 3, $retryDelay = 1000, $encryptionKey = "") {
|
||||
$this->sqlite = new SQLite3($filename, $flags, $encryptionKey);
|
||||
$this->maxRetries = $maxRetries;
|
||||
$this->retryDelay = $retryDelay;
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
# Puche 2022+ jokob jokob@duck.com GNU GPLv3
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
|
||||
// check server/api_server/api_server_start.py for equivalents
|
||||
// equivalent: /dbquery
|
||||
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// External files
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
# Puche 2021 / 2022+ jokob jokob@duck.com GNU GPLv3
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
|
||||
// check server/api_server/api_server_start.py for equivalents
|
||||
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
|
||||
|
||||
// External files
|
||||
require dirname(__FILE__).'/init.php';
|
||||
|
||||
@@ -26,35 +30,31 @@
|
||||
if (isset ($_REQUEST['action']) && !empty ($_REQUEST['action'])) {
|
||||
$action = $_REQUEST['action'];
|
||||
switch ($action) {
|
||||
case 'getServerDeviceData': getServerDeviceData(); break;
|
||||
case 'setDeviceData': setDeviceData(); break;
|
||||
case 'deleteDevice': deleteDevice(); break;
|
||||
case 'deleteAllWithEmptyMACs': deleteAllWithEmptyMACs(); break;
|
||||
// check server/api_server/api_server_start.py for equivalents
|
||||
case 'getServerDeviceData': getServerDeviceData(); break; // equivalent: get_device_data
|
||||
case 'setDeviceData': setDeviceData(); break; // equivalent: set_device_data
|
||||
case 'deleteDevice': deleteDevice(); break; // equivalent: delete_device(mac)
|
||||
case 'deleteAllWithEmptyMACs': deleteAllWithEmptyMACs(); break; // equivalent: delete_all_with_empty_macs
|
||||
|
||||
case 'deleteAllDevices': deleteAllDevices(); break;
|
||||
case 'deleteUnknownDevices': deleteUnknownDevices(); break;
|
||||
case 'deleteEvents': deleteEvents(); break;
|
||||
case 'deleteEvents30': deleteEvents30(); break;
|
||||
case 'deleteActHistory': deleteActHistory(); break;
|
||||
case 'deleteDeviceEvents': deleteDeviceEvents(); break;
|
||||
case 'resetDeviceProps': resetDeviceProps(); break;
|
||||
case 'PiaBackupDBtoArchive': PiaBackupDBtoArchive(); break;
|
||||
case 'PiaRestoreDBfromArchive': PiaRestoreDBfromArchive(); break;
|
||||
case 'PiaPurgeDBBackups': PiaPurgeDBBackups(); break;
|
||||
case 'ExportCSV': ExportCSV(); break;
|
||||
case 'ImportCSV': ImportCSV(); break;
|
||||
case 'deleteAllDevices': deleteAllDevices(); break; // equivalent: delete_devices(macs)
|
||||
case 'deleteUnknownDevices': deleteUnknownDevices(); break; // equivalent: delete_unknown_devices
|
||||
case 'deleteEvents': deleteEvents(); break; // equivalent: delete_events
|
||||
case 'deleteEvents30': deleteEvents30(); break; // equivalent: delete_events_30
|
||||
case 'deleteActHistory': deleteActHistory(); break; // equivalent: delete_online_history
|
||||
case 'deleteDeviceEvents': deleteDeviceEvents(); break; // equivalent: delete_device_events(mac)
|
||||
case 'resetDeviceProps': resetDeviceProps(); break; // equivalent: reset_device_props
|
||||
case 'ExportCSV': ExportCSV(); break; // equivalent: export_devices
|
||||
case 'ImportCSV': ImportCSV(); break; // equivalent: import_csv
|
||||
|
||||
case 'getDevicesTotals': getDevicesTotals(); break;
|
||||
case 'getDevicesListCalendar': getDevicesListCalendar(); break; //todo: slowly deprecate this
|
||||
case 'getDevicesTotals': getDevicesTotals(); break; // equivalent: devices_totals
|
||||
case 'getDevicesListCalendar': getDevicesListCalendar(); break; // equivalent: devices_by_status
|
||||
|
||||
case 'updateNetworkLeaf': updateNetworkLeaf(); break;
|
||||
case 'getIcons': getIcons(); break;
|
||||
case 'getActions': getActions(); break;
|
||||
case 'getDevices': getDevices(); break;
|
||||
case 'copyFromDevice': copyFromDevice(); break;
|
||||
case 'wakeonlan': wakeonlan(); break;
|
||||
case 'updateNetworkLeaf': updateNetworkLeaf(); break; // equivalent: update_device_column(mac, column_name, column_value)
|
||||
|
||||
default: logServerConsole ('Action: '. $action); break;
|
||||
case 'copyFromDevice': copyFromDevice(); break; // equivalent: copy_device(mac_from, mac_to)
|
||||
case 'wakeonlan': wakeonlan(); break; // equivalent: wakeonlan
|
||||
|
||||
default: logServerConsole ('Action: '. $action); break; // equivalent:
|
||||
}
|
||||
}
|
||||
|
||||
@@ -517,92 +517,6 @@ function deleteActHistory() {
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Backup DB to Archiv
|
||||
//------------------------------------------------------------------------------
|
||||
function PiaBackupDBtoArchive() {
|
||||
// prepare fast Backup
|
||||
$dbfilename = 'app.db';
|
||||
$file = '../../../db/'.$dbfilename;
|
||||
$newfile = '../../../db/'.$dbfilename.'.latestbackup';
|
||||
|
||||
// copy files as a fast Backup
|
||||
if (!copy($file, $newfile)) {
|
||||
echo lang('BackDevices_Backup_CopError');
|
||||
} else {
|
||||
// Create archive with actual date
|
||||
$Pia_Archive_Name = 'appdb_'.date("Ymd_His").'.zip';
|
||||
$Pia_Archive_Path = '../../../db/';
|
||||
exec('zip -j '.$Pia_Archive_Path.$Pia_Archive_Name.' ../../../db/'.$dbfilename, $output);
|
||||
// chheck if archive exists
|
||||
if (file_exists($Pia_Archive_Path.$Pia_Archive_Name) && filesize($Pia_Archive_Path.$Pia_Archive_Name) > 0) {
|
||||
echo lang('BackDevices_Backup_okay').': ('.$Pia_Archive_Name.')';
|
||||
unlink($newfile);
|
||||
echo("<meta http-equiv='refresh' content='1'>");
|
||||
} else {
|
||||
echo lang('BackDevices_Backup_Failed').' ('.$dbfilename.'.latestbackup)';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Restore DB from Archiv
|
||||
//------------------------------------------------------------------------------
|
||||
function PiaRestoreDBfromArchive() {
|
||||
// prepare fast Backup
|
||||
$file = '../../../db/'.$dbfilename;
|
||||
$oldfile = '../../../db/'.$dbfilename.'.prerestore';
|
||||
|
||||
// copy files as a fast Backup
|
||||
if (!copy($file, $oldfile)) {
|
||||
echo lang('BackDevices_Restore_CopError');
|
||||
} else {
|
||||
// extract latest archive and overwrite the actual .db
|
||||
$Pia_Archive_Path = '../../../db/';
|
||||
exec('/bin/ls -Art '.$Pia_Archive_Path.'*.zip | /bin/tail -n 1 | /usr/bin/xargs -n1 /bin/unzip -o -d ../../../db/', $output);
|
||||
// check if the .db exists
|
||||
if (file_exists($file)) {
|
||||
echo lang('BackDevices_Restore_okay');
|
||||
unlink($oldfile);
|
||||
echo("<meta http-equiv='refresh' content='1'>");
|
||||
} else {
|
||||
echo lang('BackDevices_Restore_Failed');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Purge Backups
|
||||
//------------------------------------------------------------------------------
|
||||
function PiaPurgeDBBackups() {
|
||||
|
||||
$Pia_Archive_Path = '../../../db';
|
||||
$Pia_Backupfiles = array();
|
||||
$files = array_diff(scandir($Pia_Archive_Path, SCANDIR_SORT_DESCENDING), array('.', '..', $dbfilename, 'netalertxdb-reset.zip'));
|
||||
|
||||
foreach ($files as &$item)
|
||||
{
|
||||
$item = $Pia_Archive_Path.'/'.$item;
|
||||
if (stristr($item, 'setting_') == '') {array_push($Pia_Backupfiles, $item);}
|
||||
}
|
||||
|
||||
if (sizeof($Pia_Backupfiles) > 3)
|
||||
{
|
||||
rsort($Pia_Backupfiles);
|
||||
unset($Pia_Backupfiles[0], $Pia_Backupfiles[1], $Pia_Backupfiles[2]);
|
||||
$Pia_Backupfiles_Purge = array_values($Pia_Backupfiles);
|
||||
for ($i = 0; $i < sizeof($Pia_Backupfiles_Purge); $i++)
|
||||
{
|
||||
unlink($Pia_Backupfiles_Purge[$i]);
|
||||
}
|
||||
}
|
||||
echo lang('BackDevices_DBTools_Purge');
|
||||
echo("<meta http-equiv='refresh' content='1'>");
|
||||
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Export CSV of devices
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -827,75 +741,6 @@ function getDevicesListCalendar() {
|
||||
// Query Device Data
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
function getIcons() {
|
||||
global $db;
|
||||
|
||||
// Device Data
|
||||
$sql = 'select devIcon from Devices group by devIcon';
|
||||
|
||||
$result = $db->query($sql);
|
||||
|
||||
// arrays of rows
|
||||
$tableData = array();
|
||||
while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
|
||||
$icon = handleNull($row['devIcon'], "<i class='fa fa-laptop'></i>");
|
||||
// Push row data
|
||||
$tableData[] = array('id' => $icon,
|
||||
'name' => $icon );
|
||||
}
|
||||
|
||||
// Control no rows
|
||||
if (empty($tableData)) {
|
||||
$tableData = [];
|
||||
}
|
||||
|
||||
// Return json
|
||||
echo (json_encode ($tableData));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
function getActions() {
|
||||
|
||||
$tableData = array(
|
||||
array('id' => 'wake-on-lan',
|
||||
'name' => lang('DevDetail_WOL_Title'))
|
||||
);
|
||||
|
||||
// Return json
|
||||
echo (json_encode ($tableData));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
function getDevices() {
|
||||
|
||||
global $db;
|
||||
|
||||
// Device Data
|
||||
$sql = 'select devMac, devName from Devices';
|
||||
|
||||
$result = $db->query($sql);
|
||||
|
||||
// arrays of rows
|
||||
$tableData = array();
|
||||
|
||||
while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
|
||||
$name = handleNull($row['devName'], "(unknown)");
|
||||
$mac = handleNull($row['devMac'], "(unknown)");
|
||||
// Push row data
|
||||
$tableData[] = array('id' => $mac,
|
||||
'name' => $name );
|
||||
}
|
||||
|
||||
// Control no rows
|
||||
if (empty($tableData)) {
|
||||
$tableData = [];
|
||||
}
|
||||
|
||||
// Return json
|
||||
echo (json_encode ($tableData));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
function updateNetworkLeaf()
|
||||
{
|
||||
|
||||
@@ -8,6 +8,12 @@
|
||||
# Puche 2021 / 2022+ jokob jokob@duck.com GNU GPLv3
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
|
||||
// check server/api_server/api_server_start.py for equivalents
|
||||
// equivalent: /sessions /events
|
||||
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
|
||||
|
||||
// External files
|
||||
require dirname(__FILE__).'/init.php';
|
||||
|
||||
|
||||
@@ -12,6 +12,12 @@
|
||||
# cvc90 2023 https://github.com/cvc90 GNU GPLv3 #
|
||||
###################################################################################
|
||||
|
||||
|
||||
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
|
||||
// check server/api_server/api_server_start.py for equivalents
|
||||
// equivalent: /nettools
|
||||
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
|
||||
|
||||
// Get init.php
|
||||
require dirname(__FILE__).'/../server/init.php';
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
|
||||
// check server/api_server/api_server_start.py for equivalents
|
||||
// equivalent: /nettools
|
||||
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
|
||||
|
||||
require 'util.php';
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
# cvc90 2023 https://github.com/cvc90 GNU GPLv3 #
|
||||
###################################################################################
|
||||
|
||||
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
|
||||
// check server/api_server/api_server_start.py for equivalents
|
||||
// equivalent: /nettools
|
||||
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
|
||||
|
||||
// Get init.php
|
||||
require dirname(__FILE__).'/../server/init.php';
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<?php
|
||||
require dirname(__FILE__).'/../server/init.php';
|
||||
|
||||
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
|
||||
// check server/api_server/api_server_start.py for equivalents
|
||||
// equivalent: /nettools
|
||||
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// check if authenticated
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
# cvc90 2023 https://github.com/cvc90 GNU GPLv3 #
|
||||
###################################################################################
|
||||
|
||||
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
|
||||
// check server/api_server/api_server_start.py for equivalents
|
||||
// equivalent: /nettools
|
||||
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
|
||||
|
||||
// Get init.php
|
||||
require dirname(__FILE__).'/../server/init.php';
|
||||
|
||||
@@ -19,6 +24,8 @@ require dirname(__FILE__).'/../server/init.php';
|
||||
// check if authenticated
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
|
||||
|
||||
// NEW ENDPOINT EQUIVALENT: /nettools/traceroute
|
||||
|
||||
// Get IP
|
||||
$ip = $_GET['ip'];
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
"BackDevices_Backup_Failed": "La còpia de seguretat s'ha executat parcialment. L'arxiu no es pot crear o està buit.",
|
||||
"BackDevices_Backup_okay": "La còpia de seguretat s'ha executat en un nou arxiu",
|
||||
"BackDevices_DBTools_DelDevError_a": "Error esborrant el Dispositiu",
|
||||
"BackDevices_DBTools_DelDevError_b": "Error esborrant els Dispositius",
|
||||
"BackDevices_DBTools_DelDevError_b": "Error esborrant dispositius",
|
||||
"BackDevices_DBTools_DelDev_a": "Dispositiu esborrat",
|
||||
"BackDevices_DBTools_DelDev_b": "Dispositius esborrats",
|
||||
"BackDevices_DBTools_DelEvents": "Esdeveniments esborrats",
|
||||
@@ -66,7 +66,7 @@
|
||||
"DAYS_TO_KEEP_EVENTS_name": "Esborrar esdeveniments més vells de",
|
||||
"DISCOVER_PLUGINS_description": "Desactiva aquesta opció per accelerar la inicialització i l'estalvi de configuració. Quan està desactivat, els connectors no es descobreixen, i no podeu afegir nous connectors a la configuració <code>LOADED_PLUGINS</code>.",
|
||||
"DISCOVER_PLUGINS_name": "Descobreix els plugins",
|
||||
"DevDetail_Children_Title": "",
|
||||
"DevDetail_Children_Title": "Relacions filles",
|
||||
"DevDetail_Copy_Device_Title": "<i class=\"fa fa-copy\"></i> Copiar detalls des del dispositiu",
|
||||
"DevDetail_Copy_Device_Tooltip": "Copiar detalls del dispositius des de la llista desplegable. Tot el d'aquesta pàgina es sobre-escriurà",
|
||||
"DevDetail_CustomProperties_Title": "Propietats personalitzades",
|
||||
@@ -75,7 +75,7 @@
|
||||
"DevDetail_EveandAl_AlertAllEvents": "Alertes",
|
||||
"DevDetail_EveandAl_AlertDown": "Cancel·lar alerta",
|
||||
"DevDetail_EveandAl_Archived": "Arxivat",
|
||||
"DevDetail_EveandAl_NewDevice": "Nou Dispositiu",
|
||||
"DevDetail_EveandAl_NewDevice": "Nou dispositiu",
|
||||
"DevDetail_EveandAl_NewDevice_Tooltip": "Es mostrarà el nou estat del dispositiu i s'inclourà a les llistes quan el filtre New Devices estigui actiu. No afecta les notificacions.",
|
||||
"DevDetail_EveandAl_RandomMAC": "MAC aleatori",
|
||||
"DevDetail_EveandAl_ScanCycle": "Dispositiu d'escaneig",
|
||||
@@ -87,7 +87,7 @@
|
||||
"DevDetail_GoToNetworkNode": "Navegació a la pàgina de la Xarxa del node donat.",
|
||||
"DevDetail_Icon": "Icona",
|
||||
"DevDetail_Icon_Descr": "Si us plau, introdueix dins de la caixa de text els caràcters que veu a la imatge de sota. Això és requerit per evitar enviaments automàtics.",
|
||||
"DevDetail_Loading": "Carregant ...",
|
||||
"DevDetail_Loading": "Carregant …",
|
||||
"DevDetail_MainInfo_Comments": "Comentaris",
|
||||
"DevDetail_MainInfo_Favorite": "Favorit",
|
||||
"DevDetail_MainInfo_Group": "Grup",
|
||||
@@ -103,11 +103,11 @@
|
||||
"DevDetail_MainInfo_Type": "Tipus",
|
||||
"DevDetail_MainInfo_Vendor": "Venedor",
|
||||
"DevDetail_MainInfo_mac": "MAC",
|
||||
"DevDetail_NavToChildNode": "",
|
||||
"DevDetail_NavToChildNode": "Obrir un node fill",
|
||||
"DevDetail_Network_Node_hover": "Seleccioneu el dispositiu de xarxa al qual aquest dispositiu està connectat, per poder omplir l'arbre de xarxa.",
|
||||
"DevDetail_Network_Port_hover": "El port on el dispositiu està connectat al dispositiu de xarxa del pare. Si es deixa buit, sortirà una icona wifi a la representació de la Xarxa.",
|
||||
"DevDetail_Nmap_Scans": "Escaneig manual Nmap",
|
||||
"DevDetail_Nmap_Scans_desc": "Aquí podeu executar les exploracions NMAP manuals. També podeu programar les exploracions NMAP automàtiques a través del connector Serveis i Ports (NMAP). Ves a <a href='/settings.php' target='_blank'>Configuració</a> per saber-ne més",
|
||||
"DevDetail_Nmap_Scans_desc": "Aquí podeu executar les exploracions NMAP manuals. També podeu programar les exploracions NMAP automàtiques a través del connector Serveis i Ports (NMAP). Ves a <a href=\"https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/nmap_scan\" target='_blank'>Docs</a> per saber-ne més",
|
||||
"DevDetail_Nmap_buttonDefault": "Escaneig predeterminat",
|
||||
"DevDetail_Nmap_buttonDefault_text": "Escaneig predeterminat: Nmap escaneja els 1000 ports superiors per a cada protocol d'exploració sol·licitat. El 93% dels ports TCP i el 49% dels ports UDP. (uns 5 segons)",
|
||||
"DevDetail_Nmap_buttonDetail": "Escaneig Detallat",
|
||||
@@ -196,13 +196,13 @@
|
||||
"DevDetail_button_Save": "Guardar",
|
||||
"DeviceEdit_ValidMacIp": "Entra una adreça <b>IP</b> i <b>Mac</b> vàlides.",
|
||||
"Device_MultiEdit": "Multi-edició",
|
||||
"Device_MultiEdit_Backup": "Atenció, entrar valors incorrectes a continuació trencarà la configuració. Si us plau, abans feu còpia de seguretat la vostra base de dades o configuració de Dispositius (<a href=\"php/server/devices.php?action=ExportCSV\">clic per descarregar <i class=\"fa-solid fa-download fa-bounce\"></i></a>). Llegiu com per recuperar Dispositius des d'aquest fitxer al <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/BACKUPS.md#scenario-2-corrupted-database\" target=\"_blank\">documentació de Còpies de seguretat</a>.",
|
||||
"Device_MultiEdit_Backup": "Atenció, entrar valors incorrectes a continuació trencarà la configuració. Si us plau, abans feu còpia de seguretat la vostra base de dades o configuració de Dispositius (<a href=\"php/server/devices.php?action=ExportCSV\">clic per descarregar <i class=\"fa-solid fa-download fa-bounce\"></i></a>). Llegiu com per recuperar Dispositius des d'aquest fitxer al <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/BACKUPS.md#scenario-2-corrupted-database\" target=\"_blank\">documentació de Còpies de seguretat</a>. Per aplicar els canvis, feu click a la <b>Save<i class=\"fa-solid fa-save\"></i></b> icona de cada camp que volgueu actualitzar.",
|
||||
"Device_MultiEdit_Fields": "Editar camps:",
|
||||
"Device_MultiEdit_MassActions": "Accions massives:",
|
||||
"Device_MultiEdit_Tooltip": "Atenció. Si feu clic a això s'aplicarà el valor de l'esquerra a tots els dispositius seleccionats a dalt.",
|
||||
"Device_Searchbox": "Cerca",
|
||||
"Device_Shortcut_AllDevices": "Els meus dispositius",
|
||||
"Device_Shortcut_AllNodes": "",
|
||||
"Device_Shortcut_AllNodes": "Tots els nodes",
|
||||
"Device_Shortcut_Archived": "Arxivat",
|
||||
"Device_Shortcut_Connected": "Connectat",
|
||||
"Device_Shortcut_Devices": "Dispositius",
|
||||
@@ -229,11 +229,11 @@
|
||||
"Device_TableHead_Name": "Nom",
|
||||
"Device_TableHead_NetworkSite": "Network Site",
|
||||
"Device_TableHead_Owner": "Propietari",
|
||||
"Device_TableHead_ParentRelType": "",
|
||||
"Device_TableHead_ParentRelType": "Tipus de relació",
|
||||
"Device_TableHead_Parent_MAC": "Node pare de xarxa",
|
||||
"Device_TableHead_Port": "Port",
|
||||
"Device_TableHead_PresentLastScan": "Presència",
|
||||
"Device_TableHead_ReqNicsOnline": "",
|
||||
"Device_TableHead_ReqNicsOnline": "Requereix NICs En línia",
|
||||
"Device_TableHead_RowID": "ID de fila",
|
||||
"Device_TableHead_Rowid": "ID de fila",
|
||||
"Device_TableHead_SSID": "SSID",
|
||||
@@ -256,7 +256,7 @@
|
||||
"ENCRYPTION_KEY_name": "Clau d'encriptació",
|
||||
"Email_display_name": "Correu electrònic",
|
||||
"Email_icon": "<i class=\"fa fa-at\"></i>",
|
||||
"Events_Loading": "Carregant ...",
|
||||
"Events_Loading": "Carregant …",
|
||||
"Events_Periodselect_All": "Tota la informació",
|
||||
"Events_Periodselect_LastMonth": "Darrer Mes",
|
||||
"Events_Periodselect_LastWeek": "Darrera setmana",
|
||||
@@ -301,7 +301,7 @@
|
||||
"Gen_Cancel": "Cancel·lar",
|
||||
"Gen_Change": "Canviar",
|
||||
"Gen_Copy": "Executar",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_CopyToClipboard": "Copia a portapapers",
|
||||
"Gen_DataUpdatedUITakesTime": "D'acord - Pot passar una estona perquè la interfície d'usuari s'actualitzi si s'està executant una exploració.",
|
||||
"Gen_Delete": "Esborrar",
|
||||
"Gen_DeleteAll": "Esborrar tot",
|
||||
@@ -309,9 +309,9 @@
|
||||
"Gen_Error": "Error",
|
||||
"Gen_Filter": "Filtrar",
|
||||
"Gen_Generate": "Generar",
|
||||
"Gen_InvalidMac": "",
|
||||
"Gen_InvalidMac": "Mac address invàlida.",
|
||||
"Gen_LockedDB": "ERROR - DB podria estar bloquejada - Fes servir F12 Eines desenvolupament -> Consola o provar-ho més tard.",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_NetworkMask": "Màscara de xarxa",
|
||||
"Gen_Offline": "Fora de línia",
|
||||
"Gen_Okay": "Ok",
|
||||
"Gen_Online": "En línia",
|
||||
@@ -329,7 +329,7 @@
|
||||
"Gen_SelectIcon": "<i class=\"fa-solid fa-chevron-down fa-fade\"></i>",
|
||||
"Gen_SelectToPreview": "Seleccioneu la vista prèvia",
|
||||
"Gen_Selected_Devices": "Dispositius seleccionats:",
|
||||
"Gen_Subnet": "",
|
||||
"Gen_Subnet": "Subxarxa",
|
||||
"Gen_Switch": "Switch",
|
||||
"Gen_Upd": "Actualitzat correctament",
|
||||
"Gen_Upd_Fail": "Actualització fallida",
|
||||
@@ -350,7 +350,7 @@
|
||||
"LOADED_PLUGINS_name": "Connectors carregats",
|
||||
"LOG_LEVEL_description": "Aquest paràmetre permetrà un registre més detallat. Útil per a la depuració d'esdeveniments d'escriptura a la base de dades.",
|
||||
"LOG_LEVEL_name": "Imprimeix el registre addicional",
|
||||
"Loading": "Carregant ...",
|
||||
"Loading": "Carregant…",
|
||||
"Login_Box": "Introduïu la vostra contrasenya",
|
||||
"Login_Default_PWD": "Contrasenya per defecte \"123456\" encara és activa.",
|
||||
"Login_Info": "Les contrasenyes es canvien al connector(plugin) Configurar Contrasenya. Comprova el <a target=\"_blank\" href=\"https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/set_password\">SETPWD docs</a> si tens dubtes fent logging.",
|
||||
@@ -419,7 +419,7 @@
|
||||
"Maintenance_Tool_del_allevents": "Elimina deteccions (presència)",
|
||||
"Maintenance_Tool_del_allevents30": "Suprimeix tots els esdeveniments anteriors a 30 dies",
|
||||
"Maintenance_Tool_del_allevents30_noti": "Eliminar Esdeveniments",
|
||||
"Maintenance_Tool_del_allevents30_noti_text": "T'és segur vols eliminar tot els successos més vells que 30 dies? Això elimina tots els dispositius presents.",
|
||||
"Maintenance_Tool_del_allevents30_noti_text": "Estàs segur que vols eliminar tot els successos més vells que 30 dies? Això elimina tots els dispositius presents.",
|
||||
"Maintenance_Tool_del_allevents30_text": "Abans d'utilitzar aquesta funció, feu una còpia de seguretat. La supressió no es pot desfer. S'eliminaran tots els esdeveniments mes vells de 30 dies a la base de dades. També es restablirà la detecció de presència de tots els dispositius. Això pot portar a sessions no vàlides. Això significa que els dispositius es mostren com a \"presents/detectats\" encara que estiguin fora de línia. Una anàlisi mentre el dispositiu en qüestió està en línia resol el problema.",
|
||||
"Maintenance_Tool_del_allevents_noti": "Eliminar Esdeveniments",
|
||||
"Maintenance_Tool_del_allevents_noti_text": "Estàs segur que vols eliminar tots els esdeveniments? Això reinicialitza la detecció de presència de tots els dispositius.",
|
||||
@@ -455,7 +455,7 @@
|
||||
"Maintenance_Tools_Tab_UISettings": "Configuració UI",
|
||||
"Maintenance_arp_status": "Estat de Scan",
|
||||
"Maintenance_arp_status_off": "actualment està desactivat",
|
||||
"Maintenance_arp_status_on": "scan(s) actualment en execució",
|
||||
"Maintenance_arp_status_on": "s'està fent un scan",
|
||||
"Maintenance_built_on": "Construït",
|
||||
"Maintenance_current_version": "Ets actual. Dona un cop d'ull al que <a href=\"https://github.com/jokob-sk/NetAlertX/issues/138\" target=\"_blank\">estic treballant</a>.",
|
||||
"Maintenance_database_backup": "Còpies de seguretat de BBDD",
|
||||
@@ -495,10 +495,10 @@
|
||||
"Navigation_Workflows": "Workflows",
|
||||
"Network_Assign": "Connecta el <i class=\"fa fa-server\"></i> node de Xarxa",
|
||||
"Network_Cant_Assign": "No es pot assignar el node arrel d'Internet com a node fill.",
|
||||
"Network_Cant_Assign_No_Node_Selected": "",
|
||||
"Network_Cant_Assign_No_Node_Selected": "No es pot assignar, no s'ha seleccionat cap node pare.",
|
||||
"Network_Configuration_Error": "Error de configuració",
|
||||
"Network_Connected": "Dispositius connectats",
|
||||
"Network_Devices": "",
|
||||
"Network_Devices": "Dispositius de xarxa",
|
||||
"Network_ManageAdd": "Afegir dispositiu",
|
||||
"Network_ManageAdd_Name": "Nom del dispositiu",
|
||||
"Network_ManageAdd_Name_text": "Nom sense caràcters especials",
|
||||
@@ -533,8 +533,8 @@
|
||||
"Network_Root": "Node arrel",
|
||||
"Network_Root_Not_Configured": "Seleccioneu un tipus de dispositiu de xarxa, per exemple un tipus <b>Gateway</b>, al camp <b>Tipus</b>del <a href=\"deviceDetails.php?mac=Internet\">dispositiu arrel d'Internet</a> per començar a configurar aquesta pantalla. <br/><br/>. Podeu trobar més documentació a la <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/NETWORK_TREE.md\" target=\"_blank\">Guia de com configurar la vostra pàgina de xarxa</a>",
|
||||
"Network_Root_Unconfigurable": "Arrel no configurable",
|
||||
"Network_ShowArchived": "",
|
||||
"Network_ShowOffline": "",
|
||||
"Network_ShowArchived": "Mostra els arxivats",
|
||||
"Network_ShowOffline": "Mostra fora de línia",
|
||||
"Network_Table_Hostname": "Hostname",
|
||||
"Network_Table_IP": "IP",
|
||||
"Network_Table_State": "Estat",
|
||||
@@ -569,7 +569,7 @@
|
||||
"Presence_Key_OnlinePastMiss": "Anteriorment en línia (miss-match)",
|
||||
"Presence_Key_OnlinePastMiss_desc": "El dispositiu estava en línia anteriorment, i actualment està fora de línia. podria ser que a la sessió d'inici li faltin dades conflictives (o podria ser un bug) - si us plau, envií una petició de canvi dins de la caixa de text si sap com solucionar-ho (millor en anglès)",
|
||||
"Presence_Key_OnlinePast_desc": "Dispositiu en línia en el passat, però actualment fora de línia.",
|
||||
"Presence_Loading": "Carregant...",
|
||||
"Presence_Loading": "Carregant…",
|
||||
"Presence_Shortcut_AllDevices": "Els meus Dispositius",
|
||||
"Presence_Shortcut_Archived": "Arxivat",
|
||||
"Presence_Shortcut_Connected": "Connectat",
|
||||
@@ -577,7 +577,7 @@
|
||||
"Presence_Shortcut_DownAlerts": "Aturar alertes",
|
||||
"Presence_Shortcut_Favorites": "Favorits",
|
||||
"Presence_Shortcut_NewDevices": "Nous dispositius",
|
||||
"Presence_Title": "Detecció de dispositius",
|
||||
"Presence_Title": "Presència per dispositiu",
|
||||
"REFRESH_FQDN_description": "Re-escaneja tots dispositius i refresca el seu (FQDN). Si està desactivat, nomes s'escanegen els noms coneguts per fer-ho més ràpid. En aquest cas, FQDN s'actualitza només durant descoberta inicial de dispositius.",
|
||||
"REFRESH_FQDN_name": "Refresc FQDN",
|
||||
"REPORT_DASHBOARD_URL_description": "Aquesta URL s'utilitza com a base per generar enllaços en informes HTML (per exemple: correus electrònics). Introduïu la URL completa començant per <code>http://</code> incloent el número de port (sense barra inicial <code>/</code>).",
|
||||
@@ -598,7 +598,7 @@
|
||||
"Settings_device_Scanners_desync": "⚠ Els horaris d'escàner de dispositius no estan en sincronia.",
|
||||
"Settings_device_Scanners_desync_popup": "Els horaris dels escàners de dispositius (<code>*_RUN_SCHD</code>) no són iguals. Això donarà lloc a notificacions inconsistents del dispositiu en línia / fora de línia. Si no és intencionat, utilitzeu el mateix horari per a tots els <b>🔍 escàners de dispositius</b>.",
|
||||
"Speedtest_Results": "Speedtest Resultats",
|
||||
"Systeminfo_AvailableIps": "",
|
||||
"Systeminfo_AvailableIps": "IPs disponibles",
|
||||
"Systeminfo_CPU": "CPU",
|
||||
"Systeminfo_CPU_Cores": "Nuclis de CPU:",
|
||||
"Systeminfo_CPU_Name": "Nom de CPU:",
|
||||
@@ -683,7 +683,7 @@
|
||||
"UI_LANG_description": "Seleccioneu l'idioma d'usuari preferit. Ajudeu a traduir o suggerir idiomes al portal en línia de <a href=\"https://hosted.weblate.org/projects/pialert/core/\" target=\"_blank\">Weblate</a>.",
|
||||
"UI_LANG_name": "Llenguatge UI",
|
||||
"UI_MY_DEVICES_description": "Els dispositius dels quals s'han de mostrar en la vista predeterminada <b> Els meus dispositius</b>.",
|
||||
"UI_MY_DEVICES_name": "Veure a la vista els meus dispositius",
|
||||
"UI_MY_DEVICES_name": "Veure a la vista Els Meus Dispositius",
|
||||
"UI_NOT_RANDOM_MAC_description": "Prefixos MAC que no s'han de marcar com a dispositius aleatoris. Introduïu per exemple <code> 52</code> per excloure els dispositius començant per <code> 52:xx:xx:xx:xx:xx</code> de ser marcats com a dispositius amb una adreça MAC aleatòria.",
|
||||
"UI_NOT_RANDOM_MAC_name": "No marqueu com a aleatori",
|
||||
"UI_PRESENCE_description": "Seleccioneu quins estats s'han de mostrar al gràfic <b> Presència de dispositius</b> a la pàgina <a href=\"/devices.php\" target=\"_blank\">Dispositius</a>.",
|
||||
@@ -721,10 +721,10 @@
|
||||
"add_icon_event_tooltip": "Afegir nova icona",
|
||||
"add_option_event_tooltip": "Afegir nou valor",
|
||||
"copy_icons_event_tooltip": "Sobreescriure icones de tots els dispositius amb el mateix tipus de dispositiu",
|
||||
"devices_old": "Refrescant...",
|
||||
"devices_old": "Refrescant…",
|
||||
"general_event_description": "L'esdeveniment que has desencadenat pot trigar un temps fins que acabin els processos de fons. L'execució acabarà una cop buida la cua d'execució (Comprova el registre d'errors <a href='/maintenance.php#tab_Logging'></a> si hi ha problemes). <br/> <br/> Cua d'execució:",
|
||||
"general_event_title": "Execució d'un esdeveniment ad-hoc",
|
||||
"go_to_device_event_tooltip": "",
|
||||
"go_to_device_event_tooltip": "Navegar al dispositiu",
|
||||
"go_to_node_event_tooltip": "Navegació a la pàgina de la Xarxa del node donat",
|
||||
"new_version_available": "Ja està disponible una nova versió.",
|
||||
"report_guid": "Notificació guid:",
|
||||
@@ -732,7 +732,7 @@
|
||||
"report_select_format": "Seleccioneu Format:",
|
||||
"report_time": "Data de recepció:",
|
||||
"run_event_tooltip": "Habiliteu la configuració i deseu els canvis al principi abans d'executar-lo.",
|
||||
"select_icon_event_tooltip": "",
|
||||
"select_icon_event_tooltip": "Selecciona la icona",
|
||||
"settings_core_icon": "fa-solid fa-gem",
|
||||
"settings_core_label": "Nucli",
|
||||
"settings_device_scanners": "Escàners de dispositius utilitzats per descobrir dispositius que escriuen a la taula de base de dades CurrentScan.",
|
||||
@@ -746,7 +746,7 @@
|
||||
"settings_imported_label": "Configuració importada",
|
||||
"settings_missing": "No tots els paràmetres carregats! Alta càrrega en la seqüència d'inici de la base de dades o aplicació. Feu clic al botó de recarregar 🔄 a la part superior.",
|
||||
"settings_missing_block": "Error: els paràmetres no carregats correctament. Fer clic el botó recarregar 🔄 dalt de tot, o també comprova el registre de navegador per a detalls (F12).",
|
||||
"settings_old": "Importar la configuració i re-inicialitzar ...",
|
||||
"settings_old": "Importar la configuració i re-iniciar…",
|
||||
"settings_other_scanners": "Uns altres plugins no relacionats amb dispositius que estan actualment activats.",
|
||||
"settings_other_scanners_icon": "fa-solid fa-recycle",
|
||||
"settings_other_scanners_label": "Altres escàners",
|
||||
|
||||
@@ -211,7 +211,7 @@
|
||||
"Device_Shortcut_Connected": "Verbunden",
|
||||
"Device_Shortcut_Devices": "Geräte",
|
||||
"Device_Shortcut_DownAlerts": "Nicht erreichbar & offline",
|
||||
"Device_Shortcut_DownOnly": "Offline",
|
||||
"Device_Shortcut_DownOnly": "Nicht erreichbar",
|
||||
"Device_Shortcut_Favorites": "Favoriten",
|
||||
"Device_Shortcut_NewDevices": "Neue Geräte",
|
||||
"Device_Shortcut_OnlineChart": "Gerätepräsenz im Laufe der Zeit",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// ###################################
|
||||
|
||||
$defaultLang = "en_us";
|
||||
$allLanguages = ["en_us", "es_es", "de_de", "fr_fr", "it_it", "ru_ru", "nb_no", "pl_pl", "pt_br", "tr_tr", "zh_cn", "cs_cz", "ar_ar", "ca_ca", "uk_ua"];
|
||||
$allLanguages = ["en_us", "es_es", "de_de", "fr_fr", "it_it", "ru_ru", "nb_no", "pl_pl", "pt_br", "pt_pt", "tr_tr", "zh_cn", "cs_cz", "ar_ar", "ca_ca", "uk_ua"];
|
||||
|
||||
|
||||
global $db;
|
||||
@@ -19,6 +19,7 @@ switch($result){
|
||||
case 'Norwegian': $pia_lang_selected = 'nb_no'; break;
|
||||
case 'Polish (pl_pl)': $pia_lang_selected = 'pl_pl'; break;
|
||||
case 'Portuguese (pt_br)': $pia_lang_selected = 'pt_br'; break;
|
||||
case 'Portuguese (pt_pt)': $pia_lang_selected = 'pt_pt'; break;
|
||||
case 'Italian (it_it)': $pia_lang_selected = 'it_it'; break;
|
||||
case 'Russian': $pia_lang_selected = 'ru_ru'; break;
|
||||
case 'Turkish (tr_tr)': $pia_lang_selected = 'tr_tr'; break;
|
||||
|
||||
@@ -33,6 +33,6 @@ def merge_translations(main_file, other_files):
|
||||
if __name__ == "__main__":
|
||||
current_path = os.path.dirname(os.path.abspath(__file__))
|
||||
# language codes can be found here: http://www.lingoes.net/en/translator/langcode.htm
|
||||
json_files = ["en_us.json", "de_de.json", "es_es.json", "fr_fr.json", "nb_no.json", "ru_ru.json", "it_it.json", "pt_br.json", "pl_pl.json", "zh_cn.json", "tr_tr.json", "cs_cz.json", "ar_ar.json", "ca_ca.json", "uk_ua.json"]
|
||||
json_files = ["en_us.json", "de_de.json", "es_es.json", "fr_fr.json", "nb_no.json", "ru_ru.json", "it_it.json", "pt_br.json", "pt_pt.json", "pl_pl.json", "zh_cn.json", "tr_tr.json", "cs_cz.json", "ar_ar.json", "ca_ca.json", "uk_ua.json"]
|
||||
file_paths = [os.path.join(current_path, file) for file in json_files]
|
||||
merge_translations(file_paths[0], file_paths[1:])
|
||||
|
||||
763
front/php/templates/language/pt_pt.json
Executable file
@@ -0,0 +1,763 @@
|
||||
{
|
||||
"API_CUSTOM_SQL_description": "Pode especificar uma consulta SQL personalizada que irá gerar um ficheiro JSON e, em seguida, expô-lo por meio do <a href=\"/php/server/query_json.php?file=table_custom_endpoint.json\" target=\"_blank\"><code>table_custom_endpoint.json</code> endpoint do ficheiro</a>.",
|
||||
"API_CUSTOM_SQL_name": "Endpoint customizado",
|
||||
"API_TOKEN_description": "Token de API para comunicação segura. Gere um ou insira qualquer valor. Ele é enviado no cabeçalho da solicitação e usado no plugin <code>SYNC</code>, no servidor GraphQL e em outros endpoints de API. Pode usar os endpoints de API para criar integrações personalizadas, conforme descrito na <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/API.md\" target=\"_blank\">documentação da API</a>.",
|
||||
"API_TOKEN_name": "API token",
|
||||
"API_display_name": "API",
|
||||
"API_icon": "<i class=\"fa fa-arrow-down-up-across-line\"></i>",
|
||||
"About_Design": "Desenvolvido por:",
|
||||
"About_Exit": "Sair",
|
||||
"About_Title": "Analisador de segurança de rede & framework de notificação",
|
||||
"AppEvents_AppEventProcessed": "Processado",
|
||||
"AppEvents_DateTimeCreated": "Registado em",
|
||||
"AppEvents_Extra": "Adicional",
|
||||
"AppEvents_GUID": "Evento de aplicação GUID",
|
||||
"AppEvents_Helper1": "Auxiliar 1",
|
||||
"AppEvents_Helper2": "Auxiliar 2",
|
||||
"AppEvents_Helper3": "Auxiliar 3",
|
||||
"AppEvents_ObjectForeignKey": "Chave Estrangeira",
|
||||
"AppEvents_ObjectIndex": "Índice",
|
||||
"AppEvents_ObjectIsArchived": "Foi arquivado (no horário do log)",
|
||||
"AppEvents_ObjectIsNew": "É novo (no horário do log)",
|
||||
"AppEvents_ObjectPlugin": "Plugin Associado",
|
||||
"AppEvents_ObjectPrimaryID": "ID Primário",
|
||||
"AppEvents_ObjectSecondaryID": "ID Secundário",
|
||||
"AppEvents_ObjectStatus": "Estado registado",
|
||||
"AppEvents_ObjectStatusColumn": "Coluna de Estado",
|
||||
"AppEvents_ObjectType": "Tipo de Objeto",
|
||||
"AppEvents_Plugin": "Plugin",
|
||||
"AppEvents_Type": "Tipo",
|
||||
"BackDevDetail_Actions_Ask_Run": "Deseja executar esta ação?",
|
||||
"BackDevDetail_Actions_Not_Registered": "Ação não registada: ",
|
||||
"BackDevDetail_Actions_Title_Run": "Executar ação",
|
||||
"BackDevDetail_Copy_Ask": "Copiar pormenores de dispositivos da lista suspenda (Tudo nesta página será substituído)?",
|
||||
"BackDevDetail_Copy_Title": "Copiar pormenores",
|
||||
"BackDevDetail_Tools_WOL_error": "O comando NÃO foi executado.",
|
||||
"BackDevDetail_Tools_WOL_okay": "O comando foi executado.",
|
||||
"BackDevices_Arpscan_disabled": "Análise Arp Desativada",
|
||||
"BackDevices_Arpscan_enabled": "Análise ARP Ativada",
|
||||
"BackDevices_Backup_CopError": "A base da dados original não pode ser gravada.",
|
||||
"BackDevices_Backup_Failed": "A copia de segurança foi parcialmente executada. O arquivo não pode ser criado ou está vazio.",
|
||||
"BackDevices_Backup_okay": "A copia de segurança foi feita executado corretamente com o novo arquivo",
|
||||
"BackDevices_DBTools_DelDevError_a": "Erro ao apagar o dispositivo",
|
||||
"BackDevices_DBTools_DelDevError_b": "Erro ao apagar dispositivos",
|
||||
"BackDevices_DBTools_DelDev_a": "Dispositivo apagado",
|
||||
"BackDevices_DBTools_DelDev_b": "Dispositivos apagados",
|
||||
"BackDevices_DBTools_DelEvents": "Eventos apagados",
|
||||
"BackDevices_DBTools_DelEventsError": "Erro ao apagar os eventos",
|
||||
"BackDevices_DBTools_ImportCSV": "Os dispositivos do ficheiro CSV foram importados com sucesso.",
|
||||
"BackDevices_DBTools_ImportCSVError": "O ficheiro CSV não pode ser importado. Assegure que o formato está correto.",
|
||||
"BackDevices_DBTools_ImportCSVMissing": "O ficheiro CSV não foi localizado em <b>/config/devices.csv.</b>",
|
||||
"BackDevices_DBTools_Purge": "As copias de segurança antigas foram apagadas",
|
||||
"BackDevices_DBTools_UpdDev": "Dispositivo atualizado com sucesso. A lista de dispositivos principais pode levar algum tempo para recarregar se uma varredura estiver em andamento.",
|
||||
"BackDevices_DBTools_UpdDevError": "Erro atualizando o dispositivo",
|
||||
"BackDevices_DBTools_Upgrade": "Base de dados atualizada com sucesso",
|
||||
"BackDevices_DBTools_UpgradeError": "A atualização da base de dados falhou",
|
||||
"BackDevices_Device_UpdDevError": "Erro atualizando os dispositivos, tente novamente mais tarde. A base de dados provavelmente está travado com uma tarefa em andamento.",
|
||||
"BackDevices_Restore_CopError": "A base de dados original não pode ser gradava.",
|
||||
"BackDevices_Restore_Failed": "A restauração falhou. Por favor restaure a copia de segurança manualmente.",
|
||||
"BackDevices_Restore_okay": "Restauração executada com sucesso.",
|
||||
"BackDevices_darkmode_disabled": "Modo Noturno Desativado",
|
||||
"BackDevices_darkmode_enabled": "Modo Noturno Ativado",
|
||||
"CLEAR_NEW_FLAG_description": "Se ativado (<code>0</code> está desativado), dispositivos marcados como<b>Novo Dispositivo</b> serão desmarcados se o limite (especificado em horas) exceder o tempo da <b>Primeira Sessão </b>.",
|
||||
"CLEAR_NEW_FLAG_name": "",
|
||||
"CustProps_cant_remove": "Não é possível remover, é necessária pelo menos uma propriedade.",
|
||||
"DAYS_TO_KEEP_EVENTS_description": "Esta é uma definição de manutenção. Especifica o número de dias de entradas de eventos que serão mantidas. Todos os eventos mais antigos serão apagados periodicamente. Também se aplica ao Histórico de eventos do plug-in.",
|
||||
"DAYS_TO_KEEP_EVENTS_name": "Apagar eventos mais antigos que",
|
||||
"DISCOVER_PLUGINS_description": "Desative esta opção para acelerar a inicialização e a gravação de definições. Quando desativada, os plug-ins não são descobertos e não é possível adicionar novos plug-ins à definição<code>LOADED_PLUGINS</code>.",
|
||||
"DISCOVER_PLUGINS_name": "Descobrir plugins",
|
||||
"DevDetail_Children_Title": "Relacionamentos de crianças",
|
||||
"DevDetail_Copy_Device_Title": "Copiar pormenores do dispositivo",
|
||||
"DevDetail_Copy_Device_Tooltip": "Copiar pormenores do dispositivo a partir da lista pendente. Tudo o que se encontra nesta página será substituído",
|
||||
"DevDetail_CustomProperties_Title": "Propriedades personalizadas",
|
||||
"DevDetail_CustomProps_reset_info": "Isto irá remover as suas propriedades personalizadas neste dispositivo e repô-las para o valor predefinido.",
|
||||
"DevDetail_DisplayFields_Title": "Visualização",
|
||||
"DevDetail_EveandAl_AlertAllEvents": "Eventos de alerta",
|
||||
"DevDetail_EveandAl_AlertDown": "",
|
||||
"DevDetail_EveandAl_Archived": "Arquivado",
|
||||
"DevDetail_EveandAl_NewDevice": "Novo dispositivo",
|
||||
"DevDetail_EveandAl_NewDevice_Tooltip": "",
|
||||
"DevDetail_EveandAl_RandomMAC": "MAC Aleatório",
|
||||
"DevDetail_EveandAl_ScanCycle": "Rastrear dispositivo",
|
||||
"DevDetail_EveandAl_ScanCycle_a": "Rastear dispositivo",
|
||||
"DevDetail_EveandAl_ScanCycle_z": "Não rastear dispositivo",
|
||||
"DevDetail_EveandAl_Skip": "Pular notificações repetidas para",
|
||||
"DevDetail_EveandAl_Title": "Configuração de Eventos & Alertas",
|
||||
"DevDetail_Events_CheckBox": "Ocultar eventos de conexão",
|
||||
"DevDetail_GoToNetworkNode": "Navega para a página Rede do nó indicado.",
|
||||
"DevDetail_Icon": "Icone",
|
||||
"DevDetail_Icon_Descr": "Introduza o nome de um ícone fantástico do tipo de letra sem o prefixo fa- ou com a classe completa, por exemplo: fa fa-brands fa-apple.",
|
||||
"DevDetail_Loading": "A carregar…",
|
||||
"DevDetail_MainInfo_Comments": "Comentários",
|
||||
"DevDetail_MainInfo_Favorite": "Favorito",
|
||||
"DevDetail_MainInfo_Group": "Grupo",
|
||||
"DevDetail_MainInfo_Location": "Localização",
|
||||
"DevDetail_MainInfo_Name": "Nome",
|
||||
"DevDetail_MainInfo_Network": "<i class=\"fa fa-server\"> </i> Node (MAC)",
|
||||
"DevDetail_MainInfo_Network_Port": "<i class=\"fa fa-ethernet\"></i>Porta",
|
||||
"DevDetail_MainInfo_Network_Site": "Site",
|
||||
"DevDetail_MainInfo_Network_Title": "Rede",
|
||||
"DevDetail_MainInfo_Owner": "Proprietário",
|
||||
"DevDetail_MainInfo_SSID": "SSID",
|
||||
"DevDetail_MainInfo_Title": "Informações principais",
|
||||
"DevDetail_MainInfo_Type": "Tipo",
|
||||
"DevDetail_MainInfo_Vendor": "Fornecedor",
|
||||
"DevDetail_MainInfo_mac": "MAC",
|
||||
"DevDetail_NavToChildNode": "",
|
||||
"DevDetail_Network_Node_hover": "Selecione o dispositivo de rede principal ao qual o dispositivo atual está conectado, para preencher a árvore Rede.",
|
||||
"DevDetail_Network_Port_hover": "A porta a que este dispositivo está ligado no dispositivo de rede principal. Se for deixado vazio, é apresentado um ícone wifi na árvore Rede.",
|
||||
"DevDetail_Nmap_Scans": "Varreduras manuais do Nmap",
|
||||
"DevDetail_Nmap_Scans_desc": "",
|
||||
"DevDetail_Nmap_buttonDefault": "Verificação predefinida",
|
||||
"DevDetail_Nmap_buttonDefault_text": "Scan padrão: Nmap verifica as 1.000 portas superiores para cada protocolo de digitalização solicitado. Isto atinge cerca de 93% das portas TCP e 49% das portas UDP. (cerca de 5 segundos)",
|
||||
"DevDetail_Nmap_buttonDetail": "Verificação Detalhada",
|
||||
"DevDetail_Nmap_buttonDetail_text": "Verificação detalhada: Verificação predefinida com deteção de SO ativada, deteção de versão, verificação de scripts e traceroute (até 30 segundos ou mais)",
|
||||
"DevDetail_Nmap_buttonFast": "Verificação rápida",
|
||||
"DevDetail_Nmap_buttonFast_text": "Verificação rápida: Verifica menos portas (100) do que a verificação predefinida (alguns segundos)",
|
||||
"DevDetail_Nmap_buttonSkipDiscovery": "Saltar descoberta do host",
|
||||
"DevDetail_Nmap_buttonSkipDiscovery_text": "Ignorar a descoberta do host (-Pn opção): Verificação padrão sem descoberta do host",
|
||||
"DevDetail_Nmap_resultsLink": "Pode deixar esta página depois de iniciar uma varredura. Os resultados também estarão disponíveis no ficheiro <code>app_front.log</code>.",
|
||||
"DevDetail_Owner_hover": "Quem é o dono deste dispositivo. Campo de texto gratuito.",
|
||||
"DevDetail_Periodselect_All": "Todas as informações",
|
||||
"DevDetail_Periodselect_LastMonth": "Último mês",
|
||||
"DevDetail_Periodselect_LastWeek": "Semana passada",
|
||||
"DevDetail_Periodselect_LastYear": "Ano passado",
|
||||
"DevDetail_Periodselect_today": "Hoje",
|
||||
"DevDetail_Run_Actions_Title": "<i class=\"fa fa-play\"></i> Executar ação no dispositivo",
|
||||
"DevDetail_Run_Actions_Tooltip": "Execute uma ação no dispositivo atual da lista suspensa.",
|
||||
"DevDetail_SessionInfo_FirstSession": "Primeira sessão",
|
||||
"DevDetail_SessionInfo_LastIP": "Último IP",
|
||||
"DevDetail_SessionInfo_LastSession": "Último offline",
|
||||
"DevDetail_SessionInfo_StaticIP": "IP estático",
|
||||
"DevDetail_SessionInfo_Status": "Estado",
|
||||
"DevDetail_SessionInfo_Title": "Informações de Sessão",
|
||||
"DevDetail_SessionTable_Additionalinfo": "Informações adicionais",
|
||||
"DevDetail_SessionTable_Connection": "Conexão",
|
||||
"DevDetail_SessionTable_Disconnection": "Desconexão",
|
||||
"DevDetail_SessionTable_Duration": "Duração",
|
||||
"DevDetail_SessionTable_IP": "IP",
|
||||
"DevDetail_SessionTable_Order": "Ordem",
|
||||
"DevDetail_Shortcut_CurrentStatus": "Estado atual",
|
||||
"DevDetail_Shortcut_DownAlerts": "Alertas para baixo",
|
||||
"DevDetail_Shortcut_Presence": "Presença",
|
||||
"DevDetail_Shortcut_Sessions": "Sessões",
|
||||
"DevDetail_Tab_Details": "Pormenores",
|
||||
"DevDetail_Tab_Events": "Eventos",
|
||||
"DevDetail_Tab_EventsTableDate": "Data",
|
||||
"DevDetail_Tab_EventsTableEvent": "Tipo de evento",
|
||||
"DevDetail_Tab_EventsTableIP": "IP",
|
||||
"DevDetail_Tab_EventsTableInfo": "Informações adicionais",
|
||||
"DevDetail_Tab_Nmap": "<i class=\"fa fa-ethernet\"> </i> Nmap",
|
||||
"DevDetail_Tab_NmapEmpty": "Nenhuma porta detetada com Nmap neste dispositivo.",
|
||||
"DevDetail_Tab_NmapTableExtra": "Adicional",
|
||||
"DevDetail_Tab_NmapTableHeader": "Resultados da verificação programada",
|
||||
"DevDetail_Tab_NmapTableIndex": "Índice",
|
||||
"DevDetail_Tab_NmapTablePort": "Porta",
|
||||
"DevDetail_Tab_NmapTableService": "Serviço",
|
||||
"DevDetail_Tab_NmapTableState": "Estado",
|
||||
"DevDetail_Tab_NmapTableText": "",
|
||||
"DevDetail_Tab_NmapTableTime": "Tempo",
|
||||
"DevDetail_Tab_Plugins": "Plugins",
|
||||
"DevDetail_Tab_Presence": "Presença",
|
||||
"DevDetail_Tab_Sessions": "Sessões",
|
||||
"DevDetail_Tab_Tools": "Ferramentas",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Description": "",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Error": "Ocorreu um erro",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Start": "",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Title": "",
|
||||
"DevDetail_Tab_Tools_Nslookup_Description": "",
|
||||
"DevDetail_Tab_Tools_Nslookup_Error": "",
|
||||
"DevDetail_Tab_Tools_Nslookup_Start": "",
|
||||
"DevDetail_Tab_Tools_Nslookup_Title": "",
|
||||
"DevDetail_Tab_Tools_Speedtest_Description": "",
|
||||
"DevDetail_Tab_Tools_Speedtest_Start": "",
|
||||
"DevDetail_Tab_Tools_Speedtest_Title": "",
|
||||
"DevDetail_Tab_Tools_Traceroute_Description": "",
|
||||
"DevDetail_Tab_Tools_Traceroute_Error": "",
|
||||
"DevDetail_Tab_Tools_Traceroute_Start": "",
|
||||
"DevDetail_Tab_Tools_Traceroute_Title": "",
|
||||
"DevDetail_Tools_WOL": "",
|
||||
"DevDetail_Tools_WOL_noti": "",
|
||||
"DevDetail_Tools_WOL_noti_text": "",
|
||||
"DevDetail_Type_hover": "",
|
||||
"DevDetail_Vendor_hover": "",
|
||||
"DevDetail_WOL_Title": "",
|
||||
"DevDetail_button_AddIcon": "",
|
||||
"DevDetail_button_AddIcon_Help": "Cole uma tag HTML SVG ou um ícone de tag HTML Font Awesome. Leia a <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/ICONS.md\" target=\"_blank\">documentação sobre ícones</a> para obter pormenores.",
|
||||
"DevDetail_button_AddIcon_Tooltip": "Adicione um novo ícone a este dispositivo que ainda não esteja disponível no menu suspenso.",
|
||||
"DevDetail_button_Delete": "Apagar dispositivo",
|
||||
"DevDetail_button_DeleteEvents": "Apagar eventos",
|
||||
"DevDetail_button_DeleteEvents_Warning": "Tem certeza de que deseja apagar todos os eventos deste dispositivo?<br><br>(isto limpará o <b>Histórico de eventos</b> e as <b>sessões</b> e poderá ajudar com constantes (persistentes) notificações)",
|
||||
"DevDetail_button_Delete_ask": "Tem a certeza de que pretende apagar este dispositivo? Em vez disso, também o pode arquivar.",
|
||||
"DevDetail_button_OverwriteIcons": "Substituir ícones",
|
||||
"DevDetail_button_OverwriteIcons_Tooltip": "Substituir ícones de todos os dispositivos pelo mesmo tipo de dispositivo",
|
||||
"DevDetail_button_OverwriteIcons_Warning": "Tem certeza de que deseja substituir todos os ícones de todos os dispositivos pelo mesmo tipo de dispositivo do tipo de dispositivo atual?",
|
||||
"DevDetail_button_Reset": "Redefinir alterações",
|
||||
"DevDetail_button_Save": "Gravar",
|
||||
"DeviceEdit_ValidMacIp": "Insira um endereço <b>Mac</b> e <b>IP</b> válidos.",
|
||||
"Device_MultiEdit": "Edição múltipla",
|
||||
"Device_MultiEdit_Backup": "",
|
||||
"Device_MultiEdit_Fields": "Editar campos:",
|
||||
"Device_MultiEdit_MassActions": "Ações em massa:",
|
||||
"Device_MultiEdit_Tooltip": "Cuidadoso. Clicar aqui aplicará o valor à esquerda a todos os dispositivos selecionados acima.",
|
||||
"Device_Searchbox": "Procurar",
|
||||
"Device_Shortcut_AllDevices": "",
|
||||
"Device_Shortcut_AllNodes": "",
|
||||
"Device_Shortcut_Archived": "Arquivado",
|
||||
"Device_Shortcut_Connected": "Conectado",
|
||||
"Device_Shortcut_Devices": "Dispositivos",
|
||||
"Device_Shortcut_DownAlerts": "Inativo e off-line",
|
||||
"Device_Shortcut_DownOnly": "Inativo",
|
||||
"Device_Shortcut_Favorites": "Favoritos",
|
||||
"Device_Shortcut_NewDevices": "",
|
||||
"Device_Shortcut_OnlineChart": "Presença do dispositivo",
|
||||
"Device_TableHead_AlertDown": "Alerta em baixo",
|
||||
"Device_TableHead_Connected_Devices": "Conexões",
|
||||
"Device_TableHead_CustomProps": "",
|
||||
"Device_TableHead_FQDN": "",
|
||||
"Device_TableHead_Favorite": "Favorito",
|
||||
"Device_TableHead_FirstSession": "Primeira sessão",
|
||||
"Device_TableHead_GUID": "GUID",
|
||||
"Device_TableHead_Group": "Grupo",
|
||||
"Device_TableHead_Icon": "Ícone",
|
||||
"Device_TableHead_LastIP": "Último IP",
|
||||
"Device_TableHead_LastIPOrder": "Último pedido de IP",
|
||||
"Device_TableHead_LastSession": "Último off-line",
|
||||
"Device_TableHead_Location": "Localização",
|
||||
"Device_TableHead_MAC": "MAC aleatório",
|
||||
"Device_TableHead_MAC_full": "MAC completo",
|
||||
"Device_TableHead_Name": "Nome",
|
||||
"Device_TableHead_NetworkSite": "Site da rede",
|
||||
"Device_TableHead_Owner": "Proprietário",
|
||||
"Device_TableHead_ParentRelType": "",
|
||||
"Device_TableHead_Parent_MAC": "",
|
||||
"Device_TableHead_Port": "Porta",
|
||||
"Device_TableHead_PresentLastScan": "Presença",
|
||||
"Device_TableHead_ReqNicsOnline": "",
|
||||
"Device_TableHead_RowID": "ID da linha",
|
||||
"Device_TableHead_Rowid": "ID da linha",
|
||||
"Device_TableHead_SSID": "SSID",
|
||||
"Device_TableHead_SourcePlugin": "Plugin de fonte",
|
||||
"Device_TableHead_Status": "Estado",
|
||||
"Device_TableHead_SyncHubNodeName": "Nó de sincronização",
|
||||
"Device_TableHead_Type": "Tipo",
|
||||
"Device_TableHead_Vendor": "Fornecedor",
|
||||
"Device_Table_Not_Network_Device": "Não configurado como um dispositivo de rede",
|
||||
"Device_Table_info": "A mostrar _START_ to _END_ of _TOTAL_ entradas",
|
||||
"Device_Table_nav_next": "Próximo",
|
||||
"Device_Table_nav_prev": "Anterior",
|
||||
"Device_Tablelenght": "Mostrar entradas do _MENU_",
|
||||
"Device_Tablelenght_all": "Todos",
|
||||
"Device_Title": "Dispositivos",
|
||||
"Devices_Filters": "Filtros",
|
||||
"ENABLE_PLUGINS_description": "Ativa a funcionalidade de <a target=\"_blank\" href=\"https://github.com/jokob-sk/NetAlertX/tree/main/docs/PLUGINS.md\">plugins</a>. Carregar plug-ins requer mais recursos de hardware, então podedesativá-los em sistemas de baixa potência.",
|
||||
"ENABLE_PLUGINS_name": "Ativar plug-ins",
|
||||
"ENCRYPTION_KEY_description": "Chave de encriptação de dados.",
|
||||
"ENCRYPTION_KEY_name": "Chave de encriptação",
|
||||
"Email_display_name": "Email",
|
||||
"Email_icon": "<i class=\"fa fa-at\"></i>",
|
||||
"Events_Loading": "",
|
||||
"Events_Periodselect_All": "Todas as informações",
|
||||
"Events_Periodselect_LastMonth": "Mês passado",
|
||||
"Events_Periodselect_LastWeek": "Semana passada",
|
||||
"Events_Periodselect_LastYear": "Ano passado",
|
||||
"Events_Periodselect_today": "Hoje",
|
||||
"Events_Searchbox": "Procurar",
|
||||
"Events_Shortcut_AllEvents": "Todos os eventos",
|
||||
"Events_Shortcut_DownAlerts": "Alertas de queda",
|
||||
"Events_Shortcut_Events": "Eventos",
|
||||
"Events_Shortcut_MissSessions": "Sessões ausentes",
|
||||
"Events_Shortcut_NewDevices": "",
|
||||
"Events_Shortcut_Sessions": "Sessões",
|
||||
"Events_Shortcut_VoidSessions": "Sessões anuladas",
|
||||
"Events_TableHead_AdditionalInfo": "Informação adicional",
|
||||
"Events_TableHead_Connection": "Conexão",
|
||||
"Events_TableHead_Date": "Data",
|
||||
"Events_TableHead_Device": "Dispositivo",
|
||||
"Events_TableHead_Disconnection": "Desconexão",
|
||||
"Events_TableHead_Duration": "Duração",
|
||||
"Events_TableHead_DurationOrder": "Duração do pedido",
|
||||
"Events_TableHead_EventType": "",
|
||||
"Events_TableHead_IP": "IP",
|
||||
"Events_TableHead_IPOrder": "Pedido de IP",
|
||||
"Events_TableHead_Order": "Ordem",
|
||||
"Events_TableHead_Owner": "Proprietário",
|
||||
"Events_TableHead_PendingAlert": "Alerta Pendente",
|
||||
"Events_Table_info": "A mostrar_START_ to _END_ of _TOTAL_ entradas",
|
||||
"Events_Table_nav_next": "Próxima",
|
||||
"Events_Table_nav_prev": "Anterior",
|
||||
"Events_Tablelenght": "Mostrar entradas do _MENU_",
|
||||
"Events_Tablelenght_all": "Todos",
|
||||
"Events_Title": "Eventos",
|
||||
"GRAPHQL_PORT_description": "O número da porta do servidor GraphQL. Certifique-se de que a porta seja exclusiva em todas as suas aplicações neste host e nas instâncias do NetAlertX.",
|
||||
"GRAPHQL_PORT_name": "Porta GraphQL",
|
||||
"Gen_Action": "Ação",
|
||||
"Gen_Add": "Adicionar",
|
||||
"Gen_AddDevice": "",
|
||||
"Gen_Add_All": "Adicionar todos",
|
||||
"Gen_All_Devices": "",
|
||||
"Gen_AreYouSure": "Tem certeza?",
|
||||
"Gen_Backup": "Executar backup",
|
||||
"Gen_Cancel": "Cancelar",
|
||||
"Gen_Change": "Alterar",
|
||||
"Gen_Copy": "Executar",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_DataUpdatedUITakesTime": "OK - Pode levar um tempo para a interface do utilizador ser atualizada se uma verificação estiver em execução.",
|
||||
"Gen_Delete": "Apagar",
|
||||
"Gen_DeleteAll": "Apagar todos",
|
||||
"Gen_Description": "Descrição",
|
||||
"Gen_Error": "Erro",
|
||||
"Gen_Filter": "Filtro",
|
||||
"Gen_Generate": "Gerar",
|
||||
"Gen_InvalidMac": "",
|
||||
"Gen_LockedDB": "ERRO - A base de dados pode estar bloqueada - Verifique F12 Ferramentas de desenvolvimento -> Console ou tente mais tarde.",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_Offline": "Offline",
|
||||
"Gen_Okay": "Ok",
|
||||
"Gen_Online": "Online",
|
||||
"Gen_Purge": "Purge",
|
||||
"Gen_ReadDocs": "Leia mais em documentos.",
|
||||
"Gen_Remove_All": "Remover tudo",
|
||||
"Gen_Remove_Last": "Remover o último",
|
||||
"Gen_Reset": "Repor",
|
||||
"Gen_Restore": "Executar restauração",
|
||||
"Gen_Run": "Executar",
|
||||
"Gen_Save": "Gravar",
|
||||
"Gen_Saved": "Gravado",
|
||||
"Gen_Search": "Procurar",
|
||||
"Gen_Select": "Selecionar",
|
||||
"Gen_SelectIcon": "<i class=\"fa-solid fa-chevron-down fa-fade\"></i>",
|
||||
"Gen_SelectToPreview": "Selecionar para pré-visualizar",
|
||||
"Gen_Selected_Devices": "",
|
||||
"Gen_Subnet": "",
|
||||
"Gen_Switch": "Trocar",
|
||||
"Gen_Upd": "Atualizado com sucesso",
|
||||
"Gen_Upd_Fail": "A atualização falhou",
|
||||
"Gen_Update": "Atualizar",
|
||||
"Gen_Update_Value": "Atualizar valor",
|
||||
"Gen_ValidIcon": "<i class=\"fa-solid fa-chevron-right \"></i>",
|
||||
"Gen_Warning": "Aviso",
|
||||
"Gen_Work_In_Progress": "Trabalho em andamento, um bom momento para enviar feedback em https://github.com/jokob-sk/NetAlertX/issues",
|
||||
"Gen_create_new_device": "Novo dispositivo",
|
||||
"Gen_create_new_device_info": "Os dispositivos são normalmente descobertos usando <a target=\"_blank\" href=\"https://github.com/jokob-sk/NetAlertX/tree/main/docs/PLUGINS.md\">plugins</a>. No entanto, em certos casos, pode ser necessário adicionar dispositivos manualmente. Para explorar cenários específicos, verifique a <a target=\"_blank\" href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/REMOTE_NETWORKS.md\">documentação de Redes Remotas</a>.",
|
||||
"General_display_name": "Geral",
|
||||
"General_icon": "<i class=\"fa fa-gears\"></i>",
|
||||
"HRS_TO_KEEP_NEWDEV_description": "",
|
||||
"HRS_TO_KEEP_NEWDEV_name": "",
|
||||
"HRS_TO_KEEP_OFFDEV_description": "",
|
||||
"HRS_TO_KEEP_OFFDEV_name": "Apagar dispositivos offline após",
|
||||
"LOADED_PLUGINS_description": "Quais plugins carregar. Adicionar plugins pode deixar a aplicação lenta. Leia mais sobre quais plugins precisam ser ativados, tipos ou opções de escaneamento na <a target=\"_blank\" href=\"https://github.com/jokob-sk/NetAlertX/tree/main/docs/PLUGINS.md\">documentação de plugins</a>. Plugins descarregados perderão as suas configurações. Somente plugins <code>desativados</code> podem ser descarregados.",
|
||||
"LOADED_PLUGINS_name": "Plugins carregados",
|
||||
"LOG_LEVEL_description": "Esta definição permite um registo mais detalhado. Útil para depurar eventos gravados na base de dados.",
|
||||
"LOG_LEVEL_name": "Imprimir registo adicional",
|
||||
"Loading": "",
|
||||
"Login_Box": "Introduza a sua palavra-passe",
|
||||
"Login_Default_PWD": "A palavra-passe predefinida “123456” ainda está ativa.",
|
||||
"Login_Info": "As palavra-passes são definidas por meio do plugin Definir palavra-passe. Verifique a <a target=\"_blank\" href=\"https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/set_password\">documentação do SETPWD</a> se tiver problemas para fazer login.",
|
||||
"Login_Psw-box": "Palavra-passe",
|
||||
"Login_Psw_alert": "Alerta de palavra-passe!",
|
||||
"Login_Psw_folder": "na pasta de configuração.",
|
||||
"Login_Psw_new": "nova_palavra-passe",
|
||||
"Login_Psw_run": "Para alterar a palavra-passe, executar:",
|
||||
"Login_Remember": "Lembrar",
|
||||
"Login_Remember_small": "(válido por 7 dias)",
|
||||
"Login_Submit": "Iniciar sessão",
|
||||
"Login_Toggle_Alert_headline": "Alerta de palavra-passe!",
|
||||
"Login_Toggle_Info": "Informações sobre a palavra-passe",
|
||||
"Login_Toggle_Info_headline": "Informações sobre a palavra-passe",
|
||||
"Maint_PurgeLog": "Limpar o registo",
|
||||
"Maint_RestartServer": "Reiniciar o servidor",
|
||||
"Maint_Restart_Server_noti_text": "Tem certeza de que deseja reiniciar o servidor backend? Isto pode causar inconsistência na app. Faça primeiro um backup da sua configuração. <br/> <br/> Nota: Isto pode levar alguns minutos.",
|
||||
"Maintenance_InitCheck": "",
|
||||
"Maintenance_InitCheck_Checking": "",
|
||||
"Maintenance_InitCheck_QuickSetupGuide": "",
|
||||
"Maintenance_InitCheck_Success": "",
|
||||
"Maintenance_ReCheck": "",
|
||||
"Maintenance_Running_Version": "Versão instalada",
|
||||
"Maintenance_Status": "Situação",
|
||||
"Maintenance_Title": "Ferramentas de manutenção",
|
||||
"Maintenance_Tool_DownloadConfig": "",
|
||||
"Maintenance_Tool_DownloadConfig_text": "Descarregue um backup completo da configuração das Configurações armazenada no ficheiro <code>app.conf</code>.",
|
||||
"Maintenance_Tool_DownloadWorkflows": "",
|
||||
"Maintenance_Tool_DownloadWorkflows_text": "",
|
||||
"Maintenance_Tool_ExportCSV": "",
|
||||
"Maintenance_Tool_ExportCSV_noti": "",
|
||||
"Maintenance_Tool_ExportCSV_noti_text": "Tem a certeza de que pretende gerar um ficheiro CSV?",
|
||||
"Maintenance_Tool_ExportCSV_text": "Gere um ficheiro CSV (valor separado por vírgula) contendo a lista de dispositivos, incluindo os relacionamentos de rede entre os nós de rede e os dispositivos conectados. Também pode acionar isto a aceder esta URL <code>your_NetAlertX_url/php/server/devices.php?action=ExportCSV</code> ou ativando o plugin <a href=\"settings.php#CSVBCKP_header\">CSV Backup</a>.",
|
||||
"Maintenance_Tool_ImportCSV": "Importação de dispositivos (csv)",
|
||||
"Maintenance_Tool_ImportCSV_noti": "Importação de dispositivos (csv)",
|
||||
"Maintenance_Tool_ImportCSV_noti_text": "Tem certeza de que deseja importar o ficheiro CSV? Isto <b>sobrescreverá</b> completamente os dispositivos na sua base de dados.",
|
||||
"Maintenance_Tool_ImportCSV_text": "Antes de usar esta função, faça um backup. Importe um ficheiro CSV (valores separados por vírgula) contendo a lista de dispositivos, incluindo os relacionamentos de rede entre os nós de rede e os dispositivos conectados. Para fazer isto, ponha o ficheiro CSV chamado <b>devices.csv</b> na sua pasta <b>/config</b>.",
|
||||
"Maintenance_Tool_ImportConfig_noti": "Importação de configurações (app.conf)",
|
||||
"Maintenance_Tool_ImportPastedCSV": "Importação de dispositivos (csv) (colar)",
|
||||
"Maintenance_Tool_ImportPastedCSV_noti_text": "Tem certeza de que deseja importar o CSV colado? Isto <b>sobrescreverá</b> completamente os dispositivos na sua base de dados.",
|
||||
"Maintenance_Tool_ImportPastedCSV_text": "Antes de usar esta função, faça um backup. Importe um ficheiro CSV (valor separado por vírgula) contendo a lista de Dispositivos, incluindo os relacionamentos de Rede entre os Nós de Rede e os dispositivos conectados.",
|
||||
"Maintenance_Tool_ImportPastedConfig": "Configurações Importar (colar)",
|
||||
"Maintenance_Tool_ImportPastedConfig_noti_text": "Tem certeza de que deseja importar as configurações coladas? Isto irá <b>sobrescrever</b> completamente o ficheiro <code>app.conf</code>.",
|
||||
"Maintenance_Tool_ImportPastedConfig_text": "Importa o ficheiro <code>app.conf</code> contendo todas as configurações da aplicação. Pode descarregar primeiro o ficheiro <code>app.conf</code> com a <b>Exportação de configurações</b>.",
|
||||
"Maintenance_Tool_arpscansw": "Alternar arp-Scan (ligado/desligado)",
|
||||
"Maintenance_Tool_arpscansw_noti": "Ativar ou desativar o arp-Scan",
|
||||
"Maintenance_Tool_arpscansw_noti_text": "Quando a análise é desligada, permanece desligada até ser novamente ativada.",
|
||||
"Maintenance_Tool_arpscansw_text": "Ligar ou desligar o arp-scan. Quando o scan é desligado, permanece desligado até ser novamente ativado. As varreduras ativas não são canceladas.",
|
||||
"Maintenance_Tool_backup": "Cópia de segurança da BD",
|
||||
"Maintenance_Tool_backup_noti": "Cópia de segurança da BD",
|
||||
"Maintenance_Tool_backup_noti_text": "Tem a certeza de que pretende executar a cópia de segurança da BD? Certifique-se de que não está a ser executada nenhuma verificação.",
|
||||
"Maintenance_Tool_backup_text": "Os backups da base de dados estão localizadas no diretório da base de dados como um arquivo zip, nomeado com a data de criação. Não há nenhum número máximo de backups.",
|
||||
"Maintenance_Tool_check_visible": "Desmarque para esconder a coluna.",
|
||||
"Maintenance_Tool_darkmode": "Modos de alternância (escuro/claro)",
|
||||
"Maintenance_Tool_darkmode_noti": "Modos de alternância",
|
||||
"Maintenance_Tool_darkmode_noti_text": "Após a mudança de tema, a página tenta recarregar-se para ativar a alteração. Se necessário, a cache deve ser limpa.",
|
||||
"Maintenance_Tool_darkmode_text": "Alternar entre o modo escuro e o modo claro. Se a alternância não funcionar corretamente, tente limpar a cache do browser. A alteração ocorre no lado do servidor, pelo que afecta todos os dispositivos em utilização.",
|
||||
"Maintenance_Tool_del_ActHistory": "A apagar a atividade da rede",
|
||||
"Maintenance_Tool_del_ActHistory_noti": "Apagar atividade de rede",
|
||||
"Maintenance_Tool_del_ActHistory_noti_text": "Tem certeza de que deseja redefinir a atividade da rede?",
|
||||
"Maintenance_Tool_del_ActHistory_text": "O gráfico de atividade da rede é redefinido. Isto não afeta os eventos.",
|
||||
"Maintenance_Tool_del_alldev": "",
|
||||
"Maintenance_Tool_del_alldev_noti": "",
|
||||
"Maintenance_Tool_del_alldev_noti_text": "Tem certeza de que deseja apagar todos os dispositivos?",
|
||||
"Maintenance_Tool_del_alldev_text": "Antes de usar esta função, faça um backup. Apagar não pode ser desfeito. Todos os dispositivos serão apagados da base de dados.",
|
||||
"Maintenance_Tool_del_allevents": "Apagar eventos (Repor presença)",
|
||||
"Maintenance_Tool_del_allevents30": "Apagar todos os eventos com mais que 30 dias",
|
||||
"Maintenance_Tool_del_allevents30_noti": "Apagar eventos",
|
||||
"Maintenance_Tool_del_allevents30_noti_text": "",
|
||||
"Maintenance_Tool_del_allevents30_text": "Antes de utilizar esta função, faça uma cópia de segurança. Apagar não pode ser anulado. Todos os eventos com mais que 30 dias na base de dados serão eliminados. Nesse momento, a presença de todos os dispositivos será reiniciada. Este facto pode dar origem a sessões inválidas. Isto significa que os dispositivos são apresentados como “presentes” apesar de estarem offline. Uma verificação enquanto o dispositivo em questão está online resolve o problema.",
|
||||
"Maintenance_Tool_del_allevents_noti": "Apagar eventos",
|
||||
"Maintenance_Tool_del_allevents_noti_text": "",
|
||||
"Maintenance_Tool_del_allevents_text": "Antes de usar esta função, faça um backup. Apagar não pode ser desfeito. Todos os eventos na base de dados serão apagados. Nesse momento, a presença de todos os dispositivos será redefinida. Isto pode levar a sessões inválidas. Isto significa que os dispositivos são exibidos como \"presente\" embora estejam offline. Uma varredura enquanto o dispositivo em questão é on-line resolve o problema.",
|
||||
"Maintenance_Tool_del_empty_macs": "",
|
||||
"Maintenance_Tool_del_empty_macs_noti": "",
|
||||
"Maintenance_Tool_del_empty_macs_noti_text": "Tem certeza que deseja apagar todos os dispositivos com endereços MAC vazios?<br>(talvez prefira arquivá-los)",
|
||||
"Maintenance_Tool_del_empty_macs_text": "Antes de usar esta função, faça um backup. Apagar não pode ser desfeito. Todos os dispositivos sem MAC serão apagados da base de dados.",
|
||||
"Maintenance_Tool_del_selecteddev": "Apagar dispositivos selecionados",
|
||||
"Maintenance_Tool_del_selecteddev_text": "Antes de usar esta função, faça um backup. Apagar não pode ser desfeito. Dispositivos selecionados serão apagados da base de dados.",
|
||||
"Maintenance_Tool_del_unknowndev": "",
|
||||
"Maintenance_Tool_del_unknowndev_noti": "",
|
||||
"Maintenance_Tool_del_unknowndev_noti_text": "Tem certeza que deseja apagar todos (desconhecidos) e (nome não encontrados) dispositivos?",
|
||||
"Maintenance_Tool_del_unknowndev_text": "Antes de usar esta função, faça um backup. Apagar não pode ser desfeito. Todos os dispositivos nomeados (não conhecidos) serão apagados da base de dados.",
|
||||
"Maintenance_Tool_displayed_columns_text": "Altere a visibilidade e a ordem das colunas na página <a href=\"devices.php\"><b> <i class=\"fa fa-portátil\"></i> Dispositivos</b></a>.",
|
||||
"Maintenance_Tool_drag_me": "Arraste-me para reordenar colunas.",
|
||||
"Maintenance_Tool_order_columns_text": "",
|
||||
"Maintenance_Tool_purgebackup": "Limpar cópias de segurança",
|
||||
"Maintenance_Tool_purgebackup_noti": "Limpar cópias de segurança",
|
||||
"Maintenance_Tool_purgebackup_noti_text": "Tem certeza que deseja apagar todos os backups exceto os últimos 3?",
|
||||
"Maintenance_Tool_purgebackup_text": "Todos os outros backups serão apagados exceto para os últimos 3 backups.",
|
||||
"Maintenance_Tool_restore": "Restauração de DB",
|
||||
"Maintenance_Tool_restore_noti": "Restauração de DB",
|
||||
"Maintenance_Tool_restore_noti_text": "Tem a certeza de que quer executar a Restauração DB? Certifique-se de que nenhuma varredura funciona atualmente.",
|
||||
"Maintenance_Tool_restore_text": "O backup mais recente pode ser restaurado através do botão, mas os backups mais antigos só podem ser restaurados manualmente. Após a restauração, faça uma verificação de integridade na base de dados para segurança, caso o db estivesse atualmente em acesso de gravação quando o backup foi criado.",
|
||||
"Maintenance_Tool_upgrade_database_noti": "Atualizar a base de dados",
|
||||
"Maintenance_Tool_upgrade_database_noti_text": "Tem certeza de que deseja atualizar a base de dados?<br>(talvez prefira arquivá-la)",
|
||||
"Maintenance_Tool_upgrade_database_text": "Este botão atualizará a base de dados para ativar o gráfico Atividade de rede nas últimas 12 horas. Faça uma cópia de segurança da sua base de dados em caso de problemas.",
|
||||
"Maintenance_Tools_Tab_BackupRestore": "Backup / Restauração",
|
||||
"Maintenance_Tools_Tab_Logging": "",
|
||||
"Maintenance_Tools_Tab_Settings": "Configurações",
|
||||
"Maintenance_Tools_Tab_Tools": "Ferramentas",
|
||||
"Maintenance_Tools_Tab_UISettings": "Configurações de interface",
|
||||
"Maintenance_arp_status": "Estado de digitalização",
|
||||
"Maintenance_arp_status_off": "está atualmente desativado",
|
||||
"Maintenance_arp_status_on": "",
|
||||
"Maintenance_built_on": "Construído em",
|
||||
"Maintenance_current_version": "Você está atualizado. Confira o que <a href=\"https://github.com/jokob-sk/NetAlertX/issues/138\" target=\"_blank\"> estou a trabalhar em</a>.",
|
||||
"Maintenance_database_backup": "Backups DB",
|
||||
"Maintenance_database_backup_found": "foram encontrados backups",
|
||||
"Maintenance_database_backup_total": "uso total do disco",
|
||||
"Maintenance_database_lastmod": "Última modificação",
|
||||
"Maintenance_database_path": "Caminho da base de dados",
|
||||
"Maintenance_database_rows": "Tabela (linhas)",
|
||||
"Maintenance_database_size": "Tamanho da base de dados",
|
||||
"Maintenance_lang_selector_apply": "Aplicar",
|
||||
"Maintenance_lang_selector_empty": "",
|
||||
"Maintenance_lang_selector_lable": "",
|
||||
"Maintenance_lang_selector_text": "A mudança ocorre no lado do cliente, por isso afeta apenas o navegador atual.",
|
||||
"Maintenance_new_version": "Uma nova versão está disponível. Confira as <a href=\"https://github.com/jokob-sk/NetAlertX/releases\" target=\"_blank\">notas de lançamento</a>.",
|
||||
"Maintenance_themeselector_apply": "Aplicar",
|
||||
"Maintenance_themeselector_empty": "Escolha uma Skin",
|
||||
"Maintenance_themeselector_lable": "Selecionar Skin",
|
||||
"Maintenance_themeselector_text": "A mudança ocorre no lado do servidor, por isso afeta todos os dispositivos em uso.",
|
||||
"Maintenance_version": "Atualizações de apps",
|
||||
"NETWORK_DEVICE_TYPES_description": "",
|
||||
"NETWORK_DEVICE_TYPES_name": "Tipos de dispositivo de rede",
|
||||
"Navigation_About": "Sobre a",
|
||||
"Navigation_AppEvents": "",
|
||||
"Navigation_Devices": "Dispositivos",
|
||||
"Navigation_Donations": "Doações",
|
||||
"Navigation_Events": "Eventos",
|
||||
"Navigation_Integrations": "Integrações",
|
||||
"Navigation_Maintenance": "Manutenção",
|
||||
"Navigation_Monitoring": "Acompanhamento",
|
||||
"Navigation_Network": "Rede",
|
||||
"Navigation_Notifications": "Notificações",
|
||||
"Navigation_Plugins": "Plugins",
|
||||
"Navigation_Presence": "",
|
||||
"Navigation_Report": "",
|
||||
"Navigation_Settings": "",
|
||||
"Navigation_SystemInfo": "",
|
||||
"Navigation_Workflows": "",
|
||||
"Network_Assign": "",
|
||||
"Network_Cant_Assign": "",
|
||||
"Network_Cant_Assign_No_Node_Selected": "",
|
||||
"Network_Configuration_Error": "",
|
||||
"Network_Connected": "",
|
||||
"Network_Devices": "",
|
||||
"Network_ManageAdd": "",
|
||||
"Network_ManageAdd_Name": "",
|
||||
"Network_ManageAdd_Name_text": "",
|
||||
"Network_ManageAdd_Port": "",
|
||||
"Network_ManageAdd_Port_text": "",
|
||||
"Network_ManageAdd_Submit": "",
|
||||
"Network_ManageAdd_Type": "",
|
||||
"Network_ManageAdd_Type_text": "",
|
||||
"Network_ManageAssign": "",
|
||||
"Network_ManageDel": "",
|
||||
"Network_ManageDel_Name": "",
|
||||
"Network_ManageDel_Name_text": "",
|
||||
"Network_ManageDel_Submit": "",
|
||||
"Network_ManageDevices": "",
|
||||
"Network_ManageEdit": "",
|
||||
"Network_ManageEdit_ID": "",
|
||||
"Network_ManageEdit_ID_text": "",
|
||||
"Network_ManageEdit_Name": "",
|
||||
"Network_ManageEdit_Name_text": "",
|
||||
"Network_ManageEdit_Port": "",
|
||||
"Network_ManageEdit_Port_text": "",
|
||||
"Network_ManageEdit_Submit": "",
|
||||
"Network_ManageEdit_Type": "",
|
||||
"Network_ManageEdit_Type_text": "",
|
||||
"Network_ManageLeaf": "",
|
||||
"Network_ManageUnassign": "",
|
||||
"Network_NoAssignedDevices": "",
|
||||
"Network_NoDevices": "",
|
||||
"Network_Node": "",
|
||||
"Network_Node_Name": "",
|
||||
"Network_Parent": "",
|
||||
"Network_Root": "",
|
||||
"Network_Root_Not_Configured": "",
|
||||
"Network_Root_Unconfigurable": "",
|
||||
"Network_ShowArchived": "",
|
||||
"Network_ShowOffline": "",
|
||||
"Network_Table_Hostname": "",
|
||||
"Network_Table_IP": "",
|
||||
"Network_Table_State": "",
|
||||
"Network_Title": "",
|
||||
"Network_UnassignedDevices": "",
|
||||
"Notifications_All": "",
|
||||
"Notifications_Mark_All_Read": "",
|
||||
"PIALERT_WEB_PASSWORD_description": "",
|
||||
"PIALERT_WEB_PASSWORD_name": "",
|
||||
"PIALERT_WEB_PROTECTION_description": "",
|
||||
"PIALERT_WEB_PROTECTION_name": "",
|
||||
"PLUGINS_KEEP_HIST_description": "",
|
||||
"PLUGINS_KEEP_HIST_name": "",
|
||||
"Plugins_DeleteAll": "",
|
||||
"Plugins_Filters_Mac": "",
|
||||
"Plugins_History": "",
|
||||
"Plugins_Obj_DeleteListed": "",
|
||||
"Plugins_Objects": "",
|
||||
"Plugins_Out_of": "",
|
||||
"Plugins_Unprocessed_Events": "",
|
||||
"Plugins_no_control": "",
|
||||
"Presence_CalHead_day": "",
|
||||
"Presence_CalHead_lang": "",
|
||||
"Presence_CalHead_month": "",
|
||||
"Presence_CalHead_quarter": "",
|
||||
"Presence_CalHead_week": "",
|
||||
"Presence_CalHead_year": "",
|
||||
"Presence_CallHead_Devices": "",
|
||||
"Presence_Key_OnlineNow": "",
|
||||
"Presence_Key_OnlineNow_desc": "",
|
||||
"Presence_Key_OnlinePast": "",
|
||||
"Presence_Key_OnlinePastMiss": "",
|
||||
"Presence_Key_OnlinePastMiss_desc": "",
|
||||
"Presence_Key_OnlinePast_desc": "",
|
||||
"Presence_Loading": "",
|
||||
"Presence_Shortcut_AllDevices": "",
|
||||
"Presence_Shortcut_Archived": "",
|
||||
"Presence_Shortcut_Connected": "",
|
||||
"Presence_Shortcut_Devices": "",
|
||||
"Presence_Shortcut_DownAlerts": "",
|
||||
"Presence_Shortcut_Favorites": "",
|
||||
"Presence_Shortcut_NewDevices": "",
|
||||
"Presence_Title": "",
|
||||
"REFRESH_FQDN_description": "",
|
||||
"REFRESH_FQDN_name": "",
|
||||
"REPORT_DASHBOARD_URL_description": "",
|
||||
"REPORT_DASHBOARD_URL_name": "",
|
||||
"REPORT_ERROR": "",
|
||||
"REPORT_MAIL_description": "",
|
||||
"REPORT_MAIL_name": "",
|
||||
"REPORT_TITLE": "",
|
||||
"RandomMAC_hover": "",
|
||||
"Reports_Sent_Log": "",
|
||||
"SCAN_SUBNETS_description": "",
|
||||
"SCAN_SUBNETS_name": "",
|
||||
"SYSTEM_TITLE": "",
|
||||
"Setting_Override": "",
|
||||
"Setting_Override_Description": "",
|
||||
"Settings_Metadata_Toggle": "",
|
||||
"Settings_Show_Description": "",
|
||||
"Settings_device_Scanners_desync": "",
|
||||
"Settings_device_Scanners_desync_popup": "",
|
||||
"Speedtest_Results": "",
|
||||
"Systeminfo_AvailableIps": "",
|
||||
"Systeminfo_CPU": "",
|
||||
"Systeminfo_CPU_Cores": "",
|
||||
"Systeminfo_CPU_Name": "",
|
||||
"Systeminfo_CPU_Speed": "",
|
||||
"Systeminfo_CPU_Temp": "",
|
||||
"Systeminfo_CPU_Vendor": "",
|
||||
"Systeminfo_Client_Resolution": "",
|
||||
"Systeminfo_Client_User_Agent": "",
|
||||
"Systeminfo_General": "",
|
||||
"Systeminfo_General_Date": "",
|
||||
"Systeminfo_General_Date2": "",
|
||||
"Systeminfo_General_Full_Date": "",
|
||||
"Systeminfo_General_TimeZone": "",
|
||||
"Systeminfo_Memory": "",
|
||||
"Systeminfo_Memory_Total_Memory": "",
|
||||
"Systeminfo_Memory_Usage": "",
|
||||
"Systeminfo_Memory_Usage_Percent": "",
|
||||
"Systeminfo_Motherboard": "",
|
||||
"Systeminfo_Motherboard_BIOS": "",
|
||||
"Systeminfo_Motherboard_BIOS_Date": "",
|
||||
"Systeminfo_Motherboard_BIOS_Vendor": "",
|
||||
"Systeminfo_Motherboard_Manufactured": "",
|
||||
"Systeminfo_Motherboard_Name": "",
|
||||
"Systeminfo_Motherboard_Revision": "",
|
||||
"Systeminfo_Network": "",
|
||||
"Systeminfo_Network_Accept_Encoding": "",
|
||||
"Systeminfo_Network_Accept_Language": "",
|
||||
"Systeminfo_Network_Connection_Port": "",
|
||||
"Systeminfo_Network_HTTP_Host": "",
|
||||
"Systeminfo_Network_HTTP_Referer": "",
|
||||
"Systeminfo_Network_HTTP_Referer_String": "",
|
||||
"Systeminfo_Network_Hardware": "",
|
||||
"Systeminfo_Network_Hardware_Interface_Mask": "",
|
||||
"Systeminfo_Network_Hardware_Interface_Name": "",
|
||||
"Systeminfo_Network_Hardware_Interface_RX": "",
|
||||
"Systeminfo_Network_Hardware_Interface_TX": "",
|
||||
"Systeminfo_Network_IP": "",
|
||||
"Systeminfo_Network_IP_Connection": "",
|
||||
"Systeminfo_Network_IP_Server": "",
|
||||
"Systeminfo_Network_MIME": "",
|
||||
"Systeminfo_Network_Request_Method": "",
|
||||
"Systeminfo_Network_Request_Time": "",
|
||||
"Systeminfo_Network_Request_URI": "",
|
||||
"Systeminfo_Network_Secure_Connection": "",
|
||||
"Systeminfo_Network_Secure_Connection_String": "",
|
||||
"Systeminfo_Network_Server_Name": "",
|
||||
"Systeminfo_Network_Server_Name_String": "",
|
||||
"Systeminfo_Network_Server_Query": "",
|
||||
"Systeminfo_Network_Server_Query_String": "",
|
||||
"Systeminfo_Network_Server_Version": "",
|
||||
"Systeminfo_Services": "",
|
||||
"Systeminfo_Services_Description": "",
|
||||
"Systeminfo_Services_Name": "",
|
||||
"Systeminfo_Storage": "",
|
||||
"Systeminfo_Storage_Device": "",
|
||||
"Systeminfo_Storage_Mount": "",
|
||||
"Systeminfo_Storage_Size": "",
|
||||
"Systeminfo_Storage_Type": "",
|
||||
"Systeminfo_Storage_Usage": "",
|
||||
"Systeminfo_Storage_Usage_Free": "",
|
||||
"Systeminfo_Storage_Usage_Mount": "",
|
||||
"Systeminfo_Storage_Usage_Total": "",
|
||||
"Systeminfo_Storage_Usage_Used": "",
|
||||
"Systeminfo_System": "",
|
||||
"Systeminfo_System_AVG": "",
|
||||
"Systeminfo_System_Architecture": "",
|
||||
"Systeminfo_System_Kernel": "",
|
||||
"Systeminfo_System_OSVersion": "",
|
||||
"Systeminfo_System_Running_Processes": "",
|
||||
"Systeminfo_System_System": "",
|
||||
"Systeminfo_System_Uname": "",
|
||||
"Systeminfo_System_Uptime": "",
|
||||
"Systeminfo_This_Client": "",
|
||||
"Systeminfo_USB_Devices": "",
|
||||
"TICKER_MIGRATE_TO_NETALERTX": "",
|
||||
"TIMEZONE_description": "",
|
||||
"TIMEZONE_name": "",
|
||||
"UI_DEV_SECTIONS_description": "",
|
||||
"UI_DEV_SECTIONS_name": "",
|
||||
"UI_ICONS_description": "",
|
||||
"UI_ICONS_name": "",
|
||||
"UI_LANG_description": "",
|
||||
"UI_LANG_name": "",
|
||||
"UI_MY_DEVICES_description": "",
|
||||
"UI_MY_DEVICES_name": "",
|
||||
"UI_NOT_RANDOM_MAC_description": "",
|
||||
"UI_NOT_RANDOM_MAC_name": "",
|
||||
"UI_PRESENCE_description": "",
|
||||
"UI_PRESENCE_name": "",
|
||||
"UI_REFRESH_description": "",
|
||||
"UI_REFRESH_name": "",
|
||||
"VERSION_description": "",
|
||||
"VERSION_name": "",
|
||||
"WF_Action_Add": "",
|
||||
"WF_Action_field": "",
|
||||
"WF_Action_type": "",
|
||||
"WF_Action_value": "",
|
||||
"WF_Actions": "",
|
||||
"WF_Add": "",
|
||||
"WF_Add_Condition": "",
|
||||
"WF_Add_Group": "",
|
||||
"WF_Condition_field": "",
|
||||
"WF_Condition_operator": "",
|
||||
"WF_Condition_value": "",
|
||||
"WF_Conditions": "",
|
||||
"WF_Conditions_logic_rules": "",
|
||||
"WF_Duplicate": "",
|
||||
"WF_Enabled": "",
|
||||
"WF_Export": "",
|
||||
"WF_Export_Copy": "",
|
||||
"WF_Import": "",
|
||||
"WF_Import_Copy": "",
|
||||
"WF_Name": "",
|
||||
"WF_Remove": "",
|
||||
"WF_Remove_Copy": "",
|
||||
"WF_Save": "",
|
||||
"WF_Trigger": "",
|
||||
"WF_Trigger_event_type": "",
|
||||
"WF_Trigger_type": "",
|
||||
"add_icon_event_tooltip": "",
|
||||
"add_option_event_tooltip": "",
|
||||
"copy_icons_event_tooltip": "",
|
||||
"devices_old": "",
|
||||
"general_event_description": "",
|
||||
"general_event_title": "",
|
||||
"go_to_device_event_tooltip": "",
|
||||
"go_to_node_event_tooltip": "",
|
||||
"new_version_available": "",
|
||||
"report_guid": "",
|
||||
"report_guid_missing": "",
|
||||
"report_select_format": "",
|
||||
"report_time": "",
|
||||
"run_event_tooltip": "",
|
||||
"select_icon_event_tooltip": "",
|
||||
"settings_core_icon": "",
|
||||
"settings_core_label": "",
|
||||
"settings_device_scanners": "",
|
||||
"settings_device_scanners_icon": "",
|
||||
"settings_device_scanners_info": "",
|
||||
"settings_device_scanners_label": "",
|
||||
"settings_enabled": "",
|
||||
"settings_enabled_icon": "",
|
||||
"settings_expand_all": "",
|
||||
"settings_imported": "",
|
||||
"settings_imported_label": "",
|
||||
"settings_missing": "",
|
||||
"settings_missing_block": "",
|
||||
"settings_old": "",
|
||||
"settings_other_scanners": "",
|
||||
"settings_other_scanners_icon": "",
|
||||
"settings_other_scanners_label": "",
|
||||
"settings_publishers": "",
|
||||
"settings_publishers_icon": "",
|
||||
"settings_publishers_info": "",
|
||||
"settings_publishers_label": "",
|
||||
"settings_readonly": "",
|
||||
"settings_saved": "",
|
||||
"settings_system_icon": "",
|
||||
"settings_system_label": "",
|
||||
"settings_update_item_warning": "",
|
||||
"test_event_tooltip": "Guarde as alterações antes de testar as definições."
|
||||
}
|
||||
@@ -107,7 +107,7 @@
|
||||
"DevDetail_Network_Node_hover": "Выберите родительское сетевое устройство, к которому подключено текущее устройство, чтобы заполнить дерево сети.",
|
||||
"DevDetail_Network_Port_hover": "Порт, к которому подключено это устройство на родительском сетевом устройстве. Если оставить пустым, в дереве сети отобразится значок Wi-Fi.",
|
||||
"DevDetail_Nmap_Scans": "Ручные сканеры Nmap",
|
||||
"DevDetail_Nmap_Scans_desc": "Здесь вы можете выполнить сканирование NMAP вручную. Вы также можете запланировать регулярное автоматическое сканирование NMAP с помощью плагина «Службы и порты» (NMAP). Чтобы узнать больше, перейдите в <a href='/settings.php' target='_blank'>Настройки</a>",
|
||||
"DevDetail_Nmap_Scans_desc": "Здесь вы можете выполнить сканирование NMAP вручную. Вы также можете запланировать регулярное автоматическое сканирование NMAP с помощью плагина «Службы и порты» (NMAP). Чтобы узнать больше, перейдите в <a href=\"https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/nmap_scan\" target=\"_blank\">Документацию</a>",
|
||||
"DevDetail_Nmap_buttonDefault": "Сканирование по умолчанию",
|
||||
"DevDetail_Nmap_buttonDefault_text": "Сканирование по умолчанию: Nmap сканирует 1000 верхних портов для каждого запрошенного протокола сканирования. Это перехватывает примерно 93% портов TCP и 49% портов UDP. (около 5 секунд)",
|
||||
"DevDetail_Nmap_buttonDetail": "Детальное сканирование",
|
||||
@@ -301,7 +301,7 @@
|
||||
"Gen_Cancel": "Отмена",
|
||||
"Gen_Change": "Изменить",
|
||||
"Gen_Copy": "Запустить",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_CopyToClipboard": "Копировать в буфер обмена",
|
||||
"Gen_DataUpdatedUITakesTime": "ОК - Обновление UI может занять некоторое время, если сканирование выполняется.",
|
||||
"Gen_Delete": "Удалить",
|
||||
"Gen_DeleteAll": "Удалить все",
|
||||
@@ -309,9 +309,9 @@
|
||||
"Gen_Error": "Ошибка",
|
||||
"Gen_Filter": "Фильтр",
|
||||
"Gen_Generate": "Генерировать",
|
||||
"Gen_InvalidMac": "",
|
||||
"Gen_InvalidMac": "Неверный Mac-адрес.",
|
||||
"Gen_LockedDB": "ОШИБКА - Возможно, база данных заблокирована. Проверьте инструменты разработчика F12 -> Консоль или повторите попытку позже.",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_NetworkMask": "Маска сети",
|
||||
"Gen_Offline": "Оффлайн",
|
||||
"Gen_Okay": "OK",
|
||||
"Gen_Online": "Онлайн",
|
||||
@@ -329,7 +329,7 @@
|
||||
"Gen_SelectIcon": "<i class=\"fa-solid fa-chevron-down fa-fade\"></i>",
|
||||
"Gen_SelectToPreview": "Выберите для предварительного просмотра",
|
||||
"Gen_Selected_Devices": "Выбранные устройства:",
|
||||
"Gen_Subnet": "",
|
||||
"Gen_Subnet": "Подсеть",
|
||||
"Gen_Switch": "Переключить",
|
||||
"Gen_Upd": "Успешное обновление",
|
||||
"Gen_Upd_Fail": "Не удалось обновить",
|
||||
@@ -567,7 +567,7 @@
|
||||
"Presence_Key_OnlineNow_desc": "Устройство, обнаруженное при последнем сканировании как подключенное к сети.",
|
||||
"Presence_Key_OnlinePast": "В прошлом в сети",
|
||||
"Presence_Key_OnlinePastMiss": "В прошлом в сети (несовпадение)",
|
||||
"Presence_Key_OnlinePastMiss_desc": "Устройство в прошлом было подключено к сети, но сейчас находится в автономном режиме, однако стартовый сеанс может отсутствовать или иметь противоречивые данные. (Возможно, это ошибка — отправьте PR, если знаете, как это исправить — здесь я немного запутался в коде)",
|
||||
"Presence_Key_OnlinePastMiss_desc": "Устройство в прошлом было подключено к сети, но сейчас находится в автономном режиме, однако стартовый сеанс может отсутствовать или иметь противоречивые данные.",
|
||||
"Presence_Key_OnlinePast_desc": "Устройство раньше было в сети, но в настоящее время не в сети.",
|
||||
"Presence_Loading": "Загрузка…",
|
||||
"Presence_Shortcut_AllDevices": "Мои устройства",
|
||||
@@ -598,7 +598,7 @@
|
||||
"Settings_device_Scanners_desync": "⚠ Расписания сканера устройств не синхронизированы.",
|
||||
"Settings_device_Scanners_desync_popup": "Расписания сканеров устройств (<code>*_RUN_SCHD</code>) не совпадают. Это приведет к несогласованным онлайн/оффлайн уведомлениям устройства. Если это не предусмотрено, используйте одно и то же расписание для всех включенных <b>🔍Сканеров устройств</b>.",
|
||||
"Speedtest_Results": "Результаты теста скорости",
|
||||
"Systeminfo_AvailableIps": "",
|
||||
"Systeminfo_AvailableIps": "Доступные IP-адреса",
|
||||
"Systeminfo_CPU": "CPU",
|
||||
"Systeminfo_CPU_Cores": "Ядра CPU:",
|
||||
"Systeminfo_CPU_Name": "Имя CPU:",
|
||||
@@ -677,7 +677,7 @@
|
||||
"TIMEZONE_description": "Часовой пояс для корректного отображения статистики. Найдите свой часовой пояс <a target=\"_blank\" href=\"https://en.wikipedia.org/wiki/List_of_tz_database_time_zones\" rel=\"nofollow\">здесь</a>.",
|
||||
"TIMEZONE_name": "Часовой пояс",
|
||||
"UI_DEV_SECTIONS_description": "Выберите, какие элементы интерфейса нужно скрыть на страницах «Устройства».",
|
||||
"UI_DEV_SECTIONS_name": "Скрыть разделы Устройств",
|
||||
"UI_DEV_SECTIONS_name": "Скрыть разделы устройств",
|
||||
"UI_ICONS_description": "Список предопределенных значков. Действуйте осторожно: предпочтительный способ добавления значков описан в разделе <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/ICONS.md\" target=\"_blank\"> документации по значкам</a>. Вы можете добавить HTML-тег SVG в кодировке Base64 или HTML-тег Font-awesome.",
|
||||
"UI_ICONS_name": "Предопределенные значки",
|
||||
"UI_LANG_description": "Выберите предпочтительный язык пользовательского интерфейса. Помогите перевести или предложите языки на онлайн-портале <a href=\"https://hosted.weblate.org/projects/pialert/core/\" target=\"_blank\">Weblate</a>.",
|
||||
@@ -737,7 +737,7 @@
|
||||
"settings_core_label": "Основные",
|
||||
"settings_device_scanners": "Сканеры устройств, используемые для обнаружения устройств, записывающих данные в таблицу базы данных CurrentScan.",
|
||||
"settings_device_scanners_icon": "fa-solid fa-magnifying-glass-plus",
|
||||
"settings_device_scanners_info": "Загрузите еще больше сканеров устройств с помощью параметра <a href=\"/settings.php#LOADED_PLUGINS\">LOADED_PLUGINS</a>",
|
||||
"settings_device_scanners_info": "Загрузите больше сканеров устройств с помощью параметра <a href=\"/settings.php#LOADED_PLUGINS\">LOADED_PLUGINS</a>",
|
||||
"settings_device_scanners_label": "Сканеры устройств",
|
||||
"settings_enabled": "Вкл. настройки",
|
||||
"settings_enabled_icon": "fa-solid fa-toggle-on",
|
||||
|
||||
@@ -301,7 +301,7 @@
|
||||
"Gen_Cancel": "取消",
|
||||
"Gen_Change": "修改",
|
||||
"Gen_Copy": "运行",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_CopyToClipboard": "复制到剪贴板",
|
||||
"Gen_DataUpdatedUITakesTime": "好的 - 如果扫描正在运行,UI 可能需要一段时间才能更新。",
|
||||
"Gen_Delete": "删除",
|
||||
"Gen_DeleteAll": "全部删除",
|
||||
@@ -309,9 +309,9 @@
|
||||
"Gen_Error": "错误",
|
||||
"Gen_Filter": "筛选",
|
||||
"Gen_Generate": "生成",
|
||||
"Gen_InvalidMac": "",
|
||||
"Gen_InvalidMac": "无效的 Mac 地址。",
|
||||
"Gen_LockedDB": "错误 - DB 可能被锁定 - 检查 F12 开发工具 -> 控制台或稍后重试。",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_NetworkMask": "网络掩码",
|
||||
"Gen_Offline": "离线",
|
||||
"Gen_Okay": "Ok",
|
||||
"Gen_Online": "在线",
|
||||
@@ -329,7 +329,7 @@
|
||||
"Gen_SelectIcon": "<i class=\"fa-solid fa-chevron-down fa-fade\"></i>",
|
||||
"Gen_SelectToPreview": "选择预览",
|
||||
"Gen_Selected_Devices": "选定的设备:",
|
||||
"Gen_Subnet": "",
|
||||
"Gen_Subnet": "子网",
|
||||
"Gen_Switch": "交换",
|
||||
"Gen_Upd": "已成功更新",
|
||||
"Gen_Upd_Fail": "更新失败",
|
||||
@@ -498,7 +498,7 @@
|
||||
"Network_Cant_Assign_No_Node_Selected": "无法分配,未选择父节点。",
|
||||
"Network_Configuration_Error": "配置错误",
|
||||
"Network_Connected": "联网设备",
|
||||
"Network_Devices": "",
|
||||
"Network_Devices": "网络设备",
|
||||
"Network_ManageAdd": "添加设备",
|
||||
"Network_ManageAdd_Name": "设备名称",
|
||||
"Network_ManageAdd_Name_text": "名称不包含特殊字符",
|
||||
@@ -567,7 +567,7 @@
|
||||
"Presence_Key_OnlineNow_desc": "该设备在上次扫描中被检测为在线。",
|
||||
"Presence_Key_OnlinePast": "之前在线",
|
||||
"Presence_Key_OnlinePastMiss": "之前在线(不匹配)",
|
||||
"Presence_Key_OnlinePastMiss_desc": "设备曾在线,但目前离线。初始会话可能缺失或存在数据冲突",
|
||||
"Presence_Key_OnlinePastMiss_desc": "设备曾在线,但目前离线,初始会话可能缺失或存在数据冲突。",
|
||||
"Presence_Key_OnlinePast_desc": "设备曾在线,但目前离线。",
|
||||
"Presence_Loading": "加载中…",
|
||||
"Presence_Shortcut_AllDevices": "我的设备",
|
||||
@@ -598,7 +598,7 @@
|
||||
"Settings_device_Scanners_desync": "⚠ 设备扫描计划不同步。",
|
||||
"Settings_device_Scanners_desync_popup": "设备扫描 (<code>*_RUN_SCHD</code>) 的时间表并不相同。这将导致设备在线/离线通知不一致。除非有意为之,否则请对所有启用的 <b>🔍设备扫描</b> 使用相同的时间表。",
|
||||
"Speedtest_Results": "Speedtest 结果",
|
||||
"Systeminfo_AvailableIps": "",
|
||||
"Systeminfo_AvailableIps": "可用 IP",
|
||||
"Systeminfo_CPU": "CPU",
|
||||
"Systeminfo_CPU_Cores": "CPU 核心:",
|
||||
"Systeminfo_CPU_Name": "CPU 名称:",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
require 'php/templates/notification.php';
|
||||
require 'php/templates/modals.php';
|
||||
//------------------------------------------------------------------------------
|
||||
// check if authenticated
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
|
||||
|
||||
@@ -117,6 +117,29 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal form input -->
|
||||
<div class="modal fade" id="modal-form" data-myparam-triggered-by="" style="display: none;">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 id="modal-form-title" class="modal-title"> Modal Title </h4>
|
||||
</div>
|
||||
|
||||
<div id="modal-form-message" class="modal-body"> Modal message </div>
|
||||
|
||||
<div id="modal-form-plc"></div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button id="modal-form-cancel" type="button" class="btn btn-outline pull-left" style="min-width: 80px;" data-dismiss="modal"> Cancel </button>
|
||||
<button id="modal-form-OK" type="button" class="btn btn-outline btn-modal-submit" style="min-width: 80px;" > OK </button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Modal field input -->
|
||||
<div class="modal modal-warning fade" id="modal-field-input" data-myparam-triggered-by="" style="display: none;">
|
||||
@@ -48,7 +48,7 @@ if (!empty($_REQUEST['action']) && $_REQUEST['action'] == 'logout') {
|
||||
|
||||
// Load configuration
|
||||
if (!file_exists(CONFIG_PATH)) {
|
||||
die("Configuration file not found.");
|
||||
die("Configuration file not found in " . $_SERVER['DOCUMENT_ROOT'] . "/../config/app.conf");
|
||||
}
|
||||
$configLines = file(CONFIG_PATH);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
require 'php/templates/header.php';
|
||||
require 'php/templates/notification.php';
|
||||
require 'php/templates/modals.php';
|
||||
?>
|
||||
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
|
||||
@@ -642,7 +642,7 @@
|
||||
{ "elementType": "input", "elementOptions": [], "transformers": [] }
|
||||
]
|
||||
},
|
||||
"default_value": "user@gmail.com",
|
||||
"default_value": "to@email.com",
|
||||
"options": [],
|
||||
"localized": ["name", "description"],
|
||||
"name": [
|
||||
@@ -674,7 +674,31 @@
|
||||
{ "elementType": "input", "elementOptions": [], "transformers": [] }
|
||||
]
|
||||
},
|
||||
"default_value": "NetAlertX <user@gmail.com>",
|
||||
"default_value": "NetAlertX <from@email.com>",
|
||||
"options": [],
|
||||
"localized": ["name", "description"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "From email"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "From which email the notification is sent <code>Name <email></code>. Some SMTP servers need this to be an email only."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "SUBJECT",
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{ "elementType": "input", "elementOptions": [], "transformers": [] }
|
||||
]
|
||||
},
|
||||
"default_value": "NetAlertX Report",
|
||||
"options": [],
|
||||
"localized": ["name", "description"],
|
||||
"name": [
|
||||
@@ -690,7 +714,7 @@
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Notification email subject line. Some SMTP servers need this to be an email."
|
||||
"string": "Notification email subject line."
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
|
||||
@@ -115,8 +115,7 @@ def send(pHTML, pText):
|
||||
|
||||
mylog('debug', [f'[{pluginName}] SMTP_REPORT_TO: {hide_email(str(get_setting_value("SMTP_REPORT_TO")))} SMTP_USER: {hide_email(str(get_setting_value("SMTP_USER")))}'])
|
||||
|
||||
|
||||
subject, from_email, to_email, message_html, message_text = sanitize_email_content('NetAlertX Report', get_setting_value("SMTP_REPORT_FROM"), get_setting_value("SMTP_REPORT_TO"), pHTML, pText)
|
||||
subject, from_email, to_email, message_html, message_text = sanitize_email_content(str(get_setting_value("SMTP_SUBJECT")), get_setting_value("SMTP_REPORT_FROM"), get_setting_value("SMTP_REPORT_TO"), pHTML, pText)
|
||||
|
||||
emails = []
|
||||
|
||||
|
||||
@@ -1,36 +1,32 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import argparse
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
from datetime import datetime
|
||||
import time
|
||||
import re
|
||||
import unicodedata
|
||||
import paho.mqtt.client as mqtt
|
||||
# from paho.mqtt import client as mqtt_client
|
||||
# from paho.mqtt import CallbackAPIVersion as mqtt_CallbackAPIVersion
|
||||
import hashlib
|
||||
import sqlite3
|
||||
from pytz import timezone
|
||||
|
||||
|
||||
# Register NetAlertX directories
|
||||
INSTALL_PATH="/app"
|
||||
INSTALL_PATH = "/app"
|
||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||
|
||||
# NetAlertX modules
|
||||
import conf
|
||||
from const import apiPath, confFileName, logPath
|
||||
from const import confFileName, logPath
|
||||
from plugin_utils import getPluginObject
|
||||
from plugin_helper import Plugin_Objects
|
||||
from logger import mylog, Logger, append_line_to_file
|
||||
from helper import timeNowTZ, get_setting_value, bytes_to_string, sanitize_string, normalize_string
|
||||
from models.notification_instance import NotificationInstance
|
||||
from logger import mylog, Logger
|
||||
from helper import timeNowTZ, get_setting_value, bytes_to_string, \
|
||||
sanitize_string, normalize_string
|
||||
from database import DB, get_device_stats
|
||||
from pytz import timezone
|
||||
|
||||
|
||||
# Make sure the TIMEZONE for logging is correct
|
||||
conf.tz = timezone(get_setting_value('TIMEZONE'))
|
||||
@@ -49,20 +45,19 @@ plugin_objects = Plugin_Objects(RESULT_FILE)
|
||||
md5_hash = hashlib.md5()
|
||||
|
||||
|
||||
|
||||
# globals
|
||||
mqtt_sensors = []
|
||||
mqtt_connected_to_broker = False
|
||||
mqtt_client = None # mqtt client
|
||||
topic_root = get_setting_value('MQTT_topic_root')
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
mylog('verbose', [f'[{pluginName}](publisher) In script'])
|
||||
|
||||
# Check if basic config settings supplied
|
||||
if check_config() == False:
|
||||
mylog('verbose', [f'[{pluginName}] ⚠ ERROR: Publisher notification gateway not set up correctly. Check your {confFileName} {pluginName}_* variables.'])
|
||||
if not check_config():
|
||||
return
|
||||
|
||||
# Create a database connection
|
||||
@@ -70,30 +65,52 @@ def main():
|
||||
db.open()
|
||||
|
||||
mqtt_start(db)
|
||||
mqtt_client.disconnect()
|
||||
|
||||
plugin_objects.write_result_file()
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# -----------------------------------------------------------------------------
|
||||
# MQTT
|
||||
#-------------------------------------------------------------------------------
|
||||
#-------------------------------------------------------------------------------
|
||||
# -----------------------------------------------------------------------------
|
||||
# -----------------------------------------------------------------------------
|
||||
def check_config():
|
||||
if get_setting_value('MQTT_BROKER') == '' or get_setting_value('MQTT_PORT') == '' or get_setting_value('MQTT_USER') == '' or get_setting_value('MQTT_PASSWORD') == '':
|
||||
mylog('verbose', [f'[Check Config] ⚠ ERROR: MQTT service not set up correctly. Check your {confFileName} MQTT_* variables.'])
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
"""
|
||||
Checks whether the MQTT configuration settings are properly set.
|
||||
|
||||
Returns:
|
||||
bool: True if all required MQTT settings
|
||||
('MQTT_BROKER', 'MQTT_PORT', 'MQTT_USER', 'MQTT_PASSWORD')
|
||||
are non-empty;
|
||||
False otherwise. Logs a verbose error message
|
||||
if any setting is missing.
|
||||
"""
|
||||
if get_setting_value('MQTT_BROKER') == '' \
|
||||
or get_setting_value('MQTT_PORT') == '' \
|
||||
or get_setting_value('MQTT_USER') == '' \
|
||||
or get_setting_value('MQTT_PASSWORD') == '':
|
||||
mylog('verbose', [f'[Check Config] ⚠ ERROR: MQTT service not set up \
|
||||
correctly. Check your {confFileName} MQTT_* variables.'])
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Sensor configs are tracking which sensors in NetAlertX exist and if a config has changed
|
||||
# -----------------------------------------------------------------------------
|
||||
# Sensor configs are tracking which sensors in NetAlertX exist
|
||||
# and if a config has changed
|
||||
class sensor_config:
|
||||
def __init__(self, deviceId, deviceName, sensorType, sensorName, icon, mac):
|
||||
def __init__(self,
|
||||
deviceId,
|
||||
deviceName,
|
||||
sensorType,
|
||||
sensorName,
|
||||
icon,
|
||||
mac):
|
||||
"""
|
||||
Initialize the sensor_config object with provided parameters. Sets up sensor configuration
|
||||
and generates necessary MQTT topics and messages based on the sensor type.
|
||||
Initialize the sensor_config object with provided parameters.
|
||||
Sets up sensor configuration and generates necessary MQTT topics
|
||||
and messages based on the sensor type.
|
||||
"""
|
||||
# Assign initial attributes
|
||||
self.deviceId = deviceId
|
||||
@@ -110,20 +127,23 @@ class sensor_config:
|
||||
self.message = {} # Initialize message as an empty dictionary
|
||||
self.unique_id = ''
|
||||
|
||||
# Call helper functions to initialize the message, generate a hash, and handle plugin object
|
||||
# Call helper functions to initialize the message, generate a hash,
|
||||
# and handle plugin object
|
||||
self.initialize_message()
|
||||
self.generate_hash()
|
||||
self.handle_plugin_object()
|
||||
|
||||
def initialize_message(self):
|
||||
"""
|
||||
Initialize the MQTT message payload based on the sensor type. This method handles sensors of types:
|
||||
Initialize the MQTT message payload based on the sensor type.
|
||||
This method handles sensors of types:
|
||||
- 'timestamp'
|
||||
- 'binary_sensor'
|
||||
- 'sensor'
|
||||
- 'device_tracker'
|
||||
"""
|
||||
# Ensure self.message is initialized as a dictionary if not already done
|
||||
# Ensure self.message is initialized as a dictionary
|
||||
# if not already done
|
||||
if not isinstance(self.message, dict):
|
||||
self.message = {}
|
||||
|
||||
@@ -153,7 +173,6 @@ class sensor_config:
|
||||
"icon": f'mdi:{self.icon}'
|
||||
})
|
||||
|
||||
|
||||
# Handle 'device_tracker' sensor type
|
||||
elif self.sensorType == 'device_tracker':
|
||||
self.topic = f'homeassistant/device_tracker/{self.deviceId}/config'
|
||||
@@ -229,25 +248,36 @@ class sensor_config:
|
||||
)
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# -------------------------------------------------------------------------------
|
||||
|
||||
def publish_mqtt(mqtt_client, topic, message):
|
||||
"""
|
||||
Publishes a message to an MQTT topic using the provided MQTT client.
|
||||
If the message is not a string, it is converted to a JSON-formatted string.
|
||||
The function retrieves the desired QoS level from settings and logs the publishing process.
|
||||
If the client is not connected to the broker, the function logs an error and aborts.
|
||||
It attempts to publish the message, retrying until the publish status indicates success.
|
||||
Args:
|
||||
mqtt_client: The MQTT client instance used to publish the message.
|
||||
topic (str): The MQTT topic to publish to.
|
||||
message (Any): The message payload to send. Non-string messages are converted to JSON.
|
||||
Returns:
|
||||
bool: True if the message was published successfully, False if not connected to the broker.
|
||||
"""
|
||||
status = 1
|
||||
|
||||
# convert anything but a simple string to json
|
||||
if not isinstance(message, str):
|
||||
message = json.dumps(message).replace("'",'"')
|
||||
message = json.dumps(message).replace("'", '"')
|
||||
|
||||
qos = get_setting_value('MQTT_QOS')
|
||||
|
||||
mylog('verbose', [f"[{pluginName}] Sending MQTT topic: {topic}"])
|
||||
mylog('verbose', [f"[{pluginName}] Sending MQTT message: {message}"])
|
||||
mylog('debug', [f"[{pluginName}] Sending MQTT topic: {topic}",
|
||||
f"[{pluginName}] Sending MQTT message: {message}"])
|
||||
# mylog('verbose', [f"[{pluginName}] get_setting_value('MQTT_QOS'): {qos}"])
|
||||
|
||||
if mqtt_connected_to_broker == False:
|
||||
|
||||
mylog('verbose', [f"[{pluginName}] ⚠ ERROR: Not connected to broker, aborting."])
|
||||
|
||||
if not mqtt_connected_to_broker:
|
||||
mylog('minimal', [f"[{pluginName}] ⚠ ERROR: Not connected to broker, aborting."])
|
||||
return False
|
||||
|
||||
while status != 0:
|
||||
@@ -268,11 +298,11 @@ def publish_mqtt(mqtt_client, topic, message):
|
||||
# mylog('verbose', [f"[{pluginName}] result: {result}"])
|
||||
|
||||
if status != 0:
|
||||
mylog('verbose', [f"[{pluginName}] Waiting to reconnect to MQTT broker"])
|
||||
mylog('debug', [f"[{pluginName}] Waiting to reconnect to MQTT broker"])
|
||||
time.sleep(0.1)
|
||||
return True
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# ------------------------------------------------------------------------------
|
||||
# Create a generic device for overal stats
|
||||
def create_generic_device(mqtt_client, deviceId, deviceName):
|
||||
|
||||
@@ -284,28 +314,29 @@ def create_generic_device(mqtt_client, deviceId, deviceName):
|
||||
create_sensor(mqtt_client, deviceId, deviceName, 'sensor', 'unknown', 'wifi-alert')
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# ------------------------------------------------------------------------------
|
||||
# Register sensor config on the broker
|
||||
def create_sensor(mqtt_client, deviceId, deviceName, sensorType, sensorName, icon, mac=""):
|
||||
|
||||
global mqtt_sensors
|
||||
|
||||
# check previous configs
|
||||
sensorConfig = sensor_config(deviceId, deviceName, sensorType, sensorName, icon, mac)
|
||||
|
||||
# send if new
|
||||
# Create the HA sensor config if a new device is discovered
|
||||
if sensorConfig.isNew:
|
||||
|
||||
# add the sensor to the global list to keep track of succesfully added sensors
|
||||
if publish_mqtt(mqtt_client, sensorConfig.topic, sensorConfig.message):
|
||||
# hack - delay adding to the queue in case the process is
|
||||
time.sleep(get_setting_value('MQTT_DELAY_SEC')) # restarted and previous publish processes aborted
|
||||
# (it takes ~2s to update a sensor config on the broker)
|
||||
# hack - delay adding to the queue in case the process is
|
||||
# restarted and previous publish processes aborted
|
||||
# (it takes ~2s to update a sensor config on the broker)
|
||||
time.sleep(get_setting_value('MQTT_DELAY_SEC'))
|
||||
mqtt_sensors.append(sensorConfig)
|
||||
|
||||
return sensorConfig
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
def mqtt_create_client():
|
||||
|
||||
# attempt reconnections on failure, ref https://www.emqx.com/en/blog/how-to-use-mqtt-in-python
|
||||
@@ -314,7 +345,7 @@ def mqtt_create_client():
|
||||
MAX_RECONNECT_COUNT = 12
|
||||
MAX_RECONNECT_DELAY = 60
|
||||
|
||||
mytransport = 'tcp' # or 'websockets'
|
||||
mytransport = 'tcp' # or 'websockets'
|
||||
|
||||
def on_disconnect(mqtt_client, userdata, rc):
|
||||
|
||||
@@ -341,7 +372,6 @@ def mqtt_create_client():
|
||||
|
||||
mqtt_connected_to_broker = False
|
||||
|
||||
|
||||
def on_connect(mqtt_client, userdata, flags, rc, properties):
|
||||
|
||||
global mqtt_connected_to_broker
|
||||
@@ -367,10 +397,12 @@ def mqtt_create_client():
|
||||
version = mqtt.MQTTv5
|
||||
|
||||
# we now hardcode the client id into here.
|
||||
# TODO: Add config ffor client id
|
||||
# TODO: Add config for client id (atm, we use a fixed client id,
|
||||
# so only one instance of NetAlertX can connect to the broker at any given time)
|
||||
# If you intend to run multiple instances simultaneously, make sure to set unique client IDs for each instance.
|
||||
mqtt_client = mqtt.Client(
|
||||
client_id='netalertx',
|
||||
callback_api_version = mqtt.CallbackAPIVersion.VERSION2,
|
||||
callback_api_version=mqtt.CallbackAPIVersion.VERSION2,
|
||||
transport=mytransport,
|
||||
protocol=version)
|
||||
mqtt_client.on_connect = on_connect
|
||||
@@ -379,8 +411,8 @@ def mqtt_create_client():
|
||||
if get_setting_value('MQTT_TLS'):
|
||||
mqtt_client.tls_set()
|
||||
|
||||
mqtt_client.username_pw_set(username = get_setting_value('MQTT_USER'), password = get_setting_value('MQTT_PASSWORD'))
|
||||
err_code = mqtt_client.connect(host = get_setting_value('MQTT_BROKER'), port = get_setting_value('MQTT_PORT'))
|
||||
mqtt_client.username_pw_set(username=get_setting_value('MQTT_USER'), password=get_setting_value('MQTT_PASSWORD'))
|
||||
err_code = mqtt_client.connect(host=get_setting_value('MQTT_BROKER'), port=get_setting_value('MQTT_PORT'))
|
||||
if (err_code == mqtt.MQTT_ERR_SUCCESS):
|
||||
# We (prematurely) set the connection state to connected
|
||||
# the callback may be delayed
|
||||
@@ -393,13 +425,13 @@ def mqtt_create_client():
|
||||
|
||||
return mqtt_client
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
def mqtt_start(db):
|
||||
|
||||
global mqtt_client, mqtt_connected_to_broker
|
||||
|
||||
if mqtt_connected_to_broker == False:
|
||||
mqtt_connected_to_broker = True
|
||||
if not mqtt_connected_to_broker:
|
||||
mqtt_client = mqtt_create_client()
|
||||
|
||||
|
||||
@@ -409,7 +441,7 @@ def mqtt_start(db):
|
||||
# General stats
|
||||
|
||||
# Create a generic device for overal stats
|
||||
if get_setting_value('MQTT_SEND_STATS') == True:
|
||||
if get_setting_value('MQTT_SEND_STATS'):
|
||||
# Create a new device representing overall stats
|
||||
create_generic_device(mqtt_client, deviceId, deviceName)
|
||||
|
||||
@@ -429,7 +461,7 @@ def mqtt_start(db):
|
||||
)
|
||||
|
||||
# Generate device-specific MQTT messages if enabled
|
||||
if get_setting_value('MQTT_SEND_DEVICES') == True:
|
||||
if get_setting_value('MQTT_SEND_DEVICES'):
|
||||
|
||||
# Specific devices processing
|
||||
|
||||
@@ -438,9 +470,7 @@ def mqtt_start(db):
|
||||
|
||||
sec_delay = len(devices) * int(get_setting_value('MQTT_DELAY_SEC'))*5
|
||||
|
||||
mylog('verbose', [f"[{pluginName}] Estimated delay: ", (sec_delay), 's ', '(', round(sec_delay/60,1) , 'min)' ])
|
||||
|
||||
debug_index = 0
|
||||
mylog('verbose', [f"[{pluginName}] Estimated delay: ", (sec_delay), 's ', '(', round(sec_delay/60, 1), 'min)'])
|
||||
|
||||
for device in devices:
|
||||
|
||||
@@ -492,7 +522,7 @@ def mqtt_start(db):
|
||||
)
|
||||
|
||||
# handle device_tracker
|
||||
sensorConfig = create_sensor(mqtt_client, deviceId, devDisplayName, 'device_tracker', 'is_home', 'home', device["devMac"])
|
||||
sensorConfig = create_sensor(mqtt_client, deviceId, devDisplayName, 'device_tracker', 'is_home', 'home', device["devMac"])
|
||||
|
||||
# <away|home> are only valid states
|
||||
state = 'away'
|
||||
@@ -505,28 +535,23 @@ def mqtt_start(db):
|
||||
publish_mqtt(mqtt_client, sensorConfig.json_attr_topic, devJson)
|
||||
|
||||
|
||||
|
||||
#===============================================================================
|
||||
# =============================================================================
|
||||
# Home Assistant UTILs
|
||||
#===============================================================================
|
||||
# =============================================================================
|
||||
def to_binary_sensor(input):
|
||||
# In HA a binary sensor returns ON or OFF
|
||||
result = "OFF"
|
||||
"""
|
||||
Converts various input types to a binary sensor state ("ON" or "OFF") for Home Assistant.
|
||||
"""
|
||||
if isinstance(input, (int, float)) and input >= 1:
|
||||
return "ON"
|
||||
elif isinstance(input, bool) and input:
|
||||
return "ON"
|
||||
elif isinstance(input, str) and input == "1":
|
||||
return "ON"
|
||||
elif isinstance(input, bytes) and bytes_to_string(input) == "1":
|
||||
return "ON"
|
||||
return "OFF"
|
||||
|
||||
# bytestring
|
||||
if isinstance(input, str):
|
||||
if input == "1":
|
||||
result = "ON"
|
||||
elif isinstance(input, int):
|
||||
if input == 1:
|
||||
result = "ON"
|
||||
elif isinstance(input, bool):
|
||||
if input == True:
|
||||
result = "ON"
|
||||
elif isinstance(input, bytes):
|
||||
if bytes_to_string(input) == "1":
|
||||
result = "ON"
|
||||
return result
|
||||
|
||||
# -------------------------------------
|
||||
# Convert to format that is interpretable by Home Assistant
|
||||
@@ -537,7 +562,7 @@ def prepTimeStamp(datetime_str):
|
||||
|
||||
# If the parsed datetime is naive (i.e., does not contain timezone info), add UTC timezone
|
||||
if parsed_datetime.tzinfo is None:
|
||||
parsed_datetime = parsed_datetime.replace(tzinfo=conf.tz)
|
||||
parsed_datetime = conf.tz.localize(parsed_datetime)
|
||||
|
||||
except ValueError:
|
||||
mylog('verbose', [f"[{pluginName}] Timestamp conversion failed of string '{datetime_str}'"])
|
||||
@@ -547,9 +572,7 @@ def prepTimeStamp(datetime_str):
|
||||
# Convert to the required format with 'T' between date and time and ensure the timezone is included
|
||||
return parsed_datetime.isoformat() # This will include the timezone offset
|
||||
|
||||
|
||||
# -------------INIT---------------------
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -20,13 +20,13 @@
|
||||
"display_name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "OMADA SDN import"
|
||||
"string": "OMADA SDN import (do not use)"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Plugin to import data from OMADA SDN."
|
||||
"string": "Unmaintained and superseded. Use OMDSDNOPENAPI instead."
|
||||
}
|
||||
],
|
||||
"icon": [
|
||||
|
||||
@@ -4,6 +4,7 @@ from pytz import timezone, all_timezones, UnknownTimeZoneError
|
||||
import sys
|
||||
import re
|
||||
import base64
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
INSTALL_PATH = "/app"
|
||||
@@ -116,6 +117,51 @@ def decodeBase64(inputParamBase64):
|
||||
|
||||
return result
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
def decode_settings_base64(encoded_str, convert_types=True):
|
||||
"""
|
||||
Decodes a base64-encoded JSON list of settings into a dict.
|
||||
|
||||
Each setting entry format:
|
||||
[group, key, type, value]
|
||||
|
||||
Example:
|
||||
[
|
||||
["group", "name", "string", "Home - local"],
|
||||
["group", "base_url", "string", "https://..."],
|
||||
["group", "api_version", "integer", "2"],
|
||||
["group", "verify_ssl", "boolean", "False"]
|
||||
]
|
||||
|
||||
Returns:
|
||||
{
|
||||
"name": "Home - local",
|
||||
"base_url": "https://...",
|
||||
"api_version": 2,
|
||||
"verify_ssl": False
|
||||
}
|
||||
"""
|
||||
decoded_json = base64.b64decode(encoded_str).decode("utf-8")
|
||||
settings_list = json.loads(decoded_json)
|
||||
|
||||
settings_dict = {}
|
||||
for _, key, _type, value in settings_list:
|
||||
if convert_types:
|
||||
_type_lower = _type.lower()
|
||||
if _type_lower == "boolean":
|
||||
settings_dict[key] = value.lower() == "true"
|
||||
elif _type_lower == "integer":
|
||||
settings_dict[key] = int(value)
|
||||
elif _type_lower == "float":
|
||||
settings_dict[key] = float(value)
|
||||
else:
|
||||
settings_dict[key] = value
|
||||
else:
|
||||
settings_dict[key] = value
|
||||
|
||||
return settings_dict
|
||||
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
def normalize_mac(mac):
|
||||
# Split the MAC address by colon (:) or hyphen (-) and convert each part to uppercase
|
||||
|
||||