Compare commits

...

299 Commits

Author SHA1 Message Date
Hosted Weblate
703ba5c75b Merge branch 'origin/main' into Weblate.
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-02-08 00:54:10 +01:00
Anonymous
aad74451ef Translated using Weblate (Polish)
Currently translated at 88.0% (663 of 753 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/pl/
2025-02-08 00:54:08 +01:00
Anonymous
a787510963 Translated using Weblate (Portuguese (Brazil))
Currently translated at 32.9% (248 of 753 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/pt_BR/
2025-02-08 00:54:07 +01:00
jokob-sk
dd2b872712 Cron Schedule validation 2025-02-08 10:53:43 +11:00
jokob-sk
2a5e419034 Better dummy newdev MAC check 2025-02-08 08:17:37 +11:00
jokob-sk
b921144dbb treeviz flatdata experiment + better invalid TZ handling
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-02-08 08:01:05 +11:00
jokob-sk
4f2ddccdde Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2025-02-08 08:00:05 +11:00
jokob-sk
780b818815 treeviz flatdata experiment + better invalid TZ handling 2025-02-08 07:59:55 +11:00
jokob-sk
5779fd34c5 treeviz flatdata experiment + better invalid TZ handling 2025-02-08 07:52:13 +11:00
Safeguard
b7a6fe9112 Translated using Weblate (Russian)
Some checks are pending
docker / docker_dev (push) Waiting to run
Currently translated at 100.0% (753 of 753 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ru/
2025-02-07 00:52:35 +01:00
Максим Горпиніч
906bfd24a4 Translated using Weblate (Ukrainian)
Some checks failed
docker / docker_dev (push) Has been cancelled
Currently translated at 100.0% (753 of 753 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/uk/
2025-02-02 07:03:04 +01:00
Massimo Pissarello
8f48172940 Translated using Weblate (Italian)
Currently translated at 100.0% (753 of 753 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/it/
2025-02-02 07:03:03 +01:00
Sylvain Pichon
736304eb8a Translated using Weblate (French)
Currently translated at 100.0% (753 of 753 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/
2025-02-02 07:03:02 +01:00
Anonymous
5fce3c79b0 Translated using Weblate (Ukrainian)
Some checks are pending
docker / docker_dev (push) Waiting to run
Currently translated at 99.8% (752 of 753 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/uk/
2025-02-01 06:14:17 +01:00
Anonymous
81c1f65816 Translated using Weblate (Catalan)
Currently translated at 99.8% (752 of 753 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ca/
2025-02-01 06:14:17 +01:00
Anonymous
edfaadf682 Translated using Weblate (Turkish)
Currently translated at 22.0% (166 of 753 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/tr/
2025-02-01 06:14:17 +01:00
Anonymous
0a51d5fe79 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 87.9% (662 of 753 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/zh_Hans/
2025-02-01 06:14:17 +01:00
Anonymous
893063c695 Translated using Weblate (Polish)
Currently translated at 88.4% (666 of 753 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/pl/
2025-02-01 06:14:17 +01:00
Anonymous
da5cf4a8f1 Translated using Weblate (Portuguese (Brazil))
Currently translated at 32.5% (245 of 753 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/pt_BR/
2025-02-01 06:14:17 +01:00
Anonymous
1e5a4e96e4 Translated using Weblate (Italian)
Currently translated at 99.8% (752 of 753 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/it/
2025-02-01 06:14:16 +01:00
Anonymous
74d7a7853a Translated using Weblate (Russian)
Currently translated at 99.7% (751 of 753 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ru/
2025-02-01 06:14:16 +01:00
Anonymous
63469007ef Translated using Weblate (Norwegian Bokmål)
Currently translated at 87.6% (660 of 753 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/nb_NO/
2025-02-01 06:14:16 +01:00
Anonymous
f5b875e2df Translated using Weblate (French)
Currently translated at 99.8% (752 of 753 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/
2025-02-01 06:14:16 +01:00
Anonymous
d18efb2103 Translated using Weblate (Spanish)
Currently translated at 96.5% (727 of 753 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2025-02-01 06:14:15 +01:00
Anonymous
e2cdce2f39 Translated using Weblate (German)
Currently translated at 89.3% (673 of 753 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/de/
2025-02-01 06:14:15 +01:00
jokob-sk
c855d50999 docs + sorting #983
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-02-01 13:07:46 +11:00
jokob-sk
9d8b147e40 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-01-31 07:04:19 +11:00
jokob-sk
3aa9be7019 docs #982 2025-01-31 07:04:15 +11:00
Hosted Weblate
72b3d5eb6d Merge branch 'origin/main' into Weblate.
Some checks failed
docker / docker_dev (push) Has been cancelled
2025-01-26 23:36:17 +00:00
anton garcias
83bc406ed6 Translated using Weblate (Catalan)
Currently translated at 100.0% (753 of 753 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ca/
2025-01-26 23:36:16 +00:00
jokob-sk
e6e1c79d6a docs 2025-01-27 10:35:57 +11:00
Jokob @NetAlertX
e0616f72fe Merge pull request #981 from vladaurosh/main
Update default PUID and PGID, and update documentation
2025-01-27 09:20:28 +11:00
vladaurosh
696403ac20 Update FILE_PERMISSIONS.md 2025-01-26 21:57:54 +00:00
vladaurosh
c946a5335a Update README.md 2025-01-26 18:18:04 +00:00
vladaurosh
9610810891 Set uid/gid to previous default 102/82 2025-01-26 17:33:30 +00:00
Jokob @NetAlertX
2ad7f02255 Merge pull request #980 from vladaurosh/main
Some checks are pending
docker / docker_dev (push) Waiting to run
Adding option to set user ID and GID
2025-01-26 13:40:13 +11:00
root
431543ba80 adding option to set user ID and GID 2025-01-26 01:46:38 +00:00
jokob-sk
09d2e68479 Undiscoverables - UNDIS removal #979
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-01-26 10:23:21 +11:00
jokob-sk
896b8b7641 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2025-01-26 10:21:01 +11:00
jokob-sk
8d607aac96 css fixes, docs 2025-01-26 10:20:46 +11:00
Максим Горпиніч
c95a371ad9 Translated using Weblate (Ukrainian)
Some checks are pending
docker / docker_dev (push) Waiting to run
Currently translated at 100.0% (753 of 753 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/uk/
2025-01-25 13:02:18 +01:00
Sylvain Pichon
4b3ff048dc Translated using Weblate (French)
Currently translated at 100.0% (753 of 753 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/
2025-01-25 13:02:17 +01:00
Massimo Pissarello
3267762280 Translated using Weblate (Italian)
Some checks are pending
docker / docker_dev (push) Waiting to run
Currently translated at 100.0% (753 of 753 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/it/
2025-01-25 06:31:12 +01:00
Hosted Weblate
885a470585 Merge branch 'origin/main' into Weblate. 2025-01-25 00:37:31 +00:00
Safeguard
d0f4faca51 Translated using Weblate (Russian)
Currently translated at 100.0% (752 of 752 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ru/
2025-01-25 00:37:30 +00:00
jokob-sk
4443c69d31 Filters UI_columns_filters #953 2025-01-25 11:37:05 +11:00
Jokob @NetAlertX
890e533969 Merge pull request #976 from cvc90/NetAlertX-Changing-absolute-path-url-to-relative-path-url-in-common.js-file
Some checks are pending
docker / docker_dev (push) Waiting to run
Changed the "absolute URL" target to "relative URL" in common.js
2025-01-25 07:51:51 +11:00
Carlos V.
d7b9bb447f Update common.js
Change absolute path url /php/server/query_json.php to relative path url php/server/query_json.php
2025-01-24 17:17:52 +01:00
jokob-sk
c63f424c7d LOADED_PLUGINS docker variable #975
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-01-24 20:05:42 +11:00
jokob-sk
dd1580e536 asus and logging #972 2025-01-24 18:58:10 +11:00
jokob-sk
630e4f6327 asus DHCPLSS guide #963
Some checks failed
docker / docker_dev (push) Has been cancelled
2025-01-22 08:37:55 +11:00
Jokob @NetAlertX
cb8af32553 Merge pull request #970 from labmonkey/asuswrt-import
Some checks are pending
docker / docker_dev (push) Waiting to run
[ASUSWRT] Added router port to configurtation
2025-01-21 06:43:42 +11:00
jokob-sk
d469a9ded4 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-01-20 23:42:48 +11:00
jokob-sk
c8a40920b4 cleanup, faster devices screen update #967 #923 2025-01-20 23:42:24 +11:00
Максим Горпиніч
b0cd9acb79 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (752 of 752 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/uk/
2025-01-20 08:35:13 +01:00
Anonymous
6129f31a24 Translated using Weblate (Polish)
Some checks are pending
docker / docker_dev (push) Waiting to run
Currently translated at 88.6% (667 of 752 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/pl/
2025-01-20 06:17:17 +01:00
Massimo Pissarello
3eb8f39b5c Translated using Weblate (Italian)
Currently translated at 100.0% (752 of 752 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/it/
2025-01-20 06:17:17 +01:00
jokob-sk
5b1002620b cleanup, css, rebase to alpine 3.21 #968 2025-01-20 14:55:26 +11:00
Pawel Derehajlo
e50d757f57 [ASUSWRT] Added router port to configurtation 2025-01-20 02:07:47 +01:00
Jokob @NetAlertX
5110a3c2f3 Merge pull request #966 from vladaurosh/main
Using busybox crond instead of dcron, starting it with s6
2025-01-20 11:47:14 +11:00
root
abf7be5958 adding new line for pem file 2025-01-19 22:23:52 +00:00
root
82708bd5df Fixing path to cert file 2025-01-19 22:19:00 +00:00
root
b5dce3f6aa Fixing crond issues #945 and #965 2025-01-19 22:11:32 +00:00
jokob-sk
5562ae7add docs + wf
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-01-19 23:19:26 +11:00
jokob-sk
4363e083d5 prevent 401 in ingress #961 2025-01-19 16:05:58 +11:00
jokob-sk
e766b19d8c css, pluginsCore, less verbose debug fixes
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-01-19 15:05:26 +11:00
jokob-sk
b18ee70b8a Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2025-01-19 13:41:52 +11:00
jokob-sk
f70bb40ef4 INTERNET ip check curl #937 2025-01-19 13:41:48 +11:00
Hosted Weblate
844a408ff4 Merge branch 'origin/main' into Weblate. 2025-01-19 01:56:31 +00:00
Максим Горпиніч
a21cc0db85 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (751 of 751 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/uk/
2025-01-19 01:56:30 +00:00
Sylvain Pichon
a5427f795b Translated using Weblate (French)
Currently translated at 99.3% (746 of 751 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/
2025-01-19 01:56:29 +00:00
jokob-sk
6b390b66de report cleanup + #960 2025-01-19 12:56:00 +11:00
jokob-sk
af1e9b921b Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-01-18 10:07:13 +11:00
jokob-sk
9503cc6397 HA addon ingress #960 2025-01-18 10:07:03 +11:00
Jokob @NetAlertX
a16c2dfed6 Update README.md
Some checks failed
docker / docker_dev (push) Has been cancelled
2025-01-16 22:53:25 +11:00
Jokob @NetAlertX
b4fc05d1e8 Merge pull request #958 from labmonkey/asuswrt-import
Added AsusWRT device import plugin
2025-01-16 22:47:09 +11:00
labmonkey
adb99e5f1a Revert leftover code 2025-01-16 03:41:26 +01:00
Pawel Derehajlo
fbce3e18c2 Added AsusWRT device import plugin 2025-01-16 03:37:37 +01:00
anton garcias
0fcda5ff0a Translated using Weblate (Catalan)
Some checks are pending
docker / docker_dev (push) Waiting to run
Currently translated at 100.0% (751 of 751 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ca/
2025-01-15 15:13:13 +01:00
jokob-sk
a5c6510654 less verbose AVAHISCAN logs
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-01-15 12:39:03 +11:00
jokob-sk
6d44ed1bba Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-01-14 13:28:38 +11:00
jokob-sk
314372a0f2 server time format #954 2025-01-14 13:28:34 +11:00
Sylvain Pichon
5a84cb5cc2 Translated using Weblate (French)
Some checks are pending
docker / docker_dev (push) Waiting to run
Currently translated at 98.0% (736 of 751 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/
2025-01-13 16:58:25 +01:00
jokob-sk
12cc71552c #951, small fixes
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-01-13 08:16:20 +11:00
jokob-sk
6752b7fc40 gpc.json #952 2025-01-13 07:54:44 +11:00
jokob-sk
f371515258 issue template
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-01-12 21:21:08 +11:00
jokob-sk
f1d73f6ad4 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2025-01-12 21:18:50 +11:00
jokob-sk
9fee846436 HA addon docs 2025-01-12 21:18:44 +11:00
Massimo Pissarello
b4ebd640e5 Translated using Weblate (Italian)
Some checks are pending
docker / docker_dev (push) Waiting to run
Currently translated at 100.0% (751 of 751 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/it/
2025-01-12 08:22:16 +01:00
jokob-sk
e94953b9af Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-01-12 11:46:35 +11:00
jokob-sk
6718d054dc #951, small fixes 2025-01-12 11:46:32 +11:00
anton garcias
88e4dbf12e Translated using Weblate (Catalan)
Some checks are pending
docker / docker_dev (push) Waiting to run
Currently translated at 100.0% (751 of 751 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ca/
2025-01-11 13:09:29 +01:00
Anonymous
9f1db5ca1a Translated using Weblate (Portuguese (Brazil))
Currently translated at 32.7% (246 of 751 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/pt_BR/
2025-01-11 10:47:32 +01:00
jokob-sk
5e7bb207c8 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2025-01-11 20:20:30 +11:00
jokob-sk
6ad90610ea Docs, small fixes 2025-01-11 20:20:09 +11:00
Anonymous
c8ff0d79d1 Translated using Weblate (Ukrainian)
Some checks are pending
docker / docker_dev (push) Waiting to run
Currently translated at 97.7% (734 of 751 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/uk/
2025-01-11 04:36:15 +01:00
Anonymous
61cf87f0d6 Translated using Weblate (Catalan)
Currently translated at 98.1% (737 of 751 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ca/
2025-01-11 04:36:14 +01:00
Anonymous
75bbc8bcf3 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 88.2% (663 of 751 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/zh_Hans/
2025-01-11 04:36:14 +01:00
Anonymous
5582c0bf5e Translated using Weblate (Polish)
Currently translated at 88.9% (668 of 751 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/pl/
2025-01-11 04:36:14 +01:00
Anonymous
8278a92b11 Translated using Weblate (Italian)
Currently translated at 98.2% (738 of 751 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/it/
2025-01-11 04:36:14 +01:00
Anonymous
d5ac00a307 Translated using Weblate (Russian)
Currently translated at 98.2% (738 of 751 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ru/
2025-01-11 04:36:13 +01:00
Anonymous
2b02596b17 Translated using Weblate (Norwegian Bokmål)
Currently translated at 88.0% (661 of 751 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/nb_NO/
2025-01-11 04:36:13 +01:00
Anonymous
2a736f3c19 Translated using Weblate (French)
Currently translated at 97.7% (734 of 751 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/
2025-01-11 04:36:13 +01:00
Anonymous
0b5c0b198a Translated using Weblate (Spanish)
Currently translated at 96.9% (728 of 751 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2025-01-11 04:36:12 +01:00
Anonymous
2d0a461724 Translated using Weblate (German)
Currently translated at 89.7% (674 of 751 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/de/
2025-01-11 04:36:12 +01:00
jokob-sk
74c6faccc7 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-01-11 11:29:17 +11:00
jokob-sk
89a154e224 Docs 2025-01-11 11:29:02 +11:00
Hosted Weblate
8b84e4b325 Merge branch 'origin/main' into Weblate. 2025-01-11 01:24:14 +01:00
Massimo Pissarello
e55002a9b0 Translated using Weblate (Italian)
Currently translated at 100.0% (745 of 745 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/it/
2025-01-11 01:24:12 +01:00
jokob-sk
b4e741568b Extending Maintenance w/ app.conf (im/ex)port 2025-01-11 11:23:32 +11:00
jokob-sk
711e0012cb sort by port #949 2025-01-11 07:54:51 +11:00
jokob-sk
ea2e8459b5 sort by port #949 2025-01-11 07:51:43 +11:00
jokob-sk
16d06e8a74 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks failed
docker / docker_dev (push) Has been cancelled
2025-01-10 07:49:04 +11:00
jokob-sk
9625c36d12 devStaticIp to multi edit #947 2025-01-10 07:48:35 +11:00
Safeguard
f47858d773 Translated using Weblate (Russian)
Some checks are pending
docker / docker_dev (push) Waiting to run
Currently translated at 100.0% (745 of 745 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ru/
2025-01-09 15:53:40 +01:00
jokob-sk
ac2ce85f33 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2025-01-09 22:16:08 +11:00
jokob-sk
e887a11755 docs, cleanup 2025-01-09 22:15:58 +11:00
Anonymous
73f432c786 Translated using Weblate (Ukrainian)
Some checks are pending
docker / docker_dev (push) Waiting to run
Currently translated at 99.3% (740 of 745 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/uk/
2025-01-09 08:26:03 +01:00
Anonymous
9c95a79e07 Translated using Weblate (Catalan)
Currently translated at 99.8% (744 of 745 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ca/
2025-01-09 08:26:03 +01:00
Anonymous
f0bbd37812 Translated using Weblate (Portuguese (Brazil))
Currently translated at 33.0% (246 of 745 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/pt_BR/
2025-01-09 08:26:03 +01:00
Anonymous
7b0777b805 Translated using Weblate (Italian)
Currently translated at 99.8% (744 of 745 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/it/
2025-01-09 08:26:03 +01:00
Safeguard
b8dce59138 Translated using Weblate (Russian)
Currently translated at 99.5% (742 of 745 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ru/
2025-01-09 08:26:03 +01:00
Anonymous
f1d79074ec Translated using Weblate (Russian)
Currently translated at 99.5% (742 of 745 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ru/
2025-01-09 08:26:03 +01:00
Anonymous
4156bc1669 Translated using Weblate (French)
Currently translated at 99.3% (740 of 745 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/
2025-01-09 08:26:02 +01:00
Anonymous
00c4dd86c6 Translated using Weblate (Spanish)
Currently translated at 98.5% (734 of 745 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2025-01-09 08:26:02 +01:00
Anonymous
979ce8fd75 Translated using Weblate (German)
Currently translated at 91.2% (680 of 745 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/de/
2025-01-09 08:26:02 +01:00
jokob-sk
ede926beee docs, cleanup 2025-01-09 14:41:56 +11:00
jokob-sk
e1f91ddf17 docs, social posts, s6 setup improvements #945 2025-01-09 14:22:39 +11:00
jokob-sk
e9539962c9 cron s6 rollback #945
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-01-09 09:23:29 +11:00
jokob-sk
87d18a9067 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2025-01-09 08:35:14 +11:00
jokob-sk
2302cd9a31 s6 setup improvements #945 2025-01-09 08:35:09 +11:00
jokob-sk
babe0eab53 Merge pull request #944 from kpolszewski/mtscan-readme-update
better readme for the MikroTik plugin
2025-01-09 08:16:22 +11:00
kpolszewski
7c878690ef better readme for the MikroTik plugin 2025-01-08 19:15:32 +01:00
jokob-sk
b29dcbfa98 CHECKMK script
Some checks failed
docker / docker_dev (push) Waiting to run
🤖Automation - Update Sponsors Table / update-table (push) Has been cancelled
2025-01-08 23:32:14 +11:00
jokob-sk
8d9c48166b Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2025-01-08 23:00:07 +11:00
jokob-sk
540f8e850b DUMMY as new device source plugin 2025-01-08 23:00:03 +11:00
github-actions[bot]
8316d8e741 [🤖Automation] Update README with sponsors information 2025-01-08 11:53:43 +00:00
jokob-sk
ad32e76a55 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-01-08 08:12:54 +11:00
jokob-sk
190ffd3237 missing start.sh in final image v0.2 2025-01-08 08:12:50 +11:00
jokob-sk
c8280184dc Merge pull request #940 from kpolszewski/mtscan-names-from-comments
Restore manual naming of static leases using comments for MKSCAN + cleanups
2025-01-08 07:01:52 +11:00
Kamil Olszewski
076d8bbcc2 Restore manual naming of static leases using comments for MKSCAN + cleanups 2025-01-07 18:23:53 +01:00
jokob-sk
b21d57c524 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-01-07 23:17:27 +11:00
jokob-sk
064e0cb0ff missing pre-setup.sh in final image v0.1 2025-01-07 23:17:00 +11:00
github-actions[bot]
ad9d61aa16 [🤖Automation] Update README with sponsors information 2025-01-07 11:53:54 +00:00
jokob-sk
64ff9710d1 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2025-01-07 21:03:24 +11:00
jokob-sk
0cc87e3cfc cleanup, docs, devSyncHubNode #931 2025-01-07 21:03:20 +11:00
github-actions[bot]
c40af37ca1 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-01-06 11:53:45 +00:00
jokob-sk
07a7ace5fc devSyncHubNode on dev details #931 + ICMP not mapped to CurrentScan #934
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-01-06 10:50:54 +11:00
jokob-sk
4a82ea87f7 devSite to dropdown #930 2025-01-06 09:56:12 +11:00
jokob-sk
db47571424 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-01-06 09:22:54 +11:00
jokob-sk
58eaa33f39 GraphQL and API docs #933 + create /plugins log folder #921 2025-01-06 09:22:34 +11:00
github-actions[bot]
6e4d34b93a [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-01-05 11:53:45 +00:00
jokob-sk
b2c445e39d Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-01-05 09:50:23 +11:00
jokob-sk
90ff2efbfb devSite to dropdown #930 2025-01-05 09:50:19 +11:00
github-actions[bot]
b49f1ab335 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-01-04 11:53:43 +00:00
jokob-sk
3da5af1b7c cleanup
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-01-04 14:46:59 +11:00
jokob-sk
90dd8e3198 cleanup 2025-01-04 13:40:08 +11:00
jokob-sk
c31966bdd0 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2025-01-04 13:20:27 +11:00
jokob-sk
f2a0018982 docs + cleanup 2025-01-04 13:20:18 +11:00
github-actions[bot]
48ac108ca0 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-01-03 11:53:49 +00:00
jokob-sk
19441a4431 graphql_schema logging too noisy #917 2025-01-03 22:34:19 +11:00
jokob-sk
5541d6c9d2 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2025-01-03 22:28:17 +11:00
jokob-sk
93886cec8a better defaults for freebox parsing issue #927 2025-01-03 22:27:56 +11:00
Hosted Weblate
8401ecda5e Merge branch 'origin/main' into Weblate. 2025-01-03 12:21:23 +01:00
anton garcias
09aba51a33 Translated using Weblate (Catalan)
Currently translated at 100.0% (745 of 745 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ca/
2025-01-03 12:21:23 +01:00
Massimo Pissarello
06946e4fea Translated using Weblate (Italian)
Currently translated at 100.0% (745 of 745 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/it/
2025-01-03 12:21:23 +01:00
Ettore Atalan
f96ca3e08b Translated using Weblate (German)
Currently translated at 91.4% (681 of 745 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/de/
2025-01-03 12:21:23 +01:00
jokob-sk
4aa18691f4 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2025-01-03 22:20:50 +11:00
jokob-sk
bd198587cd docs + freebox parsing issue #927 2025-01-03 22:20:45 +11:00
Anonymous
e8a4cb1d51 Translated using Weblate (Ukrainian)
Some checks are pending
docker / docker_dev (push) Waiting to run
Currently translated at 99.4% (741 of 745 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/uk/
2025-01-02 18:08:41 +01:00
Anonymous
75e25867e1 Translated using Weblate (Catalan)
Currently translated at 98.6% (735 of 745 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ca/
2025-01-02 18:08:41 +01:00
Anonymous
7dadb735c2 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 89.7% (669 of 745 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/zh_Hans/
2025-01-02 18:08:41 +01:00
Anonymous
b444f98e2d Translated using Weblate (Polish)
Currently translated at 90.3% (673 of 745 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/pl/
2025-01-02 18:08:40 +01:00
Anonymous
8ca2bff456 Translated using Weblate (Russian)
Currently translated at 99.4% (741 of 745 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ru/
2025-01-02 18:08:40 +01:00
Anonymous
308285f808 Translated using Weblate (Norwegian Bokmål)
Currently translated at 89.3% (666 of 745 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/nb_NO/
2025-01-02 18:08:40 +01:00
Anonymous
1a03b2ccc3 Translated using Weblate (French)
Currently translated at 99.4% (741 of 745 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/
2025-01-02 18:08:40 +01:00
Anonymous
f6a6c3684c Translated using Weblate (Spanish)
Currently translated at 98.6% (735 of 745 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2025-01-02 18:08:40 +01:00
Anonymous
f2bf379597 Translated using Weblate (German)
Currently translated at 90.2% (672 of 745 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/de/
2025-01-02 18:08:40 +01:00
github-actions[bot]
802002a5f0 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-01-02 11:53:48 +00:00
jokob-sk
e6d2a1c138 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-01-02 10:16:06 +11:00
jokob-sk
729c24029f docs + fixes to CustomProps 2025-01-02 10:15:58 +11:00
github-actions[bot]
3260a67bf4 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2025-01-01 11:53:36 +00:00
github-actions[bot]
5621f13c6e [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-31 11:53:38 +00:00
Hosted Weblate
aae08594bb Merge branch 'origin/main' into Weblate.
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-31 05:25:17 +01:00
Massimo Pissarello
ba83884b9f Translated using Weblate (Italian)
Currently translated at 100.0% (745 of 745 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/it/
2024-12-31 05:25:16 +01:00
jokob-sk
2799d3598b docs 2024-12-31 15:24:51 +11:00
jokob-sk
1ee746a625 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-31 10:14:21 +11:00
jokob-sk
e52601e062 ENCRYPTION_KEY, docs 2024-12-31 10:14:01 +11:00
Safeguard
2dad079979 Translated using Weblate (Russian)
Currently translated at 100.0% (743 of 743 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ru/
2024-12-30 20:49:35 +01:00
github-actions[bot]
47bdb60c85 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-30 11:53:52 +00:00
github-actions[bot]
6b9df66b02 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-29 11:53:50 +00:00
github-actions[bot]
b188afab44 [🤖Automation] Update README with sponsors information
Some checks failed
docker / docker_dev (push) Has been cancelled
2024-12-28 11:53:33 +00:00
Максим Горпиніч
5fe6ac9816 Translated using Weblate (Ukrainian)
Some checks are pending
docker / docker_dev (push) Waiting to run
Currently translated at 100.0% (743 of 743 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/uk/
2024-12-28 09:51:45 +01:00
Sylvain Pichon
d354acbcb2 Translated using Weblate (French)
Currently translated at 100.0% (743 of 743 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/
2024-12-28 09:51:44 +01:00
Hosted Weblate
846e15d518 Merge branch 'origin/main' into Weblate.
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-27 22:20:28 +00:00
Максим Горпиніч
067467da53 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (742 of 742 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/uk/
2024-12-27 22:20:27 +00:00
jokob-sk
8ac4112ab9 Archived devices were shown #920 2024-12-28 09:19:55 +11:00
jokob-sk
ae32b0dd42 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2024-12-28 09:19:34 +11:00
jokob-sk
4fef6e156b Archived devices were shown #920 2024-12-28 09:19:25 +11:00
github-actions[bot]
161a74dee0 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-27 11:53:49 +00:00
jokob-sk
fa570b9bc9 🔌LUCIRPC - OpenWRT import - import fix
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-27 14:18:52 +11:00
jokob-sk
9cfbc7d140 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2024-12-27 14:08:08 +11:00
jokob-sk
dda440eb53 🔌LUCIRPC - OpenWRT import 2024-12-27 14:05:49 +11:00
Hosted Weblate
efe161494e Merge branch 'origin/main' into Weblate. 2024-12-27 01:43:02 +00:00
gallegonovato
35f1624804 Translated using Weblate (Spanish)
Currently translated at 100.0% (737 of 737 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2024-12-27 01:43:00 +00:00
jokob-sk
74ec75f105 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-27 12:42:26 +11:00
jokob-sk
0f474fb884 Custom Device Properties v0.1 #876 2024-12-27 12:42:15 +11:00
Sylvain Pichon
f54ffcbbc3 Translated using Weblate (French)
Some checks are pending
docker / docker_dev (push) Waiting to run
Currently translated at 100.0% (737 of 737 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/
2024-12-26 16:00:37 +01:00
github-actions[bot]
21fd9866fe [🤖Automation] Update README with sponsors information 2024-12-26 11:53:51 +00:00
github-actions[bot]
3732416616 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-25 11:53:41 +00:00
github-actions[bot]
29396ad6bd [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-24 11:53:56 +00:00
jokob-sk
37ae75ed9a Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-24 11:06:46 +11:00
jokob-sk
7cdcf95300 Docs and simngle quote removal when editing device #918 2024-12-24 11:06:43 +11:00
github-actions[bot]
e26f7d42e9 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-23 11:53:46 +00:00
jokob-sk
8f8264c6fa WOL plugin #887
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-23 13:10:27 +11:00
jokob-sk
a4c34140bf Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2024-12-23 11:28:57 +11:00
jokob-sk
466aa62a02 DB cleanup script + docs 2024-12-23 11:28:46 +11:00
github-actions[bot]
febc26b187 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-22 11:53:40 +00:00
jokob-sk
7248e73e03 Respecting LOG_LEVEL in plugins
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-22 13:18:08 +11:00
jokob-sk
96e62468fc lang files 2024-12-22 11:25:23 +11:00
jokob-sk
c8c95b22a1 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2024-12-22 11:24:48 +11:00
jokob-sk
b63b00f30c Revert DataTable upgrade, new default docker-compose, performance tip 2024-12-22 11:24:44 +11:00
github-actions[bot]
6e07032c15 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-21 11:53:47 +00:00
Максим Горпиніч
3663e617e0 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (737 of 737 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/uk/
2024-12-21 07:00:35 +00:00
anton garcias
5a9ff3f07f Translated using Weblate (Catalan)
Currently translated at 100.0% (737 of 737 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ca/
2024-12-21 07:00:34 +00:00
github-actions[bot]
5af2b283c9 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-20 11:53:48 +00:00
Massimo Pissarello
b88cfeda4c Translated using Weblate (Italian)
Some checks are pending
docker / docker_dev (push) Waiting to run
Currently translated at 100.0% (737 of 737 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/it/
2024-12-20 05:58:22 +01:00
Anonymous
ec9466c562 Translated using Weblate (Ukrainian)
Currently translated at 99.4% (733 of 737 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/uk/
2024-12-20 04:15:52 +01:00
Anonymous
e3039b5f7c Translated using Weblate (Catalan)
Currently translated at 99.1% (731 of 737 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ca/
2024-12-20 04:15:52 +01:00
Massimo Pissarello
46bb490384 Translated using Weblate (Italian)
Currently translated at 99.7% (735 of 737 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/it/
2024-12-20 04:15:52 +01:00
Anonymous
a174eb58d7 Translated using Weblate (Italian)
Currently translated at 99.7% (735 of 737 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/it/
2024-12-20 04:15:52 +01:00
Anonymous
43df53f1c1 Translated using Weblate (Russian)
Currently translated at 99.3% (732 of 737 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ru/
2024-12-20 04:15:51 +01:00
Anonymous
4ec803f4c4 Translated using Weblate (French)
Currently translated at 99.4% (733 of 737 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/
2024-12-20 04:15:51 +01:00
Anonymous
cfbbe83b7a Translated using Weblate (Spanish)
Currently translated at 99.4% (733 of 737 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2024-12-20 04:15:51 +01:00
jokob-sk
7913d42699 Bootstrap + DataTables cleanup 2024-12-20 14:11:16 +11:00
jokob-sk
40e4502f29 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2024-12-20 11:15:07 +11:00
jokob-sk
b80718d409 📚Docs + DISCOVER_PLUGINS 2024-12-20 11:14:44 +11:00
github-actions[bot]
147d8833f7 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-19 11:53:49 +00:00
jokob-sk
8877e7a528 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2024-12-19 20:15:36 +11:00
jokob-sk
f38d72a690 Fix debounce of api points to address Disk IO #914 + NMAPDEV_FAKE_MAC 2024-12-19 20:15:15 +11:00
Hosted Weblate
be673315d4 Merge branch 'origin/main' into Weblate. 2024-12-19 08:34:50 +01:00
Максим Горпиніч
07f3eabea3 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (734 of 734 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/uk/
2024-12-19 08:34:49 +01:00
Massimo Pissarello
d51373dcab Translated using Weblate (Italian)
Currently translated at 100.0% (734 of 734 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/it/
2024-12-19 08:34:46 +01:00
jokob-sk
773b49a1b4 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2024-12-19 18:33:02 +11:00
jokob-sk
f4d215c843 Fix debounce of api points to address Disk IO #914 2024-12-19 18:32:57 +11:00
jokob-sk
30d9d4b8fc Override init fix of schedules 2024-12-19 12:12:20 +11:00
Максим Горпиніч
d82d76a1c7 Translated using Weblate (Ukrainian)
Some checks are pending
docker / docker_dev (push) Waiting to run
Currently translated at 45.5% (334 of 734 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/uk/
2024-12-19 01:05:58 +01:00
Hosted Weblate
743f97e194 Merge branch 'origin/main' into Weblate.
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-18 21:41:19 +01:00
Sylvain Pichon
31e4743caa Translated using Weblate (French)
Currently translated at 100.0% (734 of 734 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/
2024-12-18 21:41:17 +01:00
jokob-sk
0a7356f9be Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2024-12-19 07:40:45 +11:00
jokob-sk
498ca227fd Override init fix of schedules + uk_ua 2024-12-19 07:40:33 +11:00
github-actions[bot]
55fc068dda [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-18 11:53:50 +00:00
gallegonovato
0d030ef355 Translated using Weblate (Spanish)
Some checks are pending
docker / docker_dev (push) Waiting to run
Currently translated at 100.0% (734 of 734 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2024-12-17 14:18:46 +01:00
github-actions[bot]
0e381c6592 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-17 11:54:01 +00:00
jokob-sk
4976324c50 Override init fix of schedules 2024-12-17 22:46:31 +11:00
jokob-sk
48f86e91f7 Override init fix of schedules 2024-12-17 22:25:45 +11:00
jokob-sk
3cc4caa34c Override init fix of schedules 2024-12-17 22:00:41 +11:00
jokob-sk
191afdf857 Override init fix of schedules + #913 2024-12-17 21:16:34 +11:00
jokob-sk
721a275fd8 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-17 07:04:05 +11:00
jokob-sk
caf5a5347c chore: Try to reduce disk IO 2024-12-17 07:04:00 +11:00
github-actions[bot]
688944bda1 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-16 11:53:44 +00:00
jokob-sk
c132374421 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2024-12-16 20:21:59 +11:00
jokob-sk
953534724c chore: Try to reduce disk IO 2024-12-16 20:21:52 +11:00
github-actions[bot]
6115c12a6c [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-15 11:53:52 +00:00
github-actions[bot]
07d3a3fede [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-14 11:53:40 +00:00
jokob-sk
2e47af7b63 sync plugin fix - remove devStatus 2024-12-14 09:15:06 +11:00
jokob-sk
e7843e6e73 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-14 08:45:44 +11:00
jokob-sk
f247e4aabc disk io:logging improvements 2024-12-14 08:45:39 +11:00
github-actions[bot]
4f02232ee3 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-13 11:53:36 +00:00
Thales
c1c8352274 Translated using Weblate (Portuguese (Brazil))
Some checks are pending
docker / docker_dev (push) Waiting to run
Currently translated at 33.6% (247 of 733 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/pt_BR/
2024-12-13 02:00:28 +01:00
Safeguard
c3157f6ef4 Translated using Weblate (Russian)
Currently translated at 100.0% (733 of 733 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ru/
2024-12-13 02:00:27 +01:00
jokob-sk
0a200afebc /log/plugins 2024-12-13 10:43:04 +11:00
jokob-sk
c4c1f9e345 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2024-12-13 10:18:56 +11:00
jokob-sk
7b15efa913 last_result.log -> /log/plugins/last_result.PLUGPREF.log 2024-12-13 10:18:35 +11:00
github-actions[bot]
a0db36cb2d [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-12 11:53:47 +00:00
jokob-sk
796b4596ce Merge pull request #912 from ytorres/patch-3
Some checks are pending
docker / docker_dev (push) Waiting to run
Fix typo
2024-12-12 07:18:37 +11:00
Yannick Torrès
2625f2f96e Fix typo 2024-12-11 19:07:42 +01:00
github-actions[bot]
90f4abc037 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-11 11:53:47 +00:00
github-actions[bot]
8345bda25d [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-10 11:53:46 +00:00
jokob-sk
7656ef99e8 Merge pull request #910 from ytorres/patch-1
Some checks are pending
docker / docker_dev (push) Waiting to run
Update install_dependencies.debian.sh
2024-12-10 07:35:00 +11:00
jokob-sk
9c2841167c Merge pull request #911 from ytorres/patch-2
Update start.debian.sh
2024-12-10 07:34:03 +11:00
Yannick Torrès
03056f7823 Update start.debian.sh
1/ Fix {} arround user_notifications.json file => if there is just one file, this create a file named "{user_notifications.json} ;)

2/ Fix group for the above file
2024-12-09 19:06:18 +01:00
Yannick Torrès
2275bf3114 Update install_dependencies.debian.sh
Work on a fresh installation of Debian Bookworm
2024-12-09 19:00:23 +01:00
github-actions[bot]
ac0c13dced [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-09 11:53:39 +00:00
github-actions[bot]
74df660145 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-08 11:53:53 +00:00
jokob-sk
a7e35c4697 Move of LOG folder from /app/front/log to app/log 2024-12-08 21:06:44 +11:00
jokob-sk
cd9c4a2176 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-08 19:17:17 +11:00
jokob-sk
f7160f0843 Move of API folder from /app/front/api to app/api 2024-12-08 19:17:10 +11:00
github-actions[bot]
2ad80f5ba5 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-07 11:53:34 +00:00
jokob-sk
d7858c6042 GraphQL Settings 2024-12-07 19:25:07 +11:00
jokob-sk
79c4574e21 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-07 10:38:38 +11:00
jokob-sk
daff0ee7f2 📚Docs 2024-12-07 10:38:34 +11:00
github-actions[bot]
a22df52725 [🤖Automation] Update README with sponsors information
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-06 11:53:32 +00:00
jokob-sk
89b4b9b98e Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks are pending
docker / docker_dev (push) Waiting to run
2024-12-06 07:39:00 +11:00
jokob-sk
80e8cb3f74 Icon cleanup 2024-12-06 07:38:51 +11:00
Massimo Pissarello
caef64ddf4 Translated using Weblate (Italian)
Some checks are pending
docker / docker_dev (push) Waiting to run
Currently translated at 100.0% (733 of 733 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/it/
2024-12-05 13:00:38 +00:00
Sylvain Pichon
0d231caecd Translated using Weblate (French)
Currently translated at 100.0% (733 of 733 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/
2024-12-05 13:00:37 +00:00
gallegonovato
fbc43e6116 Translated using Weblate (Spanish)
Currently translated at 100.0% (733 of 733 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2024-12-05 13:00:36 +00:00
github-actions[bot]
db7a122f39 [🤖Automation] Update README with sponsors information 2024-12-05 11:53:43 +00:00
264 changed files with 57333 additions and 5257 deletions

View File

@@ -6,14 +6,10 @@
docker-compose.yml
Dockerfile
Dockerfile.debian
dockerfiles/LICENSE
dockerfiles/README.md
dockerfiles/README_ES.md
docs
LICENSE.txt
README.md
CONTRIBUTING
FUNDING.yml
config/.gitignore
db/.gitignore
db/.gitignore

View File

@@ -60,10 +60,11 @@ body:
required: false
- type: dropdown
attributes:
label: What branch are you running?
label: What installation are you running?
options:
- Production
- Dev
- Production (netalertx)
- Dev (netalertx-dev)
- Home Assistant (addon)
validations:
required: true
- type: textarea
@@ -74,7 +75,7 @@ body:
***Generally speaking, all bug reports should have logs provided.***
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
Additionally, any additional info? Screenshots? References? Anything that will give us more context about the issue you are encountering!
You can use `tail -100 /app/front/log/app.log` in the container if you have trouble getting to the log files.
You can use `tail -100 /app/log/app.log` in the container if you have trouble getting to the log files.
validations:
required: false
- type: checkboxes

32
.github/workflows/social_post_on_release.yml vendored Executable file
View File

@@ -0,0 +1,32 @@
name: 📧 Twitter and Discord Posts
on:
release:
types: [published]
jobs:
post-release:
runs-on: ubuntu-latest
steps:
- name: Wait for 15 minutes
run: sleep 900 # 15 minutes delay
# Post to Twitter
- name: Post to Twitter
uses: twitter-together/action@v3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }}
TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
TWITTER_API_KEY: ${{ secrets.TWITTER_API_KEY }}
TWITTER_API_SECRET_KEY: ${{ secrets.TWITTER_API_SECRET_KEY }}
with:
tweet: |
🎉 New release: **${{ github.event.release.name }}** is live! 🚀
Check it out here: ${{ github.event.release.html_url }}
# Post to Discord
- name: Post to Discord
run: |
curl -X POST -H "Content-Type: application/json" \
-d '{"content": "🎉 New release: **${{ github.event.release.name }}** is live! 🚀\nCheck it out here: ${{ github.event.release.html_url }}"}' \
${{ secrets.DISCORD_WEBHOOK_URL }}

View File

@@ -1,29 +0,0 @@
name: 🤖Automation - Update Sponsors Table
on:
schedule:
- cron: '50 11 * * *' # Set your preferred schedule (UTC)
jobs:
update-table:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.8
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r update_sponsors_requirements.txt # If you have any Python dependencies
- name: Update Sponsors Table
run: |
python update_sponsors.py
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

11
.gitignore vendored
View File

@@ -7,8 +7,11 @@ db/*
db/pialert.db
db/app.db
front/log/*
/log/*
front/api/*
/api/*
**/plugins/**/*.log
**/plugins/cloud_services/*
**/%40eaDir/
**/@eaDir/
@@ -22,10 +25,10 @@ __pycache__/
**/pialert.db_bak
.*.swp
front/img/account/*
**/account.php
**/account.js
front/css/account.css
front/img/cloud_services/*
**/cloud_services.php
**/cloud_services.js
front/css/cloud_services.css
docker-compose.yml.ffsb42
.env.omada.ffsb42

View File

@@ -1,11 +1,11 @@
FROM alpine:3.20 AS builder
FROM alpine:3.21 AS builder
ARG INSTALL_DIR=/app
ENV PYTHONUNBUFFERED=1
# Install build dependencies
RUN apk add --no-cache bash python3 python3-dev gcc musl-dev libffi-dev openssl-dev git\
RUN apk add --no-cache bash shadow python3 python3-dev gcc musl-dev libffi-dev openssl-dev git \
&& python -m venv /opt/venv
# Enable venv
@@ -13,34 +13,21 @@ ENV PATH="/opt/venv/bin:$PATH"
COPY . ${INSTALL_DIR}/
RUN pip install graphene flask netifaces tplink-omada-client pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros git+https://github.com/foreign-sub/aiofreepybox.git \
RUN pip install openwrt-luci-rpc asusrouter asyncio aiohttp graphene flask tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros git+https://github.com/foreign-sub/aiofreepybox.git \
&& bash -c "find ${INSTALL_DIR} -type d -exec chmod 750 {} \;" \
&& bash -c "find ${INSTALL_DIR} -type f -exec chmod 640 {} \;" \
&& bash -c "find ${INSTALL_DIR} -type f \( -name '*.sh' -o -name '*.py' -o -name 'speedtest-cli' \) -exec chmod 750 {} \;"
# Append Iliadbox certificate to aiofreepybox
RUN printf "\n-----BEGIN CERTIFICATE-----\n\
MIICOjCCAcCgAwIBAgIUI0Tu7zsrBJACQIZgLMJobtbdNn4wCgYIKoZIzj0EAwIw\n\
TDELMAkGA1UEBhMCSVQxDjAMBgNVBAgMBUl0YWx5MQ4wDAYDVQQKDAVJbGlhZDEd\n\
MBsGA1UEAwwUSWxpYWRib3ggRUNDIFJvb3QgQ0EwHhcNMjAxMTI3MDkzODEzWhcN\n\
NDAxMTIyMDkzODEzWjBMMQswCQYDVQQGEwJJVDEOMAwGA1UECAwFSXRhbHkxDjAM\n\
BgNVBAoMBUlsaWFkMR0wGwYDVQQDDBRJbGlhZGJveCBFQ0MgUm9vdCBDQTB2MBAG\n\
ByqGSM49AgEGBSuBBAAiA2IABMryJyb2loHNAioY8IztN5MI3UgbVHVP/vZwcnre\n\
ZvJOyDvE4HJgIti5qmfswlnMzpNbwf/MkT+7HAU8jJoTorRm1wtAnQ9cWD3Ebv79\n\
RPwtjjy3Bza3SgdVxmd6fWPUKaNjMGEwHQYDVR0OBBYEFDUij/4lpoJ+kOXRyrcM\n\
jf2RPzOqMB8GA1UdIwQYMBaAFDUij/4lpoJ+kOXRyrcMjf2RPzOqMA8GA1UdEwEB\n\
/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQC6eUV1\n\
pFh4UpJOTc1JToztN4ttnQR6rIzxMZ6mNCe+nhjkohWp24pr7BpUYSbEizYCMAQ6\n\
LCiBKV2j7QQGy7N1aBmdur17ZepYzR1YV0eI+Kd978aZggsmhjXENQYVTmm/XA==\n\
-----END CERTIFICATE-----\n" >> /opt/venv/lib/python3.12/site-packages/aiofreepybox/freebox_certificates.pem
RUN cat ${INSTALL_DIR}/install/freebox_certificate.pem >> /opt/venv/lib/python3.12/site-packages/aiofreepybox/freebox_certificates.pem
# second stage
FROM alpine:3.20 AS runner
FROM alpine:3.21 AS runner
ARG INSTALL_DIR=/app
COPY --from=builder /opt/venv /opt/venv
COPY --from=builder /usr/sbin/usermod /usr/sbin/groupmod /usr/sbin/
# Enable venv
ENV PATH="/opt/venv/bin:$PATH"
@@ -54,11 +41,10 @@ ENV S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0
# ❗ IMPORTANT - if you modify this file modify the /install/install_dependecies.sh file as well ❗
RUN apk update --no-cache \
&& apk add --no-cache bash zip lsblk gettext-envsubst sudo mtr tzdata s6-overlay \
&& apk add --no-cache curl arp-scan iproute2 iproute2-ss nmap nmap-scripts traceroute nbtscan avahi avahi-tools openrc dbus net-tools net-snmp-tools bind-tools awake ca-certificates \
&& apk add --no-cache bash libbsd zip lsblk gettext-envsubst sudo mtr tzdata s6-overlay \
&& apk add --no-cache curl arp-scan iproute2 iproute2-ss nmap nmap-scripts traceroute nbtscan avahi avahi-tools openrc dbus net-tools net-snmp-tools bind-tools awake ca-certificates \
&& apk add --no-cache sqlite php83 php83-fpm php83-cgi php83-curl php83-sqlite3 php83-session \
&& apk add --no-cache python3 nginx \
&& apk add --no-cache dcron \
&& ln -s /usr/bin/awake /usr/bin/wakeonlan \
&& bash -c "install -d -m 750 -o nginx -g www-data ${INSTALL_DIR} ${INSTALL_DIR}" \
&& rm -f /etc/nginx/http.d/default.conf
@@ -66,12 +52,12 @@ RUN apk update --no-cache \
COPY --from=builder --chown=nginx:www-data ${INSTALL_DIR}/ ${INSTALL_DIR}/
# Add crontab file
COPY install/crontab /etc/crontabs/root
COPY --chmod=600 --chown=root:root install/crontab /etc/crontabs/root
# Start all required services
RUN ${INSTALL_DIR}/dockerfiles/pre-setup.sh
RUN ${INSTALL_DIR}/dockerfiles/start.sh
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=2 \
CMD curl -sf -o /dev/null ${LISTEN_ADDR}:${PORT}/api/app_state.json
CMD curl -sf -o /dev/null ${LISTEN_ADDR}:${PORT}/php/server/query_json.php?file=app_state.json
ENTRYPOINT ["/init"]

View File

@@ -43,7 +43,7 @@ RUN phpenmod -v 8.2 sqlite3
RUN apt-get install -y python3-venv
RUN python3 -m venv myenv
RUN /bin/bash -c "source myenv/bin/activate && update-alternatives --install /usr/bin/python python /usr/bin/python3 10 && pip3 install tplink-omada-client pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros "
RUN /bin/bash -c "source myenv/bin/activate && update-alternatives --install /usr/bin/python python /usr/bin/python3 10 && pip3 install openwrt-luci-rpc asusrouter asyncio aiohttp graphene flask tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros "
# Create a buildtimestamp.txt to later check if a new version was released
RUN date +%s > ${INSTALL_DIR}/front/buildtimestamp.txt

122
README.md
View File

@@ -1,104 +1,80 @@
[![GitHub Committed](https://img.shields.io/github/last-commit/jokob-sk/NetAlertX?color=40ba12&label=Pushed&logo=GitHub&logoColor=fff&style=for-the-badge)](https://github.com/jokob-sk/NetAlertX)
[![Docker Size](https://img.shields.io/docker/image-size/jokobsk/netalertx?label=Size&logo=Docker&color=0aa8d2&logoColor=fff&style=for-the-badge)](https://hub.docker.com/r/jokobsk/netalertx)
[![Docker Pulls](https://img.shields.io/docker/pulls/jokobsk/netalertx?label=Pulls&logo=docker&color=0aa8d2&logoColor=fff&style=for-the-badge)](https://hub.docker.com/r/jokobsk/netalertx)
[![GitHub Release](https://img.shields.io/github/v/release/jokob-sk/NetAlertX?color=0aa8d2&logoColor=fff&logo=GitHub&style=for-the-badge)](https://github.com/jokob-sk/NetAlertX/releases)
[![Discord](https://img.shields.io/discord/1274490466481602755?color=0aa8d2&logoColor=fff&logo=Discord&style=for-the-badge)](https://discord.gg/NczTUTWyRr)
[![Home Assistant](https://img.shields.io/badge/Repo-blue?logo=home-assistant&style=for-the-badge&color=0aa8d2&logoColor=fff&label=Add)](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Falexbelgium%2Fhassio-addons)
# 🖧🔍 Network scanner & notification framework
# NetAlertX - Network, presence scanner and alert framework
Get visibility of what's going on on your WIFI/LAN network. Schedule scans for devices, port changes and get alerts if unknown devices or changes are found. Write your own [Plugins](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins#readme) with auto-generated UI and in-build notification system. Build out and easily maintain your network source of truth (NSoT).
Get visibility of what's going on on your WIFI/LAN network and enable presence detection of important devices. Schedule scans for devices, port changes and get alerts if unknown devices or changes are found. Write your own [Plugins](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins#readme) with auto-generated UI and in-build notification system. Build out and easily maintain your network source of truth (NSoT).
| 🐳 [Docker hub](https://registry.hub.docker.com/r/jokobsk/netalertx) | 📑 [Docker guide](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md) |🆕 [Release notes](https://github.com/jokob-sk/NetAlertX/releases) | 📚 [All Docs](https://github.com/jokob-sk/NetAlertX/tree/main/docs) |
|----------------------|----------------------| ----------------------| ----------------------|
| [📑 Docker guide](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md) | [🚀 Releases](https://github.com/jokob-sk/NetAlertX/releases) | [📚 Docs](https://github.com/jokob-sk/NetAlertX/tree/main/docs) | [🔌 Plugins](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/README.md) | [🤖 Ask AI](https://gurubase.io/g/netalertx)
|----------------------| ----------------------| ----------------------| ----------------------| ----------------------|
| ![Main screen][main] | ![device_details 1][device_details] | ![Screen network][network] |
|----------------------|----------------------| ----------------------|
![network_setup][network_setup]
Head to [https://netalertx.com/](https://netalertx.com/) for more gifs and screenshots 📷.
![showcase][showcase]
<details>
<summary>📷 Click for more screenshots</summary>
| ![presence][presence] | ![maintenance][maintenance] | ![settings][settings] |
| ![Main screen][main] | ![device_details 1][device_details] | ![Screen network][network] |
|----------------------|----------------------|----------------------|
| ![presence][presence] | ![maintenance][maintenance] | ![settings][settings] |
| ![sync_hub][sync_hub] | ![report1][report1] | ![device_nmap][device_nmap] |
</details>
<details>
<summary>❓ Why use Net<b>Alert</b><sup>x</sup>?</summary>
<hr>
Most of us don't know what's going on on our home network, but we want our family and data to be safe. _Command-line tools_ are great, but the output can be _hard to understand_ and action if you are not a network specialist.
Net<b>Alert</b><sup>x</sup> gives you peace of mind. _Visualize and immediately report 📬_ what is going on in your network - this is the first step to enhance your _network security 🔐_.
Net<b>Alert</b><sup>x</sup> combines several network and other scanning tools 🔍 with notifications 📧 into one user-friendly package 📦.
Set up a _kill switch ☠_ for your network via a smart plug with the available [Home Assistant](https://github.com/jokob-sk/NetAlertX/blob/main/docs/HOME_ASSISTANT.md) integration. Implement custom automations with the [CSV device Exports 📤](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/csv_backup), [Webhooks](https://github.com/jokob-sk/NetAlertX/blob/main/docs/WEBHOOK_N8N.md), or [API endpoints](https://github.com/jokob-sk/NetAlertX/blob/main/docs/API.md) features.
Extend the app if you want to create your own scanner [Plugin](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins#readme) and handle the results and notifications in Net<b>Alert</b><sup>x</sup>.
Looking forward to your contributions if you decide to share your work with the community ❤.
Head to [https://netalertx.com/](https://netalertx.com/) for even more gifs and screenshots 📷.
</details>
## Scan Methods, Notifications, Integration, Extension system
## 📦 Features
| Features | Details |
|-------------|-------------|
| 🔍 | The app scans your network for, **New devices**, **New connections** (re-connections), **Disconnections**, **"Always Connected" devices down**, Devices **IP changes** and **Internet IP address changes**. Discovery & scan methods include: **arp-scan**, **Pi-hole - DB import**, **Pi-hole - DHCP leases import**, **Generic DHCP leases import**. **UNIFI controller import**, **SNMP-enabled router import**. Check the [Plugins](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins#readme) docs for more info on individual scans. |
|📧 | Send notifications to more than 80+ services, including Telegram via [Apprise](https://hub.docker.com/r/caronc/apprise), or use [Pushsafer](https://www.pushsafer.com/), [Pushover](https://www.pushover.net/), or [NTFY](https://ntfy.sh/). |
|🧩 | Feed your data and device changes into [Home Assistant](https://github.com/jokob-sk/NetAlertX/blob/main/docs/HOME_ASSISTANT.md), read [API endpoints](https://github.com/jokob-sk/NetAlertX/blob/main/docs/API.md), or use [Webhooks](https://github.com/jokob-sk/NetAlertX/blob/main/docs/WEBHOOK_N8N.md) to setup custom automation flows. |
| | Build your own scanners with the [Plugin system](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins#readme) |
### Scanners
The app scans your network for **New devices**, **New connections** (re-connections), **Disconnections**, **"Always Connected" devices down**, Devices **IP changes** and **Internet IP address changes**. Discovery & scan methods include: **arp-scan**, **Pi-hole - DB import**, **Pi-hole - DHCP leases import**, **Generic DHCP leases import**, **UNIFI controller import**, **SNMP-enabled router import**. Check the [Plugins](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins#readme) docs for a full lits of avaliable plugins.
### Notification gateways
Send notifications to more than 80+ services, including Telegram via [Apprise](https://hub.docker.com/r/caronc/apprise), or use native [Pushsafer](https://www.pushsafer.com/), [Pushover](https://www.pushover.net/), or [NTFY](https://ntfy.sh/) publishers.
### Integrations and Plugins
Feed your data and device changes into [Home Assistant](https://github.com/jokob-sk/NetAlertX/blob/main/docs/HOME_ASSISTANT.md), read [API endpoints](https://github.com/jokob-sk/NetAlertX/blob/main/docs/API.md), or use [Webhooks](https://github.com/jokob-sk/NetAlertX/blob/main/docs/WEBHOOK_N8N.md) to setup custom automation flows. You can also
build your own scanners with the [Plugin system](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins#readme) in as little as [15 minutes](https://www.youtube.com/watch?v=cdbxlwiWhv8).
## Installation & Documentation
## 📚 Documentation
<!--- --------------------------------------------------------------------- --->
Supported browsers: Chrome, Firefox
| Docs | Link |
|-------------|-------------|
| 📥🐳 | [Docker instructions](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md)
| 📥🗄️ | [HW install (experimental 🧪)](https://github.com/jokob-sk/NetAlertX/blob/main/docs/HW_INSTALL.md) |
| 📥🟧 | [Unraid App](https://unraid.net/community/apps) |
| 📚 | [All Documentation](https://github.com/jokob-sk/NetAlertX/blob/main/docs/README.md) (App Usage and Configuration) |
> Other Alternatives
>
> - Check out [leiweibau's on HW installed fork](https://github.com/leiweibau/Pi.Alert/) (maintained)
> - [WatchYourLAN](https://github.com/aceberg/WatchYourLAN) - Lightweight network IP scanner with web GUI (Open source)
> - [Fing](https://www.fing.com/) - Network scanner app for your Internet security (Commercial, Phone App, Proprietary hardware)
> - [NetBox](https://netboxlabs.com/) - Network management software (Commercial)
- [[Installation] Docker](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md)
- [[Installation] Home Assistant](https://github.com/alexbelgium/hassio-addons/tree/master/netalertx)
- [[Installation] Bare metal](https://github.com/jokob-sk/NetAlertX/blob/main/docs/HW_INSTALL.md)
- [[Installation] Unraid App](https://unraid.net/community/apps)
- [[Setup] Usage and Configuration](https://github.com/jokob-sk/NetAlertX/blob/main/docs/README.md)
- [[Development] API docs](https://github.com/jokob-sk/NetAlertX/blob/main/docs/API.md)
- [[Development] Custom Plugins](https://github.com/jokob-sk/NetAlertX/blob/main/docs/PLUGINS_DEV.md)
## 🔔 Get notified what's new
## 📃 Everything else
<!--- --------------------------------------------------------------------- --->
### 📧 Get notified what's new
Get notified about a new release, what new functionality you can use and about breaking changes.
![Follow and star][follow_star]
### ⭐ Sponsors
### 🔀 Other Alternative Apps
[![GitHub Sponsors](https://img.shields.io/github/sponsors/jokob-sk?style=social)](https://github.com/sponsors/jokob-sk)
Thank you to all the wonderful people who are sponsoring this project.
> preventing my burnout😅 are:
<!-- SPONSORS-LIST DO NOT MODIFY BELOW -->
| All Sponsors |
|---|
<!-- SPONSORS-LIST DO NOT MODIFY ABOVE -->
- [PiAlert by leiweibau](https://github.com/leiweibau/Pi.Alert/) (maintained, bare-metal install)
- [WatchYourLAN](https://github.com/aceberg/WatchYourLAN) - Lightweight network IP scanner with web GUI (Open source)
- [Fing](https://www.fing.com/) - Network scanner app for your Internet security (Commercial, Phone App, Proprietary hardware)
- [NetBox](https://netboxlabs.com/) - Network management software (Commercial)
### 💙 Donations
Thank you to everyone who appreciates this tool and donates.
<details>
<summary>Click for more ways to donate</summary>
@@ -115,26 +91,20 @@ Thank you to all the wonderful people who are sponsoring this project.
</details>
### 🙏Contributors
### 🏗 Contributors
This project would be nothing without the amazing work of the community, with special thanks to:
> [pucherot/Pi.Alert](https://github.com/pucherot/Pi.Alert) (the original creator of PiAlert), [leiweibau](https://github.com/leiweibau/Pi.Alert): Dark mode (and much more), [Macleykun](https://github.com/Macleykun) (Help with Dockerfile clean-up) [Final-Hawk](https://github.com/Final-Hawk) (Help with NTFY, styling and other fixes), [TeroRERO](https://github.com/terorero) (Spanish translations), [Data-Monkey](https://github.com/Data-Monkey), (Split-up of the python.py file and more), [cvc90](https://github.com/cvc90) (Spanish translation and various UI work) to name a few...
## Everything else
<!--- --------------------------------------------------------------------- --->
> [pucherot/Pi.Alert](https://github.com/pucherot/Pi.Alert) (the original creator of PiAlert), [leiweibau](https://github.com/leiweibau/Pi.Alert): Dark mode (and much more), [Macleykun](https://github.com/Macleykun) (Help with Dockerfile clean-up), [vladaurosh](https://github.com/vladaurosh) for Alpine re-base help, [Final-Hawk](https://github.com/Final-Hawk) (Help with NTFY, styling and other fixes), [TeroRERO](https://github.com/terorero) (Spanish translations), [Data-Monkey](https://github.com/Data-Monkey), (Split-up of the python.py file and more), [cvc90](https://github.com/cvc90) (Spanish translation and various UI work) to name a few. Check out all the [amazing contributors](https://github.com/jokob-sk/NetAlertX/graphs/contributors).
### 🌍 Translations
Proudly using [Weblate](https://hosted.weblate.org/projects/pialert/).
Proudly using [Weblate](https://hosted.weblate.org/projects/pialert/). Help out and suggest languages in the [online portal of Weblate](https://hosted.weblate.org/projects/pialert/core/).
<a href="https://hosted.weblate.org/engage/pialert/">
<img src="https://hosted.weblate.org/widget/pialert/core/multi-auto.svg" alt="Translation status" />
</a>
Help out and suggest languages in the [online portal of Weblate](https://hosted.weblate.org/projects/pialert/core/).
### License
> GPL 3.0 | [Read more here](LICENSE.txt) | Source of the [animated GIF (Loading Animation)](https://commons.wikimedia.org/wiki/File:Loading_Animation.gif) | Source of the [selfhosted Fonts](https://github.com/adobe-fonts/source-sans)
@@ -148,7 +118,7 @@ Help out and suggest languages in the [online portal of Weblate](https://hosted.
[maintenance]: ./docs/img/maintenance.png "Screen 4"
[network]: ./docs/img/network.png "Screen 5"
[settings]: ./docs/img/settings.png "Screen 6"
[network_setup]: ./docs/img/network_setup.gif "Screen 6"
[showcase]: ./docs/img/showcase.gif "Screen 6"
[help_faq]: ./docs/img/help_faq.png "Screen 7"
[sync_hub]: ./docs/img/sync_hub.png "Screen 8"
[notification_center]: ./docs/img/notification_center.png "Screen 8"

View File

@@ -17,9 +17,10 @@
# Scan multiple interfaces (eth1 and eth0):
# SCAN_SUBNETS = [ '192.168.1.0/24 --interface=eth1', '192.168.1.0/24 --interface=eth0' ]
DISCOVER_PLUGINS=True
SCAN_SUBNETS=['192.168.1.0/24 --interface=eth0']
TIMEZONE='Europe/Berlin'
LOADED_PLUGINS = ['ARPSCAN','CSVBCKP','DBCLNP', 'INTRNT','MAINT','NEWDEV','NSLOOKUP','NTFPRCS', 'AVAHISCAN', 'SETPWD','SMTP', 'SYNC', 'VNDRPDT', 'WORKFLOWS']
LOADED_PLUGINS = ['ARPSCAN','CSVBCKP','DBCLNP', 'INTRNT','MAINT','NEWDEV','NSLOOKUP','NTFPRCS', 'AVAHISCAN', 'SETPWD','SMTP', 'SYNC', 'VNDRPDT', 'WORKFLOWS', 'UI']
DAYS_TO_KEEP_EVENTS=90
# Used for generating links in emails. Make sure not to add a trailing slash!

View File

@@ -1,12 +1,13 @@
#!/bin/bash
export INSTALL_DIR=/app
LOG_FILE="${INSTALL_DIR}/front/log/execution_queue.log"
LOG_FILE="${INSTALL_DIR}/log/execution_queue.log"
# Check if there are any entries with cron_restart_backend
if grep -q "cron_restart_backend" "$LOG_FILE"; then
# Kill all python processes and restart the server
pkill -f "python " && (python ${INSTALL_DIR}/server > /dev/null 2>&1 &) && echo 'done'
# Restart python application using s6
s6-svc -r /var/run/s6-rc/servicedirs/netalertx
echo 'done'
# Remove all lines containing cron_restart_backend from the log file
sed -i '/cron_restart_backend/d' "$LOG_FILE"

View File

@@ -1,4 +1,3 @@
version: "3"
services:
netalertx:
privileged: true
@@ -16,7 +15,12 @@ services:
# - ${APP_DATA_LOCATION}/netalertx_dev/db:/app/db
- ${APP_DATA_LOCATION}/netalertx/db:/app/db
# (optional) useful for debugging if you have issues setting up the container
# - ${LOGS_LOCATION}:/app/front/log
- ${APP_DATA_LOCATION}/netalertx/log:/app/log
# (API: OPTION 1) use for performance
- type: tmpfs
target: /app/api
# (API: OPTION 2) use when debugging issues
# - ${DEV_LOCATION}/api:/app/api
# ---------------------------------------------------------------------------
# DELETE START anyone trying to use this file: comment out / delete BELOW lines, they are only for development purposes
- ${APP_DATA_LOCATION}/netalertx/dhcp_samples/dhcp1.leases:/mnt/dhcp1.leases
@@ -34,17 +38,14 @@ services:
- ${DEV_LOCATION}/back/update_vendors.sh:/app/back/update_vendors.sh
- ${DEV_LOCATION}/front/lib:/app/front/lib
- ${DEV_LOCATION}/front/js:/app/front/js
- ${DEV_LOCATION}/front/report_templates:/front/report_templates
- ${DEV_LOCATION}/install/start.debian.sh:/app/install/start.debian.sh
- ${DEV_LOCATION}/install/user-mapping.debian.sh:/app/install/user-mapping.debian.sh
- ${DEV_LOCATION}/install/install.debian.sh:/app/install/install.debian.sh
- ${DEV_LOCATION}/install/install_dependencies.debian.sh:/app/install/install_dependencies.debian.sh
- ${DEV_LOCATION}/front/api:/app/front/api
- ${DEV_LOCATION}/front/php:/app/front/php
- ${DEV_LOCATION}/front/deviceDetails.php:/app/front/deviceDetails.php
- ${DEV_LOCATION}/front/deviceDetailsEdit.php:/app/front/deviceDetailsEdit.php
- ${DEV_LOCATION}/front/userNotifications.php:/app/front/userNotifications.php
- ${DEV_LOCATION}/front/deviceDetailsTools.php:/app/front/deviceDetailsTools.php
- ${DEV_LOCATION}/front/deviceDetailsPresence.php:/app/front/deviceDetailsPresence.php
- ${DEV_LOCATION}/front/deviceDetailsSessions.php:/app/front/deviceDetailsSessions.php
- ${DEV_LOCATION}/front/deviceDetailsEvents.php:/app/front/deviceDetailsEvents.php
- ${DEV_LOCATION}/front/devices.php:/app/front/devices.php
- ${DEV_LOCATION}/front/events.php:/app/front/events.php
- ${DEV_LOCATION}/front/plugins.php:/app/front/plugins.php
@@ -56,19 +57,19 @@ services:
- ${DEV_LOCATION}/front/presence.php:/app/front/presence.php
- ${DEV_LOCATION}/front/settings.php:/app/front/settings.php
- ${DEV_LOCATION}/front/systeminfo.php:/app/front/systeminfo.php
- ${DEV_LOCATION}/front/account.php:/app/front/account.php
- ${DEV_LOCATION}/front/cloud_services.php:/app/front/cloud_services.php
- ${DEV_LOCATION}/front/report.php:/app/front/report.php
- ${DEV_LOCATION}/front/workflows.php:/app/front/workflows.php
- ${DEV_LOCATION}/front/appEventsCore.php:/app/front/appEventsCore.php
- ${DEV_LOCATION}/front/multiEditCore.php:/app/front/multiEditCore.php
- ${DEV_LOCATION}/front/donations.php:/app/front/donations.php
- ${DEV_LOCATION}/front/plugins:/app/front/plugins
# DELETE END anyone trying to use this file: comment out / delete ABOVE lines, they are only for development purposes
# ---------------------------------------------------------------------------
environment:
# - APP_CONF_OVERRIDE={"SCAN_SUBNETS":"['192.168.1.0/24 --interface=eth1']","UI_theme":"Dark"}
environment:
# - APP_CONF_OVERRIDE={"SCAN_SUBNETS":"['192.168.1.0/24 --interface=eth1']","GRAPHQL_PORT":"20223","UI_theme":"Light"}
- TZ=${TZ}
- PORT=${PORT}
# ❗ DANGER ZONE BELOW - Setting ALWAYS_FRESH_INSTALL=true will delete the content of the /db & /config folders
- ALWAYS_FRESH_INSTALL=${ALWAYS_FRESH_INSTALL}
# - LOADED_PLUGINS=["DHCPLSS","PIHOLE","ASUSWRT","FREEBOX"]

View File

@@ -1,14 +1,13 @@
[![GitHub Committed](https://img.shields.io/github/last-commit/jokob-sk/NetAlertX?color=40ba12&label=Committed&logo=GitHub&logoColor=fff&style=for-the-badge)](https://github.com/jokob-sk/NetAlertX)
[![Docker Size](https://img.shields.io/docker/image-size/jokobsk/netalertx?label=Size&logo=Docker&color=0aa8d2&logoColor=fff&style=for-the-badge)](https://hub.docker.com/r/jokobsk/netalertx)
[![Docker Pulls](https://img.shields.io/docker/pulls/jokobsk/netalertx?label=Pulls&logo=docker&color=0aa8d2&logoColor=fff&style=for-the-badge)](https://hub.docker.com/r/jokobsk/netalertx)
[![GitHub Release](https://img.shields.io/github/v/release/jokob-sk/NetAlertX?color=0aa8d2&logoColor=fff&logo=GitHub&style=for-the-badge)](https://github.com/jokob-sk/NetAlertX/releases)
[![Discord](https://img.shields.io/discord/1274490466481602755?color=0aa8d2&logoColor=fff&logo=Discord&style=for-the-badge)](https://discord.gg/NczTUTWyRr)
[![Home Assistant](https://img.shields.io/badge/Repo-blue?logo=home-assistant&style=for-the-badge&color=0aa8d2&logoColor=fff&label=Add)](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Falexbelgium%2Fhassio-addons)
# NetAlertX - Network scanner & notification framework
# NetAlertX 🖧🔍 Network scanner & notification framework
| 🐳 [Docker hub](https://registry.hub.docker.com/r/jokobsk/netalertx) | 📑 [Docker guide](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md) |🆕 [Release notes](https://github.com/jokob-sk/NetAlertX/releases) | 📚 [All Docs](https://github.com/jokob-sk/NetAlertX/tree/main/docs) |
|----------------------|----------------------| ----------------------| ----------------------|
| [📑 Docker guide](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md) | [🚀 Releases](https://github.com/jokob-sk/NetAlertX/releases) | [📚 Docs](https://github.com/jokob-sk/NetAlertX/tree/main/docs) | [🔌 Plugins](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/README.md) | [🤖 Ask AI](https://gurubase.io/g/netalertx)
|----------------------| ----------------------| ----------------------| ----------------------| ----------------------|
<a href="https://raw.githubusercontent.com/jokob-sk/NetAlertX/main/docs/img/GENERAL/github_social_image.jpg" target="_blank">
<img src="https://raw.githubusercontent.com/jokob-sk/NetAlertX/main/docs/img/GENERAL/github_social_image.jpg" width="1000px" />
@@ -22,29 +21,35 @@ Head to [https://netalertx.com/](https://netalertx.com/) for more gifs and scree
## 📕 Basic Usage
> [!WARNING]
> You will have to run the container on the `host` network.
> You will have to run the container on the `host` network and specify `SCAN_SUBNETS` unless you use other [plugin scanners](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/README.md). The initial scan can take a few minutes, so please wait 5-10 minutes for the initial discovery to finish.
```yaml
docker run -d --rm --network=host \
-v local/path/config:/app/config \
-v local/path/db:/app/db \
-v local_path/config:/app/config \
-v local_path/db:/app/db \
--mount type=tmpfs,target=/app/api \
-e PUID=200 -e PGID=300 \
-e TZ=Europe/Berlin \
-e PORT=20211 \
jokobsk/netalertx:latest
```
- The initial scan can take up to 15min (with 50 devices and MQTT). Subsequent ones 3 and 5 minutes so wait that long for all of the scans to run.
See alternative [docked-compose examples](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DOCKER_COMPOSE.md).
### Docker environment variables
| Variable | Description | Default |
| :------------- |:-------------| -----:|
| Variable | Description | Example Value |
| :------------- |:------------------------| -----:|
| `PORT` |Port of the web interface | `20211` |
| `PUID` |Application User UID | `102` |
| `PGID` |Application User GID | `82` |
| `LISTEN_ADDR` |Set the specific IP Address for the listener address for the nginx webserver (web interface). This could be useful when using multiple subnets to hide the web interface from all untrusted networks. | `0.0.0.0` |
|`TZ` |Time zone to display stats correctly. Find your time zone [here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) | `Europe/Berlin` |
|`APP_CONF_OVERRIDE` | JSON override for settings, e.g. `{"SCAN_SUBNETS":"['192.168.1.0/24 --interface=eth1']","UI_theme":"Dark"}` (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` |
|`LOADED_PLUGINS` | Default [plugins](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/README.md) to load. Plugins cannot be loaded with `APP_CONF_OVERRIDE`, you need to use this variable instead and then specify the plugins settings with `APP_CONF_OVERRIDE`. | `["PIHOLE","ASUSWRT"]` |
|`APP_CONF_OVERRIDE` | JSON override for settings (except `LOADED_PLUGINS`). | `{"SCAN_SUBNETS":"['192.168.1.0/24 --interface=eth1']","GRAPHQL_PORT":"20212"}` |
|`ALWAYS_FRESH_INSTALL` | ⚠ If `true` will delete the content of the `/db` & `/config` folders. For testing purposes. Can be coupled with [watchtower](https://github.com/containrrr/watchtower) to have an always freshly installed `netalertx`/`netalertx-dev` image. | `true` |
> You can override the default GraphQL port setting `GRAPHQL_PORT` (set to `20212`) by using the `APP_CONF_OVERRIDE` env variable.
> You can override the default GraphQL port setting `GRAPHQL_PORT` (set to `20212`) by using the `APP_CONF_OVERRIDE` env variable. `LOADED_PLUGINS` and settings in `APP_CONF_OVERRIDE` can be specified via the UI as well.
### Docker paths
@@ -53,185 +58,41 @@ docker run -d --rm --network=host \
| Required | Path | Description |
| :------------- | :------------- | :-------------|
| ✅ | `:/app/config` | Folder which will contain the `app.conf` & `devices.csv` ([read about devices.csv](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICES_BULK_EDITING.md)) files (see below for details). |
| ✅ | `:/app/db` | Folder which will contain the `app.db` file |
| | `:/app/front/log` | Logs folder useful for debugging if you have issues setting up the container |
| | `:/etc/pihole/pihole-FTL.db` | PiHole's `pihole-FTL.db` database file. Required if you want to use PiHole DB mapping. |
| | `:/etc/pihole/dhcp.leases` | PiHole's `dhcp.leases` file. Required if you want to use PiHole `dhcp.leases` file. This has to be matched with a corresponding `DHCPLSS_paths_to_check` setting entry (the path in the container must contain `pihole`)|
| | `:/app/front/api` | A simple [API endpoint](https://github.com/jokob-sk/NetAlertX/blob/main/docs/API.md) containing static (but regularly updated) json and other files. |
| ✅ | `:/app/config` | Folder which will contain the `app.conf` & `devices.csv` ([read about devices.csv](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICES_BULK_EDITING.md)) files |
| ✅ | `:/app/db` | Folder which will contain the `app.db` database file |
| | `:/app/log` | Logs folder useful for debugging if you have issues setting up the container |
| | `:/app/api` | A simple [API endpoint](https://github.com/jokob-sk/NetAlertX/blob/main/docs/API.md) containing static (but regularly updated) json and other files. |
| | `:/app/front/plugins/<plugin>/ignore_plugin` | Map a file `ignore_plugin` to ignore a plugin. Plugins can be soft-disabled via settings. More in the [Plugin docs](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/README.md). |
| | `:/etc/resolv.conf` | Use a custom `resolv.conf` file for [better name resolution](https://github.com/jokob-sk/NetAlertX/blob/main/docs/REVERSE_DNS.md). |
> Use separate `db` and `config` directories, don't nest them.
> Use separate `db` and `config` directories, do not nest them.
### (If UI is not available) Modify the config (`app.conf`)
### Initial setup
- The preferred way is to manage the configuration via the Settings section in the UI.
- You can modify [app.conf](https://github.com/jokob-sk/NetAlertX/tree/main/config) directly, if needed.
- If unavailable, the app generates a default `app.conf` and `app.db` file on the first run.
- The preferred way is to manage the configuration via the Settings section in the UI, if UI is inaccessible you can modify [app.conf](https://github.com/jokob-sk/NetAlertX/tree/main/back) in the `/app/config/` folder directly
### ⚙ Important settings
#### Setting up scanners
These are the most important settings to get at least some output in your Devices screen. Usually, only one approach is used, but you can combine these approaches.
| Scan method | Setting | Description |
| :------------- | :------------- | :-------------|
| arp-scan, nmap-scan | `SCAN_SUBNETS` | See the documentation on how [to setup SUBNETS, VLANs & limitations](https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md) |
| PiHole | `PIHOLE_RUN` | There are 2 approaches how to get PiHole devices imported. Via the PiHole import (`PIHOLE`) plugin or DHCP leases (`DHCPLSS`) plugin. The `PIHOLE` plugin requires you to map the PiHole database, as mentioned above. |
| dhcp.leases | `DHCPLSS_RUN` | You need to map `:/etc/myfiles/dhcp.leases` in the `docker-compose.yml` file if you enable this setting. This path has to be matched with a corresponding `DHCPLSS_paths_to_check` setting entry (check the [DHCPLSS plugin readme](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/dhcp_leases#overview) for details). |
You have to specify which network(s) should be scanned. This is done by entering subnets that are accessible from the host. If you use the default `ARPSCAN` plugin, you have to specify at least one valid subnet and interface in the `SCAN_SUBNETS` setting. See the documentation on [How to set up multiple SUBNETS, VLANs and what are limitations](https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md) for troubleshooting and more advanced scenarios.
If you are running PiHole you can synchronize devices directly. Check the [PiHole configuration guide](https://github.com/jokob-sk/NetAlertX/blob/main/docs/PIHOLE_GUIDE.md) for details.
> [!NOTE]
> It's recommended to use the same schedule interval for all plugins responsible for discovering new devices.
> You can bulk-import devices via the [CSV import method](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICES_BULK_EDITING.md).
#### Community guides
#### 🧭 Community guides
You can read or watch several [community configuration guides](https://github.com/jokob-sk/NetAlertX/blob/main/docs/COMMUNITY_GUIDES.md) in Chinese, Korean, German, or French.
Use the official installation guides at first and use community content as supplementary material. Open an issue if you'd like to add your link to the list 🙏
- ▶ [Home Lab Network Monitoring - Scotti-BYTE Enterprise Consulting Services](https://www.youtube.com/watch?v=0DryhzrQSJA) (July 2024)
- 📄 [How to Install NetAlertX on Your Synology NAS - Marius hosting](https://mariushosting.com/how-to-install-pi-alert-on-your-synology-nas/) (Updated frequently)
- 📄 [Using the PiAlert Network Security Scanner on a Raspberry Pi - PiMyLifeUp](https://pimylifeup.com/raspberry-pi-pialert/)
- ▶ [How to Setup Pi.Alert on Your Synology NAS - Digital Aloha](https://www.youtube.com/watch?v=M4YhpuRFaUg)
- 📄 [防蹭网神器,网络安全助手 | 极空间部署网络扫描和通知系统『NetAlertX』](https://blog.csdn.net/qq_63499861/article/details/141105273)
- 📄 [시놀/헤놀에서 네트워크 스캐너 Pi.Alert Docker로 설치 및 사용하기](https://blog.dalso.org/article/%EC%8B%9C%EB%86%80-%ED%97%A4%EB%86%80%EC%97%90%EC%84%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%8A%A4%EC%BA%90%EB%84%88-pi-alert-docker%EB%A1%9C-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EC%82%AC%EC%9A%A9) (July 2023)
- 📄 [网络入侵探测器Pi.Alert (Chinese)](https://codeantenna.com/a/VgUvIAjZ7J) (May 2023)
- ▶ [Pi.Alert auf Synology & Docker by - Jürgen Barth](https://www.youtube.com/watch?v=-ouvA2UNu-A) (March 2023)
- ▶ [Top Docker Container for Home Server Security - VirtualizationHowto](https://www.youtube.com/watch?v=tY-w-enLF6Q) (March 2023)
- ▶ [Pi.Alert or WatchYourLAN can alert you to unknown devices appearing on your WiFi or LAN network - Danie van der Merwe](https://www.youtube.com/watch?v=v6an9QG2xF0) (November 2022)
> Ordered by last update time.
> Please note these might be outdated. Rely on official documentation first.
### **Common issues**
#### Common issues
💡 Before creating a new issue, please check if a similar issue was [already resolved](https://github.com/jokob-sk/NetAlertX/issues?q=is%3Aissue+is%3Aclosed).
- Before creating a new issue, please check if a similar issue was [already resolved](https://github.com/jokob-sk/NetAlertX/issues?q=is%3Aissue+is%3Aclosed).
- Check also common issues and [debugging tips](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEBUG_TIPS.md).
⚠ Check also common issues and [debugging tips](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEBUG_TIPS.md).
> [!NOTE]
> You can bulk-update devices via the [CSV import method](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICES_BULK_EDITING.md).
## 📄 docker-compose.yml Examples
### Example 1
```yaml
version: "3"
services:
netalertx:
container_name: netalertx
# use the below line if you want to test the latest dev image
# image: "jokobsk/netalertx-dev:latest"
image: "jokobsk/netalertx:latest"
network_mode: "host"
restart: unless-stopped
volumes:
- local/path/config:/app/config
- local/path/db:/app/db
# (optional) useful for debugging if you have issues setting up the container
- local/path/logs:/app/front/log
environment:
- TZ=Europe/Berlin
- PORT=20211
```
To run the container execute: `sudo docker-compose up -d`
### Example 2
Example by [SeimuS](https://github.com/SeimusS).
```yaml
netalertx:
container_name: NetAlertX
hostname: NetAlertX
privileged: true
# use the below line if you want to test the latest dev image
# image: "jokobsk/netalertx-dev:latest"
image: jokobsk/netalertx:latest
environment:
- TZ=Europe/Bratislava
restart: always
volumes:
- ./netalertx/db:/app/db
- ./netalertx/config:/app/config
network_mode: host
```
To run the container execute: `sudo docker-compose up -d`
### Example 3
`docker-compose.yml`
```yaml
version: "3"
services:
netalertx:
container_name: netalertx
# use the below line if you want to test the latest dev image
# image: "jokobsk/netalertx-dev:latest"
image: "jokobsk/netalertx:latest"
network_mode: "host"
restart: unless-stopped
volumes:
- ${APP_DATA_LOCATION}/netalertx/config:/app/config
- ${APP_DATA_LOCATION}/netalertx/db/:/app/db/
# (optional) useful for debugging if you have issues setting up the container
- ${LOGS_LOCATION}:/app/front/log
environment:
- TZ=${TZ}
- PORT=${PORT}
```
`.env` file
```yaml
#GLOBAL PATH VARIABLES
APP_DATA_LOCATION=/path/to/docker_appdata
APP_CONFIG_LOCATION=/path/to/docker_config
LOGS_LOCATION=/path/to/docker_logs
#ENVIRONMENT VARIABLES
TZ=Europe/Paris
PORT=20211
#DEVELOPMENT VARIABLES
DEV_LOCATION=/path/to/local/source/code
```
To run the container execute: `sudo docker-compose --env-file /path/to/.env up`
### Example 4
Courtesy of [pbek](https://github.com/pbek). The volume `netalertx_db` is used by the db directory. The two config files are mounted directly from a local folder to their places in the config folder. You can backup the `docker-compose.yaml` folder and the docker volumes folder.
```yaml
netalertx:
# use the below line if you want to test the latest dev image
# image: "jokobsk/netalertx-dev:latest"
image: jokobsk/netalertx
ports:
- "80:20211/tcp"
environment:
- TZ=Europe/Vienna
networks:
local:
ipv4_address: 192.168.1.2
restart: unless-stopped
volumes:
- netalertx_db:/app/db
- ./netalertx/:/app/config/
```
## 🏅 Recognitions
Big thanks to <a href="https://github.com/Macleykun">@Macleykun</a> & for help and tips & tricks for Dockerfile(s) and <a href="https://github.com/vladaurosh">@vladaurosh</a> for Alpine re-base help.
## ❤ Support me
## 💙 Support me
| [![GitHub](https://i.imgur.com/emsRCPh.png)](https://github.com/sponsors/jokob-sk) | [![Buy Me A Coffee](https://i.imgur.com/pIM6YXL.png)](https://www.buymeacoffee.com/jokobsk) | [![Patreon](https://i.imgur.com/MuYsrq1.png)](https://www.patreon.com/user?u=84385063) |
| --- | --- | --- |
@@ -239,4 +100,4 @@ Big thanks to <a href="https://github.com/Macleykun">@Macleykun</a> & for help a
- Bitcoin: `1N8tupjeCK12qRVU2XrV17WvKK7LCawyZM`
- Ethereum: `0x6e2749Cb42F4411bc98501406BdcD82244e3f9C7`
> 📧 Email me at [jokob@duck.com](mailto:jokob@duck.com?subject=NetAlertX) if you want to get in touch or if I should add other sponsorship platforms.
> 📧 Email me at [netalertx@gmail.com](mailto:netalertx@gmail.com?subject=NetAlertX Donations) if you want to get in touch or if I should add other sponsorship platforms.

View File

@@ -1,8 +1,36 @@
#!/usr/bin/with-contenv bash
echo "---------------------------------------------------------"
echo "[INSTALL] Run setup.sh"
echo "---------------------------------------------------------"
echo "---------------------------------------------------------
[INSTALL] Run init.sh
---------------------------------------------------------"
DEFAULT_PUID=102
DEFAULT_GID=82
PUID=${PUID:-${DEFAULT_PUID}}
PGID=${PGID:-${DEFAULT_GID}}
echo "[INSTALL] Setting up user UID and GID"
if ! groupmod -o -g "$PGID" www-data && [ "$PGID" != "$DEFAULT_GID" ] ; then
echo "Failed to set user GID to ${PGID}, trying with default GID ${DEFAULT_GID}"
groupmod -o -g "$DEFAULT_GID" www-data
fi
if ! usermod -o -u "$PUID" nginx && [ "$PUID" != "$DEFAULT_PUID" ] ; then
echo "Failed to set user UID to ${PUID}, trying with default PUID ${DEFAULT_PUID}"
usermod -o -u "$DEFAULT_PUID" nginx
fi
echo "
---------------------------------------------------------
GID/UID
---------------------------------------------------------
User UID: $(id -u nginx)
User GID: $(getent group www-data | cut -d: -f3)
---------------------------------------------------------"
chown nginx:nginx /run/nginx/ /var/log/nginx/ /var/lib/nginx/ /var/lib/nginx/tmp/
chgrp www-data /var/www/localhost/htdocs/
export INSTALL_DIR=/app # Specify the installation directory here
@@ -55,7 +83,7 @@ else
echo "Config file saved to ${INSTALL_DIR}/config/app_conf_override.json"
fi
# 🔻 FOR BACKWARD COMPATIBILITY - REMOVE AFTER 12/12/2024
# 🔻 FOR BACKWARD COMPATIBILITY - REMOVE AFTER 12/12/2025
# Check if pialert.db exists, then create a symbolic link to app.db
if [ -f "${INSTALL_DIR_OLD}/db/${OLD_APP_NAME}.db" ]; then
@@ -66,7 +94,7 @@ fi
if [ -f "${INSTALL_DIR_OLD}/config/${OLD_APP_NAME}.conf" ]; then
ln -s "${INSTALL_DIR_OLD}/config/${OLD_APP_NAME}.conf" "${INSTALL_DIR}/config/${CONF_FILE}"
fi
# 🔺 FOR BACKWARD COMPATIBILITY - REMOVE AFTER 12/12/2024
# 🔺 FOR BACKWARD COMPATIBILITY - REMOVE AFTER 12/12/2025
# Copy starter .db and .conf if they don't exist
cp -na "${INSTALL_DIR}/back/${CONF_FILE}" "${INSTALL_DIR}/config/${CONF_FILE}"
@@ -83,6 +111,13 @@ if [ -n "${TZ}" ]; then
echo $TZ > /etc/timezone
fi
# if custom variables not set we do not need to do anything
if [ -n "${LOADED_PLUGINS}" ]; then
FILECONF="${INSTALL_DIR}/config/${CONF_FILE}"
echo "[INSTALL] Setup custom LOADED_PLUGINS variable"
sed -i "\#^LOADED_PLUGINS=#c\LOADED_PLUGINS=${LOADED_PLUGINS}" "${FILECONF}"
fi
echo "[INSTALL] Setup NGINX"
echo "Setting webserver to address ($LISTEN_ADDR) and port ($PORT)"
envsubst '$INSTALL_DIR $LISTEN_ADDR $PORT' < "${INSTALL_DIR}/install/netalertx.template.conf" > "${NGINX_CONFIG_FILE}"
@@ -106,15 +141,16 @@ fi
# Create an empty log files
# Create the execution_queue.log and app_front.log files if they don't exist
touch "${INSTALL_DIR}"/front/log/{app.log,execution_queue.log,app_front.log,app.php_errors.log,stderr.log,stdout.log,db_is_locked.log}
touch "${INSTALL_DIR}"/front/api/user_notifications.json
touch "${INSTALL_DIR}"/log/{app.log,execution_queue.log,app_front.log,app.php_errors.log,stderr.log,stdout.log,db_is_locked.log}
touch "${INSTALL_DIR}"/api/user_notifications.json
# Create plugins sub-directory if it doesn't exist in case a custom log folder is used
mkdir -p "${INSTALL_DIR}"/log/plugins
echo "[INSTALL] Fixing permissions after copied starter config & DB"
chown -R nginx:www-data "${INSTALL_DIR}"/{config,front/log,db,front/api}
chown -R nginx:www-data "${INSTALL_DIR}"/front/api/user_notifications.json
chown -R nginx:www-data "${INSTALL_DIR}"
chmod 750 "${INSTALL_DIR}"/{config,front/log,db}
find "${INSTALL_DIR}"/{config,front/log,db} -type f -exec chmod 640 {} \;
chmod 750 "${INSTALL_DIR}"/{config,log,db}
find "${INSTALL_DIR}"/{config,log,db} -type f -exec chmod 640 {} \;
# Check if buildtimestamp.txt doesn't exist
if [ ! -f "${INSTALL_DIR}/front/buildtimestamp.txt" ]; then
@@ -123,10 +159,6 @@ if [ ! -f "${INSTALL_DIR}/front/buildtimestamp.txt" ]; then
chown nginx:www-data "${INSTALL_DIR}/front/buildtimestamp.txt"
fi
# Start crond service in the background
echo "[INSTALL] Starting crond service..."
crond -f -d 8 > /dev/null 2>&1 &
echo -e "
[ENV] PATH is ${PATH}
[ENV] PORT is ${PORT}

View File

@@ -13,13 +13,21 @@ sed -i "/^user/c\user = nginx" /etc/php83/php-fpm.d/www.conf
sed -i "/^group/c\group = www-data" /etc/php83/php-fpm.d/www.conf
# s6 overlay setup
mkdir -p /etc/s6-overlay/s6-rc.d/{SetupOneshot,php-fpm/dependencies.d,nginx/dependencies.d}
mkdir -p /etc/s6-overlay/s6-rc.d/{SetupOneshot,php-fpm/dependencies.d,nginx/dependencies.d,$APP_NAME/dependencies.d}
mkdir -p /etc/s6-overlay/s6-rc.d/{SetupOneshot,crond/dependencies.d,php-fpm/dependencies.d,nginx/dependencies.d,$APP_NAME/dependencies.d}
echo "oneshot" > /etc/s6-overlay/s6-rc.d/SetupOneshot/type
echo "longrun" > /etc/s6-overlay/s6-rc.d/crond/type
echo "longrun" > /etc/s6-overlay/s6-rc.d/php-fpm/type
echo "longrun" > /etc/s6-overlay/s6-rc.d/nginx/type
echo "longrun" > /etc/s6-overlay/s6-rc.d/$APP_NAME/type
echo -e "${INSTALL_DIR}/dockerfiles/setup.sh" > /etc/s6-overlay/s6-rc.d/SetupOneshot/up
echo -e "${INSTALL_DIR}/dockerfiles/init.sh" > /etc/s6-overlay/s6-rc.d/SetupOneshot/up
echo -e '#!/bin/execlineb -P
if { echo
"
[INSTALL] Starting crond service...
" }' > /etc/s6-overlay/s6-rc.d/crond/run
echo -e "/usr/sbin/crond -f" >> /etc/s6-overlay/s6-rc.d/crond/run
echo -e "#!/bin/execlineb -P\n/usr/sbin/php-fpm83 -F" > /etc/s6-overlay/s6-rc.d/php-fpm/run
echo -e '#!/bin/execlineb -P\nnginx -g "daemon off;"' > /etc/s6-overlay/s6-rc.d/nginx/run
echo -e '#!/bin/execlineb -P
@@ -33,11 +41,9 @@ echo -e '#!/bin/execlineb -P
" }' > /etc/s6-overlay/s6-rc.d/$APP_NAME/run
echo -e "python ${INSTALL_DIR}/server" >> /etc/s6-overlay/s6-rc.d/$APP_NAME/run
touch /etc/s6-overlay/s6-rc.d/user/contents.d/{SetupOneshot,php-fpm,nginx} /etc/s6-overlay/s6-rc.d/{php-fpm,nginx}/dependencies.d/SetupOneshot
touch /etc/s6-overlay/s6-rc.d/user/contents.d/{SetupOneshot,php-fpm,nginx,$APP_NAME} /etc/s6-overlay/s6-rc.d/{php-fpm,nginx,$APP_NAME}/dependencies.d/SetupOneshot
touch /etc/s6-overlay/s6-rc.d/user/contents.d/{SetupOneshot,crond,php-fpm,nginx,$APP_NAME} /etc/s6-overlay/s6-rc.d/{crond,php-fpm,nginx,$APP_NAME}/dependencies.d/SetupOneshot
touch /etc/s6-overlay/s6-rc.d/nginx/dependencies.d/php-fpm
touch /etc/s6-overlay/s6-rc.d/$APP_NAME/dependencies.d/nginx
# this removes the current file
rm -f $0

View File

@@ -1,7 +1,134 @@
## API endpoints
# API endpoints
NetAlertX comes with a simple API. These API endpoints are static files, that are periodically updated based on your settings.
NetAlertX comes with a couple of API endpoints. All requests need to be authorized (executed in a logged in browser session) or you have to pass the value of the `API_TOKEN` settings as authorization bearer, for example:
```graphql
curl 'http://host:GRAPHQL_PORT/graphql' \
-X POST \
-H 'Authorization: Bearer API_TOKEN' \
-H 'Content-Type: application/json' \
--data '{
"query": "query GetDevices($options: PageQueryOptionsInput) { devices(options: $options) { devices { rowid devMac devName devOwner devType devVendor devLastConnection devStatus } count } }",
"variables": {
"options": {
"page": 1,
"limit": 10,
"sort": [{ "field": "devName", "order": "asc" }],
"search": "",
"status": "connected"
}
}
}'
```
## API Endpoint: GraphQL
Endpoint URL: `php/server/query_graphql.php`
Host: `same as front end (web ui)`
Port: `20212` or as defined by the `GRAPHQL_PORT` setting
### Example Query to Fetch Devices
First, let's define the GraphQL query to fetch devices with pagination and sorting options.
```graphql
query GetDevices($options: PageQueryOptionsInput) {
devices(options: $options) {
devices {
rowid
devMac
devName
devOwner
devType
devVendor
devLastConnection
devStatus
}
count
}
}
```
### `curl` Command
You can use the following `curl` command to execute the query.
```sh
curl 'http://host:GRAPHQL_PORT/graphql' -X POST -H 'Authorization: Bearer API_TOKEN' -H 'Content-Type: application/json' --data '{
"query": "query GetDevices($options: PageQueryOptionsInput) { devices(options: $options) { devices { rowid devMac devName devOwner devType devVendor devLastConnection devStatus } count } }",
"variables": {
"options": {
"page": 1,
"limit": 10,
"sort": [{ "field": "devName", "order": "asc" }],
"search": "",
"status": "connected"
}
}
}'
```
### Explanation:
1. **GraphQL Query**:
- The `query` parameter contains the GraphQL query as a string.
- The `variables` parameter contains the input variables for the query.
2. **Query Variables**:
- `page`: Specifies the page number of results to fetch.
- `limit`: Specifies the number of results per page.
- `sort`: Specifies the sorting options, with `field` being the field to sort by and `order` being the sort order (`asc` for ascending or `desc` for descending).
- `search`: A search term to filter the devices.
- `status`: The status filter to apply (valid values are `my_devices` (determined by the `UI_MY_DEVICES` setting), `connected`, `favorites`, `new`, `down`, `archived`, `offline`).
3. **`curl` Command**:
- The `-X POST` option specifies that we are making a POST request.
- The `-H "Content-Type: application/json"` option sets the content type of the request to JSON.
- The `-d` option provides the request payload, which includes the GraphQL query and variables.
### Sample Response
The response will be in JSON format, similar to the following:
```json
{
"data": {
"devices": {
"devices": [
{
"rowid": 1,
"devMac": "00:11:22:33:44:55",
"devName": "Device 1",
"devOwner": "Owner 1",
"devType": "Type 1",
"devVendor": "Vendor 1",
"devLastConnection": "2025-01-01T00:00:00Z",
"devStatus": "connected"
},
{
"rowid": 2,
"devMac": "66:77:88:99:AA:BB",
"devName": "Device 2",
"devOwner": "Owner 2",
"devType": "Type 2",
"devVendor": "Vendor 2",
"devLastConnection": "2025-01-02T00:00:00Z",
"devStatus": "connected"
}
],
"count": 2
}
}
}
```
## API Endpoint: JSON files
This API endpoint retrieves static files, that are periodically updated.
Endpoint URL: `php/server/query_json.php?file=<file name>`
Host: `same as front end (web ui)`
Port: `20211` or as defined by the $PORT docker environment variable (same as the port for the web ui)
### When are the endpoints updated
@@ -9,7 +136,7 @@ The endpoints are updated when objects in the API endpoints are changed.
### Location of the endpoints
In the container, these files are located under the `/app/front/api/` folder and thus on the `<netalertx_url>/api/<File name>` url.
In the container, these files are located under the `/app/api/` folder. You can access them via the `/php/server/query_json.php?file=user_notifications.json` endpoint.
### Available endpoints
@@ -17,10 +144,8 @@ You can access the following files:
| File name | Description |
|----------------------|----------------------|
| `notification_text.txt` | The plain text version of the last notification. |
| `notification_text.html` | The full HTML of the last email notification. |
| `notification_json_final.json` | The json version of the last notification (e.g. used for webhooks - [sample JSON](https://github.com/jokob-sk/NetAlertX/blob/main/front/report_templates/webhook_json_sample.json)). |
| `table_devices.json` | The current (at the time of the last update as mentioned above on this page) state of all of the available Devices detected by the app. |
| `table_devices.json` | All of the available Devices detected by the app. |
| `table_plugins_events.json` | The list of the unprocessed (pending) notification events (plugins_events DB table). |
| `table_plugins_history.json` | The list of notification events history. |
| `table_plugins_objects.json` | The content of the plugins_objects table. Find more info on the [Plugin system here](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins)|
@@ -29,7 +154,6 @@ You can access the following files:
| `table_settings.json` | The content of the settings table. |
| `app_state.json` | Contains the current application state. |
Current/latest state of the aforementioned files depends on your settings.
### JSON Data format
@@ -95,3 +219,41 @@ Example JSON of the `table_devices.json` endpoint with two Devices (database row
```
## API Endpoint: /log files
This API endpoint retrieves files from the `/app/log` folder.
Endpoint URL: `php/server/query_logs.php?file=<file name>`
Host: `same as front end (web ui)`
Port: `20211` or as defined by the $PORT docker environment variable (same as the port for the web ui)
| File | Description |
|--------------------------|---------------------------------------------------------------|
| `IP_changes.log` | Logs of IP address changes |
| `app.log` | Main application log |
| `app.php_errors.log` | PHP error log |
| `app_front.log` | Frontend application log |
| `app_nmap.log` | Logs of Nmap scan results |
| `db_is_locked.log` | Logs when the database is locked |
| `execution_queue.log` | Logs of execution queue activities |
| `plugins/` | Directory for temporary plugin-related files (not accessible) |
| `report_output.html` | HTML report output |
| `report_output.json` | JSON format report output |
| `report_output.txt` | Text format report output |
| `stderr.log` | Logs of standard error output |
| `stdout.log` | Logs of standard output |
## API Endpoint: /config files
To retrieve files from the `/app/config` folder.
Endpoint URL: `php/server/query_config.php?file=<file name>`
Host: `same as front end (web ui)`
Port: `20211` or as defined by the $PORT docker environment variable (same as the port for the web ui)
| File | Description |
|--------------------------|--------------------------------------------------|
| `devices.csv` | Devices csv file |
| `app.conf` | Application config file |

View File

@@ -1,7 +1,8 @@
# 💾 Backing things up
> [!NOTE]
> To backup 99% of your configuration backup at least the `/config` folder. Please read the whole page (or at least "Scenario 2: Corrupted database") for details.
> To backup 99% of your configuration backup at least the `/app/config` folder. Please read the whole page (or at least "Scenario 2: Corrupted database") for details.
> Please also note that database definitions might change over versions. The safest way is to restore your older backups into the **same version** of the app and then gradually upgarde between releases to the latest version.
There are 3 artifacts that can be used to backup the application:
@@ -22,16 +23,17 @@ The core application configuration is in the `app.conf` file (See [Settings Syst
- Notification settings
- Scanner settings
- Scheduled maintenance settings
- UI configuration (80%)
- UI configuration
### Core Device Data
The core device data is backed up to the `devices_<timestamp>.csv` file via the [CSV Backup `CSVBCKP` Plugin](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/csv_backup). This file contains data, such as:
The core device data is backed up to the `devices_<timestamp>.csv` or `devices.csv` file via the [CSV Backup `CSVBCKP` Plugin](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/csv_backup). This file contains data, such as:
- Device names
- Device Icons
- Device Network configuration
- Device icons
- Device network configuration
- Device categorization
- Device custom properties data
### Historical data
@@ -40,13 +42,15 @@ Historical data is stored in the `app.db` database (See [Database overview](http
- Plugin objects
- Plugin historical entries
- History of Events, Notifications, Workflow Events
- Presence History
- Presence history
## 🧭 Backup strategies
The safest approach to backups is to backup all of the above, by taking regular file system backups (I use [Kopia](https://github.com/kopia/kopia)).
Arguably, the most time is spent setting up the device list, so if only one file is kept I'd recommend to have a latest backup of the `devices_<timestamp>.csv` file, followed by the `app.conf` file.
Arguably, the most time is spent setting up the device list, so if only one file is kept I'd recommend to have a latest backup of the `devices_<timestamp>.csv` or `devices.csv` file, followed by the `app.conf` file. You can also download `app.conf` and `devices.csv` file in the Maintenance section:
![Backup and Restore Section in Maintenance](/docs/img/BACKUPS/Maintenance_Backup_Restore.png)
### Scenario 1: Full backup
@@ -54,8 +58,8 @@ End-result: Full restore
#### Source artifacts:
- `/db/app.db` (uncorrupted)
- `/config/app.conf`
- `/app/db/app.db` (uncorrupted)
- `/app/config/app.conf`
#### Recovery:
@@ -68,15 +72,15 @@ End-result: Partial restore (historical data & configurations from the Maintenan
#### Source artifacts:
- `/config/app.conf`
- `/config/devices_<timestamp>.csv` or `/config/devices.csv`
- `/app/config/app.conf`
- `/app/config/devices_<timestamp>.csv` or `/app/config/devices.csv`
#### Recovery:
Even with a corrupted database you can recover what I would argue is 99% of the configuration.
- map the `/config/app.conf` file as described in the [Setup documentation](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md#docker-paths).
- rename the `devices_<timestamp>.csv` to `devices.csv` and place it in the `/config` folder
- upload the `app.conf` file into the mounted `/app/config/` folder as described in the [Setup documentation](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md#docker-paths).
- rename the `devices_<timestamp>.csv` to `devices.csv` and place it in the `/app/config` folder
- Restore the `devices.csv` backup via the [Maintenance section](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICES_BULK_EDITING.md)

14
docs/COMMUNITY_GUIDES.md Executable file
View File

@@ -0,0 +1,14 @@
# Community Guides
Use the official installation guides at first and use community content as supplementary material. Open an issue or PR if you'd like to add your link to the list 🙏 (Ordered by last update time)
- ▶ [Home Lab Network Monitoring - Scotti-BYTE Enterprise Consulting Services](https://www.youtube.com/watch?v=0DryhzrQSJA) (July 2024)
- 📄 [How to Install NetAlertX on Your Synology NAS - Marius hosting](https://mariushosting.com/how-to-install-pi-alert-on-your-synology-nas/) (Updated frequently)
- 📄 [Using the PiAlert Network Security Scanner on a Raspberry Pi - PiMyLifeUp](https://pimylifeup.com/raspberry-pi-pialert/)
- ▶ [How to Setup Pi.Alert on Your Synology NAS - Digital Aloha](https://www.youtube.com/watch?v=M4YhpuRFaUg)
- 📄 [防蹭网神器,网络安全助手 | 极空间部署网络扫描和通知系统『NetAlertX』](https://blog.csdn.net/qq_63499861/article/details/141105273)
- 📄 [시놀/헤놀에서 네트워크 스캐너 Pi.Alert Docker로 설치 및 사용하기](https://blog.dalso.org/article/%EC%8B%9C%EB%86%80-%ED%97%A4%EB%86%80%EC%97%90%EC%84%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%8A%A4%EC%BA%90%EB%84%88-pi-alert-docker%EB%A1%9C-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EC%82%AC%EC%9A%A9) (July 2023)
- 📄 [网络入侵探测器Pi.Alert (Chinese)](https://codeantenna.com/a/VgUvIAjZ7J) (May 2023)
- ▶ [Pi.Alert auf Synology & Docker by - Jürgen Barth](https://www.youtube.com/watch?v=-ouvA2UNu-A) (March 2023)
- ▶ [Top Docker Container for Home Server Security - VirtualizationHowto](https://www.youtube.com/watch?v=tY-w-enLF6Q) (March 2023)
- ▶ [Pi.Alert or WatchYourLAN can alert you to unknown devices appearing on your WiFi or LAN network - Danie van der Merwe](https://www.youtube.com/watch?v=v6an9QG2xF0) (November 2022)

85
docs/CUSTOM_PROPERTIES.md Executable file
View File

@@ -0,0 +1,85 @@
# Custom Properties for Devices
![Custom Properties](/docs/img/CUSTOM_PROPERTIES/Device_Custom_Properties.png)
## Overview
This functionality allows you to define **custom properties** for devices, which can store and display additional information on the device listing page. By marking properties as visible, you can enhance the user interface with quick actions, notes, or external links.
### Key Features:
- **Customizable Properties**: Define specific properties for each device.
- **Visibility Control**: Choose which properties are displayed on the device listing page.
- **Interactive Elements**: Include actions like links, modals, and device management directly in the interface.
---
## Defining Custom Properties
Custom properties are structured as a list of objects, where each property includes the following fields:
| Field | Description |
|--------------------|-----------------------------------------------------------------------------|
| `CUSTPROP_icon` | The icon (Base64-encoded HTML) displayed for the property. |
| `CUSTPROP_type` | The action type (e.g., `show_notes`, `link`, `delete_dev`). |
| `CUSTPROP_name` | A short name or title for the property. |
| `CUSTPROP_args` | Arguments for the action (e.g., URL or modal text). |
| `CUSTPROP_notes` | Additional notes or details displayed when applicable. |
| `CUSTPROP_show` | A boolean to control visibility (`true` to show on the listing page). |
---
## Available Action Types
- **Show Notes**: Displays a modal with a title and additional notes.
- **Example**: Show firmware details or custom messages.
- **Link**: Redirects to a specified URL in the current browser tab. (**Arguments** Needs to contain the full URL.)
- **Link (New Tab)**: Opens a specified URL in a new browser tab. (**Arguments** Needs to contain the full URL.)
- **Delete Device**: Deletes the device using its MAC address.
- **Run Plugin**: Placeholder for executing custom plugins (not implemented yet).
---
## Usage on the Device Listing Page
![Custom Properties](/docs/img/CUSTOM_PROPERTIES/Device_Custom_Properties_vid.gif)
Visible properties (`CUSTPROP_show: true`) are displayed as interactive icons in the device listing. Each icon can perform one of the following actions based on the `CUSTPROP_type`:
1. **Modals (e.g., Show Notes)**:
- Displays detailed information in a popup modal.
- Example: Firmware version details.
2. **Links**:
- Redirect to an external or internal URL.
- Example: Open a device's documentation or external site.
3. **Device Actions**:
- Manage devices with actions like delete.
- Example: Quickly remove a device from the network.
4. **Plugins**:
- Future placeholder for running custom plugin scripts.
- **Note**: Not implemented yet.
---
## Example Scenarios
1. **Device Documentation Link**:
- Add a custom property with `CUSTPROP_type` set to `link` or `link_new_tab` to allow quick navigation to the documentation.
2. **Firmware Details**:
- Use `CUSTPROP_type: show_notes` to display firmware versions or upgrade instructions in a modal.
3. **Device Removal**:
- Enable device removal functionality using `CUSTPROP_type: delete_dev`.
---
## Notes
- **Plugin Functionality**: The `run_plugin` action type is currently not implemented and will show an alert if used.
- **Custom Icons (Experimental 🧪)**: Use Base64-encoded HTML to provide custom icons for each property. You can add your icons in Setttings via the `CUSTPROP_icon` settings
- **Visibility Control**: Only properties with `CUSTPROP_show: true` will appear on the listing page.
This feature provides a flexible way to enhance device management and display with interactive elements tailored to your needs.

View File

@@ -55,7 +55,7 @@ Input data from the plugin might cause mapping issues in specific edge cases. Lo
17:31:05 [Plugins] SQL sqlParams for mapping: [('01:01:01:01:01:01', '172.30.0.1', 0, 'aaaa', 'vvvvvvvvv', 'PIHOLE'), ('02:42:ac:1e:00:02', '172.30.0.2', 0, 'dddd', 'vvvvv2222', 'PIHOLE')]
🔺
17:31:05 [API] Update API starting
17:31:06 [API] Updating table_plugins_history.json file in /front/api
17:31:06 [API] Updating table_plugins_history.json file in /api
```
> The debug output between the 🔻red arrows🔺 is important for debugging (arrows added only to highlight the section on this page, they are not available in the actual debug log)

View File

@@ -64,7 +64,7 @@ Sometimes specific log sections are needed to debug issues. The Devices and Curr
### Permissions
* If facing issues (AJAX errors, can't write to DB, empty screen, etc,) make sure permissions are set correctly, and check the logs under `/app/front/log`.
* If facing issues (AJAX errors, can't write to DB, empty screen, etc,) make sure permissions are set correctly, and check the logs under `/app/log`.
* To solve permission issues you can try setting the owner and group of the `app.db` by executing the following on the host system: `docker exec netalertx chown -R www-data:www-data /app/db/app.db`.
* If still facing issues, try to map the app.db file (⚠ not folder) to `:/app/db/app.db` (see [docker-compose Examples](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md#-docker-composeyml-examples) for details)
@@ -74,7 +74,7 @@ Sometimes specific log sections are needed to debug issues. The Devices and Curr
### unable to resolve host
* Check that your `SCAN_SUBNETS` variable is using the correct mask and `--interface` as outlined in the instructions above.
* Check that your `SCAN_SUBNETS` variable is using the correct mask and `--interface`. See teh [subnets docs for details](/docs/SUBNETS.md).
### Invalid JSON
@@ -95,4 +95,4 @@ The link above will probably break in time too. Go to https://packages.debian.or
### Only Router and own device show up
Make sure that the subnet and interface in SCAN_SUBNETS are the correct ones. If your device/NAS has multiple ethernet ports, you probably need to change eth0 to something else!
Make sure that the subnet and interface in `SCAN_SUBNETS` are correct. If your device/NAS has multiple ethernet ports, you probably need to change `eth0` to something else.

View File

@@ -0,0 +1,6 @@
# Device Display Settings
This set of settings allows you to group Devices under different views. The Archived toggle allows you to exclude a Device from most listings and notifications.
![Display settings](/docs/img/DEVICE_MANAGEMENT/DeviceDetails_DisplaySettings.png)

View File

@@ -1,107 +1,50 @@
# NetAlertX - Device Management
<!--- --------------------------------------------------------------------- --->
To edit device information:
- Select "Devices" in the menu on the left of the screen
- Find the device you want to edit in the central table
- Go to the device page by clicking on the device name or status
- Press "Details" tab of the device
- Edit the device data
- Press the "Save" button
The Main Info section is where most of the device identifiable information is stored and edited. Some of the information is autodetected via various plugins. Initial values for most of the fields can be specified in the `NEWDEV` plugin.
> [!NOTE]
>
> You can multi-edit devices by selecting them in the main Devices view, from the Mainetence section, or via the CSV Export functionality under Maintenance. More info can be found in the [Devices Bulk-editing docs](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICES_BULK_EDITING.md).
![Main Info](/docs/img/DEVICE_MANAGEMENT/DeviceManagement_MainInfo.png)
## Main Info
- **MAC**: MAC addres of the device. Not editable, unless creating a new dummy device.
- **Last IP**: IP addres of the device. Not editable, unless creating a new dummy device.
- **Name**: Friendly device name. Autodetected via various 🆎 Name discovery [plugins](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/README.md). The app attaches `(IP match)` if the name is discovered via an IP match and not MAC match which could mean the name could be incorrect as IPs might change.
- **Icon**: Partially autodetected. Select an existing or [add a custom icon](https://github.com/jokob-sk/NetAlertX/blob/main/docs/ICONS.md). You can also auto-apply the same icon on all devices of the same type.
- **Owner**: Device owner (The list is self-populated with existing owners and you can add custom values).
- **Type**: Select a device type from the dropdown list (`Smartphone`, `Tablet`,
`Laptop`, `TV`, `router`, etc.) or add a new device type. If you want the device to act as a **Network device** (and be able to be a network node in the Network view), select a type under Network Devices or add a new Network Device type in Settings. More information can be found in the [Network Setup docs](https://github.com/jokob-sk/NetAlertX/blob/main/docs/NETWORK_TREE.md).
- **Vendor**: The manufacturing vendor. Automatically updated by NetAlertX when empty or unknown, can be edited.
- **Group**: Select a group (`Always on`, `Personal`, `Friends`, etc.) or type
your own Group name.
- **Location**: Select the location, usually a room, where the device is located (`Kitchen`, `Attic`, `Living room`, etc.) or add a custom Location.
- **Comments**: Add any comments for the device, such as a serial number, or maintenance information.
> [!NOTE]
>
> Please note the above usage of the fields are only suggestions. You can use most of these fields for other purposes, such as storing the network interface, company owning a device, or similar.
## Dummy devices
You can create dummy devices from the Devices listing screen.
![Create Dummy Device](/docs/img/DEVICE_MANAGEMENT/Devices_CreateDummyDevice.png)
The **MAC** field and the **Last IP** field will then become editable.
![Save Dummy Device](/docs/img/DEVICE_MANAGEMENT/DeviceEdit_SaveDummyDevice.png)
> [!NOTE]
>
> [Bulk-edit devices](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICES_BULK_EDITING.md) by using the _CSV Export_ functionality in the _Maintenance_ section.
> You can couple this with the `ICMP` plugin which can be used to monitor the status of these devices, if they are actual devices reachable with the `ping` command. If not, you can use a loopback IP address so they appear online, such as `0.0.0.0` or `127.0.0.1`.
## Copying data from an existing device.
To speed up device population you can also copy data from an existing device. This can be done from the **Tools** tab on the Device details.
![Device Details][screen1]
## Main Info
- **MAC**: MAC addres of the device. Not editable.
- **Name**: Friendly device name
- **Owner**: Device owner (The list is self-populated with existing owners)
- **Type**: Select a device type from the dropdown list (Smartphone, Table,
Laptop, TV, router, ....) or type a new device type
- **Vendor**: Automatically updated by NetAlertX when empty or unknown
- **Favorite**: Mark the device as favorite and then it will appears at the
begining of the device list
- **Group**: Select a grouper ('Always on', 'Personal', Friends') or type
your own Group name
- **Comments**: Type any comments for the device
## Session Info
- **Status**: Show device status : On-line / Off-Line
- **First Session**: Date and time of the first connection
- **Last Offline**: Date and time of the last last time the device was offline
- **Last IP**: Last known IP used during the last connection
- **Static IP**: Check this box to identify devices that always use the
same IP
## Events & Alerts config
- **Scan Cycle**: Select the scan cycle: 0, 1', 15'
- Some devices do not respond to all ARP packets, for this cases is better
to use a 15' cycle.
- **For Apple devices I recommend using 15' cycle**
- **Alert All Events**: Send a notification in each event (connection,
disconnection, IP Changed, ...)
- **Alert Down**: Send a notification when the device is down
- *(Userful with "always connected" devices: Camera, Alexa,...)*
- **Skip repeated notifications during**: Do not send more than one
notification to this device for X hours
- *(Useful to avoid notification saturation on devices that frequently
connects and disconnects)*
# Privacy & Random MAC's
<!--- --------------------------------------------------------------------- --->
The latest versions of some operating systems (IOS and Android) incorporate a
new & interesting functionality to improve privacy: **Random MACs**.
This functionality allows you to **hide the true MAC** of the device and
**assign a random MAC** when we connect to WIFI networks.
This behavior is especially useful when connecting to WIFI's that we do not
know, but it **is totally useless when connecting to our own WIFI's** or known
networks.
**I recommend disabling this operation when connecting our devices to our own
WIFI's**, in this way, NetAlertX will be able to identify the device, and it
will not identify it as a new device every so often (every time IOS or Android
decides to change the MAC).
### IOS
![ios][ios]
- [Use private Wi-Fi addresses in iOS 14](https://support.apple.com/en-us/HT211227)
### Android
![Android][Android]
- [How to Disable MAC Randomization in Android 10](https://support.boingo.com/s/article/How-to-Disable-MAC-Randomization-in-Android-10-Android-Q)
- [How do I disable random Wi-Fi MAC address on Android 10](https://support.plume.com/hc/en-gb/articles/360052070714-How-do-I-disable-random-Wi-Fi-MAC-address-on-Android-10-)
### License
GPL 3.0
[Read more here](../LICENSE.txt)
### Contact
Always use the Issue tracker for the correct fork, for example:
[jokob-sk/NetAlertX](https://github.com/jokob-sk/NetAlertX/issues). Please also follow the guidelines on:
- [Pull Request guidelines](https://github.com/jokob-sk/NetAlertX/tree/main/docs#-pull-requests-prs)
- 🙏 [Feature request guidelines](https://github.com/jokob-sk/NetAlertX/tree/main/docs#-feature-requests)
- 🐛 [Issue guidelines](https://github.com/jokob-sk/NetAlertX/tree/main/docs#-submitting-an-issue-or-bug)
***Suggestions and comments are welcome***
<!--- --------------------------------------------------------------------- --->
[main]: ./img/1_devices.jpg "Main screen"
[screen1]: ./img/2_1_device_details.jpg "Screen 1"
[ios]: https://9to5mac.com/wp-content/uploads/sites/6/2020/08/how-to-use-private-wifi-mac-address-iphone-ipad.png?resize=2048,1009 "ios"
[Android]: ./img/android_random_mac.jpg "Android"

101
docs/DOCKER_COMPOSE.md Executable file
View File

@@ -0,0 +1,101 @@
# `docker-compose.yaml` Examples
### Example 1
```yaml
services:
netalertx:
container_name: netalertx
# use the below line if you want to test the latest dev image
# image: "jokobsk/netalertx-dev:latest"
image: "jokobsk/netalertx:latest"
network_mode: "host"
restart: unless-stopped
volumes:
- local_path/config:/app/config
- local_path/db:/app/db
# (optional) useful for debugging if you have issues setting up the container
- local_path/logs:/app/log
# (API: OPTION 1) use for performance
- type: tmpfs
target: /app/api
# (API: OPTION 2) use when debugging issues
# - local_path/api:/app/api
environment:
- TZ=Europe/Berlin
- PORT=20211
```
To run the container execute: `sudo docker-compose up -d`
### Example 2
Example by [SeimuS](https://github.com/SeimusS).
```yaml
netalertx:
container_name: NetAlertX
hostname: NetAlertX
privileged: true
# use the below line if you want to test the latest dev image
# image: "jokobsk/netalertx-dev:latest"
image: jokobsk/netalertx:latest
environment:
- TZ=Europe/Bratislava
restart: always
volumes:
- ./netalertx/db:/app/db
- ./netalertx/config:/app/config
network_mode: host
```
To run the container execute: `sudo docker-compose up -d`
### Example 3
`docker-compose.yml`
```yaml
services:
netalertx:
container_name: netalertx
# use the below line if you want to test the latest dev image
# image: "jokobsk/netalertx-dev:latest"
image: "jokobsk/netalertx:latest"
network_mode: "host"
restart: unless-stopped
volumes:
- ${APP_DATA_LOCATION}/netalertx/config:/app/config
- ${APP_DATA_LOCATION}/netalertx/db/:/app/db/
# (optional) useful for debugging if you have issues setting up the container
- ${LOGS_LOCATION}:/app/log
# (API: OPTION 1) use for performance
- type: tmpfs
target: /app/api
# (API: OPTION 2) use when debugging issues
# - local/path/api:/app/api
environment:
- TZ=${TZ}
- PORT=${PORT}
```
`.env` file
```yaml
#GLOBAL PATH VARIABLES
APP_DATA_LOCATION=/path/to/docker_appdata
APP_CONFIG_LOCATION=/path/to/docker_config
LOGS_LOCATION=/path/to/docker_logs
#ENVIRONMENT VARIABLES
TZ=Europe/Paris
PORT=20211
#DEVELOPMENT VARIABLES
DEV_LOCATION=/path/to/local/source/code
```
To run the container execute: `sudo docker-compose --env-file /path/to/.env up`

View File

@@ -5,37 +5,19 @@
> ```
> docker run -d --rm --network=host \
> -e TZ=Europe/Berlin \
> -e PUID=200 -e PGID=200 \
> -e PORT=20211 \
> jokobsk/netalertx:latest
> ```
NetAlertX runs on an Nginx web server. On Alpine Linux, Nginx operates as the `nginx` user (user ID 101, group ID 82 - `www-data`). Consequently, files accessed or written by the NetAlertX application are owned by `nginx:www-data`.
NetAlertX runs on an Nginx web server. On Alpine Linux, Nginx operates as the `nginx` user (if PUID and GID environment variables are not specified, nginx user UID will be set to 102, and its supplementary group `www-data` ID to 82). Consequently, files accessed or written by the NetAlertX application are owned by `nginx:www-data`.
Upon starting, NetAlertX changes the ownership of files on the host system mapped to `/app/config` and `/app/db` in the container to `nginx:www-data`. This ensures that Nginx can access and write to these files. Since the user in the Docker container is mapped to a user on the host system by ID:GID, the files in `/app/config` and `/app/db` on the host system are owned by a user with the same ID and GID (ID 101 and GID 82). On different systems, this ID:GID may belong to different users (on Debian, the user with ID 82 is `uuidd`), or there may not be a user with ID 82 at all.
Upon starting, NetAlertX changes nginx user UID and www-data GID to specified values (or defaults), and the ownership of files on the host system mapped to `/app/config` and `/app/db` in the container to `nginx:www-data`. This ensures that Nginx can access and write to these files. Since the user in the Docker container is mapped to a user on the host system by ID:GID, the files in `/app/config` and `/app/db` on the host system are owned by a user with the same ID and GID (defaults are ID 102 and GID 82). On different systems, this ID:GID may belong to different users, or there may not be a group with ID 82 at all.
While this generally isn't problematic, it can cause issues for host system users needing to access these files (e.g., backup scripts). If users other than root need access to these files, it is recommended to add those users to the group with GID 82. If that group doesn't exist, it should be created.
Option to set specific user UID and GID can be useful for host system users needing to access these files (e.g., backup scripts).
### Permissions Table for Individual Folders
| Folder | User | User ID | Group | Group ID | Permissions | Notes |
|----------------|--------|---------|-----------|----------|-------------|---------------------------------------------------------------------|
| `/app/config` | nginx | 101 | www-data | 82 | rwxr-xr-x | Ensure `nginx` can read/write; other users can read if in `www-data` |
| `/app/db` | nginx | 101 | www-data | 82 | rwxr-xr-x | Same as above |
### Steps to Add Users to Group
1. **Check if group exists:**
```sh
getent group www-data
```
2. **Create group if it does not exist:**
```sh
sudo groupadd -g 82 www-data
```
3. **Add user to group:**
```sh
sudo usermod -aG www-data <username>
```
Replace `<username>` with the actual username that requires access.
| `/app/config` | nginx | PUID (default 102) | www-data | PGID (default 82) | rwxr-xr-x | Ensure `nginx` can read/write; other users can read if in `www-data` |
| `/app/db` | nginx | PUID (default 102) | www-data | PGID (default 82) | rwxr-xr-x | Same as above |

View File

@@ -2,6 +2,9 @@
NetAlertX comes with MQTT support, allowing you to show all detected devices as devices in Home Assistant. It also supplies a collection of stats, such as number of online devices.
> [!TIP]
> You can install NetAlertX also as a Home Assistant addon [![Home Assistant](https://img.shields.io/badge/Repo-blue?logo=home-assistant&style=for-the-badge&color=0aa8d2&logoColor=fff&label=Add)](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Falexbelgium%2Fhassio-addons) via the [alexbelgium/hassio-addons](https://github.com/alexbelgium/hassio-addons/).
## ⚠ Note
- Please note that discovery takes about ~10s per device.

View File

@@ -5,7 +5,7 @@ To download and install NetAlertX on the hardware/server directly use the `curl`
> [!NOTE]
> This is an Experimental feature 🧪 and it relies on community support.
>
> Looking for maintainers for this installation method 🙂
> 🙏 Looking for maintainers for this installation method 🙂
>
> There is no guarantee that the install script or any other script will gracefully handle other installed software.
> Data loss is a possibility, **it is recommended to install NetAlertX using the supplied Docker image**.
@@ -36,6 +36,9 @@ Some facts about what and where something will be changed/installed by the HW in
## 📥 Installation via CURL
> [!TIP]
> If the below fails try grabbing and installing one of the [previous releases](https://github.com/jokob-sk/NetAlertX/releases) and run the installation from the zip package.
```bash
curl -o install.debian.sh https://raw.githubusercontent.com/jokob-sk/NetAlertX/main/install/install.debian.sh && sudo chmod +x install.debian.sh && sudo ./install.debian.sh
```

View File

@@ -38,7 +38,7 @@ Copying the HTML code from [Font Awesome](https://fontawesome.com/search?o=r&m=f
> [!NOTE]
> If you want to mass-apply an icon to all devices of the same device type (Field: Type), you can click the mass-copy button (next to the "+" button). A confirmation prompt is displayed. If you proceed, icons of all devices set to the same device type as the current device, will be overwritten with the current device's icon.
- The blue dropdown contains all icons already used in the app for device icons. You need to navigate away or refresh the page once you add a new icon.
- The dropdown contains all icons already used in the app for device icons. You might need to navigate away or refresh the page once you add a new icon.
## 🌟 Pro Font Awesome icons

View File

@@ -85,7 +85,7 @@ services:
- local/path/config:/app/config # ⚠ This has changed (🔺required)
- local/path/db:/app/db # ⚠ This has changed (🔺required)
# (optional) useful for debugging if you have issues setting up the container
- local/path/logs:/app/front/log # ⚠ This has changed (🟡optional)
- local/path/logs:/app/log # ⚠ This has changed (🟡optional)
environment:
- TZ=Europe/Berlin
- PORT=20211
@@ -135,7 +135,7 @@ services:
- local/path/config/app.conf:/app/config/app.conf # ⚠ This has changed (🔺required)
- local/path/db/app.db:/app/db/app.db # ⚠ This has changed (🔺required)
# (optional) useful for debugging if you have issues setting up the container
- local/path/logs:/app/front/log # ⚠ This has changed (🟡optional)
- local/path/logs:/app/log # ⚠ This has changed (🟡optional)
environment:
- TZ=Europe/Berlin
- PORT=20211

View File

@@ -2,7 +2,7 @@
Make sure you have a root device with the MAC `Internet` (No other MAC addresses are currently supported as the root node) set to a network device type (e.g.: **Type**:`Router`).
> 💡 Tip: You can add dummy devices via the [Undiscoverables plugin](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/undiscoverables/README.md)
> 💡 Tip: You can add dummy devices via the [Create dummy device](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICE_MANAGEMENT.md#dummy-devices) button in the Devices listing page.
> 💡 Tip: Export your configuration of the Network and Devices once in a while via the Export CSV feature under **Maintenance** -> **Backup/Restore** -> **CSV Export**.

View File

@@ -29,3 +29,31 @@ The more often you scan the networks the more resources, traffic and DB read/wri
Also consider decreasing the scanned subnet, e.g. from `/16` to `/24` if need be.
# Store temporary files in memory
You can also store temporary files in application memory (`/app/api` and `/app/log` folders). See highlighted lines `◀` below.
```yaml
version: "3"
services:
netalertx:
container_name: netalertx
# use the below line if you want to test the latest dev image
# image: "jokobsk/netalertx-dev:latest"
image: "jokobsk/netalertx:latest"
network_mode: "host"
restart: unless-stopped
volumes:
- local/path/config:/app/config
- local/path/db:/app/db
# (optional) useful for debugging if you have issues setting up the container
- local/path/logs:/app/log
# (API: OPTION 1) use for performance
- type: tmpfs # ◀
target: /app/api # ◀
# (API: OPTION 2) use when debugging issues
# - local/path/api:/app/api
environment:
- TZ=Europe/Berlin
- PORT=20211
```

45
docs/PIHOLE_GUIDE.md Executable file
View File

@@ -0,0 +1,45 @@
# Integration with PiHole
NetAlertX comes with 2 plugins suitable for integarting with your existing PiHole instace. One plugin is using a direct SQLite DB connection, the other leverages the DHCP.leases file generated by PiHole. You can combine both approaches and also supplement it with other [plugins](/front/plugins/README.md).
## Approach 1: `DHCPLSS` Plugin - Import devices from the PiHole DHCP leases file
![DHCPLSS sample settings](/docs/img/PIHOLE_GUIDE/DHCPLSS_pihole_settings.png)
### Settings
| Setting | Description | Recommended value |
| :------------- | :------------- | :-------------|
| `DHCPLSS_RUN` | When the plugin should run. | `schedule` |
| `DHCPLSS_RUN_SCHD` | If you run multiple device scanner plugins, align the schedules of all plugins to the same value. | `*/5 * * * *` |
| `DHCPLSS_paths_to_check` | You need to map the value in this setting in the `docker-compose.yml` file. The in-container path must contain `pihole` so it's parsed correctly. | `['/etc/pihole/dhcp.leases']` |
Check the [DHCPLSS plugin readme](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/dhcp_leases#overview) for details
### docker-compose changes
| Path | Description |
| :------------- | :------------- |
| `:/etc/pihole/dhcp.leases` | PiHole's `dhcp.leases` file. Required if you want to use PiHole `dhcp.leases` file. This has to be matched with a corresponding `DHCPLSS_paths_to_check` setting entry (the path in the container must contain `pihole`) |
## Approach 2: `PIHOLE` Plugin - Import devices directly from the PiHole database
![DHCPLSS sample settings](/docs/img/PIHOLE_GUIDE/PIHOLE_settings.png)
| Setting | Description | Recommended value |
| :------------- | :------------- | :-------------|
| `PIHOLE_RUN` | When the plugin should run. | `schedule` |
| `PIHOLE_RUN_SCHD` | If you run multiple device scanner plugins, align the schedules of all plugins to the same value. | `*/5 * * * *` |
| `PIHOLE_DB_PATH` | You need to map the value in this setting in the `docker-compose.yml` file. | `/etc/pihole/pihole-FTL.db` |
Check the [PiHole plugin readme](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/pihole_scan) for details
### docker-compose changes
| Path | Description |
| :------------- | :------------- |
| `:/etc/pihole/pihole-FTL.db` | PiHole's `pihole-FTL.db` database file. |
Check out other [plugins](/front/plugins/README.md) that can help you discover more about your network or check how to scan [Remote networks](/docs/REMOTE_NETWORKS.md).

View File

@@ -12,6 +12,9 @@ NetAlertX comes with a plugin system to feed events from third-party scripts int
### 🎥 Watch the video:
> [!TIP]
> Read this guide [Development environment setup guide](/docs/DEV_ENV_SETUP.md) to set up your local environment for development. 👩‍💻
[![Watch the video](/docs/img/YouTube_thumbnail.png)](https://youtu.be/cdbxlwiWhv8)
### 📸 Screenshots
@@ -46,7 +49,7 @@ Please read the below carefully if you'd like to contribute with a plugin yourse
|----------------------|----------------------|----------------------|
| `config.json` | yes | Contains the plugin configuration (manifest) including the settings available to the user. |
| `script.py` | no | The Python script itself. You may call any valid linux command. |
| `last_result.log` | no | The file used to interface between NetAlertX and the plugin. Required for a script plugin if you want to feed data into the app. |
| `last_result.<prefix>.log` | no | The file used to interface between NetAlertX and the plugin. Required for a script plugin if you want to feed data into the app. Stored in the `/api/log/plugins/` |
| `script.log` | no | Logging output (recommended) |
| `README.md` | yes | Any setup considerations or overview |
@@ -103,7 +106,7 @@ Currently, these data sources are supported (valid `data_source` value).
| External SQLite DB query | `sqlite-db-query` | yes | Executes a SQL query from the `CMD` setting on an external SQLite database mapped in the `DB_PATH` setting. |
| Plugin type | `plugin_type` | no | Specifies the type of the plugin and in which section the Plugin settings are displayed ( one of `general/system/scanner/other/publisher` ). |
> * "Needs to return a "table" means that the application expects a `last_result.log` file with some results. It's not a blocker, however warnings in the `app.log` might be logged.
> * "Needs to return a "table" means that the application expects a `last_result.<prefix>.log` file with some results. It's not a blocker, however warnings in the `app.log` might be logged.
> 🔎Example
>```json
@@ -120,21 +123,21 @@ You can show or hide the UI on the "Plugins" page and "Plugins" tab for a plugin
### "data_source": "script"
If the `data_source` is set to `script` the `CMD` setting (that you specify in the `settings` array section in the `config.json`) contains an executable Linux command, that usually generates a `last_result.log` file (not required if you don't import any data into the app). The `last_result.log` file needs to be saved in the same folder as the plugin.
If the `data_source` is set to `script` the `CMD` setting (that you specify in the `settings` array section in the `config.json`) contains an executable Linux command, that usually generates a `last_result.<prefix>.log` file (not required if you don't import any data into the app). The `last_result.<prefix>.log` file needs to be saved in `/api/log/plugins`.
> [!IMPORTANT]
> A lot of the work is taken care of by the [`plugin_helper.py` library](/front/plugins/plugin_helper.py). You don't need to manage the `last_result.log` file if using the helper objects. Check other `script.py` of other plugins for details (The [Undicoverables plugins `script.py` file](/front/plugins/undiscoverables/script.py) is a good example).
> A lot of the work is taken care of by the [`plugin_helper.py` library](/front/plugins/plugin_helper.py). You don't need to manage the `last_result.<prefix>.log` file if using the helper objects. Check other `script.py` of other plugins for details.
The content of the `last_result.log` file needs to contain the columns as defined in the "Column order and values" section above. The order of columns can't be changed. After every scan it should contain only the results from the latest scan/execution.
The content of the `last_result.<prefix>.log` file needs to contain the columns as defined in the "Column order and values" section above. The order of columns can't be changed. After every scan it should contain only the results from the latest scan/execution.
- The format of the `last_result.log` is a `csv`-like file with the pipe `|` as a separator.
- The format of the `last_result.<prefix>.log` is a `csv`-like file with the pipe `|` as a separator.
- 9 (nine) values need to be supplied, so every line needs to contain 8 pipe separators. Empty values are represented by `null`.
- Don't render "headers" for these "columns".
Every scan result/event entry needs to be on a new line.
- You can find which "columns" need to be present, and if the value is required or optional, in the "Column order and values" section.
- The order of these "columns" can't be changed.
#### 🔎 last_result.log examples
#### 🔎 last_result.prefix.log examples
Valid CSV:
@@ -575,7 +578,7 @@ Each element may also have associated events (e.g., running a scan or triggering
##### Supported settings `function` values
You can have any `"function": "my_custom_name"` custom name, however, the ones listed below have a specific functionality attached to them. If you use a custom name, then the setting is mostly used as an input parameter for the `params` section.
You can have any `"function": "my_custom_name"` custom name, however, the ones listed below have a specific functionality attached to them.
| Setting | Description |
| ------- | ----------- |
@@ -657,7 +660,7 @@ The UI will adjust how columns are displayed in the UI based on the resolvers de
| Type Definitions | |
| `device_mac` | The value is considered to be a MAC address, and a link pointing to the device with the given MAC address is generated. |
| `device_ip` | The value is considered to be an IP address. A link pointing to the device with the given IP is generated. The IP is checked against the last detected IP address and translated into a MAC address, which is then used for the link itself. |
| `device_name_mac` | The value is considered to be a MAC address, and a link pointing to the device with the given IP is generated. The link label is resolved as the target device name. |
| `device_name_mac` | The value is considered to be a MAC address, and a link pointing to the device with the given MAC is generated. The link label is resolved as the target device name. |
| `url` | The value is considered to be a URL, so a link is generated. |
| `textbox_save` | Generates an editable and saveable text box that saves values in the database. Primarily intended for the `UserData` database column in the `Plugins_Objects` table. |
| `url_http_https` | Generates two links with the `https` and `http` prefix as lock icons. |

View File

@@ -29,6 +29,7 @@ There is also an in-app Help / FAQ section that should be answering frequently a
- [Synology Guide](/docs/SYNOLOGY_GUIDE.md)
- [Subnets and VLANs configuration for arp-scan](/docs/SUBNETS.md)
- [Scanning Remote Networks](/docs/REMOTE_NETWORKS.md)
- [SMTP server config](/docs/SMTP.md)
- [Custom Icon configuration and support](/docs/ICONS.md)
- [Notifications](/docs/NOTIFICATIONS.md)
@@ -56,7 +57,6 @@ There is also an in-app Help / FAQ section that should be answering frequently a
- [Manage devices (legacy docs)](/docs/DEVICE_MANAGEMENT.md)
- [Random MAC/MAC icon meaning (legacy docs)](/docs/RANDOM_MAC.md)
#### 🔎 Examples
- [N8N webhook example](/docs/WEBHOOK_N8N.md)

49
docs/REMOTE_NETWORKS.md Executable file
View File

@@ -0,0 +1,49 @@
# Scanning Remote or Inaccessible Networks
By design, local network scanners such as `arp-scan` use ARP (Address Resolution Protocol) to map IP addresses to MAC addresses on the local network. Since ARP operates at Layer 2 (Data Link Layer), it typically works only within a single broadcast domain, usually limited to a single router or network segment.
To scan multiple locally accessible network segments, add them as subnets according to the [subnets](https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md) documentation.
## Complex Use Cases
The following network setups might make some devices undetectable. Check the specific setup to understand the cause and find potential workarounds to still report on these devices.
### Wi-Fi Extenders
Wi-Fi extenders typically create a separate network or subnet, which can prevent network scanning tools like `arp-scan` from detecting devices behind the extender.
> **Possible workaround**: Scan the specific subnet that the extender uses, if it is separate from the main network.
### VPNs
ARP operates at Layer 2 (Data Link Layer) and works only within a local area network (LAN). VPNs, which operate at Layer 3 (Network Layer), route traffic between networks, preventing ARP requests from discovering devices outside the local network.
VPNs use virtual interfaces (e.g., `tun0`, `tap0`) to encapsulate traffic, bypassing ARP-based discovery. Additionally, many VPNs use NAT, which masks individual devices behind a shared IP address.
> **Possible workaround**: Configure the VPN to bridge networks instead of routing to enable ARP, though this depends on the VPN setup and security requirements.
# Other Workarounds
The following workarounds should work for most complex network setups.
## Supplementing Plugins
You can use supplementary plugins that employ alternate methods. Protocols used by the `SNMPDSC` or `DHCPLSS` plugins are widely supported on different routers and can be effective as workarounds. Check the [plugins list](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/README.md) to find a plugin that works with your router and network setup.
## Multiple NetAlertX Instances
If you have servers in different networks, you can set up separate NetAlertX instances on those subnets and synchronize the results into one instance using the [`SYNC` plugin](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/sync).
## Manual Entry
If you don't need to discover new devices and only need to report on their status (`online`, `offline`, `down`), you can manually enter devices and check their status using the [`ICMP` plugin](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/icmp_scan/), which uses the `ping` command internally.
For more information on how to add devices manually (or dummy devices), refer to the [Device Management](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICE_MANAGEMENT.md) documentation.
To create truly dummy devices, you can use a loopback IP address (e.g., `0.0.0.0` or `127.0.0.1`) so they appear online.
## NMAP and Fake MAC Addresses
Scanning remote networks with NMAP is possible (via the `NMAPDEV` plugin), but since it cannot retrieve the MAC address, you need to enable the `NMAPDEV_FAKE_MAC` setting. This will generate a fake MAC address based on the IP address, allowing you to track devices. However, this can lead to inconsistencies, especially if the IP address changes or a previously logged device is rediscovered. If this setting is disabled, only the IP address will be discovered, and devices with missing MAC addresses will be skipped.
Check the [NMAPDEV plugin](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/nmap_dev_scan) for details

View File

@@ -40,7 +40,7 @@ services:
volumes:
- /home/netalertx/config:/app/config
- /home/netalertx/db:/app/db
- /home/netalertx/log:/app/front/log
- /home/netalertx/log:/app/log
environment:
- TZ=Europe/Berlin
- PORT=20211
@@ -66,7 +66,7 @@ services:
volumes:
- ./config/app.conf:/app/config/app.conf
- ./db:/app/db
- ./log:/app/front/log
- ./log:/app/log
- ./config/resolv.conf:/etc/resolv.conf # Mapping the /resolv.conf file for better name resolution
environment:
- TZ=Europe/Berlin

62
docs/SESSION_INFO.md Executable file
View File

@@ -0,0 +1,62 @@
# Sessions Section in Device View
The **Sessions Section** provides details about a device's connection history. This data is automatically detected and cannot be edited by the user.
![Session info](/docs/img/SESSION_INFO/DeviceDetails_SessionInfo.png)
---
## Key Fields
1. **Date and Time of First Connection**
- **Description:** Displays the first detected connection time for the device.
- **Editability:** Uneditable (auto-detected).
- **Source:** Automatically captured when the device is first added to the system.
2. **Date and Time of Last Connection**
- **Description:** Shows the most recent time the device was online.
- **Editability:** Uneditable (auto-detected).
- **Source:** Updated with every new connection event.
3. **Offline Devices with Missing or Conflicting Data**
- **Description:** Handles cases where a device is offline but has incomplete or conflicting session data (e.g., missing start times).
- **Handling:** The system flags these cases for review and attempts to infer missing details.
---
## How Sessions are Discovered and Calculated
### 1. Detecting New Devices
When a device is first detected in the network, the system logs it in the events table:
`INSERT INTO Events (eve_MAC, eve_IP, eve_DateTime, eve_EventType, eve_AdditionalInfo, eve_PendingAlertEmail) SELECT cur_MAC, cur_IP, '{startTime}', 'New Device', cur_Vendor, 1 FROM CurrentScan WHERE NOT EXISTS (SELECT 1 FROM Devices WHERE devMac = cur_MAC)`
- Devices scanned in the current cycle (**CurrentScan**) are checked against the **Devices** table.
- If a device is new:
- A **New Device** event is logged.
- The devices MAC, IP, vendor, and detection time are recorded.
### 2. Logging Connection Sessions
When a new connection is detected, the system creates a session record:
`INSERT INTO Sessions (ses_MAC, ses_IP, ses_EventTypeConnection, ses_DateTimeConnection, ses_EventTypeDisconnection, ses_DateTimeDisconnection, ses_StillConnected, ses_AdditionalInfo) SELECT cur_MAC, cur_IP, 'Connected', '{startTime}', NULL, NULL, 1, cur_Vendor FROM CurrentScan WHERE NOT EXISTS (SELECT 1 FROM Sessions WHERE ses_MAC = cur_MAC)`
- A new session is logged in the **Sessions** table if no prior session exists.
- Fields like `MAC`, `IP`, `Connection Type`, and `Connection Time` are populated.
- The `Still Connected` flag is set to `1` (active connection).
### 3. Handling Missing or Conflicting Data
- Devices with incomplete or conflicting session data (e.g., missing start times) are detected.
- The system flags these records and attempts corrections by inferring details from available data.
### 4. Updating Sessions
- When a device reconnects, its session is updated with a new connection timestamp.
- When a device disconnects:
- The **Disconnection Time** is recorded.
- The `Still Connected` flag is set to `0`.
The session information is then used to display the device presence under **Monitoring** -> **Presence**.
![Monitoring Device Presence](/docs/img/SESSION_INFO/Monitoring_Presence.png)

View File

@@ -2,15 +2,13 @@
You need to specify the network interface and the network mask. You can also configure multiple subnets and specify VLANs (see VLAN exceptions below).
`ARPSCAN` can scan multiple networks if the network allows it. To scan networks directly, the subnets must be accessible from the network where NetAlertX is running. This means NetAlertX needs to have access to the interface attached to that subnet. You can verify this by running the following command in the container:
`ARPSCAN` can scan multiple networks if the network allows it. To scan networks directly, the subnets must be accessible from the network where NetAlertX is running. This means NetAlertX needs to have access to the interface attached to that subnet. You can verify this by running the following command in the container (replace the interface and ip mask):
`sudo arp-scan --interface=eth0 192.168.1.0/24`
In this example, `--interface=eth0 192.168.1.0/24` represents a neighboring subnet. If this command returns no results, the network is not accessible due to your network or firewall restrictions.
If direct scans are not possible, you can use [supplementing plugins](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/README.md) that use alternate methods. Protocols used by the `SNMPDSC` or `DHCPLSS` plugins have good support and usually can be used as a workaround.
Alternatively, you can set up separate NetAlertX instances running on the subnets and synchronize the results into one instance with the [`SYNC` plugin](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/sync).
If direct scans are not possible (Wi-Fi Extenders, VPNs and inaccessible networks), check the [remote networks documentation](https://github.com/jokob-sk/NetAlertX/blob/main/docs/REMOTE_NETWORKS.md).
> [!TIP]
> You may need to increase the time between scans `ARPSCAN_RUN_SCHD` and the timeout `ARPSCAN_RUN_TIMEOUT` (and similar settings for related plugins) when adding more subnets. If the timeout setting is exceeded, the scan is canceled to prevent the application from hanging due to rogue plugins.
@@ -24,7 +22,7 @@ Alternatively, you can set up separate NetAlertX instances running on the subnet
* **Examples for one and two subnets:**
* 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']`
* 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 `/16` to `/24`) or increase the `TIMEOUT` setting (e.g.: `ARPSCAN_RUN_TIMEOUT` to `300` (5-minute timeout)) for the plugin and the interval between scans (e.g.: `ARPSCAN_RUN_SCHD` to `*/10 * * * *` (scans every 10 minutes)).
@@ -47,18 +45,24 @@ Specify the network filter, which **significantly** speeds up the scan process.
**Example value:** `--interface=eth0`
The adapter will probably be `eth0` or `eth1`. (Check `System Info` > `Network Hardware` or run `iwconfig` in the container to find your interface name(s)).
The adapter will probably be `eth0` or `eth1`. (Check `System Info` > `Network Hardware`, or run `iwconfig` in the container to find your interface name(s)).
![Network hardware](/docs/img/SUBNETS/system_info-network_hardware.png)
> [!TIP]
> As an alternative to `iwconfig`, run `ip -o link show | awk -F': ' '!/lo|vir|docker/ {print $2}'` in your container to find your interface name(s) (e.g.: `eth0`, `eth1`).
> As an alternative to `iwconfig`, run `ip -o link show | awk -F': ' '!/lo|vir|docker/ {print $2}'` in your container to find your interface name(s) (e.g.: `eth0`, `eth1`):
> ```bash
> Synology-NAS:/# ip -o link show | awk -F': ' '!/lo|vir|docker/ {print $2}'
> sit0@NONE
> eth1
> eth0
> ```
### VLANs
**Example value:** `-vlan=107`
**Example value:** `--vlan=107`
- Append `-vlan=107` to the interface field (e.g.: `eth0 -vlan=107`) for multiple VLANs. More details are available in this [comment](https://github.com/jokob-sk/NetAlertX/issues/170#issuecomment-1419902988).
- Append `--vlan=107` to the `SCAN_SUBNETS` field (e.g.: `192.168.1.0/24 --interface=vmbr0 --vlan=107`) for multiple VLANs.
#### VLANs on a Hyper-V Setup
@@ -85,7 +89,6 @@ By default, Hyper-V only allows untagged packets through to the VM interface, bl
2. Within the VM, set up sub-interfaces for each VLAN to enable scanning. On Ubuntu 22.04, Netplan can be used. In /etc/netplan/00-installer-config.yaml, add VLAN definitions:
```yaml
network:
ethernets:
eth0:
@@ -111,20 +114,3 @@ Please note the accessibility of macvlans when configured on the same computer.
- NetAlertX does not detect the macvlan container when it is running on the same computer.
- NetAlertX recognizes the macvlan container when it is running on a different computer.
### Wi-Fi Extenders
A Wi-Fi extender typically works by creating a separate network or subnet, which can cause certain network scanning tools, like `arp-scan`, to be unable to detect devices behind the extender.
This happens because `arp-scan` uses ARP (Address Resolution Protocol) to map IP addresses to MAC addresses on the local network. Since ARP is a Layer 2 (data link layer) protocol, it usually only works within a single broadcast domain, which is typically limited to a single router or network segment.
When you introduce a Wi-Fi extender, it may isolate devices on different segments of the network, meaning ARP packets cannot easily traverse from one segment (your main network) to another (the network behind the extender).
To scan devices behind the extender, you can try:
- Scanning the specific subnet that the extender uses, if it is separate from the main network.
- Using [supplementing plugins](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/README.md) that use alternate methods. Protocols used by the `SNMPDSC` or `DHCPLSS` plugins have good support and usually can be used as a workaround.
Check the [plugins list](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/README.md) to find a plugin supported by your router and your network setup.

View File

@@ -43,7 +43,7 @@ services:
- local/path/config:/app/config
- local/path/db:/app/db
# (optional) useful for debugging if you have issues setting up the container
- local/path/logs:/app/front/log
- local/path/logs:/app/log
environment:
- TZ=Europe/Berlin
- PORT=20211
@@ -60,7 +60,7 @@ services:
- /volume1/app_storage/netalertx/config:/app/config
- /volume1/app_storage/netalertx/db:/app/db
# (optional) useful for debugging if you have issues setting up the container
# - local/path/logs:/app/front/log <- commented out with # ⚠
# - local/path/logs:/app/log <- commented out with # ⚠
```
![Adjusting docker-compose](/docs/img/SYNOLOGY/08_Adjust_docker_compose_volumes.png)

View File

@@ -1,87 +0,0 @@
# Pi.Alert Version History
<!--- --------------------------------------------------------------------- --->
| Version | Description |
| ------- | --------------------------------------------------------------- |
| v3.00 | Major set of New features & Enhancements |
| v2.70 | New features & Usability improvements in the web prontal |
| v2.61 | Bug fixing |
| v2.60 | Improved the compability of installation process (Ubuntu) |
| v2.56 | Bug fixing |
| v2.55 | Bug fixing |
| v2.52 | Bug fixing |
| v2.51 | Bug fixing |
| v2.50 | First public release |
# 🆕 2022+ [Newest Release notes](https://github.com/jokob-sk/NetAlertX/issues/138)
## Pi.Alert v3.02
<!--- --------------------------------------------------------------------- --->
**PENDING UPDATE DOC**
- Fixed: UNIQUE constraint failed with Local MAC #114
## Pi.Alert v3.01
<!--- --------------------------------------------------------------------- --->
**PENDING UPDATE DOC**
- Fixed: Problem with local MAC & IP (raspberry) #106
## Pi.Alert v3.00
<!--- --------------------------------------------------------------------- --->
**PENDING UPDATE DOC**
- `arp-scan` config options: interface, several subnets. #101 #15
- Next/previos button while editing devices #66 #37
- Internet presence/sessions monitoring #63
- Logical delete / archive / hide Device #93
- Flag to mark device with random MAC's #87
- New Device Types predefined in combobox #92
- Ask before leave the page with unsaved changes #104
- Option to don't mark devices as new during installation #94
- Uninstall script #62
- Fixed: Error updating name of devices w/o IP #97
- Fixed: Deleted devices reappear #84
- Fixed: Device running Pi.Alert must be marked as "on-line" #76
- Fixed: Incorrect calculation of presence hours #102
- Fixed: Problem redirect to homepage clicking in logo #103
## Pi.Alert v2.70
<!--- --------------------------------------------------------------------- --->
- Added Client names resolution #43
- Added Check to mark devices as "known" #16
- Remember "Show XXX entries" dropdown value #16 #26
- Remember "sorting" in devices #16
- Remember "Device panel " in device detail #16
- Added "All" option to "Show x Entries" option #16
- Added optional Location field (Door, Basement, etc.) to devices #16
- "Device updated successfully" message now is not modal #16
- Now is possible to delete Devices #16
- Added Device Type Singleboard Computer (SBC) #16
- Allowed to use " in device name #42
## Pi.Alert v2.60
<!--- --------------------------------------------------------------------- --->
- `pialert.conf` moved from `back` to `config` folder
- `pialert.conf` splitted in two files: `pialert.conf` and `version.conf`
- Added compatibility with Python 3 (default version installed with Ubuntu)
- Added compatibility in the Installation guide with Ubuntu server
- Eliminated some unnecessary packages from the installation
### License
GPL 3.0
[Read more here](../LICENSE.txt)
### Contact
Always use the Issue tracker for the correct fork, for example:
[jokob-sk/NetAlertX](https://github.com/jokob-sk/NetAlertX/issues). Please also follow the guidelines on:
- [Pull Request guidelines](https://github.com/jokob-sk/NetAlertX/tree/main/docs#-pull-requests-prs)
- 🙏 [Feature request guidelines](https://github.com/jokob-sk/NetAlertX/tree/main/docs#-feature-requests)
- 🐛 [Issue guidelines](https://github.com/jokob-sk/NetAlertX/tree/main/docs#-submitting-an-issue-or-bug)

View File

@@ -41,7 +41,7 @@ In the container execute:
`cat /var/log/nginx/error.log`
`cat /app/front/log/app.php_errors.log`
`cat /app/log/app.php_errors.log`
## 8. Make sure permissions are correct

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/img/showcase.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 MiB

10
front/.well-known/gpc.json Executable file
View File

@@ -0,0 +1,10 @@
{
"gpc": true,
"compliance": [
{
"regulation": "GDPR",
"status": "not applicable"
}
]
}

View File

@@ -9,6 +9,8 @@
</div>
</section>
<script>
// show loading dialog
@@ -17,7 +19,7 @@ showSpinner()
$(document).ready(function() {
// Load JSON data from the provided URL
$.getJSON('api/table_appevents.json', function(data) {
$.getJSON('/php/server/query_json.php?file=table_appevents.json', function(data) {
// Process the JSON data and generate UI dynamically
processData(data)
@@ -89,7 +91,3 @@ function processData(data) {
</script>
<!-- Datatable -->
<link rel="stylesheet" href="lib/datatables.net-bs/css/dataTables.bootstrap.min.css"/>
<script src="lib/datatables.net/js/jquery.dataTables.min.js"></script>
<script src="lib/datatables.net-bs/js/dataTables.bootstrap.min.js"></script>

View File

@@ -42,6 +42,12 @@ h5
cursor: help;
}
.alignRight
{
text-align: end;
float: inline-end;
}
/* -----------------------------------------------------------------------------
Text Classes
----------------------------------------------------------------------------- */
@@ -281,11 +287,6 @@ body
margin-left: 150px;
}
#settingsPage
{
display: grid;
}
@media (max-width: 767px) {
.main-header .logo {
@@ -614,7 +615,7 @@ body
.modal_red
{
color: rgb(245, 245, 245);
color: #d41001;
background-color: #e2acaa;
border-color: #d41001;
}
@@ -887,6 +888,34 @@ height: 50px;
} */
}
/* Hide unusable buttons on the settings page for the NEWDEV plugin*/
#settingsPage #add_option_NEWDEV_devGroup,
#settingsPage #add_option_NEWDEV_devLocation,
#settingsPage #add_option_NEWDEV_devOwner,
#settingsPage #copy_icons_NEWDEV_devIcon,
#settingsPage #add_icon_NEWDEV_devIcon,
#settingsPage #add_option_NEWDEV_devSite,
#settingsPage #add_option_NEWDEV_devType
{
display: none;
}
#settingsPage
{
display: grid;
}
#settingsPage .small-box .inner .card-title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: white;
}
.settingswrap
{
margin-bottom: 100px;
@@ -920,16 +949,6 @@ height: 50px;
top:0px;
}
.overview-section
{
/* border-top: solid;
border-width: medium;
border-width: medium;
border-width: 1px;
border-radius: 15px;
margin-bottom: 3px; */
}
.settings-group i{
font-size: 16px;
@@ -1226,6 +1245,24 @@ input[readonly] {
/* Devices page */
/* ----------------------------------------------------------------- */
#columnFilters {
display: flex;
flex-wrap: wrap;
gap: 10px; /* Add spacing between items */
}
.filter-group {
box-sizing: border-box; /* Ensure padding and borders are included in the width */
padding: 1em;
padding-top: 0;
}
.filter-dropdown
{
width:7em
}
.modal-header .close
{
display: flex;
@@ -1260,6 +1297,11 @@ input[readonly] {
display: flex;
}
.iconColumn
{
max-height: 25px;
}
.iconPreviewSelector svg
{
width:40px;
@@ -1276,14 +1318,39 @@ input[readonly] {
min-width: 40px;
}
.iconPreview svg{
min-width: 20px;
max-width: 20px;
}
.dummyDevice
{
text-align: end;
}
#tableDevices .fab
{
font-size: 1.2em;
}
#tableDevices .fa
{
font-size: 1.0em;
}
#tableDevices tbody tr
{
height: 1.5em;
}
.info-icon-nav
{
top: -6px;
position: absolute;
z-index: 1;
left: 0px;
font-size: large;
left: 4px;
font-size: smaller;
}
.drag
@@ -1344,10 +1411,94 @@ input[readonly] {
text-align: left;
}
#devicePageInfoPlc
{
display: none;
padding-top: 2em;
cursor: default;
}
/* ----------------------------------------------------------------- */
/* Device details */
/* ----------------------------------------------------------------- */
.dataTables_length label .form-control, .dataTables_filter label .form-control
{
float: none;
}
.form-inline .input-group
{
width: 100%;
}
.input-group .actionIcon
{
height: 2.4em;
}
.input-group .actionIcon:hover
{
backdrop-filter: brightness(50%);
background-color: rgba(0, 0, 0, 0.2); /* darkens the background */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
transition: box-shadow 0.1s ease-in-out, background-color 0.1s ease-in-out;
}
.devicePropAction
{
width: 1.2em;
height: 1.2em;
display: inline-block;
padding: 0.1em;
margin: 0.1em;
/* transition: font-size 0.3s;*/
}
.devicePropAction:hover
{
font-size: larger;
padding: 0em;
margin: 0em;
}
#panDetails .dataTables_wrapper .bottom div
{
max-width: 34%;
display: block;
float:inline-end;
height: 2em;
}
#panDetails .dataTables_wrapper .bottom .dataTables_info
{
float:inline-start;
}
#panDetails .dataTables_wrapper .bottom .dataTables_length
{
padding: 0.3em;
}
/* #panDetails .dataTables_wrapper .bottom .paging_simple_numbers */
#panDetails #NEWDEV_devIcon
{
display: none;
}
#panDetails #NEWDEV_devCustomProps_label
{
display: none;
}
/* ----------------------------------------------------------------- */
/* MODAL popups */
/* ----------------------------------------------------------------- */
#modal-input-textarea
{
width: 100%;
@@ -1378,6 +1529,14 @@ input[readonly] {
flex-direction:column;
justify-content:center;
}
.networkHelpIcon
{
padding: 5px;
margin-left: 5px;
top: 55px;
position: absolute;
z-index:5;
}
#networkTree .netNodeText
{
position: absolute;
@@ -1438,7 +1597,7 @@ input[readonly] {
.spanNetworkTree {
display: inline-block;
width: 120px;
width: 135px;
white-space: nowrap;
overflow: hidden !important;
text-overflow: ellipsis;
@@ -1484,6 +1643,24 @@ input[readonly] {
{
display: none;
}
.dev-detail-tab-name
{
display: none;
}
}
/* ----------------------------------------------------------------- */
/* EVENTS page */
/* ----------------------------------------------------------------- */
.eventsPeriodSelectWrap{
display: inline;
float: right;
}
.eventsPage #tableEventsTitle
{
float: left ;
}
/* ----------------------------------------------------------------- */
@@ -1491,6 +1668,7 @@ input[readonly] {
/* ----------------------------------------------------------------- */
.plugin-filters
{
margin: 7px;
@@ -1518,6 +1696,7 @@ input[readonly] {
.integrations-plugins .content
{
display: table;
width: 100%;
}
.plugin-content .tab-content
@@ -1569,13 +1748,6 @@ input[readonly] {
}
}
#settingsPage .small-box .inner .card-title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: white;
}
.textOverflow
{
@@ -1673,16 +1845,14 @@ input[readonly] {
----------------------------------------------------------------------------- */
#multiEditPlc
{
position: fixed;
bottom: 50px;
right: 0px;
z-index: 10;
padding-right: 10px;
}
table.dataTable tbody > tr.selected
{
color:red;
/* color:red; */
color: #353c42;
}
/* -----------------------------------------------------------------------------

View File

@@ -17,9 +17,9 @@ html {
background-color: #353c42;
}
body {
/* body {
background-image: url('../img/boxed-bg-dark.png') !important;
}
} */
body, .bg-yellow, .callout.callout-warning, .alert-warning, .label-warning, .modal-warning .modal-body {
@@ -732,4 +732,12 @@ input[type="password"]::-webkit-caps-lock-indicator {
background-color: #000 !important;
}
.callout code {
background-color: #fff !important;
color:#000 !important;
}
.thresholdFormControl
{
color:#000;
}

View File

@@ -19,9 +19,9 @@
background-color: #353c42;
}
body {
/* body {
background-image: url('../img/boxed-bg-dark.png') !important;
}
} */
body, .bg-yellow, .callout.callout-warning, .alert-warning, .label-warning, .modal-warning .modal-body {
@@ -735,3 +735,13 @@
}
}
.callout code {
background-color: #fff !important;
color:#000 !important;
}
.thresholdFormControl
{
color:#000;
}

View File

@@ -17,6 +17,8 @@
?>
<!-- Page ------------------------------------------------------------------ -->
<div class="content-wrapper">
@@ -28,6 +30,14 @@
&nbsp<small>Quering device info...</small>
</h1>
<div id="devicePageInfoPlc" class="card-body bg-light">
<div class="small-box panel rounded">
<div class="inner text-center">
</div>
</div>
</div>
<!-- period selector -->
<span class="breadcrumb" style="top: 0px;">
<select class="form-control" id="period" onchange="javascript: periodChanged();">
@@ -51,12 +61,54 @@
<!-- <div class="box-transparent"> -->
<div id="navDevice" class="nav-tabs-custom">
<ul class="nav nav-tabs" style="font-size:16px;">
<li> <a id="tabDetails" href="#panDetails" data-toggle="tab"> <?= lang('DevDetail_Tab_Details');?> </a></li>
<li> <a id="tabTools" href="#panTools" data-toggle="tab"> <?= lang('DevDetail_Tab_Tools');?> </a></li>
<li> <a id="tabSessions" href="#panSessions" data-toggle="tab"> <?= lang('DevDetail_Tab_Sessions');?> </a></li>
<li> <a id="tabPresence" href="#panPresence" data-toggle="tab"> <?= lang('DevDetail_Tab_Presence');?> </a></li>
<li> <a id="tabEvents" href="#panEvents" data-toggle="tab"> <?= lang('DevDetail_Tab_Events');?> </a></li>
<li> <a id="tabPlugins" href="#panPlugins" data-toggle="tab"> <?= lang('DevDetail_Tab_Plugins');?> </a></li>
<li>
<a id="tabDetails" href="#panDetails" data-toggle="tab">
<i class="fa fa-info-circle"></i>
<span class="dev-detail-tab-name">
<?= lang('DevDetail_Tab_Details');?>
</span>
</a>
</li>
<li>
<a id="tabTools" href="#panTools" data-toggle="tab">
<i class="fa fa-screwdriver-wrench"></i>
<span class="dev-detail-tab-name">
<?= lang('DevDetail_Tab_Tools');?>
</span>
</a>
</li>
<li>
<a id="tabSessions" href="#panSessions" data-toggle="tab">
<i class="fa fa-list-ol"></i>
<span class="dev-detail-tab-name">
<?= lang('DevDetail_Tab_Sessions');?>
</span>
</a>
</li>
<li>
<a id="tabPresence" href="#panPresence" data-toggle="tab">
<i class="fa fa-calendar"></i>
<span class="dev-detail-tab-name">
<?= lang('DevDetail_Tab_Presence');?>
</span>
</a>
</li>
<li>
<a id="tabEvents" href="#panEvents" data-toggle="tab">
<i class="fa fa-bolt"></i>
<span class="dev-detail-tab-name">
<?= lang('DevDetail_Tab_Events');?>
</span>
</a>
</li>
<li>
<a id="tabPlugins" href="#panPlugins" data-toggle="tab">
<i class="fa fa-plug"></i>
<span class="dev-detail-tab-name">
<?= lang('DevDetail_Tab_Plugins');?>
</span>
</a>
</li>
<div class="btn-group pull-right">
<button type="button" class="btn btn-default" style="padding: 10px; min-width: 30px;"
@@ -86,20 +138,10 @@
<!-- tab page 2 ------------------------------------------------------------ -->
<div class="tab-pane fade table-responsive" id="panSessions">
<!-- Datatable Session -->
<table id="tableSessions" class="table table-bordered table-hover table-striped ">
<thead>
<tr>
<th><?= lang('DevDetail_SessionTable_Order');?></th>
<th><?= lang('DevDetail_SessionTable_Connection');?></th>
<th><?= lang('DevDetail_SessionTable_Disconnection');?></th>
<th><?= lang('DevDetail_SessionTable_Duration');?></th>
<th><?= lang('DevDetail_SessionTable_IP');?></th>
<th><?= lang('DevDetail_SessionTable_Additionalinfo');?></th>
</tr>
</thead>
</table>
<?php
require 'deviceDetailsSessions.php';
?>
</div>
@@ -115,35 +157,24 @@
</div>
<!-- tab page 3 ------------------------------------------------------------ -->
<div class="tab-pane fade table-responsive" id="panPresence">
<div class="tab-pane fade table-responsive" id="panPresence">
<?php
// Include the other page
include 'deviceDetailsPresence.php';
?>
<!-- Calendar -->
<div id="calendar">
</div>
</div>
<!-- tab page 4 ------------------------------------------------------------ -->
<div class="tab-pane fade table-responsive" id="panEvents">
<!-- Hide Connections -->
<div class="text-center">
<label>
<input class="checkbox blue hidden" id="chkHideConnectionEvents" type="checkbox" checked>
<?= lang('DevDetail_Events_CheckBox');?>
</label>
</div>
<?php
// Include the other page
include 'deviceDetailsEvents.php';
?>
<!-- Datatable Events -->
<table id="tableEvents" class="table table-bordered table-hover table-striped ">
<thead>
<tr>
<th><?= lang("DevDetail_Tab_EventsTableDate");?></th>
<th><?= lang("DevDetail_Tab_EventsTableEvent");?></th>
<th><?= lang("DevDetail_Tab_EventsTableIP");?></th>
<th><?= lang("DevDetail_Tab_EventsTableInfo");?></th>
</tr>
</thead>
</table>
</div>
<!-- tab page 7 ------------------------------------------------------------ -->
@@ -179,25 +210,6 @@
require 'php/templates/footer.php';
?>
<!-- ----------------------------------------------------------------------- -->
<!-- iCkeck -->
<link rel="stylesheet" href="lib/iCheck/all.css">
<script src="lib/iCheck/icheck.min.js"></script>
<!-- Datatable -->
<link rel="stylesheet" href="lib/datatables.net-bs/css/dataTables.bootstrap.min.css">
<script src="lib/datatables.net/js/jquery.dataTables.min.js"></script>
<script src="lib/datatables.net-bs/js/dataTables.bootstrap.min.js"></script>
<!-- fullCalendar -->
<link rel="stylesheet" href="lib/fullcalendar/fullcalendar.min.css">
<link rel="stylesheet" href="lib/fullcalendar/fullcalendar.print.min.css" media="print">
<script src="lib/moment/moment.js"></script>
<script src="lib/fullcalendar/fullcalendar.min.js"></script>
<script src="lib/fullcalendar/locale-all.js"></script>
<!-- ----------------------------------------------------------------------- -->
<!-- ----------------------------------------------------------------------- -->
@@ -222,18 +234,10 @@ switch ($UI_THEME) {
mac = getMac() // can also be rowID!! not only mac
var devicesList = []; // this will contain a list the database row IDs of the devices ordered by the position displayed in the UI
var pos = -1;
var parPeriod = 'Front_Details_Period';
var parSessionsRows = 'Front_Details_Sessions_Rows';
var parEventsRows = 'Front_Details_Events_Rows';
var parEventsHide = 'Front_Details_Events_Hide';
var period = '1 month';
var tab = 'tabDetails'
var sessionsRows = 10;
var eventsRows = 10;
var eventsHide = true;
var skipRepeatedItems = ['0 h (notify all events)', '1 h', '8 h', '24 h', '168 h (one week)'];
var selectedTab = 'tabDetails';
var emptyArr = ['undefined', "", undefined, null];
@@ -272,22 +276,15 @@ function main () {
$('#chkHideConnectionEvents')[0].checked = eval(eventsHide == 'true');
// Initialize components with parameters
initializeTabs();
initializeDatatables();
initializeCalendar();
// query data
getSessionsPresenceEvents();
// Force re-render calendar on tab change
// (bugfix for render error at left panel)
$(document).on('shown.bs.tab', 'a[data-toggle="tab"]', function (nav) {
if ($(nav.target).attr('href') == '#panPresence') {
$('#calendar').fullCalendar('rerenderEvents');
}
// Init tabs once DOM ready
$( document ).ready(function() {
initializeTabs();
});
// Events tab toggle conenction events
$('input').on('ifToggled', function(event){
@@ -308,205 +305,11 @@ function main () {
}
// -----------------------------------------------------------------------------
function initializeDatatables () {
// Sessions datatable
$('#tableSessions').DataTable({
'paging' : true,
'lengthChange': true,
'lengthMenu' : [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, 'All']],
'searching' : true,
'ordering' : true,
'info' : true,
'autoWidth' : false,
'order' : [[0,'desc'], [1,'desc']],
// Parameters
'pageLength' : sessionsRows,
'columnDefs' : [
{visible: false, targets: [0]},
// Replace HTML codes
{targets: [1,2,3,5],
'createdCell': function (td, cellData, rowData, row, col) {
$(td).html (translateHTMLcodes (cellData));
} }
],
// Processing
'processing' : true,
'language' : {
processing: '<table><td width="130px" align="middle"><?= lang("DevDetail_Loading");?></td>'+
'<td><i class="ion ion-ios-loop-strong fa-spin fa-2x fa-fw">'+
'</td></table>',
emptyTable: 'No data',
"lengthMenu": "<?= lang('Events_Tablelenght');?>",
"search": "<?= lang('Events_Searchbox');?>: ",
"paginate": {
"next": "<?= lang('Events_Table_nav_next');?>",
"previous": "<?= lang('Events_Table_nav_prev');?>"
},
"info": "<?= lang('Events_Table_info');?>",
}
});
// Events datatable
$('#tableEvents').DataTable({
'paging' : true,
'lengthChange': true,
'lengthMenu' : [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, 'All']],
'searching' : true,
'ordering' : true,
'info' : true,
'autoWidth' : false,
'order' : [[0,'desc']],
// Parameters
'pageLength' : eventsRows,
'columnDefs' : [
// Replace HTML codes
{targets: [0],
'createdCell': function (td, cellData, rowData, row, col) {
$(td).html (translateHTMLcodes (cellData));
} }
],
// Processing
'processing' : true,
'language' : {
processing: '<table><td width="130px" align="middle"><?= lang("DevDetail_Loading");?></td>'+
'<td><i class="ion ion-ios-loop-strong fa-spin fa-2x fa-fw">'+
'</td></table>',
emptyTable: 'No data',
"lengthMenu": "<?= lang('Events_Tablelenght');?>",
"search": "<?= lang('Events_Searchbox');?>: ",
"paginate": {
"next": "<?= lang('Events_Table_nav_next');?>",
"previous": "<?= lang('Events_Table_nav_prev');?>"
},
"info": "<?= lang('Events_Table_info');?>",
}
});
};
// -----------------------------------------------------------------------------
function initializeCalendar () {
$('#calendar').fullCalendar({
editable : false,
droppable : false,
defaultView : 'agendaMonth',
height : 'auto',
firstDay : 1,
allDaySlot : false,
slotDuration : '02:00:00',
slotLabelInterval : '04:00:00',
slotLabelFormat : 'H:mm',
timeFormat : 'H:mm',
locale : '<?= lang('Presence_CalHead_lang');?>',
header: {
left : 'prev,next today',
center : 'title',
right : 'agendaYear,agendaMonth,agendaWeek'
},
views: {
agendaYear: {
type : 'agenda',
duration : { year: 1 },
buttonText : '<?= lang('Presence_CalHead_year');?>',
columnHeaderFormat : ''
},
agendaMonth: {
type : 'agenda',
duration : { month: 1 },
buttonText : '<?= lang('Presence_CalHead_month');?>',
columnHeaderFormat : 'D'
},
agendaWeek: {
buttonText : '<?= lang('Presence_CalHead_week');?>',
},
agendaDay: {
type : 'agenda',
duration : { day: 1 },
buttonText : '<?= lang('Presence_CalHead_day');?>',
slotLabelFormat : 'H',
slotDuration : '01:00:00'
}
},
viewRender: function(view) {
if (view.name === 'agendaYear') {
var listHeader = $('.fc-day-header')[0];
var listContent = $('.fc-widget-content')[0];
for (i=0; i < listHeader.length-2 ; i++) {
listHeader[i].style.borderColor = 'transparent';
listContent[i+2].style.borderColor = 'transparent';
if (listHeader[i].innerHTML != '<span></span>') {
if (i==0) {
listHeader[i].style.borderLeftColor = '#808080';
} else {
listHeader[i-1].style.borderRightColor = '#808080';
listContent[i+1].style.borderRightColor = '#808080';
}
listHeader[i].style.paddingLeft = '10px';
}
};
}
},
columnHeaderText: function(mom) {
switch ($('#calendar').fullCalendar('getView').name) {
case 'agendaYear':
if (mom.date() == 1) {
return mom.format('MMM');
} else {
return '';
}
break;
case 'agendaMonth':
return mom.date();
break;
case 'agendaWeek':
return mom.format ('ddd D');
break;
default:
return mom.date();
}
},
eventRender: function (event, element) {
$(element).tooltip({container: 'body', placement: 'bottom',
title: event.tooltip});
// element.attr ('title', event.tooltip); // Alternative tooltip
},
loading: function( isLoading, view ) {
if (isLoading) {
showSpinner()
} else {
// setTimeout(() => {
// updateIconPreview($('#txtIcon'))
// }, 500);
hideSpinner()
}
}
})
}
// -----------------------------------------------------------------------------
function periodChanged () {
getSessionsPresenceEvents();
loadSessionsData();
loadPresenceData();
loadEventsData();
}
@@ -531,6 +334,8 @@ function performSwitch(direction)
{
somethingChanged = false;
devicesList = getDevicesList()
// Update the global position in the devices list variable 'pos'
if (direction === "next") {
if (pos < devicesList.length - 1) {
@@ -545,8 +350,6 @@ function performSwitch(direction)
// Get the new MAC address from devicesList
mac = devicesList[pos].devMac.toString();
console.log(mac);
setCache("naxDeviceDetailsMac", mac);
// Update the query string with the new MAC and refresh the page
@@ -556,33 +359,6 @@ function performSwitch(direction)
}
// -----------------------------------------------------------------------------
function getSessionsPresenceEvents () {
// Check MAC in url
var urlParams = new URLSearchParams(window.location.search);
mac = urlParams.get ('mac');
// Define Sessions datasource and query dada
$('#tableSessions').DataTable().ajax.url('php/server/events.php?action=getDeviceSessions&mac=' + mac +'&period='+ period).load();
// Define Presence datasource and query data
$('#calendar').fullCalendar('removeEventSources');
$('#calendar').fullCalendar('addEventSource',
{ url: 'php/server/events.php?action=getDevicePresence&mac=' + mac});
// Query events
getDeviceEvents();
}
// -----------------------------------------------------------------------------
function getDeviceEvents () {
// Define Events datasource and query dada
hideConnections = $('#chkHideConnectionEvents')[0].checked;
$('#tableEvents').DataTable().ajax.url(
'php/server/events.php?action=getDeviceEvents&mac=' + mac +'&period='+ period +'&hideConnections='+ hideConnections).load();
}
// -----------------------------------------------------------------------------
// Activate save & restore on any value change
$(document).on('input', 'input:text', function() {
@@ -602,7 +378,10 @@ function initializeTabs () {
{
selectedTab = getCache(key);
}
$('.nav-tabs a[id='+ selectedTab +']').tab('show');
// $('.nav-tabs a[id='+ selectedTab +']').parent().click();
// $('.nav-tabs a[id="tabPlugins"]').tab('show');
// When changed save new current tab
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
@@ -621,62 +400,10 @@ function initializeTabs () {
}
//-----------------------------------------------------------------------------------
function initTable(tableId, mac){
// clear table
$("#"+tableId+" tbody").remove();
// Events datatable
$('#'+tableId).DataTable({
'paging' : true,
'lengthChange': true,
'lengthMenu' : [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, 'All']],
'searching' : true,
'ordering' : true,
'info' : true,
'autoWidth' : false,
'order' : [[0,'desc']],
// Parameters
'pageLength' : 50,
'columnDefs' : [
// Replace HTML codes
{targets: [0],
'createdCell': function (td, cellData, rowData, row, col) {
$(td).html (translateHTMLcodes (cellData));
} }
],
// Processing
'processing' : true,
'language' : {
processing: '<table><td width="130px" align="middle"><?= lang("DevDetail_Loading");?></td>'+
'<td><i class="ion ion-ios-loop-strong fa-spin fa-2x fa-fw">'+
'</td></table>',
emptyTable: 'No data',
"lengthMenu": "<?= lang('Events_Tablelenght');?>",
"search": "<?= lang('Events_Searchbox');?>: ",
"paginate": {
"next": "<?= lang('Events_Table_nav_next');?>",
"previous": "<?= lang('Events_Table_nav_prev');?>"
},
"info": "<?= lang('Events_Table_info');?>",
}
});
$("#"+tableId).attr("data-mac", mac)
}
//------------------------------------------------------------------------------
// Render the small boxes on top
async function renderSmallBoxes() {
try {
// Show loading dialog
showSpinner();

View File

@@ -14,26 +14,21 @@
<!-- Buttons -->
<div class="col-xs-12">
<div class="pull-right">
<button type="button"
class="btn btn-default pa-btn pa-btn-delete"
style="margin-left:0px;"
id="btnDeleteEvents"
onclick="askDeleteDeviceEvents()">
<?= lang('DevDetail_button_DeleteEvents');?>
</button>
<button type="button"
class="btn btn-default pa-btn pa-btn-delete"
style="margin-left:0px;"
id="btnDelete"
onclick="askDeleteDevice()">
<?= lang('DevDetail_button_Delete');?>
<i class="fas fa-trash-alt"></i>
<?= lang('DevDetail_button_Delete');?>
</button>
<button type="button"
class="btn btn-primary pa-btn"
style="margin-left:6px; "
id="btnSave"
onclick="setDeviceData()" >
<?= lang('DevDetail_button_Save');?>
<i class="fas fa-save"></i>
<?= lang('DevDetail_button_Save');?>
</button>
</div>
</div>
@@ -68,47 +63,70 @@
// some race condition, need to implement delay
setTimeout(() => {
$.get('api/table_settings.json?nocache=' + Date.now(), function(res) {
$.get('/php/server/query_json.php', { file: 'table_settings.json', nocache: Date.now() }, function(res) {
settingsData = res["data"];
// columns to hide
hiddenFields = ["NEWDEV_devScan", "NEWDEV_devPresentLastScan" ]
// columns to disable - conditional depending if a new dummy device is created
disabledFields = mac == "new" ? ["NEWDEV_devLastNotification", "NEWDEV_devFirstConnection", "NEWDEV_devLastConnection"] : ["NEWDEV_devLastNotification", "NEWDEV_devFirstConnection", "NEWDEV_devLastConnection", "NEWDEV_devMac", "NEWDEV_devLastIP" ];
disabledFields = mac == "new" ? ["NEWDEV_devLastNotification", "NEWDEV_devFirstConnection", "NEWDEV_devLastConnection"] : ["NEWDEV_devLastNotification", "NEWDEV_devFirstConnection", "NEWDEV_devLastConnection", "NEWDEV_devMac", "NEWDEV_devLastIP", "NEWDEV_devSyncHubNode" ];
// Grouping of fields into categories with associated documentation links
const fieldGroups = {
// Group for device main information
DevDetail_MainInfo_Title: {
data: ["devMac", "devLastIP", "devName", "devOwner", "devType", "devVendor", "devGroup", "devIcon", "devLocation", "devComments"],
docs: "https://github.com/jokob-sk/NetAlertX/blob/main/docs/NOTIFICATIONS.md",
iconClass: "fa fa-pencil"
docs: "https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICE_MANAGEMENT.md",
iconClass: "fa fa-pencil",
inputGroupClasses: "field-group main-group col-lg-4 col-sm-6 col-xs-12",
labelClasses: "col-sm-4 col-xs-12 control-label",
inputClasses: "col-sm-8 col-xs-12 input-group"
},
// Group for session information
DevDetail_SessionInfo_Title: {
data: ["devStatus", "devLastConnection", "devFirstConnection"],
docs: "https://github.com/jokob-sk/NetAlertX/blob/main/docs/NOTIFICATIONS.md",
iconClass: "fa fa-calendar"
docs: "https://github.com/jokob-sk/NetAlertX/blob/main/docs/SESSION_INFO.md",
iconClass: "fa fa-calendar",
inputGroupClasses: "field-group session-group col-lg-4 col-sm-6 col-xs-12",
labelClasses: "col-sm-4 col-xs-12 control-label",
inputClasses: "col-sm-8 col-xs-12 input-group"
},
// Group for event and alert settings
DevDetail_EveandAl_Title: {
data: ["devAlertEvents", "devAlertDown", "devSkipRepeated"],
docs: "https://github.com/jokob-sk/NetAlertX/blob/main/docs/NOTIFICATIONS.md",
iconClass: "fa fa-bell"
iconClass: "fa fa-bell",
inputGroupClasses: "field-group alert-group col-lg-4 col-sm-6 col-xs-12",
labelClasses: "col-sm-4 col-xs-12 control-label",
inputClasses: "col-sm-8 col-xs-12 input-group"
},
// Group for network details
DevDetail_MainInfo_Network_Title: {
data: ["devParentMAC", "devParentPort", "devSSID", "devSite"],
data: ["devParentMAC", "devParentPort", "devSSID", "devSite", "devSyncHubNode"],
docs: "https://github.com/jokob-sk/NetAlertX/blob/main/docs/NETWORK_TREE.md",
iconClass: "fa fa-network-wired"
iconClass: "fa fa-network-wired",
inputGroupClasses: "field-group network-group col-lg-4 col-sm-6 col-xs-12",
labelClasses: "col-sm-4 col-xs-12 control-label",
inputClasses: "col-sm-8 col-xs-12 input-group"
},
// Group for other fields like static IP, archived status, etc.
DevDetail_DisplayFields_Title: {
data: ["devStaticIP", "devIsNew", "devFavorite", "devIsArchived"],
docs: "https://github.com/jokob-sk/NetAlertX/blob/main/docs/NOTIFICATIONS.md",
iconClass: "fa fa-list-check"
docs: "https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICE_DISPLAY_SETTINGS.md",
iconClass: "fa fa-list-check",
inputGroupClasses: "field-group display-group col-lg-4 col-sm-6 col-xs-12",
labelClasses: "col-sm-4 col-xs-12 control-label",
inputClasses: "col-sm-8 col-xs-12 input-group"
},
// Group for Custom properties.
DevDetail_CustomProperties_Title: {
data: ["devCustomProps"],
docs: "https://github.com/jokob-sk/NetAlertX/blob/main/docs/CUSTOM_PROPERTIES.md",
iconClass: "fa fa-list",
inputGroupClasses: "field-group cutprop-group col-lg-12 col-sm-12 col-xs-12",
labelClasses: "col-sm-12 col-xs-12 control-label",
inputClasses: "col-sm-12 col-xs-12 input-group"
}
};
@@ -126,7 +144,7 @@
// Loop over each field group to generate sections for each category
Object.entries(fieldGroups).forEach(([groupName, obj]) => {
const groupDiv = $('<div>').addClass('field-group col-lg-4 col-sm-6 col-xs-12'); // Create a div for each group with responsive Bootstrap classes
const groupDiv = $('<div>').addClass(obj.inputGroupClasses); // Create a div for each group with responsive Bootstrap classes
// Add group title and documentation link
groupDiv.append(`<h5><i class="${obj.iconClass}"></i> ${getString(groupName)}
@@ -137,7 +155,7 @@
</span>
</h5>
<hr>
`);
`);
// Filter relevant settings for the current group
const groupSettings = settings.filter(set => obj.data.includes(set.setKey.replace('NEWDEV_', '')));
@@ -156,7 +174,7 @@
// Additional form elements like the random MAC address button for devMac
let inlineControl = "";
// handle rendom mac
if (setting.setKey == "NEWDEV_devMac" && deviceData["devRandomMAC"] == true) {
if (setting.setKey == "NEWDEV_devMac" && deviceData["devIsRandomMAC"] == true) {
inlineControl += `<span class="input-group-addon pointer"
title="${getString("RandomMAC_hover")}">
<a href="https://github.com/jokob-sk/NetAlertX/blob/main/docs/RANDOM_MAC.md" target="_blank">
@@ -193,15 +211,15 @@
// Generate the input field HTML
const inputFormHtml = `<div class="form-group col-xs-12">
<label class="col-sm-4 col-xs-12 control-label"> ${setting.setName}
<label id="${setting.setKey}_label" class="${obj.labelClasses}" > ${setting.setName}
<i my-set-key="${setting.setKey}"
title="${getString("Settings_Show_Description")}"
class="fa fa-circle-info pointer helpIconSmallTopRight"
onclick="showDescriptionPopup(this)">
</i>
</label>
<div class="col-sm-8 col-xs-12 input-group">
${generateFormHtml(setting, fieldData.toString())}
<div class="${obj.inputClasses}">
${generateFormHtml(settingsData, setting, fieldData.toString(), null, null)}
${inlineControl}
</div>
</div>`;
@@ -214,20 +232,24 @@
});
// wait until everything is initialized to update icon
updateIconPreview();
// wait until everything is initialized to update icons
updateAllIconPreviews();
// update readonly fields
handleReadOnly(settingsData, disabledFields);
// Page title - Name
if (mac == "new") {
$('#pageTitle').html(getString("Gen_AddDevice"));
$('#pageTitle').html(`<i title="${getString("Gen_create_new_device")}" class="fa fa-square-plus"></i> ` + getString("Gen_create_new_device"));
$('#devicePageInfoPlc .inner').html(`<i class="fa fa-circle-info"></i> ` + getString("Gen_create_new_device_info"));
$('#devicePageInfoPlc').show();
} else if (deviceData['devOwner'] == null || deviceData['devOwner'] == '' ||
(deviceData['devName'].toString()).indexOf(deviceData['devOwner']) != -1) {
$('#pageTitle').html(deviceData['devName']);
$('#devicePageInfoPlc').hide();
} else {
$('#pageTitle').html(deviceData['devName'] + ' (' + deviceData['devOwner'] + ')');
$('#devicePageInfoPlc').hide();
}
};
@@ -313,64 +335,7 @@
showModalOK("Info", getString($(e).attr("my-set-key") + '_description'))
}
// -----------------------------------------------------------------------------
function askDeleteDeviceEvents () {
// Check MAC
if (mac == '') {
return;
}
// Ask delete device Events
showModalWarning ('<?= lang('DevDetail_button_DeleteEvents');?>', '<?= lang('DevDetail_button_DeleteEvents_Warning');?>',
'<?= lang('Gen_Cancel');?>', '<?= lang('Gen_Delete');?>', 'deleteDeviceEvents');
}
function deleteDeviceEvents () {
// Check MAC
if (mac == '') {
return;
}
// Delete device events
$.get('php/server/devices.php?action=deleteDeviceEvents&mac='+ mac, function(msg) {
showMessage (msg);
});
// Deactivate controls
$('#panDetails :input').attr('disabled', true);
}
// -----------------------------------------------------------------------------
function askDeleteDevice () {
// Check MAC
if (mac == '') {
return;
}
// Ask delete device
showModalWarning ('Delete Device', 'Are you sure you want to delete this device?<br>(maybe you prefer to archive it)',
'<?= lang('Gen_Cancel');?>', '<?= lang('Gen_Delete');?>', 'deleteDevice');
}
// -----------------------------------------------------------------------------
function deleteDevice () {
// Check MAC
if (mac == '') {
return;
}
// Delete device
$.get('php/server/devices.php?action=deleteDevice&mac='+ mac, function(msg) {
showMessage (msg);
});
// Deactivate controls
$('#panDetails :input').attr('disabled', true);
// refresh API
updateApi("devices,appevents")
}
// -----------------------------------------------------------------------------
function setDeviceData(direction = '', refreshCallback = '') {
@@ -383,59 +348,61 @@
const createNew = mac === 'new' ? 1 : 0;
const devLastIP = $('#NEWDEV_devLastIP').val();
const newMac = $('#NEWDEV_devMac').val()
// Validate MAC and Last IP
if (mac === '' || !(isValidIPv4(devLastIP) || isValidIPv6(devLastIP))) {
if (mac === '' || !isValidMac(newMac) || !( isValidIPv4(devLastIP) || isValidIPv6(devLastIP) )) {
showMessage(getString("DeviceEdit_ValidMacIp"), 5000, "modal_red");
return;
}
showSpinner();
// update data to server
$.get('php/server/devices.php?action=setDeviceData&mac='+ $('#NEWDEV_devMac').val()
+ '&name=' + encodeURIComponent($('#NEWDEV_devName').val().replace(/'/g, ""))
+ '&owner=' + encodeURIComponent($('#NEWDEV_devOwner').val().replace(/'/g, ""))
+ '&type=' + $('#NEWDEV_devType').val()
+ '&vendor=' + encodeURIComponent($('#NEWDEV_devVendor').val().replace(/'/g, ""))
+ '&icon=' + encodeURIComponent($('#NEWDEV_devIcon').val())
+ '&favorite=' + ($('#NEWDEV_devFavorite')[0].checked * 1)
+ '&group=' + encodeURIComponent($('#NEWDEV_devGroup').val())
+ '&location=' + encodeURIComponent($('#NEWDEV_devLocation').val())
+ '&comments=' + encodeURIComponent(encodeSpecialChars($('#NEWDEV_devComments').val()))
+ '&networknode=' + $('#NEWDEV_devParentMAC').val()
+ '&networknodeport=' + $('#NEWDEV_devParentPort').val()
+ '&ssid=' + $('#NEWDEV_devSSID').val()
+ '&networksite=' + $('#NEWDEV_devSite').val()
+ '&staticIP=' + ($('#NEWDEV_devStaticIP')[0].checked * 1)
+ '&scancycle=' + "1"
+ '&alertevents=' + ($('#NEWDEV_devAlertEvents')[0].checked * 1)
+ '&alertdown=' + ($('#NEWDEV_devAlertDown')[0].checked * 1)
+ '&skiprepeated=' + $('#NEWDEV_devSkipRepeated').val().split(' ')[0]
+ '&newdevice=' + ($('#NEWDEV_devIsNew')[0].checked * 1)
+ '&archived=' + ($('#NEWDEV_devIsArchived')[0].checked * 1)
+ '&devFirstConnection=' + ($('#NEWDEV_devFirstConnection').val())
+ '&devLastConnection=' + ($('#NEWDEV_devLastConnection').val())
+ '&ip=' + ($('#NEWDEV_devLastIP').val())
+ '&createNew=' + createNew
, function(msg) {
showMessage (msg);
// Update data to server using POST
$.post('php/server/devices.php?action=setDeviceData', {
mac: $('#NEWDEV_devMac').val(),
name: encodeURIComponent($('#NEWDEV_devName').val().replace(/'/g, "")),
owner: encodeURIComponent($('#NEWDEV_devOwner').val().replace(/'/g, "")),
type: $('#NEWDEV_devType').val().replace(/'/g, ""),
vendor: encodeURIComponent($('#NEWDEV_devVendor').val().replace(/'/g, "")),
icon: encodeURIComponent($('#NEWDEV_devIcon').val()),
favorite: ($('#NEWDEV_devFavorite')[0].checked * 1),
group: encodeURIComponent($('#NEWDEV_devGroup').val().replace(/'/g, "")),
location: encodeURIComponent($('#NEWDEV_devLocation').val().replace(/'/g, "")),
comments: encodeURIComponent(encodeSpecialChars($('#NEWDEV_devComments').val())),
networknode: $('#NEWDEV_devParentMAC').val(),
networknodeport: $('#NEWDEV_devParentPort').val(),
ssid: $('#NEWDEV_devSSID').val(),
networksite: $('#NEWDEV_devSite').val(),
staticIP: ($('#NEWDEV_devStaticIP')[0].checked * 1),
scancycle: "1",
alertevents: ($('#NEWDEV_devAlertEvents')[0].checked * 1),
alertdown: ($('#NEWDEV_devAlertDown')[0].checked * 1),
skiprepeated: $('#NEWDEV_devSkipRepeated').val().split(' ')[0],
newdevice: ($('#NEWDEV_devIsNew')[0].checked * 1),
archived: ($('#NEWDEV_devIsArchived')[0].checked * 1),
devFirstConnection: ($('#NEWDEV_devFirstConnection').val()),
devLastConnection: ($('#NEWDEV_devLastConnection').val()),
devCustomProps: btoa(JSON.stringify(collectTableData("#NEWDEV_devCustomProps_table"))),
ip: ($('#NEWDEV_devLastIP').val()),
createNew: createNew
}, function(msg) {
showMessage(msg);
// Remove navigation prompt "Are you sure you want to leave..."
window.onbeforeunload = null;
somethingChanged = false;
somethingChanged = false;
// refresh API
updateApi("devices,appevents")
updateApi("devices,appevents");
// Callback fuction
// Callback function
if (typeof refreshCallback == 'function') {
refreshCallback(direction);
refreshCallback(direction);
}
// everything loaded
hideSpinner();
// Everything loaded
hideSpinner();
});
}

94
front/deviceDetailsEvents.php Executable file
View File

@@ -0,0 +1,94 @@
<?php
//------------------------------------------------------------------------------
// check if authenticated
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
?>
<!-- ----------------------------------------------------------------------- -->
<!-- Hide Connections -->
<div class="text-center">
<label>
<input class="checkbox blue hidden" id="chkHideConnectionEvents" type="checkbox" checked>
<?= lang('DevDetail_Events_CheckBox');?>
</label>
</div>
<!-- Datatable Events -->
<table id="tableEvents" class="table table-bordered table-hover table-striped ">
<thead>
<tr>
<th><?= lang("DevDetail_Tab_EventsTableDate");?></th>
<th><?= lang("DevDetail_Tab_EventsTableEvent");?></th>
<th><?= lang("DevDetail_Tab_EventsTableIP");?></th>
<th><?= lang("DevDetail_Tab_EventsTableInfo");?></th>
</tr>
</thead>
</table>
<script>
var eventsRows = 10;
var eventsHide = true;
var parEventsRows = 'Front_Details_Events_Rows';
var parEventsHide = 'Front_Details_Events_Hide';
// -----------------------------------------------------------------------------
function loadEventsData() {
// Define Events datasource and query dada
hideConnections = $('#chkHideConnectionEvents')[0].checked;
$('#tableEvents').DataTable().ajax.url('php/server/events.php?action=getDeviceEvents&mac=' + mac +'&period='+ period +'&hideConnections='+ hideConnections).load();
}
function initializeSessionsDatatable () {
// Events datatable
$('#tableEvents').DataTable({
'paging' : true,
'lengthChange': true,
'lengthMenu' : [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, 'All']],
'searching' : true,
'ordering' : true,
'info' : true,
'autoWidth' : false,
'order' : [[0,'desc']],
// Parameters
'pageLength' : eventsRows,
'columnDefs' : [
// Replace HTML codes
{targets: [0],
'createdCell': function (td, cellData, rowData, row, col) {
$(td).html (translateHTMLcodes (cellData));
} }
],
// Processing
'processing' : true,
'language' : {
processing: '<table><td width="130px" align="middle"><?= lang("DevDetail_Loading");?></td>'+
'<td><i class="ion ion-ios-loop-strong fa-spin fa-2x fa-fw">'+
'</td></table>',
emptyTable: 'No data',
"lengthMenu": "<?= lang('Events_Tablelenght');?>",
"search": "<?= lang('Events_Searchbox');?>: ",
"paginate": {
"next": "<?= lang('Events_Table_nav_next');?>",
"previous": "<?= lang('Events_Table_nav_prev');?>"
},
"info": "<?= lang('Events_Table_info');?>",
}
});
}
initializeSessionsDatatable();
loadEventsData();
</script>

240
front/deviceDetailsPresence.php Executable file
View File

@@ -0,0 +1,240 @@
<?php
//------------------------------------------------------------------------------
// check if authenticated
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
?>
<!-- fullCalendar -->
<link rel="stylesheet" href="lib/fullcalendar/fullcalendar.min.css">
<link rel="stylesheet" href="lib/fullcalendar/fullcalendar.print.min.css" media="print">
<script src="lib/moment/moment.js"></script>
<script src="lib/fullcalendar/fullcalendar.min.js"></script>
<script src="lib/fullcalendar/locale-all.js"></script>
<!-- fullCalendar Scheduler -->
<link href="lib/fullcalendar-scheduler/scheduler.min.css" rel="stylesheet">
<script src="lib/fullcalendar-scheduler/scheduler.min.js"></script>
<!-- Calendar -->
<div id="calendar">
</div>
<script>
initializeCalendar();
loadPresenceData();
// Force re-render calendar on tab change
// (bugfix for render error at left panel)
$(document).on('shown.bs.tab', 'a[data-toggle="tab"]', function (nav) {
if ($(nav.target).attr('href') == '#panPresence') {
$('#calendar').fullCalendar('rerenderEvents');
}
});
// ---------------------------------------
// query data
function loadPresenceData()
{
// Define Presence datasource and query data
$('#calendar').fullCalendar('removeEventSources');
$('#calendar').fullCalendar('addEventSource',
{ url: 'php/server/events.php?action=getDevicePresence&mac=' + mac});
}
// ---------------------------------------
function initializeCalendar_() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridYear,dayGridMonth,timeGridWeek'
},
initialView: 'dayGridYear',
initialDate: '2023-01-12',
editable: true,
selectable: true,
dayMaxEvents: true, // allow "more" link when too many events
// businessHours: true,
// weekends: false,
events: [
{
title: 'All Day Event',
start: '2023-01-01'
},
{
title: 'Long Event',
start: '2023-01-07',
end: '2023-01-10'
},
{
groupId: 999,
title: 'Repeating Event',
start: '2023-01-09T16:00:00'
},
{
groupId: 999,
title: 'Repeating Event',
start: '2023-01-16T16:00:00'
},
{
title: 'Conference',
start: '2023-01-11',
end: '2023-01-13'
},
{
title: 'Meeting',
start: '2023-01-12T10:30:00',
end: '2023-01-12T12:30:00'
},
{
title: 'Lunch',
start: '2023-01-12T12:00:00'
},
{
title: 'Meeting',
start: '2023-01-12T14:30:00'
},
{
title: 'Happy Hour',
start: '2023-01-12T17:30:00'
},
{
title: 'Dinner',
start: '2023-01-12T20:00:00'
},
{
title: 'Birthday Party',
start: '2023-01-13T07:00:00'
},
{
title: 'Click for Google',
url: 'http://google.com/',
start: '2023-01-28'
}
]
});
calendar.render();
}
// -----------------------------------------------------------------------------
function initializeCalendar() {
$('#calendar').fullCalendar({
editable : false,
droppable : false,
defaultView : 'agendaMonth',
schedulerLicenseKey: 'GPL-My-Project-Is-Open-Source',
height : 'auto',
firstDay : 1,
allDaySlot : false,
slotDuration : '02:00:00',
slotLabelInterval : '04:00:00',
slotLabelFormat : 'H:mm',
timeFormat : 'H:mm',
locale : '<?= lang('Presence_CalHead_lang');?>',
header: {
left : 'prev,next today',
center : 'title',
right : 'agendaYear,agendaMonth,agendaWeek'
},
views: {
agendaYear: {
type : 'agenda',
duration : { year: 1 },
buttonText : '<?= lang('Presence_CalHead_year');?>',
columnHeaderFormat : ''
},
agendaMonth: {
type : 'agenda',
duration : { month: 1 },
buttonText : '<?= lang('Presence_CalHead_month');?>',
columnHeaderFormat : 'D'
},
agendaWeek: {
buttonText : '<?= lang('Presence_CalHead_week');?>',
},
agendaDay: {
type : 'agenda',
duration : { day: 1 },
buttonText : '<?= lang('Presence_CalHead_day');?>',
slotLabelFormat : 'H',
slotDuration : '01:00:00'
}
},
viewRender: function(view) {
if (view.name === 'agendaYear') {
var listHeader = $('.fc-day-header')[0];
var listContent = $('.fc-widget-content')[0];
for (i=0; i < listHeader.length-2 ; i++) {
listHeader[i].style.borderColor = 'transparent';
listContent[i+2].style.borderColor = 'transparent';
if (listHeader[i].innerHTML != '<span></span>') {
if (i==0) {
listHeader[i].style.borderLeftColor = '#808080';
} else {
listHeader[i-1].style.borderRightColor = '#808080';
listContent[i+1].style.borderRightColor = '#808080';
}
listHeader[i].style.paddingLeft = '10px';
}
};
}
},
columnHeaderText: function(mom) {
switch ($('#calendar').fullCalendar('getView').name) {
case 'agendaYear':
if (mom.date() == 1) {
return mom.format('MMM');
} else {
return '';
}
break;
case 'agendaMonth':
return mom.date();
break;
case 'agendaWeek':
return mom.format ('ddd D');
break;
default:
return mom.date();
}
},
eventRender: function (event, element) {
$(element).tooltip({container: 'body', placement: 'bottom',
title: event.tooltip});
// element.attr ('title', event.tooltip); // Alternative tooltip
},
loading: function( isLoading, view ) {
if (isLoading) {
showSpinner()
} else {
hideSpinner()
}
}
})
}
</script>

81
front/deviceDetailsSessions.php Executable file
View File

@@ -0,0 +1,81 @@
<?php
//------------------------------------------------------------------------------
// check if authenticated
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
?>
<!-- ----------------------------------------------------------------------- -->
<!-- Datatable Session -->
<table id="tableSessions" class="table table-bordered table-hover table-striped ">
<thead>
<tr>
<th><?= lang('DevDetail_SessionTable_Order');?></th>
<th><?= lang('DevDetail_SessionTable_Connection');?></th>
<th><?= lang('DevDetail_SessionTable_Disconnection');?></th>
<th><?= lang('DevDetail_SessionTable_Duration');?></th>
<th><?= lang('DevDetail_SessionTable_IP');?></th>
<th><?= lang('DevDetail_SessionTable_Additionalinfo');?></th>
</tr>
</thead>
</table>
<script>
var parSessionsRows = 'Front_Details_Sessions_Rows';
var sessionsRows = 10;
var period = '1 month';
function initializeSessionsDatatable () {
// Sessions datatable
$('#tableSessions').DataTable({
'paging' : true,
'lengthChange': true,
'lengthMenu' : [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, 'All']],
'searching' : true,
'ordering' : true,
'info' : true,
'autoWidth' : false,
'order' : [[0,'desc'], [1,'desc']],
// Parameters
'pageLength' : sessionsRows,
'columnDefs' : [
{visible: false, targets: [0]},
// Replace HTML codes
{targets: [1,2,3,5],
'createdCell': function (td, cellData, rowData, row, col) {
$(td).html (translateHTMLcodes (cellData));
} }
],
// Processing
'processing' : true,
'language' : {
processing: '<table><td width="130px" align="middle"><?= lang("DevDetail_Loading");?></td>'+
'<td><i class="ion ion-ios-loop-strong fa-spin fa-2x fa-fw">'+
'</td></table>',
emptyTable: 'No data',
"lengthMenu": "<?= lang('Events_Tablelenght');?>",
"search": "<?= lang('Events_Searchbox');?>: ",
"paginate": {
"next": "<?= lang('Events_Table_nav_next');?>",
"previous": "<?= lang('Events_Table_nav_prev');?>"
},
"info": "<?= lang('Events_Table_info');?>",
}
});
}
function loadSessionsData(){
$('#tableSessions').DataTable().ajax.url('php/server/events.php?action=getDeviceSessions&mac=' + getMac() +'&period='+ period).load();
}
initializeSessionsDatatable();
loadSessionsData();
</script>

View File

@@ -65,6 +65,48 @@
<?php } ?>
<!-- Delete Events -->
<h4 class=""><i class="fa-solid fa-bell"></i>
<?= lang("DevDetail_button_DeleteEvents") ?>
</h4>
<h5 class="">
<?= lang("DevDetail_button_DeleteEvents_Warning") ?>
</h5>
<br>
<div style="width:100%; text-align: center; margin-bottom: 50px;">
<button type="button"
class="btn btn-default pa-btn pa-btn-delete"
style="margin-left:0px;"
id="btnDeleteEvents"
onclick="askDeleteDeviceEvents()">
<?= lang('DevDetail_button_DeleteEvents');?>
</button>
<br>
<div id="wol_output" style="margin-top: 10px;"></div>
</div>
<!-- Reset Custom Proprties -->
<h4 class=""><i class="fa-solid fa-list"></i>
<?= lang("DevDetail_CustomProperties_Title") ?>
</h4>
<h5 class="">
<?= lang("DevDetail_CustomProps_reset_info") ?>
</h5>
<br>
<div style="width:100%; text-align: center; margin-bottom: 50px;">
<button type="button"
class="btn btn-default pa-btn pa-btn-delete"
style="margin-left:0px;"
id="btnDeleteEvents"
onclick="askResetDeviceProps()">
<?= lang("Gen_Reset") ?>
</button>
<br>
<div id="wol_output" style="margin-top: 10px;"></div>
</div>
<!-- SPEEDTEST -->
<?php if ($_REQUEST["mac"] == "Internet") { ?>
<h4 class=""><i class="fa-solid fa-gauge-high"></i>
@@ -336,6 +378,56 @@
return devicesList;
}
// ----------------------------------------------------------------
// -----------------------------------------------------------------------------
function askDeleteDeviceEvents () {
// Check MAC
if (mac == '') {
return;
}
// Ask delete device Events
showModalWarning ('<?= lang('DevDetail_button_DeleteEvents');?>', '<?= lang('DevDetail_button_DeleteEvents_Warning');?>',
'<?= lang('Gen_Cancel');?>', '<?= lang('Gen_Delete');?>', 'deleteDeviceEvents');
}
function deleteDeviceEvents () {
// Check MAC
if (mac == '') {
return;
}
// Delete device events
$.get('php/server/devices.php?action=deleteDeviceEvents&mac='+ mac, function(msg) {
showMessage (msg);
});
}
// -----------------------------------------------------------------------------
function askResetDeviceProps () {
// Check MAC
if (mac == '') {
return;
}
// Ask Resert Custom properties
showModalWarning ('<?= lang('Gen_Reset');?>', '<?= lang('DevDetail_CustomProps_reset_info');?>',
'<?= lang('Gen_Cancel');?>', '<?= lang('Gen_Delete');?>', 'resetDeviceProps');
}
function resetDeviceProps () {
// Check MAC
if (mac == '') {
return;
}
// Execute
$.get('php/server/devices.php?action=resetDeviceProps&mac='+ mac, function(msg) {
showMessage (msg);
});
}
// ----------------------------------------------------------------
function internetinfo() {
$( "#internetinfooutput" ).empty();

View File

@@ -22,34 +22,27 @@
checkPermissions([$dbPath, $confPath]);
?>
<!-- ----------------------------------------------------------------------- -->
<!-- Page ------------------------------------------------------------------ -->
<div class="content-wrapper">
<!-- Content header--------------------------------------------------------- -->
<section class="content-header">
<h1 id="pageTitle">
<i class="fa fa-laptop"></i>
<?= lang('Device_Title');?>
</h1>
</section>
<!-- Main content ---------------------------------------------------------- -->
<section class="content">
<!-- Tile toggle cards ------------------------------------------------------- -->
<div class="row" id="TileCards">
<div class="row " id="TileCards">
<!-- Placeholder ------------------------------------------------------- -->
</div>
<!-- Device presence / Activity Chart ------------------------------------------------------- -->
<div class="row" id="DevicePresence">
<div class="col-md-12">
<div class="box" id="clients">
<div class="box-header with-border">
<h3 class="box-title"><?= lang('Device_Shortcut_OnlineChart');?> </h3>
<div class="box" id="clients">
<div class="box-header ">
<h3 class="box-title col-md-12"><?= lang('Device_Shortcut_OnlineChart');?> </h3>
</div>
<div class="box-body">
<div class="chart">
@@ -65,6 +58,15 @@
</div>
</div>
<!-- Device Filters ------------------------------------------------------- -->
<div class="box box-aqua hidden" id="columnFiltersWrap">
<div class="box-header ">
<h3 class="box-title col-md-12"><?= lang('Devices_Filters');?> </h3>
</div>
<!-- Placeholder ------------------------------------------------------- -->
<div id="columnFilters" ></div>
</div>
<!-- datatable ------------------------------------------------------------- -->
<div class="row">
<div class="col-xs-12">
@@ -72,10 +74,17 @@
<!-- box-header -->
<div class="box-header">
<div class=" col-md-10 ">
<div class=" col-md-9 ">
<h3 id="tableDevicesTitle" class="box-title text-gray "></h3>
</div>
<div class=" col-md-2 "><a href="deviceDetails.php?mac=new"><i title="Add new dummy device" class="fa fa-square-plus"></i> <?= lang('Gen_create_new_device');?></a></div>
<div class="dummyDevice col-md-3 ">
<span id="multiEditPlc">
<!-- multi edit button placeholder -->
</span>
<span>
<a href="deviceDetails.php?mac=new"><i title="<?= lang('Gen_create_new_device');?>" class="fa fa-square-plus"></i> <?= lang('Gen_create_new_device');?></a>
</span>
</div>
</div>
<!-- table -->
@@ -100,7 +109,7 @@
<!-- ----------------------------------------------------------------------- -->
</section>
<!-- /.content -->
<div id="multiEditPlc" class="col-md-2"></div>
</div>
<!-- /.content-wrapper -->
@@ -111,19 +120,11 @@
?>
<!-- ----------------------------------------------------------------------- -->
<!-- Datatable -->
<link rel="stylesheet" href="lib/datatables.net-bs/css/dataTables.bootstrap.min.css">
<link rel="stylesheet" href="lib/datatables.net/css/select.dataTables.min.css">
<script src="lib/datatables.net/js/jquery.dataTables.min.js"></script>
<script src="lib/datatables.net-bs/js/dataTables.bootstrap.min.js"></script>
<script src="lib/datatables.net/js/dataTables.select.min.js"></script>
<!-- page script ----------------------------------------------------------- -->
<script>
var deviceStatus = 'all';
var tableRows = getCookie ("nax_parTableRows") == "" ? 10 : getCookie ("nax_parTableRows") ;
var tableOrder = getCookie ("nax_parTableOrder") == "" ? [[3,'desc'], [0,'asc']] : JSON.parse(getCookie ("nax_parTableOrder")) ;
var tableRows = getCache ("nax_parTableRows") == "" ? 10 : getCache ("nax_parTableRows") ;
var tableOrder = getCache ("nax_parTableOrder") == "" ? [[3,'desc'], [0,'asc']] : JSON.parse(getCache ("nax_parTableOrder")) ;
var tableColumnHide = [];
var tableColumnOrder = [];
@@ -140,12 +141,18 @@ function main () {
showSpinner();
initFilters();
// render tiles
getDevicesTotals();
//initialize the table headers in the correct order
var availableColumns = getSettingOptions("UI_device_columns").split(",");
headersDefaultOrder = availableColumns.map(val => getString(val));
console.log(headersDefaultOrder);
var selectedColumns = JSON.parse(getSetting("UI_device_columns").replace(/'/g, '"'));
// generate default order lists of given length
@@ -183,6 +190,9 @@ function main () {
// mapping the default order to the user specified one
function mapIndx(oldIndex)
{
// console.log(oldIndex);
// console.log(tableColumnOrder);
for(i=0;i<tableColumnOrder.length;i++)
{
if(tableColumnOrder[i] == oldIndex)
@@ -196,30 +206,61 @@ function mapIndx(oldIndex)
// Query total numbers of Devices by status
//------------------------------------------------------------------------------
function getDevicesTotals() {
maxDelay = 180; //cap at 180 seconds
// Fetch data via AJAX
$.ajax({
url: '/api/table_devices_tiles.json?nocache=' + Date.now(),
type: "GET",
dataType: "json",
success: function(response) {
if (response && response.data) {
resultJSON = response.data[0]; // Assuming the structure {"data": [ ... ]}
// Save the result to cache
setCache("getDevicesTotals", JSON.stringify(resultJSON));
let maxRetries = Math.ceil(Math.log2(maxDelay)); // Calculate maximum retries to cap at maxDelay seconds
let attempt = 0;
let calledUpdateAPI = false;
// Process the fetched data
processDeviceTotals(resultJSON);
} else {
console.error("Invalid response format from API");
}
},
error: function(xhr, status, error) {
console.error("Failed to fetch devices data:", error);
function fetchDataWithBackoff() {
// Calculate the delay (2^attempt seconds, capped at maxDelay seconds)
const delay = Math.min(2 ** attempt, maxDelay) * 1000;
// Attempt to fetch data
$.ajax({
url: '/php/server/query_json.php',
type: "GET",
dataType: "json",
data: {
file: 'table_devices_tiles.json', // Pass the file parameter
nocache: Date.now() // Prevent caching with a timestamp
},
success: function(response) {
if (response && response.data) {
const resultJSON = response.data[0]; // Assuming the structure {"data": [ ... ]}
// Save the result to cache
setCache("getDevicesTotals", JSON.stringify(resultJSON));
// Process the fetched data
processDeviceTotals(resultJSON);
} else {
console.error("Invalid response format from API");
}
},
error: function(xhr, status, error) {
console.error("Failed to fetch devices data (Attempt " + (attempt + 1) + "):", error);
// try updating the API once
if(calledUpdateAPI == false)
{
calledUpdateAPI = true;
updateApi("devices_tiles");
}
// Retry logic
if (attempt < maxRetries) {
attempt++;
setTimeout(fetchDataWithBackoff, delay);
} else {
console.error("Maximum retries reached. Unable to fetch devices data.");
}
}
});
}
});
// Start the first fetch attempt
fetchDataWithBackoff();
}
function processDeviceTotals(devicesData) {
@@ -271,6 +312,8 @@ function processDeviceTotals(devicesData) {
//------------------------------------------------------------------------------
// Render the info boxes/tiles on top
function renderInfoboxes(customData) {
if(customData.length > 0)
{
$.ajax({
url: 'php/components/tile_cards.php', // PHP script URL
type: 'POST', // Use POST method to send data
@@ -284,49 +327,187 @@ function renderInfoboxes(customData) {
}
});
}
}
// -----------------------------------------------------------------------------
// Define a function to filter data based on deviceStatus
function filterDataByStatus(data, status) {
return data.filter(function(item) {
switch (status) {
case 'my_devices':
to_display = getSetting('UI_MY_DEVICES');
let result = true;
//Render filters if specified
let columnFilters = [];
if (!to_display.includes('down') && item.devPresentLastScan === 0 && item.devAlertDown !== 0) {
result = false;
} else if (!to_display.includes('new') && item.devIsNew === 1) {
result = false;
} else if (!to_display.includes('archived') && item.devIsArchived === 1) {
result = false;
} else if (!to_display.includes('offline') && item.devPresentLastScan === 0) {
result = false;
} else if (!to_display.includes('online') && item.devPresentLastScan === 1) {
result = false;
}
function initFilters() {
// Attempt to fetch data
$.ajax({
url: '/php/server/query_json.php',
type: "GET",
dataType: "json",
data: {
file: 'table_devices_filters.json', // Pass the file parameter
nocache: Date.now() // Prevent caching with a timestamp
},
success: function(response) {
if (response && response.data) {
let resultJSON = response.data;
return result; // Include all items for 'my_devices' status
case 'connected':
return item.devPresentLastScan === 1;
case 'favorites':
return item.devFavorite === 1;
case 'new':
return item.devIsNew === 1;
case 'offline':
return item.devPresentLastScan === 0;
case 'down':
return (item.devPresentLastScan === 0 && item.devAlertDown !== 0);
case 'archived':
return item.devIsArchived === 1;
default:
return true; // Include all items for unknown statuses
// Save the result to cache
setCache("devicesFilters", JSON.stringify(resultJSON));
// Get the displayed filters from settings
const displayedFilters = createArray(getSetting("UI_columns_filters"));
// Clear any existing filters in the DOM
$('#columnFilters').empty();
console.log(displayedFilters);
// Ensure displayedFilters is an array and not empty
if (Array.isArray(displayedFilters) && displayedFilters.length > 0) {
$('#columnFiltersWrap').removeClass("hidden");
displayedFilters.forEach(columnHeaderStringKey => {
// Get the column name using the mapping function
const columnName = getColumnNameFromLangString(columnHeaderStringKey);
// Ensure columnName is valid before proceeding
if (columnName) {
// Add the filter to the columnFilters array as [columnName, columnHeaderStringKey]
columnFilters.push([columnName, columnHeaderStringKey]);
} else {
console.warn(`Invalid column header string key: ${columnHeaderStringKey}`);
}
});
// Filter resultJSON to include only entries with columnName in columnFilters
resultJSON = resultJSON.filter(entry =>
columnFilters.some(filter => filter[0] === entry.columnName)
);
// Expand resultJSON to include the columnHeaderStringKey
resultJSON.forEach(entry => {
// Find the matching columnHeaderStringKey from columnFilters
const matchingFilter = columnFilters.find(filter => filter[0] === entry.columnName);
// Add the columnHeaderStringKey to the entry
if (matchingFilter) {
entry['columnHeaderStringKey'] = matchingFilter[1];
}
});
console.log(resultJSON);
// Transforming the data
const transformed = {
filters: []
};
// Group data by columnName
resultJSON.forEach(entry => {
const existingFilter = transformed.filters.find(filter => filter.column === entry.columnName);
if (existingFilter) {
// Add the unique columnValue to options if not already present
if (!existingFilter.options.includes(entry.columnValue)) {
existingFilter.options.push(entry.columnValue);
}
} else {
// Create a new filter entry
transformed.filters.push({
column: entry.columnName,
headerKey: entry.columnHeaderStringKey,
options: [entry.columnValue]
});
}
});
// Sort options alphabetically for better readability
transformed.filters.forEach(filter => {
filter.options.sort();
});
// Output the result
transformedJson = transformed
// Process the fetched data
renderFilters(transformedJson);
} else {
console.log("No filters to display.");
}
} else {
console.error("Invalid response format from API");
}
},
error: function(xhr, status, error) {
console.error("Failed to fetch devices data 'table_devices_filters.json':", error);
}
});
}
// -------------------------------------------
// Server side component
function renderFilters(customData) {
// console.log(JSON.stringify(customData));
// Load filter data from the JSON file
$.ajax({
url: 'php/components/devices_filters.php', // PHP script URL
data: { filterObject: JSON.stringify(customData) }, // Send customData as JSON
type: 'POST',
dataType: 'html',
success: function(response) {
// console.log(response);
$('#columnFilters').html(response); // Replace container content with fetched HTML
$('#columnFilters').removeClass('hidden'); // Show the filters container
// Trigger the draw after select change
$('.filter-dropdown').on('change', function() {
// Collect filters
const columnFilters = collectFilters();
// Update DataTable with the new filters or search value (if applicable)
$('#tableDevices').DataTable().draw();
// Optionally, apply column filters (if using filters for individual columns)
const table = $('#tableDevices').DataTable();
table.columnFilters = columnFilters; // Apply your column filters logic
table.draw();
});
},
error: function(xhr, status, error) {
console.error('Error fetching filters:', error);
}
});
}
// -------------------------------------------
// Function to collect filters
function collectFilters() {
const columnFilters = [];
// Loop through each filter group
document.querySelectorAll('.filter-group').forEach(filterGroup => {
const dropdown = filterGroup.querySelector('.filter-dropdown');
if (dropdown) {
const filterColumn = dropdown.getAttribute('data-column');
const filterValue = dropdown.value;
if (filterValue && filterColumn) {
columnFilters.push({
filterColumn: filterColumn,
filterValue: filterValue
});
}
}
});
return columnFilters;
}
// -----------------------------------------------------------------------------
// Map column index to column name for GraphQL query
function mapColumnIndexToFieldName(index, tableColumnVisible) {
// the order is important, don't change it!
@@ -356,7 +537,8 @@ function mapColumnIndexToFieldName(index, tableColumnVisible) {
"devSSID",
"devSourcePlugin",
"devPresentLastScan",
"devAlertDown"
"devAlertDown",
"devCustomProps"
];
// console.log("OrderBy: " + columnNames[tableColumnOrder[index]]);
@@ -366,6 +548,7 @@ function mapColumnIndexToFieldName(index, tableColumnVisible) {
// ---------------------------------------------------------
// Initializes the main devices list datatable
function initializeDatatable (status) {
if(!status)
@@ -416,8 +599,6 @@ function initializeDatatable (status) {
}
// todo: dynamically filter based on status
var table = $('#tableDevices').DataTable({
"serverSide": true,
"processing": true,
@@ -466,6 +647,7 @@ function initializeDatatable (status) {
devStatus
devParentChildrenCount
devIpLong
devCustomProps
}
count
}
@@ -473,7 +655,13 @@ function initializeDatatable (status) {
`;
console.log(d);
// Handle empty filters
let columnFilters = collectFilters();
if (columnFilters.length === 0) {
columnFilters = [];
}
// Prepare query variables for pagination, sorting, and search
let query = {
@@ -488,7 +676,8 @@ function initializeDatatable (status) {
"order": d.order[0].dir.toUpperCase() // Sort direction (ASC/DESC)
}] : [], // Default to an empty array if no sorting is defined
"search": d.search.value, // Search query
"status": deviceStatus
"status": deviceStatus,
"filters" : columnFilters
}
}
@@ -532,7 +721,8 @@ function initializeDatatable (status) {
device.devSSID || "",
device.devSourcePlugin || "",
device.devPresentLastScan || "",
device.devAlertDown || ""
device.devAlertDown || "",
device.devCustomProps || ""
];
const newRow = [];
@@ -559,11 +749,19 @@ function initializeDatatable (status) {
'order' : tableOrder,
'select' : true, // Enable selection
'fixedHeader': true,
'fixedHeader': {
'header': true,
'footer': true
},
'columnDefs' : [
{visible: false, targets: tableColumnHide },
{className: 'text-center', targets: [mapIndx(3), mapIndx(4), mapIndx(9), mapIndx(10), mapIndx(15), mapIndx(18)] },
{className: 'text-center', targets: [mapIndx(4), mapIndx(9), mapIndx(10), mapIndx(15), mapIndx(18)] },
{className: 'iconColumn text-center', targets: [mapIndx(3)]},
{width: '80px', targets: [mapIndx(6), mapIndx(7), mapIndx(15)] },
{width: '30px', targets: [mapIndx(10), mapIndx(13), mapIndx(18)] },
{width: '85px', targets: [mapIndx(9)] },
{width: '30px', targets: [mapIndx(3), mapIndx(10), mapIndx(13), mapIndx(18)] },
{orderData: [mapIndx(12)], targets: mapIndx(8) },
// Device Name
@@ -620,9 +818,11 @@ function initializeDatatable (status) {
<a href="http://${cellData}" class="pointer" target="_blank">
${cellData}
</a>
<a href="https://${cellData}" class="pointer" target="_blank">
<span class="alignRight">
<a href="https://${cellData}" class="pointer" target="_blank">
<i class="fa fa-lock "></i>
</a>
</a>
<span>
<span>`);
} else {
$(td).html ('');
@@ -639,6 +839,17 @@ function initializeDatatable (status) {
}
}
},
// Custom Properties
{targets: [mapIndx(26)],
'createdCell': function (td, cellData, rowData, row, col) {
if (!emptyArr.includes(cellData)){
$(td).html (`<span>${renderCustomProps(cellData, rowData[mapIndx(11)])}<span>`);
} else {
$(td).html ('');
}
}
},
// Favorite
{targets: [mapIndx(4)],
@@ -717,18 +928,18 @@ function initializeDatatable (status) {
// Save cookie Rows displayed, and Parameters rows & order
$('#tableDevices').on( 'length.dt', function ( e, settings, len ) {
setCookie ("nax_parTableRows", len, 129600); // save for 90 days
setCache ("nax_parTableRows", len, 129600); // save for 90 days
} );
$('#tableDevices').on( 'order.dt', function () {
setCookie ("nax_parTableOrder", JSON.stringify (table.order()), 129600); // save for 90 days
setCache ("nax_parTableOrder", JSON.stringify (table.order()), 129600); // save for 90 days
} );
// add multi-edit button
$('#multiEditPlc').append(
`<button type="submit" id="multiEdit" class="btn btn-primary" style="display:none" onclick="multiEditDevices();">
<i class="fa fa-pencil pointer" ></i> ${getString("Device_MultiEdit")}
</button>`)
`<span type="submit" id="multiEdit" class="pointer " style="display:none" onclick="multiEditDevices();">
<a href="#"><i class="fa fa-pencil " ></i> ${getString("Device_MultiEdit")} </a>
</span>`)
// Event listener for row selection in DataTable
$('#tableDevices').on('click', 'tr', function (e) {
@@ -742,9 +953,27 @@ function initializeDatatable (status) {
});
// search only after idle
var typingTimer; // Timer identifier
var debounceTime = 500; // Delay in milliseconds
$('input[aria-controls="tableDevices"]').off().on('keyup', function () {
clearTimeout(typingTimer); // Clear the previous timer
var searchValue = this.value;
typingTimer = setTimeout(function () {
$('#tableDevices').DataTable().search(searchValue).draw(); // Trigger the search after delay
}, debounceTime);
});
hideSpinner();
},
createdRow: function(row, data, dataIndex) {
// add devMac to the table row
$(row).attr('my-devMac', data[mapIndx(11)]);
}
});
@@ -756,11 +985,9 @@ function initializeDatatable (status) {
// -----------------------------------------------------------------------------
function handleLoadingDialog(needsReload = false)
{
// console.log(`needsReload: ${needsReload}`);
// console.log('needsReload:');
// console.log(needsReload);
$.get('log/execution_queue.log?nocache=' + Date.now(), function(data) {
$.get('/php/server/query_logs.php?file=execution_queue.log&nocache=' + Date.now(), function(data) {
if(data.includes("update_api|devices"))
{
@@ -785,72 +1012,112 @@ function handleLoadingDialog(needsReload = false)
// the Miantenance section with a 'macs' query string identifying selected devices
function multiEditDevices()
{
rows = $('#tableDevices')[0].rows
// Initialize an empty array to store selected rows
var selectedRows = [];
// console.log($('#tableDevices')[0].rows);
// Loop through each row in the HTML collection
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
// Check if the row has the 'selected' class
if (row.classList.contains('selected')) {
// If selected, push the row's data to the selectedRows array
selectedRows.push(row);
}
}
// Now, selectedRows contains all selected rows
console.log(selectedRows);
var devicesDataTableData = $('#tableDevices').dataTable().fnGetData();
var selectedDevices = [];
for (var i = 0; i < selectedRows.length; i++) {
selectedDevices.push(devicesDataTableData[selectedRows[i]._DT_RowIndex]);
}
// Now, selectedDevices contains all selected devices
// console.log(selectedDevices);
// get selected devices
var selectedDevicesDataTableData = $('#tableDevices').DataTable().rows({ selected: true, page: 'current' }).data().toArray();
console.log(selectedDevicesDataTableData);
macs = ""
for (var i = 0; i < selectedDevices.length; i++) {
macs += selectedDevices[i][mapIndx(11)] + ","; // [11] == MAC
for (var j = 0; j < selectedDevicesDataTableData.length; j++) {
macs += selectedDevicesDataTableData[j][mapIndx(11)] + ","; // [11] == MAC
}
// redirect to the Maintenance section
window.location.href = window.location.origin + '/maintenance.php#tab_multiEdit?macs=' + macs.slice(0, -1);
window.location.href = './maintenance.php#tab_multiEdit?macs=' + macs.slice(0, -1);
}
// -----------------------------------------------------------------------------
// Function collects shown devices from the DataTable
function getMacsOfShownDevices() {
rows = $('#tableDevices')[0].rows;
macs = [];
rows = $('#tableDevices')[0].rows
macs = []
// var devicesDataTableData = $('#tableDevices').dataTable().fnGetData();
var devicesDataTableData = $('#tableDevices').DataTable().rows({ selected: false, page: 'current' }).data().toArray();
var devicesDataTableData = $('#tableDevices').dataTable().fnGetData();
console.log(devicesDataTableData);
var selectedDevices = [];
// first row is the heading, skip
for (var i = 1; i < rows.length; i++) {
selectedDevices.push(devicesDataTableData[rows[i]._DT_RowIndex]);
}
var rowIndex = rows[i]._DT_RowIndex;
// Ensure the rowIndex is valid and within bounds of devicesDataTableData
if (rowIndex >= 0 && rowIndex < devicesDataTableData.length) {
selectedDevices.push(devicesDataTableData[rowIndex]);
} else {
console.log(`Invalid rowIndex: ${rowIndex} at row ${i}`);
}
}
for (var j = 0; j < selectedDevices.length; j++) {
macs.push(selectedDevices[j][mapIndx(11)]); // mapIndx(11) == MAC
// Ensure that selectedDevices[j] is not undefined
if (selectedDevices[j]) {
macs.push(selectedDevices[j][mapIndx(11)]); // mapIndx(11) == MAC
} else {
console.log(`selectedDevices[${j}] is undefined`);
}
}
return macs;
}
// -----------------------------------------------------------------------------
// Handle custom actions/properties on a device
function renderCustomProps(custProps, mac) {
// Decode and parse the custom properties
if (!isBase64(custProps)) {
console.error(`Unable to decode CustomProps for ${mac}`);
console.error(custProps);
} else{
const props = JSON.parse(atob(custProps));
let html = "";
props.forEach((propGroup, index) => {
const propMap = Object.fromEntries(
propGroup.map(prop => Object.entries(prop)[0]) // Convert array of objects to key-value pairs
);
if (propMap["CUSTPROP_show"] === true) { // Render if visible
let onClickEvent = "";
switch (propMap["CUSTPROP_type"]) {
case "show_notes":
onClickEvent = `showModalOK('${propMap["CUSTPROP_name"]}','${propMap["CUSTPROP_notes"]}')`;
break;
case "link":
onClickEvent = `window.location.href='${propMap["CUSTPROP_args"]}';`;
break;
case "link_new_tab":
onClickEvent = `openInNewTab('${propMap["CUSTPROP_args"]}')`;
break;
case "run_plugin":
onClickEvent = `alert('Not implemented')`;
break;
case "delete_dev":
onClickEvent = `askDelDevDTInline('${mac}')`;
break;
default:
break;
}
html += `<div class="pointer devicePropAction" onclick="${onClickEvent}" title="${propMap["CUSTPROP_name"]} ${propMap["CUSTPROP_args"]}"> ${atob(propMap["CUSTPROP_icon"])} </div>`;
}
});
return html;
}
return "Error, check browser Console log"
}
// -----------------------------------------------------------------------------
// Update cache with shown devices before navigating away

View File

@@ -1,70 +0,0 @@
<?php
require 'php/templates/header.php';
?>
<div id="donationsPage" class="content-wrapper">
<!-- Content header--------------------------------------------------------- -->
<section class="content-header">
<h1 id="pageTitle">
<i class="fa fa-heart"></i>
</h1>
</section>
<!-- Main content ---------------------------------------------------------- -->
<section class="content donations">
<div id="donationsText" class="box box-solid"></div>
<div class="content-header">
<h3 class="box-title " id="donationsPlatforms"></h3>
</div>
<div class="box box-solid">
<div class="box-body">
<div class="col-sm-2">
<a target="_blank" href="https://github.com/sponsors/jokob-sk">
<img alt="Sponsor Me on GitHub" src="https://i.imgur.com/X6p5ACK.png" width="150px">
</a>
</div>
<div class="col-sm-2">
<a target="_blank" href="https://www.buymeacoffee.com/jokobsk">
<img alt="Buy Me A Coffee" src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" width="117px" height="30px">
</a>
</div>
<div class="col-sm-2">
<a target="_blank" href="https://www.patreon.com/user?u=84385063">
<img alt="Support me on patreon" src="https://upload.wikimedia.org/wikipedia/commons/thumb/8/82/Patreon_logo_with_wordmark.svg/512px-Patreon_logo_with_wordmark.svg.png" width="117px">
</a>
</div>
</div>
</div>
<div class="content-header">
<h3 class="box-title " id="donationsOthers"></h3>
</div>
<div class="box box-solid">
<div class="box-body">
<div class="col-sm-12">
<ul>
<li>Bitcoin: <code>1N8tupjeCK12qRVU2XrV17WvKK7LCawyZM</code></li>
<li>Ethereum: <code>0x6e2749Cb42F4411bc98501406BdcD82244e3f9C7</code></li>
</ul>
</div>
</div>
</div>
<div>
</section>
</div> <!-- End of class="content-wrapper" -->
<script>
function init()
{
$("#donationsText").html(getString("Donations_Text"))
$("#pageTitle").append(getString("Donations_Title"))
$("#donationsPlatforms").append(getString("Donations_Platforms"))
$("#donationsOthers").append(getString("Donations_Others"))
}
init();
</script>
<?php
require 'php/templates/footer.php';
?>

View File

@@ -1,42 +1,11 @@
<!--
#---------------------------------------------------------------------------------#
# NetAlertX #
# Open Source Network Guard / WIFI & LAN intrusion detector #
# #
# events.php - Front module. Events page #
#---------------------------------------------------------------------------------#
# Puche 2021 pi.alert.application@gmail.com GNU GPLv3 #
# jokob-sk 2022 jokob.sk@gmail.com GNU GPLv3 #
# leiweibau 2022 https://github.com/leiweibau GNU GPLv3 #
# cvc90 2023 https://github.com/cvc90 GNU GPLv3 #
#---------------------------------------------------------------------------------#
-->
<?php
require 'php/templates/header.php';
?>
<!-- ----------------------------------------------------------------------- -->
<!-- Page ------------------------------------------------------------------ -->
<div class="content-wrapper">
<!-- Content header--------------------------------------------------------- -->
<section class="content-header">
<h1 id="pageTitle">
<i class="fa fa-bolt"></i>
<?= lang('Events_Title');?>
</h1>
<!-- period selector -->
<span class="breadcrumb" style="top: 0px;">
<select class="form-control" id="period" onchange="javascript: periodChanged();">
<option value="1 day"><?= lang('Events_Periodselect_today');?></option>
<option value="7 days"><?= lang('Events_Periodselect_LastWeek');?></option>
<option value="1 month" selected><?= lang('Events_Periodselect_LastMonth');?></option>
<option value="1 year"><?= lang('Events_Periodselect_LastYear');?></option>
<option value="100 years"><?= lang('Events_Periodselect_All');?></option>
</select>
</span>
</section>
<div class="content-wrapper eventsPage">
<!-- Main content ---------------------------------------------------------- -->
<section class="content">
@@ -121,15 +90,31 @@
<!-- datatable ------------------------------------------------------------- -->
<div class="row">
<div class="col-xs-12">
<div id="tableEventsBox" class="box">
<!-- box-header -->
<div class="box-header">
<h3 id="tableEventsTitle" class="box-title text-gray">Events</h3>
<div class="box-header col-xs-12">
<h3 id="tableEventsTitle" class="box-title text-gray col-xs-10">Events</h3>
<div class="eventsPeriodSelectWrap col-xs-2">
<select class="form-control" id="period" onchange="javascript: periodChanged();">
<option value="1 day"><?= lang('Events_Periodselect_today');?></option>
<option value="7 days"><?= lang('Events_Periodselect_LastWeek');?></option>
<option value="1 month" selected><?= lang('Events_Periodselect_LastMonth');?></option>
<option value="1 year"><?= lang('Events_Periodselect_LastYear');?></option>
<option value="100 years"><?= lang('Events_Periodselect_All');?></option>
</select>
</div>
</div>
<!-- table -->
<div class="box-body table-responsive">
<table id="tableEvents" class="table table-bordered table-hover table-striped ">
<thead>
<tr>
@@ -173,14 +158,6 @@
require 'php/templates/footer.php';
?>
<!-- ----------------------------------------------------------------------- -->
<!-- Datatable -->
<link rel="stylesheet" href="lib/datatables.net-bs/css/dataTables.bootstrap.min.css">
<script src="lib/datatables.net/js/jquery.dataTables.min.js"></script>
<script src="lib/datatables.net-bs/js/dataTables.bootstrap.min.js"></script>
<!-- page script ----------------------------------------------------------- -->
<script>
var parPeriod = 'nax_parPeriod';

View File

@@ -1,18 +1,9 @@
<?php
require 'php/templates/header.php';
require 'php/templates/notification.php';
?>
<!-- Page ------------------------------------------------------------------ -->
<div class="content-wrapper">
<!-- Content header--------------------------------------------------------- -->
<section class="content-header">
<?php require 'php/templates/notification.php'; ?>
<h1 id="pageTitle">
<i class="fa fa-question"></i>
<?= lang('HelpFAQ_Title');?>
</h1>
</section>
<!-- Main content ---------------------------------------------------------- -->
<section class="content">
<h4>

View File

@@ -166,8 +166,7 @@ switch ($UI_THEME) {
<!-- jQuery 3 -->
<script src="lib/jquery/jquery.min.js"></script>
<!-- Bootstrap 3.3.7 -->
<script src="lib/bootstrap/bootstrap.min.js"></script>
<!-- iCheck -->
<script src="lib/iCheck/icheck.min.js"></script>
<script>

View File

@@ -12,7 +12,7 @@ var timerRefreshData = ''
var emptyArr = ['undefined', "", undefined, null, 'null'];
var UI_LANG = "English";
const allLanguages = ["en_us", "es_es", "de_de", "fr_fr", "it_it", "ru_ru", "nb_no", "pl_pl", "pt_br", "tr_tr", "zh_cn", "cs_cz", "ar_ar", "ca_ca"]; // needs to be same as in lang.php
const allLanguages = ["en_us", "es_es", "de_de", "fr_fr", "it_it", "ru_ru", "nb_no", "pl_pl", "pt_br", "tr_tr", "zh_cn", "cs_cz", "ar_ar", "ca_ca", "uk_ua"]; // needs to be same as in lang.php
var settingsJSON = {}
@@ -115,10 +115,10 @@ function cacheSettings()
return new Promise((resolve, reject) => {
if(!getCache('completedCalls').includes('cacheSettings'))
{
$.get('api/table_settings.json?nocache=' + Date.now(), function(resSet) {
$.get('api/plugins.json?nocache=' + Date.now(), function(resPlug) {
$.get('php/server/query_json.php', { file: 'table_settings.json', nocache: Date.now() }, function(resSet) {
$.get('php/server/query_json.php', { file: 'plugins.json', nocache: Date.now() }, function(resPlug) {
pluginsData = resPlug["data"];
settingsData = resSet["data"];
@@ -171,7 +171,7 @@ function cacheSettings()
}
// -----------------------------------------------------------------------------
// Get a setting value by key
// Get a setting options value by key
function getSettingOptions (key) {
// handle initial load to make sure everything is set-up and cached
@@ -225,7 +225,7 @@ function cacheStrings() {
});
// Fetch strings and translations from plugins
$.get(`api/table_plugins_language_strings.json?nocache=${Date.now()}`)
$.get('php/server/query_json.php', { file: 'table_plugins_language_strings.json', nocache: Date.now() })
.done((pluginRes) => {
const data = pluginRes["data"];
@@ -339,6 +339,9 @@ function getLangCode() {
case 'Catalan (ca_ca)':
lang_code = 'ca_ca';
break;
case 'Ukrainian (uk_uk)':
lang_code = 'uk_ua';
break;
}
return lang_code;
@@ -350,6 +353,30 @@ function getLangCode() {
// String utilities
// -----------------------------------------------------------------------------
// ----------------------------------------------------
/**
* Replaces double quotes within single-quoted strings, then converts all single quotes to double quotes,
* while preserving the intended structure.
*
* @param {string} inputString - The input string to process.
* @returns {string} - The processed string with transformations applied.
*/
function processQuotes(inputString) {
// Step 1: Replace double quotes within single-quoted strings
let tempString = inputString.replace(/'([^']*?)'/g, (match, p1) => {
const escapedContent = p1.replace(/"/g, '_escaped_double_quote_'); // Temporarily replace double quotes
return `'${escapedContent}'`;
});
// Step 2: Replace all single quotes with double quotes
tempString = tempString.replace(/'/g, '"');
// Step 3: Restore escaped double quotes
const processedString = tempString.replace(/_escaped_double_quote_/g, "'");
return processedString;
}
// ----------------------------------------------------
function jsonSyntaxHighlight(json) {
if (typeof json != 'string') {
@@ -383,6 +410,14 @@ function isValidBase64(str) {
return invalidCharacters === '';
}
// -------------------------------------------------------------------
// Utility function to check if the value is already Base64
function isBase64(value) {
const base64Regex =
/^(?:[A-Za-z0-9+\/]{4})*?(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/;
return base64Regex.test(value);
}
// ----------------------------------------------------
function isValidJSON(jsonString) {
try {
@@ -702,7 +737,7 @@ function forceLoadUrl(relativeUrl) {
// -----------------------------------------------------------------------------
function navigateToDeviceWithIp (ip) {
$.get('api/table_devices.json?nocache=' + Date.now(), function(res) {
$.get('php/server/query_json.php', { file: 'table_devices.json', nocache: Date.now() }, function(res) {
devices = res["data"];
@@ -714,7 +749,7 @@ function navigateToDeviceWithIp (ip) {
{
mac = obj.devMac;
window.open(window.location.origin +'/deviceDetails.php?mac=' + mac , "_blank");
window.open('./deviceDetails.php?mac=' + mac , "_blank");
}
});
@@ -741,6 +776,11 @@ function checkMacOrInternet(inputStr) {
}
}
// Alias
function isValidMac(value) {
return checkMacOrInternet(value);
}
// -----------------------------------------------------------------------------
// Gte MAC from query string
function getMac(){
@@ -924,7 +964,7 @@ function cacheDevices()
// if(!getCache('completedCalls').includes('cacheDevices'))
// {
$.get('api/table_devices.json?nocache=' + Date.now(), function(data) {
$.get('php/server/query_json.php', { file: 'table_devices.json', nocache: Date.now() }, function(data) {
// console.log(data)
@@ -1290,7 +1330,7 @@ function clearCache() {
// -----------------------------------------------------------------------------
// Function to check if cache needs to be refreshed because of setting changes
function checkSettingChanges() {
$.get('api/app_state.json?nocache=' + Date.now(), function(appState) {
$.get('php/server/query_json.php', { file: 'app_state.json', nocache: Date.now() }, function(appState) {
const importedMilliseconds = parseInt(appState["settingsImported"] * 1000);
const lastReloaded = parseInt(sessionStorage.getItem(sessionStorageKey + '_time'));
@@ -1345,7 +1385,7 @@ async function waitForGraphQLServer() {
// Returns 1 if running, 0 otherwise
async function isGraphQLServerRunning() {
try {
const response = await $.get('api/app_state.json?nocache=' + Date.now());
const response = await $.get('php/server/query_json.php', { file: 'app_state.json', nocache: Date.now()});
console.log("graphQLServerStarted: " + response["graphQLServerStarted"]);
setCache("graphQLServerStarted", response["graphQLServerStarted"]);
return response["graphQLServerStarted"];

View File

@@ -32,7 +32,7 @@ function renderList(
// remove first item containing the SQL query
options.shift();
const apiUrl = `php/server/dbHelper.php?action=read&rawSql=${encodeURIComponent(sqlQuery)}`;
const apiUrl = `php/server/dbHelper.php?action=read&rawSql=${btoa(encodeURIComponent(sqlQuery))}`;
$.get(apiUrl, function (sqlOptionsData) {
@@ -85,7 +85,7 @@ function renderList(
// Check if database is locked
function checkDbLock() {
$.ajax({
url: "log/db_is_locked.log", // Replace with the actual path to your PHP file
url: "/php/server/query_logs.php?file=db_is_locked.log",
type: "GET",
success: function (response) {

70
front/js/device.js Executable file
View File

@@ -0,0 +1,70 @@
// -----------------------------------------------------------------------------
function askDeleteDevice() {
// Check MAC
if (mac == '') {
return;
}
// Ask delete device
showModalWarning(
getString("DevDetail_button_Delete"),
getString("DevDetail_button_Delete_ask"),
getString('Gen_Cancel'),
getString('Gen_Delete'),
'deleteDevice');
}
// -----------------------------------------------------------------------------
function askDelDevDTInline(mac) {
// Check MAC
if (mac == '') {
return;
}
showModalWarning(
getString("DevDetail_button_Delete"),
getString("DevDetail_button_Delete_ask"),
getString('Gen_Cancel'),
getString('Gen_Delete'),
() => deleteDeviceByMac(mac))
}
// -----------------------------------------------------------------------------
function deleteDevice() {
// Check MAC
if (mac == '') {
return;
}
// Delete device
$.get('php/server/devices.php?action=deleteDevice&mac=' + mac, function (msg) {
showMessage(msg);
});
// refresh API
updateApi("devices,appevents")
}
// -----------------------------------------------------------------------------
function deleteDeviceByMac(mac) {
// Check MAC
if (mac == '') {
return;
}
// alert(mac)
// return;
// Delete device
$.get('php/server/devices.php?action=deleteDevice&mac=' + mac, function (msg) {
showMessage(msg);
});
// refresh API
updateApi("devices,appevents")
}

View File

@@ -33,7 +33,7 @@ function versionUpdateUI(){
// Checks if a new version is available via the global app_state.json
function checkIfNewVersionAvailable()
{
$.get('api/app_state.json?nocache=' + Date.now(), function(appState) {
$.get('/php/server/query_json.php', { file: 'app_state.json', nocache: Date.now() }, function(appState) {
// console.log(appState["isNewVersionChecked"])
// console.log(appState["isNewVersion"])

View File

@@ -65,7 +65,8 @@ function showModalWarning(
message,
btnCancel = getString("Gen_Cancel"),
btnOK = getString("Gen_Okay"),
callbackFunction = null
callbackFunction = null,
triggeredBy = null
) {
// set captions
$("#modal-warning-title").html(title);
@@ -77,6 +78,10 @@ function showModalWarning(
modalCallbackFunction = callbackFunction;
}
if (triggeredBy != null) {
$('#'+prefix).attr("data-myparam-triggered-by", triggeredBy)
}
// Show modal
$("#modal-warning").modal("show");
}

View File

@@ -191,14 +191,6 @@ function isSHA256(value) {
}
}
// -------------------------------------------------------------------
// Utility function to check if the value is already Base64
function isBase64(value) {
const base64Regex =
/^(?:[A-Za-z0-9+\/]{4})*?(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/;
return base64Regex.test(value);
}
// -------------------------------------------------------------------
// Validation
// -------------------------------------------------------------------
@@ -245,6 +237,68 @@ function settingsCollectedCorrectly(settingsArray, settingsJSON_DB) {
// Manipulating Editable List options
// -------------------------------------------------------------------
// ---------------------------------------------------------
// Add row to datatable
function addDataTableRow(el)
{
alert("a")
}
// ---------------------------------------------------------
// Clone datatable row
function cloneDataTableRow(el){
console.log(el);
const id = "NEWDEV_devCustomProps_table"; // Your table ID
const table = $('#'+id).DataTable();
// Get the 'my-index' attribute from the closest tr element
const myIndex = parseInt($(el).closest("tr").attr("my-index"));
// Find the row in the table with the matching 'my-index'
const row = table.rows().nodes().to$().filter(`[my-index="${myIndex}"]`).first().get(0);
// Clone the row (including its data and controls)
let clonedRow = $(row).clone(true, true); // The true arguments copy the data and event handlers
$(clonedRow).attr("my-index",table.rows().count())
console.log(clonedRow);
// Add the cloned row to the DataTable
table.row.add(clonedRow[0]).draw();
}
// ---------------------------------------------------------
// Remove current datatable row
function removeDataTableRow(el) {
console.log(el);
const id = "NEWDEV_devCustomProps_table"; // Your table ID
const table = $('#'+id).DataTable();
if(table.rows().count() > 1)
{
// Get the 'my-index' attribute from the closest tr element
const myIndex = parseInt($(el).closest("tr").attr("my-index"));
// Find the row in the table with the matching 'my-index'
const row = table.rows().nodes().to$().filter(`[my-index="${myIndex}"]`).first().get(0);
// Remove the row from the DataTable
table.row(row).remove().draw();
}
else
{
showMessage (getString("CustProps_cant_remove"), 3000, "modal_red");
}
}
// ---------------------------------------------------------
// Add item to list
function addList(element, clearInput = true) {
@@ -551,7 +605,7 @@ function overrideToggle(element) {
}
// ---------------------------------------------------------
// Generate options or set options based on the provided parameters
function generateOptionsOrSetOptions(
setKey,
@@ -559,14 +613,21 @@ function generateOptionsOrSetOptions(
placeholder, // ID of the HTML element where dropdown should be rendered (will be replaced)
processDataCallback, // Callback function to generate entries based on options
targetField, // Target field or element where selected value should be applied or updated
transformers = [] // Transformers to be applied to the values
transformers = [], // Transformers to be applied to the values
overrideOptions = null // override options if available
) {
// console.log(setKey);
// NOTE {value} options to replace with a setting or SQL value are handled in the cacheSettings() function
options = arrayToObject(createArray(getSettingOptions(setKey)))
// console.log(overrideOptions);
// console.log( getSettingOptions(setKey));
// console.log( setKey);
// NOTE {value} options to replace with a setting or SQL value are handled in the cacheSettings() function
options = arrayToObject(createArray(overrideOptions ? overrideOptions : getSettingOptions(setKey)))
// Call to render lists
renderList(
options,
@@ -655,6 +716,8 @@ const handleElementOptions = (setKey, elementOptions, transformers, val) => {
let onChange = "console.log('onChange - Not implemented');";
let customParams = "";
let customId = "";
let columns = [];
let base64Regex = "";
elementOptions.forEach((option) => {
@@ -708,6 +771,12 @@ const handleElementOptions = (setKey, elementOptions, transformers, val) => {
if (option.customId) {
customId = option.customId;
}
if (option.columns) {
columns = option.columns;
}
if (option.base64Regex) {
base64Regex = option.base64Regex;
}
});
if (transformers.includes("sha256")) {
@@ -730,7 +799,9 @@ const handleElementOptions = (setKey, elementOptions, transformers, val) => {
onClick,
onChange,
customParams,
customId
customId,
columns,
base64Regex
};
};
@@ -863,7 +934,7 @@ function genListWithInputSet(options, valuesArray, targetField, transformers, pl
// ------------------------------------------------------------------------------
// Generate the form control for setting
function generateFormHtml(set, overrideValue) {
function generateFormHtml(settingsData, set, overrideValue, overrideOptions, originalSetKey) {
let inputHtml = '';
@@ -871,14 +942,17 @@ function generateFormHtml(set, overrideValue) {
const setKey = set['setKey'];
const setType = set['setType'];
// if (setKey == '') {
// console.log(setType);
// console.log(setKey);
// console.log(overrideValue);
// console.log(inVal);
// console.log(inVal);
// }
// Parse the setType JSON string
const setTypeObject = JSON.parse(setType.replace(/'/g, '"'));
const setTypeObject = JSON.parse(processQuotes(setType))
const dataType = setTypeObject.dataType;
const elements = setTypeObject.elements || [];
@@ -903,14 +977,23 @@ function generateFormHtml(set, overrideValue) {
onClick,
onChange,
customParams,
customId
customId,
columns,
base64Regex
} = handleElementOptions(setKey, elementOptions, transformers, inVal);
// Override value
const val = valRes;
let val = valRes;
// console.log(val);
// if (setKey == '') {
// console.log(setType);
// console.log(setKey);
// console.log(overrideValue);
// console.log(inVal);
// console.log(val);
// }
// Generate HTML based on elementType
switch (elementType) {
@@ -921,20 +1004,21 @@ function generateFormHtml(set, overrideValue) {
inputHtml += `<select onChange="settingsChanged();${onChange}"
my-data-type="${dataType}"
my-editable="${editable}"
class="form-control ${addCss}"
class="form-control ${addCss} ${cssClasses}"
name="${setKey}"
id="${setKey}"
my-customparams="${customParams}"
my-customid="${customId}"
my-originalSetKey="${originalSetKey}"
${multi}>
<option value="" id="${setKey + "_temp_"}"></option>
</select>`;
generateOptionsOrSetOptions(setKey, createArray(val), `${setKey}_temp_`, generateOptions, null, transformers);
generateOptionsOrSetOptions(setKey, createArray(val), `${setKey}_temp_`, generateOptions, null, transformers, overrideOptions);
break;
case 'input':
const checked = val === 'True' || val === '1' ? 'checked' : '';
const checked = val === 'True' || val === 'true' || val === '1' ? 'checked' : '';
const inputClass = inputType === 'checkbox' ? 'checkbox' : 'form-control';
inputHtml += `<input
@@ -943,6 +1027,8 @@ function generateFormHtml(set, overrideValue) {
my-data-type="${dataType}"
my-customparams="${customParams}"
my-customid="${customId}"
my-originalSetKey="${originalSetKey}"
my-base64Regex="${base64Regex}"
id="${setKey}${suffix}"
type="${inputType}"
value="${val}"
@@ -957,6 +1043,7 @@ function generateFormHtml(set, overrideValue) {
class="btn btn-primary ${cssClasses}"
my-customparams="${customParams}"
my-customid="${customId}"
my-originalSetKey="${originalSetKey}"
my-input-from="${sourceIds}"
my-input-to="${setKey}"
onclick="${onClick}">
@@ -969,6 +1056,7 @@ function generateFormHtml(set, overrideValue) {
class="form-control input"
my-customparams="${customParams}"
my-customid="${customId}"
my-originalSetKey="${originalSetKey}"
my-data-type="${dataType}"
id="${setKey}"
${readOnly}>${val}</textarea>`;
@@ -979,10 +1067,116 @@ function generateFormHtml(set, overrideValue) {
class="${cssClasses}"
my-data-type="${dataType}"
my-customparams="${customParams}"
my-customid="${customId}">
${getString(getStringKey)}
my-customid="${customId}"
my-originalSetKey="${originalSetKey}"
onclick="${onClick}">
${getString(getStringKey)}${placeholder}
</span>`;
break;
case 'datatable':
const tableId = `${setKey}_table`;
let datatableHtml = `<table id="${tableId}" class="table table-striped">`;
// Dynamic array creation
let emptyVal = [];
let columnSettings = [];
// Generate table headers
datatableHtml += '<thead><tr>';
columns.forEach(column => {
let columnSetting = getSetObject(settingsData, column.settingKey) || {};
datatableHtml += `<th>${columnSetting.setName}</th>`;
if(column.typeOverride)
{
columnSetting["setType"] = JSON.stringify(column.typeOverride);
}
if(column.optionsOverride)
{
if (column.optionsOverride.startsWith("setting.")) {
columnSetting["setOptions"] = getSetting(column.optionsOverride.replace("setting.",""));
} else {
columnSetting["setOptions"] = column.optionsOverride;
}
}
columnSettings.push(columnSetting)
// helper for if val is empty
emptyVal.push('');
});
datatableHtml += '</tr></thead>';
// Generate table body
datatableHtml += '<tbody>';
if(val.length > 0 && isBase64(val))
{
val = atob(val)
// console.log(val);
val = JSON.parse(val)
}else{
// init empty
val = [emptyVal]
}
let index = 0;
val.forEach(rowData => {
datatableHtml += `<tr my-index="${index}">`;
let j = 0;
columnSettings.forEach(set => {
// Extract the value for the current column based on the new structure
let columnOverrideValue = rowData[j] && Object.values(rowData[j])[0];
if(columnOverrideValue == undefined)
{
columnOverrideValue = ""
}
// Create unique key to prevent dropdown data duplication
const oldKey = set["setKey"];
set["setKey"] = oldKey + "_" + index;
// Generate the cell HTML using the extracted value
const cellHtml = generateFormHtml(
settingsData,
set,
columnOverrideValue.toString(),
set["setOptions"],
oldKey
);
datatableHtml += `<td> <div class="input-group"> ${cellHtml} </div></td>`;
// Restore the original key
set["setKey"] = oldKey;
j++;
});
datatableHtml += '</tr>';
index++;
});
datatableHtml += '</tbody></table>';
inputHtml += datatableHtml;
// Initialize DataTable with jQuery
$(document).ready(() => {
$(`#${tableId}`).DataTable({
ordering: false, // Disables sorting on all columns
searching: false, // Disables the search box
dom: "<'top'rt><'bottom'ipl>", // Move length dropdown to the bottom
});
});
break;
default:
console.warn(`🟥 Unknown element type: ${elementType}`);
@@ -991,10 +1185,6 @@ function generateFormHtml(set, overrideValue) {
// Generate event HTML if applicable
let eventsHtml = '';
// console.log(setTypeObject);
// console.log(set);
const eventsList = createArray(set['setEvents']);
// inline buttons events
@@ -1007,7 +1197,7 @@ function generateFormHtml(set, overrideValue) {
id="${`${event}_${setKey}`}"
data-myparam-setkey="${setKey}"
data-myparam="${setKey}"
data-myparam-plugin="${setTypeObject.prefix || ''}"
data-myparam-plugin="${setKey.split('_')[0] || ''}"
data-myevent="${event}"
onclick="execute_settingEvent(this)">
<i title="${getString(event + "_event_tooltip")}" class="fa ${getString(event + "_event_icon")}"></i>
@@ -1019,4 +1209,68 @@ function generateFormHtml(set, overrideValue) {
return inputHtml + eventsHtml;
}
// -----------------------------------------------------
// Return the setting object by setKey
function getSetObject(settingsData, setKey) {
found = false;
result = ""
settingsData.forEach(function(set) {
if (set.setKey == setKey) {
// console.log(set);
result = set;
return;
}
});
if(result == "")
{
console.error(settingsData);
console.error(`Setting not found: ${setKey}`);
}
return result;
}
// ---------------------------------------
// Collect DataTable data
function collectTableData(tableSelector) {
const table = $(tableSelector).DataTable();
let tableData = [];
table.rows().every(function () {
const rowData = [];
const cells = $(this.node()).find('td');
cells.each((index, cell) => {
const input = $(cell).find('input, select, textarea');
if (input.length) {
if (input.attr('type') === 'checkbox') {
// For checkboxes, check if they are checked
rowData[index] = { [input.attr("my-originalsetkey")] : input.prop('checked') };
} else {
// Generic sync for other inputs (text, select, textarea)
rowData[index] = { [input.attr("my-originalsetkey")] : input.val().replace(/'/g, "").replace(/"/g, "") };
}
} else {
// Handle plain text
// rowData[index] = { [input.attr("my-originalsetkey")] : $(cell).text()};
console.log(`Nothig to collect: ${$(cell).html()}`)
}
});
tableData.push(rowData);
});
return tableData;
}

View File

@@ -97,12 +97,54 @@ function generateApiToken(elem, length) {
}
}
// ----------------------------------------------
// Generate a random N-byte hexadecimal key
function getRandomBytes(elem, length) {
// Retrieve and parse custom parameters from the element
let params = $(elem).attr("my-customparams")?.split(',').map(param => param.trim());
if (params && params.length >= 1) {
var targetElementID = params[0]; // Get the target element's ID
}
let targetElement = $('#' + targetElementID);
// Generate random bytes
const array = new Uint8Array(length);
window.crypto.getRandomValues(array);
// Convert bytes to hexadecimal string
let hexString = Array.from(array, byte =>
byte.toString(16).padStart(2, '0')
).join('');
// Format hexadecimal string with hyphens
let formattedHex = hexString.match(/.{1,2}/g).join('-');
console.log(formattedHex);
// console.log($(`#${targetInput}`).val());
// Set the formatted key value to the input field
targetElement.val(formattedHex);
}
// ----------------------------------------------
// Updates the icon preview
function updateAllIconPreviews() {
$(".iconInputVal").each((index, el)=>{
updateIconPreview(el)
})
}
// ----------------------------------------------
// Updates the icon preview
function updateIconPreview(elem) {
const targetElement = $('[my-customid="NEWDEV_devIcon_preview"]');
const iconInput = $("#NEWDEV_devIcon");
const previewSpan = $(elem).parent().find(".iconPreview");
const iconInput = $(elem);
let attempts = 0;
@@ -110,10 +152,10 @@ function updateIconPreview(elem) {
let value = iconInput.val();
if (value) {
targetElement.html(atob(value));
previewSpan.html(atob(value));
iconInput.off('change input').on('change input', function () {
let newValue = $(this).val();
targetElement.html(atob(newValue));
let newValue = $(elem).val();
previewSpan.html(atob(newValue));
});
return; // Stop retrying if successful
}
@@ -129,7 +171,47 @@ function updateIconPreview(elem) {
tryUpdateIcon();
}
// ----------------------------------------------
// Validate the value based on regex
// ⚠ IMPORTANT: use the below to get a valid REGEX ⚠
// const regexStr = String.raw`^(?:\*|(?:[0-9]|[1-5][0-9]|[0-9]+-[0-9]+|\*/[0-9]+))\s+(?:\*|(?:[0-9]|1[0-9]|2[0-3]|[0-9]+-[0-9]+|\*/[0-9]+))\s+(?:\*|(?:[1-9]|[12][0-9]|3[01]|[0-9]+-[0-9]+|\*/[0-9]+))\s+(?:\*|(?:[1-9]|1[0-2]|[0-9]+-[0-9]+|\*/[0-9]+))\s+(?:\*|(?:[0-6]|[0-6]-[0-6]|\*/[0-9]+))$`;
// console.log(btoa(regexStr));
function validateRegex(elem) {
const iconSpan = $(elem).parent().find(".validityCheck");
const inputElem = $(elem);
const regexTmp = atob($(inputElem).attr("my-base64Regex")); // Decode base64 regex
console.log(regexTmp);
const regex = new RegExp(regexTmp); // Convert to a valid RegExp object
let attempts = 0;
function tryUpdateValidityResultIcon() {
let value = inputElem.val().trim(); // Ensure trimmed value
if (value === "") {
attempts++;
if (attempts < 10) {
setTimeout(tryUpdateValidityResultIcon, 1000); // Retry after 1 sec if empty
} else {
console.error("Input value is empty after 10 attempts");
}
return;
}
// Validate against regex
if (regex.test(value)) {
iconSpan.html("<i class='fa-regular fa-check'></i>");
} else {
iconSpan.html("<i class='fa-regular fa-xmark'></i>");
}
}
// Attach real-time validation on input change
inputElem.on("input", tryUpdateValidityResultIcon);
tryUpdateValidityResultIcon(); // Initial validation
}
// -----------------------------------------------------------------------------
// Nice checkboxes with iCheck
@@ -189,38 +271,65 @@ function copyToClipboard(buttonElement) {
// Simple Sortable Table columns
// -----------------------------------------------------------------------------
// Function to handle column sorting when a user clicks on a table header
function sortColumn(element) {
var th = $(element).closest('th');
var table = th.closest('table');
var columnIndex = th.index();
var ascending = !th.data('asc');
var th = $(element).closest('th'); // Get the clicked table header
var table = th.closest('table'); // Find the closest table
var columnIndex = th.index(); // Get the index of the column
var ascending = !th.data('asc'); // Toggle sorting order
sortTable(table, columnIndex, ascending);
th.data('asc', ascending);
th.data('asc', ascending); // Store sorting order
}
// Function to sort the table based on the selected column
function sortTable(table, columnIndex, ascending) {
var tbody = table.find('tbody');
var rows = tbody.find('tr').toArray().sort(comparer(columnIndex));
var tbody = table.find('tbody'); // Get the table body
var rows = tbody.find('tr').toArray().sort(comparer(columnIndex)); // Convert rows to an array and sort
if (!ascending) {
rows = rows.reverse();
rows = rows.reverse(); // Reverse order if descending
}
for (var i = 0; i < rows.length; i++) {
tbody.append(rows[i]);
tbody.append(rows[i]); // Append sorted rows back to the table
}
}
// Function to compare values in the selected column
function comparer(index) {
return function(a, b) {
return function (a, b) {
var valA = getCellValue(a, index);
var valB = getCellValue(b, index);
return $.isNumeric(valA) && $.isNumeric(valB) ? valA - valB : valA.localeCompare(valB);
// Check if both values are valid IP addresses, and sort numerically if so
if (isIPAddress(valA) && isIPAddress(valB)) {
return ipToNum(valA) - ipToNum(valB);
}
// If both values are numbers, sort numerically
if ($.isNumeric(valA) && $.isNumeric(valB)) {
return valA - valB;
}
// Otherwise, sort as text
return valA.localeCompare(valB);
};
}
// Function to get the text value from a table cell
function getCellValue(row, index) {
return $(row).children('td').eq(index).text();
return $(row).children('td').eq(index).text().trim(); // Get text from the specified column and trim spaces
}
// Function to check if a string is a valid IPv4 address
function isIPAddress(value) {
return /^\d{1,3}(\.\d{1,3}){3}$/.test(value); // Regular expression to match IPv4 format
}
// Function to convert an IP address to a numeric value for sorting
function ipToNum(ip) {
return ip.split('.').reduce((acc, octet) => (acc << 8) + parseInt(octet, 10), 0);
}
// -----------------------------------------------------------------------------
// handling events
// -----------------------------------------------------------------------------
@@ -314,7 +423,7 @@ function updateModalState() {
setTimeout(function() {
// Fetch the content from the log file using an AJAX request
$.ajax({
url: '/log/execution_queue.log',
url: '/php/server/query_logs.php?file=execution_queue.log',
type: 'GET',
success: function(data) {
// Update the content of the HTML element (e.g., a div with id 'logContent')
@@ -449,7 +558,7 @@ function showIconSelection() {
iconDiv.addEventListener('click', () => {
selectElement.value = value; // Update the select element value
$(`#${modalId}`).modal('hide'); // Hide the modal
updateIconPreview();
updateAllIconPreviews();
});
}
});
@@ -466,8 +575,52 @@ function showIconSelection() {
}
// "Device_TableHead_Owner",
// "Device_TableHead_Type",
// "Device_TableHead_Group",
// "Device_TableHead_Status",
// "Device_TableHead_Location",
// "Device_TableHead_Vendor",
// "Device_TableHead_SyncHubNodeName",
// "Device_TableHead_NetworkSite",
// "Device_TableHead_SSID",
// "Device_TableHead_SourcePlugin"
// -----------------------------------------------------------------------------
// Get teh correct db column code name based on table header title string
function getColumnNameFromLangString(headStringKey) {
columnNameMap = {
"Device_TableHead_Name": "devName",
"Device_TableHead_Owner": "devOwner",
"Device_TableHead_Type": "devType",
"Device_TableHead_Icon": "devIcon",
"Device_TableHead_Favorite": "devFavorite",
"Device_TableHead_Group": "devGroup",
"Device_TableHead_FirstSession": "devFirstConnection",
"Device_TableHead_LastSession": "devLastConnection",
"Device_TableHead_LastIP": "devLastIP",
"Device_TableHead_MAC": "devMac",
"Device_TableHead_Status": "devStatus",
"Device_TableHead_MAC_full": "devMac",
"Device_TableHead_LastIPOrder": "devIpLong",
"Device_TableHead_Rowid": "rowid",
"Device_TableHead_Parent_MAC": "devParentMAC",
"Device_TableHead_Connected_Devices": "devParentChildrenCount",
"Device_TableHead_Location": "devLocation",
"Device_TableHead_Vendor": "devVendor",
"Device_TableHead_Port": "devParentPort",
"Device_TableHead_GUID": "devGUID",
"Device_TableHead_SyncHubNodeName": "devSyncHubNode",
"Device_TableHead_NetworkSite": "devSite",
"Device_TableHead_SSID": "devSSID",
"Device_TableHead_SourcePlugin": "devSourcePlugin",
"Device_TableHead_PresentLastScan": "devPresentLastScan",
"Device_TableHead_AlertDown": "devAlertDown",
"Device_TableHead_CustomProps": "devCustomProps"
};
return columnNameMap[headStringKey] || "";
}
// -----------------------------------------------------------------------------
@@ -528,7 +681,7 @@ window.addEventListener("load", function() {
// try to initialize
setTimeout(() => {
initSelect2()
initializeiCheck();
// initializeiCheck();
}, 1000);
});

20
front/lib/AdminLTE/dist/css/LICENSE vendored Executable file
View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2014-2023 ColorlibHQ
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

1
front/lib/AdminLTE/dist/js/adminlte.js.map vendored Executable file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

147
front/lib/AdminLTE/dist/js/pages/dashboard3.js vendored Executable file
View File

@@ -0,0 +1,147 @@
/* global Chart:false */
$(function () {
'use strict'
var ticksStyle = {
fontColor: '#495057',
fontStyle: 'bold'
}
var mode = 'index'
var intersect = true
var $salesChart = $('#sales-chart')
// eslint-disable-next-line no-unused-vars
var salesChart = new Chart($salesChart, {
type: 'bar',
data: {
labels: ['JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'],
datasets: [
{
backgroundColor: '#007bff',
borderColor: '#007bff',
data: [1000, 2000, 3000, 2500, 2700, 2500, 3000]
},
{
backgroundColor: '#ced4da',
borderColor: '#ced4da',
data: [700, 1700, 2700, 2000, 1800, 1500, 2000]
}
]
},
options: {
maintainAspectRatio: false,
tooltips: {
mode: mode,
intersect: intersect
},
hover: {
mode: mode,
intersect: intersect
},
legend: {
display: false
},
scales: {
yAxes: [{
// display: false,
gridLines: {
display: true,
lineWidth: '4px',
color: 'rgba(0, 0, 0, .2)',
zeroLineColor: 'transparent'
},
ticks: $.extend({
beginAtZero: true,
// Include a dollar sign in the ticks
callback: function (value) {
if (value >= 1000) {
value /= 1000
value += 'k'
}
return '$' + value
}
}, ticksStyle)
}],
xAxes: [{
display: true,
gridLines: {
display: false
},
ticks: ticksStyle
}]
}
}
})
var $visitorsChart = $('#visitors-chart')
// eslint-disable-next-line no-unused-vars
var visitorsChart = new Chart($visitorsChart, {
data: {
labels: ['18th', '20th', '22nd', '24th', '26th', '28th', '30th'],
datasets: [{
type: 'line',
data: [100, 120, 170, 167, 180, 177, 160],
backgroundColor: 'transparent',
borderColor: '#007bff',
pointBorderColor: '#007bff',
pointBackgroundColor: '#007bff',
fill: false
// pointHoverBackgroundColor: '#007bff',
// pointHoverBorderColor : '#007bff'
},
{
type: 'line',
data: [60, 80, 70, 67, 80, 77, 100],
backgroundColor: 'tansparent',
borderColor: '#ced4da',
pointBorderColor: '#ced4da',
pointBackgroundColor: '#ced4da',
fill: false
// pointHoverBackgroundColor: '#ced4da',
// pointHoverBorderColor : '#ced4da'
}]
},
options: {
maintainAspectRatio: false,
tooltips: {
mode: mode,
intersect: intersect
},
hover: {
mode: mode,
intersect: intersect
},
legend: {
display: false
},
scales: {
yAxes: [{
// display: false,
gridLines: {
display: true,
lineWidth: '4px',
color: 'rgba(0, 0, 0, .2)',
zeroLineColor: 'transparent'
},
ticks: $.extend({
beginAtZero: true,
suggestedMax: 200
}, ticksStyle)
}],
xAxes: [{
display: true,
gridLines: {
display: false
},
ticks: ticksStyle
}]
}
}
})
})
// lgtm [js/unused-local-variable]

48
front/lib/bcrypt/bcrypt.min.js vendored Executable file
View File

@@ -0,0 +1,48 @@
/*
bcrypt.js (c) 2013 Daniel Wirtz <dcode@dcode.io>
Released under the Apache License, Version 2.0
see: https://github.com/dcodeIO/bcrypt.js for details
*/
(function(u,r){"function"===typeof define&&define.amd?define([],r):"function"===typeof require&&"object"===typeof module&&module&&module.exports?module.exports=r():(u.dcodeIO=u.dcodeIO||{}).bcrypt=r()})(this,function(){function u(e){if("undefined"!==typeof module&&module&&module.exports)try{return require("crypto").randomBytes(e)}catch(d){}try{var c;(self.crypto||self.msCrypto).getRandomValues(c=new Uint32Array(e));return Array.prototype.slice.call(c)}catch(b){}if(!w)throw Error("Neither WebCryptoAPI nor a crypto module is available. Use bcrypt.setRandomFallback to set an alternative");
return w(e)}function r(e,d){for(var c=0,b=0,a=0,f=e.length;a<f;++a)e.charCodeAt(a)===d.charCodeAt(a)?++c:++b;return 0>c?!1:0===b}function H(e){var d=[],c=0;I.encodeUTF16toUTF8(function(){return c>=e.length?null:e.charCodeAt(c++)},function(b){d.push(b)});return d}function x(e,d){var c=0,b=[],a,f;if(0>=d||d>e.length)throw Error("Illegal len: "+d);for(;c<d;){a=e[c++]&255;b.push(s[a>>2&63]);a=(a&3)<<4;if(c>=d){b.push(s[a&63]);break}f=e[c++]&255;a|=f>>4&15;b.push(s[a&63]);a=(f&15)<<2;if(c>=d){b.push(s[a&
63]);break}f=e[c++]&255;a|=f>>6&3;b.push(s[a&63]);b.push(s[f&63])}return b.join("")}function B(e,d){var c=0,b=e.length,a=0,f=[],g,m,h;if(0>=d)throw Error("Illegal len: "+d);for(;c<b-1&&a<d;){h=e.charCodeAt(c++);g=h<q.length?q[h]:-1;h=e.charCodeAt(c++);m=h<q.length?q[h]:-1;if(-1==g||-1==m)break;h=g<<2>>>0;h|=(m&48)>>4;f.push(z(h));if(++a>=d||c>=b)break;h=e.charCodeAt(c++);g=h<q.length?q[h]:-1;if(-1==g)break;h=(m&15)<<4>>>0;h|=(g&60)>>2;f.push(z(h));if(++a>=d||c>=b)break;h=e.charCodeAt(c++);m=h<q.length?
q[h]:-1;h=(g&3)<<6>>>0;h|=m;f.push(z(h));++a}b=[];for(c=0;c<a;c++)b.push(f[c].charCodeAt(0));return b}function v(e,d,c,b){var a,f=e[d],g=e[d+1],f=f^c[0];a=b[f>>>24];a+=b[256|f>>16&255];a^=b[512|f>>8&255];a+=b[768|f&255];g=g^a^c[1];a=b[g>>>24];a+=b[256|g>>16&255];a^=b[512|g>>8&255];a+=b[768|g&255];f=f^a^c[2];a=b[f>>>24];a+=b[256|f>>16&255];a^=b[512|f>>8&255];a+=b[768|f&255];g=g^a^c[3];a=b[g>>>24];a+=b[256|g>>16&255];a^=b[512|g>>8&255];a+=b[768|g&255];f=f^a^c[4];a=b[f>>>24];a+=b[256|f>>16&255];a^=b[512|
f>>8&255];a+=b[768|f&255];g=g^a^c[5];a=b[g>>>24];a+=b[256|g>>16&255];a^=b[512|g>>8&255];a+=b[768|g&255];f=f^a^c[6];a=b[f>>>24];a+=b[256|f>>16&255];a^=b[512|f>>8&255];a+=b[768|f&255];g=g^a^c[7];a=b[g>>>24];a+=b[256|g>>16&255];a^=b[512|g>>8&255];a+=b[768|g&255];f=f^a^c[8];a=b[f>>>24];a+=b[256|f>>16&255];a^=b[512|f>>8&255];a+=b[768|f&255];g=g^a^c[9];a=b[g>>>24];a+=b[256|g>>16&255];a^=b[512|g>>8&255];a+=b[768|g&255];f=f^a^c[10];a=b[f>>>24];a+=b[256|f>>16&255];a^=b[512|f>>8&255];a+=b[768|f&255];g=g^a^
c[11];a=b[g>>>24];a+=b[256|g>>16&255];a^=b[512|g>>8&255];a+=b[768|g&255];f=f^a^c[12];a=b[f>>>24];a+=b[256|f>>16&255];a^=b[512|f>>8&255];a+=b[768|f&255];g=g^a^c[13];a=b[g>>>24];a+=b[256|g>>16&255];a^=b[512|g>>8&255];a+=b[768|g&255];f=f^a^c[14];a=b[f>>>24];a+=b[256|f>>16&255];a^=b[512|f>>8&255];a+=b[768|f&255];g=g^a^c[15];a=b[g>>>24];a+=b[256|g>>16&255];a^=b[512|g>>8&255];a+=b[768|g&255];f=f^a^c[16];e[d]=g^c[17];e[d+1]=f;return e}function t(e,d){for(var c=0,b=0;4>c;++c)b=b<<8|e[d]&255,d=(d+1)%e.length;
return{key:b,offp:d}}function C(e,d,c){for(var b=0,a=[0,0],f=d.length,g=c.length,m,h=0;h<f;h++)m=t(e,b),b=m.offp,d[h]^=m.key;for(h=0;h<f;h+=2)a=v(a,0,d,c),d[h]=a[0],d[h+1]=a[1];for(h=0;h<g;h+=2)a=v(a,0,d,c),c[h]=a[0],c[h+1]=a[1]}function J(e,d,c,b){for(var a=0,f=[0,0],g=c.length,m=b.length,h,l=0;l<g;l++)h=t(d,a),a=h.offp,c[l]^=h.key;for(l=a=0;l<g;l+=2)h=t(e,a),a=h.offp,f[0]^=h.key,h=t(e,a),a=h.offp,f[1]^=h.key,f=v(f,0,c,b),c[l]=f[0],c[l+1]=f[1];for(l=0;l<m;l+=2)h=t(e,a),a=h.offp,f[0]^=h.key,h=t(e,
a),a=h.offp,f[1]^=h.key,f=v(f,0,c,b),b[l]=f[0],b[l+1]=f[1]}function D(e,d,c,b,a){function f(){a&&a(n/c);if(n<c)for(var h=Date.now();n<c&&!(n+=1,C(e,l,k),C(d,l,k),100<Date.now()-h););else{for(n=0;64>n;n++)for(y=0;y<m>>1;y++)v(g,y<<1,l,k);h=[];for(n=0;n<m;n++)h.push((g[n]>>24&255)>>>0),h.push((g[n]>>16&255)>>>0),h.push((g[n]>>8&255)>>>0),h.push((g[n]&255)>>>0);if(b){b(null,h);return}return h}b&&p(f)}var g=E.slice(),m=g.length,h;if(4>c||31<c){h=Error("Illegal number of rounds (4-31): "+c);if(b){p(b.bind(this,
h));return}throw h;}if(16!==d.length){h=Error("Illegal salt length: "+d.length+" != 16");if(b){p(b.bind(this,h));return}throw h;}c=1<<c>>>0;var l,k,n=0,y;Int32Array?(l=new Int32Array(F),k=new Int32Array(G)):(l=F.slice(),k=G.slice());J(d,e,l,k);if("undefined"!==typeof b)f();else for(;;)if("undefined"!==typeof(h=f()))return h||[]}function A(e,d,c,b){function a(a){var b=[];b.push("$2");"a"<=f&&b.push(f);b.push("$");10>l&&b.push("0");b.push(l.toString());b.push("$");b.push(x(k,k.length));b.push(x(a,4*
E.length-1));return b.join("")}if("string"!==typeof e||"string"!==typeof d){b=Error("Invalid string / salt: Not a string");if(c){p(c.bind(this,b));return}throw b;}var f,g;if("$"!==d.charAt(0)||"2"!==d.charAt(1)){b=Error("Invalid salt version: "+d.substring(0,2));if(c){p(c.bind(this,b));return}throw b;}if("$"===d.charAt(2))f=String.fromCharCode(0),g=3;else{f=d.charAt(2);if("a"!==f&&"b"!==f&&"y"!==f||"$"!==d.charAt(3)){b=Error("Invalid salt revision: "+d.substring(2,4));if(c){p(c.bind(this,b));return}throw b;
}g=4}if("$"<d.charAt(g+2)){b=Error("Missing salt rounds");if(c){p(c.bind(this,b));return}throw b;}var m=10*parseInt(d.substring(g,g+1),10),h=parseInt(d.substring(g+1,g+2),10),l=m+h;d=d.substring(g+3,g+25);e=H(e+("a"<=f?"\x00":""));var k=B(d,16);if("undefined"==typeof c)return a(D(e,k,l));D(e,k,l,function(b,d){b?c(b,null):c(null,a(d))},b)}var k={},w=null;try{u(1)}catch(K){}w=null;k.setRandomFallback=function(e){w=e};k.genSaltSync=function(e,d){e=e||10;if("number"!==typeof e)throw Error("Illegal arguments: "+
typeof e+", "+typeof d);4>e?e=4:31<e&&(e=31);var c=[];c.push("$2a$");10>e&&c.push("0");c.push(e.toString());c.push("$");c.push(x(u(16),16));return c.join("")};k.genSalt=function(e,d,c){function b(a){p(function(){try{a(null,k.genSaltSync(e))}catch(b){a(b)}})}"function"===typeof d&&(c=d,d=void 0);"function"===typeof e&&(c=e,e=void 0);if("undefined"===typeof e)e=10;else if("number"!==typeof e)throw Error("illegal arguments: "+typeof e);if(c){if("function"!==typeof c)throw Error("Illegal callback: "+
typeof c);b(c)}else return new Promise(function(a,c){b(function(b,d){b?c(b):a(d)})})};k.hashSync=function(e,d){"undefined"===typeof d&&(d=10);"number"===typeof d&&(d=k.genSaltSync(d));if("string"!==typeof e||"string"!==typeof d)throw Error("Illegal arguments: "+typeof e+", "+typeof d);return A(e,d)};k.hash=function(e,d,c,b){function a(a){"string"===typeof e&&"number"===typeof d?k.genSalt(d,function(c,d){A(e,d,a,b)}):"string"===typeof e&&"string"===typeof d?A(e,d,a,b):p(a.bind(this,Error("Illegal arguments: "+
typeof e+", "+typeof d)))}if(c){if("function"!==typeof c)throw Error("Illegal callback: "+typeof c);a(c)}else return new Promise(function(b,c){a(function(a,d){a?c(a):b(d)})})};k.compareSync=function(e,d){if("string"!==typeof e||"string"!==typeof d)throw Error("Illegal arguments: "+typeof e+", "+typeof d);return 60!==d.length?!1:r(k.hashSync(e,d.substr(0,d.length-31)),d)};k.compare=function(e,d,c,b){function a(a){"string"!==typeof e||"string"!==typeof d?p(a.bind(this,Error("Illegal arguments: "+typeof e+
", "+typeof d))):60!==d.length?p(a.bind(this,null,!1)):k.hash(e,d.substr(0,29),function(b,c){b?a(b):a(null,r(c,d))},b)}if(c){if("function"!==typeof c)throw Error("Illegal callback: "+typeof c);a(c)}else return new Promise(function(b,c){a(function(a,d){a?c(a):b(d)})})};k.getRounds=function(e){if("string"!==typeof e)throw Error("Illegal arguments: "+typeof e);return parseInt(e.split("$")[2],10)};k.getSalt=function(e){if("string"!==typeof e)throw Error("Illegal arguments: "+typeof e);if(60!==e.length)throw Error("Illegal hash length: "+
e.length+" != 60");return e.substring(0,29)};var p="undefined"!==typeof process&&process&&"function"===typeof process.nextTick?"function"===typeof setImmediate?setImmediate:process.nextTick:setTimeout,s="./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".split(""),q=[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,1,54,55,56,57,58,59,60,61,62,63,-1,-1,-1,-1,-1,-1,-1,2,3,4,5,6,7,8,9,10,11,12,
13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,-1,-1,-1,-1,-1,-1,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,-1,-1,-1,-1,-1],z=String.fromCharCode,I=function(){var e={MAX_CODEPOINT:1114111,encodeUTF8:function(d,c){var b=null;"number"===typeof d&&(b=d,d=function(){return null});for(;null!==b||null!==(b=d());)128>b?c(b&127):(2048>b?c(b>>6&31|192):(65536>b?c(b>>12&15|224):(c(b>>18&7|240),c(b>>12&63|128)),c(b>>6&63|128)),c(b&63|128)),b=null},decodeUTF8:function(d,c){for(var b,
a,f,e,k=function(a){a=a.slice(0,a.indexOf(null));var b=Error(a.toString());b.name="TruncatedError";b.bytes=a;throw b;};null!==(b=d());)if(0===(b&128))c(b);else if(192===(b&224))null===(a=d())&&k([b,a]),c((b&31)<<6|a&63);else if(224===(b&240))null!==(a=d())&&null!==(f=d())||k([b,a,f]),c((b&15)<<12|(a&63)<<6|f&63);else if(240===(b&248))null!==(a=d())&&null!==(f=d())&&null!==(e=d())||k([b,a,f,e]),c((b&7)<<18|(a&63)<<12|(f&63)<<6|e&63);else throw RangeError("Illegal starting byte: "+b);},UTF16toUTF8:function(d,
c){for(var b,a=null;null!==(b=null!==a?a:d());)55296<=b&&57343>=b&&null!==(a=d())&&56320<=a&&57343>=a?(c(1024*(b-55296)+a-56320+65536),a=null):c(b);null!==a&&c(a)},UTF8toUTF16:function(d,c){var b=null;"number"===typeof d&&(b=d,d=function(){return null});for(;null!==b||null!==(b=d());)65535>=b?c(b):(b-=65536,c((b>>10)+55296),c(b%1024+56320)),b=null},encodeUTF16toUTF8:function(d,c){e.UTF16toUTF8(d,function(b){e.encodeUTF8(b,c)})},decodeUTF8toUTF16:function(d,c){e.decodeUTF8(d,function(b){e.UTF8toUTF16(b,
c)})},calculateCodePoint:function(d){return 128>d?1:2048>d?2:65536>d?3:4},calculateUTF8:function(d){for(var c,b=0;null!==(c=d());)b+=e.calculateCodePoint(c);return b},calculateUTF16asUTF8:function(d){var c=0,b=0;e.UTF16toUTF8(d,function(a){++c;b+=e.calculateCodePoint(a)});return[c,b]}};return e}();Date.now=Date.now||function(){return+new Date};var F=[608135816,2242054355,320440878,57701188,2752067618,698298832,137296536,3964562569,1160258022,953160567,3193202383,887688300,3232508343,3380367581,1065670069,
3041331479,2450970073,2306472731],G=[3509652390,2564797868,805139163,3491422135,3101798381,1780907670,3128725573,4046225305,614570311,3012652279,134345442,2240740374,1667834072,1901547113,2757295779,4103290238,227898511,1921955416,1904987480,2182433518,2069144605,3260701109,2620446009,720527379,3318853667,677414384,3393288472,3101374703,2390351024,1614419982,1822297739,2954791486,3608508353,3174124327,2024746970,1432378464,3864339955,2857741204,1464375394,1676153920,1439316330,715854006,3033291828,
289532110,2706671279,2087905683,3018724369,1668267050,732546397,1947742710,3462151702,2609353502,2950085171,1814351708,2050118529,680887927,999245976,1800124847,3300911131,1713906067,1641548236,4213287313,1216130144,1575780402,4018429277,3917837745,3693486850,3949271944,596196993,3549867205,258830323,2213823033,772490370,2760122372,1774776394,2652871518,566650946,4142492826,1728879713,2882767088,1783734482,3629395816,2517608232,2874225571,1861159788,326777828,3124490320,2130389656,2716951837,967770486,
1724537150,2185432712,2364442137,1164943284,2105845187,998989502,3765401048,2244026483,1075463327,1455516326,1322494562,910128902,469688178,1117454909,936433444,3490320968,3675253459,1240580251,122909385,2157517691,634681816,4142456567,3825094682,3061402683,2540495037,79693498,3249098678,1084186820,1583128258,426386531,1761308591,1047286709,322548459,995290223,1845252383,2603652396,3431023940,2942221577,3202600964,3727903485,1712269319,422464435,3234572375,1170764815,3523960633,3117677531,1434042557,
442511882,3600875718,1076654713,1738483198,4213154764,2393238008,3677496056,1014306527,4251020053,793779912,2902807211,842905082,4246964064,1395751752,1040244610,2656851899,3396308128,445077038,3742853595,3577915638,679411651,2892444358,2354009459,1767581616,3150600392,3791627101,3102740896,284835224,4246832056,1258075500,768725851,2589189241,3069724005,3532540348,1274779536,3789419226,2764799539,1660621633,3471099624,4011903706,913787905,3497959166,737222580,2514213453,2928710040,3937242737,1804850592,
3499020752,2949064160,2386320175,2390070455,2415321851,4061277028,2290661394,2416832540,1336762016,1754252060,3520065937,3014181293,791618072,3188594551,3933548030,2332172193,3852520463,3043980520,413987798,3465142937,3030929376,4245938359,2093235073,3534596313,375366246,2157278981,2479649556,555357303,3870105701,2008414854,3344188149,4221384143,3956125452,2067696032,3594591187,2921233993,2428461,544322398,577241275,1471733935,610547355,4027169054,1432588573,1507829418,2025931657,3646575487,545086370,
48609733,2200306550,1653985193,298326376,1316178497,3007786442,2064951626,458293330,2589141269,3591329599,3164325604,727753846,2179363840,146436021,1461446943,4069977195,705550613,3059967265,3887724982,4281599278,3313849956,1404054877,2845806497,146425753,1854211946,1266315497,3048417604,3681880366,3289982499,290971E4,1235738493,2632868024,2414719590,3970600049,1771706367,1449415276,3266420449,422970021,1963543593,2690192192,3826793022,1062508698,1531092325,1804592342,2583117782,2714934279,4024971509,
1294809318,4028980673,1289560198,2221992742,1669523910,35572830,157838143,1052438473,1016535060,1802137761,1753167236,1386275462,3080475397,2857371447,1040679964,2145300060,2390574316,1461121720,2956646967,4031777805,4028374788,33600511,2920084762,1018524850,629373528,3691585981,3515945977,2091462646,2486323059,586499841,988145025,935516892,3367335476,2599673255,2839830854,265290510,3972581182,2759138881,3795373465,1005194799,847297441,406762289,1314163512,1332590856,1866599683,4127851711,750260880,
613907577,1450815602,3165620655,3734664991,3650291728,3012275730,3704569646,1427272223,778793252,1343938022,2676280711,2052605720,1946737175,3164576444,3914038668,3967478842,3682934266,1661551462,3294938066,4011595847,840292616,3712170807,616741398,312560963,711312465,1351876610,322626781,1910503582,271666773,2175563734,1594956187,70604529,3617834859,1007753275,1495573769,4069517037,2549218298,2663038764,504708206,2263041392,3941167025,2249088522,1514023603,1998579484,1312622330,694541497,2582060303,
2151582166,1382467621,776784248,2618340202,3323268794,2497899128,2784771155,503983604,4076293799,907881277,423175695,432175456,1378068232,4145222326,3954048622,3938656102,3820766613,2793130115,2977904593,26017576,3274890735,3194772133,1700274565,1756076034,4006520079,3677328699,720338349,1533947780,354530856,688349552,3973924725,1637815568,332179504,3949051286,53804574,2852348879,3044236432,1282449977,3583942155,3416972820,4006381244,1617046695,2628476075,3002303598,1686838959,431878346,2686675385,
1700445008,1080580658,1009431731,832498133,3223435511,2605976345,2271191193,2516031870,1648197032,4164389018,2548247927,300782431,375919233,238389289,3353747414,2531188641,2019080857,1475708069,455242339,2609103871,448939670,3451063019,1395535956,2413381860,1841049896,1491858159,885456874,4264095073,4001119347,1565136089,3898914787,1108368660,540939232,1173283510,2745871338,3681308437,4207628240,3343053890,4016749493,1699691293,1103962373,3625875870,2256883143,3830138730,1031889488,3479347698,1535977030,
4236805024,3251091107,2132092099,1774941330,1199868427,1452454533,157007616,2904115357,342012276,595725824,1480756522,206960106,497939518,591360097,863170706,2375253569,3596610801,1814182875,2094937945,3421402208,1082520231,3463918190,2785509508,435703966,3908032597,1641649973,2842273706,3305899714,1510255612,2148256476,2655287854,3276092548,4258621189,236887753,3681803219,274041037,1734335097,3815195456,3317970021,1899903192,1026095262,4050517792,356393447,2410691914,3873677099,3682840055,3913112168,
2491498743,4132185628,2489919796,1091903735,1979897079,3170134830,3567386728,3557303409,857797738,1136121015,1342202287,507115054,2535736646,337727348,3213592640,1301675037,2528481711,1895095763,1721773893,3216771564,62756741,2142006736,835421444,2531993523,1442658625,3659876326,2882144922,676362277,1392781812,170690266,3921047035,1759253602,3611846912,1745797284,664899054,1329594018,3901205900,3045908486,2062866102,2865634940,3543621612,3464012697,1080764994,553557557,3656615353,3996768171,991055499,
499776247,1265440854,648242737,3940784050,980351604,3713745714,1749149687,3396870395,4211799374,3640570775,1161844396,3125318951,1431517754,545492359,4268468663,3499529547,1437099964,2702547544,3433638243,2581715763,2787789398,1060185593,1593081372,2418618748,4260947970,69676912,2159744348,86519011,2512459080,3838209314,1220612927,3339683548,133810670,1090789135,1078426020,1569222167,845107691,3583754449,4072456591,1091646820,628848692,1613405280,3757631651,526609435,236106946,48312990,2942717905,
3402727701,1797494240,859738849,992217954,4005476642,2243076622,3870952857,3732016268,765654824,3490871365,2511836413,1685915746,3888969200,1414112111,2273134842,3281911079,4080962846,172450625,2569994100,980381355,4109958455,2819808352,2716589560,2568741196,3681446669,3329971472,1835478071,660984891,3704678404,4045999559,3422617507,3040415634,1762651403,1719377915,3470491036,2693910283,3642056355,3138596744,1364962596,2073328063,1983633131,926494387,3423689081,2150032023,4096667949,1749200295,3328846651,
309677260,2016342300,1779581495,3079819751,111262694,1274766160,443224088,298511866,1025883608,3806446537,1145181785,168956806,3641502830,3584813610,1689216846,3666258015,3200248200,1692713982,2646376535,4042768518,1618508792,1610833997,3523052358,4130873264,2001055236,3610705100,2202168115,4028541809,2961195399,1006657119,2006996926,3186142756,1430667929,3210227297,1314452623,4074634658,4101304120,2273951170,1399257539,3367210612,3027628629,1190975929,2062231137,2333990788,2221543033,2438960610,
1181637006,548689776,2362791313,3372408396,3104550113,3145860560,296247880,1970579870,3078560182,3769228297,1714227617,3291629107,3898220290,166772364,1251581989,493813264,448347421,195405023,2709975567,677966185,3703036547,1463355134,2715995803,1338867538,1343315457,2802222074,2684532164,233230375,2599980071,2000651841,3277868038,1638401717,4028070440,3237316320,6314154,819756386,300326615,590932579,1405279636,3267499572,3150704214,2428286686,3959192993,3461946742,1862657033,1266418056,963775037,
2089974820,2263052895,1917689273,448879540,3550394620,3981727096,150775221,3627908307,1303187396,508620638,2975983352,2726630617,1817252668,1876281319,1457606340,908771278,3720792119,3617206836,2455994898,1729034894,1080033504,976866871,3556439503,2881648439,1522871579,1555064734,1336096578,3548522304,2579274686,3574697629,3205460757,3593280638,3338716283,3079412587,564236357,2993598910,1781952180,1464380207,3163844217,3332601554,1699332808,1393555694,1183702653,3581086237,1288719814,691649499,2847557200,
2895455976,3193889540,2717570544,1781354906,1676643554,2592534050,3230253752,1126444790,2770207658,2633158820,2210423226,2615765581,2414155088,3127139286,673620729,2805611233,1269405062,4015350505,3341807571,4149409754,1057255273,2012875353,2162469141,2276492801,2601117357,993977747,3918593370,2654263191,753973209,36408145,2530585658,25011837,3520020182,2088578344,530523599,2918365339,1524020338,1518925132,3760827505,3759777254,1202760957,3985898139,3906192525,674977740,4174734889,2031300136,2019492241,
3983892565,4153806404,3822280332,352677332,2297720250,60907813,90501309,3286998549,1016092578,2535922412,2839152426,457141659,509813237,4120667899,652014361,1966332200,2975202805,55981186,2327461051,676427537,3255491064,2882294119,3433927263,1307055953,942726286,933058658,2468411793,3933900994,4215176142,1361170020,2001714738,2830558078,3274259782,1222529897,1679025792,2729314320,3714953764,1770335741,151462246,3013232138,1682292957,1483529935,471910574,1539241949,458788160,3436315007,1807016891,
3718408830,978976581,1043663428,3165965781,1927990952,4200891579,2372276910,3208408903,3533431907,1412390302,2931980059,4132332400,1947078029,3881505623,4168226417,2941484381,1077988104,1320477388,886195818,18198404,3786409E3,2509781533,112762804,3463356488,1866414978,891333506,18488651,661792760,1628790961,3885187036,3141171499,876946877,2693282273,1372485963,791857591,2686433993,3759982718,3167212022,3472953795,2716379847,445679433,3561995674,3504004811,3574258232,54117162,3331405415,2381918588,
3769707343,4154350007,1140177722,4074052095,668550556,3214352940,367459370,261225585,2610173221,4209349473,3468074219,3265815641,314222801,3066103646,3808782860,282218597,3406013506,3773591054,379116347,1285071038,846784868,2669647154,3771962079,3550491691,2305946142,453669953,1268987020,3317592352,3279303384,3744833421,2610507566,3859509063,266596637,3847019092,517658769,3462560207,3443424879,370717030,4247526661,2224018117,4143653529,4112773975,2788324899,2477274417,1456262402,2901442914,1517677493,
1846949527,2295493580,3734397586,2176403920,1280348187,1908823572,3871786941,846861322,1172426758,3287448474,3383383037,1655181056,3139813346,901632758,1897031941,2986607138,3066810236,3447102507,1393639104,373351379,950779232,625454576,3124240540,4148612726,2007998917,544563296,2244738638,2330496472,2058025392,1291430526,424198748,50039436,29584100,3605783033,2429876329,2791104160,1057563949,3255363231,3075367218,3463963227,1469046755,985887462],E=[1332899944,1700884034,1701343084,1684370003,1668446532,
1869963892];k.encodeBase64=x;k.decodeBase64=B;return k});

View File

@@ -0,0 +1 @@
- Presence on device details breaks

File diff suppressed because it is too large Load Diff

33679
front/lib/datatables/datatables.js Executable file

File diff suppressed because it is too large Load Diff

37
front/lib/datatables/datatables.min.css vendored Executable file

File diff suppressed because one or more lines are too long

180
front/lib/datatables/datatables.min.js vendored Executable file

File diff suppressed because one or more lines are too long

4178
front/lib/treeviz/bundle.js Executable file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,41 +1,16 @@
<?php
#---------------------------------------------------------------------------------#
# NetAlertX #
# Open Source Network Guard / WIFI & LAN intrusion detector #
# #
# maintenance.php - Front module. Server side. Maintenance #
#---------------------------------------------------------------------------------#
# Puche 2021 pi.alert.application@gmail.com GNU GPLv3 #
# jokob-sk 2022 jokob.sk@gmail.com GNU GPLv3 #
# leiweibau 2022 https://github.com/leiweibau GNU GPLv3 #
# cvc90 2023 https://github.com/cvc90 GNU GPLv3 #
#---------------------------------------------------------------------------------#
//------------------------------------------------------------------------------
?>
<?php
require 'php/templates/header.php';
require 'php/templates/notification.php';
?>
<!-- Page ------------------------------------------------------------------ -->
<div class="content-wrapper" id="maintenancePage">
<!-- Content header--------------------------------------------------------- -->
<section class="content-header">
<?php require 'php/templates/notification.php'; ?>
<h1 id="pageTitle">
<i class="fa fa-wrench"></i>
<?= lang('Maintenance_Title');?>
</h1>
</section>
<!-- Main content ---------------------------------------------------------- -->
<section class="content">
<!-- Main content ---------------------------------------------------------- -->
<section class="content">
<?php
<?php
// Size and last mod of DB ------------------------------------------------------
@@ -85,10 +60,11 @@ $db->close();
<div class="db_info_table_row">
<div class="db_info_table_cell" style="min-width: 140px"><?= lang('Maintenance_version');?>
<a href="https://github.com/jokob-sk/NetAlertX/blob/main/docs/VERSIONS.md" target="_blank"> <span><i class="fa fa-circle-question"></i></a><span>
</div>
<div class="db_info_table_cell">
<div class="version" id="version" data-build-time="<?php echo file_get_contents( "buildtimestamp.txt");?>"><?php echo '<span id="new-version-text" class="myhidden">' .lang('Maintenance_new_version').'</span>'.'<span id="current-version-text" class="myhidden">' .lang('Maintenance_current_version').'</span>';?></div>
<div class="version" id="version" data-build-time="<?php echo file_get_contents( "buildtimestamp.txt");?>">
<?php echo '<span id="new-version-text" class="myhidden"><i class="fa-solid fa-rocket fa-beat"></i> ' .lang('Maintenance_new_version').'</span>'.'<span id="current-version-text" class="myhidden">' .lang('Maintenance_current_version').'</span>';?>
</div>
</div>
</div>
<div class="db_info_table_row">
@@ -203,26 +179,38 @@ $db->close();
</div>
</div>
<div class="tab-pane" id="tab_BackupRestore">
<div class="db_info_table">
<div class="db_info_table_row">
<div class="db_tools_table_cell_a" >
<button type="button" class="btn btn-default pa-btn bg-green dbtools-button" id="btnExportCSV" onclick="ExportCSV()"><?= lang('Maintenance_Tool_ExportCSV');?></button>
</div>
<div class="db_tools_table_cell_b"><?= lang('Maintenance_Tool_ExportCSV_text');?></div>
</div>
<div class="db_info_table_row">
<div class="db_tools_table_cell_a" >
<button type="button" class="btn btn-default pa-btn pa-btn-delete bg-red dbtools-button" id="btnImportCSV" onclick="askImportCSV()"><?= lang('Maintenance_Tool_ImportCSV');?></button>
</div>
<div class="db_tools_table_cell_b"><?= lang('Maintenance_Tool_ImportCSV_text');?></div>
</div>
<div class="db_info_table_row">
<div class="db_tools_table_cell_a" >
<button type="button" class="btn btn-default pa-btn pa-btn-delete bg-red dbtools-button" id="btnImportPastedCSV" onclick="askImportPastedCSV()"><?= lang('Maintenance_Tool_ImportPastedCSV');?></button>
</div>
<div class="db_tools_table_cell_b"><?= lang('Maintenance_Tool_ImportPastedCSV_text');?></div>
</div>
</div>
<div class="db_info_table">
<div class="db_info_table_row">
<div class="db_tools_table_cell_a" >
<button type="button" class="btn btn-default pa-btn bg-green dbtools-button" id="btnExportCSV" onclick="ExportCSV()"><?= lang('Maintenance_Tool_ExportCSV');?></button>
</div>
<div class="db_tools_table_cell_b"><?= lang('Maintenance_Tool_ExportCSV_text');?></div>
</div>
<div class="db_info_table_row">
<div class="db_tools_table_cell_a" >
<button type="button" class="btn btn-default pa-btn pa-btn-delete bg-red dbtools-button" id="btnImportCSV" onclick="askImportCSV()"><?= lang('Maintenance_Tool_ImportCSV');?></button>
</div>
<div class="db_tools_table_cell_b"><?= lang('Maintenance_Tool_ImportCSV_text');?></div>
</div>
<div class="db_info_table_row">
<div class="db_tools_table_cell_a" >
<button type="button" class="btn btn-default pa-btn pa-btn-delete bg-red dbtools-button" id="btnImportPastedCSV" onclick="askImportPastedCSV()"><?= lang('Maintenance_Tool_ImportPastedCSV');?></button>
</div>
<div class="db_tools_table_cell_b"><?= lang('Maintenance_Tool_ImportPastedCSV_text');?></div>
</div>
<div class="db_info_table_row">
<div class="db_tools_table_cell_a" >
<button type="button" class="btn btn-default pa-btn bg-green dbtools-button" id="btnDownloadConfig" onclick="DownloadConfig()"><?= lang('Maintenance_Tool_DownloadConfig');?></button>
</div>
<div class="db_tools_table_cell_b"><?= lang('Maintenance_Tool_DownloadConfig_text');?></div>
</div>
<div class="db_info_table_row">
<div class="db_tools_table_cell_a" >
<button type="button" class="btn btn-default pa-btn pa-btn-delete bg-red dbtools-button" id="btnImportPastedConfig" onclick="askImportPastedConfig()"><?= lang('Maintenance_Tool_ImportPastedConfig');?></button>
</div>
<div class="db_tools_table_cell_b"><?= lang('Maintenance_Tool_ImportPastedConfig_text');?></div>
</div>
</div>
</div>
<!-- ---------------------------Logging-------------------------------------------- -->
<div class="tab-pane" id="tab_Logging">
@@ -439,6 +427,44 @@ function restartBackend() {
})
}
// -----------------------------------------------------------
// Import pasted Config ASK
function askImportPastedConfig() {
// Add new icon as base64 string
showModalInput ('<i class="fa fa-square-plus pointer"></i> <?= lang('Maintenance_Tool_ImportConfig_noti');?>', '<?= lang('Maintenance_Tool_ImportPastedConfig_noti_text');?>',
'<?= lang('Gen_Cancel');?>', '<?= lang('Gen_Okay');?>', 'UploadConfig');
}
// -----------------------------------------------------------
// Upload Settings Config
function UploadConfig()
{
// alert("aaa")
appConf = $('#modal-input-textarea').val()
// encode for import
appConfBase64 = btoa(appConf)
// import
$.post('php/server/query_replace_config.php', { config: appConfBase64 }, function(msg) {
console.log(msg);
// showMessage(msg);
write_notification(`[Maintenance] Settings imported from backup: ${msg}`, 'interrupt');
});
}
// -----------------------------------------------------------
// Download Settings Config
function DownloadConfig()
{
// Execute
openInNewTab("php/server/query_config.php?file=app.conf&download=true")
}
// -----------------------------------------------------------
// Export CSV
function ExportCSV()
@@ -476,8 +502,13 @@ function ImportPastedCSV()
{
var csv = $('#modal-input-textarea').val();
console.log(csv);
csvBase64 = utf8ToBase64(csv);
console.log(csvBase64);
$.post('php/server/devices.php?action=ImportCSV', { content: csvBase64 }, function(msg) {
showMessage(msg);
write_notification(`[Maintenance] Devices imported from pasted content`, 'info');

View File

@@ -17,14 +17,14 @@
<h3 class="box-title"><?= lang('Gen_Selected_Devices');?></h3>
</div>
<div class="deviceSelector col-md-9 col-sm-12" style="z-index:5"></div>
<div class="deviceSelector col-md-11 col-sm-11" style="z-index:5"></div>
<div class="col-md-3">
<button type="button" class="btn btn-default" onclick="markAllSelected()">
<i class="fa-solid fa-circle-check"></i> <?= lang('Gen_Add_All');?>
<div class="col-md-1">
<button type="button" class="btn btn-default col-md-12" onclick="markAllSelected()" title="<?= lang('Gen_Add_All');?>">
<i class="fa-solid fa-circle-check"></i>
</button>
<button type="button" class="btn btn-default" onclick="markAllNotSelected()">
<i class="fa-solid fa-circle-xmark"></i> <?= lang('Gen_Remove_All');?>
<button type="button" class="btn btn-default col-md-12" onclick="markAllNotSelected()" title="<?= lang('Gen_Remove_All');?>">
<i class="fa-solid fa-circle-xmark"></i>
</button>
</div>
@@ -77,11 +77,11 @@
// some race condition, need to implement delay
setTimeout(() => {
$.get('api/table_settings.json?nocache=' + Date.now(), function(res) {
$.get('/php/server/query_json.php', { file: 'table_settings.json', nocache: Date.now() }, function(res) {
settingsData = res["data"];
excludedColumns = ["NEWDEV_devMac", "NEWDEV_devFirstConnection", "NEWDEV_devLastConnection", "NEWDEV_devLastNotification", "NEWDEV_devLastIP", "NEWDEV_devStaticIP", "NEWDEV_devScan", "NEWDEV_devPresentLastScan" ]
excludedColumns = ["NEWDEV_devMac", "NEWDEV_devFirstConnection", "NEWDEV_devLastConnection", "NEWDEV_devLastNotification", "NEWDEV_devScan", "NEWDEV_devPresentLastScan", "NEWDEV_devCustomProps" ]
const relevantColumns = settingsData.filter(set =>
set.setGroup === "NEWDEV" &&
@@ -90,21 +90,21 @@
!set.setKey.includes("__metadata")
);
const generateSimpleForm = columns => {
const generateSimpleForm = multiEditColumns => {
const form = $('#multi-edit-form');
const numColumns = 2; // Number of columns
// Calculate number of elements per column
const elementsPerColumn = Math.ceil(columns.length / numColumns);
const elementsPerColumn = Math.ceil(multiEditColumns.length / numColumns);
// Divide columns equally
for (let i = 0; i < numColumns; i++) {
const column = $('<div>').addClass('col-md-6');
// Append form groups to the column
for (let j = i * elementsPerColumn; j < Math.min((i + 1) * elementsPerColumn, columns.length); j++) {
for (let j = i * elementsPerColumn; j < Math.min((i + 1) * elementsPerColumn, multiEditColumns.length); j++) {
const setTypeObject = JSON.parse(columns[j].setType.replace(/'/g, '"'));
const setTypeObject = JSON.parse(multiEditColumns[j].setType.replace(/'/g, '"'));
// get the element with the input value(s)
let elements = setTypeObject.elements.filter(element => element.elementHasInputValue === 1);
@@ -135,37 +135,39 @@
onClick,
onChange,
customParams,
customId
customId,
columns,
base64Regex
} = handleElementOptions('none', elementOptions, transformers, val = "");
// render based on element type
if (elementType === 'select') {
targetLocation = columns[j].setKey + "_generateSetOptions"
targetLocation = multiEditColumns[j].setKey + "_generateSetOptions"
generateOptionsOrSetOptions(columns[j].setKey, [], targetLocation, generateOptions)
generateOptionsOrSetOptions(multiEditColumns[j].setKey, [], targetLocation, generateOptions, null)
console.log(columns[j].setKey)
console.log(multiEditColumns[j].setKey)
// Handle Icons as they need a preview
if(columns[j].setKey == 'NEWDEV_devIcon')
if(multiEditColumns[j].setKey == 'NEWDEV_devIcon')
{
input = `
<span class="input-group-addon iconPreview" my-customid="NEWDEV_devIcon_preview"></span>
<select class="form-control"
onChange="updateIconPreview(this)"
my-customparams="NEWDEV_devIcon,NEWDEV_devIcon_preview"
id="${columns[j].setKey}"
data-my-column="${columns[j].setKey}"
data-my-targetColumns="${columns[j].setKey.replace('NEWDEV_','')}" >
id="${multiEditColumns[j].setKey}"
data-my-column="${multiEditColumns[j].setKey}"
data-my-targetColumns="${multiEditColumns[j].setKey.replace('NEWDEV_','')}" >
<option id="${targetLocation}"></option>
</select>`
} else{
input = `<select class="form-control"
id="${columns[j].setKey}"
data-my-column="${columns[j].setKey}"
data-my-targetColumns="${columns[j].setKey.replace('NEWDEV_','')}" >
id="${multiEditColumns[j].setKey}"
data-my-column="${multiEditColumns[j].setKey}"
data-my-targetColumns="${multiEditColumns[j].setKey.replace('NEWDEV_','')}" >
<option id="${targetLocation}"></option>
</select>`
}
@@ -178,20 +180,20 @@
input = `<input class="${inputClass}"
id="${columns[j].setKey}"
my-customid="${columns[j].setKey}"
data-my-column="${columns[j].setKey}"
data-my-targetColumns="${columns[j].setKey.replace('NEWDEV_','')}"
id="${multiEditColumns[j].setKey}"
my-customid="${multiEditColumns[j].setKey}"
data-my-column="${multiEditColumns[j].setKey}"
data-my-targetColumns="${multiEditColumns[j].setKey.replace('NEWDEV_','')}"
type="${inputType}">`
}
const inputEntry = `<div class="form-group col-sm-12" >
<label class="col-sm-3 control-label">${columns[j].setName}</label>
<label class="col-sm-3 control-label">${multiEditColumns[j].setName}</label>
<div class="col-sm-9">
<div class="input-group red-hover-border">
${input}
<span class="input-group-addon pointer red-hover-background" onclick="massUpdateField('${columns[j].setKey}');" title="${getString('Device_MultiEdit_Tooltip')}">
<span class="input-group-addon pointer red-hover-background" onclick="massUpdateField('${multiEditColumns[j].setKey}');" title="${getString('Device_MultiEdit_Tooltip')}">
<i class="fa fa-save"></i>
</span>
</div>

View File

@@ -1,6 +1,7 @@
<?php
require 'php/templates/header.php';
require 'php/templates/notification.php';
// online / offline badges HTML snippets
define('badge_online', '<div class="badge bg-green text-white" style="width: 60px;">Online</div>');
@@ -11,18 +12,17 @@
?>
<script>
// show spinning icon
showSpinner()
</script>
<!-- Page ------------------------------------------------------------------ -->
<div class="content-wrapper">
<!-- Content header--------------------------------------------------------- -->
<section class="content-header">
<?php require 'php/templates/notification.php'; ?>
<h1 id="pageTitle">
<i class="fa fa-network-wired"></i> <?= lang('Network_Title');?>
<span class="helpIconSmallTopRight"> <a target="_blank" href="https://github.com/jokob-sk/NetAlertX/blob/main/docs/NETWORK_TREE.md"><i class="fa fa-circle-question"></i></a><span>
</h1>
</section>
<span class="networkHelpIcon"> <a target="_blank" href="https://github.com/jokob-sk/NetAlertX/blob/main/docs/NETWORK_TREE.md"><i class="fa fa-circle-question"></i></a></span>
<div id="networkTree" class="drag"></div>
@@ -464,9 +464,8 @@
require 'php/templates/footer.php';
?>
<script src="lib/treeviz/index.js"></script>
<script src="lib/treeviz/require.js"></script>
<script src="lib/treeviz/bundle.js"></script>
<script src="lib/treeviz/bundle.js.map"></script>
<script defer>
@@ -481,6 +480,8 @@
return;
}
orderTopologyBy = createArray(getSetting("UI_TOPOLOGY_ORDER"))
devicesListnew = rawData["data"].map(item => {
return {
"name": item[0],
@@ -494,20 +495,36 @@
"port": item[18]
};
}).sort((a, b) => {
// First sort by name alphabetically
const nameCompare = a.name.localeCompare(b.name);
if (nameCompare !== 0) {
// Helper to safely parse port into an integer; invalid ports become Infinity for sorting
const parsePort = (port) => {
const parsed = parseInt(port, 10);
return isNaN(parsed) ? Infinity : parsed;
};
switch (orderTopologyBy[0]) {
case "Name":
// First sort by name alphabetically
const nameCompare = a.name.localeCompare(b.name);
if (nameCompare !== 0) {
return nameCompare;
}
// If names are the same, sort by port numerically
return a.port - b.port;
}
// If names are the same, sort by port numerically
return parsePort(a.port) - parsePort(b.port);
case "Port":
// Sort by port numerically
return parsePort(a.port) - parsePort(b.port);
default:
// Default: Sort by rowid (as a fallback)
return a.rowid - b.rowid;
}
});
setCache('devicesListNew', JSON.stringify(devicesListnew));
// Init global variable
deviceListGlobal = devicesListnew;
// create tree
initTree(getHierarchy());
@@ -519,381 +536,414 @@
<script defer>
// ---------------------------------------------------------------------------
// Tree functionality
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Tree functionality
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
var leafNodesCount = 0;
var visibleNodesCount = 0;
var parentNodesCount = 0;
var hiddenMacs = []; // hidden children
var hiddenChildren = [];
var deviceListGlobal = null;
// ---------------------------------------------------------------------------
// Recursively get children nodes and build a tree
function getChildren(node, list, path, visited = [])
{
var children = [];
// Check for infinite recursion by seeing if the node has been visited before
if (visited.includes(node.mac.toLowerCase())) {
console.error("Infinite recursion detected at node:", node.mac);
write_notification("[ERROR] ⚠ Infinite recursion detected. You probably have assigned the Internet node to another children node or to itself. Please open a new issue on GitHub and describe how you did it.", 'interrupt')
return { error: "Infinite recursion detected", node: node.mac };
}
// Add current node to visited list
visited.push(node.mac.toLowerCase());
// Loop through all items to find children of the current node
for (var i in list) {
if (list[i].parentMac.toLowerCase() == node.mac.toLowerCase() && !hiddenMacs.includes(list[i].parentMac)) {
visibleNodesCount++;
// Process children recursively, passing a copy of the visited list
children.push(getChildren(list[i], list, path + ((path == "") ? "" : '|') + list[i].parentMac, visited));
}
}
// Track leaf and parent node counts
if (children.length == 0) {
leafNodesCount++;
} else {
parentNodesCount++;
}
return {
name: node.name,
path: path,
mac: node.mac,
port: node.port,
id: node.mac,
parentMac: node.parentMac,
icon: node.icon,
type: node.type,
status: node.status,
hasChildren: children.length > 0 || hiddenMacs.includes(node.mac),
hiddenChildren: hiddenMacs.includes(node.mac),
qty: children.length,
children: children
};
}
// ---------------------------------------------------------------------------
function getHierarchy()
{
for(i in deviceListGlobal)
{
if(deviceListGlobal[i].mac == 'Internet')
{
return (getChildren(deviceListGlobal[i], deviceListGlobal, ''))
break;
}
}
}
// ---------------------------------------------------------------------------
function getFlatData() {
var result = [];
var leafNodesCount = 0;
var visibleNodesCount = 0;
var parentNodesCount = 0;
var hiddenMacs = []; // hidden children
var hiddenChildren = [];
var deviceListGlobal = null;
var visibleNodesCount = 0;
for (let node of deviceListGlobal) {
let path = "";
let childrenCount = 0;
// ---------------------------------------------------------------------------
// Recursively get children nodes and build a tree
function getChildren(node, list, path, visited = [])
{
var children = [];
// Check for infinite recursion by seeing if the node has been visited before
if (visited.includes(node.mac.toLowerCase())) {
console.error("Infinite recursion detected at node:", node.mac);
write_notification("[ERROR] ⚠ Infinite recursion detected. You probably have assigned the Internet node to another children node or to itself. Please open a new issue on GitHub and describe how you did it.", 'interrupt')
return { error: "Infinite recursion detected", node: node.mac };
}
// Add current node to visited list
visited.push(node.mac.toLowerCase());
// Loop through all items to find children of the current node
for (var i in list) {
if (list[i].parentMac.toLowerCase() == node.mac.toLowerCase() && !hiddenMacs.includes(list[i].parentMac)) {
visibleNodesCount++;
// Process children recursively, passing a copy of the visited list
children.push(getChildren(list[i], list, path + ((path == "") ? "" : '|') + list[i].parentMac, visited));
}
}
// Track leaf and parent node counts
if (children.length == 0) {
leafNodesCount++;
} else {
parentNodesCount++;
}
return {
name: node.name,
path: path,
mac: node.mac,
port: node.port,
id: node.mac,
parentMac: node.parentMac,
icon: node.icon,
type: node.type,
status: node.status,
hasChildren: children.length > 0 || hiddenMacs.includes(node.mac),
hiddenChildren: hiddenMacs.includes(node.mac),
qty: children.length,
children: children
};
}
// ---------------------------------------------------------------------------
function getHierarchy()
{
for(i in deviceListGlobal)
{
if(deviceListGlobal[i].mac == 'Internet')
{
return (getChildren(deviceListGlobal[i], deviceListGlobal, ''))
break;
}
// count children of this node
for (let nodeTmp of deviceListGlobal) {
if (nodeTmp.parentMac === node.mac) {
childrenCount++;
}
}
}
// ---------------------------------------------------------------------------
function toggleSubTree(parentMac, treePath)
{
treePath = treePath.split('|')
if(!hiddenMacs.includes(parentMac))
{
hiddenMacs.push(parentMac)
}
else
{
removeItemFromArray(hiddenMacs, parentMac)
}
// updatedTree = myHierarchy;
updatedTree = getHierarchy()
myTree.refresh(updatedTree);
// re-attach any onclick events
attachTreeEvents();
}
// ---------------------------------------------------------------------------
function attachTreeEvents()
{
// toggle subtree functionality
$("div[data-mytreemac]").each(function(){
$(this).attr('onclick', 'toggleSubTree("'+$(this).attr('data-mytreemac')+'","'+ $(this).attr('data-mytreepath')+'")')
});
}
// ---------------------------------------------------------------------------
// Handle network node click - select correct tab in the bottom table
function handleNodeClick(event)
{
// console.log(event.target.offsetParent.offsetParent)
const targetTabMAC = $(event.target.offsetParent.offsetParent).attr("data-mytreemacmain");
var targetTab = $(`a[data-mytabmac="${targetTabMAC}"]`);
// Simulate a click event on the target tab
targetTab.click();
}
// ---------------------------------------------------------------------------
var myTree;
var visibleTreeArea = $(window).height()-155;
var nodeWidth = 160;
var emSize;
var nodeHeight;
var sizeCoefficient = 1
function initTree(myHierarchy)
{
console.log(myHierarchy)
// calculate the drawing area based on teh tree width and available screen size
var treeAreaHeight = visibleTreeArea > 800 ? 800 : visibleTreeArea;
let screenWidth = $('.content-header').width();
let treeWidth = (nodeWidth + 20) * parentNodesCount;
let treeAreaWidth = screenWidth < treeWidth ? treeWidth : screenWidth;
// init the drawing area size
$("#networkTree").attr('style', `height:${treeAreaHeight}px; width:${treeAreaWidth}px`)
if(myHierarchy.type == "")
{
showModalOk(getString('Network_Configuration_Error'), getString('Network_Root_Not_Configured'))
return;
}
// calculate the font size of the leaf nodes to fit everything into the tree area
leafNodesCount == 0 ? 1 : leafNodesCount;
emSize = ((treeAreaHeight/(25*leafNodesCount)).toFixed(2));
emSize = emSize > 1 ? 1 : emSize;
// nodeHeight = ((emSize*100*0.30).toFixed(0))
nodeHeight = ((emSize*100*0.30).toFixed(0))
console.log(Treeviz);
myTree = Treeviz.create({
htmlId: "networkTree",
renderNode: nodeData => {
var fontSize = "font-size:"+emSize+"em;";
(!emptyArr.includes(nodeData.data.port )) ? port = nodeData.data.port : port = "";
(port == "" || port == 0 || port == 'None' ) ? portBckgIcon = `<i class="fa fa-wifi"></i>` : portBckgIcon = `<i class="fa fa-ethernet"></i>`;
portHtml = (port == "" || port == 0 || port == 'None' ) ? "" : port
// Build HTML for individual nodes in the network diagram
deviceIcon = (!emptyArr.includes(nodeData.data.icon )) ?
`<div class="netIcon">
${atob(nodeData.data.icon)}
</div>` : "";
devicePort = `<div class="netPort"
style="width:${emSize*sizeCoefficient}em;height:${emSize*sizeCoefficient}em">
${portHtml}</div>
<div class="portBckgIcon"
style="margin-left:-${emSize*sizeCoefficient}em;">
${portBckgIcon}
</div>`;
collapseExpandIcon = nodeData.data.hiddenChildren ?
"square-plus" : "square-minus";
// generate +/- icon if node has children nodes
collapseExpandHtml = nodeData.data.hasChildren ?
`<div class="netCollapse"
style="font-size:${emSize*sizeCoefficient}em;top:${1/2*emSize*sizeCoefficient}em"
data-mytreepath="${nodeData.data.path}"
data-mytreemac="${nodeData.data.mac}">
<i class="fa fa-${collapseExpandIcon} pointer"></i>
</div>` : "";
selectedNodeMac = $(".nav-tabs-custom .active a").attr('data-mytabmac')
highlightedCss = nodeData.data.mac == selectedNodeMac ?
" highlightedNode" : "";
// css indicating online/offline status
statusCss = ` netStatus-${nodeData.data.status}`;
return result = `<div class="box ${nodeData.data.hasChildren ? "pointer":""} ${statusCss} ${highlightedCss}"
data-mytreemacmain="${nodeData.data.mac}"
style="height:${nodeData.settings.nodeHeight}px;${fontSize}"
>
<div class="netNodeText">
<strong>${devicePort} ${deviceIcon}
<span class="spanNetworkTree anonymizeDev" >${nodeData.data.name}</span>
</strong>
${collapseExpandHtml}
</div>
</div>`;
},
onNodeClick: nodeData => {
console.log(this)
},
mainAxisNodeSpacing: 'auto',
// mainAxisNodeSpacing: 3,
secondaryAxisNodeSpacing: 0.3,
nodeHeight: nodeHeight.toString(),
marginTop: '5',
hasZoom: true,
hasPan: true,
// marginLeft: '15',
idKey: "id",
hasFlatData: false,
linkWidth: (nodeData) => 3,
linkColor: (nodeData) => "#ffcc80",
onNodeClick: (nodeData) => handleNodeClick(nodeData),
relationnalField: "children",
});
myTree.refresh(myHierarchy);
// hide spinning icon
hideSpinner()
}
// ---------------------------------------------------------------------------
// Tabs functionality
// ---------------------------------------------------------------------------
// Register events on tab change
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
initButtons()
});
// ---------------------------------------------------------------------------
function initTab()
{
key = "activeNetworkTab"
// default selection
selectedTab = "Internet_id"
// the #target from the url
target = getQueryString('mac')
// update cookie if target specified
if(target != "")
{
setCache(key, target.replaceAll(":","_")+'_id') // _id is added so it doesn't conflict with AdminLTE tab behavior
}
// get the tab id from the cookie (already overridden by the target)
if(!emptyArr.includes(getCache(key)))
{
selectedTab = getCache(key);
}
// Activate panel
$('.nav-tabs a[id='+ selectedTab +']').tab('show');
// When changed save new current tab
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
setCache(key, $(e.target).attr('id'))
});
}
// ---------------------------------------------------------------------------
function initDeviceNamesFromMACs()
{
$('.mac-to-name').each(function() {
var dataMacValue = $(this).attr('my-data-mac');
if(dataMacValue =="" )
{
$(this).html(getString("Network_Root"))
}
else{
$(this).html(getNameByMacAddress(dataMacValue));
}
// store parent and leaf node count
if (childrenCount === 0) {
leafNodesCount++;
} else {
parentNodesCount++;
}
if (!hiddenMacs.includes(node.parentMac)) {
if (!((node.parentMac == "") && node.mac != "Internet")) { // skip leaf nodes without father that are not the root
visibleNodesCount++;
});
}
// ---------------------------------------------------------------------------
function initButtons()
{
var currentNodeMac = $(".tab-content .active td[data-mynodemac]").attr('data-mynodemac');
// change highlighted node in the tree
selNode = $("#networkTree .highlightedNode")[0]
// console.log(selNode)
if(selNode)
{
$(selNode).attr('class', $(selNode).attr('class').replace('highlightedNode'))
result.push({
name: node.name,
path: path,
mac: node.mac, // Replacing "mac" with "id"
parentMac: node.mac == "Internet" ? "" : node.parentMac, // Replacing "parentMac" with "father"
port: node.port,
icon: node.icon,
type: node.type,
status: node.status,
hasChildren: childrenCount > 0 || hiddenMacs.includes(node.mac),
hiddenChildren: hiddenMacs.includes(node.mac),
qty: childrenCount,
});
}
}
newSelNode = $("#networkTree div[data-mytreemacmain='"+currentNodeMac+"']")[0]
$(newSelNode).attr('class', $(newSelNode).attr('class') + ' highlightedNode')
// init the Assign buttons
$('#unassignedDevices button[data-myleafmac]').each(function(){
$(this).attr('onclick', `updateLeaf("${$(this).attr('data-myleafmac')}","${currentNodeMac}")`)
});
// init Unassign buttons
$('#assignedDevices button[data-myleafmac]').each(function(){
$(this).attr('onclick', `updateLeaf("${$(this).attr('data-myleafmac')}","")`)
});
}
// ---------------------------------------------------------------------------
function updateLeaf(leafMac,nodeMac)
return result;
}
//---------------------------------------------------------------------------
function toggleSubTree(parentMac, treePath)
{
treePath = treePath.split('|')
if(!hiddenMacs.includes(parentMac))
{
console.log(leafMac) // child
console.log(nodeMac) // parent
console.log(nodeMac != "") // parent
// prevent the assignment of the Internet root node avoiding recursion when generating the network tree topology
if(leafMac.toLowerCase().includes('internet') && nodeMac != "")
{
showMessage(getString('Network_Cant_Assign'))
}
else{
saveData('updateNetworkLeaf', leafMac, nodeMac);
setTimeout("location.reload();", 500); // refresh page
}
hiddenMacs.push(parentMac)
}
else
{
removeItemFromArray(hiddenMacs, parentMac)
}
// show spinning icon
showSpinner()
updatedTree = getHierarchy()
myTree.refresh(updatedTree);
// init device names where macs are used
initDeviceNamesFromMACs();
// re-attach any onclick events
attachTreeEvents();
}
// init selected (first) tab
initTab();
// ---------------------------------------------------------------------------
function attachTreeEvents()
{
// toggle subtree functionality
$("div[data-mytreemac]").each(function(){
$(this).attr('onclick', 'toggleSubTree("'+$(this).attr('data-mytreemac')+'","'+ $(this).attr('data-mytreepath')+'")')
});
}
// ---------------------------------------------------------------------------
// Handle network node click - select correct tab in the bottom table
function handleNodeClick(nodeData)
{
const targetTabMAC = nodeData.data.mac;
var targetTab = $(`a[data-mytabmac="${targetTabMAC}"]`);
// Simulate a click event on the target tab
targetTab.click();
}
// ---------------------------------------------------------------------------
var myTree;
var visibleTreeArea = $(window).height()-155;
var nodeWidth = 120;
var emSize;
var nodeHeight;
var sizeCoefficient = 1.4
function initTree(myHierarchy)
{
// calculate the drawing area based on teh tree width and available screen size
var treeAreaHeight = visibleTreeArea > 800 ? 800 : visibleTreeArea;
let screenWidth = $('.content-header').width();
let treeWidth = (nodeWidth + 20) * parentNodesCount;
let treeAreaWidth = screenWidth < treeWidth ? treeWidth : screenWidth;
// init the drawing area size
$("#networkTree").attr('style', `height:${treeAreaHeight}px; width:${treeAreaWidth}px`)
if(myHierarchy.type == "")
{
showModalOk(getString('Network_Configuration_Error'), getString('Network_Root_Not_Configured'))
return;
}
// calculate the font size of the leaf nodes to fit everything into the tree area
leafNodesCount == 0 ? 1 : leafNodesCount;
emSize = ((treeAreaHeight/(25*leafNodesCount)).toFixed(2));
emSize = emSize > 1 ? 1 : emSize;
// nodeHeight = ((emSize*100*0.30).toFixed(0))
nodeHeight = ((emSize*100*0.30).toFixed(0))
console.log(Treeviz);
myTree = Treeviz.create({
htmlId: "networkTree",
renderNode: nodeData => {
var fontSize = "font-size:"+emSize+"em;";
(!emptyArr.includes(nodeData.data.port )) ? port = nodeData.data.port : port = "";
(port == "" || port == 0 || port == 'None' ) ? portBckgIcon = `<i class="fa fa-wifi"></i>` : portBckgIcon = `<i class="fa fa-ethernet"></i>`;
portHtml = (port == "" || port == 0 || port == 'None' ) ? "" : port;
// Build HTML for individual nodes in the network diagram
deviceIcon = (!emptyArr.includes(nodeData.data.icon )) ?
`<div class="netIcon">
${atob(nodeData.data.icon)}
</div>` : "";
devicePort = `<div class="netPort"
style="width:${emSize*sizeCoefficient}em;height:${emSize*sizeCoefficient}em">
${portHtml}</div>
<div class="portBckgIcon"
style="margin-left:-${emSize*sizeCoefficient}em;">
${portBckgIcon}
</div>`;
collapseExpandIcon = nodeData.data.hiddenChildren ?
"square-plus" : "square-minus";
// generate +/- icon if node has children nodes
collapseExpandHtml = nodeData.data.hasChildren ?
`<div class="netCollapse"
style="font-size:${emSize*sizeCoefficient}em;top:${emSize/6}em"
data-mytreepath="${nodeData.data.path}"
data-mytreemac="${nodeData.data.mac}">
<i class="fa fa-${collapseExpandIcon} pointer"></i>
</div>` : "";
selectedNodeMac = $(".nav-tabs-custom .active a").attr('data-mytabmac')
highlightedCss = nodeData.data.mac == selectedNodeMac ?
" highlightedNode" : "";
// css indicating online/offline status
statusCss = ` netStatus-${nodeData.data.status}`;
return result = `<div class="box ${nodeData.data.hasChildren ? "pointer":""} ${statusCss} ${highlightedCss}"
data-mytreemacmain="${nodeData.data.mac}"
style="height:${nodeData.settings.nodeHeight}px;${fontSize}"
>
<div class="netNodeText">
<strong>${devicePort} ${deviceIcon}
<span class="spanNetworkTree anonymizeDev" >${nodeData.data.name}</span>
</strong>
${collapseExpandHtml}
</div>
</div>`;
},
mainAxisNodeSpacing: 'auto',
secondaryAxisNodeSpacing: 0.3,
nodeHeight: nodeHeight.toString(),
marginTop: '5',
isHorizontal : true,
hasZoom: true,
hasPan: true,
marginLeft: '15',
idKey: "mac",
hasFlatData: false,
relationnalField: "children",
linkWidth: (nodeData) => 3,
linkColor: (nodeData) => "#ffcc80",
onNodeClick: (nodeData) => handleNodeClick(nodeData),
});
console.log(deviceListGlobal);
myTree.refresh(myHierarchy);
// hide spinning icon
hideSpinner()
}
// ---------------------------------------------------------------------------
// Tabs functionality
// ---------------------------------------------------------------------------
// Register events on tab change
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
// init Assign/Unassign buttons
initButtons()
});
// ---------------------------------------------------------------------------
function initTab()
{
key = "activeNetworkTab"
// default selection
selectedTab = "Internet_id"
// the #target from the url
target = getQueryString('mac')
// update cookie if target specified
if(target != "")
{
setCache(key, target.replaceAll(":","_")+'_id') // _id is added so it doesn't conflict with AdminLTE tab behavior
}
// get the tab id from the cookie (already overridden by the target)
if(!emptyArr.includes(getCache(key)))
{
selectedTab = getCache(key);
}
// Activate panel
$('.nav-tabs a[id='+ selectedTab +']').tab('show');
// When changed save new current tab
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
setCache(key, $(e.target).attr('id'))
});
}
// ---------------------------------------------------------------------------
function initDeviceNamesFromMACs()
{
$('.mac-to-name').each(function() {
var dataMacValue = $(this).attr('my-data-mac');
if(dataMacValue =="" )
{
$(this).html(getString("Network_Root"))
}
else{
$(this).html(getNameByMacAddress(dataMacValue));
}
});
}
// ---------------------------------------------------------------------------
function initButtons()
{
var currentNodeMac = $(".tab-content .active td[data-mynodemac]").attr('data-mynodemac');
// change highlighted node in the tree
selNode = $("#networkTree .highlightedNode")[0]
// console.log(selNode)
if(selNode)
{
$(selNode).attr('class', $(selNode).attr('class').replace('highlightedNode'))
}
newSelNode = $("#networkTree div[data-mytreemacmain='"+currentNodeMac+"']")[0]
$(newSelNode).attr('class', $(newSelNode).attr('class') + ' highlightedNode')
// init the Assign buttons
$('#unassignedDevices button[data-myleafmac]').each(function(){
$(this).attr('onclick', `updateLeaf("${$(this).attr('data-myleafmac')}","${currentNodeMac}")`)
});
// init Unassign buttons
$('#assignedDevices button[data-myleafmac]').each(function(){
$(this).attr('onclick', `updateLeaf("${$(this).attr('data-myleafmac')}","")`)
});
}
// ---------------------------------------------------------------------------
function updateLeaf(leafMac,nodeMac)
{
console.log(leafMac) // child
console.log(nodeMac) // parent
console.log(nodeMac != "") // parent
// prevent the assignment of the Internet root node avoiding recursion when generating the network tree topology
if(leafMac.toLowerCase().includes('internet') && nodeMac != "")
{
showMessage(getString('Network_Cant_Assign'))
}
else{
saveData('updateNetworkLeaf', leafMac, nodeMac);
setTimeout("location.reload();", 500); // refresh page
}
}
// init device names where macs are used
initDeviceNamesFromMACs();
// init selected (first) tab
initTab();
// init Assign/Unassign buttons
initButtons()
</script>

View File

@@ -0,0 +1,52 @@
<?php
//------------------------------------------------------------------------------
// Check if authenticated
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/server/db.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/language/lang.php';
// Function to render a filter dropdown
function renderFilterDropdown($headerKey, $columnName, $values) {
// Generate dropdown options
$optionsHtml = '<option value="" selected>All</option>'; // Default "All" option
foreach ($values as $value) {
$escapedValue = htmlspecialchars($value);
$optionsHtml .= '<option value="' . $escapedValue . '">' . $escapedValue . '</option>';
}
// Generate the dropdown HTML
return '
<div class="filter-group input-group">
<label for="filter_' . htmlspecialchars($columnName) . '">' . lang($headerKey) . '</label>
<select id="filter_' . htmlspecialchars($columnName) . '" class="filter-dropdown" data-column="' . htmlspecialchars($columnName) . '">
' . $optionsHtml . '
</select>
</div>';
}
// Get filterObject from POST data
$filterObject = isset($_POST['filterObject']) ? json_decode($_POST['filterObject'], true) : [];
// Validate filterObject structure
if (!isset($filterObject['filters']) || !is_array($filterObject['filters'])) {
echo '<p class="error">Invalid filter data provided.</p>';
exit();
}
// Generate HTML for each filter in the filterObject
$html = '';
foreach ($filterObject['filters'] as $filter) {
if (isset($filter['column'], $filter['headerKey'], $filter['options'])) {
$html .= renderFilterDropdown($filter['headerKey'], $filter['column'], $filter['options']);
} else {
// Skip invalid entries
continue;
}
}
// Output the generated HTML
echo $html;
exit();
?>

View File

@@ -7,8 +7,18 @@
?>
<script src="js/graph_online_history.js"></script>
<script>
$.get('api/table_online_history.json?nocache=' + Date.now(), function(res) {
<script >
if (isAppInitialized()) {
initOnlineHistoryGraph();
} else {
callAfterAppInitialized(() => initOnlineHistoryGraph());
}
function initOnlineHistoryGraph() {
$.get('/php/server/query_json.php', { file: 'table_online_history.json', nocache: Date.now() }, function(res) {
// Extracting data from the JSON response
var timeStamps = [];
var onlineCounts = [];
@@ -39,6 +49,8 @@ $.get('api/table_online_history.json?nocache=' + Date.now(), function(res) {
// Handle any errors in fetching the data
console.error('Error fetching online history data.');
});
}
</script>
<!-- <canvas id="clientsChart" width="800" height="140" class="extratooltipcanvas no-user-select"></canvas> -->
<canvas id="OnlineChart" style="width:100%; height: 150px; margin-bottom: 15px;"></canvas>

View File

@@ -20,12 +20,12 @@ function renderLogArea($params) {
$content = file_get_contents($filePath);
}
// Prepare the download button HTML if filePath starts with /app/front
// Prepare the download button HTML if filePath starts with /app
$downloadButtonHtml = '';
if (strpos($filePath, '/app/front') === 0) {
if (strpos($filePath, '/app') === 0) {
$downloadButtonHtml = '
<span class="span-padding">
<a href="' . htmlspecialchars(str_replace('/app/front', '', $filePath)) . '" target="_blank">
<a href="' . htmlspecialchars(str_replace('/app/log/', '/php/server/query_logs.php?file=', $filePath)) . '" target="_blank">
<i class="fa fa-download"></i>
</a>
</span>';

View File

@@ -11,7 +11,7 @@
}
],
"fileName": "app.log",
"filePath": "/app/front/log/app.log",
"filePath": "/app/log/app.log",
"textAreaCssClass": "logs"
},
@@ -23,7 +23,7 @@
}
],
"fileName": "app_front.log",
"filePath": "/app/front/log/app_front.log",
"filePath": "/app/log/app_front.log",
"textAreaCssClass": "logs logs-small"
},
{
@@ -34,7 +34,7 @@
}
],
"fileName": "app.php_errors.log",
"filePath": "/app/front/log/app.php_errors.log",
"filePath": "/app/log/app.php_errors.log",
"textAreaCssClass": "logs logs-small"
},
{
@@ -45,7 +45,7 @@
}
],
"fileName": "execution_queue.log",
"filePath": "/app/front/log/execution_queue.log",
"filePath": "/app/log/execution_queue.log",
"textAreaCssClass": "logs logs-small"
},
{
@@ -55,17 +55,6 @@
"filePath": "/var/log/nginx/error.log",
"textAreaCssClass": "logs logs-small"
},
{
"buttons": [
{
"labelStringCode": "Maint_PurgeLog",
"event": "logManage('app_front.log', 'cleanLog')"
}
],
"fileName": "app_front.log",
"filePath": "/app/front/log/app_front.log",
"textAreaCssClass": "logs logs-small"
},
{
"buttons": [
{
@@ -74,7 +63,7 @@
}
],
"fileName": "db_is_locked.log",
"filePath": "/app/front/log/db_is_locked.log",
"filePath": "/app/log/db_is_locked.log",
"textAreaCssClass": "logs logs-small"
},
{
@@ -85,7 +74,7 @@
}
],
"fileName": "stdout.log",
"filePath": "/app/front/log/stdout.log",
"filePath": "/app/log/stdout.log",
"textAreaCssClass": "logs logs-small"
},
{
@@ -96,7 +85,7 @@
}
],
"fileName": "stderr.log",
"filePath": "/app/front/log/stderr.log",
"filePath": "/app/log/stderr.log",
"textAreaCssClass": "logs logs-small"
}
]

View File

@@ -10,8 +10,12 @@
//------------------------------------------------------------------------------
// DB File Path
$DBFILE = dirname(__FILE__).'/../../../db/app.db';
$DBFILE_LOCKED_FILE = dirname(__FILE__).'/../../../front/log/db_is_locked.log';
// $DBFILE = dirname(__FILE__).'/../../../db/app.db';
// $DBFILE_LOCKED_FILE = dirname(__FILE__).'/../../../log/db_is_locked.log';
$scriptDir = realpath(dirname(__FILE__)); // Resolves symlinks to the actual physical path
$DBFILE = $scriptDir . '/../../../db/app.db';
$DBFILE_LOCKED_FILE = $scriptDir . '/../../../log/db_is_locked.log';
//------------------------------------------------------------------------------
// check if authenticated
@@ -32,6 +36,14 @@ function SQLite3_connect($trytoreconnect = true, $retryCount = 0) {
global $db_locked;
$db_locked = false;
if (!file_exists($DBFILE)) {
die("Database file not found: $DBFILE");
}
if (!file_exists(dirname($DBFILE_LOCKED_FILE))) {
die("Log directory not found: " . dirname($DBFILE_LOCKED_FILE));
}
// Write unlock status to the locked file
file_put_contents($DBFILE_LOCKED_FILE, '0');
@@ -70,7 +82,7 @@ class CustomDatabaseWrapper {
private $maxRetries;
private $retryDelay;
public function __construct($filename, $flags = SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, $maxRetries = 10, $retryDelay = 1000, $encryptionKey = null) {
public function __construct($filename, $flags = SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, $maxRetries = 3, $retryDelay = 1000, $encryptionKey = null) {
$this->sqlite = new SQLite3($filename, $flags, $encryptionKey);
$this->maxRetries = $maxRetries;
$this->retryDelay = $retryDelay;
@@ -116,7 +128,7 @@ class CustomDatabaseWrapper {
file_put_contents($DBFILE_LOCKED_FILE, '0');
$message = 'Error executing query (attempts: ' . $attempts . '), query: ' . $query;
write_notification($message);
// write_notification($message);
error_log("Query failed after {$this->maxRetries} attempts: " . $this->sqlite->lastErrorMsg());
}

Some files were not shown because too many files have changed in this diff Show More