mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2025-12-07 09:36:05 -08:00
Compare commits
59 Commits
v25.1.19
...
b921144dbb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b921144dbb | ||
|
|
4f2ddccdde | ||
|
|
780b818815 | ||
|
|
5779fd34c5 | ||
|
|
b7a6fe9112 | ||
|
|
906bfd24a4 | ||
|
|
8f48172940 | ||
|
|
736304eb8a | ||
|
|
5fce3c79b0 | ||
|
|
81c1f65816 | ||
|
|
edfaadf682 | ||
|
|
0a51d5fe79 | ||
|
|
893063c695 | ||
|
|
da5cf4a8f1 | ||
|
|
1e5a4e96e4 | ||
|
|
74d7a7853a | ||
|
|
63469007ef | ||
|
|
f5b875e2df | ||
|
|
d18efb2103 | ||
|
|
e2cdce2f39 | ||
|
|
c855d50999 | ||
|
|
9d8b147e40 | ||
|
|
3aa9be7019 | ||
|
|
72b3d5eb6d | ||
|
|
83bc406ed6 | ||
|
|
e6e1c79d6a | ||
|
|
e0616f72fe | ||
|
|
696403ac20 | ||
|
|
c946a5335a | ||
|
|
9610810891 | ||
|
|
2ad7f02255 | ||
|
|
431543ba80 | ||
|
|
09d2e68479 | ||
|
|
896b8b7641 | ||
|
|
8d607aac96 | ||
|
|
c95a371ad9 | ||
|
|
4b3ff048dc | ||
|
|
3267762280 | ||
|
|
885a470585 | ||
|
|
d0f4faca51 | ||
|
|
4443c69d31 | ||
|
|
890e533969 | ||
|
|
d7b9bb447f | ||
|
|
c63f424c7d | ||
|
|
dd1580e536 | ||
|
|
630e4f6327 | ||
|
|
cb8af32553 | ||
|
|
d469a9ded4 | ||
|
|
c8a40920b4 | ||
|
|
b0cd9acb79 | ||
|
|
6129f31a24 | ||
|
|
3eb8f39b5c | ||
|
|
5b1002620b | ||
|
|
e50d757f57 | ||
|
|
5110a3c2f3 | ||
|
|
abf7be5958 | ||
|
|
82708bd5df | ||
|
|
b5dce3f6aa | ||
|
|
5562ae7add |
6
.github/workflows/social_post_on_release.yml
vendored
6
.github/workflows/social_post_on_release.yml
vendored
@@ -12,13 +12,13 @@ jobs:
|
||||
|
||||
# Post to Twitter
|
||||
- name: Post to Twitter
|
||||
uses: gr2m/twitter-together@v2
|
||||
uses: twitter-together/action@v3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TWITTER_API_KEY: ${{ secrets.TWITTER_API_KEY }}
|
||||
TWITTER_API_SECRET: ${{ secrets.TWITTER_API_SECRET }}
|
||||
TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }}
|
||||
TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
|
||||
TWITTER_API_KEY: ${{ secrets.TWITTER_API_KEY }}
|
||||
TWITTER_API_SECRET_KEY: ${{ secrets.TWITTER_API_SECRET_KEY }}
|
||||
with:
|
||||
tweet: |
|
||||
🎉 New release: **${{ github.event.release.name }}** is live! 🚀
|
||||
|
||||
32
Dockerfile
32
Dockerfile
@@ -1,11 +1,11 @@
|
||||
FROM alpine:3.20 AS builder
|
||||
FROM alpine:3.21 AS builder
|
||||
|
||||
ARG INSTALL_DIR=/app
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
# Install build dependencies
|
||||
RUN apk add --no-cache bash python3 python3-dev gcc musl-dev libffi-dev openssl-dev git\
|
||||
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
|
||||
@@ -13,34 +13,21 @@ ENV PATH="/opt/venv/bin:$PATH"
|
||||
|
||||
COPY . ${INSTALL_DIR}/
|
||||
|
||||
|
||||
RUN pip install openwrt-luci-rpc asusrouter asyncio aiohttp graphene flask netifaces tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros git+https://github.com/foreign-sub/aiofreepybox.git \
|
||||
RUN pip install openwrt-luci-rpc asusrouter asyncio aiohttp graphene flask tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros 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 {} \;"
|
||||
|
||||
# Append Iliadbox certificate to aiofreepybox
|
||||
RUN printf "\n-----BEGIN CERTIFICATE-----\n\
|
||||
MIICOjCCAcCgAwIBAgIUI0Tu7zsrBJACQIZgLMJobtbdNn4wCgYIKoZIzj0EAwIw\n\
|
||||
TDELMAkGA1UEBhMCSVQxDjAMBgNVBAgMBUl0YWx5MQ4wDAYDVQQKDAVJbGlhZDEd\n\
|
||||
MBsGA1UEAwwUSWxpYWRib3ggRUNDIFJvb3QgQ0EwHhcNMjAxMTI3MDkzODEzWhcN\n\
|
||||
NDAxMTIyMDkzODEzWjBMMQswCQYDVQQGEwJJVDEOMAwGA1UECAwFSXRhbHkxDjAM\n\
|
||||
BgNVBAoMBUlsaWFkMR0wGwYDVQQDDBRJbGlhZGJveCBFQ0MgUm9vdCBDQTB2MBAG\n\
|
||||
ByqGSM49AgEGBSuBBAAiA2IABMryJyb2loHNAioY8IztN5MI3UgbVHVP/vZwcnre\n\
|
||||
ZvJOyDvE4HJgIti5qmfswlnMzpNbwf/MkT+7HAU8jJoTorRm1wtAnQ9cWD3Ebv79\n\
|
||||
RPwtjjy3Bza3SgdVxmd6fWPUKaNjMGEwHQYDVR0OBBYEFDUij/4lpoJ+kOXRyrcM\n\
|
||||
jf2RPzOqMB8GA1UdIwQYMBaAFDUij/4lpoJ+kOXRyrcMjf2RPzOqMA8GA1UdEwEB\n\
|
||||
/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQC6eUV1\n\
|
||||
pFh4UpJOTc1JToztN4ttnQR6rIzxMZ6mNCe+nhjkohWp24pr7BpUYSbEizYCMAQ6\n\
|
||||
LCiBKV2j7QQGy7N1aBmdur17ZepYzR1YV0eI+Kd978aZggsmhjXENQYVTmm/XA==\n\
|
||||
-----END CERTIFICATE-----\n" >> /opt/venv/lib/python3.12/site-packages/aiofreepybox/freebox_certificates.pem
|
||||
RUN cat ${INSTALL_DIR}/install/freebox_certificate.pem >> /opt/venv/lib/python3.12/site-packages/aiofreepybox/freebox_certificates.pem
|
||||
|
||||
# second stage
|
||||
FROM alpine:3.20 AS runner
|
||||
FROM alpine:3.21 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"
|
||||
@@ -54,11 +41,10 @@ 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 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 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 \
|
||||
&& apk add --no-cache dcron \
|
||||
&& ln -s /usr/bin/awake /usr/bin/wakeonlan \
|
||||
&& bash -c "install -d -m 750 -o nginx -g www-data ${INSTALL_DIR} ${INSTALL_DIR}" \
|
||||
&& rm -f /etc/nginx/http.d/default.conf
|
||||
@@ -66,7 +52,7 @@ RUN apk update --no-cache \
|
||||
COPY --from=builder --chown=nginx:www-data ${INSTALL_DIR}/ ${INSTALL_DIR}/
|
||||
|
||||
# Add crontab file
|
||||
COPY install/crontab /etc/crontabs/root
|
||||
COPY --chmod=600 --chown=root:root install/crontab /etc/crontabs/root
|
||||
|
||||
# Start all required services
|
||||
RUN ${INSTALL_DIR}/dockerfiles/start.sh
|
||||
|
||||
@@ -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 netifaces tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros "
|
||||
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 tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros "
|
||||
|
||||
# Create a buildtimestamp.txt to later check if a new version was released
|
||||
RUN date +%s > ${INSTALL_DIR}/front/buildtimestamp.txt
|
||||
|
||||
@@ -5,8 +5,9 @@ LOG_FILE="${INSTALL_DIR}/log/execution_queue.log"
|
||||
|
||||
# Check if there are any entries with cron_restart_backend
|
||||
if grep -q "cron_restart_backend" "$LOG_FILE"; then
|
||||
# Kill all python processes (restart handled by s6 overlay)
|
||||
pkill -f "python " && echo 'done'
|
||||
# Restart python application using s6
|
||||
s6-svc -r /var/run/s6-rc/servicedirs/netalertx
|
||||
echo 'done'
|
||||
|
||||
# Remove all lines containing cron_restart_backend from the log file
|
||||
sed -i '/cron_restart_backend/d' "$LOG_FILE"
|
||||
|
||||
@@ -62,7 +62,6 @@ services:
|
||||
- ${DEV_LOCATION}/front/workflows.php:/app/front/workflows.php
|
||||
- ${DEV_LOCATION}/front/appEventsCore.php:/app/front/appEventsCore.php
|
||||
- ${DEV_LOCATION}/front/multiEditCore.php:/app/front/multiEditCore.php
|
||||
- ${DEV_LOCATION}/front/donations.php:/app/front/donations.php
|
||||
- ${DEV_LOCATION}/front/plugins:/app/front/plugins
|
||||
# DELETE END anyone trying to use this file: comment out / delete ABOVE lines, they are only for development purposes
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -72,4 +71,5 @@ services:
|
||||
- PORT=${PORT}
|
||||
# ❗ DANGER ZONE BELOW - Setting ALWAYS_FRESH_INSTALL=true will delete the content of the /db & /config folders
|
||||
- ALWAYS_FRESH_INSTALL=${ALWAYS_FRESH_INSTALL}
|
||||
# - LOADED_PLUGINS=["DHCPLSS","PIHOLE","ASUSWRT","FREEBOX"]
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ docker run -d --rm --network=host \
|
||||
-v local_path/config:/app/config \
|
||||
-v local_path/db:/app/db \
|
||||
--mount type=tmpfs,target=/app/api \
|
||||
-e PUID=200 -e PGID=300 \
|
||||
-e TZ=Europe/Berlin \
|
||||
-e PORT=20211 \
|
||||
jokobsk/netalertx:latest
|
||||
@@ -37,15 +38,18 @@ See alternative [docked-compose examples](https://github.com/jokob-sk/NetAlertX/
|
||||
|
||||
### Docker environment variables
|
||||
|
||||
| Variable | Description | Default |
|
||||
| :------------- |:-------------| -----:|
|
||||
| Variable | Description | Example Value |
|
||||
| :------------- |:------------------------| -----:|
|
||||
| `PORT` |Port of the web interface | `20211` |
|
||||
| `PUID` |Application User UID | `102` |
|
||||
| `PGID` |Application User GID | `82` |
|
||||
| `LISTEN_ADDR` |Set the specific IP Address for the listener address for the nginx webserver (web interface). This could be useful when using multiple subnets to hide the web interface from all untrusted networks. | `0.0.0.0` |
|
||||
|`TZ` |Time zone to display stats correctly. Find your time zone [here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) | `Europe/Berlin` |
|
||||
|`APP_CONF_OVERRIDE` | JSON override for settings, e.g. `{"SCAN_SUBNETS":"['192.168.1.0/24 --interface=eth1']","GRAPHQL_PORT":"20212"}` | `N/A` |
|
||||
|`ALWAYS_FRESH_INSTALL` | If `true` will delete the content of the `/db` & `/config` folders. For testing purposes. Can be coupled with [watchtower](https://github.com/containrrr/watchtower) to have an always freshly installed `netalertx`/`netalertx-dev` image. | `N/A` |
|
||||
|`LOADED_PLUGINS` | Default [plugins](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/README.md) to load. Plugins cannot be loaded with `APP_CONF_OVERRIDE`, you need to use this variable instead and then specify the plugins settings with `APP_CONF_OVERRIDE`. | `["PIHOLE","ASUSWRT"]` |
|
||||
|`APP_CONF_OVERRIDE` | JSON override for settings (except `LOADED_PLUGINS`). | `{"SCAN_SUBNETS":"['192.168.1.0/24 --interface=eth1']","GRAPHQL_PORT":"20212"}` |
|
||||
|`ALWAYS_FRESH_INSTALL` | ⚠ If `true` will delete the content of the `/db` & `/config` folders. For testing purposes. Can be coupled with [watchtower](https://github.com/containrrr/watchtower) to have an always freshly installed `netalertx`/`netalertx-dev` image. | `true` |
|
||||
|
||||
> You can override the default GraphQL port setting `GRAPHQL_PORT` (set to `20212`) by using the `APP_CONF_OVERRIDE` env variable.
|
||||
> You can override the default GraphQL port setting `GRAPHQL_PORT` (set to `20212`) by using the `APP_CONF_OVERRIDE` env variable. `LOADED_PLUGINS` and settings in `APP_CONF_OVERRIDE` can be specified via the UI as well.
|
||||
|
||||
### Docker paths
|
||||
|
||||
@@ -68,7 +72,7 @@ See alternative [docked-compose examples](https://github.com/jokob-sk/NetAlertX/
|
||||
- If unavailable, the app generates a default `app.conf` and `app.db` file on the first run.
|
||||
- The preferred way is to manage the configuration via the Settings section in the UI, if UI is inaccessible you can modify [app.conf](https://github.com/jokob-sk/NetAlertX/tree/main/back) in the `/app/config/` folder directly
|
||||
|
||||
### Setting up scanners
|
||||
#### Setting up scanners
|
||||
|
||||
You have to specify which network(s) should be scanned. This is done by entering subnets that are accessible from the host. If you use the default `ARPSCAN` plugin, you have to specify at least one valid subnet and interface in the `SCAN_SUBNETS` setting. See the documentation on [How to set up multiple SUBNETS, VLANs and what are limitations](https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md) for troubleshooting and more advanced scenarios.
|
||||
|
||||
@@ -77,19 +81,18 @@ If you are running PiHole you can synchronize devices directly. Check the [PiHol
|
||||
> [!NOTE]
|
||||
> You can bulk-import devices via the [CSV import method](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICES_BULK_EDITING.md).
|
||||
|
||||
#### 🧭 Community guides
|
||||
#### Community guides
|
||||
|
||||
You can read or watch several [community configuration guides](https://github.com/jokob-sk/NetAlertX/blob/main/docs/COMMUNITY_GUIDES.md) in Chinese, Korean, German, or French.
|
||||
|
||||
> Please note these might be outdated. Rely on official documentation first.
|
||||
|
||||
### **Common issues**
|
||||
#### Common issues
|
||||
|
||||
💡 Before creating a new issue, please check if a similar issue was [already resolved](https://github.com/jokob-sk/NetAlertX/issues?q=is%3Aissue+is%3Aclosed).
|
||||
- Before creating a new issue, please check if a similar issue was [already resolved](https://github.com/jokob-sk/NetAlertX/issues?q=is%3Aissue+is%3Aclosed).
|
||||
- Check also common issues and [debugging tips](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEBUG_TIPS.md).
|
||||
|
||||
⚠ Check also common issues and [debugging tips](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEBUG_TIPS.md).
|
||||
|
||||
## ❤ Support me
|
||||
## 💙 Support me
|
||||
|
||||
| [](https://github.com/sponsors/jokob-sk) | [](https://www.buymeacoffee.com/jokobsk) | [](https://www.patreon.com/user?u=84385063) |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -1,8 +1,36 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
|
||||
echo "---------------------------------------------------------"
|
||||
echo "[INSTALL] Run init.sh"
|
||||
echo "---------------------------------------------------------"
|
||||
echo "---------------------------------------------------------
|
||||
[INSTALL] Run init.sh
|
||||
---------------------------------------------------------"
|
||||
|
||||
DEFAULT_PUID=102
|
||||
DEFAULT_GID=82
|
||||
|
||||
PUID=${PUID:-${DEFAULT_PUID}}
|
||||
PGID=${PGID:-${DEFAULT_GID}}
|
||||
|
||||
echo "[INSTALL] Setting up user UID and GID"
|
||||
|
||||
if ! groupmod -o -g "$PGID" www-data && [ "$PGID" != "$DEFAULT_GID" ] ; then
|
||||
echo "Failed to set user GID to ${PGID}, trying with default GID ${DEFAULT_GID}"
|
||||
groupmod -o -g "$DEFAULT_GID" www-data
|
||||
fi
|
||||
if ! usermod -o -u "$PUID" nginx && [ "$PUID" != "$DEFAULT_PUID" ] ; then
|
||||
echo "Failed to set user UID to ${PUID}, trying with default PUID ${DEFAULT_PUID}"
|
||||
usermod -o -u "$DEFAULT_PUID" nginx
|
||||
fi
|
||||
|
||||
echo "
|
||||
---------------------------------------------------------
|
||||
GID/UID
|
||||
---------------------------------------------------------
|
||||
User UID: $(id -u nginx)
|
||||
User GID: $(getent group www-data | cut -d: -f3)
|
||||
---------------------------------------------------------"
|
||||
|
||||
chown nginx:nginx /run/nginx/ /var/log/nginx/ /var/lib/nginx/ /var/lib/nginx/tmp/
|
||||
chgrp www-data /var/www/localhost/htdocs/
|
||||
|
||||
export INSTALL_DIR=/app # Specify the installation directory here
|
||||
|
||||
@@ -55,7 +83,7 @@ else
|
||||
echo "Config file saved to ${INSTALL_DIR}/config/app_conf_override.json"
|
||||
fi
|
||||
|
||||
# 🔻 FOR BACKWARD COMPATIBILITY - REMOVE AFTER 12/12/2024
|
||||
# 🔻 FOR BACKWARD COMPATIBILITY - REMOVE AFTER 12/12/2025
|
||||
|
||||
# Check if pialert.db exists, then create a symbolic link to app.db
|
||||
if [ -f "${INSTALL_DIR_OLD}/db/${OLD_APP_NAME}.db" ]; then
|
||||
@@ -66,7 +94,7 @@ fi
|
||||
if [ -f "${INSTALL_DIR_OLD}/config/${OLD_APP_NAME}.conf" ]; then
|
||||
ln -s "${INSTALL_DIR_OLD}/config/${OLD_APP_NAME}.conf" "${INSTALL_DIR}/config/${CONF_FILE}"
|
||||
fi
|
||||
# 🔺 FOR BACKWARD COMPATIBILITY - REMOVE AFTER 12/12/2024
|
||||
# 🔺 FOR BACKWARD COMPATIBILITY - REMOVE AFTER 12/12/2025
|
||||
|
||||
# Copy starter .db and .conf if they don't exist
|
||||
cp -na "${INSTALL_DIR}/back/${CONF_FILE}" "${INSTALL_DIR}/config/${CONF_FILE}"
|
||||
@@ -83,6 +111,13 @@ if [ -n "${TZ}" ]; then
|
||||
echo $TZ > /etc/timezone
|
||||
fi
|
||||
|
||||
# if custom variables not set we do not need to do anything
|
||||
if [ -n "${LOADED_PLUGINS}" ]; then
|
||||
FILECONF="${INSTALL_DIR}/config/${CONF_FILE}"
|
||||
echo "[INSTALL] Setup custom LOADED_PLUGINS variable"
|
||||
sed -i "\#^LOADED_PLUGINS=#c\LOADED_PLUGINS=${LOADED_PLUGINS}" "${FILECONF}"
|
||||
fi
|
||||
|
||||
echo "[INSTALL] Setup NGINX"
|
||||
echo "Setting webserver to address ($LISTEN_ADDR) and port ($PORT)"
|
||||
envsubst '$INSTALL_DIR $LISTEN_ADDR $PORT' < "${INSTALL_DIR}/install/netalertx.template.conf" > "${NGINX_CONFIG_FILE}"
|
||||
@@ -112,8 +147,7 @@ touch "${INSTALL_DIR}"/api/user_notifications.json
|
||||
mkdir -p "${INSTALL_DIR}"/log/plugins
|
||||
|
||||
echo "[INSTALL] Fixing permissions after copied starter config & DB"
|
||||
chown -R nginx:www-data "${INSTALL_DIR}"/{config,log,db,api}
|
||||
chown -R nginx:www-data "${INSTALL_DIR}"/api/user_notifications.json
|
||||
chown -R nginx:www-data "${INSTALL_DIR}"
|
||||
|
||||
chmod 750 "${INSTALL_DIR}"/{config,log,db}
|
||||
find "${INSTALL_DIR}"/{config,log,db} -type f -exec chmod 640 {} \;
|
||||
@@ -125,10 +159,6 @@ if [ ! -f "${INSTALL_DIR}/front/buildtimestamp.txt" ]; then
|
||||
chown nginx:www-data "${INSTALL_DIR}/front/buildtimestamp.txt"
|
||||
fi
|
||||
|
||||
# Start crond service in the background
|
||||
echo "[INSTALL] Starting crond service..."
|
||||
crond -f -d 8 > /dev/null 2>&1 &
|
||||
|
||||
echo -e "
|
||||
[ENV] PATH is ${PATH}
|
||||
[ENV] PORT is ${PORT}
|
||||
|
||||
@@ -20,7 +20,14 @@ echo "longrun" > /etc/s6-overlay/s6-rc.d/php-fpm/type
|
||||
echo "longrun" > /etc/s6-overlay/s6-rc.d/nginx/type
|
||||
echo "longrun" > /etc/s6-overlay/s6-rc.d/$APP_NAME/type
|
||||
echo -e "${INSTALL_DIR}/dockerfiles/init.sh" > /etc/s6-overlay/s6-rc.d/SetupOneshot/up
|
||||
echo -e "#!/bin/execlineb -P\n/usr/sbin/crond -f -d 8" > /etc/s6-overlay/s6-rc.d/crond/run
|
||||
echo -e '#!/bin/execlineb -P
|
||||
|
||||
if { echo
|
||||
"
|
||||
[INSTALL] Starting crond service...
|
||||
|
||||
" }' > /etc/s6-overlay/s6-rc.d/crond/run
|
||||
echo -e "/usr/sbin/crond -f" >> /etc/s6-overlay/s6-rc.d/crond/run
|
||||
echo -e "#!/bin/execlineb -P\n/usr/sbin/php-fpm83 -F" > /etc/s6-overlay/s6-rc.d/php-fpm/run
|
||||
echo -e '#!/bin/execlineb -P\nnginx -g "daemon off;"' > /etc/s6-overlay/s6-rc.d/nginx/run
|
||||
echo -e '#!/bin/execlineb -P
|
||||
@@ -39,4 +46,4 @@ touch /etc/s6-overlay/s6-rc.d/nginx/dependencies.d/php-fpm
|
||||
touch /etc/s6-overlay/s6-rc.d/$APP_NAME/dependencies.d/nginx
|
||||
|
||||
# this removes the current file
|
||||
# rm -f $0
|
||||
rm -f $0
|
||||
|
||||
@@ -99,30 +99,3 @@ DEV_LOCATION=/path/to/local/source/code
|
||||
```
|
||||
|
||||
To run the container execute: `sudo docker-compose --env-file /path/to/.env up`
|
||||
|
||||
### Example 4
|
||||
|
||||
Courtesy of [pbek](https://github.com/pbek). The volume `netalertx_db` is used by the db directory. The two config files are mounted directly from a local folder to their places in the config folder. You can backup the `docker-compose.yaml` folder and the docker volumes folder.
|
||||
|
||||
```yaml
|
||||
netalertx:
|
||||
# use the below line if you want to test the latest dev image
|
||||
# image: "jokobsk/netalertx-dev:latest"
|
||||
image: jokobsk/netalertx
|
||||
ports:
|
||||
- "80:20211/tcp"
|
||||
environment:
|
||||
- TZ=Europe/Vienna
|
||||
networks:
|
||||
local:
|
||||
ipv4_address: 192.168.1.2
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- netalertx_db:/app/db
|
||||
- ./netalertx/:/app/config/
|
||||
# (API: OPTION 1) use for performance
|
||||
- type: tmpfs
|
||||
target: /app/api
|
||||
# (API: OPTION 2) use when debugging issues
|
||||
# - local/path/api:/app/api
|
||||
```
|
||||
@@ -5,37 +5,19 @@
|
||||
> ```
|
||||
> docker run -d --rm --network=host \
|
||||
> -e TZ=Europe/Berlin \
|
||||
> -e PUID=200 -e PGID=200 \
|
||||
> -e PORT=20211 \
|
||||
> jokobsk/netalertx:latest
|
||||
> ```
|
||||
NetAlertX runs on an Nginx web server. On Alpine Linux, Nginx operates as the `nginx` user (user ID 101, group ID 82 - `www-data`). Consequently, files accessed or written by the NetAlertX application are owned by `nginx:www-data`.
|
||||
NetAlertX runs on an Nginx web server. On Alpine Linux, Nginx operates as the `nginx` user (if PUID and GID environment variables are not specified, nginx user UID will be set to 102, and its supplementary group `www-data` ID to 82). Consequently, files accessed or written by the NetAlertX application are owned by `nginx:www-data`.
|
||||
|
||||
Upon starting, NetAlertX changes the ownership of files on the host system mapped to `/app/config` and `/app/db` in the container to `nginx:www-data`. This ensures that Nginx can access and write to these files. Since the user in the Docker container is mapped to a user on the host system by ID:GID, the files in `/app/config` and `/app/db` on the host system are owned by a user with the same ID and GID (ID 101 and GID 82). On different systems, this ID:GID may belong to different users (on Debian, the user with ID 82 is `uuidd`), or there may not be a user with ID 82 at all.
|
||||
Upon starting, NetAlertX changes nginx user UID and www-data GID to specified values (or defaults), and the ownership of files on the host system mapped to `/app/config` and `/app/db` in the container to `nginx:www-data`. This ensures that Nginx can access and write to these files. Since the user in the Docker container is mapped to a user on the host system by ID:GID, the files in `/app/config` and `/app/db` on the host system are owned by a user with the same ID and GID (defaults are ID 102 and GID 82). On different systems, this ID:GID may belong to different users, or there may not be a group with ID 82 at all.
|
||||
|
||||
While this generally isn't problematic, it can cause issues for host system users needing to access these files (e.g., backup scripts). If users other than root need access to these files, it is recommended to add those users to the group with GID 82. If that group doesn't exist, it should be created.
|
||||
Option to set specific user UID and GID can be useful for host system users needing to access these files (e.g., backup scripts).
|
||||
|
||||
### Permissions Table for Individual Folders
|
||||
|
||||
| Folder | User | User ID | Group | Group ID | Permissions | Notes |
|
||||
|----------------|--------|---------|-----------|----------|-------------|---------------------------------------------------------------------|
|
||||
| `/app/config` | nginx | 101 | www-data | 82 | rwxr-xr-x | Ensure `nginx` can read/write; other users can read if in `www-data` |
|
||||
| `/app/db` | nginx | 101 | www-data | 82 | rwxr-xr-x | Same as above |
|
||||
|
||||
### Steps to Add Users to Group
|
||||
|
||||
1. **Check if group exists:**
|
||||
```sh
|
||||
getent group www-data
|
||||
```
|
||||
|
||||
2. **Create group if it does not exist:**
|
||||
```sh
|
||||
sudo groupadd -g 82 www-data
|
||||
```
|
||||
|
||||
3. **Add user to group:**
|
||||
```sh
|
||||
sudo usermod -aG www-data <username>
|
||||
```
|
||||
|
||||
Replace `<username>` with the actual username that requires access.
|
||||
| `/app/config` | nginx | PUID (default 102) | www-data | PGID (default 82) | rwxr-xr-x | Ensure `nginx` can read/write; other users can read if in `www-data` |
|
||||
| `/app/db` | nginx | PUID (default 102) | www-data | PGID (default 82) | rwxr-xr-x | Same as above |
|
||||
|
||||
@@ -126,7 +126,7 @@ You can show or hide the UI on the "Plugins" page and "Plugins" tab for a plugin
|
||||
If the `data_source` is set to `script` the `CMD` setting (that you specify in the `settings` array section in the `config.json`) contains an executable Linux command, that usually generates a `last_result.<prefix>.log` file (not required if you don't import any data into the app). The `last_result.<prefix>.log` file needs to be saved in `/api/log/plugins`.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> A lot of the work is taken care of by the [`plugin_helper.py` library](/front/plugins/plugin_helper.py). You don't need to manage the `last_result.<prefix>.log` file if using the helper objects. Check other `script.py` of other plugins for details (The [Undicoverables plugins `script.py` file](/front/plugins/undiscoverables/script.py) is a good example).
|
||||
> A lot of the work is taken care of by the [`plugin_helper.py` library](/front/plugins/plugin_helper.py). You don't need to manage the `last_result.<prefix>.log` file if using the helper objects. Check other `script.py` of other plugins for details.
|
||||
|
||||
The content of the `last_result.<prefix>.log` file needs to contain the columns as defined in the "Column order and values" section above. The order of columns can't be changed. After every scan it should contain only the results from the latest scan/execution.
|
||||
|
||||
@@ -578,7 +578,7 @@ Each element may also have associated events (e.g., running a scan or triggering
|
||||
|
||||
##### Supported settings `function` values
|
||||
|
||||
You can have any `"function": "my_custom_name"` custom name, however, the ones listed below have a specific functionality attached to them. If you use a custom name, then the setting is mostly used as an input parameter for the `params` section.
|
||||
You can have any `"function": "my_custom_name"` custom name, however, the ones listed below have a specific functionality attached to them.
|
||||
|
||||
| Setting | Description |
|
||||
| ------- | ----------- |
|
||||
|
||||
@@ -1245,6 +1245,24 @@ input[readonly] {
|
||||
/* Devices page */
|
||||
/* ----------------------------------------------------------------- */
|
||||
|
||||
|
||||
#columnFilters {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px; /* Add spacing between items */
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
box-sizing: border-box; /* Ensure padding and borders are included in the width */
|
||||
padding: 1em;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.filter-dropdown
|
||||
{
|
||||
width:7em
|
||||
}
|
||||
|
||||
.modal-header .close
|
||||
{
|
||||
display: flex;
|
||||
@@ -1331,8 +1349,8 @@ input[readonly] {
|
||||
top: -6px;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
left: 0px;
|
||||
font-size: large;
|
||||
left: 4px;
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
.drag
|
||||
@@ -1397,6 +1415,7 @@ input[readonly] {
|
||||
{
|
||||
display: none;
|
||||
padding-top: 2em;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------- */
|
||||
@@ -1510,6 +1529,14 @@ input[readonly] {
|
||||
flex-direction:column;
|
||||
justify-content:center;
|
||||
}
|
||||
.networkHelpIcon
|
||||
{
|
||||
padding: 5px;
|
||||
margin-left: 5px;
|
||||
top: 55px;
|
||||
position: absolute;
|
||||
z-index:5;
|
||||
}
|
||||
#networkTree .netNodeText
|
||||
{
|
||||
position: absolute;
|
||||
@@ -1570,7 +1597,7 @@ input[readonly] {
|
||||
|
||||
.spanNetworkTree {
|
||||
display: inline-block;
|
||||
width: 120px;
|
||||
width: 135px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis;
|
||||
@@ -1616,6 +1643,24 @@ input[readonly] {
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
.dev-detail-tab-name
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------- */
|
||||
/* EVENTS page */
|
||||
/* ----------------------------------------------------------------- */
|
||||
|
||||
.eventsPeriodSelectWrap{
|
||||
display: inline;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.eventsPage #tableEventsTitle
|
||||
{
|
||||
float: left ;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------- */
|
||||
@@ -1800,10 +1845,7 @@ input[readonly] {
|
||||
----------------------------------------------------------------------------- */
|
||||
#multiEditPlc
|
||||
{
|
||||
position: fixed;
|
||||
bottom: 50px;
|
||||
right: 0px;
|
||||
z-index: 10;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -61,12 +61,54 @@
|
||||
<!-- <div class="box-transparent"> -->
|
||||
<div id="navDevice" class="nav-tabs-custom">
|
||||
<ul class="nav nav-tabs" style="font-size:16px;">
|
||||
<li> <a id="tabDetails" href="#panDetails" data-toggle="tab"> <?= lang('DevDetail_Tab_Details');?> </a></li>
|
||||
<li> <a id="tabTools" href="#panTools" data-toggle="tab"> <?= lang('DevDetail_Tab_Tools');?> </a></li>
|
||||
<li> <a id="tabSessions" href="#panSessions" data-toggle="tab"> <?= lang('DevDetail_Tab_Sessions');?> </a></li>
|
||||
<li> <a id="tabPresence" href="#panPresence" data-toggle="tab"> <?= lang('DevDetail_Tab_Presence');?> </a></li>
|
||||
<li> <a id="tabEvents" href="#panEvents" data-toggle="tab"> <?= lang('DevDetail_Tab_Events');?> </a></li>
|
||||
<li> <a id="tabPlugins" href="#panPlugins" data-toggle="tab"> <?= lang('DevDetail_Tab_Plugins');?> </a></li>
|
||||
<li>
|
||||
<a id="tabDetails" href="#panDetails" data-toggle="tab">
|
||||
<i class="fa fa-info-circle"></i>
|
||||
<span class="dev-detail-tab-name">
|
||||
<?= lang('DevDetail_Tab_Details');?>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="tabTools" href="#panTools" data-toggle="tab">
|
||||
<i class="fa fa-screwdriver-wrench"></i>
|
||||
<span class="dev-detail-tab-name">
|
||||
<?= lang('DevDetail_Tab_Tools');?>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="tabSessions" href="#panSessions" data-toggle="tab">
|
||||
<i class="fa fa-list-ol"></i>
|
||||
<span class="dev-detail-tab-name">
|
||||
<?= lang('DevDetail_Tab_Sessions');?>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="tabPresence" href="#panPresence" data-toggle="tab">
|
||||
<i class="fa fa-calendar"></i>
|
||||
<span class="dev-detail-tab-name">
|
||||
<?= lang('DevDetail_Tab_Presence');?>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="tabEvents" href="#panEvents" data-toggle="tab">
|
||||
<i class="fa fa-bolt"></i>
|
||||
<span class="dev-detail-tab-name">
|
||||
<?= lang('DevDetail_Tab_Events');?>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="tabPlugins" href="#panPlugins" data-toggle="tab">
|
||||
<i class="fa fa-plug"></i>
|
||||
<span class="dev-detail-tab-name">
|
||||
<?= lang('DevDetail_Tab_Plugins');?>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<div class="btn-group pull-right">
|
||||
<button type="button" class="btn btn-default" style="padding: 10px; min-width: 30px;"
|
||||
|
||||
@@ -28,30 +28,21 @@
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
<div class="content-wrapper">
|
||||
|
||||
<!-- Content header--------------------------------------------------------- -->
|
||||
<section class="content-header">
|
||||
<h1 id="pageTitle">
|
||||
<i class="fa fa-laptop"></i>
|
||||
<?= lang('Device_Title');?>
|
||||
</h1>
|
||||
</section>
|
||||
|
||||
<!-- Main content ---------------------------------------------------------- -->
|
||||
<section class="content">
|
||||
|
||||
<!-- Tile toggle cards ------------------------------------------------------- -->
|
||||
<div class="row" id="TileCards">
|
||||
<div class="row " id="TileCards">
|
||||
<!-- Placeholder ------------------------------------------------------- -->
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Device presence / Activity Chart ------------------------------------------------------- -->
|
||||
|
||||
<div class="row" id="DevicePresence">
|
||||
<div class="col-md-12">
|
||||
<div class="box" id="clients">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title"><?= lang('Device_Shortcut_OnlineChart');?> </h3>
|
||||
<div class="box" id="clients">
|
||||
<div class="box-header ">
|
||||
<h3 class="box-title col-md-12"><?= lang('Device_Shortcut_OnlineChart');?> </h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="chart">
|
||||
@@ -67,6 +58,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Device Filters ------------------------------------------------------- -->
|
||||
<div class="box box-aqua hidden" id="columnFiltersWrap">
|
||||
<div class="box-header ">
|
||||
<h3 class="box-title col-md-12"><?= lang('Devices_Filters');?> </h3>
|
||||
</div>
|
||||
<!-- Placeholder ------------------------------------------------------- -->
|
||||
<div id="columnFilters" ></div>
|
||||
</div>
|
||||
|
||||
<!-- datatable ------------------------------------------------------------- -->
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
@@ -78,6 +78,9 @@
|
||||
<h3 id="tableDevicesTitle" class="box-title text-gray "></h3>
|
||||
</div>
|
||||
<div class="dummyDevice col-md-3 ">
|
||||
<span id="multiEditPlc">
|
||||
<!-- multi edit button placeholder -->
|
||||
</span>
|
||||
<span>
|
||||
<a href="deviceDetails.php?mac=new"><i title="<?= lang('Gen_create_new_device');?>" class="fa fa-square-plus"></i> <?= lang('Gen_create_new_device');?></a>
|
||||
</span>
|
||||
@@ -106,7 +109,7 @@
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
</section>
|
||||
<!-- /.content -->
|
||||
<div id="multiEditPlc" class="col-md-2"></div>
|
||||
|
||||
</div>
|
||||
<!-- /.content-wrapper -->
|
||||
|
||||
@@ -138,6 +141,8 @@ function main () {
|
||||
|
||||
showSpinner();
|
||||
|
||||
initFilters();
|
||||
|
||||
// render tiles
|
||||
getDevicesTotals();
|
||||
|
||||
@@ -324,6 +329,184 @@ function renderInfoboxes(customData) {
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//Render filters if specified
|
||||
let columnFilters = [];
|
||||
|
||||
function initFilters() {
|
||||
// Attempt to fetch data
|
||||
$.ajax({
|
||||
url: '/php/server/query_json.php',
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
data: {
|
||||
file: 'table_devices_filters.json', // Pass the file parameter
|
||||
nocache: Date.now() // Prevent caching with a timestamp
|
||||
},
|
||||
success: function(response) {
|
||||
if (response && response.data) {
|
||||
|
||||
let resultJSON = response.data;
|
||||
|
||||
// Save the result to cache
|
||||
setCache("devicesFilters", JSON.stringify(resultJSON));
|
||||
|
||||
// Get the displayed filters from settings
|
||||
const displayedFilters = createArray(getSetting("UI_columns_filters"));
|
||||
|
||||
// Clear any existing filters in the DOM
|
||||
$('#columnFilters').empty();
|
||||
|
||||
console.log(displayedFilters);
|
||||
|
||||
// Ensure displayedFilters is an array and not empty
|
||||
if (Array.isArray(displayedFilters) && displayedFilters.length > 0) {
|
||||
$('#columnFiltersWrap').removeClass("hidden");
|
||||
|
||||
displayedFilters.forEach(columnHeaderStringKey => {
|
||||
// Get the column name using the mapping function
|
||||
const columnName = getColumnNameFromLangString(columnHeaderStringKey);
|
||||
|
||||
// Ensure columnName is valid before proceeding
|
||||
if (columnName) {
|
||||
// Add the filter to the columnFilters array as [columnName, columnHeaderStringKey]
|
||||
columnFilters.push([columnName, columnHeaderStringKey]);
|
||||
} else {
|
||||
console.warn(`Invalid column header string key: ${columnHeaderStringKey}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Filter resultJSON to include only entries with columnName in columnFilters
|
||||
resultJSON = resultJSON.filter(entry =>
|
||||
columnFilters.some(filter => filter[0] === entry.columnName)
|
||||
);
|
||||
|
||||
// Expand resultJSON to include the columnHeaderStringKey
|
||||
resultJSON.forEach(entry => {
|
||||
// Find the matching columnHeaderStringKey from columnFilters
|
||||
const matchingFilter = columnFilters.find(filter => filter[0] === entry.columnName);
|
||||
|
||||
// Add the columnHeaderStringKey to the entry
|
||||
if (matchingFilter) {
|
||||
entry['columnHeaderStringKey'] = matchingFilter[1];
|
||||
}
|
||||
});
|
||||
|
||||
console.log(resultJSON);
|
||||
|
||||
// Transforming the data
|
||||
const transformed = {
|
||||
filters: []
|
||||
};
|
||||
|
||||
// Group data by columnName
|
||||
resultJSON.forEach(entry => {
|
||||
const existingFilter = transformed.filters.find(filter => filter.column === entry.columnName);
|
||||
|
||||
if (existingFilter) {
|
||||
// Add the unique columnValue to options if not already present
|
||||
if (!existingFilter.options.includes(entry.columnValue)) {
|
||||
existingFilter.options.push(entry.columnValue);
|
||||
}
|
||||
} else {
|
||||
// Create a new filter entry
|
||||
transformed.filters.push({
|
||||
column: entry.columnName,
|
||||
headerKey: entry.columnHeaderStringKey,
|
||||
options: [entry.columnValue]
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Sort options alphabetically for better readability
|
||||
transformed.filters.forEach(filter => {
|
||||
filter.options.sort();
|
||||
});
|
||||
|
||||
// Output the result
|
||||
transformedJson = transformed
|
||||
|
||||
// Process the fetched data
|
||||
renderFilters(transformedJson);
|
||||
} else {
|
||||
console.log("No filters to display.");
|
||||
}
|
||||
} else {
|
||||
console.error("Invalid response format from API");
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error("Failed to fetch devices data 'table_devices_filters.json':", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------
|
||||
// Server side component
|
||||
function renderFilters(customData) {
|
||||
|
||||
// console.log(JSON.stringify(customData));
|
||||
|
||||
// Load filter data from the JSON file
|
||||
$.ajax({
|
||||
url: 'php/components/devices_filters.php', // PHP script URL
|
||||
data: { filterObject: JSON.stringify(customData) }, // Send customData as JSON
|
||||
type: 'POST',
|
||||
dataType: 'html',
|
||||
success: function(response) {
|
||||
// console.log(response);
|
||||
|
||||
$('#columnFilters').html(response); // Replace container content with fetched HTML
|
||||
$('#columnFilters').removeClass('hidden'); // Show the filters container
|
||||
|
||||
// Trigger the draw after select change
|
||||
$('.filter-dropdown').on('change', function() {
|
||||
// Collect filters
|
||||
const columnFilters = collectFilters();
|
||||
|
||||
// Update DataTable with the new filters or search value (if applicable)
|
||||
$('#tableDevices').DataTable().draw();
|
||||
|
||||
// Optionally, apply column filters (if using filters for individual columns)
|
||||
const table = $('#tableDevices').DataTable();
|
||||
table.columnFilters = columnFilters; // Apply your column filters logic
|
||||
table.draw();
|
||||
});
|
||||
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error fetching filters:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
// Function to collect filters
|
||||
function collectFilters() {
|
||||
const columnFilters = [];
|
||||
|
||||
// Loop through each filter group
|
||||
document.querySelectorAll('.filter-group').forEach(filterGroup => {
|
||||
const dropdown = filterGroup.querySelector('.filter-dropdown');
|
||||
|
||||
if (dropdown) {
|
||||
const filterColumn = dropdown.getAttribute('data-column');
|
||||
const filterValue = dropdown.value;
|
||||
|
||||
if (filterValue && filterColumn) {
|
||||
columnFilters.push({
|
||||
filterColumn: filterColumn,
|
||||
filterValue: filterValue
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return columnFilters;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Map column index to column name for GraphQL query
|
||||
function mapColumnIndexToFieldName(index, tableColumnVisible) {
|
||||
@@ -416,8 +599,6 @@ function initializeDatatable (status) {
|
||||
}
|
||||
|
||||
// todo: dynamically filter based on status
|
||||
|
||||
|
||||
var table = $('#tableDevices').DataTable({
|
||||
"serverSide": true,
|
||||
"processing": true,
|
||||
@@ -474,7 +655,13 @@ function initializeDatatable (status) {
|
||||
`;
|
||||
|
||||
console.log(d);
|
||||
|
||||
|
||||
// Handle empty filters
|
||||
let columnFilters = collectFilters();
|
||||
if (columnFilters.length === 0) {
|
||||
columnFilters = [];
|
||||
}
|
||||
|
||||
|
||||
// Prepare query variables for pagination, sorting, and search
|
||||
let query = {
|
||||
@@ -489,7 +676,8 @@ function initializeDatatable (status) {
|
||||
"order": d.order[0].dir.toUpperCase() // Sort direction (ASC/DESC)
|
||||
}] : [], // Default to an empty array if no sorting is defined
|
||||
"search": d.search.value, // Search query
|
||||
"status": deviceStatus
|
||||
"status": deviceStatus,
|
||||
"filters" : columnFilters
|
||||
}
|
||||
|
||||
}
|
||||
@@ -749,9 +937,9 @@ function initializeDatatable (status) {
|
||||
|
||||
// add multi-edit button
|
||||
$('#multiEditPlc').append(
|
||||
`<button type="submit" id="multiEdit" class="btn btn-primary" style="display:none" onclick="multiEditDevices();">
|
||||
<i class="fa fa-pencil pointer" ></i> ${getString("Device_MultiEdit")}
|
||||
</button>`)
|
||||
`<span type="submit" id="multiEdit" class="pointer " style="display:none" onclick="multiEditDevices();">
|
||||
<a href="#"><i class="fa fa-pencil " ></i> ${getString("Device_MultiEdit")} </a>
|
||||
</span>`)
|
||||
|
||||
// Event listener for row selection in DataTable
|
||||
$('#tableDevices').on('click', 'tr', function (e) {
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
<?php
|
||||
require 'php/templates/header.php';
|
||||
?>
|
||||
|
||||
<div id="donationsPage" class="content-wrapper">
|
||||
<!-- Content header--------------------------------------------------------- -->
|
||||
<section class="content-header">
|
||||
<h1 id="pageTitle">
|
||||
<i class="fa fa-heart"></i>
|
||||
</h1>
|
||||
</section>
|
||||
|
||||
<!-- Main content ---------------------------------------------------------- -->
|
||||
<section class="content donations">
|
||||
<div id="donationsText" class="box box-solid"></div>
|
||||
<div class="content-header">
|
||||
<h3 class="box-title " id="donationsPlatforms"></h3>
|
||||
</div>
|
||||
<div class="box box-solid">
|
||||
<div class="box-body">
|
||||
<div class="col-sm-2">
|
||||
<a target="_blank" href="https://github.com/sponsors/jokob-sk">
|
||||
<img alt="Sponsor Me on GitHub" src="https://i.imgur.com/X6p5ACK.png" width="150px">
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<a target="_blank" href="https://www.buymeacoffee.com/jokobsk">
|
||||
<img alt="Buy Me A Coffee" src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" width="117px" height="30px">
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<a target="_blank" href="https://www.patreon.com/user?u=84385063">
|
||||
<img alt="Support me on patreon" src="https://upload.wikimedia.org/wikipedia/commons/thumb/8/82/Patreon_logo_with_wordmark.svg/512px-Patreon_logo_with_wordmark.svg.png" width="117px">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-header">
|
||||
<h3 class="box-title " id="donationsOthers"></h3>
|
||||
</div>
|
||||
<div class="box box-solid">
|
||||
<div class="box-body">
|
||||
<div class="col-sm-12">
|
||||
<ul>
|
||||
<li>Bitcoin: <code>1N8tupjeCK12qRVU2XrV17WvKK7LCawyZM</code></li>
|
||||
<li>Ethereum: <code>0x6e2749Cb42F4411bc98501406BdcD82244e3f9C7</code></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
</section>
|
||||
|
||||
</div> <!-- End of class="content-wrapper" -->
|
||||
|
||||
<script>
|
||||
function init()
|
||||
{
|
||||
$("#donationsText").html(getString("Donations_Text"))
|
||||
$("#pageTitle").append(getString("Donations_Title"))
|
||||
$("#donationsPlatforms").append(getString("Donations_Platforms"))
|
||||
$("#donationsOthers").append(getString("Donations_Others"))
|
||||
}
|
||||
|
||||
init();
|
||||
</script>
|
||||
|
||||
<?php
|
||||
require 'php/templates/footer.php';
|
||||
?>
|
||||
@@ -1,17 +1,3 @@
|
||||
<!--
|
||||
#---------------------------------------------------------------------------------#
|
||||
# NetAlertX #
|
||||
# Open Source Network Guard / WIFI & LAN intrusion detector #
|
||||
# #
|
||||
# events.php - Front module. Events page #
|
||||
#---------------------------------------------------------------------------------#
|
||||
# Puche 2021 pi.alert.application@gmail.com GNU GPLv3 #
|
||||
# jokob-sk 2022 jokob.sk@gmail.com GNU GPLv3 #
|
||||
# leiweibau 2022 https://github.com/leiweibau GNU GPLv3 #
|
||||
# cvc90 2023 https://github.com/cvc90 GNU GPLv3 #
|
||||
#---------------------------------------------------------------------------------#
|
||||
-->
|
||||
|
||||
<?php
|
||||
require 'php/templates/header.php';
|
||||
?>
|
||||
@@ -19,26 +5,7 @@
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
<div class="content-wrapper">
|
||||
|
||||
<!-- Content header--------------------------------------------------------- -->
|
||||
<section class="content-header">
|
||||
<h1 id="pageTitle">
|
||||
<i class="fa fa-bolt"></i>
|
||||
<?= lang('Events_Title');?>
|
||||
</h1>
|
||||
|
||||
<!-- period selector -->
|
||||
<span class="breadcrumb" style="top: 0px;">
|
||||
<select class="form-control" id="period" onchange="javascript: periodChanged();">
|
||||
<option value="1 day"><?= lang('Events_Periodselect_today');?></option>
|
||||
<option value="7 days"><?= lang('Events_Periodselect_LastWeek');?></option>
|
||||
<option value="1 month" selected><?= lang('Events_Periodselect_LastMonth');?></option>
|
||||
<option value="1 year"><?= lang('Events_Periodselect_LastYear');?></option>
|
||||
<option value="100 years"><?= lang('Events_Periodselect_All');?></option>
|
||||
</select>
|
||||
</span>
|
||||
</section>
|
||||
<div class="content-wrapper eventsPage">
|
||||
|
||||
<!-- Main content ---------------------------------------------------------- -->
|
||||
<section class="content">
|
||||
@@ -123,15 +90,31 @@
|
||||
<!-- datatable ------------------------------------------------------------- -->
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
|
||||
<div id="tableEventsBox" class="box">
|
||||
|
||||
<!-- box-header -->
|
||||
<div class="box-header">
|
||||
<h3 id="tableEventsTitle" class="box-title text-gray">Events</h3>
|
||||
<div class="box-header col-xs-12">
|
||||
<h3 id="tableEventsTitle" class="box-title text-gray col-xs-10">Events</h3>
|
||||
<div class="eventsPeriodSelectWrap col-xs-2">
|
||||
<select class="form-control" id="period" onchange="javascript: periodChanged();">
|
||||
<option value="1 day"><?= lang('Events_Periodselect_today');?></option>
|
||||
<option value="7 days"><?= lang('Events_Periodselect_LastWeek');?></option>
|
||||
<option value="1 month" selected><?= lang('Events_Periodselect_LastMonth');?></option>
|
||||
<option value="1 year"><?= lang('Events_Periodselect_LastYear');?></option>
|
||||
<option value="100 years"><?= lang('Events_Periodselect_All');?></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- table -->
|
||||
<div class="box-body table-responsive">
|
||||
|
||||
|
||||
|
||||
<table id="tableEvents" class="table table-bordered table-hover table-striped ">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
<?php
|
||||
require 'php/templates/header.php';
|
||||
require 'php/templates/notification.php';
|
||||
?>
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
<div class="content-wrapper">
|
||||
|
||||
<!-- Content header--------------------------------------------------------- -->
|
||||
<section class="content-header">
|
||||
<?php require 'php/templates/notification.php'; ?>
|
||||
<h1 id="pageTitle">
|
||||
<i class="fa fa-question"></i>
|
||||
<?= lang('HelpFAQ_Title');?>
|
||||
</h1>
|
||||
</section>
|
||||
|
||||
<!-- Main content ---------------------------------------------------------- -->
|
||||
<section class="content">
|
||||
<h4>
|
||||
|
||||
@@ -115,9 +115,9 @@ function cacheSettings()
|
||||
return new Promise((resolve, reject) => {
|
||||
if(!getCache('completedCalls').includes('cacheSettings'))
|
||||
{
|
||||
$.get('/php/server/query_json.php', { file: 'table_settings.json', nocache: Date.now() }, function(resSet) {
|
||||
$.get('php/server/query_json.php', { file: 'table_settings.json', nocache: Date.now() }, function(resSet) {
|
||||
|
||||
$.get('/php/server/query_json.php', { file: 'plugins.json', nocache: Date.now() }, function(resPlug) {
|
||||
$.get('php/server/query_json.php', { file: 'plugins.json', nocache: Date.now() }, function(resPlug) {
|
||||
|
||||
pluginsData = resPlug["data"];
|
||||
settingsData = resSet["data"];
|
||||
@@ -225,7 +225,7 @@ function cacheStrings() {
|
||||
});
|
||||
|
||||
// Fetch strings and translations from plugins
|
||||
$.get('/php/server/query_json.php', { file: 'table_plugins_language_strings.json', nocache: Date.now() })
|
||||
$.get('php/server/query_json.php', { file: 'table_plugins_language_strings.json', nocache: Date.now() })
|
||||
.done((pluginRes) => {
|
||||
const data = pluginRes["data"];
|
||||
|
||||
@@ -737,7 +737,7 @@ function forceLoadUrl(relativeUrl) {
|
||||
// -----------------------------------------------------------------------------
|
||||
function navigateToDeviceWithIp (ip) {
|
||||
|
||||
$.get('/php/server/query_json.php', { file: 'table_devices.json', nocache: Date.now() }, function(res) {
|
||||
$.get('php/server/query_json.php', { file: 'table_devices.json', nocache: Date.now() }, function(res) {
|
||||
|
||||
devices = res["data"];
|
||||
|
||||
@@ -959,7 +959,7 @@ function cacheDevices()
|
||||
|
||||
// if(!getCache('completedCalls').includes('cacheDevices'))
|
||||
// {
|
||||
$.get('/php/server/query_json.php', { file: 'table_devices.json', nocache: Date.now() }, function(data) {
|
||||
$.get('php/server/query_json.php', { file: 'table_devices.json', nocache: Date.now() }, function(data) {
|
||||
|
||||
// console.log(data)
|
||||
|
||||
@@ -1325,7 +1325,7 @@ function clearCache() {
|
||||
// -----------------------------------------------------------------------------
|
||||
// Function to check if cache needs to be refreshed because of setting changes
|
||||
function checkSettingChanges() {
|
||||
$.get('/php/server/query_json.php', { file: 'app_state.json', nocache: Date.now() }, function(appState) {
|
||||
$.get('php/server/query_json.php', { file: 'app_state.json', nocache: Date.now() }, function(appState) {
|
||||
const importedMilliseconds = parseInt(appState["settingsImported"] * 1000);
|
||||
const lastReloaded = parseInt(sessionStorage.getItem(sessionStorageKey + '_time'));
|
||||
|
||||
@@ -1380,7 +1380,7 @@ async function waitForGraphQLServer() {
|
||||
// Returns 1 if running, 0 otherwise
|
||||
async function isGraphQLServerRunning() {
|
||||
try {
|
||||
const response = await $.get('/php/server/query_json.php', { file: 'app_state.json', nocache: Date.now()});
|
||||
const response = await $.get('php/server/query_json.php', { file: 'app_state.json', nocache: Date.now()});
|
||||
console.log("graphQLServerStarted: " + response["graphQLServerStarted"]);
|
||||
setCache("graphQLServerStarted", response["graphQLServerStarted"]);
|
||||
return response["graphQLServerStarted"];
|
||||
|
||||
@@ -231,38 +231,65 @@ function copyToClipboard(buttonElement) {
|
||||
// Simple Sortable Table columns
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Function to handle column sorting when a user clicks on a table header
|
||||
function sortColumn(element) {
|
||||
var th = $(element).closest('th');
|
||||
var table = th.closest('table');
|
||||
var columnIndex = th.index();
|
||||
var ascending = !th.data('asc');
|
||||
var th = $(element).closest('th'); // Get the clicked table header
|
||||
var table = th.closest('table'); // Find the closest table
|
||||
var columnIndex = th.index(); // Get the index of the column
|
||||
var ascending = !th.data('asc'); // Toggle sorting order
|
||||
sortTable(table, columnIndex, ascending);
|
||||
th.data('asc', ascending);
|
||||
th.data('asc', ascending); // Store sorting order
|
||||
}
|
||||
|
||||
// Function to sort the table based on the selected column
|
||||
function sortTable(table, columnIndex, ascending) {
|
||||
var tbody = table.find('tbody');
|
||||
var rows = tbody.find('tr').toArray().sort(comparer(columnIndex));
|
||||
var tbody = table.find('tbody'); // Get the table body
|
||||
var rows = tbody.find('tr').toArray().sort(comparer(columnIndex)); // Convert rows to an array and sort
|
||||
if (!ascending) {
|
||||
rows = rows.reverse();
|
||||
rows = rows.reverse(); // Reverse order if descending
|
||||
}
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
tbody.append(rows[i]);
|
||||
tbody.append(rows[i]); // Append sorted rows back to the table
|
||||
}
|
||||
}
|
||||
|
||||
// Function to compare values in the selected column
|
||||
function comparer(index) {
|
||||
return function(a, b) {
|
||||
return function (a, b) {
|
||||
var valA = getCellValue(a, index);
|
||||
var valB = getCellValue(b, index);
|
||||
return $.isNumeric(valA) && $.isNumeric(valB) ? valA - valB : valA.localeCompare(valB);
|
||||
|
||||
// Check if both values are valid IP addresses, and sort numerically if so
|
||||
if (isIPAddress(valA) && isIPAddress(valB)) {
|
||||
return ipToNum(valA) - ipToNum(valB);
|
||||
}
|
||||
|
||||
// If both values are numbers, sort numerically
|
||||
if ($.isNumeric(valA) && $.isNumeric(valB)) {
|
||||
return valA - valB;
|
||||
}
|
||||
|
||||
// Otherwise, sort as text
|
||||
return valA.localeCompare(valB);
|
||||
};
|
||||
}
|
||||
|
||||
// Function to get the text value from a table cell
|
||||
function getCellValue(row, index) {
|
||||
return $(row).children('td').eq(index).text();
|
||||
return $(row).children('td').eq(index).text().trim(); // Get text from the specified column and trim spaces
|
||||
}
|
||||
|
||||
// Function to check if a string is a valid IPv4 address
|
||||
function isIPAddress(value) {
|
||||
return /^\d{1,3}(\.\d{1,3}){3}$/.test(value); // Regular expression to match IPv4 format
|
||||
}
|
||||
|
||||
// Function to convert an IP address to a numeric value for sorting
|
||||
function ipToNum(ip) {
|
||||
return ip.split('.').reduce((acc, octet) => (acc << 8) + parseInt(octet, 10), 0);
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// handling events
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -508,8 +535,52 @@ function showIconSelection() {
|
||||
|
||||
}
|
||||
|
||||
// "Device_TableHead_Owner",
|
||||
// "Device_TableHead_Type",
|
||||
// "Device_TableHead_Group",
|
||||
// "Device_TableHead_Status",
|
||||
// "Device_TableHead_Location",
|
||||
// "Device_TableHead_Vendor",
|
||||
// "Device_TableHead_SyncHubNodeName",
|
||||
// "Device_TableHead_NetworkSite",
|
||||
// "Device_TableHead_SSID",
|
||||
// "Device_TableHead_SourcePlugin"
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Get teh correct db column code name based on table header title string
|
||||
function getColumnNameFromLangString(headStringKey) {
|
||||
columnNameMap = {
|
||||
"Device_TableHead_Name": "devName",
|
||||
"Device_TableHead_Owner": "devOwner",
|
||||
"Device_TableHead_Type": "devType",
|
||||
"Device_TableHead_Icon": "devIcon",
|
||||
"Device_TableHead_Favorite": "devFavorite",
|
||||
"Device_TableHead_Group": "devGroup",
|
||||
"Device_TableHead_FirstSession": "devFirstConnection",
|
||||
"Device_TableHead_LastSession": "devLastConnection",
|
||||
"Device_TableHead_LastIP": "devLastIP",
|
||||
"Device_TableHead_MAC": "devMac",
|
||||
"Device_TableHead_Status": "devStatus",
|
||||
"Device_TableHead_MAC_full": "devMac",
|
||||
"Device_TableHead_LastIPOrder": "devIpLong",
|
||||
"Device_TableHead_Rowid": "rowid",
|
||||
"Device_TableHead_Parent_MAC": "devParentMAC",
|
||||
"Device_TableHead_Connected_Devices": "devParentChildrenCount",
|
||||
"Device_TableHead_Location": "devLocation",
|
||||
"Device_TableHead_Vendor": "devVendor",
|
||||
"Device_TableHead_Port": "devParentPort",
|
||||
"Device_TableHead_GUID": "devGUID",
|
||||
"Device_TableHead_SyncHubNodeName": "devSyncHubNode",
|
||||
"Device_TableHead_NetworkSite": "devSite",
|
||||
"Device_TableHead_SSID": "devSSID",
|
||||
"Device_TableHead_SourcePlugin": "devSourcePlugin",
|
||||
"Device_TableHead_PresentLastScan": "devPresentLastScan",
|
||||
"Device_TableHead_AlertDown": "devAlertDown",
|
||||
"Device_TableHead_CustomProps": "devCustomProps"
|
||||
};
|
||||
|
||||
return columnNameMap[headStringKey] || "";
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
4178
front/lib/treeviz/bundle.js
Executable file
4178
front/lib/treeviz/bundle.js
Executable file
File diff suppressed because it is too large
Load Diff
1
front/lib/treeviz/bundle.js.map
Executable file
1
front/lib/treeviz/bundle.js.map
Executable file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,47 +1,16 @@
|
||||
<?php
|
||||
#---------------------------------------------------------------------------------#
|
||||
# NetAlertX #
|
||||
# Open Source Network Guard / WIFI & LAN intrusion detector #
|
||||
# #
|
||||
# maintenance.php - Front module. Server side. Maintenance #
|
||||
#---------------------------------------------------------------------------------#
|
||||
# Puche 2021 pi.alert.application@gmail.com GNU GPLv3 #
|
||||
# jokob-sk 2022 jokob.sk@gmail.com GNU GPLv3 #
|
||||
# leiweibau 2022 https://github.com/leiweibau GNU GPLv3 #
|
||||
# cvc90 2023 https://github.com/cvc90 GNU GPLv3 #
|
||||
#---------------------------------------------------------------------------------#
|
||||
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
?>
|
||||
|
||||
|
||||
|
||||
<?php
|
||||
require 'php/templates/header.php';
|
||||
require 'php/templates/notification.php';
|
||||
?>
|
||||
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
|
||||
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
<div class="content-wrapper" id="maintenancePage">
|
||||
|
||||
<!-- Content header--------------------------------------------------------- -->
|
||||
<section class="content-header">
|
||||
<?php require 'php/templates/notification.php'; ?>
|
||||
<h1 id="pageTitle">
|
||||
<i class="fa fa-wrench"></i>
|
||||
<?= lang('Maintenance_Title');?>
|
||||
</h1>
|
||||
</section>
|
||||
|
||||
<!-- Main content ---------------------------------------------------------- -->
|
||||
<section class="content">
|
||||
<!-- Main content ---------------------------------------------------------- -->
|
||||
<section class="content">
|
||||
|
||||
|
||||
<?php
|
||||
<?php
|
||||
|
||||
// Size and last mod of DB ------------------------------------------------------
|
||||
|
||||
@@ -91,10 +60,11 @@ $db->close();
|
||||
<div class="db_info_table_row">
|
||||
<div class="db_info_table_cell" style="min-width: 140px"><?= lang('Maintenance_version');?>
|
||||
<a href="https://github.com/jokob-sk/NetAlertX/blob/main/docs/VERSIONS.md" target="_blank"> <span><i class="fa fa-circle-question"></i></a><span>
|
||||
|
||||
</div>
|
||||
<div class="db_info_table_cell">
|
||||
<div class="version" id="version" data-build-time="<?php echo file_get_contents( "buildtimestamp.txt");?>"><?php echo '<span id="new-version-text" class="myhidden">' .lang('Maintenance_new_version').'</span>'.'<span id="current-version-text" class="myhidden">' .lang('Maintenance_current_version').'</span>';?></div>
|
||||
<div class="version" id="version" data-build-time="<?php echo file_get_contents( "buildtimestamp.txt");?>">
|
||||
<?php echo '<span id="new-version-text" class="myhidden"><i class="fa-solid fa-rocket fa-beat"></i> ' .lang('Maintenance_new_version').'</span>'.'<span id="current-version-text" class="myhidden">' .lang('Maintenance_current_version').'</span>';?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="db_info_table_row">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
require 'php/templates/header.php';
|
||||
require 'php/templates/notification.php';
|
||||
|
||||
// online / offline badges HTML snippets
|
||||
define('badge_online', '<div class="badge bg-green text-white" style="width: 60px;">Online</div>');
|
||||
@@ -15,21 +16,13 @@
|
||||
// show spinning icon
|
||||
showSpinner()
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
<div class="content-wrapper">
|
||||
|
||||
<!-- Content header--------------------------------------------------------- -->
|
||||
<section class="content-header">
|
||||
<?php require 'php/templates/notification.php'; ?>
|
||||
<h1 id="pageTitle">
|
||||
<i class="fa fa-network-wired"></i> <?= lang('Network_Title');?>
|
||||
<span class="helpIconSmallTopRight"> <a target="_blank" href="https://github.com/jokob-sk/NetAlertX/blob/main/docs/NETWORK_TREE.md"><i class="fa fa-circle-question"></i></a><span>
|
||||
</h1>
|
||||
</section>
|
||||
|
||||
<span class="networkHelpIcon"> <a target="_blank" href="https://github.com/jokob-sk/NetAlertX/blob/main/docs/NETWORK_TREE.md"><i class="fa fa-circle-question"></i></a></span>
|
||||
|
||||
<div id="networkTree" class="drag"></div>
|
||||
|
||||
@@ -471,9 +464,8 @@
|
||||
require 'php/templates/footer.php';
|
||||
?>
|
||||
|
||||
|
||||
<script src="lib/treeviz/index.js"></script>
|
||||
<script src="lib/treeviz/require.js"></script>
|
||||
<script src="lib/treeviz/bundle.js"></script>
|
||||
<script src="lib/treeviz/bundle.js.map"></script>
|
||||
|
||||
|
||||
<script defer>
|
||||
@@ -533,7 +525,6 @@
|
||||
|
||||
// Init global variable
|
||||
deviceListGlobal = devicesListnew;
|
||||
|
||||
|
||||
// create tree
|
||||
initTree(getHierarchy());
|
||||
@@ -545,378 +536,414 @@
|
||||
|
||||
|
||||
<script defer>
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tree functionality
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tree functionality
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
var leafNodesCount = 0;
|
||||
var visibleNodesCount = 0;
|
||||
var parentNodesCount = 0;
|
||||
var hiddenMacs = []; // hidden children
|
||||
var hiddenChildren = [];
|
||||
var deviceListGlobal = null;
|
||||
|
||||
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Recursively get children nodes and build a tree
|
||||
function getChildren(node, list, path, visited = [])
|
||||
{
|
||||
var children = [];
|
||||
|
||||
// Check for infinite recursion by seeing if the node has been visited before
|
||||
if (visited.includes(node.mac.toLowerCase())) {
|
||||
console.error("Infinite recursion detected at node:", node.mac);
|
||||
write_notification("[ERROR] ⚠ Infinite recursion detected. You probably have assigned the Internet node to another children node or to itself. Please open a new issue on GitHub and describe how you did it.", 'interrupt')
|
||||
return { error: "Infinite recursion detected", node: node.mac };
|
||||
}
|
||||
|
||||
// Add current node to visited list
|
||||
visited.push(node.mac.toLowerCase());
|
||||
|
||||
// Loop through all items to find children of the current node
|
||||
for (var i in list) {
|
||||
if (list[i].parentMac.toLowerCase() == node.mac.toLowerCase() && !hiddenMacs.includes(list[i].parentMac)) {
|
||||
|
||||
visibleNodesCount++;
|
||||
|
||||
// Process children recursively, passing a copy of the visited list
|
||||
children.push(getChildren(list[i], list, path + ((path == "") ? "" : '|') + list[i].parentMac, visited));
|
||||
}
|
||||
}
|
||||
|
||||
// Track leaf and parent node counts
|
||||
if (children.length == 0) {
|
||||
leafNodesCount++;
|
||||
} else {
|
||||
parentNodesCount++;
|
||||
}
|
||||
|
||||
return {
|
||||
name: node.name,
|
||||
path: path,
|
||||
mac: node.mac,
|
||||
port: node.port,
|
||||
id: node.mac,
|
||||
parentMac: node.parentMac,
|
||||
icon: node.icon,
|
||||
type: node.type,
|
||||
status: node.status,
|
||||
hasChildren: children.length > 0 || hiddenMacs.includes(node.mac),
|
||||
hiddenChildren: hiddenMacs.includes(node.mac),
|
||||
qty: children.length,
|
||||
children: children
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
function getHierarchy()
|
||||
{
|
||||
for(i in deviceListGlobal)
|
||||
{
|
||||
if(deviceListGlobal[i].mac == 'Internet')
|
||||
{
|
||||
return (getChildren(deviceListGlobal[i], deviceListGlobal, ''))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
function getFlatData() {
|
||||
var result = [];
|
||||
var leafNodesCount = 0;
|
||||
var visibleNodesCount = 0;
|
||||
var parentNodesCount = 0;
|
||||
var hiddenMacs = []; // hidden children
|
||||
var hiddenChildren = [];
|
||||
var deviceListGlobal = null;
|
||||
var visibleNodesCount = 0;
|
||||
|
||||
for (let node of deviceListGlobal) {
|
||||
let path = "";
|
||||
let childrenCount = 0;
|
||||
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Recursively get children nodes and build a tree
|
||||
function getChildren(node, list, path, visited = [])
|
||||
{
|
||||
var children = [];
|
||||
|
||||
// Check for infinite recursion by seeing if the node has been visited before
|
||||
if (visited.includes(node.mac.toLowerCase())) {
|
||||
console.error("Infinite recursion detected at node:", node.mac);
|
||||
write_notification("[ERROR] ⚠ Infinite recursion detected. You probably have assigned the Internet node to another children node or to itself. Please open a new issue on GitHub and describe how you did it.", 'interrupt')
|
||||
return { error: "Infinite recursion detected", node: node.mac };
|
||||
}
|
||||
|
||||
// Add current node to visited list
|
||||
visited.push(node.mac.toLowerCase());
|
||||
|
||||
// Loop through all items to find children of the current node
|
||||
for (var i in list) {
|
||||
if (list[i].parentMac.toLowerCase() == node.mac.toLowerCase() && !hiddenMacs.includes(list[i].parentMac)) {
|
||||
|
||||
visibleNodesCount++;
|
||||
|
||||
// Process children recursively, passing a copy of the visited list
|
||||
children.push(getChildren(list[i], list, path + ((path == "") ? "" : '|') + list[i].parentMac, visited));
|
||||
}
|
||||
}
|
||||
|
||||
// Track leaf and parent node counts
|
||||
if (children.length == 0) {
|
||||
leafNodesCount++;
|
||||
} else {
|
||||
parentNodesCount++;
|
||||
}
|
||||
|
||||
return {
|
||||
name: node.name,
|
||||
path: path,
|
||||
mac: node.mac,
|
||||
port: node.port,
|
||||
id: node.mac,
|
||||
parentMac: node.parentMac,
|
||||
icon: node.icon,
|
||||
type: node.type,
|
||||
status: node.status,
|
||||
hasChildren: children.length > 0 || hiddenMacs.includes(node.mac),
|
||||
hiddenChildren: hiddenMacs.includes(node.mac),
|
||||
qty: children.length,
|
||||
children: children
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function getHierarchy()
|
||||
{
|
||||
for(i in deviceListGlobal)
|
||||
{
|
||||
if(deviceListGlobal[i].mac == 'Internet')
|
||||
{
|
||||
return (getChildren(deviceListGlobal[i], deviceListGlobal, ''))
|
||||
break;
|
||||
}
|
||||
// count children of this node
|
||||
for (let nodeTmp of deviceListGlobal) {
|
||||
if (nodeTmp.parentMac === node.mac) {
|
||||
childrenCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
function toggleSubTree(parentMac, treePath)
|
||||
{
|
||||
treePath = treePath.split('|')
|
||||
|
||||
if(!hiddenMacs.includes(parentMac))
|
||||
{
|
||||
hiddenMacs.push(parentMac)
|
||||
}
|
||||
else
|
||||
{
|
||||
removeItemFromArray(hiddenMacs, parentMac)
|
||||
}
|
||||
|
||||
// updatedTree = myHierarchy;
|
||||
updatedTree = getHierarchy()
|
||||
|
||||
myTree.refresh(updatedTree);
|
||||
|
||||
// re-attach any onclick events
|
||||
attachTreeEvents();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
function attachTreeEvents()
|
||||
{
|
||||
// toggle subtree functionality
|
||||
$("div[data-mytreemac]").each(function(){
|
||||
$(this).attr('onclick', 'toggleSubTree("'+$(this).attr('data-mytreemac')+'","'+ $(this).attr('data-mytreepath')+'")')
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Handle network node click - select correct tab in the bottom table
|
||||
function handleNodeClick(event)
|
||||
{
|
||||
// console.log(event.target.offsetParent.offsetParent)
|
||||
|
||||
const targetTabMAC = $(event.target.offsetParent.offsetParent).attr("data-mytreemacmain");
|
||||
|
||||
var targetTab = $(`a[data-mytabmac="${targetTabMAC}"]`);
|
||||
|
||||
// Simulate a click event on the target tab
|
||||
targetTab.click();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
var myTree;
|
||||
var visibleTreeArea = $(window).height()-155;
|
||||
var nodeWidth = 160;
|
||||
var emSize;
|
||||
var nodeHeight;
|
||||
var sizeCoefficient = 1
|
||||
|
||||
function initTree(myHierarchy)
|
||||
{
|
||||
console.log(myHierarchy)
|
||||
|
||||
// calculate the drawing area based on teh tree width and available screen size
|
||||
var treeAreaHeight = visibleTreeArea > 800 ? 800 : visibleTreeArea;
|
||||
let screenWidth = $('.content-header').width();
|
||||
let treeWidth = (nodeWidth + 20) * parentNodesCount;
|
||||
let treeAreaWidth = screenWidth < treeWidth ? treeWidth : screenWidth;
|
||||
|
||||
// init the drawing area size
|
||||
$("#networkTree").attr('style', `height:${treeAreaHeight}px; width:${treeAreaWidth}px`)
|
||||
|
||||
if(myHierarchy.type == "")
|
||||
{
|
||||
showModalOk(getString('Network_Configuration_Error'), getString('Network_Root_Not_Configured'))
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// calculate the font size of the leaf nodes to fit everything into the tree area
|
||||
leafNodesCount == 0 ? 1 : leafNodesCount;
|
||||
emSize = ((treeAreaHeight/(25*leafNodesCount)).toFixed(2));
|
||||
emSize = emSize > 1 ? 1 : emSize;
|
||||
|
||||
// nodeHeight = ((emSize*100*0.30).toFixed(0))
|
||||
nodeHeight = ((emSize*100*0.30).toFixed(0))
|
||||
|
||||
console.log(Treeviz);
|
||||
|
||||
myTree = Treeviz.create({
|
||||
htmlId: "networkTree",
|
||||
renderNode: nodeData => {
|
||||
var fontSize = "font-size:"+emSize+"em;";
|
||||
|
||||
(!emptyArr.includes(nodeData.data.port )) ? port = nodeData.data.port : port = "";
|
||||
|
||||
(port == "" || port == 0 || port == 'None' ) ? portBckgIcon = `<i class="fa fa-wifi"></i>` : portBckgIcon = `<i class="fa fa-ethernet"></i>`;
|
||||
|
||||
portHtml = (port == "" || port == 0 || port == 'None' ) ? "" : port
|
||||
|
||||
// Build HTML for individual nodes in the network diagram
|
||||
deviceIcon = (!emptyArr.includes(nodeData.data.icon )) ?
|
||||
`<div class="netIcon">
|
||||
${atob(nodeData.data.icon)}
|
||||
</div>` : "";
|
||||
devicePort = `<div class="netPort"
|
||||
style="width:${emSize*sizeCoefficient}em;height:${emSize*sizeCoefficient}em">
|
||||
${portHtml}</div>
|
||||
<div class="portBckgIcon"
|
||||
style="margin-left:-${emSize*sizeCoefficient}em;">
|
||||
${portBckgIcon}
|
||||
</div>`;
|
||||
collapseExpandIcon = nodeData.data.hiddenChildren ?
|
||||
"square-plus" : "square-minus";
|
||||
|
||||
// generate +/- icon if node has children nodes
|
||||
collapseExpandHtml = nodeData.data.hasChildren ?
|
||||
`<div class="netCollapse"
|
||||
style="font-size:${emSize*sizeCoefficient}em;top:${1/2*emSize*sizeCoefficient}em"
|
||||
data-mytreepath="${nodeData.data.path}"
|
||||
data-mytreemac="${nodeData.data.mac}">
|
||||
<i class="fa fa-${collapseExpandIcon} pointer"></i>
|
||||
</div>` : "";
|
||||
|
||||
selectedNodeMac = $(".nav-tabs-custom .active a").attr('data-mytabmac')
|
||||
|
||||
highlightedCss = nodeData.data.mac == selectedNodeMac ?
|
||||
" highlightedNode" : "";
|
||||
|
||||
// css indicating online/offline status
|
||||
statusCss = ` netStatus-${nodeData.data.status}`;
|
||||
|
||||
return result = `<div class="box ${nodeData.data.hasChildren ? "pointer":""} ${statusCss} ${highlightedCss}"
|
||||
data-mytreemacmain="${nodeData.data.mac}"
|
||||
style="height:${nodeData.settings.nodeHeight}px;${fontSize}"
|
||||
>
|
||||
<div class="netNodeText">
|
||||
<strong>${devicePort} ${deviceIcon}
|
||||
<span class="spanNetworkTree anonymizeDev" >${nodeData.data.name}</span>
|
||||
</strong>
|
||||
${collapseExpandHtml}
|
||||
</div>
|
||||
</div>`;
|
||||
},
|
||||
|
||||
onNodeClick: nodeData => {
|
||||
console.log(this)
|
||||
},
|
||||
mainAxisNodeSpacing: 'auto',
|
||||
// mainAxisNodeSpacing: 3,
|
||||
secondaryAxisNodeSpacing: 0.3,
|
||||
nodeHeight: nodeHeight.toString(),
|
||||
marginTop: '5',
|
||||
hasZoom: true,
|
||||
hasPan: true,
|
||||
// marginLeft: '15',
|
||||
idKey: "id",
|
||||
hasFlatData: false,
|
||||
linkWidth: (nodeData) => 3,
|
||||
linkColor: (nodeData) => "#ffcc80",
|
||||
onNodeClick: (nodeData) => handleNodeClick(nodeData),
|
||||
relationnalField: "children",
|
||||
});
|
||||
|
||||
|
||||
|
||||
myTree.refresh(myHierarchy);
|
||||
|
||||
// hide spinning icon
|
||||
hideSpinner()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tabs functionality
|
||||
// ---------------------------------------------------------------------------
|
||||
// Register events on tab change
|
||||
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
|
||||
initButtons()
|
||||
|
||||
});
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
function initTab()
|
||||
{
|
||||
key = "activeNetworkTab"
|
||||
|
||||
// default selection
|
||||
selectedTab = "Internet_id"
|
||||
|
||||
// the #target from the url
|
||||
target = getQueryString('mac')
|
||||
|
||||
// update cookie if target specified
|
||||
if(target != "")
|
||||
{
|
||||
setCache(key, target.replaceAll(":","_")+'_id') // _id is added so it doesn't conflict with AdminLTE tab behavior
|
||||
}
|
||||
|
||||
// get the tab id from the cookie (already overridden by the target)
|
||||
if(!emptyArr.includes(getCache(key)))
|
||||
{
|
||||
selectedTab = getCache(key);
|
||||
}
|
||||
|
||||
// Activate panel
|
||||
$('.nav-tabs a[id='+ selectedTab +']').tab('show');
|
||||
|
||||
// When changed save new current tab
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
setCache(key, $(e.target).attr('id'))
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
function initDeviceNamesFromMACs()
|
||||
{
|
||||
$('.mac-to-name').each(function() {
|
||||
var dataMacValue = $(this).attr('my-data-mac');
|
||||
|
||||
if(dataMacValue =="" )
|
||||
{
|
||||
$(this).html(getString("Network_Root"))
|
||||
}
|
||||
else{
|
||||
$(this).html(getNameByMacAddress(dataMacValue));
|
||||
}
|
||||
|
||||
// store parent and leaf node count
|
||||
if (childrenCount === 0) {
|
||||
leafNodesCount++;
|
||||
} else {
|
||||
parentNodesCount++;
|
||||
}
|
||||
|
||||
if (!hiddenMacs.includes(node.parentMac)) {
|
||||
if (!((node.parentMac == "") && node.mac != "Internet")) { // skip leaf nodes without father that are not the root
|
||||
visibleNodesCount++;
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
function initButtons()
|
||||
{
|
||||
|
||||
var currentNodeMac = $(".tab-content .active td[data-mynodemac]").attr('data-mynodemac');
|
||||
|
||||
// change highlighted node in the tree
|
||||
selNode = $("#networkTree .highlightedNode")[0]
|
||||
|
||||
// console.log(selNode)
|
||||
|
||||
if(selNode)
|
||||
{
|
||||
$(selNode).attr('class', $(selNode).attr('class').replace('highlightedNode'))
|
||||
result.push({
|
||||
name: node.name,
|
||||
path: path,
|
||||
mac: node.mac, // Replacing "mac" with "id"
|
||||
parentMac: node.mac == "Internet" ? "" : node.parentMac, // Replacing "parentMac" with "father"
|
||||
port: node.port,
|
||||
icon: node.icon,
|
||||
type: node.type,
|
||||
status: node.status,
|
||||
hasChildren: childrenCount > 0 || hiddenMacs.includes(node.mac),
|
||||
hiddenChildren: hiddenMacs.includes(node.mac),
|
||||
qty: childrenCount,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
newSelNode = $("#networkTree div[data-mytreemacmain='"+currentNodeMac+"']")[0]
|
||||
|
||||
$(newSelNode).attr('class', $(newSelNode).attr('class') + ' highlightedNode')
|
||||
|
||||
|
||||
// init the Assign buttons
|
||||
$('#unassignedDevices button[data-myleafmac]').each(function(){
|
||||
$(this).attr('onclick', `updateLeaf("${$(this).attr('data-myleafmac')}","${currentNodeMac}")`)
|
||||
});
|
||||
|
||||
// init Unassign buttons
|
||||
$('#assignedDevices button[data-myleafmac]').each(function(){
|
||||
$(this).attr('onclick', `updateLeaf("${$(this).attr('data-myleafmac')}","")`)
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
function updateLeaf(leafMac,nodeMac)
|
||||
return result;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
function toggleSubTree(parentMac, treePath)
|
||||
{
|
||||
treePath = treePath.split('|')
|
||||
|
||||
if(!hiddenMacs.includes(parentMac))
|
||||
{
|
||||
console.log(leafMac) // child
|
||||
console.log(nodeMac) // parent
|
||||
console.log(nodeMac != "") // parent
|
||||
|
||||
// prevent the assignment of the Internet root node avoiding recursion when generating the network tree topology
|
||||
if(leafMac.toLowerCase().includes('internet') && nodeMac != "")
|
||||
{
|
||||
showMessage(getString('Network_Cant_Assign'))
|
||||
}
|
||||
else{
|
||||
saveData('updateNetworkLeaf', leafMac, nodeMac);
|
||||
setTimeout("location.reload();", 500); // refresh page
|
||||
}
|
||||
hiddenMacs.push(parentMac)
|
||||
}
|
||||
else
|
||||
{
|
||||
removeItemFromArray(hiddenMacs, parentMac)
|
||||
}
|
||||
|
||||
// init device names where macs are used
|
||||
initDeviceNamesFromMACs();
|
||||
updatedTree = getHierarchy()
|
||||
myTree.refresh(updatedTree);
|
||||
|
||||
// init selected (first) tab
|
||||
initTab();
|
||||
// re-attach any onclick events
|
||||
attachTreeEvents();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
function attachTreeEvents()
|
||||
{
|
||||
// toggle subtree functionality
|
||||
$("div[data-mytreemac]").each(function(){
|
||||
$(this).attr('onclick', 'toggleSubTree("'+$(this).attr('data-mytreemac')+'","'+ $(this).attr('data-mytreepath')+'")')
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Handle network node click - select correct tab in the bottom table
|
||||
function handleNodeClick(nodeData)
|
||||
{
|
||||
const targetTabMAC = nodeData.data.mac;
|
||||
|
||||
var targetTab = $(`a[data-mytabmac="${targetTabMAC}"]`);
|
||||
|
||||
// Simulate a click event on the target tab
|
||||
targetTab.click();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
var myTree;
|
||||
var visibleTreeArea = $(window).height()-155;
|
||||
var nodeWidth = 120;
|
||||
var emSize;
|
||||
var nodeHeight;
|
||||
var sizeCoefficient = 1.4
|
||||
|
||||
function initTree(myHierarchy)
|
||||
{
|
||||
// calculate the drawing area based on teh tree width and available screen size
|
||||
var treeAreaHeight = visibleTreeArea > 800 ? 800 : visibleTreeArea;
|
||||
let screenWidth = $('.content-header').width();
|
||||
let treeWidth = (nodeWidth + 20) * parentNodesCount;
|
||||
let treeAreaWidth = screenWidth < treeWidth ? treeWidth : screenWidth;
|
||||
|
||||
// init the drawing area size
|
||||
$("#networkTree").attr('style', `height:${treeAreaHeight}px; width:${treeAreaWidth}px`)
|
||||
|
||||
if(myHierarchy.type == "")
|
||||
{
|
||||
showModalOk(getString('Network_Configuration_Error'), getString('Network_Root_Not_Configured'))
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// calculate the font size of the leaf nodes to fit everything into the tree area
|
||||
leafNodesCount == 0 ? 1 : leafNodesCount;
|
||||
emSize = ((treeAreaHeight/(25*leafNodesCount)).toFixed(2));
|
||||
emSize = emSize > 1 ? 1 : emSize;
|
||||
|
||||
// nodeHeight = ((emSize*100*0.30).toFixed(0))
|
||||
nodeHeight = ((emSize*100*0.30).toFixed(0))
|
||||
|
||||
console.log(Treeviz);
|
||||
|
||||
myTree = Treeviz.create({
|
||||
htmlId: "networkTree",
|
||||
renderNode: nodeData => {
|
||||
|
||||
var fontSize = "font-size:"+emSize+"em;";
|
||||
|
||||
(!emptyArr.includes(nodeData.data.port )) ? port = nodeData.data.port : port = "";
|
||||
|
||||
(port == "" || port == 0 || port == 'None' ) ? portBckgIcon = `<i class="fa fa-wifi"></i>` : portBckgIcon = `<i class="fa fa-ethernet"></i>`;
|
||||
|
||||
portHtml = (port == "" || port == 0 || port == 'None' ) ? "" : port;
|
||||
|
||||
// Build HTML for individual nodes in the network diagram
|
||||
deviceIcon = (!emptyArr.includes(nodeData.data.icon )) ?
|
||||
`<div class="netIcon">
|
||||
${atob(nodeData.data.icon)}
|
||||
</div>` : "";
|
||||
devicePort = `<div class="netPort"
|
||||
style="width:${emSize*sizeCoefficient}em;height:${emSize*sizeCoefficient}em">
|
||||
${portHtml}</div>
|
||||
<div class="portBckgIcon"
|
||||
style="margin-left:-${emSize*sizeCoefficient}em;">
|
||||
${portBckgIcon}
|
||||
</div>`;
|
||||
collapseExpandIcon = nodeData.data.hiddenChildren ?
|
||||
"square-plus" : "square-minus";
|
||||
|
||||
// generate +/- icon if node has children nodes
|
||||
collapseExpandHtml = nodeData.data.hasChildren ?
|
||||
`<div class="netCollapse"
|
||||
style="font-size:${emSize*sizeCoefficient}em;top:${emSize/6}em"
|
||||
data-mytreepath="${nodeData.data.path}"
|
||||
data-mytreemac="${nodeData.data.mac}">
|
||||
<i class="fa fa-${collapseExpandIcon} pointer"></i>
|
||||
</div>` : "";
|
||||
|
||||
selectedNodeMac = $(".nav-tabs-custom .active a").attr('data-mytabmac')
|
||||
|
||||
highlightedCss = nodeData.data.mac == selectedNodeMac ?
|
||||
" highlightedNode" : "";
|
||||
|
||||
// css indicating online/offline status
|
||||
statusCss = ` netStatus-${nodeData.data.status}`;
|
||||
|
||||
return result = `<div class="box ${nodeData.data.hasChildren ? "pointer":""} ${statusCss} ${highlightedCss}"
|
||||
data-mytreemacmain="${nodeData.data.mac}"
|
||||
style="height:${nodeData.settings.nodeHeight}px;${fontSize}"
|
||||
>
|
||||
<div class="netNodeText">
|
||||
<strong>${devicePort} ${deviceIcon}
|
||||
<span class="spanNetworkTree anonymizeDev" >${nodeData.data.name}</span>
|
||||
</strong>
|
||||
${collapseExpandHtml}
|
||||
</div>
|
||||
</div>`;
|
||||
},
|
||||
mainAxisNodeSpacing: 'auto',
|
||||
secondaryAxisNodeSpacing: 0.3,
|
||||
nodeHeight: nodeHeight.toString(),
|
||||
marginTop: '5',
|
||||
isHorizontal : true,
|
||||
hasZoom: true,
|
||||
hasPan: true,
|
||||
marginLeft: '15',
|
||||
idKey: "mac",
|
||||
hasFlatData: false,
|
||||
relationnalField: "children",
|
||||
linkWidth: (nodeData) => 3,
|
||||
linkColor: (nodeData) => "#ffcc80",
|
||||
onNodeClick: (nodeData) => handleNodeClick(nodeData),
|
||||
});
|
||||
|
||||
console.log(deviceListGlobal);
|
||||
myTree.refresh(myHierarchy);
|
||||
|
||||
// hide spinning icon
|
||||
hideSpinner()
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tabs functionality
|
||||
// ---------------------------------------------------------------------------
|
||||
// Register events on tab change
|
||||
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
|
||||
// init Assign/Unassign buttons
|
||||
initButtons()
|
||||
|
||||
});
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
function initTab()
|
||||
{
|
||||
key = "activeNetworkTab"
|
||||
|
||||
// default selection
|
||||
selectedTab = "Internet_id"
|
||||
|
||||
// the #target from the url
|
||||
target = getQueryString('mac')
|
||||
|
||||
// update cookie if target specified
|
||||
if(target != "")
|
||||
{
|
||||
setCache(key, target.replaceAll(":","_")+'_id') // _id is added so it doesn't conflict with AdminLTE tab behavior
|
||||
}
|
||||
|
||||
// get the tab id from the cookie (already overridden by the target)
|
||||
if(!emptyArr.includes(getCache(key)))
|
||||
{
|
||||
selectedTab = getCache(key);
|
||||
}
|
||||
|
||||
// Activate panel
|
||||
$('.nav-tabs a[id='+ selectedTab +']').tab('show');
|
||||
|
||||
// When changed save new current tab
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
setCache(key, $(e.target).attr('id'))
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
function initDeviceNamesFromMACs()
|
||||
{
|
||||
$('.mac-to-name').each(function() {
|
||||
var dataMacValue = $(this).attr('my-data-mac');
|
||||
|
||||
if(dataMacValue =="" )
|
||||
{
|
||||
$(this).html(getString("Network_Root"))
|
||||
}
|
||||
else{
|
||||
$(this).html(getNameByMacAddress(dataMacValue));
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
function initButtons()
|
||||
{
|
||||
|
||||
var currentNodeMac = $(".tab-content .active td[data-mynodemac]").attr('data-mynodemac');
|
||||
|
||||
// change highlighted node in the tree
|
||||
selNode = $("#networkTree .highlightedNode")[0]
|
||||
|
||||
// console.log(selNode)
|
||||
|
||||
if(selNode)
|
||||
{
|
||||
$(selNode).attr('class', $(selNode).attr('class').replace('highlightedNode'))
|
||||
}
|
||||
|
||||
newSelNode = $("#networkTree div[data-mytreemacmain='"+currentNodeMac+"']")[0]
|
||||
|
||||
$(newSelNode).attr('class', $(newSelNode).attr('class') + ' highlightedNode')
|
||||
|
||||
|
||||
// init the Assign buttons
|
||||
$('#unassignedDevices button[data-myleafmac]').each(function(){
|
||||
$(this).attr('onclick', `updateLeaf("${$(this).attr('data-myleafmac')}","${currentNodeMac}")`)
|
||||
});
|
||||
|
||||
// init Unassign buttons
|
||||
$('#assignedDevices button[data-myleafmac]').each(function(){
|
||||
$(this).attr('onclick', `updateLeaf("${$(this).attr('data-myleafmac')}","")`)
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
function updateLeaf(leafMac,nodeMac)
|
||||
{
|
||||
console.log(leafMac) // child
|
||||
console.log(nodeMac) // parent
|
||||
console.log(nodeMac != "") // parent
|
||||
|
||||
// prevent the assignment of the Internet root node avoiding recursion when generating the network tree topology
|
||||
if(leafMac.toLowerCase().includes('internet') && nodeMac != "")
|
||||
{
|
||||
showMessage(getString('Network_Cant_Assign'))
|
||||
}
|
||||
else{
|
||||
saveData('updateNetworkLeaf', leafMac, nodeMac);
|
||||
setTimeout("location.reload();", 500); // refresh page
|
||||
}
|
||||
}
|
||||
|
||||
// init device names where macs are used
|
||||
initDeviceNamesFromMACs();
|
||||
|
||||
// init selected (first) tab
|
||||
initTab();
|
||||
|
||||
// init Assign/Unassign buttons
|
||||
initButtons()
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
52
front/php/components/devices_filters.php
Executable file
52
front/php/components/devices_filters.php
Executable file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Check if authenticated
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/server/db.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/language/lang.php';
|
||||
|
||||
// Function to render a filter dropdown
|
||||
function renderFilterDropdown($headerKey, $columnName, $values) {
|
||||
// Generate dropdown options
|
||||
$optionsHtml = '<option value="" selected>All</option>'; // Default "All" option
|
||||
foreach ($values as $value) {
|
||||
$escapedValue = htmlspecialchars($value);
|
||||
$optionsHtml .= '<option value="' . $escapedValue . '">' . $escapedValue . '</option>';
|
||||
}
|
||||
|
||||
// Generate the dropdown HTML
|
||||
return '
|
||||
<div class="filter-group input-group">
|
||||
<label for="filter_' . htmlspecialchars($columnName) . '">' . lang($headerKey) . '</label>
|
||||
<select id="filter_' . htmlspecialchars($columnName) . '" class="filter-dropdown" data-column="' . htmlspecialchars($columnName) . '">
|
||||
' . $optionsHtml . '
|
||||
</select>
|
||||
</div>';
|
||||
}
|
||||
|
||||
// Get filterObject from POST data
|
||||
$filterObject = isset($_POST['filterObject']) ? json_decode($_POST['filterObject'], true) : [];
|
||||
|
||||
// Validate filterObject structure
|
||||
if (!isset($filterObject['filters']) || !is_array($filterObject['filters'])) {
|
||||
echo '<p class="error">Invalid filter data provided.</p>';
|
||||
exit();
|
||||
}
|
||||
|
||||
// Generate HTML for each filter in the filterObject
|
||||
$html = '';
|
||||
foreach ($filterObject['filters'] as $filter) {
|
||||
if (isset($filter['column'], $filter['headerKey'], $filter['options'])) {
|
||||
$html .= renderFilterDropdown($filter['headerKey'], $filter['column'], $filter['options']);
|
||||
} else {
|
||||
// Skip invalid entries
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Output the generated HTML
|
||||
echo $html;
|
||||
exit();
|
||||
|
||||
?>
|
||||
@@ -356,7 +356,10 @@
|
||||
<!-- Maintenance menu item -->
|
||||
<li class=" treeview <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('maintenance.php') ) ){ echo 'active menu-open'; } ?>">
|
||||
<a href="#" onclick="openUrl(['./maintenance.php'])">
|
||||
<div class="info-icon-nav myhidden" id="version" data-build-time="<?php echo file_get_contents( "buildtimestamp.txt");?>">🆕</div>
|
||||
<!-- NEW version available -->
|
||||
<div class="info-icon-nav myhidden" id="version" title="<?= lang('new_version_available');?>" data-build-time="<?php echo file_get_contents( "buildtimestamp.txt");?>">
|
||||
<i class="fa-solid fa-rocket fa-beat"></i>
|
||||
</div>
|
||||
<i class="fa fa-fw fa-wrench"></i> <span><?= lang('Navigation_Maintenance');?></span>
|
||||
<span class="pull-right-container">
|
||||
<i class="fa fa-angle-left pull-right"></i>
|
||||
@@ -430,17 +433,14 @@
|
||||
</li>
|
||||
|
||||
<!-- About menu item -->
|
||||
<li class=" treeview <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('donations.php', 'help_faq.php', 'systeminfo.php' ) ) ){ echo 'active menu-open'; } ?>">
|
||||
<li class=" treeview <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('help_faq.php', 'systeminfo.php' ) ) ){ echo 'active menu-open'; } ?>">
|
||||
<a href="#">
|
||||
<i class="fa fa-fw fa-info"></i> <span><?= lang('Navigation_About');?></span>
|
||||
<span class="pull-right-container">
|
||||
<i class="fa fa-angle-left pull-right"></i>
|
||||
</span>
|
||||
</a>
|
||||
<ul class="treeview-menu " style="display: <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('donations.php', 'help_faq.php', 'systeminfo.php' ) ) ){ echo 'block'; } else {echo 'none';} ?>;">
|
||||
<li>
|
||||
<a href="donations.php"> <?= lang("Navigation_Donations");?> </a>
|
||||
</li>
|
||||
<ul class="treeview-menu " style="display: <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('help_faq.php', 'systeminfo.php' ) ) ){ echo 'block'; } else {echo 'none';} ?>;">
|
||||
<li>
|
||||
<a href="help_faq.php"> <?= lang("Navigation_HelpFAQ");?> </a>
|
||||
</li>
|
||||
|
||||
@@ -242,6 +242,7 @@
|
||||
"Device_Tablelenght": "",
|
||||
"Device_Tablelenght_all": "",
|
||||
"Device_Title": "",
|
||||
"Devices_Filters": "",
|
||||
"Donations_Others": "",
|
||||
"Donations_Platforms": "",
|
||||
"Donations_Text": "",
|
||||
@@ -716,6 +717,7 @@
|
||||
"general_event_title": "",
|
||||
"go_to_node_event_icon": "",
|
||||
"go_to_node_event_tooltip": "",
|
||||
"new_version_available": "",
|
||||
"report_guid": "",
|
||||
"report_guid_missing": "",
|
||||
"report_select_format": "",
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
"CLEAR_NEW_FLAG_name": "Netejar indicador de nou",
|
||||
"CustProps_cant_remove": "No es pot eliminar, es necessita una propietat mínim.",
|
||||
"DAYS_TO_KEEP_EVENTS_description": "Això és una configuració de manteniment. Especifica el nombre de dies que es conservaran els esdeveniments. Els esdeveniments antics s'esborraran periòdicament. També aplica als esdeveniments dels Connectors (Plugins).",
|
||||
"DAYS_TO_KEEP_EVENTS_name": "Esborrar esdeveniments anteriors",
|
||||
"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_Copy_Device_Title": "<i class=\"fa fa-copy\"></i> Copiar detalls des del dispositiu",
|
||||
@@ -138,8 +138,8 @@
|
||||
"DevDetail_Shortcut_DownAlerts": "Aturar alertes",
|
||||
"DevDetail_Shortcut_Presence": "Presència",
|
||||
"DevDetail_Shortcut_Sessions": "Sessions",
|
||||
"DevDetail_Tab_Details": "<i class=\"fa fa-info-circle\"></i> Detalls",
|
||||
"DevDetail_Tab_Events": "<i class=\"fa fa-bolt\"></i> Esdeveniments",
|
||||
"DevDetail_Tab_Details": "Detalls",
|
||||
"DevDetail_Tab_Events": "Esdeveniments",
|
||||
"DevDetail_Tab_EventsTableDate": "Data",
|
||||
"DevDetail_Tab_EventsTableEvent": "Tipus d'esdeveniment",
|
||||
"DevDetail_Tab_EventsTableIP": "IP",
|
||||
@@ -154,10 +154,10 @@
|
||||
"DevDetail_Tab_NmapTableState": "Estat",
|
||||
"DevDetail_Tab_NmapTableText": "Configurar un calendari a <a href=\"/settings.php#NMAP_ACTIVE\">Configuració</a>",
|
||||
"DevDetail_Tab_NmapTableTime": "Temps",
|
||||
"DevDetail_Tab_Plugins": "<i class=\"fa fa-plug\"></i> Connectors (Plugins)",
|
||||
"DevDetail_Tab_Presence": "<i class=\"fa fa-calendar\"></i> Presència",
|
||||
"DevDetail_Tab_Sessions": "<i class=\"fa fa-list-ol\"></i> Sessions",
|
||||
"DevDetail_Tab_Tools": "<i class=\"fa fa-screwdriver-wrench\"></i> Eines",
|
||||
"DevDetail_Tab_Plugins": "Connectors (Plugins)",
|
||||
"DevDetail_Tab_Presence": "Presència",
|
||||
"DevDetail_Tab_Sessions": "Sessions",
|
||||
"DevDetail_Tab_Tools": "Eines",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Description": "L'eina d'informació d'Internet mostra informació sobre la connexió a Internet, com ara adreça IP, ciutat, país, codi d'àrea i zona horària.",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Error": "S'ha produït un error",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Start": "Inici Informació d'Internet",
|
||||
@@ -242,6 +242,7 @@
|
||||
"Device_Tablelenght": "Veure_entrades_MENU",
|
||||
"Device_Tablelenght_all": "Tot",
|
||||
"Device_Title": "Dispositius",
|
||||
"Devices_Filters": "Filtres",
|
||||
"Donations_Others": "Altres",
|
||||
"Donations_Platforms": "Plataformes patrocinadores",
|
||||
"Donations_Text": "Hola 👋! </br> Gràcies per fer clic en aquest element de menú 😅 </br> </br> Estic intentant recollir algunes donacions per fer un millor programari. També, m'ajudaria per cremar-me, i així recolzar aquesta aplicació més temps. Qualsevol petit (recurrent o no) patrocini em farà posar més esforç a aquesta aplicació. </br> M'agradaria escurçar la meva setmana de feina i en el temps restant enfocar-me en el NetAlertX. Així rebries més funcionalitat, una aplicació més neta i menys bugs. </br> </br> Gràcies per llegir-ho - Agraeixo qualsevol suport ❤🙏 </br> </br> TL;DR: Pel teu suport reps: </br> </br> <ul><li>Actualitzacions regulars per seguir les vostres dades i mantenir la família segura 🔄</li><li>Menys bugs 🐛🔫</li><li>Millor i més funcionalitat➕</li><li>Que no m'arribi el \"burn out\" 🔥🤯</li><li>Menys actualitzacions d'emergència 💨</li><li>Millors documentacions📚</li><li>Suport més ràpid i millor amb les incidències 🆘</li></ul> </br> 📧Correu electrònic <a href='mailto:jokob@duck.com?subject=NetAlertX'>jokob@duck.com</a> si vols contactar o si hauria d'afegir altres programes de patrocini. </br>",
|
||||
@@ -489,7 +490,7 @@
|
||||
"Maintenance_lang_selector_empty": "Tria idioma",
|
||||
"Maintenance_lang_selector_lable": "Selecció d'idioma",
|
||||
"Maintenance_lang_selector_text": "El canvi té lloc en el cantó del client, així que afecta només el navegador actual.",
|
||||
"Maintenance_new_version": "🆕 Hi ha una nova versió. Comprova <a href=\"https://github.com/jokob-sk/NetAlertX/releases\" target=\"_blank\">release notes</a>.",
|
||||
"Maintenance_new_version": "Hi ha una nova versió. Comprova <a href=\"https://github.com/jokob-sk/NetAlertX/releases\" target=\"_blank\">release notes</a>.",
|
||||
"Maintenance_themeselector_apply": "Aplica",
|
||||
"Maintenance_themeselector_empty": "Tria una Skin",
|
||||
"Maintenance_themeselector_lable": "Selecciona una Skin",
|
||||
@@ -716,6 +717,7 @@
|
||||
"general_event_title": "Execució d'un esdeveniment ad-hoc",
|
||||
"go_to_node_event_icon": "fa-square-up-right",
|
||||
"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:",
|
||||
"report_guid_missing": "No s'ha trobat la notificació enllaçada. Hi ha un petit retard entre les notificacions enviades recentment i que estiguin disponibles. Refresqui la pàgina i la memòria cau d'aquí uns segons. També és possible que la notificació seleccionada s'hagi esborrat durant el manteniment tal com s'especifica a la configuració <code>DBCLNP_NOTIFI_HIST</code>. <br/> <br/>L'última notificació es mostra en el seu lloc. La notificació perduda té el següent GUID:",
|
||||
"report_select_format": "Seleccioneu Format:",
|
||||
@@ -750,4 +752,4 @@
|
||||
"settings_update_item_warning": "Actualitza el valor sota. Sigues curós de seguir el format anterior. <b>No hi ha validació.</b>",
|
||||
"test_event_icon": "fa-vial-circle-check",
|
||||
"test_event_tooltip": "Deseu els canvis primer abans de comprovar la configuració."
|
||||
}
|
||||
}
|
||||
@@ -242,6 +242,7 @@
|
||||
"Device_Tablelenght": "",
|
||||
"Device_Tablelenght_all": "",
|
||||
"Device_Title": "",
|
||||
"Devices_Filters": "",
|
||||
"Donations_Others": "",
|
||||
"Donations_Platforms": "",
|
||||
"Donations_Text": "",
|
||||
@@ -716,6 +717,7 @@
|
||||
"general_event_title": "",
|
||||
"go_to_node_event_icon": "",
|
||||
"go_to_node_event_tooltip": "",
|
||||
"new_version_available": "",
|
||||
"report_guid": "",
|
||||
"report_guid_missing": "",
|
||||
"report_select_format": "",
|
||||
|
||||
@@ -150,8 +150,8 @@
|
||||
"DevDetail_Shortcut_DownAlerts": "Down Meldungen",
|
||||
"DevDetail_Shortcut_Presence": "Anwesenheit",
|
||||
"DevDetail_Shortcut_Sessions": "Sitzungen",
|
||||
"DevDetail_Tab_Details": "<i class=\"fa fa-info-circle\"></i> Details",
|
||||
"DevDetail_Tab_Events": "<i class=\"fa fa-bolt\"></i> Ereignisse",
|
||||
"DevDetail_Tab_Details": "Details",
|
||||
"DevDetail_Tab_Events": "Ereignisse",
|
||||
"DevDetail_Tab_EventsTableDate": "Datum",
|
||||
"DevDetail_Tab_EventsTableEvent": "Ereignistype",
|
||||
"DevDetail_Tab_EventsTableIP": "IP",
|
||||
@@ -166,10 +166,10 @@
|
||||
"DevDetail_Tab_NmapTableState": "Status",
|
||||
"DevDetail_Tab_NmapTableText": "Erstelle einen Plan über die<a href=\"/settings.php#NMAP_ACTIVE\">Einstellungen</a>",
|
||||
"DevDetail_Tab_NmapTableTime": "Zeit",
|
||||
"DevDetail_Tab_Plugins": "<i class=\"fa fa-plug\"></i> Plugins",
|
||||
"DevDetail_Tab_Presence": "<i class=\"fa fa-calendar\"></i> Anwesenheit",
|
||||
"DevDetail_Tab_Sessions": "<i class=\"fa fa-list-ol\"></i> Sitzungen",
|
||||
"DevDetail_Tab_Tools": "<i class=\"fa fa-screwdriver-wrench\"></i> Werkzeuge",
|
||||
"DevDetail_Tab_Plugins": "Plugins",
|
||||
"DevDetail_Tab_Presence": "Anwesenheit",
|
||||
"DevDetail_Tab_Sessions": "Sitzungen",
|
||||
"DevDetail_Tab_Tools": "Werkzeuge",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Description": "Das Internet-Info-Tool zeigt Informationen über die Internetverbindung an, wie z. B. IP-Adresse, Stadt, Land, Ortsvorwahl und Zeitzone.",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Error": "Es ist ein Fehler aufgetreten",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Start": "Internet-Info starten",
|
||||
@@ -254,6 +254,7 @@
|
||||
"Device_Tablelenght": "Zeige _MENU_ Einträge",
|
||||
"Device_Tablelenght_all": "Alle",
|
||||
"Device_Title": "Geräte",
|
||||
"Devices_Filters": "",
|
||||
"Donations_Others": "Andere",
|
||||
"Donations_Platforms": "Sponsor-Platformen",
|
||||
"Donations_Text": "Hey 👋! </br> Thanks for clicking on this menu item 😅 </br> </br> I'm trying to collect some donations to make you better software. Also, it would help me not to get burned out. Me burning out might mean end of support for this app. Any small (recurring or not) sponsorship makes me want ot put more effort into this app. I don't want to lock features (new plugins) behind paywalls 🔐. </br> Currently, I'm waking up 2h before work so I contribute to the app a bit. If I had some recurring income I could shorten my workweek and in the remaining time fully focus on NetAlertX. You'd get more functionality, a more polished app and less bugs. </br> </br> Thanks for reading - I'm super grateful for any support ❤🙏 </br> </br> TL;DR: By supporting me you get: </br> </br> <ul><li>Regular updates to keep your data and family safe 🔄</li><li>Less bugs 🐛🔫</li><li>Better and more functionality➕</li><li>I don't get burned out 🔥🤯</li><li>Less rushed releases 💨</li><li>Better docs📚</li><li>Quicker and better support with issues 🆘</li><li>Less grumpy me 😄</li></ul> </br> 📧Email me to <a href='mailto:jokob@duck.com?subject=NetAlertX'>jokob@duck.com</a> if you want to get in touch or if I should add other sponsorship platforms. </br>",
|
||||
@@ -515,7 +516,7 @@
|
||||
"Maintenance_lang_selector_empty": "Sprache wählen",
|
||||
"Maintenance_lang_selector_lable": "Sprachauswahl",
|
||||
"Maintenance_lang_selector_text": "Die Änderung findet serverseitig statt, betrifft also alle verwendeten Geräte.",
|
||||
"Maintenance_new_version": "🆕 Eine neue Version ist vefügbar. Sieh dir die <a href=\"https://github.com/jokob-sk/NetAlertX/releases\" target=\"_blank\">Versionshinweise</a> an.",
|
||||
"Maintenance_new_version": "Eine neue Version ist vefügbar. Sieh dir die <a href=\"https://github.com/jokob-sk/NetAlertX/releases\" target=\"_blank\">Versionshinweise</a> an.",
|
||||
"Maintenance_themeselector_apply": "Übernehmen",
|
||||
"Maintenance_themeselector_empty": "Skin wählen",
|
||||
"Maintenance_themeselector_lable": "Skin Auswahl",
|
||||
@@ -797,6 +798,7 @@
|
||||
"general_event_title": "",
|
||||
"go_to_node_event_icon": "",
|
||||
"go_to_node_event_tooltip": "",
|
||||
"new_version_available": "",
|
||||
"report_guid": "",
|
||||
"report_guid_missing": "",
|
||||
"report_select_format": "Format auswählen:",
|
||||
@@ -831,4 +833,4 @@
|
||||
"settings_update_item_warning": "",
|
||||
"test_event_icon": "",
|
||||
"test_event_tooltip": "Speichere die Änderungen, bevor Sie die Einstellungen testen."
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@
|
||||
"BackDevices_DBTools_ImportCSVError": "The CSV file could not be imported. Make sure the format is correct.",
|
||||
"BackDevices_DBTools_ImportCSVMissing": "The CSV file could not be found under <b>/config/devices.csv.</b>",
|
||||
"BackDevices_DBTools_Purge": "The oldest backups were deleted",
|
||||
"BackDevices_DBTools_UpdDev": "Device updated successfully",
|
||||
"BackDevices_DBTools_UpdDev": "Device updated successfully. Main devices list may need some time to reload if a scan is in progress.",
|
||||
"BackDevices_DBTools_UpdDevError": "Error updating device",
|
||||
"BackDevices_DBTools_Upgrade": "Database upgraded successfully",
|
||||
"BackDevices_DBTools_UpgradeError": "Database upgrade failed",
|
||||
@@ -138,8 +138,8 @@
|
||||
"DevDetail_Shortcut_DownAlerts": "Down Alerts",
|
||||
"DevDetail_Shortcut_Presence": "Presence",
|
||||
"DevDetail_Shortcut_Sessions": "Sessions",
|
||||
"DevDetail_Tab_Details": "<i class=\"fa fa-info-circle\"></i> Details",
|
||||
"DevDetail_Tab_Events": "<i class=\"fa fa-bolt\"></i> Events",
|
||||
"DevDetail_Tab_Details": "Details",
|
||||
"DevDetail_Tab_Events": "Events",
|
||||
"DevDetail_Tab_EventsTableDate": "Date",
|
||||
"DevDetail_Tab_EventsTableEvent": "Event type",
|
||||
"DevDetail_Tab_EventsTableIP": "IP",
|
||||
@@ -154,10 +154,10 @@
|
||||
"DevDetail_Tab_NmapTableState": "State",
|
||||
"DevDetail_Tab_NmapTableText": "Set up a schedule in <a href=\"/settings.php#NMAP_ACTIVE\">Settings</a>",
|
||||
"DevDetail_Tab_NmapTableTime": "Time",
|
||||
"DevDetail_Tab_Plugins": "<i class=\"fa fa-plug\"></i> Plugins",
|
||||
"DevDetail_Tab_Presence": "<i class=\"fa fa-calendar\"></i> Presence",
|
||||
"DevDetail_Tab_Sessions": "<i class=\"fa fa-list-ol\"></i> Sessions",
|
||||
"DevDetail_Tab_Tools": "<i class=\"fa fa-screwdriver-wrench\"></i> Tools",
|
||||
"DevDetail_Tab_Plugins": "Plugins",
|
||||
"DevDetail_Tab_Presence": "Presence",
|
||||
"DevDetail_Tab_Sessions": "Sessions",
|
||||
"DevDetail_Tab_Tools": "Tools",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Description": "The Internet info tool displays information about the Internet connection, such as IP address, city, country, area code and time zone.",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Error": "An error has occurred",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Start": "Start Internet Info",
|
||||
@@ -242,6 +242,7 @@
|
||||
"Device_Tablelenght": "Show _MENU_ entries",
|
||||
"Device_Tablelenght_all": "All",
|
||||
"Device_Title": "Devices",
|
||||
"Devices_Filters": "Filters",
|
||||
"Donations_Others": "Others",
|
||||
"Donations_Platforms": "Sponsor platforms",
|
||||
"Donations_Text": "Hey 👋! </br> Thanks for clicking on this menu item 😅 </br> </br> I'm trying to collect some donations to make you better software. Also, it would help me not to get burned out, so I can support this app longer. Any small (recurring or not) sponsorship makes me want to put more effort into this app. </br> I'd love to shorten my work week and in the remaining time fully focus on NetAlertX. You'd get more functionality, a more polished app and less bugs. </br> </br> Thanks for reading - I'm grateful for any support ❤🙏 </br> </br> TL;DR: By supporting me you get: </br> </br> <ul><li>Regular updates to keep your data and family safe 🔄</li><li>Less bugs 🐛🔫</li><li>Better and more functionality➕</li><li>I don't get burned out 🔥🤯</li><li>Less rushed releases 💨</li><li>Better docs📚</li><li>Quicker and better support with issues 🆘</li></ul> </br> 📧Email me to <a href='mailto:jokob@duck.com?subject=NetAlertX'>jokob@duck.com</a> if you want to get in touch or if I should add other sponsorship platforms. </br>",
|
||||
@@ -489,7 +490,7 @@
|
||||
"Maintenance_lang_selector_empty": "Choose Language",
|
||||
"Maintenance_lang_selector_lable": "Select Language",
|
||||
"Maintenance_lang_selector_text": "The change takes place on the client side, so it affects only the current browser.",
|
||||
"Maintenance_new_version": "🆕 A new version is available. Check out the <a href=\"https://github.com/jokob-sk/NetAlertX/releases\" target=\"_blank\">release notes</a>.",
|
||||
"Maintenance_new_version": "A new version is available. Check out the <a href=\"https://github.com/jokob-sk/NetAlertX/releases\" target=\"_blank\">release notes</a>.",
|
||||
"Maintenance_themeselector_apply": "Apply",
|
||||
"Maintenance_themeselector_empty": "Choose a Skin",
|
||||
"Maintenance_themeselector_lable": "Select Skin",
|
||||
@@ -716,6 +717,7 @@
|
||||
"general_event_title": "Executing an ad-hoc event",
|
||||
"go_to_node_event_icon": "fa-square-up-right",
|
||||
"go_to_node_event_tooltip": "Navigate to the Network page of the given node",
|
||||
"new_version_available": "A new version is available.",
|
||||
"report_guid": "Notification guid:",
|
||||
"report_guid_missing": "Linked notification not found. There is a small delay between recently sent notifications and them being available. Referesh your page and cache after a few seconds. It's also possible the selected notification have been deleted during maintenance as specified in the <code>DBCLNP_NOTIFI_HIST</code> setting. <br/> <br/>The latest notification is displayed instead. The missing notification has the following GUID:",
|
||||
"report_select_format": "Select Format:",
|
||||
|
||||
@@ -148,8 +148,8 @@
|
||||
"DevDetail_Shortcut_DownAlerts": "Alerta(s) de caída(s)",
|
||||
"DevDetail_Shortcut_Presence": "Historial",
|
||||
"DevDetail_Shortcut_Sessions": "Sesiones",
|
||||
"DevDetail_Tab_Details": "<i class=\"fa fa-info-circle\"></i> Detalles",
|
||||
"DevDetail_Tab_Events": "<i class=\"fa fa-bolt\"></i> Eventos",
|
||||
"DevDetail_Tab_Details": "Detalles",
|
||||
"DevDetail_Tab_Events": "Eventos",
|
||||
"DevDetail_Tab_EventsTableDate": "Fecha",
|
||||
"DevDetail_Tab_EventsTableEvent": "Tipo de evento",
|
||||
"DevDetail_Tab_EventsTableIP": "IP",
|
||||
@@ -164,10 +164,10 @@
|
||||
"DevDetail_Tab_NmapTableState": "Estado",
|
||||
"DevDetail_Tab_NmapTableText": "Establece la programación en los <a href=\"/settings.php#NMAP_ACTIVE\">Ajustes</a>",
|
||||
"DevDetail_Tab_NmapTableTime": "Tiempo",
|
||||
"DevDetail_Tab_Plugins": "<i class=\"fa fa-plug\"></i> Plugins",
|
||||
"DevDetail_Tab_Presence": "<i class=\"fa fa-calendar\"></i> Historial",
|
||||
"DevDetail_Tab_Sessions": "<i class=\"fa fa-list-ol\"></i> Sesiones",
|
||||
"DevDetail_Tab_Tools": "<i class=\"fa fa-screwdriver-wrench\"></i> Herramientas",
|
||||
"DevDetail_Tab_Plugins": "Plugins",
|
||||
"DevDetail_Tab_Presence": "Historial",
|
||||
"DevDetail_Tab_Sessions": "Sesiones",
|
||||
"DevDetail_Tab_Tools": "Herramientas",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Description": "La herramienta de información de internet muestra información sobre la conexión a Internet, como dirección IP, ciudad, país, código de área y zona horaria.",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Error": "Se ha producido un error",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Start": "Iniciar información de Internet",
|
||||
@@ -252,6 +252,7 @@
|
||||
"Device_Tablelenght": "Mostrar _MENU_ entradas",
|
||||
"Device_Tablelenght_all": "Todos",
|
||||
"Device_Title": "Dispositivos",
|
||||
"Devices_Filters": "",
|
||||
"Donations_Others": "Otros",
|
||||
"Donations_Platforms": "Plataforma de patrocinadores",
|
||||
"Donations_Text": "¡Hola! 👋 </br> Gracias por hacer clic en este elemento 😅 del menú </br> </br>, estoy tratando de recolectar algunas donaciones para mejorar el software. Además, me ayudaría a no quemarse, por lo que puedo apoyar esta aplicación por más tiempo. Cualquier pequeño patrocinio (recurrente o no) me hace querer esforzarme más en esta aplicación. </br> Me encantaría acortar mi semana de trabajo y en el tiempo que me queda centrarme por completo en NetAlertX. Obtendrías más funcionalidad, una aplicación más pulida y menos errores. </br> </br> Gracias por leer, agradezco cualquier apoyo ❤🙏 </br> </br> TL; DR: Al apoyarme, obtienes: </br> </br> <ul><li>Actualizaciones periódicas para mantener tus datos y tu familia seguros 🔄</li><li>Menos errores 🐛🔫</li><li>Mejor y más funcionalidad➕</li><li>No me quemo 🔥🤯</li><li>Lanzamientos 💨menos apresurados</li> <li>Mejores documentos📚</li><li>Soporte más rápido y mejor con problemas 🆘</li></ul> </br> 📧Envíame un correo electrónico a <a href='mailto:jokob@duck.com?subject=NetAlertX'>jokob@duck.com</a> si quieres ponerte en contacto o si debo añadir otras plataformas de patrocinio. </br>",
|
||||
@@ -513,7 +514,7 @@
|
||||
"Maintenance_lang_selector_empty": "Elija un idioma",
|
||||
"Maintenance_lang_selector_lable": "Seleccione su idioma",
|
||||
"Maintenance_lang_selector_text": "El cambio se produce en el lado del cliente, por lo que sólo afecta al navegador actual.",
|
||||
"Maintenance_new_version": "🆕 Una nueva versión está disponible. Comprueba las <a href=\"https://github.com/jokob-sk/NetAlertX/releases\" target=\"_blank\">notas de lanzamiento</a>.",
|
||||
"Maintenance_new_version": "Una nueva versión está disponible. Comprueba las <a href=\"https://github.com/jokob-sk/NetAlertX/releases\" target=\"_blank\">notas de lanzamiento</a>.",
|
||||
"Maintenance_themeselector_apply": "Aplicar",
|
||||
"Maintenance_themeselector_empty": "Elige un tema",
|
||||
"Maintenance_themeselector_lable": "Seleccionar tema",
|
||||
@@ -795,6 +796,7 @@
|
||||
"general_event_title": "Ejecutar un evento ad-hoc",
|
||||
"go_to_node_event_icon": "fa-square-up-right",
|
||||
"go_to_node_event_tooltip": "Vaya a la página de Red del nodo indicado",
|
||||
"new_version_available": "",
|
||||
"report_guid": "Guía de las notificaciones:",
|
||||
"report_guid_missing": "No se ha encontrado la notificación vinculada. Hay un pequeño retraso entre las notificaciones enviadas recientemente y su disponibilidad. Actualiza tu página y la caché después de unos segundos. También es posible que la notificación seleccionada se haya eliminado durante el mantenimiento, tal y como se especifica en la configuración <code>de DBCLNP_NOTIFI_HIST</code>. <br/> <br/>En su lugar, se muestra la notificación más reciente. La notificación que falta tiene el siguiente GUID:",
|
||||
"report_select_format": "Selecciona el formato:",
|
||||
@@ -829,4 +831,4 @@
|
||||
"settings_update_item_warning": "Actualice el valor a continuación. Tenga cuidado de seguir el formato anterior. <b>O la validación no se realiza.</b>",
|
||||
"test_event_icon": "fa-vial-circle-check",
|
||||
"test_event_tooltip": "Guarda tus cambios antes de probar nuevos ajustes."
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"API_CUSTOM_SQL_description": "Vous pouvez spécifier votre propre requête SQL qui retournera un fichier JSON et l'exposer via <a href=\"/api/table_custom_endpoint.json\" target=\"_blank\"><code>table_custom_endpoint.json</code> le point de terminaison de fichier</a>.",
|
||||
"API_CUSTOM_SQL_name": "Point de terminaison personnalisé",
|
||||
"API_TOKEN_description": "Vous pouvez renseigner ou générer un jeton API pour sécuriser les échanges. Il est transmis dans le header de la requête. C'est utilisé dans le plugin <code>SYNC</code> du serveur GraphQL.",
|
||||
"API_TOKEN_description": "Vous pouvez renseigner ou générer un jeton API pour sécuriser les échanges. Il est transmis dans le header de la requête, et utilisé dans le plugin <code>SYNC</code>, le serveur GraphQL et d'autres usages API. Vous pouvez utiliser les points de terminaison API pour créer des intégrations spécifiques, comme décrit dans la <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/API.md\" target=\"_blank\">documentation de l'API</a>.",
|
||||
"API_TOKEN_name": "Jeton d'API",
|
||||
"API_display_name": "API",
|
||||
"API_icon": "<i class=\"fa fa-arrow-down-up-across-line\"></i>",
|
||||
@@ -48,7 +48,7 @@
|
||||
"BackDevices_DBTools_ImportCSVError": "Le fichier CSV n'a pas pu être importé. Assurez-vous que le format est correct.",
|
||||
"BackDevices_DBTools_ImportCSVMissing": "Le fichier CSV est introuvable sous <b>/config/devices.csv.</b>",
|
||||
"BackDevices_DBTools_Purge": "Les sauvegardes les plus anciennes ont été supprimées",
|
||||
"BackDevices_DBTools_UpdDev": "Appareil mis à jour avec succès",
|
||||
"BackDevices_DBTools_UpdDev": "Appareil mis à jour avec succès. La liste principale des appareils peut prendre un peu de temps à se mettre à jour si un scan est en cours.",
|
||||
"BackDevices_DBTools_UpdDevError": "Erreur lors de la mise à jour de l'appareil",
|
||||
"BackDevices_DBTools_Upgrade": "Base de données mise à niveau avec succès",
|
||||
"BackDevices_DBTools_UpgradeError": "La mise à niveau de la base de données a échoué",
|
||||
@@ -138,8 +138,8 @@
|
||||
"DevDetail_Shortcut_DownAlerts": "Alertes de panne",
|
||||
"DevDetail_Shortcut_Presence": "Présence",
|
||||
"DevDetail_Shortcut_Sessions": "Sessions",
|
||||
"DevDetail_Tab_Details": "<i class=\"fa fa-info-circle\"></i> Détails",
|
||||
"DevDetail_Tab_Events": "<i class=\"fa fa-bolt\"></i> Événements",
|
||||
"DevDetail_Tab_Details": "Détails",
|
||||
"DevDetail_Tab_Events": "Événements",
|
||||
"DevDetail_Tab_EventsTableDate": "Date",
|
||||
"DevDetail_Tab_EventsTableEvent": "Type d'événement",
|
||||
"DevDetail_Tab_EventsTableIP": "IP",
|
||||
@@ -154,10 +154,10 @@
|
||||
"DevDetail_Tab_NmapTableState": "État",
|
||||
"DevDetail_Tab_NmapTableText": "Configurer une programmation dans les <a href=\"/settings.php#NMAP_ACTIVE\">Paramètres</a>",
|
||||
"DevDetail_Tab_NmapTableTime": "Heure",
|
||||
"DevDetail_Tab_Plugins": "<i class=\"fa fa-plug\"></i> Plugins",
|
||||
"DevDetail_Tab_Presence": "<i class=\"fa fa-calendar\"></i> Présence",
|
||||
"DevDetail_Tab_Sessions": "<i class=\"fa fa-list-ol\"></i> Sessions",
|
||||
"DevDetail_Tab_Tools": "<i class=\"fa fa-screwdriver-wrench\"></i> Outils",
|
||||
"DevDetail_Tab_Plugins": "Plugins",
|
||||
"DevDetail_Tab_Presence": "Présence",
|
||||
"DevDetail_Tab_Sessions": "Sessions",
|
||||
"DevDetail_Tab_Tools": "Outils",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Description": "L'outil Infos Internet affiche les informations sur la connexion Internet, comme l'adresse IP, la ville, la région, le code région et le fuseau horaire.",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Error": "Une erreur est survenue",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Start": "Lancer les informations Internet",
|
||||
@@ -242,6 +242,7 @@
|
||||
"Device_Tablelenght": "Afficher les entrées _MENU_",
|
||||
"Device_Tablelenght_all": "Tous",
|
||||
"Device_Title": "Appareils",
|
||||
"Devices_Filters": "Filtres",
|
||||
"Donations_Others": "Autres",
|
||||
"Donations_Platforms": "Plateformes de sponsoring",
|
||||
"Donations_Text": "Coucou 👋 ! </br> Merci d'avoir cliqué ici 😅 </br> </br> J'essaie de récolter des donations pour vous faire un meilleur produit. En plus, ça m'aide à éviter le burn-out pour développer cette application plus longtemps. Toute subvention (régulière ou non) me donne envie de poursuivre le développement de cette application.</br> J'aimerais réduire mon activité principale pour me concentrer plus longuement à NetAlertX. Vous auriez plus de fonctionnalités, une application mieux finie et avec moins de bugs.</br> </br> Merci de votre lecture - je vous suis reconnaissant pour votre soutien ❤🙏 </br> </br> Version courte : en me soutenant, vous aurez : </br> </br> <ul><li>Des mises à jour régulières pour protéger vos données personnelles et familiales 🔄</li><li>Moins de bugs 🐛🔫</li><li>Des fonctionnalités plus riches et plus nombreuses ➕</li><li>Je ne me retrouve pas en burn-out 🔥🤯</li><li>Des versions moins à la va-vite 💨</li><li>une meilleure documentation <20></li><li>Un support meilleur et plus réactif en cas de problème 🆘</li></ul> </br> 📧Envoyez-moi un courriel à <a href='mailto :jokob@duck.com ?subject=NetAlertX'>jokob@duck.com</a> si vous voulez me contacter ou du je peux ajouter une autre plateforme de soutien. </br>",
|
||||
@@ -403,7 +404,7 @@
|
||||
"Maintenance_Tool_ExportCSV": "Export des appareils (csv)",
|
||||
"Maintenance_Tool_ExportCSV_noti": "Export des appareils (csv)",
|
||||
"Maintenance_Tool_ExportCSV_noti_text": "Êtes-vous sûr de vouloir générer un fichier CSV ?",
|
||||
"Maintenance_Tool_ExportCSV_text": "Génère un fichier CSV (valeurs séparées par des virgules), contenant la liste des appareils, dont les liens entre nœuds Réseaux et les appareils connectés. Vous pouvez aussi lancer cet export depuis l'URL <code>votre URL de NetAlertX/php/server/devices.php?action=ExportCSV</code> ou en activant le plugin <a href=\"settings.php#CSVBCKP_header\">CSV Backup</a>.",
|
||||
"Maintenance_Tool_ExportCSV_text": "Génère un fichier CSV (valeurs séparées par des virgules), contenant la liste des appareils, dont les liens entre nœuds Réseaux et les appareils connectés. Vous pouvez aussi lancer cet export depuis l'URL <code>votre_URL_de_NetAlertX/php/server/devices.php?action=ExportCSV</code> ou en activant le plugin <a href=\"settings.php#CSVBCKP_header\">CSV Backup</a>.",
|
||||
"Maintenance_Tool_ImportCSV": "Import des appareils (csv)",
|
||||
"Maintenance_Tool_ImportCSV_noti": "Import des appareils (csv)",
|
||||
"Maintenance_Tool_ImportCSV_noti_text": "Êtes-vous sûr de vouloir importer le fichier CSV ? Cela <b>écrasera</b> complètement les appareils de votre base de données.",
|
||||
@@ -413,8 +414,8 @@
|
||||
"Maintenance_Tool_ImportPastedCSV_noti_text": "Êtes-vous sûr de vouloir importer les CSV copié ? Cela va complètement <b>remplacer</b> les appareils de votre base de données.",
|
||||
"Maintenance_Tool_ImportPastedCSV_text": "Avant d'utiliser cette fonctionnalité, il est recommandé de faire une sauvegarde. Importe un fichier CSV (valeurs séparées par des virgules) contenant la liste des appareils, dont les liens réseaux entre les nœuds du réseau et les appareils connectés.",
|
||||
"Maintenance_Tool_ImportPastedConfig": "Import des paramètres (coller)",
|
||||
"Maintenance_Tool_ImportPastedConfig_noti_text": "",
|
||||
"Maintenance_Tool_ImportPastedConfig_text": "",
|
||||
"Maintenance_Tool_ImportPastedConfig_noti_text": "Êtes-vous sûr de vouloir importer les paramètres de configuration copiés ? Cela va complètement <b>remplacer</b> le fichier <code>app.conf</code>.",
|
||||
"Maintenance_Tool_ImportPastedConfig_text": "Importe le fichier <code>app.conf</code>, qui contient tous les paramètres de l'application. Vous devriez commencer par télécharger le fichier actuel<code>app.conf</code> avec la fonctionnalité <b>Export des paramètres</b>.",
|
||||
"Maintenance_Tool_arpscansw": "Basculer l'arp-Scan (activé/désactivé)",
|
||||
"Maintenance_Tool_arpscansw_noti": "Activer ou désactiver l'arp-Scan",
|
||||
"Maintenance_Tool_arpscansw_noti_text": "Une fois le scan désactivé, il reste désactivé jusqu'à ce qu'il soit réactivé.",
|
||||
@@ -489,13 +490,13 @@
|
||||
"Maintenance_lang_selector_empty": "Choix de la langue",
|
||||
"Maintenance_lang_selector_lable": "Sélectionner une langue",
|
||||
"Maintenance_lang_selector_text": "Le changement est effectué côté client, cela ne concerne donc que le navigateur actuel.",
|
||||
"Maintenance_new_version": "🆕 Une nouvelle version est disponible. Consulter les <a href=\"https://github.com/jokob-sk/NetAlertX/releases\" target=\"_blank\">notes de version</a>.",
|
||||
"Maintenance_new_version": "Une nouvelle version est disponible. Consulter les <a href=\"https://github.com/jokob-sk/NetAlertX/releases\" target=\"_blank\">notes de version</a>.",
|
||||
"Maintenance_themeselector_apply": "Appliquer",
|
||||
"Maintenance_themeselector_empty": "Choisir un thème",
|
||||
"Maintenance_themeselector_lable": "Sélectionner un thème",
|
||||
"Maintenance_themeselector_text": "Le changement s'effectue côté serveur, il s'applique donc à tous les appareils connectés à l'interface graphique.",
|
||||
"Maintenance_version": "Mises à jour de l'application",
|
||||
"NETWORK_DEVICE_TYPES_description": "Les types d'appareils autorisés à verre utilisés comme appareils réseau dans la vue Réseau. Le type d'appareils doit être identique au paramètre <code>Type</code> d'un appareil dans le détail des appareils. Ne pas supprimer de valeurs, seulement en ajouter de nouvelles.",
|
||||
"NETWORK_DEVICE_TYPES_description": "Les types d'appareils autorisés à être utilisés comme appareils réseau dans la vue Réseau. Le type d'appareils doit être identique au paramètre <code>Type</code> d'un appareil dans le détail des appareils. Ajouter le sur l'appareil grâce au bouton <code>+</code>. Ne pas supprimer de valeurs, seulement en ajouter de nouvelles.",
|
||||
"NETWORK_DEVICE_TYPES_name": "Type d'appareil réseau",
|
||||
"Navigation_About": "À propos",
|
||||
"Navigation_Devices": "Appareils",
|
||||
@@ -716,6 +717,7 @@
|
||||
"general_event_title": "Lancement d'un événement sur mesure",
|
||||
"go_to_node_event_icon": "fa-square-up-right",
|
||||
"go_to_node_event_tooltip": "Aller vers la page Réseau du nœud concerné",
|
||||
"new_version_available": "Une nouvelle version est disponible.",
|
||||
"report_guid": "GUID de la notification :",
|
||||
"report_guid_missing": "La notification associée n'a pas été trouvée. Un petit délai existe entre l'envoi d'une notification et sa disponibilité réelle pour affichage. Rafraichissez la page et votre cache après quelques secondes. Il est aussi possible que la notification sélectionnée ait été supprimée durant une opération de maintenance, comme renseigné dans le paramètre <code>DBCLNP_NOTIFI_HIST</code>. <br/> <br/> La dernière notification est affichée à sa place. La notification manquante dispose du GUID suivant :",
|
||||
"report_select_format": "Sélectionner un format :",
|
||||
@@ -750,4 +752,4 @@
|
||||
"settings_update_item_warning": "Mettre à jour la valeur ci-dessous. Veillez à bien suivre le même format qu'auparavant. <b>Il n'y a pas de pas de contrôle.</b>",
|
||||
"test_event_icon": "fa-vial-circle-check",
|
||||
"test_event_tooltip": "Enregistrer d'abord vos modifications avant de tester vôtre paramétrage."
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@
|
||||
"BackDevices_DBTools_ImportCSVError": "Impossibile importare il file CSV. Assicurati che il formato del file sia corretto.",
|
||||
"BackDevices_DBTools_ImportCSVMissing": "Impossibile trovare il file CSV in <b>/config/devices.csv.</b>",
|
||||
"BackDevices_DBTools_Purge": "I backup più vecchi sono stati eliminati",
|
||||
"BackDevices_DBTools_UpdDev": "Dispositivo aggiornato correttamente",
|
||||
"BackDevices_DBTools_UpdDev": "Dispositivo aggiornato correttamente. L'elenco dei dispositivi principali potrebbe richiedere del tempo per ricaricarsi se è in corso una scansione.",
|
||||
"BackDevices_DBTools_UpdDevError": "Errore durante l'aggiornamento del dispositivo",
|
||||
"BackDevices_DBTools_Upgrade": "Database aggiornato correttamente",
|
||||
"BackDevices_DBTools_UpgradeError": "Aggiornamento del database non riuscito",
|
||||
@@ -138,8 +138,8 @@
|
||||
"DevDetail_Shortcut_DownAlerts": "Avvisi disconnessione",
|
||||
"DevDetail_Shortcut_Presence": "Presenza",
|
||||
"DevDetail_Shortcut_Sessions": "Sessioni",
|
||||
"DevDetail_Tab_Details": "<i class=\"fa fa-info-circle\"></i> Dettagli",
|
||||
"DevDetail_Tab_Events": "<i class=\"fa fa-bolt\"></i> Eventi",
|
||||
"DevDetail_Tab_Details": "Dettagli",
|
||||
"DevDetail_Tab_Events": "Eventi",
|
||||
"DevDetail_Tab_EventsTableDate": "Data",
|
||||
"DevDetail_Tab_EventsTableEvent": "Tipo evento",
|
||||
"DevDetail_Tab_EventsTableIP": "IP",
|
||||
@@ -154,10 +154,10 @@
|
||||
"DevDetail_Tab_NmapTableState": "Stato",
|
||||
"DevDetail_Tab_NmapTableText": "Imposta una pianificazione nelle <a href=\"/settings.php#NMAP_ACTIVE\">Impostazioni</a>",
|
||||
"DevDetail_Tab_NmapTableTime": "Ora",
|
||||
"DevDetail_Tab_Plugins": "<i class=\"fa fa-plug\"></i> Plugin",
|
||||
"DevDetail_Tab_Presence": "<i class=\"fa fa-calendar\"></i> Presenza",
|
||||
"DevDetail_Tab_Sessions": "<i class=\"fa fa-list-ol\"></i> Sessioni",
|
||||
"DevDetail_Tab_Tools": "<i class=\"fa fa-screwdriver-wrench\"></i> Strumenti",
|
||||
"DevDetail_Tab_Plugins": "Plugin",
|
||||
"DevDetail_Tab_Presence": "Presenza",
|
||||
"DevDetail_Tab_Sessions": "Sessioni",
|
||||
"DevDetail_Tab_Tools": "Strumenti",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Description": "Lo strumento informazioni Internet visualizza informazioni sulla connessione Internet, come indirizzo IP, città, paese, prefisso e fuso orario.",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Error": "Si è verificato un errore",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Start": "Avvia info Internet",
|
||||
@@ -242,6 +242,7 @@
|
||||
"Device_Tablelenght": "Mostra _MENU_ elementi",
|
||||
"Device_Tablelenght_all": "Tutti",
|
||||
"Device_Title": "Dispositivi",
|
||||
"Devices_Filters": "Filtri",
|
||||
"Donations_Others": "Altri",
|
||||
"Donations_Platforms": "Piattaforme sponsor",
|
||||
"Donations_Text": "Hey 👋! </br> Grazie per aver cliccato su questa voce di menu 😅 </br> </br> Sto cercando di ricevere donazioni per poter fornire un software migliore. Inoltre potrebbe aiutarmi a non andare in burnout, in modo da poter supportare questa app più a lungo. Ogni piccola (ricorrente o non) sponsorizzazione mi invoglia a mettere più impegno nello sviluppo di questa app. </br> Mi piacerebbe accorciare la mia settimana lavorativa e nel tempo rimanente dedicarmi completamente a NetAlertX. Riceverai più funzionalità, un'applicazione più rifinita e con meno bug.</br> </br> Grazie per aver letto, ti sono grato per ogni tipo di supporto ❤🙏 </br> </br> TL;DR: Supportandomi otterrai: </br> </br> <ul><li>Aggiornamenti più regolari per mantenere i tuoi dati e la tua famiglia sicuri 🔄</li><li>Meno bug 🐛🔫</li><li>Funzionalità migliori e più numerose➕</li><li>Io non vado in burnout 🔥🤯</li><li>Rilasci meno affrettati 💨</li><li>Migliore documentazione 📚</li><li>Supporto migliore e più veloce in caso di problemi 🆘</li></ul> </br> 📧Invia una mail a <a href='mailto:jokob@duck.com?subject=NetAlertX'>jokob@duck.com</a> se vuoi contattarmi o chiedermi di aggiungere altre piattaforme di sponsorizzazione. </br>",
|
||||
@@ -489,7 +490,7 @@
|
||||
"Maintenance_lang_selector_empty": "Scegli lingua",
|
||||
"Maintenance_lang_selector_lable": "Seleziona lingua",
|
||||
"Maintenance_lang_selector_text": "Questa modifica avviene lato client, quindi influisce solo sul browser attualmente in uso.",
|
||||
"Maintenance_new_version": "🆕 È disponibile una nuova versione. Controlla le <a href=\"https://github.com/jokob-sk/NetAlertX/releases\" target=\"_blank\">note di rilascio</a>.",
|
||||
"Maintenance_new_version": "È disponibile una nuova versione. Controlla le <a href=\"https://github.com/jokob-sk/NetAlertX/releases\" target=\"_blank\">note di rilascio</a>.",
|
||||
"Maintenance_themeselector_apply": "Applica",
|
||||
"Maintenance_themeselector_empty": "Scegli una skin",
|
||||
"Maintenance_themeselector_lable": "Seleziona skin",
|
||||
@@ -716,6 +717,7 @@
|
||||
"general_event_title": "Esecuzione di un evento ad-hoc",
|
||||
"go_to_node_event_icon": "fa-square-up-right",
|
||||
"go_to_node_event_tooltip": "Passa alla pagina Rete del nodo specificato",
|
||||
"new_version_available": "È disponibile una nuova versione.",
|
||||
"report_guid": "GUID notifica:",
|
||||
"report_guid_missing": "Notifica collegata non trovata. C'è un piccolo ritardo tra la disponibilità delle notifiche inviate di recente e la loro disponibilità. Aggiorna la pagina e la cache dopo alcuni secondi. È anche possibile che la notifica selezionata sia stata eliminata durante la manutenzione come specificato nell'impostazione <code>DBCLNP_NOTIFI_HIST</code>. <br/> <br/>Viene invece visualizzata l'ultima notifica. La notifica mancante ha il seguente GUID:",
|
||||
"report_select_format": "Seleziona formato:",
|
||||
@@ -750,4 +752,4 @@
|
||||
"settings_update_item_warning": "Aggiorna il valore qui sotto. Fai attenzione a seguire il formato precedente. <b>La convalida non viene eseguita.</b>",
|
||||
"test_event_icon": "fa-vial-circle-check",
|
||||
"test_event_tooltip": "Salva le modifiche prima di provare le nuove impostazioni."
|
||||
}
|
||||
}
|
||||
@@ -138,8 +138,8 @@
|
||||
"DevDetail_Shortcut_DownAlerts": "Nede Varslinger",
|
||||
"DevDetail_Shortcut_Presence": "Tilstedeværelse",
|
||||
"DevDetail_Shortcut_Sessions": "Sesjoner",
|
||||
"DevDetail_Tab_Details": "<i class=\"fa fa-info-circle\"></i> Detaljer",
|
||||
"DevDetail_Tab_Events": "<i class=\"fa fa-bolt\"></i> Hendelser",
|
||||
"DevDetail_Tab_Details": "Detaljer",
|
||||
"DevDetail_Tab_Events": "Hendelser",
|
||||
"DevDetail_Tab_EventsTableDate": "Dato",
|
||||
"DevDetail_Tab_EventsTableEvent": "Hendelstype",
|
||||
"DevDetail_Tab_EventsTableIP": "IP",
|
||||
@@ -154,10 +154,10 @@
|
||||
"DevDetail_Tab_NmapTableState": "Tilstand",
|
||||
"DevDetail_Tab_NmapTableText": "Konfigurer en tidsplan i <a href=\"/settings.php#NMAP_ACTIVE\">Innstillinger</a>",
|
||||
"DevDetail_Tab_NmapTableTime": "Tidspunkt",
|
||||
"DevDetail_Tab_Plugins": "<i class=\"fa fa-plug\"></i> Plugins",
|
||||
"DevDetail_Tab_Presence": "<i class=\"fa fa-calendar\"></i> Tilstedeværelse",
|
||||
"DevDetail_Tab_Sessions": "<i class=\"fa fa-list-ol\"></i> Økter",
|
||||
"DevDetail_Tab_Tools": "<i class=\"fa fa-screwdriver-wrench\"></i> Verktøy",
|
||||
"DevDetail_Tab_Plugins": "Plugins",
|
||||
"DevDetail_Tab_Presence": "Tilstedeværelse",
|
||||
"DevDetail_Tab_Sessions": "Økter",
|
||||
"DevDetail_Tab_Tools": "Verktøy",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Description": "Internett-informasjonsverktøyet viser informasjon om Internett-tilkoblingen, for eksempel IP-adresse, by, land, retningsnummer og tidssone.",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Error": "En feil har oppstått",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Start": "Start Internett-informasjonsverktøyet",
|
||||
@@ -242,6 +242,7 @@
|
||||
"Device_Tablelenght": "Show _MENU_ entries",
|
||||
"Device_Tablelenght_all": "Alle",
|
||||
"Device_Title": "Enheter",
|
||||
"Devices_Filters": "",
|
||||
"Donations_Others": "Andre",
|
||||
"Donations_Platforms": "Sponsorplattformer",
|
||||
"Donations_Text": "Hei 👋! </br> Takk for at du klikket på dette menyelementet 😅 </br> </br> Jeg prøver å samle inn noen donasjoner for å lage bedre programvare. Dessuten ville det hjelpe meg å ikke bli utbrent, så jeg kan støtte denne appen lenger. Enhver liten (tilbakevendende eller ikke) sponsing gjør at jeg ønsker å legge mer innsats i denne appen. </br> Jeg vil gjerne forkorte arbeidsuken min og i den gjenværende tiden fokusere fullt ut på NetAlertX. Du vil få mer funksjonalitet, en mer polert app og mindre feil. </br> </br> Takk for at du leste - jeg er takknemlig for all støtte ❤🙏 </br> </br> TL;DR: Ved å støtte meg får du: </br> </br> <ul> <li>Jevne oppdateringer for å holde dataene dine og familien din trygge 🔄</li><li>Mindre feil 🐛🔫</li><li>Bedre og mer funksjonalitet➕</li><li>Jeg blir ikke utbrent 🔥🤯</li><li>Mindre forhastede utgivelser 💨</li><li>Bedre dokumenter📚</li><li>Raskere og bedre støtte med problemer 🆘</li></ul> </br> 📧 Send meg en e-post til <a href='mailto:jokob@duck.com?subject=NetAlertX'>jokob@duck.com</a> hvis du ønsker å komme i kontakt eller hvis jeg skal legge til andre sponsorplattformer. </br>",
|
||||
@@ -489,7 +490,7 @@
|
||||
"Maintenance_lang_selector_empty": "Velg språk",
|
||||
"Maintenance_lang_selector_lable": "Velg språk",
|
||||
"Maintenance_lang_selector_text": "Endringen skjer på klientsiden, så den påvirker bare den nåværende nettleseren.",
|
||||
"Maintenance_new_version": "🆕 En ny versjon er tilgjengelig. Sjekk ut <a href=\"https://github.com/jokob-sk/NetAlertX/releases\" target=\"_blank\">utgivelsesnotater</a>.",
|
||||
"Maintenance_new_version": "En ny versjon er tilgjengelig. Sjekk ut <a href=\"https://github.com/jokob-sk/NetAlertX/releases\" target=\"_blank\">utgivelsesnotater</a>.",
|
||||
"Maintenance_themeselector_apply": "Bruk",
|
||||
"Maintenance_themeselector_empty": "Velg ett skinn",
|
||||
"Maintenance_themeselector_lable": "Velg skinn",
|
||||
@@ -716,6 +717,7 @@
|
||||
"general_event_title": "Utfører en ad-hoc hendelse",
|
||||
"go_to_node_event_icon": "",
|
||||
"go_to_node_event_tooltip": "",
|
||||
"new_version_available": "",
|
||||
"report_guid": "Notifikasjons GUID:",
|
||||
"report_guid_missing": "Koblet notifikasjon ikke funnet. Det er en liten forsinkelse mellom nylig sendt notifikasjoner og at de er tilgjengelige. Oppdater siden din og hurtigbufferen etter noen sekunder. Det er også mulig den valgte notifikasjonen er slettet under vedlikehold som spesifisert i <code>DBCLNP_NOTIFI_HIST</code> innstillingen. <br/> <br/> Den siste notifikasjonen vises i stedet. Den manglende notifikasjonen har følgende GUID:",
|
||||
"report_select_format": "Velg format:",
|
||||
@@ -750,4 +752,4 @@
|
||||
"settings_update_item_warning": "Oppdater verdien nedenfor. Pass på å følge forrige format. <b>Validering etterpå utføres ikke.</b>",
|
||||
"test_event_icon": "fa-vial-circle-check",
|
||||
"test_event_tooltip": "Lagre endringene først, før du tester innstillingene dine."
|
||||
}
|
||||
}
|
||||
@@ -155,9 +155,9 @@
|
||||
"DevDetail_Tab_NmapTableText": "Ustaw harmonogram w <a href=\"/settings.php#NMAP_ACTIVE\">Ustawieniach</a>",
|
||||
"DevDetail_Tab_NmapTableTime": "Czas",
|
||||
"DevDetail_Tab_Plugins": "<i class=\"fa fa-plug\"></i>Pluginy",
|
||||
"DevDetail_Tab_Presence": "<i class=\"fa fa-calendar\"></i> Obecność",
|
||||
"DevDetail_Tab_Sessions": "<i class=\"fa fa-list-ol\"></i> Sesje",
|
||||
"DevDetail_Tab_Tools": "<i class=\"fa fa-screwdriver-wrench\"></i> Narzędzia",
|
||||
"DevDetail_Tab_Presence": "Obecność",
|
||||
"DevDetail_Tab_Sessions": "Sesje",
|
||||
"DevDetail_Tab_Tools": "Narzędzia",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Description": "Narzędzie informacji o Internecie wyświetla informację o połączeniu z Internetem, takie jak adres IP, miasto, państwo, kod pocztowy i strefa czasowa.",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Error": "Wystąpił błąd",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Start": "Rozpocznij Informacje o Internecie",
|
||||
@@ -242,6 +242,7 @@
|
||||
"Device_Tablelenght": "Pokaż_wpisy_MENU",
|
||||
"Device_Tablelenght_all": "Wszystkie",
|
||||
"Device_Title": "Urządzenia",
|
||||
"Devices_Filters": "",
|
||||
"Donations_Others": "Inne",
|
||||
"Donations_Platforms": "Platforma Sponsora",
|
||||
"Donations_Text": "Cześć 👋! </br> Dziękuje że kliknąłeś w to menu 😅 </br> </br> Próbuje zebrać trochę dotacji by ulepszyć to oprogramowanie. Także pomaga mi to się nie wypalić bym dalej mógł ulepszać to narzędzie. Każdy mały (powtarzający się lub nie) sponsoring sprawia że chce wkładać więcej pracy w tą aplikację. </br> Chciałbym skrócić mój tydzień pracy i w wolnym czasie skupić się nad NetAlertX. Dostawalibyście wtedy więcej funkcjonalności i były by one ciągle udoskonalane i posiadające mniej błędów. </br> </br> Dziękuję że to przeczytałeś - Jestem wdzięczny za pomoc ❤🙏</br> </br> TL;DR: Wspierając mnie otrzymujesz: </br> </br> <ul><li> Regularne aktualizacje by zapewnić twoim danym i rodzinie bezpieczeństwo 🔄 </li><li>Mniej błędów (bugów) 🐛🔫</li><li>Nowe i lepsze funkcjonalności➕</li><li>Nie tracę zapału do dalszego tworzenia 🔥🤯</li><li>Mniej pośpieszne, bardziej dopracowane wydania💨</li><li>Lepsza dokumentacja📚</li><li>Szybsza i lepsza pomoc w problemach🆘</li></ul> </br>📧Napisz E-mail do mnie na<a href='mailto:jokob@duck.com?subject=NetAlertX'>jokob@duck.com</a> jeżeli chcesz nawiązać kontakt albo czy powinien dodać kolejną platformę z sponsoringiem.</br>",
|
||||
@@ -716,6 +717,7 @@
|
||||
"general_event_title": "Wykonywanie wydarzeń ad-hoc",
|
||||
"go_to_node_event_icon": "",
|
||||
"go_to_node_event_tooltip": "",
|
||||
"new_version_available": "",
|
||||
"report_guid": "Przewodnik powiadomień:",
|
||||
"report_guid_missing": "Linkowane powiadomienie nie znaleziono. Jest małe opóźnienie między wysłaniem powiadomienia a ich dostępnością. Odśwież stronę i cache za kilka sekund. Możliwe też że zaznaczone powiadomienie zostało usunięte podczas konserwacji tak jak określono w ustawieniu <code>DBCLNP_NOTIFI_HIST</code>. <br/><br/>Zamiast tego wyświetlane jest najnowsze powiadomienie. Brakujące powiadomienie ma następujące GUID:",
|
||||
"report_select_format": "Wybierz Format:",
|
||||
@@ -750,4 +752,4 @@
|
||||
"settings_update_item_warning": "Zaktualizuj poniższą wartość. Zachowaj ostrożność i postępuj zgodnie z poprzednim formatem. <b>Walidacja nie jest wykonywana.</b>",
|
||||
"test_event_icon": "fa-vial-circle-check",
|
||||
"test_event_tooltip": "Zapisz zmiany zanim będziesz testować swoje ustawienia."
|
||||
}
|
||||
}
|
||||
@@ -138,8 +138,8 @@
|
||||
"DevDetail_Shortcut_DownAlerts": "Alertas para baixo",
|
||||
"DevDetail_Shortcut_Presence": "Presença",
|
||||
"DevDetail_Shortcut_Sessions": "Sessões",
|
||||
"DevDetail_Tab_Details": "<i class=\"fa fa-info-circle\"></i> Detalhes",
|
||||
"DevDetail_Tab_Events": "<i class=\"fa fa-bolt\"></i> Eventos",
|
||||
"DevDetail_Tab_Details": "Detalhes",
|
||||
"DevDetail_Tab_Events": "Eventos",
|
||||
"DevDetail_Tab_EventsTableDate": "Data",
|
||||
"DevDetail_Tab_EventsTableEvent": "Tipo de evento",
|
||||
"DevDetail_Tab_EventsTableIP": "IP",
|
||||
@@ -155,9 +155,9 @@
|
||||
"DevDetail_Tab_NmapTableText": "Configure uma programação em <a href=\"/settings.php#NMAP_ACTIVE\">Configurações</a>",
|
||||
"DevDetail_Tab_NmapTableTime": "Tempo",
|
||||
"DevDetail_Tab_Plugins": "<i class=\"fa fa-plug\"> </i> Plugins",
|
||||
"DevDetail_Tab_Presence": "<i class=\"fa fa-calendar\"></i> Presença",
|
||||
"DevDetail_Tab_Sessions": "<i class=\"fa fa-list-ol\"></i> Sessões",
|
||||
"DevDetail_Tab_Tools": "<i class=\"fa fa-screwdriver-wrench\"></i> Ferramentas",
|
||||
"DevDetail_Tab_Presence": "Presença",
|
||||
"DevDetail_Tab_Sessions": "Sessões",
|
||||
"DevDetail_Tab_Tools": "Ferramentas",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Description": "A ferramenta info Internet exibe informações sobre a conexão com a Internet, como endereço IP, cidade, país, código de área e fuso horário.",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Error": "Um erro ocorreu",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Start": "Iniciar informações da Internet",
|
||||
@@ -242,6 +242,7 @@
|
||||
"Device_Tablelenght": "Mostrar entradas do _MENU_",
|
||||
"Device_Tablelenght_all": "Todos",
|
||||
"Device_Title": "Dispositivos",
|
||||
"Devices_Filters": "",
|
||||
"Donations_Others": "Outros",
|
||||
"Donations_Platforms": "Plataformas de patrocinadores",
|
||||
"Donations_Text": "Ei 👋! </br> Obrigado por clicar neste item de menu 😅 </br> </br> Estou tentando coletar algumas doações para melhorar o software. Além disso, isso me ajudaria a não ficar exausto, para que eu pudesse oferecer suporte a este aplicativo por mais tempo. Qualquer pequeno patrocínio (recorrente ou não) me faz querer colocar mais esforço neste aplicativo. </br> Eu adoraria encurtar minha semana de trabalho e no tempo restante focar totalmente no NetAlertX. Você obteria mais funcionalidades, um aplicativo mais sofisticado e menos bugs. </br> </br> Obrigado pela leitura - sou grato por qualquer apoio ❤🙏 </br> </br> TL;DR: Ao me apoiar, você obtém: </br> </br> <ul> <li>Atualizações regulares para manter seus dados e sua família seguros 🔄</li><li>Menos bugs 🐛🔫</li><li>Melhor e mais funcionalidade➕</li><li>Eu não fico exausto 🔥🤯</li><li>Lançamentos menos apressados 💨</li><li>Documentos melhores📚</li><li>Suporte melhor e mais rápido com problemas 🆘</li></ul> </br> 📧 Envie-me um e-mail para <a href='mailto:jokob@duck.com?subject=NetAlertX'>jokob@duck.com</a> se quiser entrar em contato ou se devo adicionar outras plataformas de patrocínio. </br>",
|
||||
@@ -716,6 +717,7 @@
|
||||
"general_event_title": "",
|
||||
"go_to_node_event_icon": "",
|
||||
"go_to_node_event_tooltip": "",
|
||||
"new_version_available": "",
|
||||
"report_guid": "",
|
||||
"report_guid_missing": "",
|
||||
"report_select_format": "",
|
||||
@@ -750,4 +752,4 @@
|
||||
"settings_update_item_warning": "",
|
||||
"test_event_icon": "",
|
||||
"test_event_tooltip": ""
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@
|
||||
"BackDevices_DBTools_ImportCSVError": "Не удалось импортировать файл CSV. Убедитесь, что формат правильный.",
|
||||
"BackDevices_DBTools_ImportCSVMissing": "CSV-файл не найден в<b>/config/devices.csv.</b>",
|
||||
"BackDevices_DBTools_Purge": "Самые старые резервные копии были удалены",
|
||||
"BackDevices_DBTools_UpdDev": "Устройство успешно обновлено",
|
||||
"BackDevices_DBTools_UpdDev": "Устройство успешно обновлено. При выполнении сканирования может потребоваться некоторое время для перезагрузки основного списка устройств.",
|
||||
"BackDevices_DBTools_UpdDevError": "Ошибка обновления устройства",
|
||||
"BackDevices_DBTools_Upgrade": "База данных успешно обновлена",
|
||||
"BackDevices_DBTools_UpgradeError": "Обновление базы данных не удалось",
|
||||
@@ -138,8 +138,8 @@
|
||||
"DevDetail_Shortcut_DownAlerts": "Оповещения о сбое",
|
||||
"DevDetail_Shortcut_Presence": "Присутствие",
|
||||
"DevDetail_Shortcut_Sessions": "Сеансы",
|
||||
"DevDetail_Tab_Details": "<i class=\"fa fa-info-circle\"></i> Детали",
|
||||
"DevDetail_Tab_Events": "<i class=\"fa fa-bolt\"></i> События",
|
||||
"DevDetail_Tab_Details": "Детали",
|
||||
"DevDetail_Tab_Events": "События",
|
||||
"DevDetail_Tab_EventsTableDate": "Дата",
|
||||
"DevDetail_Tab_EventsTableEvent": "Тип события",
|
||||
"DevDetail_Tab_EventsTableIP": "IP",
|
||||
@@ -154,10 +154,10 @@
|
||||
"DevDetail_Tab_NmapTableState": "Состояние",
|
||||
"DevDetail_Tab_NmapTableText": "Настройте расписание в <a href=\"/settings.php#NMAP_ACTIVE\">Настройки</a>",
|
||||
"DevDetail_Tab_NmapTableTime": "Время",
|
||||
"DevDetail_Tab_Plugins": "<i class=\"fa fa-plug\"></i> Плагины",
|
||||
"DevDetail_Tab_Presence": "<i class=\"fa fa-calendar\"></i> Присутствие",
|
||||
"DevDetail_Tab_Sessions": "<i class=\"fa fa-list-ol\"></i> Сеансы",
|
||||
"DevDetail_Tab_Tools": "<i class=\"fa fa-screwdriver-wrench\"></i> Инструменты",
|
||||
"DevDetail_Tab_Plugins": "Плагины",
|
||||
"DevDetail_Tab_Presence": "Присутствие",
|
||||
"DevDetail_Tab_Sessions": "Сеансы",
|
||||
"DevDetail_Tab_Tools": "Инструменты",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Description": "Инструмент «Информация об Интернете» отображает информацию о подключении к Интернету, такую как IP-адрес, город, страна, код города и часовой пояс.",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Error": "Произошла ошибка",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Start": "Показать инфо об Интернете",
|
||||
@@ -242,6 +242,7 @@
|
||||
"Device_Tablelenght": "Показать _MENU_ записей",
|
||||
"Device_Tablelenght_all": "Все",
|
||||
"Device_Title": "Устройства",
|
||||
"Devices_Filters": "Фильтры",
|
||||
"Donations_Others": "Другие",
|
||||
"Donations_Platforms": "Спонсорские платформы",
|
||||
"Donations_Text": "Привет 👋! </br> Спасибо, что нажали на этот пункт меню 😅 </br> </br> Я пытаюсь собрать пожертвования, чтобы сделать ваше программное обеспечение лучше. Кроме того, это поможет мне не перегореть, и я смогу дольше поддерживать это приложение. Любое небольшое спонсорство (периодическое или нет) вызывает у меня желание приложить больше усилий к этому приложению. </br> Мне бы хотелось сократить свою рабочую неделю и в оставшееся время полностью сосредоточиться на NetAlertX. Вы получите больше функциональности, более усовершенствованное приложение и меньше ошибок. </br> </br> Спасибо за прочтение – буду благодарен за любую поддержку❤🙏 </br> </br> TL;DR: Поддержав меня, вы получаете: </br> </br> <ul><li>Регулярные обновления для обеспечения безопасности ваших данных и семьи 🔄</li><li>Меньше ошибок 🐛🔫</li><li>Лучшую функциональность➕</li><li>Я не выгораю 🔥🤯</li><li>Меньше поспешных релизов 💨</li><li>Лучшая документация📚</li><li>Быстрее и лучше поддержка по вопросам 🆘</li></ul> </br> 📧Напишите мне на <a href='mailto:jokob@duck.com?subject=NetAlertX'>jokob@duck.com</a> если вы хотите связаться или если следует добавить другие спонсорские платформы. </br>",
|
||||
@@ -398,23 +399,23 @@
|
||||
"Maintenance_Running_Version": "Установленная версия",
|
||||
"Maintenance_Status": "Статус",
|
||||
"Maintenance_Title": "Инструменты обслуживания",
|
||||
"Maintenance_Tool_DownloadConfig": "",
|
||||
"Maintenance_Tool_DownloadConfig_text": "",
|
||||
"Maintenance_Tool_ExportCSV": "Экспорт CSV",
|
||||
"Maintenance_Tool_ExportCSV_noti": "Экспорт CSV",
|
||||
"Maintenance_Tool_DownloadConfig": "Экспорт настроек",
|
||||
"Maintenance_Tool_DownloadConfig_text": "Загрузите полную резервную копию конфигурации настроек, хранящуюся в файле <code>app.conf</code>.",
|
||||
"Maintenance_Tool_ExportCSV": "Экспорт устройств (csv)",
|
||||
"Maintenance_Tool_ExportCSV_noti": "Экспорт устройств (csv)",
|
||||
"Maintenance_Tool_ExportCSV_noti_text": "Вы уверены, что хотите создать файл CSV?",
|
||||
"Maintenance_Tool_ExportCSV_text": "Создайте файл CSV (значения, разделенные запятыми), содержащий список устройств, включая сетевые отношения между сетевыми узлами и подключенными устройствами. Вы также можете открыть этот URL-адрес <code> URL Вашего NetAlertX/php/server/devices.php?action=ExportCSV</code> или включить плагин <a href=\"settings.php#CSVBCKP_header\">Резервное копирование в CSV</a>.",
|
||||
"Maintenance_Tool_ImportCSV": "Импорт CSV",
|
||||
"Maintenance_Tool_ImportCSV_noti": "Импорт CSV",
|
||||
"Maintenance_Tool_ImportCSV": "Импорт устройств (csv)",
|
||||
"Maintenance_Tool_ImportCSV_noti": "Импорт устройств (csv)",
|
||||
"Maintenance_Tool_ImportCSV_noti_text": "Вы уверены, что хотите импортировать файл CSV? Это полностью <b>перезапишет</b> устройства в вашей базе данных.",
|
||||
"Maintenance_Tool_ImportCSV_text": "Прежде чем использовать эту функцию, сделайте резервную копию. Импортируйте файл CSV (значения, разделенные запятыми), содержащий список устройств, включая сетевые отношения между сетевыми узлами и подключенными устройствами. Для этого поместите файл CSV с именем <b>devices.csv</b> в папку <b>/config</b>.",
|
||||
"Maintenance_Tool_ImportConfig_noti": "",
|
||||
"Maintenance_Tool_ImportPastedCSV": "Импорт CSV (вставка)",
|
||||
"Maintenance_Tool_ImportConfig_noti": "Импорт настроек (app.conf)",
|
||||
"Maintenance_Tool_ImportPastedCSV": "Импорт устройств CSV (вставка)",
|
||||
"Maintenance_Tool_ImportPastedCSV_noti_text": "Вы уверены, что хотите импортировать вставленный CSV? Это полностью <b>перезапишет</b> устройства в вашей базе данных.",
|
||||
"Maintenance_Tool_ImportPastedCSV_text": "Перед использованием этой функции, пожалуйста, создайте резервную копию. Импортируйте файл формата CSV (значения, разделенные запятыми), содержащий список устройств, включая сетевые связи между узлами сети и подключенными устройствами.",
|
||||
"Maintenance_Tool_ImportPastedConfig": "",
|
||||
"Maintenance_Tool_ImportPastedConfig_noti_text": "",
|
||||
"Maintenance_Tool_ImportPastedConfig_text": "",
|
||||
"Maintenance_Tool_ImportPastedConfig": "Импорт настроек (вставка)",
|
||||
"Maintenance_Tool_ImportPastedConfig_noti_text": "Вы уверены, что хотите импортировать вставленные настройки конфигурации? Это полностью <b>перезапишет</b> файл <code>app.conf</code>.",
|
||||
"Maintenance_Tool_ImportPastedConfig_text": "Импорт файла <code>app.conf</code>, содержащего все настройки приложения. Возможно, вам захочется сначала загрузить текущий файл <code>app.conf</code> с помощью команды <b>Экспорт настроек</b>.",
|
||||
"Maintenance_Tool_arpscansw": "Переключить arp-скан (ВКЛ./ВЫКЛ.)",
|
||||
"Maintenance_Tool_arpscansw_noti": "Включить или выключить arp-скан",
|
||||
"Maintenance_Tool_arpscansw_noti_text": "Когда сканирование было выключено, оно остается выключенным до тех пор, пока не будет активировано снова.",
|
||||
@@ -489,7 +490,7 @@
|
||||
"Maintenance_lang_selector_empty": "Выберите язык",
|
||||
"Maintenance_lang_selector_lable": "Выбрать язык",
|
||||
"Maintenance_lang_selector_text": "Изменение происходит на стороне клиента, поэтому оно влияет только на текущий браузер.",
|
||||
"Maintenance_new_version": "🆕 Доступна новая версия. Ознакомьтесь с <a href=\"https://github.com/jokob-sk/NetAlertX/releases\" target=\"_blank\">примечаниями к выпуску</a>.",
|
||||
"Maintenance_new_version": "Доступна новая версия. Ознакомьтесь с <a href=\"https://github.com/jokob-sk/NetAlertX/releases\" target=\"_blank\">примечаниями к выпуску</a>.",
|
||||
"Maintenance_themeselector_apply": "Применить",
|
||||
"Maintenance_themeselector_empty": "Выбрать скин",
|
||||
"Maintenance_themeselector_lable": "Выбрать Скин",
|
||||
@@ -716,6 +717,7 @@
|
||||
"general_event_title": "Выполнение специального события",
|
||||
"go_to_node_event_icon": "fa-square-up-right",
|
||||
"go_to_node_event_tooltip": "Переход на страницу \"Сеть\" данного узла",
|
||||
"new_version_available": "Доступна новая версия.",
|
||||
"report_guid": "Идентификатор уведомления:",
|
||||
"report_guid_missing": "Связанное уведомление не найдено. Между недавно отправленными уведомлениями и их доступностью существует небольшая задержка. Обновите страницу и кэшируйте ее через несколько секунд. Также возможно, что выбранное уведомление было удалено во время обслуживания, как указано в настройке <code>DBCLNP_NOTIFI_HIST</code>. <br/> <br/>Вместо этого отображается последнее уведомление. Отсутствующее уведомление имеет следующий GUID:",
|
||||
"report_select_format": "Выбрать формат:",
|
||||
@@ -750,4 +752,4 @@
|
||||
"settings_update_item_warning": "Обновить значение ниже. Будьте осторожны, следуя предыдущему формату. <b>Проверка не выполняется.</b>",
|
||||
"test_event_icon": "fa-vial-circle-check",
|
||||
"test_event_tooltip": "Сначала сохраните изменения, прежде чем проверять настройки."
|
||||
}
|
||||
}
|
||||
@@ -138,7 +138,7 @@
|
||||
"DevDetail_Shortcut_DownAlerts": "",
|
||||
"DevDetail_Shortcut_Presence": "",
|
||||
"DevDetail_Shortcut_Sessions": "Oturumlar",
|
||||
"DevDetail_Tab_Details": "<i class=\"fa fa-info-circle\"></i> Detaylar",
|
||||
"DevDetail_Tab_Details": "Detaylar",
|
||||
"DevDetail_Tab_Events": "",
|
||||
"DevDetail_Tab_EventsTableDate": "Tarih",
|
||||
"DevDetail_Tab_EventsTableEvent": "",
|
||||
@@ -156,7 +156,7 @@
|
||||
"DevDetail_Tab_NmapTableTime": "Zaman",
|
||||
"DevDetail_Tab_Plugins": "",
|
||||
"DevDetail_Tab_Presence": "",
|
||||
"DevDetail_Tab_Sessions": "<i class=\"fa fa-list-ol\"></i> Oturumlar",
|
||||
"DevDetail_Tab_Sessions": "Oturumlar",
|
||||
"DevDetail_Tab_Tools": "",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Description": "",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Error": "Bir hata oluştu",
|
||||
@@ -242,6 +242,7 @@
|
||||
"Device_Tablelenght": "",
|
||||
"Device_Tablelenght_all": "",
|
||||
"Device_Title": "Cihazlar",
|
||||
"Devices_Filters": "",
|
||||
"Donations_Others": "Diğerleri",
|
||||
"Donations_Platforms": "",
|
||||
"Donations_Text": "",
|
||||
@@ -716,6 +717,7 @@
|
||||
"general_event_title": "",
|
||||
"go_to_node_event_icon": "",
|
||||
"go_to_node_event_tooltip": "",
|
||||
"new_version_available": "",
|
||||
"report_guid": "",
|
||||
"report_guid_missing": "",
|
||||
"report_select_format": "",
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"BackDevices_DBTools_ImportCSVError": "Не вдалося імпортувати файл CSV. Переконайтеся, що формат правильний.",
|
||||
"BackDevices_DBTools_ImportCSVMissing": "Не вдалося знайти файл CSV у <b>/config/devices.csv.</b>",
|
||||
"BackDevices_DBTools_Purge": "Найстаріші резервні копії видалено",
|
||||
"BackDevices_DBTools_UpdDev": "Пристрій успішно оновлено",
|
||||
"BackDevices_DBTools_UpdDev": "Пристрій успішно оновлено. Перезавантаження основного списку пристроїв може знадобитися деякий час, якщо триває сканування.",
|
||||
"BackDevices_DBTools_UpdDevError": "Помилка оновлення пристрою",
|
||||
"BackDevices_DBTools_Upgrade": "Базу даних успішно оновлено",
|
||||
"BackDevices_DBTools_UpgradeError": "Не вдалося оновити базу даних",
|
||||
@@ -138,8 +138,8 @@
|
||||
"DevDetail_Shortcut_DownAlerts": "Сповіщення про падіння",
|
||||
"DevDetail_Shortcut_Presence": "Присутність",
|
||||
"DevDetail_Shortcut_Sessions": "Сеанси",
|
||||
"DevDetail_Tab_Details": "<i class=\"fa fa-info-circle\"></i> Подробиці",
|
||||
"DevDetail_Tab_Events": "<i class=\"fa fa-bolt\"></i> Події",
|
||||
"DevDetail_Tab_Details": "Подробиці",
|
||||
"DevDetail_Tab_Events": "Події",
|
||||
"DevDetail_Tab_EventsTableDate": "Дата",
|
||||
"DevDetail_Tab_EventsTableEvent": "Тип події",
|
||||
"DevDetail_Tab_EventsTableIP": "IP",
|
||||
@@ -154,10 +154,10 @@
|
||||
"DevDetail_Tab_NmapTableState": "Держава",
|
||||
"DevDetail_Tab_NmapTableText": "Налаштуйте розклад у <a href=\"/settings.php#NMAP_ACTIVE\">Settings</a>",
|
||||
"DevDetail_Tab_NmapTableTime": "час",
|
||||
"DevDetail_Tab_Plugins": "<i class=\"fa fa-plug\"></i> Плагіни",
|
||||
"DevDetail_Tab_Presence": "<i class=\"fa fa-calendar\"></i> Присутність",
|
||||
"DevDetail_Tab_Sessions": "<i class=\"fa fa-list-ol\"></i> Сеанси",
|
||||
"DevDetail_Tab_Tools": "<i class=\"fa fa-screwdriver-wrench\"></i> Інструменти",
|
||||
"DevDetail_Tab_Plugins": "Плагіни",
|
||||
"DevDetail_Tab_Presence": "Присутність",
|
||||
"DevDetail_Tab_Sessions": "Сеанси",
|
||||
"DevDetail_Tab_Tools": "Інструменти",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Description": "Інструмент Інтернет-інформації відображає інформацію про підключення до Інтернету, таку як IP-адреса, місто, країна, код міста та часовий пояс.",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Error": "Сталася помилка",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Start": "Запустіть Internet Info",
|
||||
@@ -242,6 +242,7 @@
|
||||
"Device_Tablelenght": "Показати записи _МЕНЮ_",
|
||||
"Device_Tablelenght_all": "все",
|
||||
"Device_Title": "Пристрої",
|
||||
"Devices_Filters": "Фільтри",
|
||||
"Donations_Others": "інші",
|
||||
"Donations_Platforms": "Спонсорські платформи",
|
||||
"Donations_Text": "Привіт 👋! </br> Дякуємо, що натиснули цей пункт меню 😅 </br> </br> Я намагаюся зібрати пожертви, щоб зробити ваше програмне забезпечення кращим. Крім того, це допоможе мені не згоріти, тому я можу підтримувати цю програму довше. Будь-яке невелике (постійне чи ні) спонсорство спонукає мене докласти більше зусиль до цієї програми. </br> Я хотів би скоротити свій робочий тиждень і в час, що залишився, повністю зосередитися на NetAlertX. Ви отримаєте більше функціональності, досконаліший додаток і менше помилок. </br> </br> Дякую, що прочитали – я вдячний за будь-яку підтримку ❤🙏 </br> </br> TL;DR: Підтримуючи мене, ви отримуєте: </br> </br> <ul> <li>Регулярні оновлення для захисту ваших даних і родини 🔄</li><li>Менше помилок 🐛🔫</li><li>Краще та більше функціональність➕</li><li>Я не згорів 🔥🤯</li><li>Менш поспішних випусків 💨</li><li>Кращі документи📚</li><li>Швидша та краща підтримка із проблемами 🆘</li></ul> </br> 📧Напишіть мені електронною поштою на <a href='mailto:jokob@duck.com?subject=NetAlertX'>jokob@duck.com</a>, якщо ви хочете зв’язатися з нами або якщо я маю додати інші платформи спонсорства. </br>",
|
||||
@@ -489,7 +490,7 @@
|
||||
"Maintenance_lang_selector_empty": "Виберіть мову",
|
||||
"Maintenance_lang_selector_lable": "Виберіть мови",
|
||||
"Maintenance_lang_selector_text": "Зміна відбувається на стороні клієнта, тому вона впливає лише на поточний браузер.",
|
||||
"Maintenance_new_version": "🆕 Доступна нова версія. Перегляньте <a href=\"https://github.com/jokob-sk/NetAlertX/releases\" target=\"_blank\">примітки до випуску</a>.",
|
||||
"Maintenance_new_version": "Доступна нова версія. Перегляньте <a href=\"https://github.com/jokob-sk/NetAlertX/releases\" target=\"_blank\">примітки до випуску</a>.",
|
||||
"Maintenance_themeselector_apply": "Застосувати",
|
||||
"Maintenance_themeselector_empty": "Виберіть скін",
|
||||
"Maintenance_themeselector_lable": "Виберіть Скін",
|
||||
@@ -716,6 +717,7 @@
|
||||
"general_event_title": "Виконання спеціальної події",
|
||||
"go_to_node_event_icon": "fa-квадрат-вгору-вправо",
|
||||
"go_to_node_event_tooltip": "Перейдіть на сторінку Мережа даного вузла",
|
||||
"new_version_available": "Доступна нова версія.",
|
||||
"report_guid": "Довідник сповіщень:",
|
||||
"report_guid_missing": "Пов’язане сповіщення не знайдено. Існує невелика затримка між нещодавно надісланими сповіщеннями та їх доступністю. Оновіть сторінку та кеш через кілька секунд. Також можливо, вибране сповіщення було видалено під час обслуговування, як зазначено в параметрі <code>DBCLNP_NOTIFI_HIST</code>. <br/> <br/>Натомість відображається останнє сповіщення. Відсутнє сповіщення має такий GUID:",
|
||||
"report_select_format": "Виберіть формат:",
|
||||
@@ -750,4 +752,4 @@
|
||||
"settings_update_item_warning": "Оновіть значення нижче. Слідкуйте за попереднім форматом. <b>Перевірка не виконана.</b>",
|
||||
"test_event_icon": "fa-vial-circle- check",
|
||||
"test_event_tooltip": "Перш ніж перевіряти налаштування, збережіть зміни."
|
||||
}
|
||||
}
|
||||
@@ -138,8 +138,8 @@
|
||||
"DevDetail_Shortcut_DownAlerts": "下线警报",
|
||||
"DevDetail_Shortcut_Presence": "存在",
|
||||
"DevDetail_Shortcut_Sessions": "会话",
|
||||
"DevDetail_Tab_Details": "<i class=\"fa fa-info-circle\"></i> 详细信息",
|
||||
"DevDetail_Tab_Events": "<i class=\"fa fa-bolt\"></i> 事件",
|
||||
"DevDetail_Tab_Details": "详细信息",
|
||||
"DevDetail_Tab_Events": "事件",
|
||||
"DevDetail_Tab_EventsTableDate": "日期",
|
||||
"DevDetail_Tab_EventsTableEvent": "事件类型",
|
||||
"DevDetail_Tab_EventsTableIP": "IP",
|
||||
@@ -154,10 +154,10 @@
|
||||
"DevDetail_Tab_NmapTableState": "状态",
|
||||
"DevDetail_Tab_NmapTableText": "<a href=\"/settings.php#NMAP_ACTIVE\">设置</a>时间表",
|
||||
"DevDetail_Tab_NmapTableTime": "时间",
|
||||
"DevDetail_Tab_Plugins": "<i class=\"fa fa-plug\"></i> 插件",
|
||||
"DevDetail_Tab_Presence": "<i class=\"fa fa-calendar\"></i> 存在",
|
||||
"DevDetail_Tab_Sessions": "<i class=\"fa fa-list-ol\"></i> 会话",
|
||||
"DevDetail_Tab_Tools": "<i class=\"fa fa-screwdriver-wrench\"></i> 工具",
|
||||
"DevDetail_Tab_Plugins": "插件",
|
||||
"DevDetail_Tab_Presence": "存在",
|
||||
"DevDetail_Tab_Sessions": "会话",
|
||||
"DevDetail_Tab_Tools": "工具",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Description": "互联网信息工具显示有关互联网连接的信息,例如 IP 地址、城市、国家、区号和时区。",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Error": "发生了错误",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Start": "开始互联网信息",
|
||||
@@ -242,6 +242,7 @@
|
||||
"Device_Tablelenght": "显示 _MENU_ 页",
|
||||
"Device_Tablelenght_all": "所有",
|
||||
"Device_Title": "设备",
|
||||
"Devices_Filters": "",
|
||||
"Donations_Others": "其他",
|
||||
"Donations_Platforms": "赞助平台",
|
||||
"Donations_Text": "嘿👋!</br> 感谢您点击此菜单项😅 </br> </br> 我正在尝试收集一些捐款来为您制作更好的软件。此外,这将有助于我避免精疲力竭,这样我就可以更长时间地支持这个应用程序。任何小额(无论是否经常性)赞助都会让我想在这个应用程序上投入更多精力。</br> 我希望缩短我的工作周,并在剩余的时间里完全专注于 NetAlertX。您将获得更多功能、更精致的应用程序和更少的错误。 </br> </br> 感谢阅读 - 我感谢任何支持 ❤🙏 </br> </br> TL;DR:通过支持我,您将获得:</br> </br> <ul><li>定期更新以确保您的数据和家人安全🔄</li><li>更少的错误🐛🔫</li><li>更好、更多的功能➕</li><li>我不会精疲力竭🔥🤯</li><li>更不仓促的发布💨</li><li>更好的文档📚</li><li>更快、更好地解决问题🆘</li></ul> </br> 📧如果您想联系我或者我应该添加其他赞助平台,请给我发电子邮件至<a href='mailto:jokob@duck.com?subject=NetAlertX'>jokob@duck.com</a>。 </br>",
|
||||
@@ -489,7 +490,7 @@
|
||||
"Maintenance_lang_selector_empty": "选择语言",
|
||||
"Maintenance_lang_selector_lable": "选择语言",
|
||||
"Maintenance_lang_selector_text": "该更改发生在客户端,因此只影响当前浏览器。",
|
||||
"Maintenance_new_version": "🆕 有新版本可用。查看<a href=\"https://github.com/jokob-sk/NetAlertX/releases\" target=\"_blank\">发行说明</a>。",
|
||||
"Maintenance_new_version": "有新版本可用。查看<a href=\"https://github.com/jokob-sk/NetAlertX/releases\" target=\"_blank\">发行说明</a>。",
|
||||
"Maintenance_themeselector_apply": "应用",
|
||||
"Maintenance_themeselector_empty": "选择皮肤",
|
||||
"Maintenance_themeselector_lable": "选择皮肤",
|
||||
@@ -716,6 +717,7 @@
|
||||
"general_event_title": "执行自组织网络事件",
|
||||
"go_to_node_event_icon": "",
|
||||
"go_to_node_event_tooltip": "",
|
||||
"new_version_available": "",
|
||||
"report_guid": "通知guid:",
|
||||
"report_guid_missing": "未找到链接的通知。最近发送的通知与可用通知之间存在短暂延迟。几秒钟后刷新页面并缓存。所选通知也可能已在维护期间被删除,如 <code>DBCLNP_NOTIFI_HIST</code> 设置中所述。<br/> <br/>系统将改为显示最新通知。缺失的通知具有以下 GUID:",
|
||||
"report_select_format": "选择格式:",
|
||||
@@ -750,4 +752,4 @@
|
||||
"settings_update_item_warning": "更新下面的值。请注意遵循先前的格式。<b>未执行验证。</b>",
|
||||
"test_event_icon": "",
|
||||
"test_event_tooltip": "在测试设置之前,请先保存更改。"
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ $configFolderPath = dirname(__FILE__)."/../../../config/";
|
||||
$config_file = "app.conf";
|
||||
$logFolderPath = "/app/log/";
|
||||
$log_file = "app_front.log";
|
||||
$default_tz = "Europe/Berlin";
|
||||
|
||||
|
||||
$fullConfPath = $configFolderPath.$config_file;
|
||||
@@ -29,7 +30,13 @@ foreach ($config_file_lines as $line)
|
||||
|
||||
if($timeZone == "")
|
||||
{
|
||||
$timeZone = "Europe/Berlin";
|
||||
$timeZone = $default_tz;
|
||||
}
|
||||
|
||||
// Validate the timezone
|
||||
if (!in_array($timeZone, timezone_identifiers_list())) {
|
||||
error_log("Invalid timezone '$timeZone' in config. Falling back to default: '$default_tz' ");
|
||||
$timeZone = $default_tz;
|
||||
}
|
||||
|
||||
date_default_timezone_set($timeZone);
|
||||
|
||||
@@ -7,20 +7,9 @@
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
<div class="content-wrapper integrations-plugins">
|
||||
|
||||
<!-- Content header--------------------------------------------------------- -->
|
||||
<section class="content-header">
|
||||
|
||||
<h1 id="pageTitle">
|
||||
<i class="fa fa-fw fa-plug"></i> <?= lang('Navigation_Plugins');?>
|
||||
<span class="pageHelp"> <a target="_blank" href="https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins"><i class="fa fa-circle-question"></i></a><span>
|
||||
</h1>
|
||||
</section>
|
||||
|
||||
|
||||
<?php
|
||||
require 'pluginsCore.php';
|
||||
?>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -28,8 +28,9 @@ Device-detecting plugins insert values into the `CurrentScan` database table. T
|
||||
| `APPRISE` | ▶️ | Apprise notification proxy | | | Script | [_publisher_apprise](/front/plugins/_publisher_apprise/) |
|
||||
| `ARPSCAN` | 🔍 | ARP-scan on current network | | | Script | [arp_scan](/front/plugins/arp_scan/) |
|
||||
| `AVAHISCAN` | 🆎 | Avahi (mDNS-based) name resolution | | | Script | [avahi_scan](/front/plugins/avahi_scan/) |
|
||||
| `ASUSWRT` | 🔍 | Import connected devices from AsusWRT | | | Script | [asuswrt_import](/front/plugins/asuswrt_import/) |
|
||||
| `CSVBCKP` | ⚙ | CSV devices backup | | | Script | [csv_backup](/front/plugins/csv_backup/) |
|
||||
| `CUSTPROP` | ⚙ | Managing custom device properties values | | Yes | Template | [custom_props](/front/plugins/custom_props/) |
|
||||
| `CUSTPROP` | ⚙ | Managing custom device properties values | | Yes | Template | [custom_props](/front/plugins/custom_props/) |
|
||||
| `DBCLNP` | ⚙ | Database cleanup | | Yes* | Script | [db_cleanup](/front/plugins/db_cleanup/) |
|
||||
| `DDNS` | ⚙ | DDNS update | | | Script | [ddns_update](/front/plugins/ddns_update/) |
|
||||
| `DHCPLSS` | 🔍/📥/🆎| Import devices from DHCP leases | | | Script | [dhcp_leases](/front/plugins/dhcp_leases/) |
|
||||
@@ -39,8 +40,7 @@ Device-detecting plugins insert values into the `CurrentScan` database table. T
|
||||
| `INTRNT` | 🔍 | Internet IP scanner | | | Script | [internet_ip](/front/plugins/internet_ip/) |
|
||||
| `INTRSPD` | ♻ | Internet speed test | | | Script | [internet_speedtest](/front/plugins/internet_speedtest/) |
|
||||
| `IPNEIGH` | 🔍 | Scan ARP (IPv4) and NDP (IPv6) tables | | | Script | [ipneigh](/front/plugins/ipneigh/) |
|
||||
| `LUCIRPC` | 🔍 | Import connected devices from OpenWRT | | | Script | [luci_import](/front/plugins/luci_import/) |
|
||||
| `ASUSWRT` | 🔍 | Import connected devices from AsusWRT | | | Script | [asuswrt_import](/front/plugins/asuswrt_import/) |
|
||||
| `LUCIRPC` | 🔍 | Import connected devices from OpenWRT | | | Script | [luci_import](/front/plugins/luci_import/) |
|
||||
| `MAINT` | ⚙ | Maintenance of logs, etc. | | | Script | [maintenance](/front/plugins/maintenance/) |
|
||||
| `MQTT` | ▶️ | MQTT for synching to Home Assistant | | | Script | [_publisher_mqtt](/front/plugins/_publisher_mqtt/) |
|
||||
| `NBTSCAN` | 🆎 | Nbtscan (NetBIOS-based) name resolution | | | Script | [nbtscan_scan](/front/plugins/nbtscan_scan/) |
|
||||
@@ -57,10 +57,9 @@ Device-detecting plugins insert values into the `CurrentScan` database table. T
|
||||
| `SETPWD` | ⚙ | Set password | | Yes | Template | [set_password](/front/plugins/set_password/) |
|
||||
| `SMTP` | ▶️ | Email notifications | | | Script | [_publisher_email](/front/plugins/_publisher_email/) |
|
||||
| `SNMPDSC` | 🔍/📥 | SNMP device import & sync | | | Script | [snmp_discovery](/front/plugins/snmp_discovery/) |
|
||||
| `SYNC` | 🔍/⚙/📥| Sync & import from NetAlertX instances | 🖧 🔄 | Yes | Script | [sync](/front/plugins/sync/) |
|
||||
| `SYNC` | 🔍/⚙/📥| Sync & import from NetAlertX instances | 🖧 🔄 | Yes | Script | [sync](/front/plugins/sync/) |
|
||||
| `TELEGRAM` | ▶️ | Telegram notifications | | | Script | [_publisher_telegram](/front/plugins/_publisher_telegram/) |
|
||||
| `UI` | ♻ | UI specific settings | | Yes | Template | [ui_settings](/front/plugins/ui_settings/) |
|
||||
| `UNDIS` | 🔍/📥 | Create dummy devices ❌ | | | Script | [undiscoverables](/front/plugins/undiscoverables/) |
|
||||
| `UNFIMP` | 🔍/📥/🆎| UniFi device import & sync | 🖧 | | Script | [unifi_import](/front/plugins/unifi_import/) |
|
||||
| `VNDRPDT` | ⚙ | Vendor database update | | | Script | [vendor_update](/front/plugins/vendor_update/) |
|
||||
| `WEBHOOK` | ▶️ | Webhook notifications | | | Script | [_publisher_webhook](/front/plugins/_publisher_webhook/) |
|
||||
@@ -69,7 +68,6 @@ Device-detecting plugins insert values into the `CurrentScan` database table. T
|
||||
|
||||
|
||||
> \* The database cleanup plugin (`DBCLNP`) is not _required_ but the app will become unusable after a while if not executed.
|
||||
> \** The Undiscoverables plugin (`UNDIS`) inserts only user-specified dummy devices.
|
||||
> ❌ marked for removal
|
||||
> ⌚It's recommended to use the same schedule interval for all plugins responsible for discovering new devices.
|
||||
|
||||
|
||||
@@ -20,4 +20,8 @@ To set up the plugin correctly, make sure...
|
||||
|
||||
### Notes
|
||||
|
||||
- Additional notes, limitations, Author info.
|
||||
- Additional notes, limitations, Author info.
|
||||
|
||||
- Version: 1.0.0
|
||||
- Author: `<your github handle>`
|
||||
- Release Date: `<release date>`
|
||||
@@ -8,13 +8,16 @@ This Plugin is using awesome [asusrouter](https://github.com/Vaskivskyi/asusrout
|
||||
|
||||
- Enable the `ASUSWRT` plugin
|
||||
- Head to **Settings** > **AsusWRT device import** to adjust the default values.
|
||||
- If you have troubles configuring the plugin set the `LOG_LEVEL='debug'` to get a more detailed error message.
|
||||
|
||||
### Notes
|
||||
|
||||
- In case an existing imported device is renamed in Asus Router it will not be renamed in NetAlertX. In this case it has to be done manually or the device should be removed and it will appear on the next scan.
|
||||
- In case an existing imported device is renamed in Asus Router it will not be renamed in NetAlertX. In this case it has to be done manually or the device should be removed and it will appear on the next scan.
|
||||
- Only clients listed in the main AsusWRT interface are imported. If using plugins, such as the `YazFi plugin`, check the [Asus routers DHCPLSS guide](/front/plugins/dhcp_leases/ASUS_ROUTERS.md) for a possible workaround.
|
||||
|
||||
## Other info
|
||||
|
||||
Version: 1.0.0
|
||||
Date: 16.1.2025
|
||||
Author: @labmonkey
|
||||
- Version: 1.0.0
|
||||
- Author: [labmonkey](https://github.com/labmonkey)
|
||||
- Release Date: 16.1.2025
|
||||
|
||||
|
||||
@@ -207,7 +207,7 @@
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Router ip(do not include <code>http://</code> or <code>https://</code>)."
|
||||
"string": "Router ip(do not include port, <code>http://</code> or <code>https://</code>)."
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -242,6 +242,37 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "port",
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": [
|
||||
"name",
|
||||
"description"
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Router port"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Router port. Leave empty for default."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "password",
|
||||
"type": {
|
||||
|
||||
@@ -77,8 +77,13 @@ def get_device_data():
|
||||
# Create aiohttp session
|
||||
session = aiohttp.ClientSession(loop=loop)
|
||||
|
||||
port = get_setting_value("ASUSWRT_port").strip()
|
||||
|
||||
mylog("verbose", [f"[{pluginName}] Connecting to the Router..."])
|
||||
|
||||
router = AsusRouter( # required - both IP and URL supported
|
||||
hostname=get_setting_value("ASUSWRT_host"), # required
|
||||
port=(None if not port else port), # optional
|
||||
username=get_setting_value("ASUSWRT_user"), # required
|
||||
password=get_setting_value("ASUSWRT_password"), # required
|
||||
use_ssl=get_setting_value("ASUSWRT_ssl"), # optional
|
||||
@@ -86,14 +91,9 @@ def get_device_data():
|
||||
)
|
||||
|
||||
# Connect to the router
|
||||
# Throws an error in case of failure
|
||||
loop.run_until_complete(router.async_connect())
|
||||
|
||||
if router.connected:
|
||||
mylog("verbose", [f"[{pluginName}] logged in successfully."])
|
||||
else:
|
||||
mylog("error", [f"[{pluginName}] failed to login."])
|
||||
return []
|
||||
|
||||
# Now you can use the router object to call methods
|
||||
clients = loop.run_until_complete(router.async_get_data(AsusData.CLIENTS))
|
||||
|
||||
|
||||
96
front/plugins/dhcp_leases/ASUS_ROUTERS.md
Executable file
96
front/plugins/dhcp_leases/ASUS_ROUTERS.md
Executable file
@@ -0,0 +1,96 @@
|
||||
# Configuring the `DHCPLSS` plugin to import clients from the YazFi plugin
|
||||
|
||||
## Requirements:
|
||||
|
||||
1. Only for ASUS routers with the Merlin FW and Entware installed
|
||||
2. You have guest networks modified with the YazFi pluginwith unidirectional communication from the private network to the guest network configured:
|
||||
- One way to guest: Yes
|
||||
|
||||
## Problem: Clients inaccessible with the Asus API:
|
||||
|
||||
- When using YazFi on an ASUS router, the guest clients will no longer be displayed in the regular client list
|
||||
- The guests are logged in the YazFi plugin and the networks are in an advanced mode
|
||||
- The `ASUSWRT` plugin by [labmonkey](https://github.com/labmonkey) can only access the clients from the Asus client list but not the guests in the YazFi plugin
|
||||
|
||||
## Solution: Getting the `dnsmasq.leases` from the Asus router and configuriong the `DHCPLSS` plugin:
|
||||
|
||||
1. Enable SSH login on your Asus router
|
||||
2. Generate a pair of SSH keys and place them inside `/root/.ssh/`
|
||||
3. In your router's admin-settings, paste the public key and disable "password login" for SSH
|
||||
4. On your docker machine, create a script (I placed it in /home/root):
|
||||
- Replace the IP if necessary.
|
||||
- Replace `ssh2_privateKey` and `asususer` with your keyfile and your routers login name.
|
||||
- Replace `/mnt/service-data/netalertx_dhcp.leases/` with your preferred save path inside the docker machine.
|
||||
|
||||
`nano grabdnsmasq.sh`
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
rsync -avzh -e "ssh -i /root/.ssh/ssh2_privateKey" asususer@192.168.1.1:/var/lib/misc/dnsmasq.leases /mnt/service-data/netalertx_dhcp.leases/
|
||||
```
|
||||
|
||||
5. Create a config file in `/root/.ssh/`:
|
||||
|
||||
- Again, replace the IP, the SSH key and the user and also the port if necessary
|
||||
|
||||
```
|
||||
Host ASUS-GT-AXE16000
|
||||
HostName 192.168.1.1
|
||||
IdentityFile /root/.ssh/ssh2_privateKey
|
||||
IdentitiesOnly yes
|
||||
User asususer
|
||||
Port 22
|
||||
```
|
||||
6. Try a dry run with the command in step 4. If everything is fine, you should have a `dnsmasq.leases` file at your target location
|
||||
7. Edit crontab for root:
|
||||
|
||||
`crontab -e`
|
||||
|
||||
add your scheduled time and the path to your script file:
|
||||
|
||||
`*/2 * * * * /root/grabdnsmasq.sh`
|
||||
|
||||
8. Save and reload the cron service:
|
||||
|
||||
`service cron reload`
|
||||
|
||||
9. Load the `DHCPLSS` plugin in NetAlertX and add the newly generated dhcp.leases file into the container with a path that must contain the string `dnsmasq`. An example of the mount point could be:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- /mnt/service-data/netalertx_dhcp.leases:/etc/dnsmasq
|
||||
...
|
||||
```
|
||||
|
||||
10. Load the `DHCPLSS` plugin and add the search path: `/etc/dnsmasq/dnsmasq.leases`
|
||||
|
||||
Configure the plugin, and save everything. You can trigger a manual run.
|
||||
|
||||
> [!NOTE]
|
||||
> DHCP leases don't allow for realtime tracking and the freshness of the data depends on the DHCP leasing time (usually set to 1 or 24h, or 3600 to 86400 seconds).
|
||||
|
||||
For a Docker LXC setup the file could be located at `/mnt/service-data/netalertx_dhcp.leases/dnsmasq.leases`.
|
||||
|
||||
## Quick setup overview:
|
||||
|
||||
```python
|
||||
DHCPLSS_RUN: 'schedule'
|
||||
DHCPLSS_CMD: 'python3 /app/front/plugins/dhcp_leases/script.py paths={paths}'
|
||||
DHCPLSS_paths_to_check: ['/etc/dnsmasq/dnsmasq.leases']
|
||||
DHCPLSS_RUN_SCHD: '*/5 * * * *'
|
||||
DHCPLSS_TUN_TIMEOUT: 5
|
||||
DHCPLSS_WATCH: ['Watched_Value1', 'Watched_Value4']
|
||||
DHCPLSS_REPORT_ON: ['new', 'watched_changed']
|
||||
```
|
||||
|
||||
You can check the the `dnsmasq.leases` file in the container by running `ls /etc/dnsmasq/`:
|
||||
|
||||
```bash
|
||||
CT_NetAlertX:/# ls /etc/dnsmasq/
|
||||
dnsmasq.leases
|
||||
```
|
||||
|
||||
## Other Info
|
||||
|
||||
- Publishing date: 22.1.2025
|
||||
- Author: [EinKantHolz - odin](https://github.com/EinKantHolz)
|
||||
@@ -50,7 +50,8 @@ If the Freebox is your gateway you need to find its HTTPS (or HTTP if you prefer
|
||||
As address, you can either use the public IP of the Freebox, or the unique domain name you found on http://mafreebox.freebox.fr:80/api_version listed as `api_domain`.
|
||||
|
||||
|
||||
### Other info
|
||||
## Other info
|
||||
|
||||
- Author : [KayJay7](https://github.com/KayJay7) & [Lucide](https://github.com/Lucide)
|
||||
- Date : 2-Dec-2024 - version 1.0
|
||||
- Version: 1.0
|
||||
- Author: [KayJay7](https://github.com/KayJay7) & [Lucide](https://github.com/Lucide)
|
||||
- Release Date: 2-Dec-2024
|
||||
@@ -5,3 +5,9 @@ Plugin for pinging existing devices via the [ping](https://linux.die.net/man/8/p
|
||||
### Usage
|
||||
|
||||
- Check the Settings page for details.
|
||||
|
||||
## Other info
|
||||
|
||||
- Version: 1.0.0
|
||||
- Author: [jokob-sk](https://github.com/jokob-sk)
|
||||
- Release Date: 19.1.2025
|
||||
|
||||
@@ -8,3 +8,10 @@ Plugin to run regular Internet connectivity and IP checks. Change the [dig utili
|
||||
### Usage
|
||||
|
||||
- Check the Settings page for details.
|
||||
|
||||
|
||||
## Other info
|
||||
|
||||
- Version: 1.0.0
|
||||
- Author: [jokob-sk](https://github.com/jokob-sk)
|
||||
- Release Date: 1.1.2023
|
||||
@@ -68,8 +68,6 @@ def main():
|
||||
|
||||
new_internet_IP, cmd_output = check_internet_IP( PREV_IP, DIG_GET_IP_ARG)
|
||||
|
||||
#todo: use `curl ifconfig.me/ip` if above fails
|
||||
|
||||
if new_internet_IP == no_internet_ip:
|
||||
time.sleep(1*i) # Exponential backoff strategy
|
||||
else:
|
||||
|
||||
@@ -21,7 +21,8 @@ To set up the plugin correctly, make sure to add in the plugin settings the name
|
||||
- `ARPSCAN` does a better job at discovering IPv4 devices because it explicitly sends arp requests
|
||||
- IPv6 devices will often have multiple addresses, but the ping answer will contain only one. This means that in general this plugin will not discover every address but only those who answer
|
||||
|
||||
### Other info
|
||||
## Other info
|
||||
|
||||
- Author : [KayJay7](https://github.com/KayJay7)
|
||||
- Date : 31-Nov-2024 - version 1.0
|
||||
- Version: 1.0
|
||||
- Author: [KayJay7](https://github.com/KayJay7)
|
||||
- Release Date: 31-Nov-2024
|
||||
|
||||
@@ -4,6 +4,7 @@ The plugin is used to import connected devices from OpenWRT
|
||||
|
||||
### Other info
|
||||
|
||||
- Author : [vaga9938](https://github.com/vaga9938)
|
||||
- Date : 28-Dec-2024 - version 1.0
|
||||
- Version: 1.0
|
||||
- Author: [vaga9938](https://github.com/vaga9938)
|
||||
- Release Date: 28-Dec-2024
|
||||
|
||||
|
||||
@@ -65,7 +65,8 @@ can not fix some of tplinks OMADA SDN own limitations/bugs:
|
||||
|
||||
|
||||
|
||||
### Other info
|
||||
## Other info
|
||||
|
||||
- Version: 1.0
|
||||
- Author : [Flying Toto](https://github.com/FlyingToto)
|
||||
- Date : 04-Jul-2024 - version 1.0
|
||||
- Release Date: 04-Jul-2024
|
||||
|
||||
@@ -36,8 +36,6 @@ import subprocess
|
||||
import multiprocessing
|
||||
|
||||
|
||||
# import netifaces
|
||||
|
||||
# Define the installation path and extend the system path for plugin imports
|
||||
INSTALL_PATH = "/app"
|
||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from time import strftime
|
||||
import pytz
|
||||
import pytz, all_timezones
|
||||
import sys
|
||||
import re
|
||||
import base64
|
||||
@@ -11,7 +11,7 @@ sys.path.append(f"{INSTALL_PATH}/front/plugins")
|
||||
sys.path.append(f'{INSTALL_PATH}/server')
|
||||
|
||||
from logger import mylog, Logger
|
||||
from const import confFileName
|
||||
from const import confFileName, default_tz
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
def read_config_file():
|
||||
@@ -35,6 +35,8 @@ def read_config_file():
|
||||
|
||||
configFile = read_config_file()
|
||||
timeZoneSetting = configFile['TIMEZONE']
|
||||
if timeZoneSetting not in all_timezones:
|
||||
timeZoneSetting = default_tz
|
||||
timeZone = pytz.timezone(timeZoneSetting)
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Encryption key used to encrypt the data before sending and for decryption on the hub. The key needs to be the same on the hub and on the nodes."
|
||||
"string": "The encryption key is used to secure data by encrypting it before transmission and decrypting it upon arrival at the hub. For the system to function correctly, the encryption key must be identical on both the hub and all the nodes. Similarly, the <code>API_TOKEN</code> must also be set to the same value across the hub and all the nodes to ensure proper authentication and communication."
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -222,7 +222,7 @@
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "If specified, the hub will pull Devices data from the listed nodes."
|
||||
"string": "If specified, the hub will pull Devices data from the listed nodes. The <code>API_TOKEN</code> and <code>SYNC_encryption_key</code> must be set to the same value across the hub and all the nodes to ensure proper authentication and communication."
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -393,6 +393,82 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "columns_filters",
|
||||
"type": {
|
||||
"dataType": "array",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "select",
|
||||
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||
"transformers": ["getString"]
|
||||
},
|
||||
{
|
||||
"elementType": "button",
|
||||
"elementOptions": [
|
||||
{ "sourceSuffixes": [] },
|
||||
{ "separator": "" },
|
||||
{ "cssClasses": "col-xs-4" },
|
||||
{ "onClick": "selectAll(this)" },
|
||||
{ "getStringKey": "Gen_Add_All" }
|
||||
],
|
||||
"transformers": []
|
||||
},
|
||||
{
|
||||
"elementType": "button",
|
||||
"elementOptions": [
|
||||
{ "sourceSuffixes": [] },
|
||||
{ "separator": "" },
|
||||
{ "cssClasses": "col-xs-4" },
|
||||
{ "onClick": "unselectAll(this)" },
|
||||
{ "getStringKey": "Gen_Remove_All" }
|
||||
],
|
||||
"transformers": []
|
||||
},
|
||||
{
|
||||
"elementType": "button",
|
||||
"elementOptions": [
|
||||
{ "sourceSuffixes": [] },
|
||||
{ "separator": "" },
|
||||
{ "cssClasses": "col-xs-4" },
|
||||
{ "onClick": "selectChange(this)" },
|
||||
{ "getStringKey": "Gen_Change" }
|
||||
],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"maxLength": 50,
|
||||
"default_value": [
|
||||
"Device_TableHead_Type",
|
||||
"Device_TableHead_SyncHubNodeName",
|
||||
"Device_TableHead_SourcePlugin"
|
||||
],
|
||||
"options": [
|
||||
"Device_TableHead_Owner",
|
||||
"Device_TableHead_Type",
|
||||
"Device_TableHead_Group",
|
||||
"Device_TableHead_Location",
|
||||
"Device_TableHead_Vendor",
|
||||
"Device_TableHead_SyncHubNodeName",
|
||||
"Device_TableHead_NetworkSite",
|
||||
"Device_TableHead_SSID",
|
||||
"Device_TableHead_SourcePlugin"
|
||||
],
|
||||
"localized": ["name", "description"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Column Filters"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Which column filters should be displayed in the main Devices screen. Remove all to hide the Filters section."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "shown_cards",
|
||||
"type": {
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
## Overview
|
||||
|
||||
A plugin allowing for importing Un-Discoverable devices from the settings page.
|
||||
The main usecase is to add dumb network gear like unmanaged hubs and switches to the network view.
|
||||
There might be other usecases, please let me know.
|
||||
|
||||
### Usage
|
||||
|
||||
- Go to settings and find Un-Discoverabe Devices in the list of plugins.
|
||||
- Enable the plugin by changing the RUN parameter from disabled to `once` or `always_after_scan`.
|
||||
- Add the name of your device to the list. (remove the sample entry first)
|
||||
- SAVE
|
||||
- wait for the next scan to finish
|
||||
|
||||
#### Examples:
|
||||
Settings:
|
||||

|
||||
|
||||
resulting in these devices:
|
||||

|
||||
|
||||
Allowing Un-Discoverable devices like hubs, switches or APs to be added to the network view.
|
||||

|
||||
|
||||
### Known Limitations
|
||||
- Un-Discoverable Devices always show as offline. That is expected as they can not be discovered by NetAlertX.
|
||||
- All IPs are set to 0.0.0.0 therefore the "Random MAC" icon might show up.
|
||||
|
||||
Made with ❤ by [@Data-Monkey](https://github.com/Data-Monkey) 🙏
|
||||
|
||||
@@ -1,573 +0,0 @@
|
||||
{
|
||||
"code_name": "undiscoverables",
|
||||
"unique_prefix": "UNDIS",
|
||||
"plugin_type": "device_scanner",
|
||||
"enabled": true,
|
||||
"data_source": "script",
|
||||
"mapped_to_table": "CurrentScan",
|
||||
"show_ui": true,
|
||||
"localized": ["display_name", "description", "icon"],
|
||||
"display_name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Un-Discoverable Devices"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Dispositivos no detectables"
|
||||
},
|
||||
{
|
||||
"language_code": "de_de",
|
||||
"string": "Nicht erkennbare Geräte"
|
||||
}
|
||||
],
|
||||
"icon": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "<i class=\"fa-solid fa-binoculars\"></i>"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "This plugin is to import undiscoverable devices from a file. Only ASCII characters are supported."
|
||||
}
|
||||
],
|
||||
"params": [
|
||||
{
|
||||
"name": "devices",
|
||||
"type": "setting",
|
||||
"value": "UNDIS_devices_to_import"
|
||||
}
|
||||
],
|
||||
"settings": [
|
||||
{
|
||||
"function": "RUN",
|
||||
"events": ["run"],
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{ "elementType": "select", "elementOptions": [], "transformers": [] }
|
||||
]
|
||||
},
|
||||
"default_value": "disabled",
|
||||
"options": ["disabled", "once", "schedule", "always_after_scan"],
|
||||
"localized": ["name", "description"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "When to run"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Cuándo ejecuta"
|
||||
},
|
||||
{
|
||||
"language_code": "de_de",
|
||||
"string": "Wann ausführen"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "When enabled, <code>once</code> is the preferred option, or set to <code>schedule</code> and align the <code>UNDIS_RUN_SCHD</code> setting with the other scanners if you want the devices to show up as ONLINE. It runs at startup and after every save of the config here.<br> Changes will only show in the devices <b> after the next scan!</b>"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Cuando está habilitado, <code>once</code> es la opción preferida. Se ejecuta al inicio y después de cada guardado de la configuración aquí.<br> ¡Los cambios solo se mostrarán en los dispositivos <b> después del próximo escaneo!</b>"
|
||||
},
|
||||
{
|
||||
"language_code": "de_de",
|
||||
"string": "Wenn dieses Plugin aktiviert ist, ist <code>once</code> die bevorzugte Methode. Das Plugin wird dann bei jedem Start und nach jedem Speichern der Einstellungen ausgeführt.</br>Änderungen scheinen in den Geräten erst <b>nach dem nächsten Scan</b> auf!"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "CMD",
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{ "elementType": "input", "elementOptions": [], "transformers": [] }
|
||||
]
|
||||
},
|
||||
"default_value": "python3 /app/front/plugins/undiscoverables/script.py devices={devices}",
|
||||
"options": [],
|
||||
"localized": ["name", "description"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Command"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Comando"
|
||||
},
|
||||
{
|
||||
"language_code": "de_de",
|
||||
"string": "Befehl"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Command to run. This can not be changed"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Comando a ejecutar. Esto no se puede cambiar"
|
||||
},
|
||||
{
|
||||
"language_code": "de_de",
|
||||
"string": "Befehl zum Ausführen. Dies kann nicht geändert werden"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "RUN_TIMEOUT",
|
||||
"type": {
|
||||
"dataType": "integer",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [{ "type": "number" }],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": 10,
|
||||
"options": [],
|
||||
"localized": ["name", "description"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Run timeout"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Tiempo límite de ejecución"
|
||||
},
|
||||
{
|
||||
"language_code": "de_de",
|
||||
"string": "Zeitlimit"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Maximum time in seconds to wait for the script to finish. If this time is exceeded the script is aborted."
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Tiempo máximo en segundos para esperar a que finalice el script. Si se supera este tiempo, el script se cancela."
|
||||
},
|
||||
{
|
||||
"language_code": "de_de",
|
||||
"string": "Maximale Zeit in Sekunden, die auf den Abschluss des Skripts gewartet werden soll. Bei Überschreitung dieser Zeit wird das Skript abgebrochen."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "WATCH",
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [{ "readonly": "true" }],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": [],
|
||||
"options": [],
|
||||
"localized": ["name", "description"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Watched"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Visto"
|
||||
},
|
||||
{
|
||||
"language_code": "de_de",
|
||||
"string": "Überwacht"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Undiscoverable Devices can not change their status, no watch is enabled."
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Los dispositivos no detectables no pueden cambiar su estado, ningún reloj está habilitado."
|
||||
},
|
||||
{
|
||||
"language_code": "de_de",
|
||||
"string": "Status von nicht erkennbaren Geräten können sich nicht ändern, keine Überwachung aktiviert."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "REPORT_ON",
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [{ "readonly": "true" }],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": [],
|
||||
"options": [
|
||||
"new",
|
||||
"watched-changed",
|
||||
"watched-not-changed",
|
||||
"missing-in-last-scan"
|
||||
],
|
||||
"localized": ["name", "description"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Report on"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Informar sobre"
|
||||
},
|
||||
{
|
||||
"language_code": "de_de",
|
||||
"string": "Benachrichtige wenn"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "No notifications will be sent."
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "No se enviarán notificaciones."
|
||||
},
|
||||
{
|
||||
"language_code": "de_de",
|
||||
"string": "Keine Benachrichtigungen werden versendet."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "devices_to_import",
|
||||
"type": {
|
||||
"dataType": "array",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [
|
||||
{ "placeholder": "Enter value" },
|
||||
{ "suffix": "_in" },
|
||||
{ "cssClasses": "col-sm-10" },
|
||||
{ "prefillValue": "null" }
|
||||
],
|
||||
"transformers": []
|
||||
},
|
||||
{
|
||||
"elementType": "button",
|
||||
"elementOptions": [
|
||||
{ "sourceSuffixes": ["_in"] },
|
||||
{ "separator": "" },
|
||||
{ "cssClasses": "col-xs-12" },
|
||||
{ "onClick": "addList(this, false)" },
|
||||
{ "getStringKey": "Gen_Add" }
|
||||
],
|
||||
"transformers": []
|
||||
},
|
||||
{
|
||||
"elementType": "select",
|
||||
"elementHasInputValue": 1,
|
||||
"elementOptions": [
|
||||
{ "multiple": "true" },
|
||||
{ "readonly": "true" },
|
||||
{ "editable": "true" }
|
||||
],
|
||||
"transformers": []
|
||||
},
|
||||
{
|
||||
"elementType": "button",
|
||||
"elementOptions": [
|
||||
{ "sourceSuffixes": [] },
|
||||
{ "separator": "" },
|
||||
{ "cssClasses": "col-xs-6" },
|
||||
{ "onClick": "removeAllOptions(this)" },
|
||||
{ "getStringKey": "Gen_Remove_All" }
|
||||
],
|
||||
"transformers": []
|
||||
},
|
||||
{
|
||||
"elementType": "button",
|
||||
"elementOptions": [
|
||||
{ "sourceSuffixes": [] },
|
||||
{ "separator": "" },
|
||||
{ "cssClasses": "col-xs-6" },
|
||||
{ "onClick": "removeFromList(this)" },
|
||||
{ "getStringKey": "Gen_Remove_Last" }
|
||||
],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": ["dummy_router"],
|
||||
"options": [],
|
||||
"localized": ["name", "description"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "UnDiscoverable Devices"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Dispositivo no detectable"
|
||||
},
|
||||
{
|
||||
"language_code": "de_de",
|
||||
"string": "Nicht erkennbare Geräte"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Devices to be added to the devices list."
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Dispositivos que se añadirán a la lista de dispositivos."
|
||||
},
|
||||
{
|
||||
"language_code": "de_de",
|
||||
"string": "Geräte, welche der Geräteliste hinzugefügt werden."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "RUN_SCHD",
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{ "elementType": "input", "elementOptions": [], "transformers": [] }
|
||||
]
|
||||
},
|
||||
"default_value": "*/5 * * * *",
|
||||
"options": [],
|
||||
"localized": ["name", "description"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Schedule"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Schedule"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Only enabled if you select <code>schedule</code> in the <a href=\"#UNDIS_RUN\"><code>UNDIS_RUN</code> setting</a>. Make sure you enter the schedule in the correct cron-like format (e.g. validate at <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). For example entering <code>0 4 * * *</code> will run the scan after 4 am in the <a onclick=\"toggleAllSettings()\" href=\"#TIMEZONE\"><code>TIMEZONE</code> you set above</a>. Will be run NEXT time the time passes. <br/> It's recommended to use the same schedule interval for all plugins responsible for discovering new devices. "
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Solo está habilitado si selecciona <code>schedule</code> en la configuración <a href=\"#UNDIS_RUN\"><code>UNDIS_RUN</code></a>. Asegúrese de ingresar la programación en el formato similar a cron correcto (por ejemplo, valide en <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). Por ejemplo, ingresar <code>0 4 * * *</code> ejecutará el escaneo después de las 4 a.m. en el <a onclick=\"toggleAllSettings()\" href=\"#TIMEZONE\"><code>TIMEZONE</ código> que configuró arriba</a>. Se ejecutará la PRÓXIMA vez que pase el tiempo."
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"database_column_definitions": [
|
||||
{
|
||||
"column": "Index",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "none",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": ["name"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Index"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"column": "Watched_Value1",
|
||||
"mapped_to_column": "cur_Name",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "label",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": ["name"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Device Name"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Nombre del dispositivo"
|
||||
},
|
||||
{
|
||||
"language_code": "de_de",
|
||||
"string": "Gerätename"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"column": "Object_PrimaryID",
|
||||
"mapped_to_column": "cur_MAC",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "label",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": ["name"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "MAC address"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Dirección MAC"
|
||||
},
|
||||
{
|
||||
"language_code": "de_de",
|
||||
"string": "MAC-Adresse"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"column": "Object_SecondaryID",
|
||||
"mapped_to_column": "cur_IP",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "device_ip",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": ["name"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "IP"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "IP"
|
||||
},
|
||||
{
|
||||
"language_code": "de_de",
|
||||
"string": "IP"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"column": "DateTimeCreated",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "label",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": ["name"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Created"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Creado"
|
||||
},
|
||||
{
|
||||
"language_code": "de_de",
|
||||
"string": "Erstellt"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"column": "DateTimeChanged",
|
||||
"mapped_to_column": "cur_DateTime",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "label",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": ["name"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Changed"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Cambiado"
|
||||
},
|
||||
{
|
||||
"language_code": "de_de",
|
||||
"string": "Geändert"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"column": "Dummy",
|
||||
"mapped_to_column": "cur_ScanMethod",
|
||||
"mapped_to_column_data": {
|
||||
"value": "UNDIS"
|
||||
},
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "label",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": ["name"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Scan method"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Método de escaneo"
|
||||
},
|
||||
{
|
||||
"language_code": "de_de",
|
||||
"string": "Scanmethode"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"column": "ForeignKey",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": false,
|
||||
"type": "device_mac",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": ["name"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "MAC"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "MAC"
|
||||
},
|
||||
{
|
||||
"language_code": "de_de",
|
||||
"string": "MAC"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# test script by running python script.py devices=test,dummy
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import argparse
|
||||
import sys
|
||||
import hashlib
|
||||
|
||||
# Register NetAlertX directories
|
||||
INSTALL_PATH="/app"
|
||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||
|
||||
from plugin_helper import Plugin_Object, Plugin_Objects, decodeBase64
|
||||
from logger import mylog, Logger, append_line_to_file
|
||||
from helper import timeNowTZ, get_setting_value
|
||||
from const import logPath, applicationPath
|
||||
import conf
|
||||
from pytz import timezone
|
||||
|
||||
# Make sure the TIMEZONE for logging is correct
|
||||
conf.tz = timezone(get_setting_value('TIMEZONE'))
|
||||
|
||||
# Make sure log level is initialized correctly
|
||||
Logger(get_setting_value('LOG_LEVEL'))
|
||||
|
||||
pluginName = 'UNDIS'
|
||||
|
||||
LOG_PATH = logPath + '/plugins'
|
||||
LOG_FILE = os.path.join(LOG_PATH, f'script.{pluginName}.log')
|
||||
RESULT_FILE = os.path.join(LOG_PATH, f'last_result.{pluginName}.log')
|
||||
|
||||
def main():
|
||||
|
||||
# the script expects a parameter in the format of devices=device1,device2,...
|
||||
parser = argparse.ArgumentParser(description='Import devices from settings')
|
||||
parser.add_argument('devices', action="store", help="list of device names separated by ','")
|
||||
values = parser.parse_args()
|
||||
|
||||
mylog('verbose', [f'[{pluginName}] In script'])
|
||||
|
||||
plugin_objects = Plugin_Objects( RESULT_FILE )
|
||||
|
||||
if values.devices:
|
||||
for fake_dev in values.devices.split('=')[1].split(','):
|
||||
|
||||
fake_mac = string_to_mac_hash(fake_dev)
|
||||
|
||||
plugin_objects.add_object(
|
||||
primaryId=fake_mac, # MAC (Device Name)
|
||||
secondaryId="0.0.0.0", # IP Address (always 0.0.0.0)
|
||||
watched1=fake_dev, # Device Name
|
||||
watched2="",
|
||||
watched3="",
|
||||
watched4="",
|
||||
extra="",
|
||||
foreignKey=fake_mac)
|
||||
|
||||
plugin_objects.write_result_file()
|
||||
|
||||
return 0
|
||||
|
||||
def string_to_mac_hash(input_string):
|
||||
# Calculate a hash using SHA-256
|
||||
sha256_hash = hashlib.sha256(input_string.encode()).hexdigest()
|
||||
|
||||
# Take the first 12 characters of the hash and format as a MAC address
|
||||
mac_hash = ':'.join(sha256_hash[i:i+2] for i in range(0, 12, 2))
|
||||
|
||||
return mac_hash
|
||||
|
||||
#===============================================================================
|
||||
# BEGIN
|
||||
#===============================================================================
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -19,14 +19,6 @@
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
<div class="content-wrapper">
|
||||
|
||||
<!-- Content header--------------------------------------------------------- -->
|
||||
<section class="content-header">
|
||||
<h1 id="pageTitle">
|
||||
<i class="fa fa-calendar"></i>
|
||||
<?= lang('Presence_Title');?>
|
||||
</h1>
|
||||
</section>
|
||||
|
||||
<!-- Main content ---------------------------------------------------------- -->
|
||||
<section class="content">
|
||||
|
||||
|
||||
@@ -1,31 +1,14 @@
|
||||
<?php
|
||||
|
||||
#---------------------------------------------------------------------------------#
|
||||
# NetAlertX #
|
||||
# Open Source Network Guard / WIFI & LAN intrusion detector #
|
||||
# #
|
||||
# report.php - Front module. Server side. Manage Devices #
|
||||
#---------------------------------------------------------------------------------# #
|
||||
# jokob-sk 2022 jokob.sk@gmail.com GNU GPLv3 #
|
||||
# leiweibau 2022 https://github.com/leiweibau GNU GPLv3 #
|
||||
# cvc90 2023 https://github.com/cvc90 GNU GPLv3 #
|
||||
#---------------------------------------------------------------------------------#
|
||||
|
||||
require 'php/templates/header.php';
|
||||
require 'php/templates/notification.php';
|
||||
|
||||
?>
|
||||
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
<div class="content-wrapper">
|
||||
|
||||
<!-- Content header--------------------------------------------------------- -->
|
||||
<section class="content-header">
|
||||
<?php require 'php/templates/notification.php'; ?>
|
||||
<h1 id="pageTitle">
|
||||
<i class="fa fa-paper-plane"></i>
|
||||
<?= lang('Navigation_Report') ;?>
|
||||
</h1>
|
||||
</section>
|
||||
|
||||
<!-- Main content ---------------------------------------------------------- -->
|
||||
<section class="content tab-content">
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<table align=center width=100% cellpadding=0 cellspacing=0 style="border-radius: 5px;">
|
||||
<tr>
|
||||
<td bgcolor=#3c8dbc align=center style="padding: 20px 10px 10px 10px; font-size: 20px; color:#ffffff; border-top-right-radius: 5px; border-top-left-radius: 5px; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2)">
|
||||
<img src="https://netalertx.com/NetAlertX_logo.png" alt="NetAlertX Logo" style="vertical-align: middle;" width="32" height="32" />
|
||||
<img src="https://netalertx.com/NetAlertX_logo.png" alt="NetAlertX Logo" style="vertical-align: middle; margin-top:-6px" width="32" height="32" />
|
||||
Net<b>Alert</b><sup>x</sup>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -59,35 +59,11 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
|
||||
<div id="settingsPage" class="content-wrapper">
|
||||
|
||||
<!-- Content header--------------------------------------------------------- -->
|
||||
<section class="content-header">
|
||||
|
||||
<div class="col-sm-5">
|
||||
<h1 id="pageTitle col-sm-3">
|
||||
<i class="fa fa-cog"></i>
|
||||
<?= lang('Navigation_Settings');?>
|
||||
<a style="cursor:pointer">
|
||||
<span>
|
||||
<i id='toggleSettings' onclick="toggleAllSettings()" class="settings-expand-icon fa fa-angle-double-down"></i>
|
||||
</span>
|
||||
</a>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-7 settingsImportedTimestamp" title="<?= lang("settings_imported");?> ">
|
||||
<div class="settingsImported ">
|
||||
<?= lang("settings_imported_label");?>:
|
||||
|
||||
<span id="lastImportedTime"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
<section class="content-header">
|
||||
|
||||
<div class ="bg-white color-palette box box-solid box-primary col-sm-12 panel panel-default panel-title" >
|
||||
<!-- Settings imported time -->
|
||||
|
||||
|
||||
|
||||
<a data-toggle="collapse" href="#settingsOverview">
|
||||
<div class ="settings-group col-sm-12 panel-heading panel-title">
|
||||
<i class="<?= lang("settings_enabled_icon");?>"></i> <?= lang("settings_enabled");?>
|
||||
@@ -142,6 +118,15 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
|
||||
|
||||
<section class=" padding-bottom col-sm-12">
|
||||
<!-- needed so the filter & save button don't hide the settings -->
|
||||
<!-- Settings imported time -->
|
||||
|
||||
<div class="col-sm-7 settingsImportedTimestamp" style="display:none" title="<?= lang("settings_imported");?> ">
|
||||
<div class="settingsImported ">
|
||||
<?= lang("settings_imported_label");?>:
|
||||
|
||||
<span id="lastImportedTime"></span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
@@ -13,21 +13,13 @@
|
||||
|
||||
require 'php/templates/header.php';
|
||||
?>
|
||||
<?php require 'php/templates/notification.php'; ?>
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
|
||||
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
<div class="content-wrapper">
|
||||
|
||||
<!-- Content header--------------------------------------------------------- -->
|
||||
<section class="content-header">
|
||||
<?php require 'php/templates/notification.php'; ?>
|
||||
<h1 id="pageTitle">
|
||||
<i class="fa fa-microchip"></i>
|
||||
<?= lang('SYSTEM_TITLE') ;?>
|
||||
</h1>
|
||||
</section>
|
||||
|
||||
<!-- Main content ---------------------------------------------------------- -->
|
||||
<section class="content">
|
||||
|
||||
|
||||
@@ -11,18 +11,6 @@ require 'php/templates/header.php';
|
||||
|
||||
|
||||
<div id="notifications" class="content-wrapper">
|
||||
|
||||
|
||||
<!-- Content header--------------------------------------------------------- -->
|
||||
<section class="content-header ">
|
||||
|
||||
<h1 id="pageTitle">
|
||||
<i class="fa fa-bell"></i>
|
||||
<?= lang('Navigation_Notifications');?>
|
||||
</h1>
|
||||
|
||||
</section>
|
||||
|
||||
<section class="content">
|
||||
<div class="box box-gray col-xs-12" >
|
||||
<div class="box-header">
|
||||
|
||||
@@ -9,16 +9,6 @@
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
<div class="content-wrapper">
|
||||
|
||||
<!-- Content header--------------------------------------------------------- -->
|
||||
<section class="content-header">
|
||||
|
||||
<h1 id="pageTitle">
|
||||
<i class="fa fa-fw fa-plug"></i> <?= lang('Navigation_Workflows');?>
|
||||
<span class="pageHelp"> <a target="_blank" href="https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins"><i class="fa fa-circle-question"></i></a><span>
|
||||
</h1>
|
||||
</section>
|
||||
|
||||
|
||||
<?php
|
||||
require 'appEventsCore.php';
|
||||
?>
|
||||
|
||||
@@ -1,5 +1,2 @@
|
||||
|
||||
|
||||
# Schedule cron jobs
|
||||
* * * * * /app/back/cron_script.sh
|
||||
#* * * * * echo "$(date +'%Y-%m-%d %H:%M:%S') - Cron job ran" >> /app/log/cron_timestamp.log
|
||||
|
||||
15
install/freebox_certificate.pem
Executable file
15
install/freebox_certificate.pem
Executable file
@@ -0,0 +1,15 @@
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICOjCCAcCgAwIBAgIUI0Tu7zsrBJACQIZgLMJobtbdNn4wCgYIKoZIzj0EAwIw
|
||||
TDELMAkGA1UEBhMCSVQxDjAMBgNVBAgMBUl0YWx5MQ4wDAYDVQQKDAVJbGlhZDEd
|
||||
MBsGA1UEAwwUSWxpYWRib3ggRUNDIFJvb3QgQ0EwHhcNMjAxMTI3MDkzODEzWhcN
|
||||
NDAxMTIyMDkzODEzWjBMMQswCQYDVQQGEwJJVDEOMAwGA1UECAwFSXRhbHkxDjAM
|
||||
BgNVBAoMBUlsaWFkMR0wGwYDVQQDDBRJbGlhZGJveCBFQ0MgUm9vdCBDQTB2MBAG
|
||||
ByqGSM49AgEGBSuBBAAiA2IABMryJyb2loHNAioY8IztN5MI3UgbVHVP/vZwcnre
|
||||
ZvJOyDvE4HJgIti5qmfswlnMzpNbwf/MkT+7HAU8jJoTorRm1wtAnQ9cWD3Ebv79
|
||||
RPwtjjy3Bza3SgdVxmd6fWPUKaNjMGEwHQYDVR0OBBYEFDUij/4lpoJ+kOXRyrcM
|
||||
jf2RPzOqMB8GA1UdIwQYMBaAFDUij/4lpoJ+kOXRyrcMjf2RPzOqMA8GA1UdEwEB
|
||||
/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQC6eUV1
|
||||
pFh4UpJOTc1JToztN4ttnQR6rIzxMZ6mNCe+nhjkohWp24pr7BpUYSbEizYCMAQ6
|
||||
LCiBKV2j7QQGy7N1aBmdur17ZepYzR1YV0eI+Kd978aZggsmhjXENQYVTmm/XA==
|
||||
-----END CERTIFICATE-----
|
||||
@@ -30,5 +30,5 @@ source myenv/bin/activate
|
||||
update-alternatives --install /usr/bin/python python /usr/bin/python3 10
|
||||
|
||||
# install packages thru pip3
|
||||
pip3 install openwrt-luci-rpc asusrouter asyncio aiohttp graphene flask netifaces tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros git+https://github.com/foreign-sub/aiofreepybox.git
|
||||
pip3 install openwrt-luci-rpc asusrouter asyncio aiohttp graphene flask tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros git+https://github.com/foreign-sub/aiofreepybox.git
|
||||
|
||||
|
||||
@@ -17,8 +17,9 @@ For more details, see the [Checkmk Local Checks Documentation](https://docs.chec
|
||||
|
||||
### Other info
|
||||
|
||||
- Date : 08-Jan-2025 - version 1.0
|
||||
- Version: 1.0
|
||||
- Author: N/A
|
||||
- Release Date: 08-Jan-2025
|
||||
|
||||
> [!NOTE]
|
||||
> This is a community supplied script and not maintained.
|
||||
@@ -34,7 +34,8 @@ For each MAC or IP address provided, the script:
|
||||
|
||||
### Other info
|
||||
|
||||
- Date : 23-Dec-2024 - version 1.0
|
||||
- Version: 1.0
|
||||
- Release Date: 23-Dec-2024
|
||||
- Author: [laxduke](https://github.com/laxduke)
|
||||
|
||||
|
||||
|
||||
@@ -25,7 +25,8 @@ import subprocess
|
||||
import conf
|
||||
from const import *
|
||||
from logger import mylog
|
||||
from helper import filePermissions, timeNowTZ, updateState, get_setting_value
|
||||
from helper import filePermissions, timeNowTZ, get_setting_value
|
||||
from app_state import updateState
|
||||
from api import update_api
|
||||
from networkscan import process_scan
|
||||
from initialise import importConfigs
|
||||
@@ -89,7 +90,7 @@ def main ():
|
||||
while True:
|
||||
|
||||
# re-load user configuration and plugins
|
||||
all_plugins = importConfigs(db, all_plugins)
|
||||
all_plugins, imported = importConfigs(db, all_plugins)
|
||||
|
||||
# update time started
|
||||
conf.loop_start_time = timeNowTZ()
|
||||
@@ -98,11 +99,11 @@ def main ():
|
||||
|
||||
# Handle plugins executed ONCE
|
||||
if conf.plugins_once_run == False:
|
||||
pluginsState = run_plugin_scripts(db, all_plugins, 'once')
|
||||
run_plugin_scripts(db, all_plugins, 'once')
|
||||
conf.plugins_once_run = True
|
||||
|
||||
# check if there is a front end initiated event which needs to be executed
|
||||
pluginsState = check_and_run_user_event(db, all_plugins, pluginsState)
|
||||
|
||||
# check if user is waiting for api_update
|
||||
check_and_run_user_event(db, all_plugins)
|
||||
|
||||
# Update API endpoints
|
||||
update_api(db, all_plugins, False)
|
||||
@@ -121,27 +122,27 @@ def main ():
|
||||
startTime = startTime.replace (microsecond=0)
|
||||
|
||||
# Check if any plugins need to run on schedule
|
||||
pluginsState = run_plugin_scripts(db, all_plugins, 'schedule', pluginsState)
|
||||
run_plugin_scripts(db, all_plugins, 'schedule')
|
||||
|
||||
# determine run/scan type based on passed time
|
||||
# --------------------------------------------
|
||||
|
||||
# Runs plugin scripts which are set to run every timne after a scans finished
|
||||
pluginsState = run_plugin_scripts(db, all_plugins, 'always_after_scan', pluginsState)
|
||||
|
||||
# Runs plugin scripts which are set to run every time after a scans finished
|
||||
run_plugin_scripts(db, all_plugins, 'always_after_scan')
|
||||
|
||||
# process all the scanned data into new devices
|
||||
mylog('debug', [f'[MAIN] processScan: {pluginsState.processScan}'])
|
||||
processScan = updateState("Check scan").processScan
|
||||
mylog('debug', [f'[MAIN] processScan: {processScan}'])
|
||||
|
||||
if pluginsState.processScan == True:
|
||||
mylog('debug', "[MAIN] start processig scan results")
|
||||
pluginsState.processScan = False
|
||||
if processScan == True:
|
||||
mylog('debug', "[MAIN] start processig scan results")
|
||||
process_scan(db)
|
||||
updateState("Scan processed", None, None, None, None, False)
|
||||
|
||||
# --------
|
||||
# Reporting
|
||||
# run plugins before notification processing (e.g. Plugins to discover device names)
|
||||
pluginsState = run_plugin_scripts(db, all_plugins, 'before_name_updates', pluginsState)
|
||||
run_plugin_scripts(db, all_plugins, 'before_name_updates')
|
||||
|
||||
# Resolve devices names
|
||||
mylog('debug','[Main] Resolve devices names')
|
||||
@@ -155,7 +156,7 @@ def main ():
|
||||
# new devices were found
|
||||
if len(newDevices) > 0:
|
||||
# run all plugins registered to be run when new devices are found
|
||||
pluginsState = run_plugin_scripts(db, all_plugins, 'on_new_device', pluginsState)
|
||||
run_plugin_scripts(db, all_plugins, 'on_new_device')
|
||||
|
||||
# Notification handling
|
||||
# ----------------------------------------
|
||||
@@ -170,12 +171,10 @@ def main ():
|
||||
# run all enabled publisher gateways
|
||||
if notificationObj.HasNotifications:
|
||||
|
||||
pluginsState = run_plugin_scripts(db, all_plugins, 'on_notification', pluginsState)
|
||||
run_plugin_scripts(db, all_plugins, 'on_notification')
|
||||
notification.setAllProcessed()
|
||||
notification.clearPendingEmailFlag()
|
||||
|
||||
|
||||
|
||||
else:
|
||||
mylog('verbose', ['[Notification] No changes to report'])
|
||||
|
||||
|
||||
@@ -5,10 +5,11 @@ import datetime
|
||||
|
||||
# Register NetAlertX modules
|
||||
import conf
|
||||
from const import (apiPath, sql_appevents, sql_devices_all, sql_events_pending_alert, sql_settings, sql_plugins_events, sql_plugins_history, sql_plugins_objects,sql_language_strings, sql_notifications_all, sql_online_history, sql_devices_tiles)
|
||||
from const import (apiPath, sql_appevents, sql_devices_all, sql_events_pending_alert, sql_settings, sql_plugins_events, sql_plugins_history, sql_plugins_objects,sql_language_strings, sql_notifications_all, sql_online_history, sql_devices_tiles, sql_devices_filters)
|
||||
from logger import mylog
|
||||
from helper import write_file, get_setting_value, updateState, timeNowTZ
|
||||
from execution_log import ExecutionLog
|
||||
from helper import write_file, get_setting_value, timeNowTZ
|
||||
from app_state import updateState
|
||||
from user_events_queue import UserEventsQueue
|
||||
from notification import write_notification
|
||||
|
||||
# Import the start_server function
|
||||
@@ -31,12 +32,10 @@ def update_api(db, all_plugins, forceUpdate, updateOnlyDataSources=[], is_ad_hoc
|
||||
start_periodic_write(interval=1)
|
||||
|
||||
# Update app_state.json and retrieve app_state to check if GraphQL server is running
|
||||
app_state = updateState("Update: API", None, None, None, None)
|
||||
app_state = updateState()
|
||||
|
||||
folder = apiPath
|
||||
|
||||
# Save plugins
|
||||
write_file(folder + 'plugins.json', json.dumps({"data": all_plugins}))
|
||||
write_file(apiPath + 'plugins.json', json.dumps({"data": all_plugins}))
|
||||
|
||||
# Prepare database tables we want to expose
|
||||
dataSourcesSQLs = [
|
||||
@@ -51,13 +50,14 @@ def update_api(db, all_plugins, forceUpdate, updateOnlyDataSources=[], is_ad_hoc
|
||||
["notifications", sql_notifications_all],
|
||||
["online_history", sql_online_history],
|
||||
["devices_tiles", sql_devices_tiles],
|
||||
["devices_filters", sql_devices_filters],
|
||||
["custom_endpoint", conf.API_CUSTOM_SQL],
|
||||
]
|
||||
|
||||
# Save selected database tables
|
||||
for dsSQL in dataSourcesSQLs:
|
||||
if not updateOnlyDataSources or dsSQL[0] in updateOnlyDataSources:
|
||||
api_endpoint_class(db, forceUpdate, dsSQL[1], folder + 'table_' + dsSQL[0] + '.json', is_ad_hoc_user_event)
|
||||
api_endpoint_class(db, forceUpdate, dsSQL[1], apiPath + 'table_' + dsSQL[0] + '.json', is_ad_hoc_user_event)
|
||||
|
||||
# Start the GraphQL server
|
||||
graphql_port_value = get_setting_value("GRAPHQL_PORT")
|
||||
@@ -87,7 +87,7 @@ class api_endpoint_class:
|
||||
self.path = path
|
||||
self.fileName = path.split('/')[-1]
|
||||
self.hash = hash(json.dumps(self.jsonData))
|
||||
self.debounce_interval = 5 # Time to wait before writing
|
||||
self.debounce_interval = 3 # Time in seconds to wait before writing
|
||||
self.changeDetectedWhen = None
|
||||
# self.last_update_time = current_time - datetime.timedelta(minutes=1) # Last time data was updated
|
||||
self.is_ad_hoc_user_event = is_ad_hoc_user_event
|
||||
@@ -147,7 +147,7 @@ class api_endpoint_class:
|
||||
# Update user event execution log
|
||||
# mylog('verbose', [f'[API] api_endpoint_class: is_ad_hoc_user_event {self.is_ad_hoc_user_event}'])
|
||||
if self.is_ad_hoc_user_event:
|
||||
execution_log = ExecutionLog()
|
||||
execution_log = UserEventsQueue()
|
||||
execution_log.finalize_event("update_api")
|
||||
self.is_ad_hoc_user_event = False
|
||||
|
||||
|
||||
109
server/app_state.py
Executable file
109
server/app_state.py
Executable file
@@ -0,0 +1,109 @@
|
||||
import os
|
||||
import json
|
||||
|
||||
import conf
|
||||
from const import *
|
||||
from logger import mylog, logResult
|
||||
from helper import timeNowTZ, timeNow, checkNewVersion
|
||||
|
||||
# Register NetAlertX directories
|
||||
INSTALL_PATH="/app"
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# App state
|
||||
#-------------------------------------------------------------------------------
|
||||
# A class to manage the application state and to provide a frontend accessible API point
|
||||
# To keep an existing value pass None
|
||||
class app_state_class:
|
||||
def __init__(self, currentState = None, settingsSaved=None, settingsImported=None, showSpinner=False, graphQLServerStarted=0, processScan=False):
|
||||
# json file containing the state to communicate with the frontend
|
||||
stateFile = apiPath + 'app_state.json'
|
||||
previousState = ""
|
||||
|
||||
# Update self
|
||||
self.lastUpdated = str(timeNowTZ())
|
||||
|
||||
if os.path.exists(stateFile):
|
||||
try:
|
||||
with open(stateFile, 'r') as json_file:
|
||||
previousState = json.load(json_file)
|
||||
except json.decoder.JSONDecodeError as e:
|
||||
mylog('none', [f'[app_state_class] Failed to handle app_state.json: {e}'])
|
||||
|
||||
# Check if the file exists and recover previous values
|
||||
if previousState != "":
|
||||
self.settingsSaved = previousState.get("settingsSaved", 0)
|
||||
self.settingsImported = previousState.get("settingsImported", 0)
|
||||
self.processScan = previousState.get("processScan", False)
|
||||
self.showSpinner = previousState.get("showSpinner", False)
|
||||
self.isNewVersion = previousState.get("isNewVersion", False)
|
||||
self.isNewVersionChecked = previousState.get("isNewVersionChecked", 0)
|
||||
self.graphQLServerStarted = previousState.get("graphQLServerStarted", 0)
|
||||
self.currentState = previousState.get("currentState", "Init")
|
||||
else: # init first time values
|
||||
self.settingsSaved = 0
|
||||
self.settingsImported = 0
|
||||
self.showSpinner = False
|
||||
self.processScan = False
|
||||
self.isNewVersion = checkNewVersion()
|
||||
self.isNewVersionChecked = int(timeNow().timestamp())
|
||||
self.graphQLServerStarted = 0
|
||||
self.currentState = "Init"
|
||||
|
||||
# Overwrite with provided parameters if supplied
|
||||
if settingsSaved is not None:
|
||||
self.settingsSaved = settingsSaved
|
||||
if settingsImported is not None:
|
||||
self.settingsImported = settingsImported
|
||||
if showSpinner is not None:
|
||||
self.showSpinner = showSpinner
|
||||
if graphQLServerStarted is not None:
|
||||
self.graphQLServerStarted = graphQLServerStarted
|
||||
if processScan is not None:
|
||||
self.processScan = processScan
|
||||
if currentState is not None:
|
||||
self.currentState = currentState
|
||||
|
||||
# check for new version every hour and if currently not running new version
|
||||
if self.isNewVersion is False and self.isNewVersionChecked + 3600 < int(timeNow().timestamp()):
|
||||
self.isNewVersion = checkNewVersion()
|
||||
self.isNewVersionChecked = int(timeNow().timestamp())
|
||||
|
||||
# Update .json file
|
||||
# with open(stateFile, 'w') as json_file:
|
||||
# json.dump(self, json_file, cls=AppStateEncoder, indent=4)
|
||||
|
||||
# Remove lastUpdated from the dictionary for comparison
|
||||
currentStateDict = self.__dict__.copy()
|
||||
currentStateDict.pop('lastUpdated', None)
|
||||
|
||||
# Compare current state with previous state before updating
|
||||
if previousState != currentStateDict:
|
||||
# Sanity check before saving the .json file
|
||||
try:
|
||||
json_data = json.dumps(self, cls=AppStateEncoder, indent=4)
|
||||
with open(stateFile, 'w') as json_file:
|
||||
json_file.write(json_data)
|
||||
except (TypeError, ValueError) as e:
|
||||
mylog('none', [f'[app_state_class] Failed to serialize object to JSON: {e}'])
|
||||
|
||||
return # Allows chaining by returning self
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# method to update the state
|
||||
def updateState(newState = None, settingsSaved = None, settingsImported = None, showSpinner = False, graphQLServerStarted = None, processScan = None):
|
||||
|
||||
return app_state_class(newState, settingsSaved, settingsImported, showSpinner, graphQLServerStarted, processScan)
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Checks if the object has a __dict__ attribute. If it does, it assumes that it's an instance of a class and serializes its attributes dynamically.
|
||||
class AppStateEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
if hasattr(obj, '__dict__'):
|
||||
# If the object has a '__dict__', assume it's an instance of a class
|
||||
return obj.__dict__
|
||||
return super().default(obj)
|
||||
@@ -20,7 +20,7 @@ fullDbPath = applicationPath + dbPath
|
||||
vendorsPath = '/usr/share/arp-scan/ieee-oui.txt'
|
||||
vendorsPathNewest = '/usr/share/arp-scan/ieee-oui_all_filtered.txt'
|
||||
|
||||
|
||||
default_tz = 'Europe/Berlin'
|
||||
|
||||
|
||||
#===============================================================================
|
||||
@@ -105,6 +105,35 @@ sql_devices_tiles = """
|
||||
(SELECT COUNT(*) FROM MyDevicesFilter) AS my_devices
|
||||
FROM Statuses;
|
||||
"""
|
||||
sql_devices_filters = """
|
||||
SELECT DISTINCT 'devSite' AS columnName, devSite AS columnValue
|
||||
FROM Devices WHERE devSite NOT IN ('', 'null') AND devSite IS NOT NULL
|
||||
UNION
|
||||
SELECT DISTINCT 'devSourcePlugin' AS columnName, devSourcePlugin AS columnValue
|
||||
FROM Devices WHERE devSourcePlugin NOT IN ('', 'null') AND devSourcePlugin IS NOT NULL
|
||||
UNION
|
||||
SELECT DISTINCT 'devOwner' AS columnName, devOwner AS columnValue
|
||||
FROM Devices WHERE devOwner NOT IN ('', 'null') AND devOwner IS NOT NULL
|
||||
UNION
|
||||
SELECT DISTINCT 'devType' AS columnName, devType AS columnValue
|
||||
FROM Devices WHERE devType NOT IN ('', 'null') AND devType IS NOT NULL
|
||||
UNION
|
||||
SELECT DISTINCT 'devGroup' AS columnName, devGroup AS columnValue
|
||||
FROM Devices WHERE devGroup NOT IN ('', 'null') AND devGroup IS NOT NULL
|
||||
UNION
|
||||
SELECT DISTINCT 'devLocation' AS columnName, devLocation AS columnValue
|
||||
FROM Devices WHERE devLocation NOT IN ('', 'null') AND devLocation IS NOT NULL
|
||||
UNION
|
||||
SELECT DISTINCT 'devVendor' AS columnName, devVendor AS columnValue
|
||||
FROM Devices WHERE devVendor NOT IN ('', 'null') AND devVendor IS NOT NULL
|
||||
UNION
|
||||
SELECT DISTINCT 'devSyncHubNode' AS columnName, devSyncHubNode AS columnValue
|
||||
FROM Devices WHERE devSyncHubNode NOT IN ('', 'null') AND devSyncHubNode IS NOT NULL
|
||||
UNION
|
||||
SELECT DISTINCT 'devSSID' AS columnName, devSSID AS columnValue
|
||||
FROM Devices WHERE devSSID NOT IN ('', 'null') AND devSSID IS NOT NULL
|
||||
ORDER BY columnName;
|
||||
"""
|
||||
sql_devices_stats = """SELECT Online_Devices as online, Down_Devices as down, All_Devices as 'all', Archived_Devices as archived,
|
||||
(select count(*) from Devices a where devIsNew = 1 ) as new,
|
||||
(select count(*) from Devices a where devName = '(unknown)' or devName = '(name not found)' ) as unknown
|
||||
|
||||
@@ -8,7 +8,7 @@ import json
|
||||
from const import fullDbPath, sql_devices_stats, sql_devices_all, sql_generateGuid
|
||||
|
||||
from logger import mylog
|
||||
from helper import json_obj, initOrSetParam, row_to_json, timeNowTZ#, split_string #, updateState
|
||||
from helper import json_obj, initOrSetParam, row_to_json, timeNowTZ
|
||||
from appevent import AppEvent_obj
|
||||
|
||||
class DB():
|
||||
|
||||
@@ -20,6 +20,10 @@ class SortOptionsInput(InputObjectType):
|
||||
field = String()
|
||||
order = String()
|
||||
|
||||
class FilterOptionsInput(InputObjectType):
|
||||
filterColumn = String()
|
||||
filterValue = String()
|
||||
|
||||
|
||||
class PageQueryOptionsInput(InputObjectType):
|
||||
page = Int()
|
||||
@@ -27,6 +31,7 @@ class PageQueryOptionsInput(InputObjectType):
|
||||
sort = List(SortOptionsInput)
|
||||
search = String()
|
||||
status = String()
|
||||
filters = List(FilterOptionsInput)
|
||||
|
||||
|
||||
# Device ObjectType
|
||||
@@ -162,7 +167,14 @@ class Query(ObjectType):
|
||||
elif status == "offline":
|
||||
devices_data = [device for device in devices_data if device["devPresentLastScan"] == 0]
|
||||
|
||||
|
||||
# additional filters
|
||||
if options.filters:
|
||||
for filter in options.filters:
|
||||
if filter.filterColumn and filter.filterValue:
|
||||
devices_data = [
|
||||
device for device in devices_data
|
||||
if str(device.get(filter.filterColumn, "")).lower() == str(filter.filterValue).lower()
|
||||
]
|
||||
|
||||
# Filter data if a search term is provided
|
||||
if options.search:
|
||||
|
||||
@@ -9,7 +9,8 @@ INSTALL_PATH = "/app"
|
||||
sys.path.extend([f"{INSTALL_PATH}/server"])
|
||||
|
||||
from logger import mylog
|
||||
from helper import get_setting_value, timeNowTZ, updateState
|
||||
from helper import get_setting_value, timeNowTZ
|
||||
from app_state import updateState
|
||||
from notification import write_notification
|
||||
|
||||
# Flask application
|
||||
|
||||
@@ -52,94 +52,6 @@ def get_timezone_offset():
|
||||
return offset_formatted
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# App state
|
||||
#-------------------------------------------------------------------------------
|
||||
# A class to manage the application state and to provide a frontend accessible API point
|
||||
# To keep an existing value pass None
|
||||
class app_state_class:
|
||||
def __init__(self, currentState, settingsSaved=None, settingsImported=None, showSpinner=False, graphQLServerStarted=0):
|
||||
# json file containing the state to communicate with the frontend
|
||||
stateFile = apiPath + '/app_state.json'
|
||||
previousState = ""
|
||||
|
||||
# if currentState == 'Initializing':
|
||||
# checkNewVersion(False)
|
||||
|
||||
# Update self
|
||||
self.currentState = currentState
|
||||
self.lastUpdated = str(timeNowTZ())
|
||||
|
||||
if os.path.exists(stateFile):
|
||||
try:
|
||||
with open(stateFile, 'r') as json_file:
|
||||
previousState = json.load(json_file)
|
||||
except json.decoder.JSONDecodeError as e:
|
||||
mylog('none', [f'[app_state_class] Failed to handle app_state.json: {e}'])
|
||||
|
||||
|
||||
# Check if the file exists and recover previous values
|
||||
if previousState != "":
|
||||
self.settingsSaved = previousState.get("settingsSaved", 0)
|
||||
self.settingsImported = previousState.get("settingsImported", 0)
|
||||
self.showSpinner = previousState.get("showSpinner", False)
|
||||
self.isNewVersion = previousState.get("isNewVersion", False)
|
||||
self.isNewVersionChecked = previousState.get("isNewVersionChecked", 0)
|
||||
self.graphQLServerStarted = previousState.get("graphQLServerStarted", 0)
|
||||
else: # init first time values
|
||||
self.settingsSaved = 0
|
||||
self.settingsImported = 0
|
||||
self.showSpinner = False
|
||||
self.isNewVersion = checkNewVersion()
|
||||
self.isNewVersionChecked = int(timeNow().timestamp())
|
||||
self.graphQLServerStarted = 0
|
||||
|
||||
# Overwrite with provided parameters if supplied
|
||||
if settingsSaved is not None:
|
||||
self.settingsSaved = settingsSaved
|
||||
if settingsImported is not None:
|
||||
self.settingsImported = settingsImported
|
||||
if showSpinner is not None:
|
||||
self.showSpinner = showSpinner
|
||||
if graphQLServerStarted is not None:
|
||||
self.graphQLServerStarted = graphQLServerStarted
|
||||
|
||||
# check for new version every hour and if currently not running new version
|
||||
if self.isNewVersion is False and self.isNewVersionChecked + 3600 < int(timeNow().timestamp()):
|
||||
self.isNewVersion = checkNewVersion()
|
||||
self.isNewVersionChecked = int(timeNow().timestamp())
|
||||
|
||||
# Update .json file
|
||||
# with open(stateFile, 'w') as json_file:
|
||||
# json.dump(self, json_file, cls=AppStateEncoder, indent=4)
|
||||
|
||||
# Sanity check before saving the .json file
|
||||
try:
|
||||
json_data = json.dumps(self, cls=AppStateEncoder, indent=4)
|
||||
with open(stateFile, 'w') as json_file:
|
||||
json_file.write(json_data)
|
||||
except (TypeError, ValueError) as e:
|
||||
mylog('none', [f'[app_state_class] Failed to serialize object to JSON: {e}'])
|
||||
|
||||
|
||||
|
||||
def isSet(self):
|
||||
|
||||
result = False
|
||||
|
||||
if self.currentState != "":
|
||||
result = True
|
||||
|
||||
return result
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# method to update the state
|
||||
def updateState(newState, settingsSaved = None, settingsImported = None, showSpinner = False, graphQLServerStarted = None):
|
||||
|
||||
return app_state_class(newState, settingsSaved, settingsImported, showSpinner, graphQLServerStarted)
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
def updateSubnets(scan_subnets):
|
||||
subnets = []
|
||||
@@ -887,14 +799,6 @@ def add_json_list (row, list):
|
||||
return list
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Checks if the object has a __dict__ attribute. If it does, it assumes that it's an instance of a class and serializes its attributes dynamically.
|
||||
class AppStateEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
if hasattr(obj, '__dict__'):
|
||||
# If the object has a '__dict__', assume it's an instance of a class
|
||||
return obj.__dict__
|
||||
return super().default(obj)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Checks if the object has a __dict__ attribute. If it does, it assumes that it's an instance of a class and serializes its attributes dynamically.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
import os
|
||||
import time
|
||||
from pytz import timezone
|
||||
from pytz import timezone, all_timezones, UnknownTimeZoneError
|
||||
from cron_converter import Cron
|
||||
from pathlib import Path
|
||||
import datetime
|
||||
@@ -11,8 +11,9 @@ import re
|
||||
|
||||
# Register NetAlertX libraries
|
||||
import conf
|
||||
from const import fullConfPath, applicationPath, fullConfFolder
|
||||
from helper import fixPermissions, collect_lang_strings, updateSubnets, initOrSetParam, isJsonObject, updateState, setting_value_to_python_type, timeNowTZ, get_setting_value, generate_random_string
|
||||
from const import fullConfPath, applicationPath, fullConfFolder, default_tz
|
||||
from helper import fixPermissions, collect_lang_strings, updateSubnets, initOrSetParam, isJsonObject, setting_value_to_python_type, timeNowTZ, get_setting_value, generate_random_string
|
||||
from app_state import updateState
|
||||
from logger import mylog
|
||||
from api import update_api
|
||||
from scheduler import schedule_class
|
||||
@@ -133,7 +134,7 @@ def importConfigs (db, all_plugins):
|
||||
|
||||
if (fileModifiedTime == conf.lastImportedConfFile) and all_plugins is not None:
|
||||
mylog('debug', ['[Import Config] skipping config file import'])
|
||||
return all_plugins
|
||||
return all_plugins, False
|
||||
|
||||
# Header
|
||||
updateState("Import config", showSpinner = True)
|
||||
@@ -160,7 +161,7 @@ def importConfigs (db, all_plugins):
|
||||
conf.DISCOVER_PLUGINS = ccd('DISCOVER_PLUGINS', True , c_d, 'Discover plugins', """{"dataType": "boolean","elements": [{"elementType": "input","elementOptions": [{ "type": "checkbox" }],"transformers": []}]}""", '[]', 'General')
|
||||
conf.SCAN_SUBNETS = ccd('SCAN_SUBNETS', ['192.168.1.0/24 --interface=eth1', '192.168.1.0/24 --interface=eth0'] , c_d, 'Subnets to scan', '''{"dataType": "array","elements": [{"elementType": "input","elementOptions": [{"placeholder": "192.168.1.0/24 --interface=eth1"},{"suffix": "_in"},{"cssClasses": "col-sm-10"},{"prefillValue": "null"}],"transformers": []},{"elementType": "button","elementOptions": [{"sourceSuffixes": ["_in"]},{"separator": ""},{"cssClasses": "col-xs-12"},{"onClick": "addList(this, false)"},{"getStringKey": "Gen_Add"}],"transformers": []},{"elementType": "select","elementHasInputValue": 1,"elementOptions": [{"multiple": "true"},{"readonly": "true"},{"editable": "true"}],"transformers": []},{"elementType": "button","elementOptions": [{"sourceSuffixes": []},{"separator": ""},{"cssClasses": "col-xs-6"},{"onClick": "removeAllOptions(this)"},{"getStringKey": "Gen_Remove_All"}],"transformers": []},{"elementType": "button","elementOptions": [{"sourceSuffixes": []},{"separator": ""},{"cssClasses": "col-xs-6"},{"onClick": "removeFromList(this)"},{"getStringKey": "Gen_Remove_Last"}],"transformers": []}]}''', '[]', 'General')
|
||||
conf.LOG_LEVEL = ccd('LOG_LEVEL', 'verbose' , c_d, 'Log verboseness', '{"dataType":"string", "elements": [{"elementType" : "select", "elementOptions" : [] ,"transformers": []}]}', "['none', 'minimal', 'verbose', 'debug', 'trace']", 'General')
|
||||
conf.TIMEZONE = ccd('TIMEZONE', 'Europe/Berlin' , c_d, 'Time zone', '{"dataType":"string", "elements": [{"elementType" : "input", "elementOptions" : [] ,"transformers": []}]}', '[]', 'General')
|
||||
conf.TIMEZONE = ccd('TIMEZONE', default_tz , c_d, 'Time zone', '{"dataType":"string", "elements": [{"elementType" : "input", "elementOptions" : [] ,"transformers": []}]}', '[]', 'General')
|
||||
conf.PLUGINS_KEEP_HIST = ccd('PLUGINS_KEEP_HIST', 250 , c_d, 'Keep history entries', '{"dataType":"integer", "elements": [{"elementType" : "input", "elementOptions" : [{"type": "number"}] ,"transformers": []}]}', '[]', 'General')
|
||||
conf.REPORT_DASHBOARD_URL = ccd('REPORT_DASHBOARD_URL', 'http://netalertx/' , c_d, 'NetAlertX URL', '{"dataType":"string", "elements": [{"elementType" : "input", "elementOptions" : [] ,"transformers": []}]}', '[]', 'General')
|
||||
conf.DAYS_TO_KEEP_EVENTS = ccd('DAYS_TO_KEEP_EVENTS', 90 , c_d, 'Delete events days', '{"dataType":"integer", "elements": [{"elementType" : "input", "elementOptions" : [{"type": "number"}] ,"transformers": []}]}', '[]', 'General')
|
||||
@@ -176,8 +177,15 @@ def importConfigs (db, all_plugins):
|
||||
# UI
|
||||
conf.UI_LANG = ccd('UI_LANG', 'English' , c_d, 'Language Interface', '{"dataType":"string", "elements": [{"elementType" : "select", "elementOptions" : [] ,"transformers": []}]}', "['English', 'German', 'Spanish', 'French', 'Norwegian', 'Russian', 'Italian (it_it)', 'Portuguese (pt_br)', 'Polish (pl_pl)', 'Chinese (zh_cn)', 'Turkish (tr_tr)', 'Czech (cs_cz)', 'Arabic (ar_ar)', 'Catalan (ca_ca)', 'Ukrainian (uk_ua)' ]", 'UI')
|
||||
|
||||
# Init timezone in case it changed
|
||||
conf.tz = timezone(conf.TIMEZONE)
|
||||
# Init timezone in case it changed and handle invalid values
|
||||
try:
|
||||
if conf.TIMEZONE not in all_timezones:
|
||||
raise UnknownTimeZoneError(f"Invalid timezone: {conf.TIMEZONE}")
|
||||
conf.tz = timezone(conf.TIMEZONE)
|
||||
except UnknownTimeZoneError:
|
||||
conf.tz = timezone(default_tz) # Init Default
|
||||
conf.TIMEZONE = ccd('TIMEZONE', conf.tz , c_d, '_KEEP_', '_KEEP_', '[]', 'General')
|
||||
mylog('none', [f"[Config] Invalid timezone '{conf.TIMEZONE}', defaulting to {default_tz}."])
|
||||
|
||||
# TODO cleanup later ----------------------------------------------------------------------------------
|
||||
# init all time values as we have timezone - all this shoudl be moved into plugin/plugin settings
|
||||
@@ -311,8 +319,7 @@ def importConfigs (db, all_plugins):
|
||||
|
||||
# -----------------
|
||||
# HANDLE APP_CONF_OVERRIDE via app_conf_override.json
|
||||
|
||||
# Assuming fullConfFolder is defined elsewhere
|
||||
|
||||
app_conf_override_path = fullConfFolder + '/app_conf_override.json'
|
||||
|
||||
if os.path.exists(app_conf_override_path):
|
||||
@@ -413,7 +420,7 @@ def importConfigs (db, all_plugins):
|
||||
# front end app log loggging
|
||||
write_notification(msg, 'info', timeNowTZ())
|
||||
|
||||
return all_plugins
|
||||
return all_plugins, True
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,10 @@ import datetime
|
||||
import threading
|
||||
import queue
|
||||
import time
|
||||
import logging
|
||||
|
||||
# NetAlertX imports
|
||||
|
||||
import conf
|
||||
from const import *
|
||||
|
||||
@@ -16,6 +20,16 @@ def timeNowTZ():
|
||||
else:
|
||||
return datetime.datetime.now().replace(microsecond=0)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Map custom debug levels to Python logging levels
|
||||
custom_to_logging_levels = {
|
||||
'none': logging.NOTSET,
|
||||
'minimal': logging.WARNING,
|
||||
'verbose': logging.INFO,
|
||||
'debug': logging.DEBUG,
|
||||
'trace': logging.DEBUG, # Can map to DEBUG or lower custom level if needed
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# More verbose as the numbers go up
|
||||
debugLevels = [
|
||||
@@ -25,6 +39,10 @@ debugLevels = [
|
||||
# use the LOG_LEVEL from the config, may be overridden
|
||||
currentLevel = conf.LOG_LEVEL
|
||||
|
||||
# tracking log levels
|
||||
setLvl = 0
|
||||
reqLvl = 0
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
class Logger:
|
||||
def __init__(self, LOG_LEVEL='verbose'):
|
||||
@@ -32,10 +50,36 @@ class Logger:
|
||||
|
||||
currentLevel = LOG_LEVEL
|
||||
|
||||
# Automatically set up custom logging handler
|
||||
self.setup_logging()
|
||||
|
||||
def setup_logging(self):
|
||||
root_logger = logging.getLogger()
|
||||
# Clear existing handlers to prevent duplicates
|
||||
if root_logger.hasHandlers():
|
||||
root_logger.handlers.clear()
|
||||
|
||||
# Create the custom handler
|
||||
my_log_handler = MyLogHandler()
|
||||
# my_log_handler.setLevel(custom_to_logging_levels.get(currentLevel, logging.NOTSET))
|
||||
|
||||
# Optional: Add a formatter for consistent log message format
|
||||
# formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
formatter = logging.Formatter('%(message)s', datefmt='%H:%M:%S')
|
||||
my_log_handler.setFormatter(formatter)
|
||||
|
||||
# Attach the handler to the root logger
|
||||
root_logger.addHandler(my_log_handler)
|
||||
root_logger.setLevel(custom_to_logging_levels.get(currentLevel, logging.NOTSET))
|
||||
|
||||
# for python logging
|
||||
class MyLogHandler(logging.Handler):
|
||||
def emit(self, record):
|
||||
log_entry = self.format(record)
|
||||
log_queue.put(log_entry)
|
||||
|
||||
def mylog(requestedDebugLevel, n):
|
||||
setLvl = 0
|
||||
reqLvl = 0
|
||||
global setLvl, reqLvl
|
||||
|
||||
# Get debug urgency/relative weight
|
||||
for lvl in debugLevels:
|
||||
@@ -86,23 +130,14 @@ def file_print(*args):
|
||||
result = timeNowTZ().strftime('%H:%M:%S') + ' '
|
||||
|
||||
for arg in args:
|
||||
result += str(arg)
|
||||
result += str(arg)
|
||||
|
||||
logging.log(custom_to_logging_levels.get(currentLevel, logging.NOTSET), result) # Forward to Python's logging system
|
||||
print(result)
|
||||
|
||||
# Ensure the log writer thread is running
|
||||
start_log_writer_thread()
|
||||
|
||||
# Queue the log entry for writing
|
||||
append_to_file_with_timeout( result, 5)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Function to append to the file with a timeout
|
||||
def append_to_file_with_timeout(data, timeout):
|
||||
try:
|
||||
log_queue.put_nowait(data)
|
||||
except queue.Full:
|
||||
print("Log queue is full, dropping log entry:" + data)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
def print_log(pText):
|
||||
# Check if logging is active
|
||||
|
||||
@@ -12,11 +12,12 @@ from collections import namedtuple
|
||||
import conf
|
||||
from const import pluginsPath, logPath, applicationPath, reportTemplatesPath
|
||||
from logger import mylog, Logger
|
||||
from helper import timeNowTZ, updateState, get_file_content, write_file, get_setting, get_setting_value
|
||||
from helper import timeNowTZ, get_file_content, write_file, get_setting, get_setting_value
|
||||
from app_state import updateState
|
||||
from api import update_api
|
||||
from plugin_utils import logEventStatusCounts, get_plugin_string, get_plugin_setting_obj, print_plugin_info, list_to_csv, combine_plugin_objects, resolve_wildcards_arr, handle_empty, custom_plugin_decoder, decode_and_rename_files
|
||||
from notification import Notification_obj, write_notification
|
||||
from execution_log import ExecutionLog
|
||||
from user_events_queue import UserEventsQueue
|
||||
|
||||
# Make sure log level is initialized correctly
|
||||
Logger(get_setting_value('LOG_LEVEL'))
|
||||
@@ -102,12 +103,7 @@ class plugin_param:
|
||||
self.multiplyTimeout = multiplyTimeout
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
class plugins_state:
|
||||
def __init__(self, processScan = False):
|
||||
self.processScan = processScan
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
def run_plugin_scripts(db, all_plugins, runType, pluginsState = plugins_state()):
|
||||
def run_plugin_scripts(db, all_plugins, runType):
|
||||
|
||||
# Header
|
||||
updateState("Run: Plugins")
|
||||
@@ -140,7 +136,7 @@ def run_plugin_scripts(db, all_plugins, runType, pluginsState = plugins_state())
|
||||
|
||||
print_plugin_info(plugin, ['display_name'])
|
||||
mylog('debug', ['[Plugins] CMD: ', get_plugin_setting_obj(plugin, "CMD")["value"]])
|
||||
pluginsState = execute_plugin(db, all_plugins, plugin, pluginsState)
|
||||
execute_plugin(db, all_plugins, plugin)
|
||||
# update last run time
|
||||
if runType == "schedule":
|
||||
for schd in conf.mySchedules:
|
||||
@@ -148,9 +144,6 @@ def run_plugin_scripts(db, all_plugins, runType, pluginsState = plugins_state())
|
||||
# note the last time the scheduled plugin run was executed
|
||||
schd.last_run = timeNowTZ()
|
||||
|
||||
return pluginsState
|
||||
|
||||
|
||||
|
||||
# Function to run a plugin command
|
||||
def run_plugin(command, set_RUN_TIMEOUT, plugin):
|
||||
@@ -167,20 +160,15 @@ def run_plugin(command, set_RUN_TIMEOUT, plugin):
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Executes the plugin command specified in the setting with the function specified as CMD
|
||||
def execute_plugin(db, all_plugins, plugin, pluginsState = plugins_state() ):
|
||||
def execute_plugin(db, all_plugins, plugin ):
|
||||
sql = db.sql
|
||||
|
||||
|
||||
if pluginsState is None:
|
||||
mylog('debug', ['[Plugins] pluginsState is None'])
|
||||
pluginsState = plugins_state()
|
||||
|
||||
# ------- necessary settings check --------
|
||||
set = get_plugin_setting_obj(plugin, "CMD")
|
||||
|
||||
# handle missing "function":"CMD" setting
|
||||
if set == None:
|
||||
return pluginsState
|
||||
return
|
||||
|
||||
set_CMD = set["value"]
|
||||
|
||||
@@ -394,7 +382,7 @@ def execute_plugin(db, all_plugins, plugin, pluginsState = plugins_state() ):
|
||||
# handle missing "function":"DB_PATH" setting
|
||||
if set == None:
|
||||
mylog('none', ['[Plugins] ⚠ ERROR: DB_PATH setting for plugin type sqlite-db-query missing.'])
|
||||
return pluginsState
|
||||
return
|
||||
|
||||
fullSqlitePath = set["value"]
|
||||
|
||||
@@ -408,7 +396,7 @@ def execute_plugin(db, all_plugins, plugin, pluginsState = plugins_state() ):
|
||||
except sqlite3.Error as e:
|
||||
mylog('none',[f'[Plugins] ⚠ ERROR: DB_PATH setting ({fullSqlitePath}) for plugin {plugin["unique_prefix"]}. Did you mount it correctly?'])
|
||||
mylog('none',[f'[Plugins] ⚠ ERROR: ATTACH DATABASE failed with SQL ERROR: ', e])
|
||||
return pluginsState
|
||||
return
|
||||
|
||||
for row in arr:
|
||||
# There has to be always 9 or 13 columns
|
||||
@@ -459,7 +447,7 @@ def execute_plugin(db, all_plugins, plugin, pluginsState = plugins_state() ):
|
||||
# check if the subprocess / SQL query failed / there was no valid output
|
||||
if len(sqlParams) == 0:
|
||||
mylog('none', [f'[Plugins] No output received from the plugin "{plugin["unique_prefix"]}"'])
|
||||
return pluginsState
|
||||
return
|
||||
else:
|
||||
mylog('verbose', ['[Plugins] SUCCESS, received ', len(sqlParams), ' entries'])
|
||||
mylog('debug', ['[Plugins] sqlParam entries: ', sqlParams])
|
||||
@@ -468,17 +456,24 @@ def execute_plugin(db, all_plugins, plugin, pluginsState = plugins_state() ):
|
||||
if len(sqlParams) > 0:
|
||||
|
||||
# create objects
|
||||
pluginsState = process_plugin_events(db, plugin, pluginsState, sqlParams)
|
||||
process_plugin_events(db, plugin, sqlParams)
|
||||
|
||||
# update API endpoints
|
||||
update_api(db, all_plugins, False, ["plugins_events","plugins_objects", "plugins_history", "appevents"])
|
||||
endpoints = ["plugins_events","plugins_objects", "plugins_history", "appevents"]
|
||||
|
||||
# check if we need to update devices api endpoint as well to prevent long user waits on Loading...
|
||||
userUpdatedDevices = UserEventsQueue().has_update_devices
|
||||
if userUpdatedDevices:
|
||||
endpoints += ["devices"]
|
||||
|
||||
update_api(db, all_plugins, True, endpoints, userUpdatedDevices)
|
||||
|
||||
return pluginsState
|
||||
return
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Check if watched values changed for the given plugin
|
||||
def process_plugin_events(db, plugin, pluginsState, plugEventsArr):
|
||||
def process_plugin_events(db, plugin, plugEventsArr):
|
||||
|
||||
sql = db.sql
|
||||
|
||||
@@ -780,13 +775,14 @@ def process_plugin_events(db, plugin, pluginsState, plugEventsArr):
|
||||
db.commitDB()
|
||||
|
||||
# perform scan if mapped to CurrentScan table
|
||||
if dbTable == 'CurrentScan':
|
||||
pluginsState.processScan = True
|
||||
if dbTable == 'CurrentScan':
|
||||
updateState("Process scan: True", None, None, None, None, True) # set processScan = True in the appState
|
||||
|
||||
|
||||
db.commitDB()
|
||||
|
||||
|
||||
return pluginsState
|
||||
return
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
@@ -844,14 +840,15 @@ class plugin_object_class:
|
||||
self.watchedHash = str(hash(tmp))
|
||||
|
||||
|
||||
|
||||
#===============================================================================
|
||||
# Handling of user initialized front-end events
|
||||
#===============================================================================
|
||||
def check_and_run_user_event(db, all_plugins, pluginsState):
|
||||
def check_and_run_user_event(db, all_plugins):
|
||||
"""
|
||||
Process user events from the execution queue log file and notify the user about executed events.
|
||||
"""
|
||||
execution_log = ExecutionLog()
|
||||
execution_log = UserEventsQueue()
|
||||
|
||||
# Track whether to show notification for executed events
|
||||
executed_events = []
|
||||
@@ -859,7 +856,10 @@ def check_and_run_user_event(db, all_plugins, pluginsState):
|
||||
# Read the log file to get the lines
|
||||
lines = execution_log.read_log()
|
||||
if not lines:
|
||||
return pluginsState # Exit early if the log file is empty
|
||||
mylog('debug', ['[check_and_run_user_event] User Execution Queue is empty'])
|
||||
return # Exit early if the log file is empty
|
||||
else:
|
||||
mylog('debug', ['[check_and_run_user_event] Process User Execution Queue:' + ', '.join(map(str, lines))])
|
||||
|
||||
for line in lines:
|
||||
# Extract event name and parameters from the log line
|
||||
@@ -871,11 +871,11 @@ def check_and_run_user_event(db, all_plugins, pluginsState):
|
||||
|
||||
# Process each event type
|
||||
if event == 'test':
|
||||
pluginsState = handle_test(param, db, all_plugins, pluginsState)
|
||||
handle_test(param, db, all_plugins)
|
||||
executed_events.append(f"test with param {param}")
|
||||
execution_log.finalize_event("test")
|
||||
elif event == 'run':
|
||||
pluginsState = handle_run(param, db, all_plugins, pluginsState)
|
||||
handle_run(param, db, all_plugins)
|
||||
executed_events.append(f"run with param {param}")
|
||||
execution_log.finalize_event("run")
|
||||
elif event == 'update_api':
|
||||
@@ -892,27 +892,27 @@ def check_and_run_user_event(db, all_plugins, pluginsState):
|
||||
mylog('minimal', ['[check_and_run_user_event] INFO: Executed events: ', executed_events_message])
|
||||
write_notification(f"[Ad-hoc events] Events executed: {executed_events_message}", "interrupt", timeNowTZ())
|
||||
|
||||
return pluginsState
|
||||
return
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
def handle_run(runType, db, all_plugins, pluginsState):
|
||||
def handle_run(runType, db, all_plugins):
|
||||
|
||||
mylog('minimal', ['[', timeNowTZ(), '] START Run: ', runType])
|
||||
|
||||
# run the plugin to run
|
||||
for plugin in all_plugins:
|
||||
if plugin["unique_prefix"] == runType:
|
||||
pluginsState = execute_plugin(db, all_plugins, plugin, pluginsState)
|
||||
execute_plugin(db, all_plugins, plugin)
|
||||
|
||||
mylog('minimal', ['[', timeNowTZ(), '] END Run: ', runType])
|
||||
return pluginsState
|
||||
return
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
def handle_test(runType, db, all_plugins, pluginsState):
|
||||
def handle_test(runType, db, all_plugins):
|
||||
|
||||
mylog('minimal', ['[', timeNowTZ(), '] [Test] START Test: ', runType])
|
||||
|
||||
@@ -924,12 +924,12 @@ def handle_test(runType, db, all_plugins, pluginsState):
|
||||
notificationObj = notification.create(sample_json, "")
|
||||
|
||||
# Run test
|
||||
pluginsState = handle_run(runType, db, all_plugins, pluginsState)
|
||||
handle_run(runType, db, all_plugins)
|
||||
|
||||
# Remove sample notification
|
||||
notificationObj.remove(notificationObj.GUID)
|
||||
|
||||
mylog('minimal', ['[Test] END Test: ', runType])
|
||||
|
||||
return pluginsState
|
||||
return
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ import json
|
||||
import conf
|
||||
from logger import mylog
|
||||
from const import pluginsPath, logPath, apiPath
|
||||
from helper import timeNowTZ, updateState, get_file_content, write_file, get_setting, get_setting_value, setting_value_to_python_type
|
||||
from helper import timeNowTZ, get_file_content, write_file, get_setting, get_setting_value, setting_value_to_python_type
|
||||
from app_state import updateState
|
||||
from crypto_utils import decrypt_data
|
||||
|
||||
module_name = 'Plugin utils'
|
||||
|
||||
@@ -4,7 +4,7 @@ import os
|
||||
from const import pluginsPath, logPath, applicationPath, reportTemplatesPath
|
||||
from logger import mylog
|
||||
|
||||
class ExecutionLog:
|
||||
class UserEventsQueue:
|
||||
"""
|
||||
Handles the execution queue log file, allowing reading, writing,
|
||||
and removing processed events.
|
||||
@@ -14,12 +14,23 @@ class ExecutionLog:
|
||||
self.log_path = logPath
|
||||
self.log_file = os.path.join(self.log_path, "execution_queue.log")
|
||||
|
||||
|
||||
def has_update_devices(self):
|
||||
lines = self.read_log()
|
||||
|
||||
for line in lines:
|
||||
if 'update_api|devices' in line:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def read_log(self):
|
||||
"""
|
||||
Reads the log file and returns all lines.
|
||||
Returns an empty list if the file doesn't exist.
|
||||
"""
|
||||
if not os.path.exists(self.log_file):
|
||||
mylog('none', ['[UserEventsQueue] Log file not found: ', self.log_file])
|
||||
return [] # No log file, return empty list
|
||||
with open(self.log_file, "r") as file:
|
||||
return file.readlines()
|
||||
@@ -61,7 +72,7 @@ class ExecutionLog:
|
||||
self.write_log(updated_lines)
|
||||
|
||||
|
||||
mylog('minimal', ['[ExecutionLog] Processed event: ', event])
|
||||
mylog('minimal', ['[UserEventsQueue] Processed event: ', event])
|
||||
|
||||
return removed
|
||||
|
||||
Reference in New Issue
Block a user