Compare commits

...

75 Commits

Author SHA1 Message Date
jokob-sk
15a7779d6e Sanitize input #805
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-26 08:08:24 +10:00
jokob-sk
2784f2ebeb Sanitize input #807 2024-09-26 07:32:04 +10:00
jokob-sk
d46046beea Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2024-09-26 07:22:02 +10:00
jokob-sk
6233f4d646 Sanitize input #805 2024-09-26 07:21:58 +10:00
github-actions[bot]
31411e0a14 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-25 11:53:41 +00:00
Hosted Weblate
8d824af3bd Merge branch 'origin/main' into Weblate.
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-24 23:50:28 +02:00
gallegonovato
f05f0d625a Translated using Weblate (Spanish)
Currently translated at 100.0% (698 of 698 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2024-09-24 23:50:28 +02:00
jokob-sk
2fec3b6607 📚Docs 2024-09-25 07:50:13 +10:00
jokob-sk
f285a28887 Cleanup 2024-09-25 07:25:31 +10:00
github-actions[bot]
11cb47fada [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-24 11:53:56 +00:00
Anonymous
d8b413b5e7 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 96.7% (675 of 698 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/zh_Hans/
2024-09-24 13:48:15 +02:00
Anonymous
656bba7ff7 Translated using Weblate (Polish)
Currently translated at 97.4% (680 of 698 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/pl/
2024-09-24 13:48:15 +02:00
Anonymous
a2cf8c1167 Translated using Weblate (Portuguese (Brazil))
Currently translated at 34.8% (243 of 698 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/pt_BR/
2024-09-24 13:48:15 +02:00
Anonymous
737cb07403 Translated using Weblate (Italian)
Currently translated at 99.1% (692 of 698 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/it/
2024-09-24 13:48:15 +02:00
Anonymous
3febbc21cb Translated using Weblate (Russian)
Currently translated at 99.1% (692 of 698 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ru/
2024-09-24 13:48:15 +02:00
Anonymous
7e14fae29c Translated using Weblate (Norwegian Bokmål)
Currently translated at 96.2% (672 of 698 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/nb_NO/
2024-09-24 13:48:14 +02:00
Anonymous
a16fe4561b Translated using Weblate (French)
Currently translated at 98.7% (689 of 698 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/
2024-09-24 13:48:14 +02:00
Anonymous
f2afe9d681 Translated using Weblate (Spanish)
Currently translated at 99.2% (693 of 698 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2024-09-24 13:48:14 +02:00
Anonymous
f8c0a5a1ef Translated using Weblate (German)
Currently translated at 93.4% (652 of 698 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/de/
2024-09-24 13:48:14 +02:00
github-actions[bot]
631e992411 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-23 11:54:00 +00:00
jokob-sk
feafaff218 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-23 13:33:17 +10:00
jokob-sk
f6a06842cc Overridden by ENV + Icons preview #802 2024-09-23 13:32:36 +10:00
jokob-sk
0cc3ede86c Merge pull request #801 from alnviana/patch-1
Some checks are pending
docker / docker_dev (push) Waiting to run
Fixes to work using a reverse proxy
2024-09-23 11:25:39 +10:00
Allan Viana
aa277136c6 Making table_appevents and table_notifications relative 2024-09-22 21:51:49 -03:00
Allan Viana
82ccb0c0b6 Making user_notifications relative 2024-09-22 21:18:48 -03:00
jokob-sk
30750a9449 ui components defer 2024-09-23 08:39:07 +10:00
jokob-sk
5278af48c5 Sync Hub fix + overriddenByEnv 2024-09-23 08:15:35 +10:00
github-actions[bot]
77f19c3575 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-22 11:53:38 +00:00
jokob-sk
10df7363d6 Merge pull request #798 from ingoratsdorf/contrib
Some checks are pending
docker / docker_dev (push) Waiting to run
Fixes to CSS and scripts not loading properly
2024-09-22 19:16:50 +10:00
jokob-sk
06e49f7adb 📚Docs 2024-09-22 17:05:49 +10:00
Ingo Ratsdorf
9fcbd9d64e DEFER break devicedetails page
Removed DEFER from ui_components as the device details page would not populate any more and the browser console would throw errors re function not found
2024-09-22 08:18:21 +12:00
Ingo Ratsdorf
c6888a79fd Fixes CSS typo in deviceDetails 2024-09-22 08:13:09 +12:00
github-actions[bot]
ef458903b7 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-21 11:53:41 +00:00
jokob-sk
b544734209 NEWDEV_dev_Icon preview #789
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-21 17:40:41 +10:00
github-actions[bot]
815810dc7a [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-20 11:53:33 +00:00
github-actions[bot]
552d79eee8 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-19 11:53:52 +00:00
jokob-sk
2f70e2e8d8 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-19 15:45:16 +10:00
jokob-sk
4a20b66c92 Clear NEW flag setting 2024-09-19 15:43:16 +10:00
github-actions[bot]
36cec0ab38 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-18 11:53:41 +00:00
jokob-sk
6bde0f9084 🔑 Set PWD not working #793
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-18 12:56:09 +10:00
jokob-sk
f64ef5b881 🔑 Set PWD not working #793
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-18 09:57:46 +10:00
jokob-sk
1895f68233 🔄 Sync hub 2.0.1 - init Node field #788 2024-09-18 09:27:32 +10:00
jokob-sk
d2fe53bc81 🔃 Sync hub 2.0.1 #788 2024-09-18 08:48:08 +10:00
github-actions[bot]
e9e45c34ae [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-17 11:53:54 +00:00
jokob-sk
064a51acee 🩹 Handle vendor NoneType #791
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-17 08:05:17 +10:00
github-actions[bot]
7340ce6da2 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-16 11:53:54 +00:00
github-actions[bot]
703885308a [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-15 11:53:52 +00:00
jokob-sk
71856b49a4 🩹 Handle vendor NoneType #791
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-15 08:32:35 +10:00
github-actions[bot]
86c7d26107 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-14 11:53:34 +00:00
jokob-sk
d858f4f9d0 🔄Sync hub 2.0
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-14 10:37:30 +10:00
jokob-sk
aefe470d31 🔄Sync hub 2.0 2024-09-14 09:37:27 +10:00
github-actions[bot]
99fb60c1b5 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-13 11:53:38 +00:00
Hosted Weblate
ec37e4d71b Merge branch 'origin/main' into Weblate. 2024-09-13 11:09:18 +00:00
gallegonovato
e240821d6c Translated using Weblate (Spanish)
Currently translated at 100.0% (694 of 694 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2024-09-13 11:09:15 +00:00
github-actions[bot]
632e441dda [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-12 11:53:59 +00:00
jokob-sk
24f7935891 📚Docs
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-12 07:55:20 +10:00
github-actions[bot]
dcc43d1f3c [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-11 11:53:35 +00:00
github-actions[bot]
8f35bf36ff [🤖Automation] Update README with sponsors information
Some checks failed
docker / docker_dev (push) Has been cancelled
2024-09-10 11:53:40 +00:00
jokob-sk
1548168eba Lang
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-10 08:26:06 +10:00
github-actions[bot]
2e35bac6ec [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-09 11:53:47 +00:00
jokob-sk
ba348fc4c2 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-09 07:30:38 +10:00
jokob-sk
d3337e75a9 ⚙ Settings/Lang cache improvements #687 + #766 2024-09-09 07:30:33 +10:00
github-actions[bot]
9e0bc043b0 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-08 11:53:36 +00:00
jokob-sk
29fdd0b115 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-08 13:57:24 +10:00
jokob-sk
48e92a186e 🧪 Override Settings via ENV variable [experimental] #687 2024-09-08 13:57:08 +10:00
github-actions[bot]
1dcb66e972 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-07 11:53:42 +00:00
jokob-sk
fa0d6d312d Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-09-07 09:28:36 +10:00
jokob-sk
a19fe342e7 🚀 Better upgarde message 2024-09-07 09:28:19 +10:00
jokob-sk
c4fc68cac8 Merge pull request #759 from elraro/fix-mtscan
chore: fixed mtscan and Dockerfile
2024-09-07 09:08:49 +10:00
jokob-sk
3a050c31a7 Update feature_request.yml 2024-09-07 09:06:29 +10:00
jokob-sk
2cd406a390 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2024-09-07 08:38:10 +10:00
jokob-sk
b086417686 💾 Cache update for proper status color + All display #779 2024-09-07 08:38:03 +10:00
elraro
615fd08f5b chore: changed mtscan type to device_scanner 2024-09-06 00:29:10 +02:00
elraro
4839211fe1 chore: fixed mtscan 2024-09-06 00:23:17 +02:00
elraro
ae1673c1c3 chore: fixed mtscan and Dockerfile 2024-08-11 23:55:02 +02:00
58 changed files with 1485 additions and 430 deletions

View File

@@ -9,20 +9,6 @@ body:
options:
- label: I have searched the existing open and closed issues
required: true
- type: checkboxes
attributes:
label: Am I willing to test this? 🧪
description: I rely on the community to test unreleased features. If you are requesting a feature, please be willing to test it within 48h of test request. Otherwise, the feature might be pulled from the code base.
options:
- label: I will do my best to test this feature on the `netlertx-dev` image when requested within 48h and report bugs to help deliver a great user experience for everyone and not to break existing installations.
required: true
- type: checkboxes
attributes:
label: Can I help implement this? 👩‍💻👨‍💻
description: The maintainer will provide guidance and help. The implementer will read the PR guidelines https://github.com/jokob-sk/NetAlertX/tree/main/docs#-pull-requests-prs
options:
- label: "Yes"
- label: "No"
- type: textarea
attributes:
label: Is your feature request related to a problem? Please describe
@@ -50,3 +36,17 @@ body:
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
required: true
- type: checkboxes
attributes:
label: Am I willing to test this? 🧪
description: I rely on the community to test unreleased features. If you are requesting a feature, please be willing to test it within 48h of test request. Otherwise, the feature might be pulled from the code base.
options:
- label: I will do my best to test this feature on the `netlertx-dev` image when requested within 48h and report bugs to help deliver a great user experience for everyone and not to break existing installations.
required: true
- type: checkboxes
attributes:
label: Can I help implement this? 👩‍💻👨‍💻
description: The maintainer will provide guidance and help. The implementer will read the PR guidelines https://github.com/jokob-sk/NetAlertX/tree/main/docs#-pull-requests-prs
options:
- label: "Yes"
- label: "No"

View File

@@ -1,8 +1,8 @@
FROM alpine:3.20 as builder
FROM alpine:3.20 AS builder
ARG INSTALL_DIR=/app
ENV PYTHONUNBUFFERED 1
ENV PYTHONUNBUFFERED=1
# Install build dependencies
RUN apk add --no-cache bash python3 python3-dev gcc musl-dev libffi-dev openssl-dev \
@@ -21,7 +21,7 @@ RUN pip install netifaces tplink-omada-client pycryptodome requests paho-mqtt sc
&& bash -c "find ${INSTALL_DIR} -type f \( -name '*.sh' -o -name '*.py' -o -name 'speedtest-cli' \) -exec chmod 750 {} \;"
# second stage
FROM alpine:3.20 as runner
FROM alpine:3.20 AS runner
ARG INSTALL_DIR=/app

View File

@@ -1,7 +1,7 @@
FROM debian:bookworm-slim
# default UID and GID
ENV USER=pi USER_ID=1000 USER_GID=1000 PORT=20211
ENV USER=pi USER_ID=1000 USER_GID=1000 PORT=20211
#TZ=Europe/London
# Todo, figure out why using a workdir instead of full paths don't work

View File

@@ -64,7 +64,9 @@ services:
# DELETE END anyone trying to use this file: comment out / delete ABOVE lines, they are only for development purposes
# ---------------------------------------------------------------------------
environment:
# - APP_CONF_OVERRIDE={"SCAN_SUBNETS":"['192.168.1.0/24 --interface=eth1']","UI_dark_mode":"True"}
- TZ=${TZ}
- PORT=${PORT}
- 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}

View File

@@ -41,7 +41,8 @@ docker run -d --rm --network=host \
| `PORT` |Port of the web interface | `20211` |
| `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` |
|`ALWAYS_FRESH_INSTALL` | Setting to `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`/`-dev` image. | `N/A` |
|`APP_CONF_OVERRIDE` | JSON override for settings, e.g. `{"SCAN_SUBNETS":"['192.168.1.0/24 --interface=eth1']","UI_dark_mode":"True"}` (Experimental 🧪) | `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` |
### Docker paths

View File

@@ -41,6 +41,16 @@ if [ "$ALWAYS_FRESH_INSTALL" = true ]; then
rm -rf "$INSTALL_DIR_OLD/db/"*
fi
# OVERRIDE settings: Handling APP_CONF_OVERRIDE
# Check if APP_CONF_OVERRIDE is set
if [ -z "$APP_CONF_OVERRIDE" ]; then
echo "APP_CONF_OVERRIDE is not set. Skipping config file creation."
else
# Save the APP_CONF_OVERRIDE env variable as a JSON file
echo "$APP_CONF_OVERRIDE" > "${INSTALL_DIR}/config/app_conf_override.json"
echo "Config file saved to ${INSTALL_DIR}/config/app_conf_override.json"
fi
# 🔻 FOR BACKWARD COMPATIBILITY - REMOVE AFTER 12/12/2024
# Check if pialert.db exists, then create a symbolic link to app.db

275
docs/AUTHELIA.md Executable file
View File

@@ -0,0 +1,275 @@
(DRAFT) Authelia support
```yaml
theme: dark
default_2fa_method: "totp"
server:
address: 0.0.0.0:9091
endpoints:
enable_expvars: false
enable_pprof: false
authz:
forward-auth:
implementation: 'ForwardAuth'
authn_strategies:
- name: 'HeaderAuthorization'
schemes:
- 'Basic'
- name: 'CookieSession'
ext-authz:
implementation: 'ExtAuthz'
authn_strategies:
- name: 'HeaderAuthorization'
schemes:
- 'Basic'
- name: 'CookieSession'
auth-request:
implementation: 'AuthRequest'
authn_strategies:
- name: 'HeaderAuthRequestProxyAuthorization'
schemes:
- 'Basic'
- name: 'CookieSession'
legacy:
implementation: 'Legacy'
authn_strategies:
- name: 'HeaderLegacy'
- name: 'CookieSession'
disable_healthcheck: false
tls:
key: ""
certificate: ""
client_certificates: []
headers:
csp_template: ""
log:
## Level of verbosity for logs: info, debug, trace.
level: info
###############################################################
# The most important section
###############################################################
access_control:
## Default policy can either be 'bypass', 'one_factor', 'two_factor' or 'deny'.
default_policy: deny
networks:
- name: internal
networks:
- '192.168.0.0/18'
- '10.10.10.0/8' # Zerotier
- name: private
networks:
- '172.16.0.0/12'
rules:
- networks:
- private
domain:
- '*'
policy: bypass
- networks:
- internal
domain:
- '*'
policy: bypass
- domain:
# exclude itself from auth, should not happen as we use Traefik middleware on a case-by-case screnario
- 'auth.MYDOMAIN1.TLD'
- 'authelia.MYDOMAIN1.TLD'
- 'auth.MYDOMAIN2.TLD'
- 'authelia.MYDOMAIN2.TLD'
policy: bypass
- domain:
#All subdomains match
- 'MYDOMAIN1.TLD'
- '*.MYDOMAIN1.TLD'
policy: two_factor
- domain:
# This will not work yet as Authelio does not support multi-domain authentication
- 'MYDOMAIN2.TLD'
- '*.MYDOMAIN2.TLD'
policy: two_factor
############################################################
identity_validation:
reset_password:
jwt_secret: "[REDACTED]"
identity_providers:
oidc:
enable_client_debug_messages: true
enforce_pkce: public_clients_only
hmac_secret: [REDACTED]
lifespans:
authorize_code: 1m
id_token: 1h
refresh_token: 90m
access_token: 1h
cors:
endpoints:
- authorization
- token
- revocation
- introspection
- userinfo
allowed_origins:
- "*"
allowed_origins_from_client_redirect_uris: false
jwks:
- key: [REDACTED]
certificate_chain:
clients:
- client_id: portainer
client_name: Portainer
# generate secret with "authelia crypto hash generate pbkdf2 --random --random.length 32 --random.charset alphanumeric"
# Random Password: [REDACTED]
# Digest: [REDACTED]
client_secret: [REDACTED]
token_endpoint_auth_method: 'client_secret_post'
public: false
authorization_policy: two_factor
consent_mode: pre-configured #explicit
pre_configured_consent_duration: '6M' #Must be re-authorised every 6 Months
scopes:
- openid
#- groups #Currently not supported in Authelia V
- email
- profile
redirect_uris:
- https://portainer.MYDOMAIN1.LTD
userinfo_signed_response_alg: none
- client_id: openproject
client_name: OpenProject
# generate secret with "authelia crypto hash generate pbkdf2 --random --random.length 32 --random.charset alphanumeric"
# Random Password: [REDACTED]
# Digest: [REDACTED]
client_secret: [REDACTED]
token_endpoint_auth_method: 'client_secret_basic'
public: false
authorization_policy: two_factor
consent_mode: pre-configured #explicit
pre_configured_consent_duration: '6M' #Must be re-authorised every 6 Months
scopes:
- openid
#- groups #Currently not supported in Authelia V
- email
- profile
redirect_uris:
- https://op.MYDOMAIN.TLD
#grant_types:
# - refresh_token
# - authorization_code
#response_types:
# - code
#response_modes:
# - form_post
# - query
# - fragment
userinfo_signed_response_alg: none
##################################################################
telemetry:
metrics:
enabled: false
address: tcp://0.0.0.0:9959
totp:
disable: false
issuer: authelia.com
algorithm: sha1
digits: 6
period: 30 ## The period in seconds a one-time password is valid for.
skew: 1
secret_size: 32
webauthn:
disable: false
timeout: 60s ## Adjust the interaction timeout for Webauthn dialogues.
display_name: Authelia
attestation_conveyance_preference: indirect
user_verification: preferred
ntp:
address: "pool.ntp.org"
version: 4
max_desync: 5s
disable_startup_check: false
disable_failure: false
authentication_backend:
password_reset:
disable: false
custom_url: ""
refresh_interval: 5m
file:
path: /config/users_database.yml
watch: true
password:
algorithm: argon2
argon2:
variant: argon2id
iterations: 3
memory: 65536
parallelism: 4
key_length: 32
salt_length: 16
password_policy:
standard:
enabled: false
min_length: 8
max_length: 0
require_uppercase: true
require_lowercase: true
require_number: true
require_special: true
## zxcvbn is a well known and used password strength algorithm. It does not have tunable settings.
zxcvbn:
enabled: false
min_score: 3
regulation:
max_retries: 3
find_time: 2m
ban_time: 5m
session:
name: authelia_session
secret: [REDACTED]
expiration: 60m
inactivity: 15m
cookies:
- domain: 'MYDOMAIN1.LTD'
authelia_url: 'https://auth.MYDOMAIN1.LTD'
name: 'authelia_session'
default_redirection_url: 'https://MYDOMAIN1.LTD'
- domain: 'MYDOMAIN2.LTD'
authelia_url: 'https://auth.MYDOMAIN2.LTD'
name: 'authelia_session_other'
default_redirection_url: 'https://MYDOMAIN2.LTD'
storage:
encryption_key: [REDACTED]
local:
path: /config/db.sqlite3
notifier:
disable_startup_check: true
smtp:
address: MYOTHERDOMAIN.LTD:465
timeout: 5s
username: "USER@DOMAIN"
password: "[REDACTED]"
sender: "Authelia <postmaster@MYOTHERDOMAIN.LTD>"
identifier: NAME@MYOTHERDOMAIN.LTD
subject: "[Authelia] {title}"
startup_check_address: postmaster@MYOTHERDOMAIN.LTD
```

View File

@@ -8,7 +8,7 @@ There are 3 artifacts that can be used to backup the application:
| File | Description | Limitations |
|-----------------------|-------------------------------|-------------------------------|
| `/db/app.db` | Database file(s) | The database file might be in an uncommitted state or corrupted |
| `/config/app.conf` | Configuration file | Doesn't contain settings from the Maintenance section |
| `/config/app.conf` | Configuration file | Can be overridden with the [`APP_CONF_OVERRIDE` env variable](https://github.com/jokob-sk/NetAlertX/tree/main/dockerfiles#docker-environment-variables). |
| `/config/devices.csv` | CSV file containing device information | Doesn't contain historical data |
## Data and backup storage

View File

@@ -17,7 +17,7 @@
| Pholus_Scan | Scan results of the Pholus python network penetration script. | ![Screen8][screen8] |
| Plugins_Events | For capturing events exposed by a plugin via the `last_result.log` file. If unique then saved into the `Plugins_Objects` table. Entries are deleted once processed and stored in the `Plugins_History` and/or `Plugins_Objects` tables. | ![Screen10][screen10] |
| Plugins_History | History of all entries from the `Plugins_Events` table | ![Screen11][screen11] |
| Plugins_Language_Strings | Language strings colelcted from the plugin `config.json` files used for string resolution in the frontend. | ![Screen12][screen12] |
| Plugins_Language_Strings | Language strings collected from the plugin `config.json` files used for string resolution in the frontend. | ![Screen12][screen12] |
| Plugins_Objects | Unique objects detected by individual plugins. | ![Screen13][screen13] |
| Sessions | Used to display sessions in the charts | ![Screen15][screen15] |
| Settings | Database representation of the sum of all settings from `app.conf` and plugins coming from `config.json` files. | ![Screen16][screen16] |

View File

@@ -518,6 +518,60 @@ Required attributes are:
| (optional) `"override_value"` | Used to determine a user-defined override for the setting. Useful for template-based plugins, where you can choose to leave the current value or override it with the value defined in the setting. (Work in progress) |
| (optional) `"events"` | Used to trigger the plugin. Usually used on the `RUN` setting. Not fully tested in all scenarios. Will show a play button next to the setting. After clicking, an event is generated for the backend in the `Parameters` database table to process the front-end event on the next run. |
### UI Component Types Documentation
This section outlines the structure and types of UI components, primarily used to build HTML forms or interactive elements dynamically. Each UI component has a `"type"` which defines its structure, behavior, and rendering options.
#### UI Component JSON Structure
The UI component is defined as a JSON object containing a list of `elements`. Each element specifies how it should behave, with properties like `elementType`, `elementOptions`, and any associated `transformers` to modify the data. The example below demonstrates how a component with two elements (`span` and `select`) is structured:
```json
{
"function": "dev_Icon",
"type": {
"dataType": "string",
"elements": [
{
"elementType": "span",
"elementOptions": [
{ "cssClasses": "input-group-addon iconPreview" },
{ "getStringKey": "Gen_SelectToPreview" },
{ "customId": "NEWDEV_dev_Icon_preview" }
],
"transformers": []
},
{
"elementType": "select",
"elementHasInputValue": 1,
"elementOptions": [
{ "cssClasses": "col-xs-12" },
{
"onChange": "updateIconPreview(this)"
},
{ "customParams": "NEWDEV_dev_Icon,NEWDEV_dev_Icon_preview" }
],
"transformers": []
}
]
}
}
```
### Rendering Logic
The code snippet provided demonstrates how the elements are iterated over to generate their corresponding HTML. Depending on the `elementType`, different HTML tags (like `<select>`, `<input>`, `<textarea>`, `<button>`, etc.) are created with the respective attributes such as `onChange`, `my-data-type`, and `class` based on the provided `elementOptions`. Events can also be attached to elements like buttons or select inputs.
### Key Element Types
- **`select`**: Renders a dropdown list. Additional options like `isMultiSelect` and event handlers (e.g., `onChange`) can be attached.
- **`input`**: Handles various types of input fields, including checkboxes, text, and others, with customizable attributes like `readOnly`, `placeholder`, etc.
- **`button`**: Generates clickable buttons with custom event handlers (`onClick`), icons, or labels.
- **`textarea`**: Creates a multi-line input box for text input.
- **`span`**: Used for inline text or content with customizable classes and data attributes.
Each element may also have associated events (e.g., running a scan or triggering a notification) defined under `Events`.
##### Supported settings `function` values

View File

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

View File

@@ -15,6 +15,7 @@ You need to specify the network interface and the network mask. You can also con
* One subnet: `SCAN_SUBNETS = ['192.168.1.0/24 --interface=eth0']`
* Two subnets: `SCAN_SUBNETS = ['192.168.1.0/24 --interface=eth0','192.168.1.0/24 --interface=eth1 -vlan=107']`
If you get timeout messages, decrease the network mask (e.g.: from a `/16` to `/24`) or increase the `TIMEOUT` setting (e.g.: `ARPSCAN_RUN_TIMEOUT` to `300` (a timeout of 5min)) for the plugin and the interval between scans (e.g.: `ARPSCAN_RUN_SCHD` to `*/10 * * * *` (scans every 10 min)).
## Explanation

View File

@@ -17,7 +17,7 @@ showSpinner()
$(document).ready(function() {
// Load JSON data from the provided URL
$.getJSON('/api/table_appevents.json', function(data) {
$.getJSON('api/table_appevents.json', function(data) {
// Process the JSON data and generate UI dynamically
processData(data)

View File

@@ -1097,6 +1097,11 @@ input[readonly] {
display: inline-block;
}
#settingsPage .form-control
{
min-height: 42px;
}
#settingsPage .select2-selection
{
background-color: rgb(96, 96, 96);
@@ -1112,11 +1117,41 @@ input[readonly] {
display: inline-grid;
}
/* Basic style for the div elements */
#settingsPage .setting_overriden_by_env {
position: relative;
/* width: 300px;
height: 200px; */
background-color: #f3f3f3;
border: 1px solid #ccc;
margin: 20px;
z-index: 10;
}
/* Style for the overlay */
#settingsPage .setting_overriden_by_env::after {
content: "Overridden with ENV variable";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6); /* semi-transparent black overlay */
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
font-weight: 700;
z-index: 11;
}
/* ----------------------------------------------------------------- */
/* Devices page */
/* ----------------------------------------------------------------- */
#txtIconFA {
.iconPreview {
min-width: 40px;
}

View File

@@ -100,7 +100,7 @@
<div class="col-lg-12 col-sm-12 col-xs-12">
<!-- <div class="box-transparent"> -->
<div id="navDevice" class="nav-tabs-custom">
<ul class="nav nav-tabs" style="fon t-size:16px;">
<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>
@@ -197,8 +197,8 @@
</label>
<div class="col-sm-9">
<div class="input-group">
<span class="input-group-addon" id="txtIconFA"></span>
<input class="form-control" id="txtIcon" type="text" value="--" readonly>
<span class="input-group-addon iconPreview" id="txtIconPreview" my-customid="txtIconPreview"></span>
<input class="form-control" id="txtIcon" my-customid="txtIcon" my-customparams="txtIcon,txtIconPreview" type="text" value="--" readonly>
<span class="input-group-addon" title='<?= lang('DevDetail_button_AddIcon_Tooltip');?>'><i class="fa fa-square-plus pointer" onclick="askAddIcon();"></i></span>
<span class="input-group-addon" title='<?= lang('DevDetail_button_OverwriteIcons_Tooltip');?>'><i class="fa fa-copy pointer" onclick="askOverwriteIconType();"></i></span>
<div class="input-group-btn">
@@ -693,7 +693,6 @@ if ($ENABLED_DARKMODE === True) {
var pos = -1;
var parPeriod = 'Front_Details_Period';
var parTab = 'Front_Details_Tab';
var parSessionsRows = 'Front_Details_Sessions_Rows';
var parEventsRows = 'Front_Details_Events_Rows';
var parEventsHide = 'Front_Details_Events_Hide';
@@ -736,7 +735,7 @@ function main () {
$('#chkHideConnectionEvents')[0].checked = eval(eventsHide == 'true');
// Initialize components with parameters
initializeTabs();
initializeTabsNew();
initializeiCheck();
initializeCombos();
initializeDatatables();
@@ -756,24 +755,13 @@ function main () {
// Show device icon as it changes
$('#txtIcon').on('change input', function() {
updateIconPreview('#txtIcon')
updateIconPreview(this)
});
}
// -----------------------------------------------------------------------------
function initializeTabs () {
// Activate panel
$('.nav-tabs a[id='+ tab +']').tab('show');
// When changed save new current tab
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
setParameter (parTab, $(e.target).attr('id'));
});
}
// -----------------------------------------------------------------------------
function initializeiCheck () {
// Blue
@@ -1148,7 +1136,7 @@ function initializeCalendar () {
showSpinner()
} else {
setTimeout(() => {
updateIconPreview('#txtIcon')
updateIconPreview($('#txtIcon'))
}, 500);
hideSpinner()
@@ -1457,10 +1445,10 @@ function setDeviceData (direction='', refreshCallback='') {
// update data to server
$.get('php/server/devices.php?action=setDeviceData&mac='+ mac
+ '&name=' + encodeURIComponent($('#txtName').val())
+ '&owner=' + encodeURIComponent($('#txtOwner').val())
+ '&name=' + encodeURIComponent($('#txtName').val().replace(/'/g, ""))
+ '&owner=' + encodeURIComponent($('#txtOwner').val().replace(/'/g, ""))
+ '&type=' + $('#txtDeviceType').val()
+ '&vendor=' + encodeURIComponent($('#txtVendor').val())
+ '&vendor=' + encodeURIComponent($('#txtVendor').val().replace(/'/g, ""))
+ '&icon=' + encodeURIComponent($('#txtIcon').val())
+ '&favorite=' + ($('#chkFavorite')[0].checked * 1)
+ '&group=' + encodeURIComponent($('#txtGroup').val())
@@ -1679,7 +1667,7 @@ function addAsBase64 () {
$('#txtIcon').val(iconHtmlBase64);
updateIconPreview('#txtIcon')
updateIconPreview($('#txtIcon'))
}

View File

@@ -137,10 +137,8 @@
<!-- page script ----------------------------------------------------------- -->
<script>
var deviceStatus = 'all';
var parTableRows = 'Front_Devices_Rows';
var parTableOrder = 'Front_Devices_Order';
var tableRows = getCookie ("nax_parTableRows") == "" ? 10 : getCookie ("nax_parTableRows") ;
var tableOrder = getCookie ("nax_parTableOrder") == "" ? [[3,'desc'], [0,'asc']] : JSON.parse(getCookie ("nax_parTableOrder")) ;
var tableOrder = getCookie ("nax_parTableOrder") == "" ? [[3,'desc'], [0,'asc']] : JSON.parse(getCookie ("nax_parTableOrder")) ;
var tableColumnHide = [];
var tableColumnOrder = [];
@@ -403,6 +401,11 @@ function initializeDatatable (status) {
}
$.get('api/table_devices.json?nocache=' + Date.now(), function(result) {
// refresh devices cache
devicesListAll_JSON = result["data"]
devicesListAll_JSON_str = JSON.stringify(devicesListAll_JSON)
setCache('devicesListAll_JSON', devicesListAll_JSON_str)
// query data
getDevicesTotals(result.data);
@@ -451,10 +454,6 @@ function initializeDatatable (status) {
})
};
// TODO displayed columns
// Check if the DataTable already exists
if ($.fn.dataTable.isDataTable('#tableDevices')) {
// The DataTable exists, so destroy it
@@ -462,12 +461,12 @@ function initializeDatatable (status) {
table.clear().destroy();
}
var table=
var table =
$('#tableDevices').DataTable({
'data' : dataArray["data"],
'paging' : true,
'lengthChange' : true,
'lengthMenu' : [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, getString('Device_Tablelenght_all')]],
'lengthMenu' : [[10, 25, 50, 100, 500, 100000], [10, 25, 50, 100, 500, getString('Device_Tablelenght_all')]],
'searching' : true,
'ordering' : true,
@@ -639,8 +638,6 @@ function initializeDatatable (status) {
// Check if any row is selected
var anyRowSelected = $('#tableDevices tr.selected').length > 0;
console.log(anyRowSelected);
// Toggle visibility of element with ID 'multiEdit'
$('#multiEdit').toggle(anyRowSelected);
}, 200);

View File

@@ -78,7 +78,7 @@ if (isset ($_SESSION["login"]) == FALSE || $_SESSION["login"] != 1)
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Net Alert X | Log in</title>
<title>NetAlert X | Log in</title>
<!-- Tell the browser to be responsive to screen width -->
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<!-- Bootstrap 3.3.7 -->
@@ -104,7 +104,7 @@ if ($ENABLED_DARKMODE === True) {
<body class="hold-transition login-page">
<div class="login-box login-custom">
<div class="login-logo">
<a href="/index2.php">Net <b>Alert</b><sup>x</sup></a>
<a href="/index2.php">Net<b>Alert</b><sup>x</sup></a>
</div>
<!-- /.login-logo -->
<div class="login-box-body">

View File

@@ -212,7 +212,8 @@ function cacheStrings() {
return new Promise((resolve, reject) => {
// Create a promise for each language
const languagePromises = allLanguages.map((language_code) => {
languagesToLoad = ['en_us', getLangCode()]
const languagePromises = languagesToLoad.map((language_code) => {
return new Promise((resolveLang, rejectLang) => {
// Fetch core strings and translations
@@ -267,6 +268,29 @@ function cacheStrings() {
function getString(key) {
function fetchString(key) {
lang_code = getLangCode();
let result = getCache(`pia_lang_${key}_${lang_code}`, true);
if (isEmpty(result)) {
result = getCache(`pia_lang_${key}_en_us`, true);
}
return result;
}
if (isAppInitialized()) {
return fetchString(key);
} else {
callAfterAppInitialized(() => fetchString(key));
}
}
// -----------------------------------------------------------------------------
// Get current language ISO code
function getLangCode() {
UI_LANG = getSetting("UI_LANG");
let lang_code = 'en_us';
@@ -310,20 +334,7 @@ function getString(key) {
break;
}
let result = getCache(`pia_lang_${key}_${lang_code}`, true);
if (isEmpty(result)) {
result = getCache(`pia_lang_${key}_en_us`, true);
}
return result;
}
if (isAppInitialized()) {
return fetchString(key);
} else {
callAfterAppInitialized(() => fetchString(key));
}
return lang_code;
}
@@ -619,17 +630,11 @@ function debugTimer () {
// -----------------------------------------------------------------------------
function secondsSincePageLoad() {
// Get the current time
var currentTime = Date.now();
// Get the time when the page was loaded
var pageLoadTime = performance.timeOrigin;
// Calculate the difference in milliseconds
var timeDifference = currentTime - pageLoadTime;
// Get the current time since the page was loaded
var timeSincePageLoad = performance.now();
// Convert milliseconds to seconds
var secondsAgo = Math.floor(timeDifference / 1000);
var secondsAgo = Math.floor(timeSincePageLoad / 1000);
return secondsAgo;
}
@@ -1193,7 +1198,7 @@ const sessionStorageKey = "myScriptExecuted_common_js";
var completedCalls = []
var completedCalls_final = ['cacheSettings', 'cacheStrings', 'cacheDevices'];
var completedCallsCount = 0;
var completedCallsCount_final = allLanguages.length + 2; // number of language files + cacheDevices + cacheSettings
var completedCallsCount_final;
// -----------------------------------------------------------------------------
// Clearing all the caches
@@ -1249,6 +1254,10 @@ function callAfterAppInitialized(callback) {
// Check if the code has been executed before by checking sessionStorage
function isAppInitialized() {
// return arraysContainSameValues(getCache("completedCalls").split(',').filter(Boolean), completedCalls_final);
// loading settings + 1 (or 2 language files if not english) + device cache.
completedCallsCount_final = getLangCode() == 'en_us' ? 3 : 4 ;
return (parseInt(getCache("completedCallsCount")) >= completedCallsCount_final);
}

View File

@@ -295,6 +295,7 @@ function checkNotification() {
console.log(response);
// After marking the notification as read, check for the next one
checkNotification();
hideSpinner();
},
error: function(xhr, status, error) {
console.error("Error marking notification as read:", status, error);

View File

@@ -667,7 +667,11 @@ const handleElementOptions = (codeName, elementOptions, transformers, val) => {
let valRes = val;
let sourceIds = [];
let getStringKey = "";
let onClick = "alert('Not implemented');";
let onClick = "console.log('onClick - Not implemented');";
let onChange = "console.log('onChange - Not implemented');";
let customParams = "";
let customId = "";
elementOptions.forEach((option) => {
if (option.prefillValue) {
@@ -711,6 +715,15 @@ const handleElementOptions = (codeName, elementOptions, transformers, val) => {
if (option.onClick) {
onClick = option.onClick;
}
if (option.onChange) {
onChange = option.onChange;
}
if (option.customParams) {
customParams = option.customParams;
}
if (option.customId) {
customId = option.customId;
}
});
if (transformers.includes("sha256")) {
@@ -731,6 +744,9 @@ const handleElementOptions = (codeName, elementOptions, transformers, val) => {
valRes,
getStringKey,
onClick,
onChange,
customParams,
customId
};
};

View File

@@ -68,23 +68,66 @@ function initDeviceSelectors(devicesListAll_JSON) {
}
// -----------------------------------------------------------------------------
// ----------------------------------------------
// Updates the icon preview
function updateIconPreview (inputId) {
// update icon
iconInput = $(inputId)
function updateIconPreview(elem) {
// Retrieve and parse custom parameters from the element
let params = $(elem).attr("my-customparams")?.split(',').map(param => param.trim());
value = iconInput.val()
// console.log(params);
iconInput.on('change input', function() {
$('#txtIconFA').html(atob(value))
});
if (params && params.length >= 2) {
var inputElementID = params[0];
var targetElementID = params[1];
} else {
console.error("Invalid parameters passed to updateIconPreview function");
return;
}
$('#txtIconFA').html(atob(value))
// Get the input element using the inputElementID
let iconInput = $("#" + inputElementID);
if (iconInput.length === 0) {
console.error("Icon input element not found");
return;
}
// Get the initial value and update the target element
let value = iconInput.val();
if (!value) {
console.error("Input value is empty or not defined");
return;
}
if (!targetElementID) {
targetElementID = "txtIcon";
}
// Check if the target element exists, if not find an element with matching custom attribute
let targetElement = $('#' + targetElementID);
if (targetElement.length === 0) {
// Look for an element with my-custom-id attribute equal to targetElementID
targetElement = $('[my-customid="' + targetElementID + '"]');
if (targetElement.length === 0) {
console.error("Neither target element with ID nor element with custom attribute found");
return;
}
}
// Update the target element with decoded base64 value
targetElement.html(atob(value));
// Add event listener to update the icon on input change
iconInput.on('change input', function () {
let newValue = $(this).val();
$('#' + targetElementID).html(atob(newValue));
});
}
// -----------------------------------------------------------------------------
// Generic function to copy text to clipboard
function copyToClipboard(buttonElement) {

View File

@@ -1034,7 +1034,7 @@ var mouse = $.widget("ui.mouse", {
return this.mouseDelayMet;
},
// These are placeholder methods, to be overriden by extending plugin
// These are placeholder methods, to be overridden by extending plugin
_mouseStart: function(/* event */) {},
_mouseDrag: function(/* event */) {},
_mouseStop: function(/* event */) {},

View File

@@ -101,19 +101,20 @@
for (let j = i * elementsPerColumn; j < Math.min((i + 1) * elementsPerColumn, columns.length); j++) {
const setTypeObject = JSON.parse(columns[j].Type.replace(/'/g, '"'));
// console.log(setTypeObject); 🔽
// const lastElementObj = setTypeObject.elements[setTypeObject.elements.length - 1]
// get the element with the input value(s)
let elementsWithInputValue = setTypeObject.elements.filter(element => element.elementHasInputValue === 1);
let elements = setTypeObject.elements.filter(element => element.elementHasInputValue === 1);
// if none found, take last
if(elementsWithInputValue.length == 0)
if(elements.length == 0)
{
elementsWithInputValue = setTypeObject.elements[setTypeObject.elements.length - 1]
elementWithInputValue = setTypeObject.elements[setTypeObject.elements.length - 1]
} else
{
elementWithInputValue = elements[0]
}
const { elementType, elementOptions = [], transformers = [] } = elementsWithInputValue;
const { elementType, elementOptions = [], transformers = [] } = elementWithInputValue;
const {
inputType,
readOnly,
@@ -127,26 +128,28 @@
editable,
valRes,
getStringKey,
onClick
onClick,
onChange,
customParams,
customId
} = handleElementOptions('none', elementOptions, transformers, val = "");
// console.log(setTypeObject);
// console.log(inputType);
// render based on element type
if (elementsWithInputValue.elementType === 'select') {
if (elementType === 'select') {
targetLocation = columns[j].Code_Name + "_generateSetOptions"
generateOptionsOrSetOptions(columns[j].Code_Name, [], targetLocation, generateOptions)
// Handle Icons as tehy need a preview
console.log(columns[j].Code_Name)
// Handle Icons as they need a preview
if(columns[j].Code_Name == 'NEWDEV_dev_Icon')
{
input = `
<span class="input-group-addon" id="txtIconFA"></span>
<span class="input-group-addon iconPreview" my-customid="NEWDEV_dev_Icon_preview"></span>
<select class="form-control"
onChange="updateIconPreview('#NEWDEV_dev_Icon')"
onChange="updateIconPreview(this)"
my-customparams="NEWDEV_dev_Icon,NEWDEV_dev_Icon_preview"
id="${columns[j].Code_Name}"
data-my-column="${columns[j].Code_Name}"
data-my-targetColumns="${columns[j].Code_Name.replace('NEWDEV_','')}" >
@@ -164,7 +167,7 @@
}
} else if (elementsWithInputValue.elementType === 'input'){
} else if (elementType === 'input'){
// Add classes specifically for checkboxes
inputType === 'checkbox' ? inputClass = 'checkbox' : inputClass = 'form-control';
@@ -172,6 +175,7 @@
input = `<input class="${inputClass}"
id="${columns[j].Code_Name}"
my-customid="${columns[j].Code_Name}"
data-my-column="${columns[j].Code_Name}"
data-my-targetColumns="${columns[j].Code_Name.replace('NEWDEV_','')}"
type="${inputType}">`

View File

@@ -783,7 +783,7 @@
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 overriden by the target)
// get the tab id from the cookie (already overridden by the target)
if(!emptyArr.includes(getCache(key)))
{
selectedTab = getCache(key);

View File

@@ -53,8 +53,8 @@
<link rel="stylesheet" href="lib/AdminLTE/bower_components/select2/dist/css/select2.min.css">
<!-- NetAlertX -->
<script src="js/handle_version.js"></script>
<script defer src="js/ui_components.js?v=<?php include 'php/templates/version.php'; ?>"></script>
<script defer src="js/handle_version.js"></script>
<script src="js/ui_components.js?v=<?php include 'php/templates/version.php'; ?>"></script>
<!-- Select2 JavaScript -->

View File

@@ -56,6 +56,8 @@
"BackDevices_Restore_okay": "",
"BackDevices_darkmode_disabled": "",
"BackDevices_darkmode_enabled": "",
"CLEAR_NEW_FLAG_description": "",
"CLEAR_NEW_FLAG_name": "",
"DAYS_TO_KEEP_EVENTS_description": "",
"DAYS_TO_KEEP_EVENTS_name": "",
"DevDetail_Copy_Device_Title": "",
@@ -293,6 +295,7 @@
"Gen_Save": "",
"Gen_Saved": "",
"Gen_Search": "",
"Gen_SelectToPreview": "",
"Gen_Selected_Devices": "",
"Gen_Switch": "",
"Gen_Upd": "",
@@ -557,6 +560,7 @@
"RandomMAC_hover": "",
"Reports_Sent_Log": "",
"SCAN_SUBNETS_description": "",
"SCAN_SUBNETS_name": "",
"SYSTEM_TITLE": "",
"Setting_Override": "",
"Setting_Override_Description": "",

View File

@@ -68,6 +68,8 @@
"BackDevices_Restore_okay": "Die Wiederherstellung wurde erfolgreich ausgeführt.",
"BackDevices_darkmode_disabled": "Heller Modus aktiviert.",
"BackDevices_darkmode_enabled": "Dunkler Modus aktiviert.",
"CLEAR_NEW_FLAG_description": "",
"CLEAR_NEW_FLAG_name": "",
"DAYS_TO_KEEP_EVENTS_description": "Dies ist eine Wartungseinstellung. Spezifiziert wie viele Tage Events gespeichert bleiben. Alle älteren Events werden periodisch gelöscht. Wird auch auf die Plugins History angewendet.",
"DAYS_TO_KEEP_EVENTS_name": "Lösche Events älter als",
"DevDetail_Copy_Device_Title": "<i class=\"fa fa-copy\"></i> Details von Gerät kopieren",
@@ -305,6 +307,7 @@
"Gen_Save": "Speichern",
"Gen_Saved": "Gespeichert",
"Gen_Search": "Suchen",
"Gen_SelectToPreview": "",
"Gen_Selected_Devices": "Ausgewählte Geräte:",
"Gen_Switch": "Umschalten",
"Gen_Upd": "Aktualisierung erfolgreich",
@@ -612,6 +615,7 @@
"RandomMAC_hover": "Autodetected - indicates if the device randomizes it's MAC address.",
"Reports_Sent_Log": "Protokoll gesendeter Berichte",
"SCAN_SUBNETS_description": "",
"SCAN_SUBNETS_name": "",
"SMTP_FORCE_SSL_description": "Force SSL when connecting to your SMTP server.",
"SMTP_FORCE_SSL_name": "Force SSL",
"SMTP_PASS_description": "The SMTP server password. ",
@@ -774,4 +778,4 @@
"settings_update_item_warning": "",
"test_event_icon": "fa-vial-circle-check",
"test_event_tooltip": "Save your changes at first before you test your settings."
}
}

View File

@@ -56,6 +56,8 @@
"BackDevices_Restore_okay": "Restore executed successfully.",
"BackDevices_darkmode_disabled": "Darkmode Disabled",
"BackDevices_darkmode_enabled": "Darkmode Enabled",
"CLEAR_NEW_FLAG_description": "If enabled (<code>0</code> is disabled), devices flagged as <b>New Device</b> will be unflagged if the time limit (specified in hours) exceeds their <b>First Session</b> time.",
"CLEAR_NEW_FLAG_name": "Clear new flag",
"DAYS_TO_KEEP_EVENTS_description": "This is a maintenance setting. This specifies the number of days worth of event entries that will be kept. All older events will be deleted periodically. Also applies on Plugin Events History.",
"DAYS_TO_KEEP_EVENTS_name": "Delete events older than",
"DevDetail_Copy_Device_Title": "<i class=\"fa fa-copy\"></i> Copy details from device",
@@ -293,6 +295,7 @@
"Gen_Save": "Save",
"Gen_Saved": "Saved",
"Gen_Search": "Search",
"Gen_SelectToPreview": "Select to preview",
"Gen_Selected_Devices": "Selected Devices:",
"Gen_Switch": "Switch",
"Gen_Upd": "Updated successfully",
@@ -303,8 +306,8 @@
"Gen_Work_In_Progress": "Work in progress, good time to feedback on https://github.com/jokob-sk/NetAlertX/issues",
"General_display_name": "General",
"General_icon": "<i class=\"fa fa-gears\"></i>",
"HRS_TO_KEEP_NEWDEV_description": "This is a maintenance setting. If enabled (<code>0</code> is disabled), devices marked as <b>New Device</b> will be deleted if their <b>First Session</b> time was older than the specified hours in this setting. Use this setting if you want to auto-delete <b>New Devices</b> after <code>X</code> hours.",
"HRS_TO_KEEP_NEWDEV_name": "Keep new devices for",
"HRS_TO_KEEP_NEWDEV_description": "This is a maintenance setting <b>DELETING devices</b>. If enabled (<code>0</code> is disabled), devices marked as <b>New Device</b> will be deleted if their <b>First Session</b> time was older than the specified hours in this setting. Use this setting if you want to auto-delete <b>New Devices</b> after <code>X</code> hours.",
"HRS_TO_KEEP_NEWDEV_name": "Delete new devices after",
"HelpFAQ_Cat_Detail": "Details",
"HelpFAQ_Cat_Detail_300_head": "What means ",
"HelpFAQ_Cat_Detail_300_text_a": "means a network device (a device of the type AP, Gateway, Firewall, Hypervisor, Powerline, Switch, WLAN, PLC, Router,USB LAN Adapter, USB WIFI Adapter, or Internet). Custom types can be added via the <code>NETWORK_DEVICE_TYPES</code> setting.",
@@ -557,6 +560,7 @@
"RandomMAC_hover": "Autodetected - indicates if the device randomizes it's MAC address.",
"Reports_Sent_Log": "Sent Reports Log",
"SCAN_SUBNETS_description": "Most on-network scanners (ARP-SCAN, NMAP, NSLOOKUP, DIG, PHOLUS) rely on scanning specific network interfaces and subnets. Check the <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md\" target=\"_blank\">subnets documentation</a> for help on this setting, especially VLANs, what VLANs are supported, or how to figure out the network mask and your interface. <br/> <br/> An alternative to on-network scanners is to enable some other Device scanners/importers that don't rely on NetAlert<sup>X</sup> having access to the network (UNIFI, dhcp.leases, PiHole, etc.). <br/> <br/> Note: The scan time itself depends on the number of IP addresses to check, so set this up carefully with the appropriate network mask and interface.",
"SCAN_SUBNETS_name": "Networks to scan",
"SYSTEM_TITLE": "System Information",
"Setting_Override": "Override value",
"Setting_Override_Description": "Enabling this option will override an App supplied default value with the value specified above.",

View File

@@ -66,6 +66,8 @@
"BackDevices_Restore_okay": "Restauración ejecutado con éxito.",
"BackDevices_darkmode_disabled": "Darkmode Desactivado",
"BackDevices_darkmode_enabled": "Darkmode Activado",
"CLEAR_NEW_FLAG_description": "Si está habilitado (<code>0</code> está desactivado), los dispositivos marcados como <b>Nuevo dispositivo</b> se desmarcarán si el límite de tiempo (especificado en horas) excede su tiempo de <b>primera sesión</b>.",
"CLEAR_NEW_FLAG_name": "Eliminar la nueva bandera",
"DAYS_TO_KEEP_EVENTS_description": "Esta es una configuración de mantenimiento. Esto especifica el número de días de entradas de eventos que se guardarán. Todos los eventos anteriores se eliminarán periódicamente.",
"DAYS_TO_KEEP_EVENTS_name": "Eliminar eventos anteriores a",
"DevDetail_Copy_Device_Title": "<i class=\"fa fa-copy\"></i> Copiar detalles del dispositivo",
@@ -284,7 +286,7 @@
"Gen_AreYouSure": "¿Estás seguro?",
"Gen_Backup": "Ejecutar copia de seguridad",
"Gen_Cancel": "Cancelar",
"Gen_Change": "",
"Gen_Change": "Cambiar",
"Gen_Copy": "Ejecutar",
"Gen_DataUpdatedUITakesTime": "Correcto - La interfaz puede tardar en actualizarse si se está ejecutando un escaneo.",
"Gen_Delete": "Eliminar",
@@ -303,6 +305,7 @@
"Gen_Save": "Guardar",
"Gen_Saved": "Guardado",
"Gen_Search": "Buscar",
"Gen_SelectToPreview": "Seleccionar para previsualizar",
"Gen_Selected_Devices": "Dispositivos seleccionados:",
"Gen_Switch": "Cambiar",
"Gen_Upd": "Actualizado correctamente",
@@ -313,8 +316,8 @@
"Gen_Work_In_Progress": "Trabajo en curso, un buen momento para hacer comentarios en https://github.com/jokob-sk/NetAlertX/issues",
"General_display_name": "General",
"General_icon": "<i class=\"fa fa-gears\"></i>",
"HRS_TO_KEEP_NEWDEV_description": "Esta es una configuración de mantenimiento. Si está habilitado (<code>0</code> está deshabilitado), los dispositivos marcados como <b>Nuevo dispositivo</b> se eliminarán si su <b>Primera sesión</b> el tiempo era anterior a las horas especificadas en esta configuración. Utilice esta configuración si desea eliminar automáticamente <b>Nuevos dispositivos</b> después de <code>X</code> horas.",
"HRS_TO_KEEP_NEWDEV_name": "Guardar nuevos dispositivos para",
"HRS_TO_KEEP_NEWDEV_description": "Se trata de una configuración de mantenimiento <b>BORRAR dispositivos</b>. Si está activado (<code>0</code> está desactivado), los dispositivos marcados como <b>Nuevo dispositivo</b> se eliminarán si su fecha de <b>primera sesión</b> es anterior a las horas especificadas en este ajuste. Use este ajuste si desea eliminar automáticamente <b>Nuevos dispositivos</b> después de <code>X</code> horas.",
"HRS_TO_KEEP_NEWDEV_name": "Eliminar nuevos dispositivos después",
"HelpFAQ_Cat_Detail": "Detalles",
"HelpFAQ_Cat_Detail_300_head": "¿Qué significa? ",
"HelpFAQ_Cat_Detail_300_text_a": "significa un dispositivo de red (un dispositivo del tipo AP, Gateway, Firewall, Hypervisor, Powerline, Switch, WLAN, PLC, Router,Adaptador LAN USB, Adaptador WIFI USB o Internet). Los tipos personalizados pueden añadirse mediante el ajuste <code>NETWORK_DEVICE_TYPES</code>.",
@@ -724,8 +727,8 @@
"UI_PRESENCE_name": "Mostrar en el gráfico de presencia",
"UI_REFRESH_description": "Ingrese el número de segundos después de los cuales se recarga la interfaz de usuario. Ajustado a <code> 0 </code> para desactivar.",
"UI_REFRESH_name": "Actualización automática de la interfaz de usuario",
"VERSION_description": "",
"VERSION_name": "",
"VERSION_description": "Valor de ayuda de versión o marca de tiempo para comprobar si la aplicación se ha actualizado.",
"VERSION_name": "Versión o marca de tiempo",
"WEBHOOK_PAYLOAD_description": "El formato de datos de carga de Webhook para el atributo <code>body</code> > <code>attachments</code> > <code>text</code> en el json de carga. Vea un ejemplo de la carga <a target=\"_blank\" href=\"https://github.com/jokob-sk/NetAlertX/blob/main/front/report_templates/webhook_json_sample.json\">aquí</a>. (por ejemplo: para discord use <code>text</code>)",
"WEBHOOK_PAYLOAD_name": "Tipo de carga",
"WEBHOOK_REQUEST_METHOD_description": "El método de solicitud HTTP que se utilizará para la llamada de webhook.",
@@ -773,4 +776,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."
}
}

View File

@@ -56,6 +56,8 @@
"BackDevices_Restore_okay": "Restauration exécutée avec succès.",
"BackDevices_darkmode_disabled": "Mode sombre désactivé",
"BackDevices_darkmode_enabled": "Mode sombre activé",
"CLEAR_NEW_FLAG_description": "",
"CLEAR_NEW_FLAG_name": "",
"DAYS_TO_KEEP_EVENTS_description": "Il s'agit d'un paramètre de maintenance. Il indique le nombre de jours pendant lesquels les entrées d'événements seront conservées. Tous les événements plus anciens seront supprimés périodiquement. S'applique également à l'historique des événements du plugin.",
"DAYS_TO_KEEP_EVENTS_name": "Supprimer les événements plus anciens que",
"DevDetail_Copy_Device_Title": "<i class=\"fa fa-copy\"></i> Copier les détails de l'appareil",
@@ -293,6 +295,7 @@
"Gen_Save": "Enregistrer",
"Gen_Saved": "Enregistré",
"Gen_Search": "Recherche",
"Gen_SelectToPreview": "",
"Gen_Selected_Devices": "Appareils sélectionnés:",
"Gen_Switch": "Basculer",
"Gen_Upd": "Mise à jour réussie",
@@ -557,6 +560,7 @@
"RandomMAC_hover": "Détecté automatiquement - indique si l'appareil dispose d'une adresse MAC générée aléatoirement.",
"Reports_Sent_Log": "Rapports de log transmis",
"SCAN_SUBNETS_description": "La plupart des scanners sur le réseau (scan ARP, NMAP, Nslookup, DIG, Pholud) se base sur le scan d'une partie spécifique des interfaces réseau ou de sous-réseau. Consulter la <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md\" target=\"_blank\">documentation des sous-réseaux</a> pour plus d'aide sur ce paramètre, notamment pour des VLAN, lesquels sont supportés ou sur comment identifier le masque réseau et votre interface réseau. <br/> <br/> Une alternative à ces scanner sur le réseau et d'activer d'autres scanners d'appareils ou des importe, qui ne dépendent pas du fait de laisser NetAlert<sup>X</sup> accéder au réseau (Unifié, baux DHCP, Pi-hole, etc.).<br/><br/> Remarque: la durée du scan en lui-même dépend du nombre d'adresses IP à scanner, renseignez donc soigneusement avec le bon masque réseau et la bonne interface réseau.",
"SCAN_SUBNETS_name": "",
"SYSTEM_TITLE": "Informations système",
"Setting_Override": "Remplacer la valeur",
"Setting_Override_Description": "Activer cette option va remplacer la valeur fournie par défaut par une application par la valeur renseignée au-dessus.",
@@ -693,4 +697,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."
}
}

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

@@ -56,6 +56,8 @@
"BackDevices_Restore_okay": "Ripristino eseguito correttamente.",
"BackDevices_darkmode_disabled": "Modalità scura disabilitata",
"BackDevices_darkmode_enabled": "Modalità scura abilitata",
"CLEAR_NEW_FLAG_description": "",
"CLEAR_NEW_FLAG_name": "",
"DAYS_TO_KEEP_EVENTS_description": "Questa è un'impostazione di manutenzione. Specifica il numero di giorni delle voci degli eventi che verranno conservati. Tutti gli eventi più vecchi verranno eliminati periodicamente. Si applica anche alla cronologia degli eventi del plugin (Plugin Events History).",
"DAYS_TO_KEEP_EVENTS_name": "Elimina eventi più vecchi di",
"DevDetail_Copy_Device_Title": "<i class=\"fa fa-copy\"></i> Copia dettagli dal dispositivo",
@@ -293,6 +295,7 @@
"Gen_Save": "Salva",
"Gen_Saved": "Salvato",
"Gen_Search": "Cerca",
"Gen_SelectToPreview": "",
"Gen_Selected_Devices": "Dispositivi selezionati:",
"Gen_Switch": "Cambia",
"Gen_Upd": "Aggiornato correttamente",
@@ -557,6 +560,7 @@
"RandomMAC_hover": "Rilevato automaticamente: indica se il dispositivo genera il suo indirizzo MAC casualmente.",
"Reports_Sent_Log": "Log rapporti inviati",
"SCAN_SUBNETS_description": "La maggior parte degli scanner di rete (ARP-SCAN, NMAP, NSLOOKUP, DIG, PHOLUS) si basano sulla scansione di interfacce di rete e sottoreti specifiche. Consulta la <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md\" target=\"_blank\">documentazione sulle sottoreti</a> per assistenza su questa impostazione, in particolare VLAN, quali VLAN sono supportate o come individuare la maschera di rete e l'interfaccia. <br/> <br/> Un'alternativa agli scanner in rete è abilitare altri scanner/importatori di dispositivi che non si affidano a NetAlert<sup>X</sup> che hanno accesso alla rete (UNIFI, dhcp.leases , PiHole, ecc.). <br/> <br/> Nota: il tempo di scansione stesso dipende dal numero di indirizzi IP da controllare, quindi impostalo attentamente con la maschera di rete e l'interfaccia appropriate.",
"SCAN_SUBNETS_name": "",
"SYSTEM_TITLE": "Informazioni sistema",
"Setting_Override": "Sovrascrivi valore",
"Setting_Override_Description": "L'abilitazione di questa opzione sovrascriverà il valore predefinito fornito dall'app con il valore specificato sopra.",

View File

@@ -56,6 +56,8 @@
"BackDevices_Restore_okay": "Gjenoppretting utført.",
"BackDevices_darkmode_disabled": "Mørk modus Deaktivert",
"BackDevices_darkmode_enabled": "Mørk modus Aktivert",
"CLEAR_NEW_FLAG_description": "",
"CLEAR_NEW_FLAG_name": "",
"DAYS_TO_KEEP_EVENTS_description": "Dette er en vedlikeholdsinnstilling. Dette spesifiserer antall dager verdt med hendelsesoppføringer som vil beholdes. Alle eldre hendelser vil bli slettet med jevne mellomrom. Gjelder også for plugin-hendelseshistorikk.",
"DAYS_TO_KEEP_EVENTS_name": "Slett hendelser eldre enn",
"DevDetail_Copy_Device_Title": "<i class=\"fa fa-copy\"></i> Kopier detaljer fra enhet",
@@ -293,6 +295,7 @@
"Gen_Save": "Lagre",
"Gen_Saved": "Lagret",
"Gen_Search": "Søk",
"Gen_SelectToPreview": "",
"Gen_Selected_Devices": "Valgte Enheter:",
"Gen_Switch": "Bytt",
"Gen_Upd": "Oppdatering vellykket",
@@ -557,6 +560,7 @@
"RandomMAC_hover": "Autodetektert - indikerer om enheten randomiserer MAC-adressen sin.",
"Reports_Sent_Log": "Sendte rapport logger",
"SCAN_SUBNETS_description": "De fleste skannere på nettet (ARP-Scan, NMAP, NSlookup, Dig, Pholus) er avhengige av å skanne spesifikke nettverksgrensesnitt og undernett. Sjekk <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md\" target=\"_blank\">subnett dokumentasjonen</a> for hjelp på denne innstillingen, spesielt VLAN-er, hvilke VLAN-er som støttes, eller hvordan du kan finne ut nettverksmasken og grensesnittet ditt. <br/> <br/> Et alternativ til skannere på nettet er å aktivere noen andre enhetsskannere/importører som ikke er avhengige av Netalert<sup>X</sup> med tilgang til nettverket (UniFi, DHCP-Leaser, Pihole, osv.). <br/> <br/> Merk: Selve skanningstiden avhenger av antall IP -adresser som skal sjekkes, så sett dette opp nøye med riktig nettverksmaske og grensesnitt.",
"SCAN_SUBNETS_name": "",
"SYSTEM_TITLE": "Systeminformasjon",
"Setting_Override": "Overstyr verdi",
"Setting_Override_Description": "Aktivering av dette alternativet vil overstyre en App som leveres standard-verdi med verdien som er spesifisert ovenfor.",
@@ -693,4 +697,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."
}
}

View File

@@ -56,6 +56,8 @@
"BackDevices_Restore_okay": "Przywracanie wykonane z sukcesem.",
"BackDevices_darkmode_disabled": "Tryb ciemny Wyłączony",
"BackDevices_darkmode_enabled": "Tryb ciemny Włączony",
"CLEAR_NEW_FLAG_description": "",
"CLEAR_NEW_FLAG_name": "",
"DAYS_TO_KEEP_EVENTS_description": "To jest ustawienie konserwacji. Określa ile dni mają być utrzymywane wpisy wydarzeń. Wszystkie starsze wpisy wydarzeń zostaną usunięte okresowo. Dotyczy także Historii Wydarzeń Pluginów.",
"DAYS_TO_KEEP_EVENTS_name": "Usuń wydarzenia starsze niż",
"DevDetail_Copy_Device_Title": "<i class=\"fa fa-copy\"></i>Kopiuj opis z urządzenia",
@@ -293,6 +295,7 @@
"Gen_Save": "Zapisz",
"Gen_Saved": "Zapisano",
"Gen_Search": "Szukaj",
"Gen_SelectToPreview": "",
"Gen_Selected_Devices": "Wybierz Urządzenia:",
"Gen_Switch": "Switch",
"Gen_Upd": "Zaktualizowane poprawnie",
@@ -557,6 +560,7 @@
"RandomMAC_hover": "Auto wykrywanie - oznacza czy urządzenie randomizuje swój adres MAC.",
"Reports_Sent_Log": "Wyślij zgłoszenie logów",
"SCAN_SUBNETS_description": "Większość skanerów sieciowych (ARP-SCAN, NMAP, NSLOOKUP, DIG, PHOLUS) opiera się na konkretnych interfejsach sieciowych oraz podsieci. Sprawdź <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md\" target=\"_blank\"> dokumentacji podsieci</a> jeżeli potrzebujesz pomocy w ustawieniach, a szczególnie z VLAN'ami, jakie VLAN'y są wspierane oraz jak rozgryźć maskę podsieci twojego interfejsu.<br/><br/> Alternatywą do skanerów sieciowych jest uruchomienie innego Skanera Urządzeń/Importera który nie polega by NetAlert<sup>X</sup> miał dostęp do sieci (UNIFI, dhcp.leases, PiHole, itp.).<br/><br/> Notatka: Czas skanu zależy od liczby adresów IP do sprawdzenia, więc ustaw go tak by skanował odpowiedni interfejs i maskę sieciową.",
"SCAN_SUBNETS_name": "",
"SYSTEM_TITLE": "Informacje o Systemie",
"Setting_Override": "Nadpisz wartość",
"Setting_Override_Description": "Włączanie tej opcji nadpisze podstawową wartość na wartość podaną powyżej.",
@@ -693,4 +697,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."
}
}

View File

@@ -56,6 +56,8 @@
"BackDevices_Restore_okay": "Restauração executada com sucesso.",
"BackDevices_darkmode_disabled": "Modo Noturno Desabilitado",
"BackDevices_darkmode_enabled": "Modo Noturno Habilitado",
"CLEAR_NEW_FLAG_description": "",
"CLEAR_NEW_FLAG_name": "",
"DAYS_TO_KEEP_EVENTS_description": "Esta é uma definição de manutenção. Especifica o número de dias de entradas de eventos que serão mantidas. Todos os eventos mais antigos serão eliminados periodicamente. Também se aplica ao Histórico de eventos do plug-in.",
"DAYS_TO_KEEP_EVENTS_name": "Excluir eventos mais antigos que",
"DevDetail_Copy_Device_Title": "<i class=\"fa fa-copy\"></i> Copiar detalhes do dispositivo",
@@ -293,6 +295,7 @@
"Gen_Save": "Salvar",
"Gen_Saved": "Salvo",
"Gen_Search": "Procurar",
"Gen_SelectToPreview": "",
"Gen_Selected_Devices": "Dispositivos selecionados:",
"Gen_Switch": "Trocar",
"Gen_Upd": "Atualizado com sucesso",
@@ -557,6 +560,7 @@
"RandomMAC_hover": "",
"Reports_Sent_Log": "",
"SCAN_SUBNETS_description": "",
"SCAN_SUBNETS_name": "",
"SYSTEM_TITLE": "",
"Setting_Override": "",
"Setting_Override_Description": "",
@@ -693,4 +697,4 @@
"settings_update_item_warning": "",
"test_event_icon": "",
"test_event_tooltip": ""
}
}

4
front/php/templates/language/ru_ru.json Normal file → Executable file
View File

@@ -56,6 +56,8 @@
"BackDevices_Restore_okay": "Восстановление выполнено успешно.",
"BackDevices_darkmode_disabled": "Темный режим отключен",
"BackDevices_darkmode_enabled": "Темный режим включен",
"CLEAR_NEW_FLAG_description": "",
"CLEAR_NEW_FLAG_name": "",
"DAYS_TO_KEEP_EVENTS_description": "Это настройка обслуживания. Здесь указывается количество дней, в течение которых будут храниться записи о событиях. Все старые события будут периодически удаляться. Также применимо к истории событий плагина.",
"DAYS_TO_KEEP_EVENTS_name": "Удалить события старше",
"DevDetail_Copy_Device_Title": "<i class=\"fa fa-copy\"></i> Скопировать данные с устройства",
@@ -293,6 +295,7 @@
"Gen_Save": "Сохранить",
"Gen_Saved": "Сохранено",
"Gen_Search": "Поиск",
"Gen_SelectToPreview": "",
"Gen_Selected_Devices": "Выбранные устройства:",
"Gen_Switch": "Переключить",
"Gen_Upd": "Успешное обновление",
@@ -557,6 +560,7 @@
"RandomMAC_hover": "Автоматически обнаружено — указывает, рандомизирует ли устройство свой MAC-адрес.",
"Reports_Sent_Log": "Отправить журнал логов",
"SCAN_SUBNETS_description": "Большинство сетевых сканеров (ARP-SCAN, NMAP, NSLOOKUP, DIG, PHOLUS) полагаются на сканирование определенных сетевых интерфейсов и подсетей. Дополнительную информацию по этому параметру можно найти в <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md\" target=\"_blank\">документации по подсетям</a>, особенно VLAN, какие VLAN поддерживаются или как разобраться в маске сети и своем интерфейсе. <br/> <br/> Альтернативой сетевым сканерам является включение некоторых других сканеров/импортеров устройств, которые не полагаются на NetAlert<sup>X</sup>, имеющий доступ к сети (UNIFI, dhcp.leases , PiHole и др.). <br/> <br/> Примечание. Само время сканирования зависит от количества проверяемых IP-адресов, поэтому тщательно настройте его, указав соответствующую маску сети и интерфейс.",
"SCAN_SUBNETS_name": "",
"SYSTEM_TITLE": "Системная информация",
"Setting_Override": "Переопределить значение",
"Setting_Override_Description": "Включение этой опции приведет к переопределению значения по умолчанию, предоставленного приложением, на значение, указанное выше.",

View File

@@ -56,6 +56,8 @@
"BackDevices_Restore_okay": "",
"BackDevices_darkmode_disabled": "",
"BackDevices_darkmode_enabled": "",
"CLEAR_NEW_FLAG_description": "",
"CLEAR_NEW_FLAG_name": "",
"DAYS_TO_KEEP_EVENTS_description": "",
"DAYS_TO_KEEP_EVENTS_name": "",
"DevDetail_Copy_Device_Title": "",
@@ -293,6 +295,7 @@
"Gen_Save": "Kaydet",
"Gen_Saved": "Kaydedildi",
"Gen_Search": "",
"Gen_SelectToPreview": "",
"Gen_Selected_Devices": "Seçilmiş Cihazlar:",
"Gen_Switch": "",
"Gen_Upd": "Başarılı bir şekilde güncellendi",
@@ -557,6 +560,7 @@
"RandomMAC_hover": "",
"Reports_Sent_Log": "",
"SCAN_SUBNETS_description": "",
"SCAN_SUBNETS_name": "",
"SYSTEM_TITLE": "",
"Setting_Override": "",
"Setting_Override_Description": "",

View File

@@ -56,6 +56,8 @@
"BackDevices_Restore_okay": "已成功恢复。",
"BackDevices_darkmode_disabled": "暗黑模式已禁用",
"BackDevices_darkmode_enabled": "已启用暗黑模式",
"CLEAR_NEW_FLAG_description": "",
"CLEAR_NEW_FLAG_name": "",
"DAYS_TO_KEEP_EVENTS_description": "这是维护设置。它指定将保留的事件条目的天数。所有较旧的事件将被定期删除。也适用于插件事件历史记录。",
"DAYS_TO_KEEP_EVENTS_name": "删除早于",
"DevDetail_Copy_Device_Title": "<i class=\"fa fa-copy\"></i> 从设备复制详细信息",
@@ -293,6 +295,7 @@
"Gen_Save": "保存",
"Gen_Saved": "已保存",
"Gen_Search": "搜索",
"Gen_SelectToPreview": "",
"Gen_Selected_Devices": "选定的设备:",
"Gen_Switch": "交换",
"Gen_Upd": "已成功更新",
@@ -557,6 +560,7 @@
"RandomMAC_hover": "自动检测 - 表示设备是否随机化其 MAC 地址。",
"Reports_Sent_Log": "已发送报告日志",
"SCAN_SUBNETS_description": "大多数网络扫描器ARP-SCAN、NMAP、NSLOOKUP、DIG、PHOLUS依赖于扫描特定的网络接口和子网。查看<a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md\" target=\"_blank\">子网文档</a>以获取有关此设置的帮助,尤其是 VLAN、支持哪些 VLAN或者如何确定网络掩码和接口。<br/> <br/> 网络扫描器的替代方法是启用一些其他不依赖于 NetAlert<sup>X</sup> 访问网络的设备扫描器/导入器UNIFI、dhcp.leases、PiHole 等)。<br/> <br/> 注意:扫描时间本身取决于要检查的 IP 地址数量,因此请使用适当的网络掩码和接口仔细设置。",
"SCAN_SUBNETS_name": "",
"SYSTEM_TITLE": "系统信息",
"Setting_Override": "覆盖值",
"Setting_Override_Description": "启用此选项将用上面指定的值覆盖应用程序提供的默认值。",
@@ -693,4 +697,4 @@
"settings_update_item_warning": "更新下面的值。请注意遵循先前的格式。<b>未执行验证。</b>",
"test_event_icon": "",
"test_event_tooltip": "在测试设置之前,请先保存更改。"
}
}

View File

@@ -44,12 +44,13 @@ def main():
HRS_TO_KEEP_NEWDEV = int(values.hourstokeepnewdevice.split('=')[1])
DAYS_TO_KEEP_EVENTS = int(values.daystokeepevents.split('=')[1])
PHOLUS_DAYS_DATA = get_setting_value("PHOLUS_DAYS_DATA")
CLEAR_NEW_FLAG = get_setting_value("CLEAR_NEW_FLAG")
mylog('verbose', [f'[{pluginName}] In script'])
# Execute cleanup/upkeep
cleanup_database(fullDbPath, DAYS_TO_KEEP_EVENTS, PHOLUS_DAYS_DATA, HRS_TO_KEEP_NEWDEV, PLUGINS_KEEP_HIST)
cleanup_database(fullDbPath, DAYS_TO_KEEP_EVENTS, PHOLUS_DAYS_DATA, HRS_TO_KEEP_NEWDEV, PLUGINS_KEEP_HIST, CLEAR_NEW_FLAG)
mylog('verbose', [f'[{pluginName}] Cleanup complete'])
@@ -58,7 +59,7 @@ def main():
#===============================================================================
# Cleanup / upkeep database
#===============================================================================
def cleanup_database (dbPath, DAYS_TO_KEEP_EVENTS, PHOLUS_DAYS_DATA, HRS_TO_KEEP_NEWDEV, PLUGINS_KEEP_HIST):
def cleanup_database (dbPath, DAYS_TO_KEEP_EVENTS, PHOLUS_DAYS_DATA, HRS_TO_KEEP_NEWDEV, PLUGINS_KEEP_HIST, CLEAR_NEW_FLAG):
"""
Cleaning out old records from the tables that don't need to keep all data.
"""
@@ -151,6 +152,17 @@ def cleanup_database (dbPath, DAYS_TO_KEEP_EVENTS, PHOLUS_DAYS_DATA, HRS_TO_KEEP
mylog('verbose', [f'[{pluginName}] Query: {query} '])
cursor.execute (query)
# -----------------------------------------------------
# Clear New Flag
if CLEAR_NEW_FLAG != 0:
mylog('verbose', [f'[{pluginName}] Devices: Clear "New Device" flag for all devices older than {str(CLEAR_NEW_FLAG)} hours (CLEAR_NEW_FLAG setting)'])
query = f"""UPDATE Devices SET dev_NewDevice = 0 WHERE dev_NewDevice = 1 AND date(dev_FirstConnection, '+{str(CLEAR_NEW_FLAG)} hour') < date('now')"""
# select * from Devices where dev_NewDevice = 1 AND date(dev_FirstConnection, '+3 hour' ) < date('now')
mylog('verbose', [f'[{pluginName}] Query: {query} '])
cursor.execute(query)
# -----------------------------------------------------
# Cleanup Pholus_Scan
if PHOLUS_DAYS_DATA != "" and PHOLUS_DAYS_DATA != 0:

View File

@@ -1,7 +1,7 @@
{
"code_name": "mikrotik_scan",
"unique_prefix": "MTSCAN",
"plugin_type": "other",
"plugin_type": "device_scanner",
"execution_order" : "Layer_4",
"enabled": true,
"data_source": "script",
@@ -11,7 +11,7 @@
"display_name": [
{
"language_code": "en_us",
"string": "Mikrotik (Name discovery)"
"string": "Mikrotik (Device discovery)"
}
],
"icon": [
@@ -23,7 +23,7 @@
"description": [
{
"language_code": "en_us",
"string": "A plugin to discover device names."
"string": "A plugin to discover devices via Mikrotik."
}
],
"params": [

View File

@@ -1,6 +1,4 @@
#!/usr/bin/env python
# test script by running:
# tbc
import os
import pathlib
@@ -40,42 +38,35 @@ pluginName = 'MTSCAN'
def main():
mylog('verbose', [f'[{pluginName}] In script'])
mylog('verbose', [f'[{pluginName}] In script'])
mt_host = get_setting_value('MTSCAN_MT_HOST')
mt_port = get_setting_value('MTSCAN_MT_PORT')
mt_user = get_setting_value('MTSCAN_MT_USER')
mt_password = get_setting_value('MTSCAN_MT_PASS')
#mylog('verbose', [f'[{pluginName}] Router: {mt_host}:{mt_port} user: {mt_user}, pass: {mt_password}'])
# Create a database connection
db = DB() # instance of class DB
db.open()
# init global variables
global MT_HOST, MT_PORT, MT_USER, MT_PASS
# Initialize the Plugin obj output file
plugin_objects = Plugin_Objects(RESULT_FILE)
# Create a Device_obj instance
device_handler = Device_obj(db)
# Mikrotik settings
MT_HOST = get_setting_value('MTSCAN_MT_HOST')
MT_PORT = get_setting_value('MTSCAN_MT_PORT')
MT_USER = get_setting_value('MTSCAN_MT_USER')
MT_PASS = get_setting_value('MTSCAN_MT_PASS')
# Retrieve devices
#unknown_devices = device_handler.getUnknown()
#mylog('verbose', [f'[{pluginName}] Unknown devices count: {len(unknown_devices)}'])
plugin_objects = get_entries(plugin_objects)
all_devices = device_handler.getAll()
plugin_objects.write_result_file()
mylog('verbose', [f'[{pluginName}] Scan finished, found {len(plugin_objects)} devices'])
mylog('verbose', [f'[{pluginName}] all devices count: {len(all_devices)}'])
device_map = {d['dev_MAC']:d['dev_LastIP'] for d in all_devices}
def get_entries(plugin_objects: Plugin_Objects) -> Plugin_Objects:
try:
# connect router
api = connect(username=mt_user, password=mt_password, host=mt_host, port=mt_port)
api = connect(username=MT_USER, password=MT_PASS, host=MT_HOST, port=MT_PORT)
# get dhcp leases
leases = api('/ip/dhcp-server/lease/print')
for lease in leases:
lease_id = lease.get('.id')
@@ -84,56 +75,31 @@ def main():
host_name = lease.get('host-name')
comment = lease.get('comment')
last_seen = lease.get('last-seen')
status = lease.get('status')
mylog('verbose', [f"ID: {lease_id}, Address: {address}, MAC Address: {mac_address}, Host Name: {host_name}, Comment: {comment}, Last Seen: {last_seen}"])
if mac_address in device_map.keys():
device_name = host_name
if comment != '':
device_name = comment
mylog('verbose', [f"ID: {lease_id}, Address: {address}, MAC Address: {mac_address}, Host Name: {host_name}, Comment: {comment}, Last Seen: {last_seen}, Status: {status}"])
if (status == "bound"):
plugin_objects.add_object(
# "Name-MAC", "LastIP", "IP", "Name","Host","LastSeen","Comment"
primaryId = mac_address,
secondaryId = device_map[mac_address],
secondaryId = '',
watched1 = address,
watched2 = device_name,
watched3 = host_name,
watched4 = last_seen,
watched2 = host_name,
watched3 = last_seen,
watched4 = '',
extra = '',
helpVal1 = comment,
foreignKey = mac_address)
plugin_objects.write_result_file()
except TrapError as e:
mylog('error', [f"An error occurred: {e}"])
except Exception as e:
mylog('error', [f"Failed to connect to MikroTik API: {e}"])
#for device in unknown_devices:
# domain_name, dns_server = execute_nslookup(device['dev_LastIP'], timeout)
# if domain_name != '':
# plugin_objects.add_object(
# # "MAC", "IP", "Server", "Name"
# primaryId = device['dev_MAC'],
# secondaryId = device['dev_LastIP'],
# watched1 = dns_server,
# watched2 = domain_name,
# watched3 = '',
# watched4 = '',
# extra = '',
# foreignKey = device['dev_MAC'])
#plugin_objects.write_result_file()
mylog('verbose', [f'[{pluginName}] Script finished'])
return 0
return plugin_objects
#===============================================================================
# BEGIN

View File

@@ -198,7 +198,7 @@
"elementOptions": [
{ "sourceSuffixes": ["_in"] },
{ "separator": "" },
{ "cssClasses": "col-sm-2" },
{ "cssClasses": "col-xs-12" },
{ "onClick": "addList(this, false)" },
{ "getStringKey": "Gen_Add" }
],
@@ -219,7 +219,7 @@
"elementOptions": [
{ "sourceSuffixes": [] },
{ "separator": "" },
{ "cssClasses": "col-sm-3" },
{ "cssClasses": "col-xs-6" },
{ "onClick": "removeFromList(this)" },
{ "getStringKey": "Gen_Remove_Last" }
],
@@ -230,7 +230,7 @@
"elementOptions": [
{ "sourceSuffixes": [] },
{ "separator": "" },
{ "cssClasses": "col-sm-3" },
{ "cssClasses": "col-xs-6" },
{ "onClick": "removeAllOptions(this)" },
{ "getStringKey": "Gen_Remove_All" }
],
@@ -261,7 +261,7 @@
"description": [
{
"language_code": "en_us",
"string": "All the newly discovered device names are clened up by applying the following REGEX expression in this order. All the below are replaced by a blank string."
"string": "All the newly discovered device names are cleaned up by applying the following REGEX expression in this order. All the below are replaced by a blank string."
}
]
},
@@ -977,7 +977,27 @@
"type": {
"dataType": "string",
"elements": [
{ "elementType": "select", "elementOptions": [], "transformers": [] }
{
"elementType": "span",
"elementOptions": [
{ "cssClasses": "input-group-addon iconPreview" },
{ "getStringKey": "Gen_SelectToPreview" },
{ "customId": "NEWDEV_dev_Icon_preview" }
],
"transformers": []
},
{
"elementType": "select",
"elementHasInputValue": 1,
"elementOptions": [
{ "cssClasses": "col-xs-12" },
{
"onChange": "updateIconPreview(this)"
},
{ "customParams": "NEWDEV_dev_Icon,NEWDEV_dev_Icon_preview" }
],
"transformers": []
}
]
},
"default_value": "",

View File

@@ -1057,7 +1057,7 @@ def main():
elif values.rdns_scanning:
file_print_pr("[DEBUG] Timestamp 45: ", timeNow())
dns_query=None
ipn = ipaddress.ip_network(values.rdns_scanning)
ipn = ipaddress.ip_network(values.rdns_scanning, strict=False)
for ip in ipn.hosts():
the_query = ip.reverse_pointer
if not dns_query:

View File

@@ -1,41 +1,63 @@
## Overview
Synchronization plugin to synchronize multiple app instances. The Plugin can sychronize 2 types of data:
The synchronization plugin is designed to synchronize data across multiple instances of the app. It supports the following data synchronization modes:
1. 💻 Devices: The plugin sends an encrypted `table_devices.json` file to synchronize the whole Devices DB table.
1. 🔌 Plugin data: The plugin sends encrypted `last_result.log` files for individual plugins.
1. **💻 Devices**: Sends an encrypted `table_devices.json` file to synchronize the entire Devices database table.
2. **🔌 Plugin Data**: Sends encrypted `last_result.log` files for individual plugins.
> [!TIP]
> `[n]` indicates a setting that is usually specified for the node instance. `[n,h]` indicates a setting used both, on the node and on the hub instance.
> **Note:** `[n]` indicates a setting specified for the node instance, and `[n,h]` indicates a setting used on both the node and the hub instances.
### Synchronizing 💻 Devices data or 🔌 Plugins data
### Synchronization Modes
Most of the setups will probably only use 💻 Devices synchronization. 🔌 Plugins data will be probably used in only special use cases.
The plugin operates in three different modes based on the configuration settings:
#### [n] Node (Source) Settings
1. **Mode 1: PUSH (NODE)** - Sends data from the node to the hub.
- This mode is activated if `SYNC_hub_url` is set and either `SYNC_devices` or `SYNC_plugins` is enabled.
- **Actions**:
- Sends `table_devices.json` to the hub if `SYNC_devices` is enabled.
- Sends individual plugin `last_result.log` files to the hub if `SYNC_plugins` is enabled.
- When to run [n,h] `SYNC_RUN`
- Schedule [n,h] `SYNC_RUN_SCHD`
- API token [n,h] `SYNC_api_token`
- Encryption Key [n,h] `SYNC_encryption_key`
- Node name [n] `SYNC_node_name`
- Hub URL [n] `SYNC_hub_url`
- Sync Devices [n] `SYNC_devices` or Sync Plugins [n] `SYNC_plugins` (or both)
2. **Mode 2: PULL (HUB)** - Retrieves data from nodes to the hub.
- This mode is activated if `SYNC_nodes` is set.
- **Actions**:
- Retrieves data from configured nodes using the API and saves it locally for further processing.
#### [h] Hub (Target) Settings
3. **Mode 3: RECEIVE (HUB)** - Processes received data on the hub.
- Activated when data is received in Mode 2 and is ready to be processed.
- **Actions**:
- Decodes received data files, processes them, and updates the Devices table accordingly.
- When to run [n,h] `SYNC_RUN`
- Schedule [n,h] `SYNC_RUN_SCHD`
- API token [n,h] `SYNC_api_token`
- Encryption Key [n,h] `SYNC_encryption_key`
### Settings
#### Node (Source) Settings `[n]`
- **When to Run** `[n,h]`: `SYNC_RUN`
- **Schedule** `[n,h]`: `SYNC_RUN_SCHD`
- **API Token** `[n,h]`: `SYNC_api_token`
- **Encryption Key** `[n,h]`: `SYNC_encryption_key`
- **Node Name** `[n]`: `SYNC_node_name`
- **Hub URL** `[n]`: `SYNC_hub_url`
- **Sync Devices** `[n]`: `SYNC_devices`
- **Sync Plugins** `[n]`: `SYNC_plugins`
#### Hub (Target) Settings `[h]`
- **When to Run** `[n,h]`: `SYNC_RUN`
- **Schedule** `[n,h]`: `SYNC_RUN_SCHD`
- **API Token** `[n,h]`: `SYNC_api_token`
- **Encryption Key** `[n,h]`: `SYNC_encryption_key`
- **Nodes to Pull From** `[h]`: `SYNC_nodes`
### Usage
- Head to **Settings** > **Sync Hub** to adjust the default values.
1. **Adjust Settings**:
- Navigate to **Settings** > **Sync Hub** to modify default settings.
2. **Data Flow**:
- Nodes send or receive data based on the specified modes, either pushing data to the hub or pulling from nodes.
### Notes
- If a MAC address already exists on the hub, the device will be skipped in the data coming from this SYNC plugin.
- Existing devices on the hub will not be updated by the data received from this SYNC plugin if their MAC addresses are already present.
- It is recommended to use Device synchronization primarily. Plugin data synchronization is more suitable for specific use cases.
![Syn Hub Setup Diagram](/front/plugins/sync/sync_hub.png)
![Sync Hub Setup Diagram](/front/plugins/sync/sync_hub.png)

View File

@@ -159,6 +159,81 @@
"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."
}
]
},{
"function": "nodes",
"type": {
"dataType": "array",
"elements": [
{
"elementType": "input",
"elementOptions": [
{ "placeholder": "Enter full url" },
{ "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": [],
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Nodes [h]"
}
],
"description": [
{
"language_code": "en_us",
"string": "If specified, the hub will pull Devices data from the listed nodes."
}
]
},
{
"function": "hub_url",

View File

@@ -3,8 +3,11 @@
// External files
require '/app/front/php/server/init.php';
$method = $_SERVER['REQUEST_METHOD'];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// ----------------------------------------------
// Method to check authorization
function checkAuthorization($method) {
// Retrieve the authorization header
$headers = apache_request_headers();
$auth_header = $headers['Authorization'] ?? '';
@@ -14,16 +17,56 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if ($auth_header !== $expected_token) {
http_response_code(403);
echo 'Forbidden';
write_notification("[Plugin: SYNC] Incoming data: Incorrect API Token", "alert");
write_notification("[Plugin: SYNC] Incoming data: Incorrect API Token (".$method.")", "alert");
exit;
}
}
// ----------------------------------------------
// Function to return JSON response
function jsonResponse($status, $data = '', $message = '') {
http_response_code($status);
header('Content-Type: application/json');
echo json_encode([
'node_name' => getSettingValue('SYNC_node_name'),
'status' => $status,
'message' => $message,
'data_base64' => $data,
'timestamp' => date('Y-m-d H:i:s')
]);
}
// ----------------------------------------------
// MAIN
// ----------------------------------------------
// requesting data (this is a NODE)
if ($method === 'GET') {
checkAuthorization($method);
$file_path = "/app/front/api/table_devices.json";
$data = file_get_contents($file_path);
// Prepare the data to return as a JSON response
$response_data = base64_encode($data);
// Return JSON response
jsonResponse(200, $response_data, 'OK');
write_notification("[Plugin: SYNC] Data sent", "info");
}
// receiving data (this is a HUB)
else if ($method === 'POST') {
checkAuthorization($method);
// Retrieve and decode the data from the POST request
$data = $_POST['data'] ?? '';
$plugin_folder = $_POST['plugin_folder'] ?? '';
$node_name = $_POST['node_name'] ?? '';
$storage_path = "/app/front/plugins/{$plugin_folder}";
// Create the storage directory if it doesn't exist
@@ -43,12 +86,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$file_path = "{$storage_path}/last_result.encoded.{$node_name}.{$file_count}.log";
// Save the decoded data to the file
file_put_contents($file_path, $data);
http_response_code(200);
echo 'Data received and stored successfully';
write_notification("[Plugin: SYNC] Data received ({$plugin_folder})", "info");
} else {
http_response_code(405);
echo 'Method Not Allowed';

View File

@@ -7,6 +7,7 @@ import hashlib
import requests
import json
import sqlite3
import base64
# Define the installation path and extend the system path for plugin imports
@@ -46,24 +47,66 @@ def main():
hub_url = get_setting_value('SYNC_hub_url')
node_name = get_setting_value('SYNC_node_name')
send_devices = get_setting_value('SYNC_devices')
pull_nodes = get_setting_value('SYNC_nodes')
# variables to determine operation mode
is_hub = False
is_node = False
# Check if api_token set
if not api_token:
mylog('verbose', [f'[{pluginName}] ⚠ ERROR api_token not defined - quitting.'])
return -1
# Get all plugin configurations
all_plugins = get_plugins_configs()
# check if this is a hub or a node
if len(hub_url) > 0 and (send_devices or plugins_to_sync):
is_node = True
mylog('verbose', [f'[{pluginName}] Mode 1: PUSH (NODE) - This is a NODE as SYNC_hub_url, SYNC_devices or SYNC_plugins are set'])
if len(pull_nodes) > 0:
is_hub = True
mylog('verbose', [f'[{pluginName}] Mode 2: PULL (HUB) - This is a HUB as SYNC_nodes is set'])
mylog('verbose', [f'[{pluginName}] plugins_to_sync {plugins_to_sync}'])
# Mode 1: PUSH/SEND (NODE)
if is_node:
# PUSHING/SENDING Plugins
# Get all plugin configurations
all_plugins = get_plugins_configs()
# Plugins processing
index = 0
for plugin in all_plugins:
pref = plugin["unique_prefix"]
mylog('verbose', [f'[{pluginName}] plugins_to_sync {plugins_to_sync}'])
for plugin in all_plugins:
pref = plugin["unique_prefix"]
if pref in plugins_to_sync:
index += 1
mylog('verbose', [f'[{pluginName}] synching "{pref}" ({index}/{len(plugins_to_sync)})'])
index = 0
if pref in plugins_to_sync:
index += 1
mylog('verbose', [f'[{pluginName}] synching "{pref}" ({index}/{len(plugins_to_sync)})'])
# Construct the file path for the plugin's last_result.log file
plugin_folder = plugin["code_name"]
file_path = f"{INSTALL_PATH}/front/plugins/{plugin_folder}/last_result.log"
# Construct the file path for the plugin's last_result.log file
plugin_folder = plugin["code_name"]
file_path = f"{INSTALL_PATH}/front/plugins/{plugin_folder}/last_result.log"
if os.path.exists(file_path):
# Read the content of the log file
with open(file_path, 'r') as f:
file_content = f.read()
mylog('verbose', [f'[{pluginName}] Sending file_content: "{file_content}"'])
# encrypt and send data to the hub
send_data(api_token, file_content, encryption_key, plugin_folder, node_name, pref, hub_url)
else:
mylog('verbose', [f'[{pluginName}] {plugin_folder}/last_result.log not found'])
# PUSHING/SENDING devices
if send_devices:
file_path = f"{INSTALL_PATH}/front/api/table_devices.json"
plugin_folder = 'sync'
pref = 'SYNC'
if os.path.exists(file_path):
# Read the content of the log file
@@ -71,131 +114,147 @@ def main():
file_content = f.read()
mylog('verbose', [f'[{pluginName}] Sending file_content: "{file_content}"'])
# encrypt and send data to the hub
send_data(api_token, file_content, encryption_key, plugin_folder, node_name, pref, hub_url)
else:
mylog('verbose', [f'[{pluginName}] SYNC_hub_url not defined, skipping posting "Devices" data'])
else:
mylog('verbose', [f'[{pluginName}] SYNC_hub_url not defined, skipping posting "Plugins" and "Devices" data'])
else:
mylog('verbose', [f'[{pluginName}] {plugin_folder}/last_result.log not found'])
# Devices procesing
if send_devices:
file_path = f"{INSTALL_PATH}/front/api/table_devices.json"
plugin_folder = 'sync'
pref = 'SYNC'
if os.path.exists(file_path):
# Read the content of the log file
with open(file_path, 'r') as f:
file_content = f.read()
mylog('verbose', [f'[{pluginName}] Sending file_content: "{file_content}"'])
send_data(api_token, file_content, encryption_key, plugin_folder, node_name, pref, hub_url)
# process any received data for the Device DB table
# Create the file path
# Mode 2: PULL/GET (HUB)
# PULLING DEVICES
file_dir = os.path.join(pluginsPath, 'sync')
file_prefix = 'last_result'
# pull data from nodes if specified
if is_hub:
for node_url in pull_nodes:
response_json = get_data(api_token, node_url)
# Extract node_name and base64 data
node_name = response_json.get('node_name', 'unknown_node')
data_base64 = response_json.get('data_base64', '')
# Decode base64 data
decoded_data = base64.b64decode(data_base64)
# Create log file name using node name
log_file_name = f'{file_prefix}.{node_name}.log'
# Write decoded data to log file
with open(os.path.join(file_dir, log_file_name), 'wb') as log_file:
log_file.write(decoded_data)
message = f'[{pluginName}] Device data from node "{node_name}" written to {log_file_name}'
mylog('verbose', [message])
write_notification(message, 'info', timeNowTZ())
# Process any received data for the Device DB table
# Create the file path
# Decode files, rename them, and get the list of files
files_to_process = decode_and_rename_files(file_dir, file_prefix)
# Connect to the App database
conn = sqlite3.connect(fullDbPath)
cursor = conn.cursor()
# Collect all unique dev_MAC values from the JSON files
unique_mac_addresses = set()
device_data = []
mylog('verbose', [f'[{pluginName}] Devices files to process: "{files_to_process}"'])
for file_name in files_to_process:
# only process received .log files, skipping the one logging the progress of this plugin
if file_name != 'last_result.log':
mylog('verbose', [f'[{pluginName}] Processing: "{file_name}"'])
# Store e.g. Node_1 from last_result.encoded.Node_1.1.log
tmp_SyncHubNodeName = ''
if len(file_name.split('.')) > 3:
tmp_SyncHubNodeName = file_name.split('.')[2]
file_path = f"{INSTALL_PATH}/front/plugins/sync/{file_name}"
with open(file_path, 'r') as f:
data = json.load(f)
for device in data['data']:
if device['dev_MAC'] not in unique_mac_addresses:
device['dev_SyncHubNodeName'] = tmp_SyncHubNodeName
unique_mac_addresses.add(device['dev_MAC'])
device_data.append(device)
if len(files_to_process) > 0:
mylog('verbose', [f'[{pluginName}] Mode 3: RECEIVE (HUB) - This is a HUB as received data found'])
if len(device_data) > 0:
# Retrieve existing dev_MAC values from the Devices table
placeholders = ', '.join('?' for _ in unique_mac_addresses)
cursor.execute(f'SELECT dev_MAC FROM Devices WHERE dev_MAC IN ({placeholders})', tuple(unique_mac_addresses))
existing_mac_addresses = set(row[0] for row in cursor.fetchall())
# Connect to the App database
conn = sqlite3.connect(fullDbPath)
cursor = conn.cursor()
# insert devices into the lats_result.log to manage state
for device in device_data:
if device['dev_PresentLastScan'] == 1:
plugin_objects.add_object(
primaryId = device['dev_MAC'],
secondaryId = device['dev_LastIP'],
watched1 = device['dev_Name'],
watched2 = device['dev_Vendor'],
watched3 = device['dev_SyncHubNodeName'],
watched4 = device['dev_GUID'],
extra = '',
foreignKey = device['dev_GUID'])
# Collect all unique dev_MAC values from the JSON files
unique_mac_addresses = set()
device_data = []
# Filter out existing devices
new_devices = [device for device in device_data if device['dev_MAC'] not in existing_mac_addresses]
mylog('verbose', [f'[{pluginName}] Devices files to process: "{files_to_process}"'])
# Remove 'rowid' key if it exists
for device in new_devices:
device.pop('rowid', None)
for file_name in files_to_process:
mylog('verbose', [f'[{pluginName}] All devices: "{len(device_data)}"'])
mylog('verbose', [f'[{pluginName}] New devices: "{len(new_devices)}"'])
# only process received .log files, skipping the one logging the progress of this plugin
if file_name != 'last_result.log':
mylog('verbose', [f'[{pluginName}] Processing: "{file_name}"'])
# Prepare the insert statement
if new_devices:
# Store e.g. Node_1 from last_result.encoded.Node_1.1.log
tmp_SyncHubNodeName = ''
if len(file_name.split('.')) > 3:
tmp_SyncHubNodeName = file_name.split('.')[2]
columns = ', '.join(k for k in new_devices[0].keys() if k != 'rowid')
placeholders = ', '.join('?' for k in new_devices[0] if k != 'rowid')
sql = f'INSERT INTO Devices ({columns}) VALUES ({placeholders})'
# Extract values for the new devices
values = [tuple(device.values()) for device in new_devices]
file_path = f"{INSTALL_PATH}/front/plugins/sync/{file_name}"
with open(file_path, 'r') as f:
data = json.load(f)
for device in data['data']:
if device['dev_MAC'] not in unique_mac_addresses:
device['dev_SyncHubNodeName'] = tmp_SyncHubNodeName
unique_mac_addresses.add(device['dev_MAC'])
device_data.append(device)
mylog('verbose', [f'[{pluginName}] Inserting Devices SQL : "{sql}"'])
mylog('verbose', [f'[{pluginName}] Inserting Devices VALUES: "{values}"'])
if len(device_data) > 0:
# Retrieve existing dev_MAC values from the Devices table
placeholders = ', '.join('?' for _ in unique_mac_addresses)
cursor.execute(f'SELECT dev_MAC FROM Devices WHERE dev_MAC IN ({placeholders})', tuple(unique_mac_addresses))
existing_mac_addresses = set(row[0] for row in cursor.fetchall())
# Use executemany for batch insertion
cursor.executemany(sql, values)
# insert devices into the lats_result.log to manage state
for device in device_data:
if device['dev_PresentLastScan'] == 1:
plugin_objects.add_object(
primaryId = device['dev_MAC'],
secondaryId = device['dev_LastIP'],
watched1 = device['dev_Name'],
watched2 = device['dev_Vendor'],
watched3 = device['dev_SyncHubNodeName'],
watched4 = device['dev_GUID'],
extra = '',
foreignKey = device['dev_GUID'])
message = f'[{pluginName}] Inserted "{len(new_devices)}" new devices'
# Filter out existing devices
new_devices = [device for device in device_data if device['dev_MAC'] not in existing_mac_addresses]
mylog('verbose', [message])
write_notification(message, 'info', timeNowTZ())
# Remove 'rowid' key if it exists
for device in new_devices:
device.pop('rowid', None)
# Commit and close the connection
conn.commit()
conn.close()
mylog('verbose', [f'[{pluginName}] All devices: "{len(device_data)}"'])
mylog('verbose', [f'[{pluginName}] New devices: "{len(new_devices)}"'])
# log result
plugin_objects.write_result_file()
# Prepare the insert statement
if new_devices:
columns = ', '.join(k for k in new_devices[0].keys() if k != 'rowid')
placeholders = ', '.join('?' for k in new_devices[0] if k != 'rowid')
sql = f'INSERT INTO Devices ({columns}) VALUES ({placeholders})'
# Extract values for the new devices
values = [tuple(device.values()) for device in new_devices]
mylog('verbose', [f'[{pluginName}] Inserting Devices SQL : "{sql}"'])
mylog('verbose', [f'[{pluginName}] Inserting Devices VALUES: "{values}"'])
# Use executemany for batch insertion
cursor.executemany(sql, values)
message = f'[{pluginName}] Inserted "{len(new_devices)}" new devices'
mylog('verbose', [message])
write_notification(message, 'info', timeNowTZ())
# Commit and close the connection
conn.commit()
conn.close()
# log result
plugin_objects.write_result_file()
return 0
# send data to the HUB
def send_data(api_token, file_content, encryption_key, plugin_folder, node_name, pref, hub_url):
# Encrypt the log data using the encryption_key
encrypted_data = encrypt_data(file_content, encryption_key)
@@ -223,6 +282,36 @@ def send_data(api_token, file_content, encryption_key, plugin_folder, node_name,
message = f'[{pluginName}] Failed to send data for "{plugin_folder}" (Status code: {response.status_code})'
mylog('verbose', [message])
write_notification(message, 'alert', timeNowTZ())
# get data from the nodes to the HUB
def get_data(api_token, node_url):
mylog('verbose', [f'[{pluginName}] Getting data from node: "{node_url}"'])
# Set the authorization header with the API token
headers = {'Authorization': f'Bearer {api_token}'}
api_endpoint = f"{node_url}/plugins/sync/hub.php"
response = requests.get(api_endpoint, headers=headers)
# mylog('verbose', [f'[{pluginName}] response: "{response}"'])
if response.status_code == 200:
try:
# Parse JSON response
response_json = response.json()
return response_json
except json.JSONDecodeError:
message = f'[{pluginName}] Failed to parse JSON response from "{node_url}"'
mylog('verbose', [message])
write_notification(message, 'alert', timeNowTZ())
return ""
else:
message = f'[{pluginName}] Failed to send data for "{node_url}" (Status code: {response.status_code})'
mylog('verbose', [message])
write_notification(message, 'alert', timeNowTZ())
return ""

View File

@@ -26,6 +26,274 @@
],
"params": [],
"settings": [
{
"function": "NOT_RANDOM_MAC",
"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": []
}
]
},
"maxLength": 50,
"default_value": [],
"options": [],
"localized": [],
"name": [
{
"string": "_GLOBAL_LANG_FILES_"
}
],
"description": [
{
"string": "_GLOBAL_LANG_FILES_"
}
]
},
{
"function": "ICONS",
"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": []
}
]
},
"maxLength": 50,
"default_value": [
"PGkgY2xhc3M9ImZhIGZhLWNvbXB1dGVyIj48L2k+",
"PGkgY2xhc3M9ImZhIGZhLWV0aGVybmV0Ij48L2k+",
"PGkgY2xhc3M9ImZhIGZhLWdhbWVwYWQiPjwvaT4=",
"PGkgY2xhc3M9ImZhIGZhLWdsb2JlIj48L2k+",
"PGkgY2xhc3M9ImZhIGZhLWxhcHRvcCI+PC9pPg==",
"PGkgY2xhc3M9ImZhIGZhLWxpZ2h0YnVsYiI+PC9pPg==",
"PGkgY2xhc3M9ImZhIGZhLXNoaWVsZCI+PC9pPg==",
"PGkgY2xhc3M9ImZhIGZhLXdpZmkiPjwvaT4=",
"PGkgY2xhc3M9ImZhIGZhLWdhbWVwYWQiPjwvaT4="
],
"options": [],
"localized": [],
"name": [
{
"string": "_GLOBAL_LANG_FILES_"
}
],
"description": [
{
"string": "_GLOBAL_LANG_FILES_"
}
]
},
{
"function": "REFRESH",
"type": {
"dataType": "integer",
"elements": [
{
"elementType": "input",
"elementOptions": [{ "type": "number" }],
"transformers": []
}
]
},
"maxLength": 50,
"default_value": 0,
"options": [],
"localized": [],
"name": [
{
"string": "_GLOBAL_LANG_FILES_"
}
],
"description": [
{
"string": "_GLOBAL_LANG_FILES_"
}
]
},
{
"function": "DEV_SECTIONS",
"type": {
"dataType": "array",
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"transformers": []
}
]
},
"maxLength": 50,
"default_value": [],
"options": ["Tile Cards", "Device Presence"],
"localized": [],
"name": [
{
"string": "_GLOBAL_LANG_FILES_"
}
],
"description": [
{
"string": "_GLOBAL_LANG_FILES_"
}
]
},
{
"function": "PRESENCE",
"type": {
"dataType": "array",
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"transformers": []
}
]
},
"maxLength": 50,
"default_value": ["online", "offline", "archived"],
"options": ["online", "offline", "archived"],
"localized": [],
"name": [
{
"string": "_GLOBAL_LANG_FILES_"
}
],
"description": [
{
"string": "_GLOBAL_LANG_FILES_"
}
]
},
{
"function": "MY_DEVICES",
"type": {
"dataType": "array",
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"transformers": []
}
]
},
"maxLength": 50,
"default_value": ["online", "offline", "archived", "new", "down"],
"options": ["online", "offline", "archived", "new", "down"],
"localized": [],
"name": [
{
"string": "_GLOBAL_LANG_FILES_"
}
],
"description": [
{
"string": "_GLOBAL_LANG_FILES_"
}
]
},
{
"function": "device_columns",
"type": {
@@ -83,7 +351,7 @@
"options": [
"Device_TableHead_Name",
"Device_TableHead_Owner",
"Device_TableHead_Type",
"Device_TableHead_Type",
"Device_TableHead_Icon",
"Device_TableHead_Favorite",
"Device_TableHead_Group",

View File

@@ -25,28 +25,12 @@
{
"language_code": "en_us",
"string": "<i class=\"fa-solid fa-binoculars\"></i>"
},
{
"language_code": "es_es",
"string": "<i class=\"fa-solid fa-binoculars\"></i>"
},
{
"language_code": "de_de",
"string": "<i class=\"fa-solid fa-binoculars\"></i>"
}
],
"description": [
{
"language_code": "en_us",
"string": "This plugin is to import undiscoverable devices from a file."
},
{
"language_code": "es_es",
"string": "Este complemento es para importar dispositivos no detectables desde un archivo."
},
{
"language_code": "de_de",
"string": "Ein Plugin zum Importieren von nicht erkennbaren Geräten aus einer Datei."
"string": "This plugin is to import undiscoverable devices from a file. Only ASCII characters are supported."
}
],
"params": [

View File

@@ -42,7 +42,7 @@ def main():
fake_mac = string_to_mac_hash(fake_dev)
plugin_objects.add_object(
primaryId=fake_dev, # MAC (Device Name)
primaryId=fake_mac, # MAC (Device Name)
secondaryId="0.0.0.0", # IP Address (always 0.0.0.0)
watched1=fake_dev, # Device Name
watched2="",

View File

@@ -99,7 +99,7 @@
// Function to update the displayed data and timestamp based on the selected format and index
function updateData(format, index) {
// Fetch data from the API endpoint
fetch('/api/table_notifications.json?nocache=' + Date.now())
fetch('api/table_notifications.json?nocache=' + Date.now())
.then(response => response.json())
.then(data => {
if (index < 0) {
@@ -163,7 +163,7 @@
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has('guid')) {
const guid = urlParams.get('guid');
fetch('/api/table_notifications.json')
fetch('api/table_notifications.json')
.then(response => response.json())
.then(data => {
const index = findIndexByGUID(data.data, guid);

View File

@@ -378,6 +378,7 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
const valIn = set['Value'];
const codeName = set['Code_Name'];
const overriddenByEnv = set['OverriddenByEnv'] == 1;
const setType = set['Type'];
const isMetadata = codeName.includes('__metadata');
// is this isn't a metadata entry, get corresponding metadata object from the dummy setting
@@ -416,11 +417,11 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
<div class="table_cell setting_description">
${getString(codeName + '_description', set['Description'])}
</div>
<div class="table_cell setting_input input-group col-sm-12">
<div class="table_cell input-group setting_input ${overriddenByEnv ? "setting_overriden_by_env" : ""} input-group col-sm-12">
`;
// OVERRIDE
// surface settings override functionality if the setting is a template that can be overriden with user defined values
// surface settings override functionality if the setting is a template that can be overridden with user defined values
// if the setting is a json of the correct structure, handle like a template setting
let overrideHtml = "";
@@ -428,7 +429,7 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
//pre-check if this is a json object that needs value extraction
let overridable = false; // indicates if the setting is overridable
let override = false; // If the setting is set to be overriden by the user or by default
let override = false; // If the setting is set to be overridden by the user or by default
let readonly = ""; // helper variable to make text input readonly
let disabled = ""; // helper variable to make checkbox input readonly
@@ -485,7 +486,10 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
editable,
valRes,
getStringKey,
onClick
onClick,
onChange,
customParams,
customId
} = handleElementOptions(codeName, elementOptions, transformers, valIn);
// override
@@ -498,7 +502,15 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
let addCss = isOrdeable ? "select2 select2-hidden-accessible" : "";
inputHtml += `<select onChange="settingsChanged()" my-data-type="${dataType}" my-editable="${editable}" class="form-control ${addCss}" name="${codeName}" id="${codeName}" ${multi}>
inputHtml += `<select onChange="settingsChanged();${onChange}"
my-data-type="${dataType}"
my-editable="${editable}"
class="form-control ${addCss}"
name="${codeName}"
id="${codeName}"
my-customparams="${customParams}"
my-customid="${customId}"
${multi}>
<option value="" id="${codeName + "_temp_"}"></option>
</select>`;
@@ -513,8 +525,10 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
inputHtml += `
<input
class="${inputClass} ${cssClasses}"
onChange="settingsChanged()"
onChange="settingsChanged();${onChange}"
my-data-type="${dataType}"
my-customparams="${customParams}"
my-customid="${customId}"
id="${codeName}${suffix}"
type="${inputType}"
value="${val}"
@@ -529,6 +543,8 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
inputHtml += `
<button
class="btn btn-primary ${cssClasses}"
my-customparams="${customParams}"
my-customid="${customId}"
my-input-from="${sourceIds}"
my-input-to="${codeName}"
onclick="${onClick}">
@@ -539,12 +555,25 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
inputHtml += `
<textarea
class="form-control input"
my-customparams="${customParams}"
my-customid="${customId}"
my-data-type="${dataType}"
id="${codeName}"
${readOnly}>
${val}
</textarea>`;
break;
case 'span':
inputHtml += `
<span
class="${cssClasses}"
my-data-type="${dataType}"
my-customparams="${customParams}"
my-customid="${customId}"
>
${getString(getStringKey)}
</span>`;
break;
default:
console.warn(`🟥Unknown element type: ${elementType}`);
@@ -640,12 +669,21 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
// console.log(setTypeObject);
const dataType = setTypeObject.dataType;
// const lastElementObj = setTypeObject.elements[setTypeObject.elements.length - 1]; //🔽
// get the element with the input value(s)
const elementsWithInputValue = setTypeObject.elements.filter(element => element.elementHasInputValue === 1);
let elements = setTypeObject.elements.filter(element => element.elementHasInputValue === 1);
const { elementType, elementOptions = [], transformers = [] } = elementsWithInputValue;
// if none found, take last
if(elements.length == 0)
{
elementWithInputValue = setTypeObject.elements[setTypeObject.elements.length - 1]
} else
{
elementWithInputValue = elements[0]
}
const { elementType, elementOptions = [], transformers = [] } = elementWithInputValue;
const {
inputType,
readOnly,
@@ -659,22 +697,27 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
editable,
valRes,
getStringKey,
onClick
onClick,
onChange,
customParams,
customId
} = handleElementOptions('none', elementOptions, transformers, val = "");
let value;
if (dataType === "string" ||
(dataType === "integer" && (inputType === "number" || inputType === "text"))) {
value = $('#' + setCodeName).val();
value = applyTransformers(value, transformers);
settingsArray.push([prefix, setCodeName, dataType, value]);
} else if (dataType === 'boolean') {
value = $(`#${setCodeName}`).is(':checked') ? 1 : 0;
value = applyTransformers(value, transformers);
settingsArray.push([prefix, setCodeName, dataType, value]);
} else if (dataType === "array" ) {
@@ -813,7 +856,8 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
// Reload page if outdated information might be displayed
if (secondsSincePageLoad() > 10) {
clearCache();
console.log("App outdated, reloading...");
clearCache();
}
} else {
console.log("App not initialized, checking again in 1s...");

View File

@@ -60,7 +60,7 @@ require 'php/templates/header.php';
<script>
function fetchData(callback) {
$.ajax({
url: '/api/user_notifications.json?nocache=' + Date.now(),
url: 'api/user_notifications.json?nocache=' + Date.now(),
method: 'GET',
dataType: 'json',
success: function(response) {

View File

@@ -15,6 +15,7 @@ pluginsPath = applicationPath + '/front/plugins'
logPath = applicationPath + '/front/log'
apiPath = applicationPath + '/front/api/'
reportTemplatesPath = applicationPath + '/front/report_templates/'
fullConfFolder = applicationPath + '/config'
fullConfPath = applicationPath + confPath
fullDbPath = applicationPath + dbPath
vendorsPath = '/usr/share/arp-scan/ieee-oui.txt'

View File

@@ -213,15 +213,16 @@ class DB():
self.sql.execute(""" DROP TABLE IF EXISTS Settings;""")
self.sql.execute("""
CREATE TABLE "Settings" (
"Code_Name" TEXT,
"Display_Name" TEXT,
"Description" TEXT,
"Type" TEXT,
"Options" TEXT,
"RegEx" TEXT,
"Value" TEXT,
"Group" TEXT,
"Events" TEXT
"Code_Name" TEXT,
"Display_Name" TEXT,
"Description" TEXT,
"Type" TEXT,
"Options" TEXT,
"RegEx" TEXT,
"Group" TEXT,
"Value" TEXT,
"Events" TEXT,
"OverriddenByEnv" INTEGER
);
""")

View File

@@ -4,7 +4,7 @@ import subprocess
import conf
import os
import re
from helper import timeNowTZ, get_setting, get_setting_value, list_to_where, resolve_device_name_dig, resolve_device_name_pholus, get_device_name_nbtlookup, get_device_name_nslookup, check_IP_format
from helper import timeNowTZ, get_setting, get_setting_value, list_to_where, resolve_device_name_dig, resolve_device_name_pholus, get_device_name_nbtlookup, get_device_name_nslookup, check_IP_format, sanitize_SQL_input
from logger import mylog, print_log
from const import vendorsPath, vendorsPathNewest, sql_generateGuid
@@ -192,12 +192,12 @@ def create_new_devices (db):
{get_setting_value('NEWDEV_dev_NewDevice')},
{get_setting_value('NEWDEV_dev_SkipRepeated')},
{get_setting_value('NEWDEV_dev_ScanCycle')},
'{get_setting_value('NEWDEV_dev_Owner')}',
'{sanitize_SQL_input(get_setting_value('NEWDEV_dev_Owner'))}',
{get_setting_value('NEWDEV_dev_Favorite')},
'{get_setting_value('NEWDEV_dev_Group')}',
'{get_setting_value('NEWDEV_dev_Comments')}',
'{sanitize_SQL_input(get_setting_value('NEWDEV_dev_Group'))}',
'{sanitize_SQL_input(get_setting_value('NEWDEV_dev_Comments'))}',
{get_setting_value('NEWDEV_dev_LogEvents')},
'{get_setting_value('NEWDEV_dev_Location')}'"""
'{sanitize_SQL_input(get_setting_value('NEWDEV_dev_Location'))}'"""
# Fetch data from CurrentScan
current_scan_data = sql.execute("SELECT cur_MAC, cur_Name, cur_Vendor, cur_IP, cur_SyncHubNodeName, cur_NetworkNodeMAC, cur_PORT, cur_NetworkSite, cur_SSID, cur_Type FROM CurrentScan").fetchall()
@@ -210,6 +210,7 @@ def create_new_devices (db):
cur_Type = cur_Type.strip() if cur_Type else get_setting_value("NEWDEV_dev_DeviceType")
cur_NetworkNodeMAC = cur_NetworkNodeMAC.strip() if cur_NetworkNodeMAC else ''
cur_NetworkNodeMAC = cur_NetworkNodeMAC if cur_NetworkNodeMAC and cur_MAC != "Internet" else (get_setting_value("NEWDEV_dev_Network_Node_MAC_ADDR") if cur_MAC != "Internet" else "null")
cur_SyncHubNodeName = cur_SyncHubNodeName if cur_SyncHubNodeName and cur_SyncHubNodeName != "null" else (get_setting_value("SYNC_node_name"))
# Preparing the individual insert statement
sqlQuery = f"""INSERT OR IGNORE INTO Devices
@@ -231,19 +232,19 @@ def create_new_devices (db):
)
VALUES
(
'{cur_MAC}',
'{cur_Name}',
'{cur_Vendor}',
'{cur_IP}',
'{sanitize_SQL_input(cur_MAC)}',
'{sanitize_SQL_input(cur_Name)}',
'{sanitize_SQL_input(cur_Vendor)}',
'{sanitize_SQL_input(cur_IP)}',
?,
?,
'{cur_SyncHubNodeName}',
'{sanitize_SQL_input(cur_SyncHubNodeName)}',
{sql_generateGuid},
'{cur_NetworkNodeMAC}',
'{cur_PORT}',
'{cur_NetworkSite}',
'{cur_SSID}',
'{cur_Type}',
'{sanitize_SQL_input(cur_NetworkNodeMAC)}',
'{sanitize_SQL_input(cur_PORT)}',
'{sanitize_SQL_input(cur_NetworkSite)}',
'{sanitize_SQL_input(cur_SSID)}',
'{sanitize_SQL_input(cur_Type)}',
{newDevDefaults}
)"""
@@ -637,8 +638,8 @@ icons = {
def guess_icon(vendor, mac, ip, name, default):
result = default
mac = mac.upper()
vendor = vendor.lower()
name = name.lower()
vendor = vendor.lower() if vendor else "unknown"
name = name.lower() if name else "(unknown)"
# Guess icon based on vendor
if any(brand in vendor for brand in {"samsung", "motorola"}):
@@ -693,8 +694,8 @@ def guess_icon(vendor, mac, ip, name, default):
def guess_type(vendor, mac, ip, name, default):
result = default
mac = mac.upper()
vendor = vendor.lower()
name = name.lower()
vendor = vendor.lower() if vendor else "unknown"
name = name.lower() if name else "(unknown)"
# Guess icon based on vendor
if any(brand in vendor for brand in {"samsung", "motorola"}):

View File

@@ -319,7 +319,7 @@ def get_setting_value(key):
set_type = 'Error: Not handled'
set_value = 'Error: Not handled'
set_value = setting["Value"] # Setting value (Value (upper case) = user overriden default_value)
set_value = setting["Value"] # Setting value (Value (upper case) = user overridden default_value)
set_type = setting["Type"] # Setting type # lower case "type" - default json value vs uppper-case "Type" (= from user defined settings)
value = setting_value_to_python_type(set_type, set_value)
@@ -806,6 +806,13 @@ def sanitize_string(input):
return input
#-------------------------------------------------------------------------------
def sanitize_SQL_input(val):
if val is None:
return ''
return val.replace("'", "_")
#-------------------------------------------------------------------------------
def generate_mac_links (html, deviceUrl):

View File

@@ -11,8 +11,8 @@ import re
import conf
from const import fullConfPath, applicationPath
from helper import collect_lang_strings, updateSubnets, initOrSetParam, isJsonObject, updateState, setting_value_to_python_type, timeNowTZ
from const import fullConfPath, applicationPath, fullConfFolder
from helper import collect_lang_strings, updateSubnets, initOrSetParam, isJsonObject, updateState, setting_value_to_python_type, timeNowTZ, get_setting_value
from logger import mylog
from api import update_api
from scheduler import schedule_class
@@ -29,7 +29,8 @@ from notification import write_notification
# Check config dictionary
#-------------------------------------------------------------------------------
def ccd(key, default, config_dir, name, inputtype, options, group, events=None, desc="", regex="", setJsonMetadata=None, overrideTemplate=None):
# managing application settings, ensuring SQL safety for user input, and updating internal configuration lists
def ccd(key, default, config_dir, name, inputtype, options, group, events=None, desc="", regex="", setJsonMetadata=None, overrideTemplate=None, forceDefault=False, overriddenByEnv=0):
if events is None:
events = []
if setJsonMetadata is None:
@@ -41,7 +42,7 @@ def ccd(key, default, config_dir, name, inputtype, options, group, events=None,
result = default
# Use existing value if already supplied, otherwise default value is used
if key in config_dir:
if forceDefault == False and key in config_dir:
result = config_dir[key]
# Single quotes might break SQL queries, replacing them
@@ -49,8 +50,8 @@ def ccd(key, default, config_dir, name, inputtype, options, group, events=None,
result = result.replace('\'', "{s-quote}")
# Create the tuples
sql_safe_tuple = (key, name, desc, str(inputtype), options, regex, str(result), group, str(events))
settings_tuple = (key, name, desc, inputtype, options, regex, result, group, str(events))
sql_safe_tuple = (key, name, desc, str(inputtype), options, regex, str(result), group, str(events), overriddenByEnv)
settings_tuple = (key, name, desc, inputtype, options, regex, result, group, str(events), overriddenByEnv)
# Update or append the tuples in the lists
conf.mySettingsSQLsafe = update_or_append(conf.mySettingsSQLsafe, sql_safe_tuple, key)
@@ -58,7 +59,7 @@ def ccd(key, default, config_dir, name, inputtype, options, group, events=None,
# Save metadata in dummy setting if not a metadata key
if '__metadata' not in key:
metadata_tuple = (f'{key}__metadata', "metadata name", "metadata desc", '{"dataType":"json", "elements": [{"elementType" : "textarea", "elementOptions" : [{"readonly": "true"}] ,"transformers": []}]}', '[]', "", json.dumps(setJsonMetadata), group, '[]')
metadata_tuple = (f'{key}__metadata', "metadata name", "metadata desc", '{"dataType":"json", "elements": [{"elementType" : "textarea", "elementOptions" : [{"readonly": "true"}] ,"transformers": []}]}', '[]', "", json.dumps(setJsonMetadata), group, '[]', overriddenByEnv)
conf.mySettingsSQLsafe = update_or_append(conf.mySettingsSQLsafe, metadata_tuple, f'{key}__metadata')
conf.mySettings = update_or_append(conf.mySettings, metadata_tuple, f'{key}__metadata')
@@ -70,16 +71,20 @@ def update_or_append(settings_list, item_tuple, key):
if settings_list is None:
settings_list = []
# mylog('debug', ['[Import Config] update_or_append debug '])
# mylog('debug', ['[Import Config] update_or_append ', settings_list])
# mylog('debug', ['[Import Config] update_or_append item_tuple ' , item_tuple])
for index, item in enumerate(settings_list):
if item[0] == key:
settings_list[index] = item_tuple
mylog('debug', ['[Import Config] FOUND key : ', key])
return settings_list
mylog('trace', ['[Import Config] OLD TUPLE : ', item])
# Keep values marked as "_KEEP_"
updated_tuple = tuple(
new_val if new_val != "_KEEP_" else old_val
for old_val, new_val in zip(item, item_tuple)
)
mylog('trace', ['[Import Config] NEW TUPLE : ', updated_tuple])
settings_list[index] = updated_tuple
mylog('trace', ['[Import Config] FOUND key : ', key])
return settings_list
settings_list.append(item_tuple)
return settings_list
@@ -140,18 +145,13 @@ def importConfigs (db, all_plugins):
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')
conf.HRS_TO_KEEP_NEWDEV = ccd('HRS_TO_KEEP_NEWDEV', 0 , c_d, 'Keep new devices for', '{"dataType":"integer", "elements": [{"elementType" : "input", "elementOptions" : [{"type": "number"}] ,"transformers": []}]}', "[]", 'General')
conf.CLEAR_NEW_FLAG = ccd('CLEAR_NEW_FLAG', 0 , c_d, 'Clear new flag', '{"dataType":"integer", "elements": [{"elementType" : "input", "elementOptions" : [{"type": "number"}] ,"transformers": []}]}', "[]", 'General')
conf.API_CUSTOM_SQL = ccd('API_CUSTOM_SQL', 'SELECT * FROM Devices WHERE dev_PresentLastScan = 0' , c_d, 'Custom endpoint', '{"dataType":"string", "elements": [{"elementType" : "input", "elementOptions" : [] ,"transformers": []}]}', '[]', 'General')
conf.VERSION = ccd('VERSION', '' , c_d, 'Version', '{"dataType":"string", "elements": [{"elementType" : "input", "elementOptions" : [{ "readonly": "true" }] ,"transformers": []}]}', '', 'General')
conf.NETWORK_DEVICE_TYPES = ccd('NETWORK_DEVICE_TYPES', ['AP', 'Gateway', 'Firewall', 'Hypervisor', 'Powerline', 'Switch', 'WLAN', 'PLC', 'Router','USB LAN Adapter', 'USB WIFI Adapter', 'Internet'] , c_d, 'Network device types', '{"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":[]}]}', '[]', 'General')
# UI
conf.UI_LANG = ccd('UI_LANG', 'English' , c_d, 'Language Interface', '{"dataType":"string", "elements": [{"elementType" : "select", "elementOptions" : [] ,"transformers": []}]}', "['English', 'French', 'German', 'Norwegian', 'Russian', 'Spanish', 'Italian (it_it)', 'Portuguese (pt_br)', 'Polish (pl_pl)', 'Turkish (tr_tr)', 'Chinese (zh_cn)', 'Czech (cs_cz)' ]", 'UI')
conf.UI_NOT_RANDOM_MAC = ccd('UI_NOT_RANDOM_MAC', [] , c_d, 'Exlude from Random Prefix', '{"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":[]}]}', "[]", 'UI')
conf.UI_ICONS = ccd('UI_ICONS', ['PGkgY2xhc3M9J2ZhIGZhLXdpZmknPjwvaT4=', 'PGkgY2xhc3M9ImZhIGZhLWNvbXB1dGVyIj48L2k+', 'PGkgY2xhc3M9ImZhIGZhLWV0aGVybmV0Ij48L2k+', 'PGkgY2xhc3M9ImZhIGZhLWdhbWVwYWQiPjwvaT4', 'PGkgY2xhc3M9ImZhIGZhLWdsb2JlIj48L2k+', 'PGkgY2xhc3M9ImZhIGZhLWxhcHRvcCI+PC9pPg==', 'PGkgY2xhc3M9ImZhIGZhLWxpZ2h0YnVsYiI+PC9pPg==', 'PGkgY2xhc3M9ImZhIGZhLXNoaWVsZCI+PC9pPg==', 'PGkgY2xhc3M9ImZhIGZhLXdpZmkiPjwvaT4', 'PGkgY2xhc3M9J2ZhIGZhLWdhbWVwYWQnPjwvaT4'] , c_d, 'Icons', '{"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":[]}]}', "[]", 'UI')
conf.UI_REFRESH = ccd('UI_REFRESH', 0 , c_d, 'Refresh interval', '{"dataType":"integer", "elements": [{"elementType" : "input", "elementOptions" : [{"type": "number"}] ,"transformers": []}]}', "[]", 'UI')
conf.UI_DEV_SECTIONS = ccd('UI_DEV_SECTIONS', [] , c_d, 'Show sections', '{"dataType":"array", "elements": [{"elementType" : "select", "elementOptions" : [{"multiple":"true"}] ,"transformers": []}]}', "['Tile Cards', 'Device Presence']", 'UI')
conf.UI_PRESENCE = ccd('UI_PRESENCE', ['online', 'offline', 'archived'] , c_d, 'Include in presence', '{"dataType":"array", "elements": [{"elementType" : "select", "elementOptions" : [{"multiple":"true"}] ,"transformers": []}]}', "['online', 'offline', 'archived']", 'UI')
conf.UI_MY_DEVICES = ccd('UI_MY_DEVICES', ['online', 'offline', 'archived', 'new', 'down'] , c_d, 'Include in My Devices', '{"dataType":"array", "elements": [{"elementType" : "select", "elementOptions" : [{"multiple":"true"}] ,"transformers": []}]}', "['online', 'offline', 'archived', 'new', 'down']", 'UI')
conf.UI_LANG = ccd('UI_LANG', 'English' , c_d, 'Language Interface', '{"dataType":"string", "elements": [{"elementType" : "select", "elementOptions" : [] ,"transformers": []}]}', "['English', 'French', 'German', 'Norwegian', 'Russian', 'Spanish', 'Italian (it_it)', 'Portuguese (pt_br)', 'Polish (pl_pl)', 'Turkish (tr_tr)', 'Chinese (zh_cn)', 'Czech (cs_cz)' ]", 'UI')
# Init timezone in case it changed
conf.tz = timezone(conf.TIMEZONE)
@@ -297,24 +297,60 @@ def importConfigs (db, all_plugins):
conf.plugins_once_run = False
# -----------------
# Plugins END
# 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):
with open(app_conf_override_path, 'r') as f:
try:
# Load settings_override from the JSON file
settings_override = json.load(f)
# Loop through settings_override dictionary
for setting_name, value in settings_override.items():
# Ensure the value is treated as a string and passed directly
if isinstance(value, str):
# Log the value being passed
# ccd(key, default, config_dir, name, inputtype, options, group, events=None, desc="", regex="", setJsonMetadata=None, overrideTemplate=None, forceDefault=False)
mylog('debug', [f"[Config] Setting override {setting_name} with value: {value}"])
ccd(setting_name, value, c_d, '_KEEP_', '_KEEP_', '_KEEP_', '_KEEP_', None, "_KEEP_", "", None, None, True, 1)
else:
# Convert to string and log
# ccd(key, default, config_dir, name, inputtype, options, group, events=None, desc="", regex="", setJsonMetadata=None, overrideTemplate=None, forceDefault=False)
mylog('debug', [f"[Config] Setting override {setting_name} with value: {str(value)}"])
ccd(setting_name, str(value), c_d, '_KEEP_', '_KEEP_', '_KEEP_', '_KEEP_', None, "_KEEP_", "", None, None, True, 1)
except json.JSONDecodeError:
mylog('none', [f"[Config] [ERROR] Setting override decoding JSON from {app_conf_override_path}"])
else:
mylog('debug', [f"[Config] File {app_conf_override_path} does not exist."])
# Check if app was upgraded
with open(applicationPath + '/front/buildtimestamp.txt', 'r') as f:
buildTimestamp = int(f.read().strip())
if str(conf.VERSION) != str(buildTimestamp):
buildTimestamp = int(f.read().strip())
cur_version = conf.VERSION
mylog('debug', [f"[Config] buildTimestamp: '{buildTimestamp}'"])
mylog('debug', [f"[Config] conf.VERSION : '{cur_version}'"])
if str(cur_version) != str(buildTimestamp):
mylog('none', ['[Config] App upgraded 🎉'])
mylog('none', ['[Config] App upgraded 🚀'])
# ccd(key, default, config_dir, name, inputtype, options, group, events=None, desc="", regex="", setJsonMetadata=None, overrideTemplate=None, forceDefault=False)
ccd('VERSION', buildTimestamp , c_d, '_KEEP_', '_KEEP_', '_KEEP_', '_KEEP_', None, "_KEEP_", "", None, None, True)
conf.VERSION = ccd('VERSION', buildTimestamp , c_d, 'Version', '{"dataType":"string", "elements": [{"elementType" : "input", "elementOptions" : [{ "readonly": "true" }] ,"transformers": []}]}', '', 'General')
write_notification(f'[Upgrade] : App upgraded 🎉. Please clear app cache with the 🔄 button in the header and clear the browser cache (shift + browser refresh button).', 'interrupt', timeNowTZ())
write_notification(f'[Upgrade] : App upgraded 🚀 Please clear the cache: <ol> <li>Click OK below</li> <li>Clear the browser cache (shift + browser refresh button)</li> <li> Clear app cache with the 🔄 (reload) button in the header</li><li>Go to Settings and click Save</li> </ol> Check out new features and what has changed in the <a href="https://github.com/jokob-sk/NetAlertX/releases" target="_blank">📓 release notes</a>.', 'interrupt', timeNowTZ())
# Insert settings into the DB
sql.execute ("DELETE FROM Settings")
# mylog('debug', [f"[Config] conf.mySettingsSQLsafe : '{conf.mySettingsSQLsafe}'"])
sql.executemany ("""INSERT INTO Settings ("Code_Name", "Display_Name", "Description", "Type", "Options",
"RegEx", "Value", "Group", "Events" ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""", conf.mySettingsSQLsafe)
"RegEx", "Value", "Group", "Events", "OverriddenByEnv" ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", conf.mySettingsSQLsafe)
db.commitDB()

View File

@@ -248,7 +248,11 @@ def execute_plugin(db, all_plugins, plugin, pluginsState = plugins_state() ):
for line in newLines:
columns = line.split("|")
# There have to be 9 or 13 columns
# There have to be 9 or 13 columns
if len(columns) not in [9, 13]:
mylog('none', [f'[Plugins] Wrong number of input values, must be 9 or 13, got {len(columns)} from: {line}'])
continue # Skip lines with incorrect number of columns
# Common part of the SQL parameters
base_params = [
0, # "Index" placeholder
@@ -284,8 +288,6 @@ def execute_plugin(db, all_plugins, plugin, pluginsState = plugins_state() ):
'null', # "HelpVal3"
'null' # "HelpVal4"
])
else:
mylog('none', [f'[Plugins] Wrong number of input values, must be 9 or 13, got {len(columns)} from: {line} '])
# Create a tuple containing values to be inserted into the database.
# Each value corresponds to a column in the table in the order of the columns.