Compare commits

..

132 Commits

Author SHA1 Message Date
jokob-sk
2b2ae516da weblate
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-09-09 07:47:11 +10:00
jokob-sk
2df7d143d3 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2025-09-09 07:46:50 +10:00
jokob-sk
1688d029b9 docs
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-09-09 07:38:15 +10:00
anton garcias
6d8f451be1 Translated using Weblate (Catalan)
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Currently translated at 100.0% (761 of 761 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ca/
2025-09-08 19:01:55 +02:00
jokob-sk
840e1e50a9 docs
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-09-08 21:17:55 +10:00
jokob-sk
164fe504a4 weblate
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-09-08 21:17:45 +10:00
jokob-sk
9040e49e16 sync plugin
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-09-08 08:14:42 +10:00
jokob-sk
629736ad39 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2025-09-08 08:12:04 +10:00
jokob-sk
ebc41ada45 logger
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-09-08 08:11:33 +10:00
jokob-sk
4fea786e16 sync plugin
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-09-08 08:11:23 +10:00
Jokob @NetAlertX
0edd20c82c Merge pull request #1155 from FlyingToto/main
adding example 5 of docker compose (3rd try!)
2025-09-08 07:05:10 +10:00
Jokob @NetAlertX
296dd0d0df Merge pull request #1165 from adamoutler/patch-1
Enhance in-app tooltips for clarity
2025-09-08 07:04:33 +10:00
Adam Outler
f2151cd9e8 Enhance in-app tooltips for clarity 2025-09-07 14:47:04 -04:00
Hosted Weblate
60876b14ce Merge branch 'origin/main' into Weblate.
Some checks failed
Deploy MkDocs / deploy (push) Has been cancelled
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
2025-09-04 23:58:20 +02:00
ssantos
9231ba742c Translated using Weblate (Portuguese (Portugal))
Currently translated at 54.5% (415 of 761 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/pt_PT/
2025-09-04 23:58:18 +02:00
jokob-sk
8a538102da weblate
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-09-05 07:57:57 +10:00
jokob-sk
31f901da35 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2025-09-05 07:57:25 +10:00
jokob-sk
c5b731fcb2 weblate
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-09-05 07:56:57 +10:00
jokob-sk
b2c7945513 docs
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-09-05 07:56:42 +10:00
suibian
6bf5c1f535 Translated using Weblate (Chinese (Simplified Han script))
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Currently translated at 100.0% (761 of 761 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/zh_Hans/
2025-09-03 12:38:56 +02:00
martinkuck
3da50fe83d Translated using Weblate (German)
Currently translated at 81.2% (618 of 761 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/de/
2025-09-03 12:38:56 +02:00
Jokob @NetAlertX
b46bdb9b60 Merge pull request #1156 from ingoratsdorf/contrib
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Adding secondary cache to settings
2025-09-03 07:02:11 +10:00
Ingo Ratsdorf
00c7bb65e1 Update server/helper.py
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-09-03 07:10:26 +12:00
Ingo Ratsdorf
9946f9affd Merge branch 'jokob-sk:main' into contrib 2025-09-02 20:43:25 +12:00
anton garcias
46a11b1cca Translated using Weblate (Catalan)
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Currently translated at 93.0% (708 of 761 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ca/
2025-09-02 10:42:15 +02:00
Ingo Ratsdorf
8a003ad805 Merge branch 'jokob-sk:main' into contrib 2025-09-02 20:41:59 +12:00
Jokob @NetAlertX
7dd860b2ab Merge branch 'main' into main 2025-09-02 15:22:04 +10:00
Jokob @NetAlertX
a9d7ca8809 Merge pull request #1154 from FlyingToto/patch-2
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
added a variant of example 2 as 5...
2025-09-02 15:16:02 +10:00
Ingo Ratsdorf
5695f4f3e7 Adding secondary cache to settings
Caching get_setting_value independent from what backend is used.
2025-09-02 14:48:12 +12:00
FlyingToto
1d74398337 adding address and uid/gid
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-09-01 22:13:02 -04:00
FlyingToto
e8f17346ff 3rd attempt to add example 5 of docker compose! 2025-09-01 22:02:25 -04:00
FlyingToto
bb1e00301c fixing typo
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-09-01 18:29:39 -04:00
FlyingToto
883786ec91 added a variant of example 2 as 5... 2025-09-01 17:39:58 -04:00
jokob-sk
3a023a675f CPU optimization work 5 #1144
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-09-01 09:13:13 +10:00
jokob-sk
8c895864da CPU optimizartion work 4 #1144
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-09-01 08:45:41 +10:00
jokob-sk
90474a6b92 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2025-09-01 08:33:38 +10:00
Jokob @NetAlertX
f7cf8a0b1d Merge pull request #1151 from ingoratsdorf/contrib
Added cache to get_settings
2025-09-01 08:33:29 +10:00
jokob-sk
98fdccb58f CPU optimizartion work 2 #1144
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-09-01 08:33:14 +10:00
jokob-sk
6f606f34d1 docs
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-08-31 10:23:31 +10:00
jokob-sk
fd3f1fc929 api layer v0.3.2 - /settings
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-08-31 09:54:56 +10:00
Ingo Ratsdorf
36ea3e62fd Added cache to get_settings
The settings file  is read about 30 times per second and parsed from json. Cache function added for now.
2025-08-30 21:35:15 +12:00
jokob-sk
7c9b37d827 lang
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-08-30 08:23:35 +10:00
jokob-sk
3fc0787b84 docs
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-08-30 08:23:25 +10:00
jokob-sk
5ba50f6d80 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
2025-08-29 08:09:39 +10:00
jokob-sk
c0c685c561 FE code disclaimers
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-08-29 08:09:29 +10:00
Artyom Rybakov
64a0fd0446 Translated using Weblate (Russian)
Currently translated at 100.0% (761 of 761 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ru/
2025-08-28 08:03:31 +02:00
jokob-sk
b1b67c268f api layer v0.3.1 - /dbquery
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Signed-off-by: jokob-sk <jokob-sk@gmail.com>
2025-08-28 08:12:23 +10:00
jokob-sk
ae12195439 localizeTimestamp 2 #1147
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Signed-off-by: jokob-sk <jokob-sk@gmail.com>
2025-08-27 07:34:43 +10:00
jokob-sk
3106b39566 CPU optimizartion work 2 #1144
Some checks failed
Deploy MkDocs / deploy (push) Has been cancelled
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
2025-08-26 08:26:55 +10:00
jokob-sk
d88aa9d6eb FE more defensive localizeTimestamp #1147 2025-08-26 07:33:11 +10:00
jokob-sk
9f9f2ff58c docs 2025-08-25 18:24:28 +10:00
jokob-sk
ce887968b7 docs 2025-08-25 18:19:02 +10:00
jokob-sk
40e9fbdb3f docs
Some checks failed
Deploy MkDocs / deploy (push) Has been cancelled
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
2025-08-24 13:12:33 +10:00
jokob-sk
3227cbbfa4 docs 2025-08-24 12:59:58 +10:00
jokob-sk
df9a17ed85 docs
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
2025-08-24 12:57:36 +10:00
jokob-sk
3ad7b59c84 docs 2025-08-24 10:05:41 +10:00
jokob-sk
b94da568a9 CPU optimizartion work #1144 2025-08-24 09:35:25 +10:00
jokob-sk
0146ae7c30 FE 2025-08-24 09:21:13 +10:00
jokob-sk
afbcf5985f SMTP_SUBJECT #1146 2025-08-24 08:45:32 +10:00
jokob-sk
af879ec84d graphql fix 2025-08-23 08:25:09 +10:00
jokob-sk
f78c84d9a8 api layer v0.3 - /events /sessions work
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
2025-08-21 22:36:22 +10:00
jokob-sk
2d11d3dd3e api layer v0.2.6 - /events work 2025-08-21 21:16:34 +10:00
jokob-sk
39c556576c FE - graphql response wrap into data 2025-08-21 15:51:58 +10:00
jokob-sk
73fd094cfc api layer v0.2.5 - graphql standardization 2025-08-21 15:33:32 +10:00
jokob-sk
cbf2cd0ee8 FE 2025-08-21 15:15:45 +10:00
jokob-sk
915bb523d6 api layer v0.2.5 - /sessions + graphql tests 2025-08-21 15:10:47 +10:00
jokob-sk
3dc87d2adb FE 2025-08-20 08:59:42 +10:00
jokob-sk
9155303674 api layer v0.2.4 - /nettools/speedtest endpoint 2025-08-20 08:58:34 +10:00
jokob-sk
0777824d96 FE 2025-08-20 08:50:35 +10:00
jokob-sk
b170ca3e18 api layer v0.2.4 - /nettools/traceroute endpoint 2025-08-20 08:49:34 +10:00
jokob-sk
5fd30fe3c8 FE
Some checks failed
Deploy MkDocs / deploy (push) Has been cancelled
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
2025-08-20 08:41:38 +10:00
jokob-sk
2fa181ffbc api layer v0.2.4 - /nettools endpoint 2025-08-20 08:40:14 +10:00
jokob-sk
a2bccdfb8e FE 2025-08-20 08:11:56 +10:00
jokob-sk
f3b159116f Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2025-08-20 08:11:05 +10:00
jokob-sk
03b9a9cf0d api layer v0.2.3 - /device(s) endpoints work 2025-08-20 08:10:55 +10:00
Jokob @NetAlertX
bf2fae6e1a Merge pull request #1140 from cvc90/Fix-Relative-URL-in-report.php
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Changing absolute path URL to relative path URL in report.php
2025-08-19 22:19:53 +10:00
Carlos V.
086fa54035 Update report.php
Change static route to relative route in URL for proper proxy operation
2025-08-19 13:07:58 +02:00
jokob-sk
962bbaa5a1 api layer v0.2.2 - CSV import/export, refactor
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
2025-08-19 07:56:54 +10:00
jokob-sk
9c71a8ecab api layer v0.2.1 - /events /history 2025-08-16 17:19:14 +10:00
jokob-sk
deff5a4ed0 api layer v0.2 - /devices
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
2025-08-16 16:43:15 +10:00
jokob-sk
e10c1c9c8d Added pt_pt language file
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
2025-08-16 08:18:23 +10:00
jokob-sk
b155fe2b06 api layer v0.1
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
2025-08-15 08:04:02 +10:00
jokob-sk
840bfe32d2 sync plugin endpoint refactor 2025-08-14 14:28:10 +10:00
jokob-sk
f33ef9861b css fixes, CurrentScan removed mac uniqueness check
Some checks failed
Deploy MkDocs / deploy (push) Has been cancelled
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
2025-08-13 08:22:30 +10:00
jokob-sk
cbe71cc203 UNIFIAPI v0.5, css fixes
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
2025-08-13 06:48:36 +10:00
jokob-sk
beaf8131ae UNIFIAPI v0.4
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
2025-08-11 21:39:51 +10:00
jokob-sk
99bfbb56de better check for available device #1132 2025-08-11 19:58:24 +10:00
jokob-sk
e73c8e830a better check for available device #1132 2025-08-11 19:52:16 +10:00
jokob-sk
1c4e6c7e38 UNIFIAPI v0.3 FE setings done 2025-08-11 15:00:22 +10:00
jokob-sk
1319c3380d UNIFIAPI v0.3
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
2025-08-10 21:24:17 +10:00
jokob-sk
dce8c34064 docs, rewrite docker image 2025-08-10 20:22:43 +10:00
jokob-sk
9502ee0cd0 UNIFIAPI v0.2, not ofund mac handling #1132 2025-08-10 20:08:09 +10:00
jokob-sk
8eb4ffe3ed logging
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
2025-08-08 07:34:23 +10:00
jokob-sk
4be59807e5 docs, UNIFIAPI v0.1 2025-08-07 16:41:40 +10:00
jokob-sk
4712a2ff29 css fixes, nav menu update, searchable devParentNodeMac
Some checks failed
Deploy MkDocs / deploy (push) Has been cancelled
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
2025-08-06 09:15:45 +10:00
jokob-sk
f9179a1e89 safe device name if number #1131 2025-08-06 07:20:04 +10:00
jokob-sk
a6df204721 github timeout #1124, css fixes, change button on LOADED_PLUGINS
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
2025-08-05 21:32:35 +10:00
jokob-sk
101189ae7c devParentNodeMac chips in devices list 2025-08-05 20:54:28 +10:00
jokob-sk
f25c012fbe external ip rework #1124
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
2025-08-05 14:42:00 +10:00
jokob-sk
868a85d84c Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2025-08-05 14:03:18 +10:00
jokob-sk
771dd4b176 docs 2025-08-05 14:02:48 +10:00
Hosted Weblate
ed4d3bf17c Merge branch 'origin/main' into Weblate. 2025-08-05 03:27:56 +00:00
Massimo Pissarello
7c728fbe36 Translated using Weblate (Italian)
Currently translated at 100.0% (761 of 761 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/it/
2025-08-05 03:27:56 +00:00
jokob-sk
4ff9d01ef5 heuristics docs 2025-08-05 13:27:30 +10:00
jokob-sk
1bce2e80e8 replace external IP check AJAX #1124
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
2025-08-05 08:15:49 +10:00
jokob-sk
1556d74406 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2025-08-05 08:06:48 +10:00
jokob-sk
9b3947cc90 device tools init loading #1130 2025-08-05 08:06:42 +10:00
Sylvain Pichon
18b0309ac4 Translated using Weblate (French)
Currently translated at 100.0% (761 of 761 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/
2025-08-04 19:02:09 +00:00
jokob-sk
0afd4ae115 prometheus metrics docs
Some checks failed
docker / docker_dev (push) Has been cancelled
Code checks / check-url-paths (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
2025-08-04 18:13:35 +10:00
jokob-sk
09e360c746 prometheus metrics endpoint 2025-08-04 15:12:51 +10:00
jokob-sk
5dbe79ba2f Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2025-08-04 13:25:23 +10:00
jokob-sk
779707761f heuristics refactor #1129 2025-08-04 13:25:17 +10:00
Hosted Weblate
16992bb2bd Merge branch 'origin/main' into Weblate.
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
2025-08-03 15:08:18 +02:00
Максим Горпиніч
3374f83255 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (761 of 761 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/uk/
2025-08-03 15:08:17 +02:00
jokob-sk
8f420a14cd fix 2025-08-03 23:06:43 +10:00
jokob-sk
57024c0cb1 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
2025-08-03 09:37:36 +10:00
jokob-sk
db7fb825fe Copy to clipboard IP 2025-08-03 09:37:18 +10:00
Hosted Weblate
49e8c6a4f2 Merge branch 'origin/main' into Weblate. 2025-08-02 22:40:40 +00:00
Максим Горпиніч
66bf4241b2 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (760 of 760 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/uk/
2025-08-02 22:40:39 +00:00
Massimo Pissarello
76a5dda553 Translated using Weblate (Italian)
Currently translated at 100.0% (760 of 760 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/it/
2025-08-02 22:40:38 +00:00
Sylvain Pichon
6393aa7f2c Translated using Weblate (French)
Currently translated at 100.0% (760 of 760 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/
2025-08-02 22:40:37 +00:00
Ettore Atalan
c5f938113f Translated using Weblate (German)
Currently translated at 81.3% (618 of 760 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/de/
2025-08-02 22:40:35 +00:00
jokob-sk
dac7eaba6d localized spinner support 2025-08-03 08:40:09 +10:00
jokob-sk
35e6059068 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2025-08-03 07:44:17 +10:00
jokob-sk
afebc8dc39 systeminfo refactor #1124 2025-08-03 07:44:11 +10:00
Hosted Weblate
34151a86b1 Merge branch 'origin/main' into Weblate.
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
2025-08-01 21:57:38 +00:00
Massimo Pissarello
72d6934345 Translated using Weblate (Italian)
Currently translated at 100.0% (759 of 759 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/it/
2025-08-01 21:57:37 +00:00
jokob-sk
f5f7031030 copy icon issue #1128 2025-08-02 07:56:57 +10:00
jokob-sk
ffccca9424 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
2025-08-01 08:15:38 +10:00
jokob-sk
3f5ae334a2 new device button #1126 2025-08-01 08:15:32 +10:00
Максим Горпиніч
bb45c4d345 Translated using Weblate (Ukrainian)
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Currently translated at 100.0% (759 of 759 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/uk/
2025-07-31 16:02:16 +02:00
Sylvain Pichon
bad3c76de9 Translated using Weblate (French)
Currently translated at 100.0% (759 of 759 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/
2025-07-31 16:02:14 +02:00
131 changed files with 12196 additions and 2393 deletions

2
.github/FUNDING.yml vendored
View File

@@ -1,3 +1,3 @@
github: jokob-sk
patreon: 84385063
patreon: netalertx
buy_me_a_coffee: jokobsk

81
.github/workflows/docker_rewrite.yml vendored Executable file
View File

@@ -0,0 +1,81 @@
name: docker
on:
push:
branches:
- rewrite
tags:
- '*.*.*'
pull_request:
branches:
- rewrite
jobs:
docker_rewrite:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
packages: write
if: >
contains(github.event.head_commit.message, 'PUSHPROD') != 'True' &&
github.repository == 'jokob-sk/NetAlertX'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set up dynamic build ARGs
id: getargs
run: echo "version=$(cat ./stable/VERSION)" >> $GITHUB_OUTPUT
- name: Get release version
id: get_version
run: echo "version=Dev" >> $GITHUB_OUTPUT
- name: Create .VERSION file
run: echo "${{ steps.get_version.outputs.version }}" >> .VERSION
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
ghcr.io/jokob-sk/netalertx-dev-rewrite
jokobsk/netalertx-dev-rewrite
tags: |
type=raw,value=latest
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- name: Log in to Github Container Registry (GHCR)
uses: docker/login-action@v3
with:
registry: ghcr.io
username: jokob-sk
password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v3
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View File

@@ -13,7 +13,7 @@ ENV PATH="/opt/venv/bin:$PATH"
COPY . ${INSTALL_DIR}/
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 yattag git+https://github.com/foreign-sub/aiofreepybox.git \
RUN pip install openwrt-luci-rpc asusrouter asyncio aiohttp graphene flask flask-cors unifi-sm-api tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros yattag git+https://github.com/foreign-sub/aiofreepybox.git \
&& bash -c "find ${INSTALL_DIR} -type d -exec chmod 750 {} \;" \
&& bash -c "find ${INSTALL_DIR} -type f -exec chmod 640 {} \;" \
&& bash -c "find ${INSTALL_DIR} -type f \( -name '*.sh' -o -name '*.py' -o -name 'speedtest-cli' \) -exec chmod 750 {} \;"

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 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 yattag "
RUN /bin/bash -c "source myenv/bin/activate && update-alternatives --install /usr/bin/python python /usr/bin/python3 10 && pip3 install openwrt-luci-rpc asusrouter asyncio aiohttp graphene flask flask-cors unifi-sm-api tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros yattag "
# Create a buildtimestamp.txt to later check if a new version was released
RUN date +%s > ${INSTALL_DIR}/front/buildtimestamp.txt

View File

@@ -24,7 +24,7 @@ LOADED_PLUGINS=['ARPSCAN', 'AVAHISCAN', 'CSVBCKP','DBCLNP', 'DIGSCAN', 'INTRNT',
DAYS_TO_KEEP_EVENTS=90
# Used for generating links in emails. Make sure not to add a trailing slash!
REPORT_DASHBOARD_URL='http://127.0.0.1'
REPORT_DASHBOARD_URL='update_REPORT_DASHBOARD_URL_setting'
# Make sure at least these scanners are enabled for new installs, other defaults are taken from the config.json
INTRNT_RUN='schedule'

200
back/device_heuristics_rules.json Executable file
View File

@@ -0,0 +1,200 @@
[
{
"dev_type": "Gateway",
"icon_html": "<i class=\"fa fa-globe\"></i>",
"matching_pattern": [
{ "mac_prefix": "INTERNET", "vendor": "" }
],
"name_pattern": []
},
{
"dev_type": "Access Point",
"icon_html": "<i class=\"fa fa-network-wired\"></i>",
"matching_pattern": [
{ "mac_prefix": "74ACB9", "vendor": "Ubiquiti" },
{ "mac_prefix": "002468", "vendor": "Cisco" },
{ "mac_prefix": "F4F5D8", "vendor": "TP-Link" },
{ "mac_prefix": "F88E85", "vendor": "Netgear" }
],
"name_pattern": ["router", "gateway", "ap", "access point", "access-point", "switch"]
},
{
"dev_type": "Phone",
"icon_html": "<i class=\"fa-brands fa-apple\"></i>",
"matching_pattern": [
{ "mac_prefix": "001A79", "vendor": "Apple" },
{ "mac_prefix": "B0BE83", "vendor": "Samsung" },
{ "mac_prefix": "BC926B", "vendor": "Motorola" }
],
"name_pattern": ["iphone", "ipad", "pixel", "galaxy", "redmi"]
},
{
"dev_type": "Phone",
"icon_html": "<i class=\"fa-solid fa-mobile\"></i>",
"matching_pattern": [
],
"name_pattern": ["android","samsung"]
},
{
"dev_type": "Tablet",
"icon_html": "<i class=\"fa fa-tablet\"></i>",
"matching_pattern": [
{ "mac_prefix": "001B63", "vendor": "Apple" },
{ "mac_prefix": "BC4C4C", "vendor": "Samsung" }
],
"name_pattern": ["tablet", "pad"]
},
{
"dev_type": "IoT",
"icon_html": "<i class=\"fa-brands fa-raspberry-pi\"></i>",
"matching_pattern": [
{ "mac_prefix": "B827EB", "vendor": "Raspberry Pi" },
{ "mac_prefix": "DCA632", "vendor": "Raspberry Pi" }
],
"name_pattern": ["raspberry", "pi"]
},
{
"dev_type": "IoT",
"icon_html": "<i class=\"fa-solid fa-microchip\"></i>",
"matching_pattern": [
{ "mac_prefix": "840D8E", "vendor": "Espressif" },
{ "mac_prefix": "ECFABC", "vendor": "Espressif" },
{ "mac_prefix": "7C9EBD", "vendor": "Espressif" }
],
"name_pattern": ["raspberry", "pi"]
},
{
"dev_type": "Desktop",
"icon_html": "<i class=\"fa fa-desktop\"></i>",
"matching_pattern": [
{ "mac_prefix": "001422", "vendor": "Dell" },
{ "mac_prefix": "001874", "vendor": "Lenovo" },
{ "mac_prefix": "00E04C", "vendor": "Hewlett Packard" }
],
"name_pattern": ["desktop", "pc", "computer"]
},
{
"dev_type": "Laptop",
"icon_html": "<i class=\"fa fa-laptop\"></i>",
"matching_pattern": [
{ "mac_prefix": "3C0754", "vendor": "HP" },
{ "mac_prefix": "0017A4", "vendor": "Dell" },
{ "mac_prefix": "F4CE46", "vendor": "Lenovo" },
{ "mac_prefix": "409F38", "vendor": "Acer" }
],
"name_pattern": ["macbook", "imac", "laptop", "notebook"]
},
{
"dev_type": "Server",
"icon_html": "<i class=\"fa fa-server\"></i>",
"matching_pattern": [
{ "mac_prefix": "001CBF", "vendor": "Supermicro" },
{ "mac_prefix": "002186", "vendor": "Dell" },
{ "mac_prefix": "D02788", "vendor": "Hewlett Packard" },
{ "mac_prefix": "002590", "vendor": "IBM" }
],
"name_pattern": ["server", "nas"]
},
{
"dev_type": "VM",
"icon_html": "<i class=\"fa fa-server\"></i>",
"matching_pattern": [
{ "mac_prefix": "525400", "vendor": "QEMU" },
{ "mac_prefix": "005056", "vendor": "VMware" },
{ "mac_prefix": "000C29", "vendor": "VMware" },
{ "mac_prefix": "000569", "vendor": "VMware" },
{ "mac_prefix": "00163E", "vendor": "Xen" },
{ "mac_prefix": "080027", "vendor": "VirtualBox" }
]
},
{
"dev_type": "TV",
"icon_html": "<i class=\"fa fa-tv\"></i>",
"matching_pattern": [
{ "mac_prefix": "0013CE", "vendor": "Samsung" },
{ "mac_prefix": "0017C8", "vendor": "LG" },
{ "mac_prefix": "D46E0E", "vendor": "Sony" }
],
"name_pattern": ["tv", "television", "smarttv"]
},
{
"dev_type": "Gaming Console",
"icon_html": "<i class=\"fa fa-gamepad\"></i>",
"matching_pattern": [
{ "mac_prefix": "001FA7", "vendor": "Sony" },
{ "mac_prefix": "7C04D0", "vendor": "Nintendo" },
{ "mac_prefix": "EC26CA", "vendor": "Sony" }
],
"name_pattern": ["playstation", "xbox"]
},
{
"dev_type": "Camera",
"icon_html": "<i class=\"fa fa-camera\"></i>",
"matching_pattern": [
{ "mac_prefix": "A45E60", "vendor": "Hikvision" },
{ "mac_prefix": "00408C", "vendor": "Axis" },
{ "mac_prefix": "00156D", "vendor": "Amcrest" },
{ "mac_prefix": "AC9E17", "vendor": "Reolink" }
],
"name_pattern": ["camera", "cam", "webcam"]
},
{
"dev_type": "Smart Speaker",
"icon_html": "<i class=\"fa fa-volume-up\"></i>",
"matching_pattern": [
{ "mac_prefix": "44650D", "vendor": "Amazon" },
{ "mac_prefix": "74ACB9", "vendor": "Google" }
],
"name_pattern": ["echo", "alexa", "dot"]
},
{
"dev_type": "Router",
"icon_html": "<i class=\"fa fa-random\"></i>",
"matching_pattern": [
{ "mac_prefix": "000C29", "vendor": "Cisco" },
{ "mac_prefix": "00155D", "vendor": "MikroTik" }
],
"name_pattern": ["router", "gateway", "ap", "access point", "access-point"],
"ip_pattern": [
"^192\\.168\\.[0-1]\\.1$",
"^10\\.0\\.0\\.1$"
]
},
{
"dev_type": "Smart Light",
"icon_html": "<i class=\"fa fa-lightbulb\"></i>",
"matching_pattern": [],
"name_pattern": ["hue", "lifx", "bulb"]
},
{
"dev_type": "Smart Home",
"icon_html": "<i class=\"fa fa-house\"></i>",
"matching_pattern": [],
"name_pattern": ["google", "chromecast", "nest"]
},
{
"dev_type": "Smartwatch",
"icon_html": "<i class=\"fa fa-watch\"></i>",
"matching_pattern": [],
"name_pattern": ["watch", "wear"]
},
{
"dev_type": "Printer",
"icon_html": "<i class=\"fa fa-print\"></i>",
"matching_pattern": [],
"name_pattern": ["printer", "print"]
},
{
"dev_type": "Security Device",
"icon_html": "<i class=\"fa fa-shield-alt\"></i>",
"matching_pattern": [],
"name_pattern": ["doorbell", "lock", "security"]
},
{
"dev_type": "Smart Light",
"icon_html": "<i class=\"fa-solid fa-lightbulb\"></i>",
"matching_pattern": [
],
"name_pattern": ["light","bulb"]
}
]

View File

@@ -59,6 +59,9 @@ 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/systeminfoNetwork.php:/app/front/systeminfoNetwork.php
- ${DEV_LOCATION}/front/systeminfoServer.php:/app/front/systeminfoServer.php
- ${DEV_LOCATION}/front/systeminfoStorage.php:/app/front/systeminfoStorage.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

View File

@@ -1,6 +1,6 @@
# API endpoints
# NetAlertX API Documentation
NetAlertX comes with a couple of API endpoints. All requests need to be authorized (executed in a logged in browser session) or you have to pass the value of the `API_TOKEN` settings as authorization bearer, for example:
This API provides programmatic access to **devices, events, sessions, metrics, network tools, and sync** in NetAlertX. It is implemented as a **REST and GraphQL server**. All requests require authentication via **API Token** (`API_TOKEN` setting) unless explicitly noted. For example, to authorize a GraphQL request, you need to use a `Authorization: Bearer API_TOKEN` header as per example below:
```graphql
curl 'http://host:GRAPHQL_PORT/graphql' \
@@ -21,241 +21,59 @@ curl 'http://host:GRAPHQL_PORT/graphql' \
}'
```
## API Endpoint: GraphQL
The API server runs on `0.0.0.0:<graphql_port>` with **CORS enabled** for all main endpoints.
- Endpoint URL: `php/server/query_graphql.php`
- Host: `same as front end (web ui)`
- Port: `20212` or as defined by the `GRAPHQL_PORT` setting
---
### Example Query to Fetch Devices
## Authentication
First, let's define the GraphQL query to fetch devices with pagination and sorting options.
All endpoints require an API token provided in the HTTP headers:
```graphql
query GetDevices($options: PageQueryOptionsInput) {
devices(options: $options) {
devices {
rowid
devMac
devName
devOwner
devType
devVendor
devLastConnection
devStatus
}
count
}
}
```http
Authorization: Bearer <API_TOKEN>
```
See also: [Debugging GraphQL issues](./DEBUG_GRAPHQL.md)
### `curl` Command
You can use the following `curl` command to execute the query.
```sh
curl 'http://host:GRAPHQL_PORT/graphql' -X POST -H 'Authorization: Bearer API_TOKEN' -H 'Content-Type: application/json' --data '{
"query": "query GetDevices($options: PageQueryOptionsInput) { devices(options: $options) { devices { rowid devMac devName devOwner devType devVendor devLastConnection devStatus } count } }",
"variables": {
"options": {
"page": 1,
"limit": 10,
"sort": [{ "field": "devName", "order": "asc" }],
"search": "",
"status": "connected"
}
}
}'
```
### Explanation:
1. **GraphQL Query**:
- The `query` parameter contains the GraphQL query as a string.
- The `variables` parameter contains the input variables for the query.
2. **Query Variables**:
- `page`: Specifies the page number of results to fetch.
- `limit`: Specifies the number of results per page.
- `sort`: Specifies the sorting options, with `field` being the field to sort by and `order` being the sort order (`asc` for ascending or `desc` for descending).
- `search`: A search term to filter the devices.
- `status`: The status filter to apply (valid values are `my_devices` (determined by the `UI_MY_DEVICES` setting), `connected`, `favorites`, `new`, `down`, `archived`, `offline`).
3. **`curl` Command**:
- The `-X POST` option specifies that we are making a POST request.
- The `-H "Content-Type: application/json"` option sets the content type of the request to JSON.
- The `-d` option provides the request payload, which includes the GraphQL query and variables.
### Sample Response
The response will be in JSON format, similar to the following:
If the token is missing or invalid, the server will return:
```json
{
"data": {
"devices": {
"devices": [
{
"rowid": 1,
"devMac": "00:11:22:33:44:55",
"devName": "Device 1",
"devOwner": "Owner 1",
"devType": "Type 1",
"devVendor": "Vendor 1",
"devLastConnection": "2025-01-01T00:00:00Z",
"devStatus": "connected"
},
{
"rowid": 2,
"devMac": "66:77:88:99:AA:BB",
"devName": "Device 2",
"devOwner": "Owner 2",
"devType": "Type 2",
"devVendor": "Vendor 2",
"devLastConnection": "2025-01-02T00:00:00Z",
"devStatus": "connected"
}
],
"count": 2
}
}
}
{ "error": "Forbidden" }
```
## API Endpoint: JSON files
---
This API endpoint retrieves static files, that are periodically updated.
- Endpoint URL: `php/server/query_json.php?file=<file name>`
- Host: `same as front end (web ui)`
- Port: `20211` or as defined by the $PORT docker environment variable (same as the port for the web ui)
### When are the endpoints updated
The endpoints are updated when objects in the API endpoints are changed.
### Location of the endpoints
In the container, these files are located under the `/app/api/` folder. You can access them via the `/php/server/query_json.php?file=user_notifications.json` endpoint.
### Available endpoints
You can access the following files:
| File name | Description |
|----------------------|----------------------|
| `notification_json_final.json` | The json version of the last notification (e.g. used for webhooks - [sample JSON](https://github.com/jokob-sk/NetAlertX/blob/main/front/report_templates/webhook_json_sample.json)). |
| `table_devices.json` | All of the available Devices detected by the app. |
| `table_plugins_events.json` | The list of the unprocessed (pending) notification events (plugins_events DB table). |
| `table_plugins_history.json` | The list of notification events history. |
| `table_plugins_objects.json` | The content of the plugins_objects table. Find more info on the [Plugin system here](https://github.com/jokob-sk/NetAlertX/tree/main/docs/PLUGINS.md)|
| `language_strings.json` | The content of the language_strings table, which in turn is loaded from the plugins `config.json` definitions. |
| `table_custom_endpoint.json` | A custom endpoint generated by the SQL query specified by the `API_CUSTOM_SQL` setting. |
| `table_settings.json` | The content of the settings table. |
| `app_state.json` | Contains the current application state. |
### JSON Data format
The endpoints starting with the `table_` prefix contain most, if not all, data contained in the corresponding database table. The common format for those is:
```JSON
{
"data": [
{
"db_column_name": "data",
"db_column_name2": "data2"
},
{
"db_column_name": "data3",
"db_column_name2": "data4"
}
]
}
## Base URL
```
Example JSON of the `table_devices.json` endpoint with two Devices (database rows):
```JSON
{
"data": [
{
"devMac": "Internet",
"devName": "Net - Huawei",
"devType": "Router",
"devVendor": null,
"devGroup": "Always on",
"devFirstConnection": "2021-01-01 00:00:00",
"devLastConnection": "2021-01-28 22:22:11",
"devLastIP": "192.168.1.24",
"devStaticIP": 0,
"devPresentLastScan": 1,
"devLastNotification": "2023-01-28 22:22:28.998715",
"devIsNew": 0,
"devParentMAC": "",
"devParentPort": "",
"devIcon": "globe"
},
{
"devMac": "a4:8f:ff:aa:ba:1f",
"devName": "Net - USG",
"devType": "Firewall",
"devVendor": "Ubiquiti Inc",
"devGroup": "",
"devFirstConnection": "2021-02-12 22:05:00",
"devLastConnection": "2021-07-17 15:40:00",
"devLastIP": "192.168.1.1",
"devStaticIP": 1,
"devPresentLastScan": 1,
"devLastNotification": "2021-07-17 15:40:10.667717",
"devIsNew": 0,
"devParentMAC": "Internet",
"devParentPort": 1,
"devIcon": "shield-halved"
}
]
}
http://<server>:<GRAPHQL_PORT>/
```
## API Endpoint: /log files
---
This API endpoint retrieves files from the `/app/log` folder.
## Endpoints
- 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)
> [!TIP]
> When retrieving devices or settings try using the GraphQL API endpoint first as it is read-optimized.
| 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 |
* [Device API Endpoints](API_DEVICE.md) Manage individual devices
* [Devices Collection](API_DEVICES.md) Bulk operations on multiple devices
* [Events](API_EVENTS.md) Device event logging and management
* [Sessions](API_SESSIONS.md) Connection sessions and history
* [Settings](API_SETTINGS.md) Settings
* [Metrics](API_METRICS.md) Prometheus metrics and per-device status
* [Network Tools](API_NETTOOLS.md) Utilities like Wake-on-LAN, traceroute, nslookup, nmap, and internet info
* [Online History](API_ONLINEHISTORY.md) Online/offline device records
* [GraphQL](API_GRAPHQL.md) Advanced queries and filtering
* [Sync](API_SYNC.md) Synchronization between multiple NetAlertX instances
* [DB query](API_DBQUERY.md) (⚠ Internal) - Low level database access - use other endpoints if possible
See [Testing](API_TESTS.md) for example requests and usage.
## API Endpoint: /config files
---
To retrieve files from the `/app/config` folder.
- Endpoint URL: `php/server/query_config.php?file=<file name>`
- Host: `same as front end (web ui)`
- Port: `20211` or as defined by the $PORT docker environment variable (same as the port for the web ui)
| File | Description |
|--------------------------|--------------------------------------------------|
| `devices.csv` | Devices csv file |
| `app.conf` | Application config file |
## Notes
* All endpoints enforce **Bearer token authentication**.
* Errors return JSON with `success: False` and an error message.
* GraphQL is available for advanced queries, while REST endpoints cover structured use cases.
* Endpoints run on `0.0.0.0:<GRAPHQL_PORT>` with **CORS enabled**.
* Use consistent API tokens and node/plugin names when interacting with `/sync` to ensure data integrity.

183
docs/API_DBQUERY.md Executable file
View File

@@ -0,0 +1,183 @@
# Database Query API
The **Database Query API** provides direct, low-level access to the NetAlertX database. It allows **read, write, update, and delete** operations against tables, using **base64-encoded** SQL or structured parameters.
> [!Warning]
> This API is primarily used internally to generate and render the application UI. These endpoints are low-level and powerful, and should be used with caution. Wherever possible, prefer the [standard API endpoints](API.md). Invalid or unsafe queries can corrupt data.
> If you need data in a specific format that is not already provided, please open an issue or pull request with a clear, broadly useful use case. This helps ensure new endpoints benefit the wider community rather than relying on raw database queries.
---
## Authentication
All `/dbquery/*` endpoints require an API token in the HTTP headers:
```http
Authorization: Bearer <API_TOKEN>
```
If the token is missing or invalid:
```json
{ "error": "Forbidden" }
```
---
## Endpoints
### 1. `POST /dbquery/read`
Execute a **read-only** SQL query (e.g., `SELECT`).
#### Request Body
```json
{
"rawSql": "U0VMRUNUICogRlJPTSBERVZJQ0VT" // base64 encoded SQL
}
```
Decoded SQL:
```sql
SELECT * FROM Devices;
```
#### Response
```json
{
"success": true,
"results": [
{ "devMac": "AA:BB:CC:DD:EE:FF", "devName": "Phone" }
]
}
```
#### `curl` Example
```bash
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/dbquery/read" \
-H "Authorization: Bearer <API_TOKEN>" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"rawSql": "U0VMRUNUICogRlJPTSBERVZJQ0VT"
}'
```
---
### 2. `POST /dbquery/update` (safer than `/dbquery/write`)
Update rows in a table by `columnName` + `id`. `/dbquery/update` is parameterized to reduce the risk of SQL injection, while `/dbquery/write` executes raw SQL directly.
#### Request Body
```json
{
"columnName": "devMac",
"id": ["AA:BB:CC:DD:EE:FF"],
"dbtable": "Devices",
"columns": ["devName", "devOwner"],
"values": ["Laptop", "Alice"]
}
```
#### Response
```json
{ "success": true, "updated_count": 1 }
```
#### `curl` Example
```bash
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/dbquery/update" \
-H "Authorization: Bearer <API_TOKEN>" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"columnName": "devMac",
"id": ["AA:BB:CC:DD:EE:FF"],
"dbtable": "Devices",
"columns": ["devName", "devOwner"],
"values": ["Laptop", "Alice"]
}'
```
---
### 3. `POST /dbquery/write`
Execute a **write query** (`INSERT`, `UPDATE`, `DELETE`).
#### Request Body
```json
{
"rawSql": "SU5TRVJUIElOVE8gRGV2aWNlcyAoZGV2TWFjLCBkZXYgTmFtZSwgZGV2Rmlyc3RDb25uZWN0aW9uLCBkZXZMYXN0Q29ubmVjdGlvbiwgZGV2TGFzdElQKSBWQUxVRVMgKCc2QTpCQjo0Qzo1RDo2RTonLCAnVGVzdERldmljZScsICcyMDI1LTA4LTMwIDEyOjAwOjAwJywgJzIwMjUtMDgtMzAgMTI6MDA6MDAnLCAnMTAuMC4wLjEwJyk="
}
```
Decoded SQL:
```sql
INSERT INTO Devices (devMac, devName, devFirstConnection, devLastConnection, devLastIP)
VALUES ('6A:BB:4C:5D:6E', 'TestDevice', '2025-08-30 12:00:00', '2025-08-30 12:00:00', '10.0.0.10');
```
#### Response
```json
{ "success": true, "affected_rows": 1 }
```
#### `curl` Example
```bash
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/dbquery/write" \
-H "Authorization: Bearer <API_TOKEN>" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"rawSql": "SU5TRVJUIElOVE8gRGV2aWNlcyAoZGV2TWFjLCBkZXYgTmFtZSwgZGV2Rmlyc3RDb25uZWN0aW9uLCBkZXZMYXN0Q29ubmVjdGlvbiwgZGV2TGFzdElQKSBWQUxVRVMgKCc2QTpCQjo0Qzo1RDo2RTonLCAnVGVzdERldmljZScsICcyMDI1LTA4LTMwIDEyOjAwOjAwJywgJzIwMjUtMDgtMzAgMTI6MDA6MDAnLCAnMTAuMC4wLjEwJyk="
}'
```
---
### 4. `POST /dbquery/delete`
Delete rows in a table by `columnName` + `id`.
#### Request Body
```json
{
"columnName": "devMac",
"id": ["AA:BB:CC:DD:EE:FF"],
"dbtable": "Devices"
}
```
#### Response
```json
{ "success": true, "deleted_count": 1 }
```
#### `curl` Example
```bash
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/dbquery/delete" \
-H "Authorization: Bearer <API_TOKEN>" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"columnName": "devMac",
"id": ["AA:BB:CC:DD:EE:FF"],
"dbtable": "Devices"
}'
```

233
docs/API_DEVICE.md Executable file
View File

@@ -0,0 +1,233 @@
# Device API Endpoints
Manage a **single device** by its MAC address. Operations include retrieval, updates, deletion, resetting properties, and copying data between devices. All endpoints require **authorization** via Bearer token.
---
## 1. Retrieve Device Details
* **GET** `/device/<mac>`
Fetch all details for a single device, including:
* Computed status (`devStatus`) → `On-line`, `Off-line`, or `Down`
* Session and event counts (`devSessions`, `devEvents`, `devDownAlerts`)
* Presence hours (`devPresenceHours`)
* Children devices (`devChildrenDynamic`) and NIC children (`devChildrenNicsDynamic`)
**Special case**: `mac=new` returns a template for a new device with default values.
**Response** (success):
```json
{
"devMac": "AA:BB:CC:DD:EE:FF",
"devName": "Net - Huawei",
"devOwner": "Admin",
"devType": "Router",
"devVendor": "Huawei",
"devStatus": "On-line",
"devSessions": 12,
"devEvents": 5,
"devDownAlerts": 1,
"devPresenceHours": 32,
"devChildrenDynamic": [...],
"devChildrenNicsDynamic": [...],
...
}
```
**Error Responses**:
* Device not found → HTTP 404
* Unauthorized → HTTP 403
---
## 2. Update Device Fields
* **POST** `/device/<mac>`
Create or update a device record.
**Request Body**:
```json
{
"devName": "New Device",
"devOwner": "Admin",
"createNew": true
}
```
**Behavior**:
* If `createNew=true` → creates a new device
* Otherwise → updates existing device fields
**Response**:
```json
{
"success": true
}
```
**Error Responses**:
* Unauthorized → HTTP 403
---
## 3. Delete a Device
* **DELETE** `/device/<mac>/delete`
Deletes the device with the given MAC.
**Response**:
```json
{
"success": true
}
```
**Error Responses**:
* Unauthorized → HTTP 403
---
## 4. Delete All Events for a Device
* **DELETE** `/device/<mac>/events/delete`
Removes all events associated with a device.
**Response**:
```json
{
"success": true
}
```
---
## 5. Reset Device Properties
* **POST** `/device/<mac>/reset-props`
Resets the device's custom properties to default values.
**Request Body**: Optional JSON for additional parameters.
**Response**:
```json
{
"success": true
}
```
---
## 6. Copy Device Data
* **POST** `/device/copy`
Copy all data from one device to another. If a device exists with `macTo`, it is replaced.
**Request Body**:
```json
{
"macFrom": "AA:BB:CC:DD:EE:FF",
"macTo": "11:22:33:44:55:66"
}
```
**Response**:
```json
{
"success": true,
"message": "Device copied from AA:BB:CC:DD:EE:FF to 11:22:33:44:55:66"
}
```
**Error Responses**:
* Missing `macFrom` or `macTo` → HTTP 400
* Unauthorized → HTTP 403
---
## 7. Update a Single Column
* **POST** `/device/<mac>/update-column`
Update one specific column for a device.
**Request Body**:
```json
{
"columnName": "devName",
"columnValue": "Updated Device Name"
}
```
**Response** (success):
```json
{
"success": true
}
```
**Error Responses**:
* Device not found → HTTP 404
* Missing `columnName` or `columnValue` → HTTP 400
* Unauthorized → HTTP 403
---
## Example `curl` Requests
**Get Device Details**:
```bash
curl -X GET "http://<server_ip>:<GRAPHQL_PORT>/device/AA:BB:CC:DD:EE:FF" \
-H "Authorization: Bearer <API_TOKEN>"
```
**Update Device Fields**:
```bash
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/device/AA:BB:CC:DD:EE:FF" \
-H "Authorization: Bearer <API_TOKEN>" \
-H "Content-Type: application/json" \
--data '{"devName": "New Device Name"}'
```
**Delete Device**:
```bash
curl -X DELETE "http://<server_ip>:<GRAPHQL_PORT>/device/AA:BB:CC:DD:EE:FF/delete" \
-H "Authorization: Bearer <API_TOKEN>"
```
**Copy Device Data**:
```bash
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/device/copy" \
-H "Authorization: Bearer <API_TOKEN>" \
-H "Content-Type: application/json" \
--data '{"macFrom":"AA:BB:CC:DD:EE:FF","macTo":"11:22:33:44:55:66"}'
```
**Update Single Column**:
```bash
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/device/AA:BB:CC:DD:EE:FF/update-column" \
-H "Authorization: Bearer <API_TOKEN>" \
-H "Content-Type: application/json" \
--data '{"columnName":"devName","columnValue":"Updated Device"}'
```

249
docs/API_DEVICES.md Executable file
View File

@@ -0,0 +1,249 @@
# Devices Collection API Endpoints
The Devices Collection API provides operations to **retrieve, manage, import/export, and filter devices** in bulk. All endpoints require **authorization** via Bearer token.
---
## Endpoints
### 1. Get All Devices
* **GET** `/devices`
Retrieves all devices from the database.
**Response** (success):
```json
{
"success": true,
"devices": [
{
"devName": "Net - Huawei",
"devMAC": "AA:BB:CC:DD:EE:FF",
"devIP": "192.168.1.1",
"devType": "Router",
"devFavorite": 0,
"devStatus": "online"
},
...
]
}
```
**Error Responses**:
* Unauthorized → HTTP 403
---
### 2. Delete Devices by MAC
* **DELETE** `/devices`
Deletes devices by MAC address. Supports exact matches or wildcard `*`.
**Request Body**:
```json
{
"macs": ["AA:BB:CC:DD:EE:FF", "11:22:33:*"]
}
```
**Behavior**:
* If `macs` is omitted or `null` → deletes **all devices**.
* Wildcards `*` match multiple devices.
**Response**:
```json
{
"success": true,
"deleted_count": 5
}
```
**Error Responses**:
* Unauthorized → HTTP 403
---
### 3. Delete Devices with Empty MACs
* **DELETE** `/devices/empty-macs`
Removes all devices where MAC address is null or empty.
**Response**:
```json
{
"success": true,
"deleted": 3
}
```
---
### 4. Delete Unknown Devices
* **DELETE** `/devices/unknown`
Deletes devices with names marked as `(unknown)` or `(name not found)`.
**Response**:
```json
{
"success": true,
"deleted": 2
}
```
---
### 5. Export Devices
* **GET** `/devices/export` or `/devices/export/<format>`
Exports all devices in **CSV** (default) or **JSON** format.
**Query Parameter / URL Parameter**:
* `format` (optional) → `csv` (default) or `json`
**CSV Response**:
* Returns as a downloadable CSV file: `Content-Disposition: attachment; filename=devices.csv`
**JSON Response**:
```json
{
"data": [
{ "devName": "Net - Huawei", "devMAC": "AA:BB:CC:DD:EE:FF", ... },
...
],
"columns": ["devName", "devMAC", "devIP", "devType", "devFavorite", "devStatus"]
}
```
**Error Responses**:
* Unsupported format → HTTP 400
---
### 6. Import Devices from CSV
* **POST** `/devices/import`
Imports devices from an uploaded CSV or base64-encoded CSV content.
**Request Body** (multipart file or JSON with `content` field):
```json
{
"content": "<base64-encoded CSV content>"
}
```
**Response**:
```json
{
"success": true,
"inserted": 25,
"skipped_lines": [3, 7]
}
```
**Error Responses**:
* Missing file or content → HTTP 400 / 404
* CSV malformed → HTTP 400
---
### 7. Get Device Totals
* **GET** `/devices/totals`
Returns counts of devices by various categories.
**Response**:
```json
[
120, // Total devices
85, // Connected
5, // Favorites
10, // New
8, // Down
12 // Archived
]
```
*Order: `[all, connected, favorites, new, down, archived]`*
---
### 8. Get Devices by Status
* **GET** `/devices/by-status?status=<status>`
Returns devices filtered by status.
**Query Parameter**:
* `status` → Supported values: `online`, `offline`, `down`, `archived`, `favorites`, `new`, `my`
* If omitted, returns **all devices**.
**Response** (success):
```json
[
{ "id": "AA:BB:CC:DD:EE:FF", "title": "Net - Huawei", "favorite": 0 },
{ "id": "11:22:33:44:55:66", "title": "★ USG Firewall", "favorite": 1 }
]
```
*If `devFavorite=1`, the title is prepended with a star `★`.*
---
## Example `curl` Requests
**Get All Devices**:
```sh
curl -X GET "http://<server_ip>:<GRAPHQL_PORT>/devices" \
-H "Authorization: Bearer <API_TOKEN>"
```
**Delete Devices by MAC**:
```sh
curl -X DELETE "http://<server_ip>:<GRAPHQL_PORT>/devices" \
-H "Authorization: Bearer <API_TOKEN>" \
-H "Content-Type: application/json" \
--data '{"macs":["AA:BB:CC:DD:EE:FF","11:22:33:*"]}'
```
**Export Devices CSV**:
```sh
curl -X GET "http://<server_ip>:<GRAPHQL_PORT>/devices/export?format=csv" \
-H "Authorization: Bearer <API_TOKEN>"
```
**Import Devices from CSV**:
```sh
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/devices/import" \
-H "Authorization: Bearer <API_TOKEN>" \
-F "file=@devices.csv"
```
**Get Devices by Status**:
```sh
curl -X GET "http://<server_ip>:<GRAPHQL_PORT>/devices/by-status?status=online" \
-H "Authorization: Bearer <API_TOKEN>"
```

169
docs/API_EVENTS.md Executable file
View File

@@ -0,0 +1,169 @@
# Events API Endpoints
The Events API provides access to **device event logs**, allowing creation, retrieval, deletion, and summary of events over time.
---
## Endpoints
### 1. Create Event
* **POST** `/events/create/<mac>`
Create an event for a device identified by its MAC address.
**Request Body** (JSON):
```json
{
"ip": "192.168.1.10",
"event_type": "Device Down",
"additional_info": "Optional info about the event",
"pending_alert": 1,
"event_time": "2025-08-24T12:00:00Z"
}
```
* **Parameters**:
* `ip` (string, optional): IP address of the device
* `event_type` (string, optional): Type of event (default `"Device Down"`)
* `additional_info` (string, optional): Extra information
* `pending_alert` (int, optional): 1 if alert email is pending (default 1)
* `event_time` (ISO datetime, optional): Event timestamp; defaults to current time
**Response** (JSON):
```json
{
"success": true,
"message": "Event created for 00:11:22:33:44:55"
}
```
---
### 2. Get Events
* **GET** `/events`
Retrieve all events, optionally filtered by MAC address:
```
/events?mac=<mac>
```
**Response**:
```json
{
"success": true,
"events": [
{
"eve_MAC": "00:11:22:33:44:55",
"eve_IP": "192.168.1.10",
"eve_DateTime": "2025-08-24T12:00:00Z",
"eve_EventType": "Device Down",
"eve_AdditionalInfo": "",
"eve_PendingAlertEmail": 1
}
]
}
```
---
### 3. Delete Events
* **DELETE** `/events/<mac>` → Delete events for a specific MAC
* **DELETE** `/events` → Delete **all** events
* **DELETE** `/events/<days>` → Delete events older than N days
**Response**:
```json
{
"success": true,
"message": "Deleted events older than <days> days"
}
```
---
### 4. Event Totals Over a Period
* **GET** `/sessions/totals?period=<period>`
Return event and session totals over a given period.
**Query Parameters**:
| Parameter | Description |
| --------- | -------------------------------------------------------------------------------- |
| `period` | Time period for totals, e.g., `"7 days"`, `"1 month"`, `"1 year"`, `"100 years"` |
**Sample Response** (JSON Array):
```json
[120, 85, 5, 10, 3, 7]
```
**Meaning of Values**:
1. Total events in the period
2. Total sessions
3. Missing sessions
4. Voided events (`eve_EventType LIKE 'VOIDED%'`)
5. New device events (`eve_EventType LIKE 'New Device'`)
6. Device down events (`eve_EventType LIKE 'Device Down'`)
---
## Notes
* All endpoints require **authorization** (Bearer token). Unauthorized requests return:
```json
{ "error": "Forbidden" }
```
* Events are stored in the **Events table** with the following fields:
`eve_MAC`, `eve_IP`, `eve_DateTime`, `eve_EventType`, `eve_AdditionalInfo`, `eve_PendingAlertEmail`.
* Event creation automatically logs activity for debugging.
---
## Example `curl` Requests
**Create Event**:
```sh
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/events/create/00:11:22:33:44:55" \
-H "Authorization: Bearer <API_TOKEN>" \
-H "Content-Type: application/json" \
--data '{
"ip": "192.168.1.10",
"event_type": "Device Down",
"additional_info": "Power outage",
"pending_alert": 1
}'
```
**Get Events for a Device**:
```sh
curl "http://<server_ip>:<GRAPHQL_PORT>/events?mac=00:11:22:33:44:55" \
-H "Authorization: Bearer <API_TOKEN>"
```
**Delete Events Older Than 30 Days**:
```sh
curl -X DELETE "http://<server_ip>:<GRAPHQL_PORT>/events/30" \
-H "Authorization: Bearer <API_TOKEN>"
```
**Get Event Totals for 7 Days**:
```sh
curl "http://<server_ip>:<GRAPHQL_PORT>/sessions/totals?period=7 days" \
-H "Authorization: Bearer <API_TOKEN>"
```

200
docs/API_GRAPHQL.md Executable file
View File

@@ -0,0 +1,200 @@
# GraphQL API Endpoint
GraphQL queries are **read-optimized for speed**. Data may be slightly out of date until the file system cache refreshes. The GraphQL endpoints allows you to access the following objects:
- Devices
- Settings
## Endpoints
* **GET** `/graphql`
Returns a simple status message (useful for browser or debugging).
* **POST** `/graphql`
Execute GraphQL queries against the `devicesSchema`.
---
## Devices Query
### Sample Query
```graphql
query GetDevices($options: PageQueryOptionsInput) {
devices(options: $options) {
devices {
rowid
devMac
devName
devOwner
devType
devVendor
devLastConnection
devStatus
}
count
}
}
```
### Query Parameters
| Parameter | Description |
| --------- | ------------------------------------------------------------------------------------------------------- |
| `page` | Page number of results to fetch. |
| `limit` | Number of results per page. |
| `sort` | Sorting options (`field` = field name, `order` = `asc` or `desc`). |
| `search` | Term to filter devices. |
| `status` | Filter devices by status: `my_devices`, `connected`, `favorites`, `new`, `down`, `archived`, `offline`. |
| `filters` | Additional filters (array of `{ filterColumn, filterValue }`). |
---
### `curl` Example
```sh
curl 'http://host:GRAPHQL_PORT/graphql' \
-X POST \
-H 'Authorization: Bearer API_TOKEN' \
-H 'Content-Type: application/json' \
--data '{
"query": "query GetDevices($options: PageQueryOptionsInput) { devices(options: $options) { devices { rowid devMac devName devOwner devType devVendor devLastConnection devStatus } count } }",
"variables": {
"options": {
"page": 1,
"limit": 10,
"sort": [{ "field": "devName", "order": "asc" }],
"search": "",
"status": "connected"
}
}
}'
```
---
### Sample Response
```json
{
"data": {
"devices": {
"devices": [
{
"rowid": 1,
"devMac": "00:11:22:33:44:55",
"devName": "Device 1",
"devOwner": "Owner 1",
"devType": "Type 1",
"devVendor": "Vendor 1",
"devLastConnection": "2025-01-01T00:00:00Z",
"devStatus": "connected"
}
],
"count": 1
}
}
}
```
---
## Settings Query
The **settings query** provides access to NetAlertX configuration stored in the settings table.
### Sample Query
```graphql
query GetSettings {
settings {
settings {
setKey
setName
setDescription
setType
setOptions
setGroup
setValue
setEvents
setOverriddenByEnv
}
count
}
}
```
### Schema Fields
| Field | Type | Description |
| -------------------- | ------- | ------------------------------------------------------------------------ |
| `setKey` | String | Unique key identifier for the setting. |
| `setName` | String | Human-readable name. |
| `setDescription` | String | Description or documentation of the setting. |
| `setType` | String | Data type (`string`, `int`, `bool`, `json`, etc.). |
| `setOptions` | String | Available options (for dropdown/select-type settings). |
| `setGroup` | String | Group/category the setting belongs to. |
| `setValue` | String | Current value of the setting. |
| `setEvents` | String | Events or triggers related to this setting. |
| `setOverriddenByEnv` | Boolean | Whether the setting is overridden by an environment variable at runtime. |
---
### `curl` Example
```sh
curl 'http://host:GRAPHQL_PORT/graphql' \
-X POST \
-H 'Authorization: Bearer API_TOKEN' \
-H 'Content-Type: application/json' \
--data '{
"query": "query GetSettings { settings { settings { setKey setName setDescription setType setOptions setGroup setValue setEvents setOverriddenByEnv } count } }"
}'
```
---
### Sample Response
```json
{
"data": {
"settings": {
"settings": [
{
"setKey": "UI_MY_DEVICES",
"setName": "My Devices Filter",
"setDescription": "Defines which statuses to include in the 'My Devices' view.",
"setType": "list",
"setOptions": "[\"online\",\"new\",\"down\",\"offline\",\"archived\"]",
"setGroup": "UI",
"setValue": "[\"online\",\"new\"]",
"setEvents": null,
"setOverriddenByEnv": false
},
{
"setKey": "NETWORK_DEVICE_TYPES",
"setName": "Network Device Types",
"setDescription": "Types of devices considered as network infrastructure.",
"setType": "list",
"setOptions": "[\"Router\",\"Switch\",\"AP\"]",
"setGroup": "Network",
"setValue": "[\"Router\",\"Switch\"]",
"setEvents": null,
"setOverriddenByEnv": true
}
],
"count": 2
}
}
}
```
---
## Notes
* Device and settings queries can be combined in one request since GraphQL supports batching.
* The `setOverriddenByEnv` flag helps identify setting values that are locked at container runtime.
* The schema is **read-only** — updates must be performed through other APIs or configuration management. See the other [API](API.md) endpoints for details.

103
docs/API_METRICS.md Executable file
View File

@@ -0,0 +1,103 @@
# Metrics API Endpoint
The `/metrics` endpoint exposes **Prometheus-compatible metrics** for NetAlertX, including aggregate device counts and per-device status.
---
## Endpoint Details
* **GET** `/metrics` → Returns metrics in plain text.
* **Host**: NetAlertX server
* **Port**: As configured in `GRAPHQL_PORT` (default: `20212`)
---
## Example Output
```text
netalertx_connected_devices 31
netalertx_offline_devices 54
netalertx_down_devices 0
netalertx_new_devices 0
netalertx_archived_devices 31
netalertx_favorite_devices 2
netalertx_my_devices 54
netalertx_device_status{device="Net - Huawei", mac="Internet", ip="1111.111.111.111", vendor="None", first_connection="2021-01-01 00:00:00", last_connection="2025-08-04 17:57:00", dev_type="Router", device_status="Online"} 1
netalertx_device_status{device="Net - USG", mac="74:ac:74:ac:74:ac", ip="192.168.1.1", vendor="Ubiquiti Networks Inc.", first_connection="2022-02-12 22:05:00", last_connection="2025-06-07 08:16:49", dev_type="Firewall", device_status="Archived"} 1
netalertx_device_status{device="Raspberry Pi 4 LAN", mac="74:ac:74:ac:74:74", ip="192.168.1.9", vendor="Raspberry Pi Trading Ltd", first_connection="2022-02-12 22:05:00", last_connection="2025-08-04 17:57:00", dev_type="Singleboard Computer (SBC)", device_status="Online"} 1
...
```
---
## Metrics Overview
### 1. Aggregate Device Counts
| Metric | Description |
| ----------------------------- | ---------------------------------------- |
| `netalertx_connected_devices` | Devices currently connected |
| `netalertx_offline_devices` | Devices currently offline |
| `netalertx_down_devices` | Down/unreachable devices |
| `netalertx_new_devices` | Recently detected devices |
| `netalertx_archived_devices` | Archived devices |
| `netalertx_favorite_devices` | User-marked favorites |
| `netalertx_my_devices` | Devices associated with the current user |
---
### 2. Per-Device Status
Metric: `netalertx_device_status`
Each device has labels:
* `device`: friendly name
* `mac`: MAC address (or placeholder)
* `ip`: last recorded IP
* `vendor`: manufacturer or "None"
* `first_connection`: timestamp of first detection
* `last_connection`: most recent contact
* `dev_type`: device type/category
* `device_status`: current status (`Online`, `Offline`, `Archived`, `Down`, …)
Metric value is always `1` (presence indicator).
---
## Querying with `curl`
```sh
curl 'http://<server_ip>:<GRAPHQL_PORT>/metrics' \
-H 'Authorization: Bearer <API_TOKEN>' \
-H 'Accept: text/plain'
```
Replace placeholders:
* `<server_ip>` NetAlertX host IP/hostname
* `<GRAPHQL_PORT>` configured port (default `20212`)
* `<API_TOKEN>` your API token
---
## Prometheus Scraping Configuration
```yaml
scrape_configs:
- job_name: 'netalertx'
metrics_path: /metrics
scheme: http
scrape_interval: 60s
static_configs:
- targets: ['<server_ip>:<GRAPHQL_PORT>']
authorization:
type: Bearer
credentials: <API_TOKEN>
```
---
## Grafana Dashboard Template
Sample template JSON: [Download](./samples/API/Grafana_Dashboard.json)

243
docs/API_NETTOOLS.md Executable file
View File

@@ -0,0 +1,243 @@
# Net Tools API Endpoints
The Net Tools API provides **network diagnostic utilities**, including Wake-on-LAN, traceroute, speed testing, DNS resolution, nmap scanning, and internet connection information.
All endpoints require **authorization** via Bearer token.
---
## Endpoints
### 1. Wake-on-LAN
* **POST** `/nettools/wakeonlan`
Sends a Wake-on-LAN packet to wake a device.
**Request Body** (JSON):
```json
{
"devMac": "AA:BB:CC:DD:EE:FF"
}
```
**Response** (success):
```json
{
"success": true,
"message": "WOL packet sent",
"output": "Sent magic packet to AA:BB:CC:DD:EE:FF"
}
```
**Error Responses**:
* Invalid MAC address → HTTP 400
* Command failure → HTTP 500
---
### 2. Traceroute
* **POST** `/nettools/traceroute`
Performs a traceroute to a specified IP address.
**Request Body**:
```json
{
"devLastIP": "192.168.1.1"
}
```
**Response** (success):
```json
{
"success": true,
"output": "traceroute output as string"
}
```
**Error Responses**:
* Invalid IP → HTTP 400
* Traceroute command failure → HTTP 500
---
### 3. Speedtest
* **GET** `/nettools/speedtest`
Runs an internet speed test using `speedtest-cli`.
**Response** (success):
```json
{
"success": true,
"output": [
"Ping: 15 ms",
"Download: 120.5 Mbit/s",
"Upload: 22.4 Mbit/s"
]
}
```
**Error Responses**:
* Command failure → HTTP 500
---
### 4. DNS Lookup (nslookup)
* **POST** `/nettools/nslookup`
Resolves an IP address or hostname using `nslookup`.
**Request Body**:
```json
{
"devLastIP": "8.8.8.8"
}
```
**Response** (success):
```json
{
"success": true,
"output": [
"Server: 8.8.8.8",
"Address: 8.8.8.8#53",
"Name: google-public-dns-a.google.com"
]
}
```
**Error Responses**:
* Missing or invalid `devLastIP` → HTTP 400
* Command failure → HTTP 500
---
### 5. Nmap Scan
* **POST** `/nettools/nmap`
Runs an nmap scan on a target IP address or range.
**Request Body**:
```json
{
"scan": "192.168.1.0/24",
"mode": "fast"
}
```
**Supported Modes**:
| Mode | nmap Arguments |
| --------------- | -------------- |
| `fast` | `-F` |
| `normal` | default |
| `detail` | `-A` |
| `skipdiscovery` | `-Pn` |
**Response** (success):
```json
{
"success": true,
"mode": "fast",
"ip": "192.168.1.0/24",
"output": [
"Starting Nmap 7.91",
"Host 192.168.1.1 is up",
"... scan results ..."
]
}
```
**Error Responses**:
* Invalid IP → HTTP 400
* Invalid mode → HTTP 400
* Command failure → HTTP 500
---
### 6. Internet Connection Info
* **GET** `/nettools/internetinfo`
Fetches public internet connection information using `ipinfo.io`.
**Response** (success):
```json
{
"success": true,
"output": "IP: 203.0.113.5 City: Sydney Country: AU Org: Example ISP"
}
```
**Error Responses**:
* Failed request or empty response → HTTP 500
---
## Example `curl` Requests
**Wake-on-LAN**:
```sh
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/nettools/wakeonlan" \
-H "Authorization: Bearer <API_TOKEN>" \
-H "Content-Type: application/json" \
--data '{"devMac":"AA:BB:CC:DD:EE:FF"}'
```
**Traceroute**:
```sh
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/nettools/traceroute" \
-H "Authorization: Bearer <API_TOKEN>" \
-H "Content-Type: application/json" \
--data '{"devLastIP":"192.168.1.1"}'
```
**Speedtest**:
```sh
curl "http://<server_ip>:<GRAPHQL_PORT>/nettools/speedtest" \
-H "Authorization: Bearer <API_TOKEN>"
```
**Nslookup**:
```sh
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/nettools/nslookup" \
-H "Authorization: Bearer <API_TOKEN>" \
-H "Content-Type: application/json" \
--data '{"devLastIP":"8.8.8.8"}'
```
**Nmap Scan**:
```sh
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/nettools/nmap" \
-H "Authorization: Bearer <API_TOKEN>" \
-H "Content-Type: application/json" \
--data '{"scan":"192.168.1.0/24","mode":"fast"}'
```
**Internet Info**:
```sh
curl "http://<server_ip>:<GRAPHQL_PORT>/nettools/internetinfo" \
-H "Authorization: Bearer <API_TOKEN>"
```

370
docs/API_OLD.md Executable file
View File

@@ -0,0 +1,370 @@
# [Deprecated] API endpoints
> [!WARNING]
> Some of these endpoints will be deprecated soon. Please refere to the new [API](API.md) endpoints docs for details on the new API layer.
NetAlertX comes with a couple of API endpoints. All requests need to be authorized (executed in a logged in browser session) or you have to pass the value of the `API_TOKEN` settings as authorization bearer, for example:
```graphql
curl 'http://host:GRAPHQL_PORT/graphql' \
-X POST \
-H 'Authorization: Bearer API_TOKEN' \
-H 'Content-Type: application/json' \
--data '{
"query": "query GetDevices($options: PageQueryOptionsInput) { devices(options: $options) { devices { rowid devMac devName devOwner devType devVendor devLastConnection devStatus } count } }",
"variables": {
"options": {
"page": 1,
"limit": 10,
"sort": [{ "field": "devName", "order": "asc" }],
"search": "",
"status": "connected"
}
}
}'
```
## API Endpoint: GraphQL
- Endpoint URL: `php/server/query_graphql.php`
- Host: `same as front end (web ui)`
- Port: `20212` or as defined by the `GRAPHQL_PORT` setting
### Example Query to Fetch Devices
First, let's define the GraphQL query to fetch devices with pagination and sorting options.
```graphql
query GetDevices($options: PageQueryOptionsInput) {
devices(options: $options) {
devices {
rowid
devMac
devName
devOwner
devType
devVendor
devLastConnection
devStatus
}
count
}
}
```
See also: [Debugging GraphQL issues](./DEBUG_GRAPHQL.md)
### `curl` Command
You can use the following `curl` command to execute the query.
```sh
curl 'http://host:GRAPHQL_PORT/graphql' -X POST -H 'Authorization: Bearer API_TOKEN' -H 'Content-Type: application/json' --data '{
"query": "query GetDevices($options: PageQueryOptionsInput) { devices(options: $options) { devices { rowid devMac devName devOwner devType devVendor devLastConnection devStatus } count } }",
"variables": {
"options": {
"page": 1,
"limit": 10,
"sort": [{ "field": "devName", "order": "asc" }],
"search": "",
"status": "connected"
}
}
}'
```
### Explanation:
1. **GraphQL Query**:
- The `query` parameter contains the GraphQL query as a string.
- The `variables` parameter contains the input variables for the query.
2. **Query Variables**:
- `page`: Specifies the page number of results to fetch.
- `limit`: Specifies the number of results per page.
- `sort`: Specifies the sorting options, with `field` being the field to sort by and `order` being the sort order (`asc` for ascending or `desc` for descending).
- `search`: A search term to filter the devices.
- `status`: The status filter to apply (valid values are `my_devices` (determined by the `UI_MY_DEVICES` setting), `connected`, `favorites`, `new`, `down`, `archived`, `offline`).
3. **`curl` Command**:
- The `-X POST` option specifies that we are making a POST request.
- The `-H "Content-Type: application/json"` option sets the content type of the request to JSON.
- The `-d` option provides the request payload, which includes the GraphQL query and variables.
### Sample Response
The response will be in JSON format, similar to the following:
```json
{
"data": {
"devices": {
"devices": [
{
"rowid": 1,
"devMac": "00:11:22:33:44:55",
"devName": "Device 1",
"devOwner": "Owner 1",
"devType": "Type 1",
"devVendor": "Vendor 1",
"devLastConnection": "2025-01-01T00:00:00Z",
"devStatus": "connected"
},
{
"rowid": 2,
"devMac": "66:77:88:99:AA:BB",
"devName": "Device 2",
"devOwner": "Owner 2",
"devType": "Type 2",
"devVendor": "Vendor 2",
"devLastConnection": "2025-01-02T00:00:00Z",
"devStatus": "connected"
}
],
"count": 2
}
}
}
```
## API Endpoint: JSON files
This API endpoint retrieves static files, that are periodically updated.
- Endpoint URL: `php/server/query_json.php?file=<file name>`
- Host: `same as front end (web ui)`
- Port: `20211` or as defined by the $PORT docker environment variable (same as the port for the web ui)
### When are the endpoints updated
The endpoints are updated when objects in the API endpoints are changed.
### Location of the endpoints
In the container, these files are located under the `/app/api/` folder. You can access them via the `/php/server/query_json.php?file=user_notifications.json` endpoint.
### Available endpoints
You can access the following files:
| File name | Description |
|----------------------|----------------------|
| `notification_json_final.json` | The json version of the last notification (e.g. used for webhooks - [sample JSON](https://github.com/jokob-sk/NetAlertX/blob/main/front/report_templates/webhook_json_sample.json)). |
| `table_devices.json` | All of the available Devices detected by the app. |
| `table_plugins_events.json` | The list of the unprocessed (pending) notification events (plugins_events DB table). |
| `table_plugins_history.json` | The list of notification events history. |
| `table_plugins_objects.json` | The content of the plugins_objects table. Find more info on the [Plugin system here](https://github.com/jokob-sk/NetAlertX/tree/main/docs/PLUGINS.md)|
| `language_strings.json` | The content of the language_strings table, which in turn is loaded from the plugins `config.json` definitions. |
| `table_custom_endpoint.json` | A custom endpoint generated by the SQL query specified by the `API_CUSTOM_SQL` setting. |
| `table_settings.json` | The content of the settings table. |
| `app_state.json` | Contains the current application state. |
### JSON Data format
The endpoints starting with the `table_` prefix contain most, if not all, data contained in the corresponding database table. The common format for those is:
```JSON
{
"data": [
{
"db_column_name": "data",
"db_column_name2": "data2"
},
{
"db_column_name": "data3",
"db_column_name2": "data4"
}
]
}
```
Example JSON of the `table_devices.json` endpoint with two Devices (database rows):
```JSON
{
"data": [
{
"devMac": "Internet",
"devName": "Net - Huawei",
"devType": "Router",
"devVendor": null,
"devGroup": "Always on",
"devFirstConnection": "2021-01-01 00:00:00",
"devLastConnection": "2021-01-28 22:22:11",
"devLastIP": "192.168.1.24",
"devStaticIP": 0,
"devPresentLastScan": 1,
"devLastNotification": "2023-01-28 22:22:28.998715",
"devIsNew": 0,
"devParentMAC": "",
"devParentPort": "",
"devIcon": "globe"
},
{
"devMac": "a4:8f:ff:aa:ba:1f",
"devName": "Net - USG",
"devType": "Firewall",
"devVendor": "Ubiquiti Inc",
"devGroup": "",
"devFirstConnection": "2021-02-12 22:05:00",
"devLastConnection": "2021-07-17 15:40:00",
"devLastIP": "192.168.1.1",
"devStaticIP": 1,
"devPresentLastScan": 1,
"devLastNotification": "2021-07-17 15:40:10.667717",
"devIsNew": 0,
"devParentMAC": "Internet",
"devParentPort": 1,
"devIcon": "shield-halved"
}
]
}
```
## API Endpoint: Prometheus Exporter
* **Endpoint URL**: `/metrics`
* **Host**: (where NetAlertX exporter is running)
* **Port**: as configured in the `GRAPHQL_PORT` setting (`20212` by default)
---
### Example Output of the `/metrics` Endpoint
Below is a representative snippet of the metrics you may find when querying the `/metrics` endpoint for `netalertx`. It includes both aggregate counters and `device_status` labels per device.
```
netalertx_connected_devices 31
netalertx_offline_devices 54
netalertx_down_devices 0
netalertx_new_devices 0
netalertx_archived_devices 31
netalertx_favorite_devices 2
netalertx_my_devices 54
netalertx_device_status{device="Net - Huawei", mac="Internet", ip="1111.111.111.111", vendor="None", first_connection="2021-01-01 00:00:00", last_connection="2025-08-04 17:57:00", dev_type="Router", device_status="Online"} 1
netalertx_device_status{device="Net - USG", mac="74:ac:74:ac:74:ac", ip="192.168.1.1", vendor="Ubiquiti Networks Inc.", first_connection="2022-02-12 22:05:00", last_connection="2025-06-07 08:16:49", dev_type="Firewall", device_status="Archived"} 1
netalertx_device_status{device="Raspberry Pi 4 LAN", mac="74:ac:74:ac:74:74", ip="192.168.1.9", vendor="Raspberry Pi Trading Ltd", first_connection="2022-02-12 22:05:00", last_connection="2025-08-04 17:57:00", dev_type="Singleboard Computer (SBC)", device_status="Online"} 1
...
```
---
### Metrics Explanation
#### 1. Aggregate Device Counts
Metric names prefixed with `netalertx_` provide aggregated counts by device status:
* `netalertx_connected_devices`: number of devices currently connected
* `netalertx_offline_devices`: devices currently offline
* `netalertx_down_devices`: down/unreachable devices
* `netalertx_new_devices`: devices recently detected
* `netalertx_archived_devices`: archived devices
* `netalertx_favorite_devices`: user-marked favorite devices
* `netalertx_my_devices`: devices associated with the current user context
These numeric values give a high-level overview of device distribution.
#### 2. PerDevice Status with Labels
Each individual device is represented by a `netalertx_device_status` metric, with descriptive labels:
* `device`: friendly name of the device
* `mac`: MAC address (or placeholder)
* `ip`: last recorded IP address
* `vendor`: manufacturer or "None" if unknown
* `first_connection`: timestamp when the device was first observed
* `last_connection`: most recent contact timestamp
* `dev_type`: device category or type
* `device_status`: current status (Online / Offline / Archived / Down / ...)
The metric value is always `1` (indicating presence or active state) and the combination of labels identifies the device.
---
### How to Query with `curl`
To fetch the metrics from the NetAlertX exporter:
```sh
curl 'http://<server_ip>:<GRAPHQL_PORT>/metrics' \
-H 'Authorization: Bearer <API_TOKEN>' \
-H 'Accept: text/plain'
```
Replace:
* `<server_ip>`: IP or hostname of the NetAlertX server
* `<GRAPHQL_PORT>`: port specified in your `GRAPHQL_PORT` setting (default: `20212`)
* `<API_TOKEN>` your Bearer token from the `API_TOKEN` setting
---
### Summary
* **Endpoint**: `/metrics` provides both summary counters and per-device status entries.
* **Aggregate metrics** help monitor overall device states.
* **Detailed metrics** expose each devices metadata via labels.
* **Use case**: feed into Prometheus for scraping, monitoring, alerting, or charting dashboard views.
### Prometheus Scraping Configuration
```yaml
scrape_configs:
- job_name: 'netalertx'
metrics_path: /metrics
scheme: http
scrape_interval: 60s
static_configs:
- targets: ['<server_ip>:<GRAPHQL_PORT>']
authorization:
type: Bearer
credentials: <API_TOKEN>
```
### Grafana template
Grafana template sample: [Download json](./samples/API/Grafana_Dashboard.json)
## API Endpoint: /log files
This API endpoint retrieves files from the `/app/log` folder.
- Endpoint URL: `php/server/query_logs.php?file=<file name>`
- Host: `same as front end (web ui)`
- Port: `20211` or as defined by the $PORT docker environment variable (same as the port for the web ui)
| File | Description |
|--------------------------|---------------------------------------------------------------|
| `IP_changes.log` | Logs of IP address changes |
| `app.log` | Main application log |
| `app.php_errors.log` | PHP error log |
| `app_front.log` | Frontend application log |
| `app_nmap.log` | Logs of Nmap scan results |
| `db_is_locked.log` | Logs when the database is locked |
| `execution_queue.log` | Logs of execution queue activities |
| `plugins/` | Directory for temporary plugin-related files (not accessible) |
| `report_output.html` | HTML report output |
| `report_output.json` | JSON format report output |
| `report_output.txt` | Text format report output |
| `stderr.log` | Logs of standard error output |
| `stdout.log` | Logs of standard output |
## API Endpoint: /config files
To retrieve files from the `/app/config` folder.
- Endpoint URL: `php/server/query_config.php?file=<file name>`
- Host: `same as front end (web ui)`
- Port: `20211` or as defined by the $PORT docker environment variable (same as the port for the web ui)
| File | Description |
|--------------------------|--------------------------------------------------|
| `devices.csv` | Devices csv file |
| `app.conf` | Application config file |

32
docs/API_ONLINEHISTORY.md Executable file
View File

@@ -0,0 +1,32 @@
# Online History API Endpoints
Manage the **online history records** of devices. Currently, the API supports deletion of all history entries. All endpoints require **authorization**.
---
## 1. Delete Online History
* **DELETE** `/history`
Remove **all records** from the online history table (`Online_History`). This operation **cannot be undone**.
**Response** (success):
```json
{
"success": true,
"message": "Deleted online history"
}
```
**Error Responses**:
* Unauthorized → HTTP 403
---
### Example `curl` Request
```bash
curl -X DELETE "http://<server_ip>:<GRAPHQL_PORT>/history" \
-H "Authorization: Bearer <API_TOKEN>"
```

240
docs/API_SESSIONS.md Executable file
View File

@@ -0,0 +1,240 @@
# Sessions API Endpoints
Track and manage device connection sessions. Sessions record when a device connects or disconnects on the network.
### Create a Session
* **POST** `/sessions/create` → Create a new session for a device
**Request Body:**
```json
{
"mac": "AA:BB:CC:DD:EE:FF",
"ip": "192.168.1.10",
"start_time": "2025-08-01T10:00:00",
"end_time": "2025-08-01T12:00:00", // optional
"event_type_conn": "Connected", // optional, default "Connected"
"event_type_disc": "Disconnected" // optional, default "Disconnected"
}
```
**Response:**
```json
{
"success": true,
"message": "Session created for MAC AA:BB:CC:DD:EE:FF"
}
```
#### `curl` Example
```bash
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/sessions/create" \
-H "Authorization: Bearer <API_TOKEN>" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"mac": "AA:BB:CC:DD:EE:FF",
"ip": "192.168.1.10",
"start_time": "2025-08-01T10:00:00",
"end_time": "2025-08-01T12:00:00",
"event_type_conn": "Connected",
"event_type_disc": "Disconnected"
}'
```
---
### Delete Sessions
* **DELETE** `/sessions/delete` → Delete all sessions for a given MAC
**Request Body:**
```json
{
"mac": "AA:BB:CC:DD:EE:FF"
}
```
**Response:**
```json
{
"success": true,
"message": "Deleted sessions for MAC AA:BB:CC:DD:EE:FF"
}
```
#### `curl` Example
```bash
curl -X DELETE "http://<server_ip>:<GRAPHQL_PORT>/sessions/delete" \
-H "Authorization: Bearer <API_TOKEN>" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"mac": "AA:BB:CC:DD:EE:FF"
}'
```
---
### List Sessions
* **GET** `/sessions/list` → Retrieve sessions optionally filtered by device and date range
**Query Parameters:**
* `mac` (optional) → Filter by device MAC address
* `start_date` (optional) → Filter sessions starting from this date (`YYYY-MM-DD`)
* `end_date` (optional) → Filter sessions ending by this date (`YYYY-MM-DD`)
**Example:**
```
/sessions/list?mac=AA:BB:CC:DD:EE:FF&start_date=2025-08-01&end_date=2025-08-21
```
**Response:**
```json
{
"success": true,
"sessions": [
{
"ses_MAC": "AA:BB:CC:DD:EE:FF",
"ses_Connection": "2025-08-01 10:00",
"ses_Disconnection": "2025-08-01 12:00",
"ses_Duration": "2h 0m",
"ses_IP": "192.168.1.10",
"ses_Info": ""
}
]
}
```
#### `curl` Example
```bash
curl -X GET "http://<server_ip>:<GRAPHQL_PORT>/sessions/list?mac=AA:BB:CC:DD:EE:FF&start_date=2025-08-01&end_date=2025-08-21" \
-H "Authorization: Bearer <API_TOKEN>" \
-H "Accept: application/json"
```
---
### Calendar View of Sessions
* **GET** `/sessions/calendar` → View sessions in calendar format
**Query Parameters:**
* `start` → Start date (`YYYY-MM-DD`)
* `end` → End date (`YYYY-MM-DD`)
**Example:**
```
/sessions/calendar?start=2025-08-01&end=2025-08-21
```
**Response:**
```json
{
"success": true,
"sessions": [
{
"resourceId": "AA:BB:CC:DD:EE:FF",
"title": "",
"start": "2025-08-01T10:00:00",
"end": "2025-08-01T12:00:00",
"color": "#00a659",
"tooltip": "Connection: 2025-08-01 10:00\nDisconnection: 2025-08-01 12:00\nIP: 192.168.1.10",
"className": "no-border"
}
]
}
```
#### `curl` Example
```bash
curl -X GET "http://<server_ip>:<GRAPHQL_PORT>/sessions/calendar?start=2025-08-01&end=2025-08-21" \
-H "Authorization: Bearer <API_TOKEN>" \
-H "Accept: application/json"
```
---
### Device Sessions
* **GET** `/sessions/<mac>` → Retrieve sessions for a specific device
**Query Parameters:**
* `period` → Period to retrieve sessions (`1 day`, `7 days`, `1 month`, etc.)
Default: `1 day`
**Example:**
```
/sessions/AA:BB:CC:DD:EE:FF?period=7 days
```
**Response:**
```json
{
"success": true,
"sessions": [
{
"ses_MAC": "AA:BB:CC:DD:EE:FF",
"ses_Connection": "2025-08-01 10:00",
"ses_Disconnection": "2025-08-01 12:00",
"ses_Duration": "2h 0m",
"ses_IP": "192.168.1.10",
"ses_Info": ""
}
]
}
```
#### `curl` Example
```bash
curl -X GET "http://<server_ip>:<GRAPHQL_PORT>/sessions/AA:BB:CC:DD:EE:FF?period=7%20days" \
-H "Authorization: Bearer <API_TOKEN>" \
-H "Accept: application/json"
```
---
### Session Events Summary
* **GET** `/sessions/session-events` → Retrieve a summary of session events
**Query Parameters:**
* `type` → Event type (`all`, `sessions`, `missing`, `voided`, `new`, `down`)
Default: `all`
* `period` → Period to retrieve events (`7 days`, `1 month`, etc.)
**Example:**
```
/sessions/session-events?type=all&period=7 days
```
**Response:**
Returns a list of events or sessions with formatted connection, disconnection, duration, and IP information.
#### `curl` Example
```bash
curl -X GET "http://<server_ip>:<GRAPHQL_PORT>/sessions/session-events?type=all&period=7%20days" \
-H "Authorization: Bearer <API_TOKEN>" \
-H "Accept: application/json"
```

92
docs/API_SETTINGS.md Executable file
View File

@@ -0,0 +1,92 @@
# Settings API Endpoints
Retrieve application settings stored in the configuration system. This endpoint is useful for quickly fetching individual settings such as `API_TOKEN` or `TIMEZONE`.
For bulk or structured access (all settings, schema details, or filtering), use the [GraphQL API Endpoint](API_GRAPHQL.md).
---
### Get a Setting
* **GET** `/settings/<key>` → Retrieve the value of a specific setting
**Path Parameter:**
* `key` → The setting key to retrieve (e.g., `API_TOKEN`, `TIMEZONE`)
**Authorization:**
Requires a valid API token in the `Authorization` header.
---
#### `curl` Example (Success)
```sh
curl 'http://<server_ip>:<GRAPHQL_PORT>/settings/API_TOKEN' \
-H 'Authorization: Bearer <API_TOKEN>' \
-H 'Accept: application/json'
```
**Response:**
```json
{
"success": true,
"value": "my-secret-token"
}
```
---
#### `curl` Example (Invalid Key)
```sh
curl 'http://<server_ip>:<GRAPHQL_PORT>/settings/DOES_NOT_EXIST' \
-H 'Authorization: Bearer <API_TOKEN>' \
-H 'Accept: application/json'
```
**Response:**
```json
{
"success": true,
"value": null
}
```
---
#### `curl` Example (Unauthorized)
```sh
curl 'http://<server_ip>:<GRAPHQL_PORT>/settings/API_TOKEN' \
-H 'Accept: application/json'
```
**Response:**
```json
{
"error": "Forbidden"
}
```
---
### Notes
* This endpoint is optimized for **direct retrieval of a single setting**.
* For **complex retrieval scenarios** (listing all settings, retrieving schema metadata like `setName`, `setDescription`, `setType`, or checking if a setting is overridden by environment variables), use the **GraphQL Settings Query**:
```sh
curl 'http://<server_ip>:<GRAPHQL_PORT>/graphql' \
-X POST \
-H 'Authorization: Bearer <API_TOKEN>' \
-H 'Content-Type: application/json' \
--data '{
"query": "query GetSettings { settings { settings { setKey setName setDescription setType setOptions setGroup setValue setEvents setOverriddenByEnv } count } }"
}'
```
See the [GraphQL API Endpoint](API_GRAPHQL.md) for more details.

125
docs/API_SYNC.md Executable file
View File

@@ -0,0 +1,125 @@
# Sync API Endpoint
---
The `/sync` endpoint is used by the **SYNC plugin** to synchronize data between multiple NetAlertX instances (e.g., from a node to a hub). It supports both **GET** and **POST** requests.
#### 9.1 GET `/sync`
Fetches data from a node to the hub. The data is returned as a **base64-encoded JSON file**.
**Example Request:**
```sh
curl 'http://<server>:<GRAPHQL_PORT>/sync' \
-H 'Authorization: Bearer <API_TOKEN>'
```
**Response Example:**
```json
{
"node_name": "NODE-01",
"status": 200,
"message": "OK",
"data_base64": "eyJkZXZpY2VzIjogW3siZGV2TWFjIjogIjAwOjExOjIyOjMzOjQ0OjU1IiwiZGV2TmFtZSI6ICJEZXZpY2UgMSJ9XSwgImNvdW50Ijog1fQ==",
"timestamp": "2025-08-24T10:15:00+10:00"
}
```
**Notes:**
* `data_base64` contains the full JSON data encoded in Base64.
* `node_name` corresponds to the `SYNC_node_name` setting on the node.
* Errors (e.g., missing file) return HTTP 500 with an error message.
---
#### 9.2 POST `/sync`
The **POST** endpoint is used by nodes to **send data to the hub**. The hub expects the data as **form-encoded fields** (application/x-www-form-urlencoded or multipart/form-data). The hub then stores the data in the plugin log folder for processing.
#### Required Fields
| Field | Type | Description |
| ----------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `data` | string | The payload from the plugin or devices. Typically **plain text**, **JSON**, or **encrypted Base64** data. In your Python script, `encrypt_data()` is applied before sending. |
| `node_name` | string | The name of the node sending the data. Matches the nodes `SYNC_node_name` setting. Used to generate the filename on the hub. |
| `plugin` | string | The name of the plugin sending the data. Determines the filename prefix (`last_result.<plugin>...`). |
| `file_path` | string (optional) | Path of the local file being sent. Used only for logging/debugging purposes on the hub; **not required for processing**. |
---
### How the Hub Processes the POST Data
1. **Receives the data** and validates the API token.
2. **Stores the raw payload** in:
```
INSTALL_PATH/log/plugins/last_result.<plugin>.encoded.<node_name>.<sequence>.log
```
* `<plugin>` → plugin name from the POST request.
* `<node_name>` → node name from the POST request.
* `<sequence>` → incremented number for each submission.
3. **Decodes / decrypts the data** if necessary (Base64 or encrypted) before processing.
4. **Processes JSON payloads** (e.g., device info) to:
* Avoid duplicates by tracking `devMac`.
* Add metadata like `devSyncHubNode`.
* Insert new devices into the database.
5. **Renames files** to indicate they have been processed:
```
processed_last_result.<plugin>.<node_name>.<sequence>.log
```
---
### Example POST Payload
If a node is sending device data:
```bash
curl -X POST 'http://<hub>:<PORT>/sync' \
-H 'Authorization: Bearer <API_TOKEN>' \
-F 'data={"data":[{"devMac":"00:11:22:33:44:55","devName":"Device 1","devVendor":"Vendor A","devLastIP":"192.168.1.10"}]}' \
-F 'node_name=NODE-01' \
-F 'plugin=SYNC'
```
* The `data` field contains JSON with a **`data` array**, where each element is a **device object** or **plugin data object**.
* The `plugin` and `node_name` fields allow the hub to **organize and store the file correctly**.
* The data is only processed if the relevant plugins are enabled and run on the target server.
---
### Key Notes
* **Always use the same `plugin` and `node_name` values** for consistent storage.
* **Encrypted data**: The Python script uses `encrypt_data()` before sending, and the hub decodes it before processing.
* **Sequence numbers**: Every submission generates a new sequence, preventing overwriting previous data.
* **Form-encoded**: The hub expects `multipart/form-data` (cURL `-F`) or `application/x-www-form-urlencoded`.
**Storage Details:**
* Data is stored under `INSTALL_PATH/log/plugins` with filenames following the pattern:
```
last_result.<plugin>.encoded.<node_name>.<sequence>.log
```
* Both encoded and decoded files are tracked, and new submissions increment the sequence number.
* If storing fails, the API returns HTTP 500 with an error message.
* The data is only processed if the relevant plugins are enabled and run on the target server.
---
#### 9.3 Notes and Best Practices
* **Authorization Required** Both GET and POST require a valid API token.
* **Data Integrity** Ensure that `node_name` and `plugin` are consistent to avoid overwriting files.
* **Monitoring** Notifications are generated whenever data is sent or received (`write_notification`), which can be used for alerting or auditing.
* **Use Case** Typically used in multi-node deployments to consolidate device and event data on a central hub.

12
docs/API_TESTS.md Executable file
View File

@@ -0,0 +1,12 @@
### Unit Tests
>[!WARNING]
> Please note these test modify data in the database.
1. See the `/test` directory for available test cases. These are not exhaustive but cover the main API endpoints.
2. To run a test case, SSH into the container:
`sudo docker exec -it netalertx /bin/bash`
3. Inside the container, install pytest (if not already installed):
`pip install pytest`
4. Run a specific test case:
`pytest /app/test/TESTFILE.py`

34
docs/DEBUG_PHP.md Executable file
View File

@@ -0,0 +1,34 @@
# Debugging backend PHP issues
## Logs in UI
![Logs UI](./img/DEBUG/maintenance_debug_php.png)
You can view recent backend PHP errors directly in the **Maintenance > Logs** section of the UI. This provides quick access to logs without needing terminal access.
## Accessing logs directly
Sometimes, the UI might not be accessible. In that case, you can access the logs directly inside the container.
### Step-by-step:
1. **Open a shell into the container:**
```bash
docker exec -it netalertx /bin/sh
```
2. **Check the NGINX error log:**
```bash
cat /var/log/nginx/error.log
```
3. **Check the PHP application error log:**
```bash
cat /app/log/app.php_errors.log
```
These logs will help identify syntax issues, fatal errors, or startup problems when the UI fails to load properly.

114
docs/DEVICE_HEURISTICS.md Executable file
View File

@@ -0,0 +1,114 @@
# Device Heuristics: Icon and Type Guessing
This module is responsible for inferring the most likely **device type** and **icon** based on minimal identifying data like MAC address, vendor, IP, or device name.
It does this using a set of heuristics defined in an external JSON rules file, which it evaluates **in priority order**.
>[!NOTE]
> You can find the full source code of the heuristics module in the `device_heuristics.py` file.
---
## JSON Rule Format
Rules are defined in a file called `device_heuristics_rules.json` (located under `/back`), structured like:
```json
[
{
"dev_type": "Phone",
"icon_html": "<i class=\"fa-brands fa-apple\"></i>",
"matching_pattern": [
{ "mac_prefix": "001A79", "vendor": "Apple" }
],
"name_pattern": ["iphone", "pixel"]
}
]
```
>[!NOTE]
> Feel free to raise a PR in case you'd like to add any rules into the `device_heuristics_rules.json` file. Please place new rules into the correct position and consider the priority of already available rules.
### Supported fields:
| Field | Type | Description |
| ------------------ | -------------------- | --------------------------------------------------------------- |
| `dev_type` | `string` | Type to assign if rule matches (e.g. `"Gateway"`, `"Phone"`) |
| `icon_html` | `string` | Icon (HTML string) to assign if rule matches. Encoded to base64 at load time. |
| `matching_pattern` | `array` | List of `{ mac_prefix, vendor }` objects for first strict and then loose matching |
| `name_pattern` | `array` *(optional)* | List of lowercase substrings (used with regex) |
| `ip_pattern` | `array` *(optional)* | Regex patterns to match IPs |
**Order in this array defines priority** — rules are checked top-down and short-circuit on first match.
---
## Matching Flow (in Priority Order)
The function `guess_device_attributes(...)` runs a series of matching functions in strict order:
1. MAC + Vendor → `match_mac_and_vendor()`
2. Vendor only → `match_vendor()`
3. Name pattern → `match_name()`
4. IP pattern → `match_ip()`
5. Final fallback → defaults defined in the `NEWDEV_devIcon` and `NEWDEV_devType` settings.
> [!NOTE]
> The app will try guessing the device type or icon if `devType` or `devIcon` are `""` or `"null"`.
### Use of default values
The guessing process runs for every device **as long as the current type or icon still matches the default values**. Even if earlier heuristics return a match, the system continues evaluating additional clues — like name or IP — to try and replace placeholders.
```python
# Still considered a match attempt if current values are defaults
if (not type_ or type_ == default_type) or (not icon or icon == default_icon):
type_, icon = match_ip(ip, default_type, default_icon)
```
In other words: if the type or icon is still `"unknown"` (or matches the default), the system assumes the match isnt final — and keeps looking. It stops only when both values are non-default (defaults are defined in the `NEWDEV_devIcon` and `NEWDEV_devType` settings).
---
## Match Behavior (per function)
These functions are executed in the following order:
### `match_mac_and_vendor(mac_clean, vendor, ...)`
* Looks for MAC prefix **and** vendor substring match
* Most precise
* Stops as soon as a match is found
### `match_vendor(vendor, ...)`
* Falls back to substring match on vendor only
* Ignores rules where `mac_prefix` is present (ensures this is really a fallback)
### `match_name(name, ...)`
* Lowercase name is compared against all `name_pattern` values using regex
* Good for user-assigned labels (e.g. "AP Office", "iPhone")
### `match_ip(ip, ...)`
* If IP is present and matches regex patterns under any rule, it returns that type/icon
* Usually used for gateways or local IP ranges
---
## Icons
* Each rule can define an `icon_html`, which is converted to a `icon_base64` on load
* If missing, it falls back to the passed-in `default_icon` (`NEWDEV_devIcon` setting)
* If a match is found but icon is still blank, default is used
**TL;DR:** Type and icon must both be matched. If only one is matched, the other falls back to the default.
---
## Priority Mechanics
* JSON rules are evaluated **top-to-bottom**
* Matching is **first-hit wins** — no scoring, no weights
* Rules that are more specific (e.g. exact MAC prefixes) should be listed earlier

View File

@@ -1,32 +1,42 @@
# Development environment set up
# Development Environment Setup
>[!NOTE]
> Replace `/development` with the path where your code files will be stored. The default container name is `netalertx` so there might be a conflict with your running containers.
I truly appreciate all contributions! To help keep this project maintainable, this guide provides an overview of project priorities, key design considerations, and overall philosophy. It also includes instructions for setting up your environment so you can start contributing right away.
## Development Guidelines
Before starting development, please scan the below development guidelines.
Before starting development, please review the following guidelines.
### Priority Order (Highest to Lowest)
1. 🔼 Fixing core bugs that lack workarounds.
2. 🔵 Adding core functionality that unlocks other features (e.g., plugins).
3. 🔵 Refactoring to enable faster development.
4. 🔽 UI improvements (PRs welcome).
1. 🔼 Fixing core bugs that lack workarounds
2. 🔵 Adding core functionality that unlocks other features (e.g., plugins)
3. 🔵 Refactoring to enable faster development
4. 🔽 UI improvements (PRs welcome, but low priority)
### Design Philosophy
Focus on core functionality and integrate with existing tools rather than reinventing the wheel.
Examples:
The application architecture is designed for extensibility and maintainability. It relies heavily on configuration manifests via plugins and settings to dynamically build the UI and populate the application with data from various sources.
- Using **Apprise** for notifications instead of implementing multiple separate gateways.
- Implementing **regex-based validation** instead of one-off validation for each setting.
For details, see:
- [Plugins Development](PLUGINS_DEV.md) (includes video)
- [Settings System](SETTINGS_SYSTEM.md)
> [!NOTE]
> UI changes have lower priority, however, PRs are welcome, but **keep them small & focused**.
Focus on **core functionality** and integrate with existing tools rather than reinventing the wheel.
Examples:
- Using **Apprise** for notifications instead of implementing multiple separate gateways
- Implementing **regex-based validation** instead of one-off validation for each setting
> [!NOTE]
> UI changes have lower priority. PRs are welcome, but please keep them **small and focused**.
## Development Environment Set Up
The following steps will guide you to set up your environment for local development and to run a custom docker build on your system. For most changes the container doesn't need to be rebuild which speeds up the development significantly.
>[!NOTE]
> Replace `/development` with the path where your code files will be stored. The default container name is `netalertx` so there might be a conflict with your running containers.
### 1. Download the code:
- `mkdir /development`
@@ -91,7 +101,7 @@ Most code changes can be tested without rebuilding the container. When working o
- `sudo docker exec -it netalertx /bin/bash`
- `pkill -f "python /app/server" && python /app/server & `
3. If none of the above work, restart the docker caontainer.
3. If none of the above work, restart the docker container.
- This is usually the last resort as sometimes the Docker engine becomes unresponsive and the whole engine needs to be restarted.
@@ -119,3 +129,6 @@ Most code changes can be tested without rebuilding the container. When working o
- Updating a Device
- Plugin functionality.
- Error log inspection.
> [!NOTE]
> Always run all available tests as per the [Testing documentation](API_TESTS.md).

View File

@@ -137,3 +137,67 @@ networks:
```
### Example 5: same as 3 but with a top-level root directory; also works in Portainer as-is
`docker-compose.yml`
```yaml
services:
netalertx:
container_name: netalertx
# use the below line if you want to test the latest dev image instead of the stable release
# image: "ghcr.io/jokob-sk/netalertx-dev:latest"
image: "ghcr.io/jokob-sk/netalertx:latest"
network_mode: "host"
restart: unless-stopped
volumes:
- ${APP_FOLDER}/netalertx/config:/app/config
- ${APP_FOLDER}/netalertx/db:/app/db
# (optional) useful for debugging if you have issues setting up the container
- ${APP_FOLDER}/netalertx/log:/app/log
# (API: OPTION 1) default -> use for performance
- type: tmpfs
target: /app/api
# (API: OPTION 2) use when debugging issues
# - ${APP_FOLDER}/netalertx/api:/app/api
environment:
- TZ=${TZ}
- PORT=${PORT}
- PUID=${PUID}
- PGID=${PGID}
- LISTEN_ADDR=${LISTEN_ADDR}
```
`.env` file
```yaml
APP_FOLDER=/path/to/local/NetAlertX/location
#ENVIRONMENT VARIABLES
PUID=200
PGID=300
TZ=America/New_York
LISTEN_ADDR=0.0.0.0
PORT=20211
#GLOBAL PATH VARIABLE
# you may want to create a dedicated user and group to run the container with
# sudo groupadd -g 300 nax-g
# sudo useradd -u 200 -g 300 nax-u
# mkdir -p $APP_FOLDER/{db,config,log}
# chown -R 200:300 $APP_FOLDER
# chmod -R 775 $APP_FOLDER
# DEVELOPMENT VARIABLES
# you can create multiple env files called .env.dev1, .env.dev2 etc and use them by running:
# docker compose --env-file .env.dev1 up -d
# you can then clone multiple dev copies of NetAlertX just make sure to change the APP_FOLDER and PORT variables in each .env.devX file
```
To run the container execute: `sudo docker-compose --env-file /path/to/.env up`

97
docs/DOCKER_PORTAINER.md Executable file
View File

@@ -0,0 +1,97 @@
# Deploying NetAlertX in Portainer (via Stacks)
This guide shows you how to set up **NetAlertX** using Portainers **Stacks** feature.
![Portainer > Stacks](./img/DOCKER/DOCKER_PORTAINER.png)
---
## 1. Prepare Your Host
Before deploying, make sure you have a folder on your Docker host for NetAlertX data. Replace `APP_FOLDER` with your preferred location, for example `/opt` here:
```bash
mkdir -p /opt/netalertx/config
mkdir -p /opt/netalertx/db
mkdir -p /opt/netalertx/log
```
---
## 2. Open Portainer Stacks
1. Log in to your **Portainer UI**.
2. Navigate to **Stacks****Add stack**.
3. Give your stack a name (e.g., `netalertx`).
---
## 3. Paste the Stack Configuration
Copy and paste the following YAML into the **Web editor**:
```yaml
services:
netalertx:
container_name: netalertx
# Use this line for stable release
image: "ghcr.io/jokob-sk/netalertx:latest"
# Or, use this for the latest development build
# image: "ghcr.io/jokob-sk/netalertx-dev:latest"
network_mode: "host"
restart: unless-stopped
volumes:
- ${APP_FOLDER}/netalertx/config:/app/config
- ${APP_FOLDER}/netalertx/db:/app/db
# Optional: logs (useful for debugging setup issues, comment out for performance)
- ${APP_FOLDER}/netalertx/log:/app/log
# API storage options:
# (Option 1) tmpfs (default, best performance)
- type: tmpfs
target: /app/api
# (Option 2) bind mount (useful for debugging)
# - ${APP_FOLDER}/netalertx/api:/app/api
environment:
- TZ=${TZ}
- PORT=${PORT}
- APP_CONF_OVERRIDE=${APP_CONF_OVERRIDE}
```
---
## 4. Configure Environment Variables
In the **Environment variables** section of Portainer, add the following:
* `APP_FOLDER=/opt` (or wherever you created the directories in step 1)
* `TZ=Europe/Berlin` (replace with your timezone)
* `PORT=22022` (or another port if needed)
* `APP_CONF_OVERRIDE={"GRAPHQL_PORT":"22023"}` (optional advanced settings)
---
## 5. Deploy the Stack
1. Scroll down and click **Deploy the stack**.
2. Portainer will pull the image and start NetAlertX.
3. Once running, access the app at:
```
http://<your-docker-host-ip>:22022
```
---
## 6. Verify and Troubleshoot
* Check logs via Portainer → **Containers**`netalertx`**Logs**.
* Logs are stored under `${APP_FOLDER}/netalertx/log` if you enabled that volume.
Once the application is running, configure it by reading the [initial setup](INITIAL_SETUP.md) guide, or [troubleshoot common issues](COMMON_ISSUES.md).

View File

@@ -91,6 +91,12 @@ You can confirm that `raspberrypi` now acts as a network device in two places:
> Hovering over devices in the tree reveals connection details and tooltips for quick inspection.
> [!NOTE]
> Selecting certain relationship types hides the device in the default device views.
> You can change this behavior by adjusting the `UI_hide_rel_types` setting, which by default is set to `["nic","virtual"]`.
> This means devices with `devParentRelType` set to `nic` or `virtual` will not be shown.
> All devices, regardless of relationship type, are always accessible in the **All devices** view.
---
## ✅ Summary

View File

@@ -79,10 +79,11 @@ Device-detecting plugins insert values into the `CurrentScan` database table. T
| `SETPWD` | [set_password](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/set_password/) | ⚙ | Set password | | Yes |
| `SMTP` | [_publisher_email](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/_publisher_email/) | ▶️ | Email notifications | | |
| `SNMPDSC` | [snmp_discovery](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/snmp_discovery/) | 🔍/📥 | SNMP device import & sync | | |
| `SYNC` | [sync](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/sync/) | 🔍/⚙/📥 | Sync & import from NetAlertX instances | 🖧 🔄 | Yes |
| `SYNC` | [sync](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/sync/) | 🔍/⚙/📥 | Sync & import from NetAlertX instances | 🖧 🔄 | Yes |
| `TELEGRAM` | [_publisher_telegram](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/_publisher_telegram/) | ▶️ | Telegram notifications | | |
| `UI` | [ui_settings](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/ui_settings/) | ♻ | UI specific settings | | Yes |
| `UNFIMP` | [unifi_import](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/unifi_import/) | 🔍/📥/🆎 | UniFi device import & sync | 🖧 | |
| `UNIFIAPI` | [unifi_api_import](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/unifi_api_import/) | 🔍/📥/🆎 | UniFi device import (SM API, multi-site) | | |
| `VNDRPDT` | [vendor_update](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/vendor_update/) | ⚙ | Vendor database update | | |
| `WEBHOOK` | [_publisher_webhook](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/_publisher_webhook/) | ▶️ | Webhook notifications | | |
| `WEBMON` | [website_monitor](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/website_monitor/) | ♻ | Website down monitoring | | |

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
<?php
require 'php/templates/header.php';
require 'php/templates/notification.php';
require 'php/templates/modals.php';
?>
<!-- ----------------------------------------------------------------------- -->

View File

@@ -18,6 +18,7 @@
--color-yellow: #f39c12;
--color-red: #dd4b39;
--color-gray: #8c8c8c;
--color-black: #000;
}
.input-group .checkbox
@@ -387,6 +388,16 @@ body
margin-bottom: 0px;
}
.plugin-content #tabs-location .nav-tabs-custom > .nav-tabs > li
{
display: contents;
}
.plugin-content .left-nav
{
display: contents;
}
.pa-small-box-2 .inner h3 {
margin-left: 0em;
margin-bottom: 1.3em;
@@ -594,6 +605,20 @@ body
cursor: default;
}
.btn-outline:hover
{
border: 1px solid var(--color-black);
background: transparent;
color: var(--color-black);
}
.btn-outline
{
border: 1px solid var(--color-gray);
background: transparent;
color: var(--color-gray);
}
/* -----------------------------------------------------------------------------
Customized Full Calendar
----------------------------------------------------------------------------- */
@@ -1411,6 +1436,7 @@ input[readonly] {
.iconPreview svg{
min-width: 20px;
max-width: 20px;
margin-bottom: -3px;
}
@@ -1489,7 +1515,7 @@ input[readonly] {
}
#tableDevicesBox td svg, #tableDevicesBox td i{
height: 1.5em !important;
height: 1em !important;
}
#TileCards .tile .inner
@@ -1649,6 +1675,21 @@ input[readonly] {
pointer-events: none;
}
.custom-badge a
{
color: #fff !important;
font-size: 14px;
}
.custom-badge
{
border: 1px solid #aaa;
border-radius: 4px;
border-style: solid;
padding: 0 5px;
font-size: 14px;
display: inline-block;
}
#deviceDetailsEdit .form-control
{
min-height: 42px;
@@ -1700,6 +1741,16 @@ input[readonly] {
width: 92%;
}
#modal-ok
{
z-index: 1051; /*highest priority*/
}
#modal-form-plc
{
display: grid;
}
/* ----------------------------------------------------------------- */
/* NETWORK page */
/* ----------------------------------------------------------------- */
@@ -2093,10 +2144,10 @@ input[readonly] {
#loadingSpinner {
position: fixed;
z-index: 1000;
top: 0;
left: 0;
width: 100%;
height: 100%;
/* top: 0; */
/* left: 0; */
/* width: 100%; */
/* height: 100%; */
opacity: 0;
transition: opacity 0.3s ease-in-out;
pointer-events: none;
@@ -2114,16 +2165,16 @@ input[readonly] {
pointer-events: auto;
}
.pa_semitransparent-panel {
.nax_semitransparent-panel {
position: absolute;
width: 100%;
height: 100%;
background-color: #fff;
opacity: 0.8;
opacity: 0.5;
z-index: 99;
}
.pa_spinner {
.nax_spinner {
position: absolute;
top: 100px;
left: 50%;
@@ -2160,9 +2211,10 @@ input[readonly] {
}
.pia-top-left-logo
.top-left-logo
{
height:50px;
height:35px;
width:35px;
}
/* -----------------------------------------------------------------------------

View File

@@ -419,6 +419,12 @@ td.highlight {
border: 1px solid #353c42;
}
.btn-outline {
border: 1px solid var(--color-black);
background: transparent;
color: var(--color-white);
}
/* Used in debug log page */
.log-red {
color: #ff4038;
@@ -744,7 +750,7 @@ table.dataTable tbody tr.selected, table.dataTable tbody tr .selected
top: 0.01em;
font-size: 3.25em;
}
.pa_semitransparent-panel{
.nax_semitransparent-panel{
background-color: #000 !important;
}

View File

@@ -20,6 +20,7 @@
--color-yellow: #f39c12;
--color-red: #dd4b39;
--color-gray: #8c8c8c;
--color-white: #fff;
}
:root {
@@ -421,6 +422,12 @@
border: 1px solid #353c42;
}
.btn-outline {
border: 1px solid var(--color-black);
background: transparent;
color: var(--color-white);
}
/* Used in debug log page */
.log-red {
color: #ff4038;
@@ -746,7 +753,7 @@
top: 0.01em;
font-size: 3.25em;
}
.pa_semitransparent-panel{
.nax_semitransparent-panel{
background-color: #000 !important;
}
@@ -793,5 +800,5 @@
.btn:hover
{
color: var(--color-gray);
color: var(--color-white);
}

View File

@@ -25,7 +25,7 @@
<!-- Content header--------------------------------------------------------- -->
<section class="content-header">
<?php require 'php/templates/notification.php'; ?>
<?php require 'php/templates/modals.php'; ?>
<h1 id="pageTitle">
&nbsp<small>Quering device info...</small>
@@ -123,7 +123,7 @@
</div>
</ul>
<div class="tab-content" style="min-height: 430px;">
<div class="tab-content spinnerTarget" style="min-height: 430px;">
<!-- tab page 1 ------------------------------------------------------------ -->
@@ -497,7 +497,7 @@ function updateDevicePageName(mac) {
let owner = getDevDataByMac(mac, "devOwner");
// If data is missing, re-cache and retry once
if (name === "Unknown" || owner === "Unknown") {
if (mac != 'new' && (name === null|| owner === null)) {
console.warn("Device not found in cache, retrying after re-cache:", mac);
showSpinner();
cacheDevices().then(() => {
@@ -541,7 +541,7 @@ function updateDevicePageName(mac) {
window.onload = function async()
{
initializeTabs();
// initializeTabs();
updateChevrons(mac);
updateDevicePageName(mac);

View File

@@ -53,14 +53,6 @@
var deviceData = JSON.parse(data);
// // Deactivate next previous buttons
// if (readAllData) {
// $('#btnPrevious').attr ('disabled','');
// $('#btnPrevious').addClass ('text-gray50');
// $('#btnNext').attr ('disabled','');
// $('#btnNext').addClass ('text-gray50');
// }
// some race condition, need to implement delay
setTimeout(() => {
$.get('php/server/query_json.php', {
@@ -256,25 +248,27 @@
// update readonly fields
handleReadOnly(settingsData, disabledFields);
};
};
// console.log(relevantSettings)
// console.log(relevantSettings)
generateSimpleForm(relevantSettings);
generateSimpleForm(relevantSettings);
toggleNetworkConfiguration(mac == 'Internet')
toggleNetworkConfiguration(mac == 'Internet')
initSelect2();
initHoverNodeInfo();
initSelect2();
initHoverNodeInfo();
hideSpinner();
hideSpinner();
})
})
}, 100);
});
}, 100);
});
}
}
// ----------------------------------------
@@ -290,18 +284,6 @@
});
}
// ----------------------------------------
// Show the description of a setting
function showDescriptionPopup(e) {
console.log($(e).attr("my-set-key"));
showModalOK("Info", getString($(e).attr("my-set-key") + '_description'))
}
// -----------------------------------------------------------------------------
// Save device data to DB
function setDeviceData(direction = '', refreshCallback = '') {

View File

@@ -443,6 +443,37 @@
}
// init first time
initNmapButtons();
initCopyFromDevice();
// -----------------------------------------------------------
var toolsPageInitialized = false;
function initDeviceToolsPage()
{
// Only proceed if .panTools is visible
if (!$('#panTools:visible').length) {
return; // exit early if nothing is visible
}
// init page once
if (toolsPageInitialized) return;
toolsPageInitialized = true;
initNmapButtons();
initCopyFromDevice();
hideSpinner();
}
// -----------------------------------------------------------------------------
// Recurring function to monitor the URL and reinitialize if needed
function deviceToolsPageUpdater() {
initDeviceToolsPage();
// Run updater again after delay
setTimeout(deviceToolsPageUpdater, 200);
}
// start updater
deviceToolsPageUpdater();
</script>

View File

@@ -503,36 +503,36 @@ function collectFilters() {
function mapColumnIndexToFieldName(index, tableColumnVisible) {
// the order is important, don't change it!
const columnNames = [
"devName",
"devOwner",
"devType",
"devIcon",
"devFavorite",
"devGroup",
"devFirstConnection",
"devLastConnection",
"devLastIP",
"devIsRandomMac", // resolved on the fly
"devStatus", // resolved on the fly
"devMac",
"devIpLong", //formatIPlong(device.devLastIP) || "", // IP orderable
"rowid",
"devParentMAC",
"devParentChildrenCount", // resolved on the fly
"devLocation",
"devVendor",
"devParentPort",
"devGUID",
"devSyncHubNode",
"devSite",
"devSSID",
"devSourcePlugin",
"devPresentLastScan",
"devAlertDown",
"devCustomProps",
"devFQDN",
"devParentRelType",
"devReqNicsOnline"
"devName", // 0
"devOwner", // 1
"devType", // 2
"devIcon", // 3
"devFavorite", // 4
"devGroup", // 5
"devFirstConnection", // 6
"devLastConnection", // 7
"devLastIP", // 8
"devIsRandomMac", // 9 resolved on the fly
"devStatus", // 10 resolved on the fly
"devMac", // 11
"devIpLong", // 12 formatIPlong(device.devLastIP) || "", // IP orderable
"rowid", // 13
"devParentMAC", // 14
"devParentChildrenCount", // 15 resolved on the fly
"devLocation", // 16
"devVendor", // 17
"devParentPort", // 18
"devGUID", // 19
"devSyncHubNode", // 20
"devSite", // 21
"devSSID", // 22
"devSourcePlugin", // 23
"devPresentLastScan", // 24
"devAlertDown", // 25
"devCustomProps", // 26
"devFQDN", // 27
"devParentRelType", // 28
"devReqNicsOnline" // 29
];
// console.log("OrderBy: " + columnNames[tableColumnOrder[index]]);
@@ -683,11 +683,19 @@ function initializeDatatable (status) {
return JSON.stringify(query); // Send the JSON request
},
"dataSrc": function (json) {
// Set the total number of records for pagination
json.recordsTotal = json.devices.count || 0;
json.recordsFiltered = json.devices.count || 0;
"dataSrc": function (res) {
console.log("Raw response:", res);
const json = res["data"];
// Set the total number of records for pagination at the *root level* so DataTables sees them
res.recordsTotal = json.devices.count || 0;
res.recordsFiltered = json.devices.count || 0;
// console.log("recordsTotal:", res.recordsTotal, "recordsFiltered:", res.recordsFiltered);
// console.log("tableRows:", tableRows);
// Return only the array of rows for the table
return json.devices.devices.map(device => {
// Convert each device record into the required DataTable row format
// Order has to be the same as in the UI_device_columns setting options
@@ -701,10 +709,10 @@ function initializeDatatable (status) {
device.devFirstConnection || "",
device.devLastConnection || "",
device.devLastIP || "",
device.devIsRandomMac || "", // Custom logic for randomized MAC
device.devIsRandomMac || "",
device.devStatus || "",
device.devMac || "", // hidden
device.devIpLong || "", // IP orderable
device.devMac || "",
device.devIpLong || "",
device.rowid || "",
device.devParentMAC || "",
device.devParentChildrenCount || 0,
@@ -729,7 +737,6 @@ function initializeDatatable (status) {
for (let index = 0; index < tableColumnOrder.length; index++) {
newRow.push(originalRow[tableColumnOrder[index]]);
}
return newRow;
});
}
@@ -899,6 +906,28 @@ function initializeDatatable (status) {
}
} },
// Parent Mac
{targets: [mapIndx(14)],
'createdCell': function (td, cellData, rowData, row, col) {
if (!isValidMac(cellData)) {
$(td).html('');
return;
}
const data = {
id: cellData, // MAC address
text: cellData // Optional display text (you could use a name or something else)
};
spanWrap = $(`<span class="custom-badge text-white"></span>`)
$(td).html(spanWrap);
const chipHtml = renderDeviceLink(data, spanWrap, true); // pass the td as container
$(spanWrap).append(chipHtml);
}
},
// Status color
{targets: [mapIndx(10)],
'createdCell': function (td, cellData, rowData, row, col) {
@@ -982,9 +1011,7 @@ function initializeDatatable (status) {
}
});
});
}

View File

@@ -4,7 +4,7 @@
"display": "standalone",
"icons": [
{
"src": "",
"src": "/img/NetAlertX_logo.png",
"sizes": "180x180",
"type": "image/png"
}

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", "uk_ua"]; // needs to be same as in lang.php
const allLanguages = ["en_us", "es_es", "de_de", "fr_fr", "it_it", "ru_ru", "nb_no", "pl_pl", "pt_br", "pt_pt", "tr_tr", "zh_cn", "cs_cz", "ar_ar", "ca_ca", "uk_ua"]; // needs to be same as in lang.php
var settingsJSON = {}
@@ -328,6 +328,9 @@ function getLangCode() {
case 'Portuguese (pt_br)':
lang_code = 'pt_br';
break;
case 'Portuguese (pt_pt)':
lang_code = 'pt_pt';
break;
case 'Turkish (tr_tr)':
lang_code = 'tr_tr';
break;
@@ -364,75 +367,70 @@ function getLangCode() {
// -----------------------------------------------------------------------------
function localizeTimestamp(input) {
let tz = getSetting("TIMEZONE") || 'Europe/Berlin';
// Convert to string and trim
input = String(input || '').trim();
// Normalize multiple spaces and remove commas
const cleaned = input.replace(',', ' ').replace(/\s+/g, ' ');
// DD/MM/YYYY format check
const dateTimeParts = cleaned.split(' ');
if (dateTimeParts.length >= 2 && dateTimeParts[0].includes('/')) {
const [day, month, year] = dateTimeParts[0].split('/');
const timePart = dateTimeParts[1];
if (day && month && year && timePart) {
const isoString = `${year}-${month}-${day}T${timePart.length === 5 ? timePart + ':00' : timePart}`;
const date = new Date(isoString);
if (!isFinite(date)) return 'b-';
return new Intl.DateTimeFormat('default', {
timeZone: tz,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
}).format(date);
}
}
// ISO style YYYY-MM-DD HH:mm(:ss)?
const match = cleaned.match(/^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2})(:\d{2})?$/);
if (match) {
let iso = `${match[1]}T${match[2]}${match[3] || ':00'}`;
const date = new Date(iso);
if (!isFinite(date)) return 'c-';
// ✅ 1. Unix timestamps (10 or 13 digits)
if (/^\d+$/.test(input)) {
const ms = input.length === 10 ? parseInt(input, 10) * 1000 : parseInt(input, 10);
return new Intl.DateTimeFormat('default', {
timeZone: tz,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit', second: '2-digit',
hour12: false
}).format(new Date(ms));
}
// ✅ 2. European DD/MM/YYYY
let match = input.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:[ ,]+(\d{1,2}:\d{2}(?::\d{2})?))?(.*)$/);
if (match) {
let [ , d, m, y, t = "00:00:00", tzPart = "" ] = match;
const iso = `${y}-${m.padStart(2,'0')}-${d.padStart(2,'0')}T${t.length===5?t+":00":t}${tzPart}`;
return formatSafe(iso, tz);
}
// ✅ 3. US MM/DD/YYYY
match = input.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:[ ,]+(\d{1,2}:\d{2}(?::\d{2})?))?(.*)$/);
if (match) {
let [ , m, d, y, t = "00:00:00", tzPart = "" ] = match;
const iso = `${y}-${m.padStart(2,'0')}-${d.padStart(2,'0')}T${t.length===5?t+":00":t}${tzPart}`;
return formatSafe(iso, tz);
}
// ✅ 4. ISO-style (with T, Z, offsets)
match = input.match(/^(\d{4}-\d{1,2}-\d{1,2})[ T](\d{1,2}:\d{2}(?::\d{2})?)(Z|[+-]\d{2}:?\d{2})?$/);
if (match) {
let [ , ymd, time, offset = "" ] = match;
// normalize to YYYY-MM-DD
let [y, m, d] = ymd.split('-').map(x => x.padStart(2,'0'));
const iso = `${y}-${m}-${d}T${time.length===5?time+":00":time}${offset}`;
return formatSafe(iso, tz);
}
// ✅ 5. RFC2822 / "25 Aug 2025 13:45:22 +0200"
match = input.match(/^\d{1,2} [A-Za-z]{3,} \d{4}/);
if (match) {
return formatSafe(input, tz);
}
// ✅ 6. Fallback (whatever Date() can parse)
return formatSafe(input, tz);
function formatSafe(str, tz) {
const date = new Date(str);
if (!isFinite(date)) {
console.error(`ERROR: Couldn't parse date: '${str}' with TIMEZONE ${tz}`);
return 'Failed conversion - Check browser console';
}
return new Intl.DateTimeFormat('default', {
timeZone: tz,
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit', second: '2-digit',
hour12: false
}).format(date);
}
// Fallback: try to parse any other string input
const date = new Date(input);
if (!isFinite(date)) return 'Failed conversion: ' + input;
return new Intl.DateTimeFormat('default', {
timeZone: tz,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
}).format(date);
}
// ----------------------------------------------------
/**
* Replaces double quotes within single-quoted strings, then converts all single quotes to double quotes,
@@ -609,7 +607,7 @@ function createDeviceLink(input)
{
if(checkMacOrInternet(input))
{
return `<span class="anonymizeMac"><a href="/deviceDetails.php?mac=${input}" target="_blank">${getNameByMacAddress(input)}</a><span>`
return `<span class="anonymizeMac"><a href="/deviceDetails.php?mac=${input}" target="_blank">${getDevDataByMac(input, "devName")}</a><span>`
}
return input;
@@ -813,7 +811,6 @@ function forceLoadUrl(relativeUrl) {
}
// -----------------------------------------------------------------------------
function navigateToDeviceWithIp (ip) {
@@ -836,11 +833,6 @@ function navigateToDeviceWithIp (ip) {
});
}
// -----------------------------------------------------------------------------
function getNameByMacAddress(macAddress) {
return getDevDataByMac(macAddress, "devName")
}
// -----------------------------------------------------------------------------
// Check if MAC or Internet
function checkMacOrInternet(inputStr) {
@@ -1013,7 +1005,7 @@ function getDevDataByMac(macAddress, dbColumn) {
if (!devicesCache || devicesCache == "") {
console.error(`Session variable "${sessionDataKey}" not found.`);
return "Unknown";
return null;
}
const devices = JSON.parse(devicesCache);
@@ -1032,7 +1024,8 @@ function getDevDataByMac(macAddress, dbColumn) {
}
}
return "Unknown"; // Return a default value if MAC address is not found
console.error("⚠ Device with MAC not found:" + macAddress)
return null; // Return a default value if MAC address is not found
}
// -----------------------------------------------------------------------------
@@ -1112,38 +1105,92 @@ let animationTime = 300
function showSpinner(stringKey = 'Loading') {
const text = isEmpty(stringKey) ? "Loading" : getString(stringKey || "Loading");
const spinner = $("#loadingSpinner");
if (spinner.length && spinner.is(':visible')) {
clearTimeout(spinnerTimeout);
$("#loadingSpinnerText").text(text);
spinner.addClass("visible");
const target = $(".spinnerTarget").first(); // Only use the first one if multiple exist
spinner.fadeIn(animationTime);
$("#loadingSpinnerText").text(text);
if (target.length) {
// Position relative to target
const offset = target.offset();
const width = target.outerWidth();
const height = target.outerHeight();
spinner.css({
position: "absolute",
top: offset.top,
left: offset.left,
width: width,
height: height,
zIndex: 800
});
} else {
$("#loadingSpinnerText").text(text);
requestAnimationFrame(() => {
spinner.addClass("visible");
spinner.fadeIn(animationTime);
// Fullscreen fallback
spinner.css({
position: "fixed",
top: 0,
left: 0,
width: "100%",
height: "100%",
zIndex: 800
});
}
requestAnimationFrame(() => {
spinner.addClass("visible");
spinner.fadeIn(animationTime);
});
}
function hideSpinner() {
clearTimeout(spinnerTimeout);
const spinner = $("#loadingSpinner");
if (spinner.length) {
spinner.removeClass("visible");
spinner.fadeOut(animationTime);
if (!spinner.length) return;
spinnerTimeout = setTimeout(() => {
spinner.removeClass("visible");
spinner.fadeOut(animationTime); // optional remove or hide again
}, 300);
const target = $(".spinnerTarget").first();
if (target.length) {
// Lock position to target
const offset = target.offset();
const width = target.outerWidth();
const height = target.outerHeight();
spinner.css({
position: "absolute",
top: offset.top,
left: offset.left,
width: width,
height: height,
zIndex: 800
});
} else {
// Fullscreen fallback
spinner.css({
position: "fixed",
top: 0,
left: 0,
width: "100%",
height: "100%",
zIndex: 800
});
}
// Trigger fade-out and only remove styles AFTER fade completes AND display is none
spinner.removeClass("visible").fadeOut(animationTime, () => {
// Ensure it's really hidden before resetting styles
spinner.css({
display: "none"
});
spinner.css({
position: "",
top: "",
left: "",
width: "",
height: "",
zIndex: ""
});
});
}

View File

@@ -161,6 +161,139 @@ function showModalFieldInput(
$(`#${prefix}`).modal("show");
}
// -----------------------------------------------------------------------------
function showModalPopupForm(
title,
message,
btnCancel = getString("Gen_Cancel"),
btnOK = getString("Gen_Okay"),
curValue = null,
popupFormJson = null,
parentSettingKey = null,
triggeredBy = null
) {
// set captions
prefix = "modal-form";
console.log(popupFormJson);
$(`#${prefix}-title`).html(title);
$(`#${prefix}-message`).html(message);
$(`#${prefix}-cancel`).html(btnCancel);
$(`#${prefix}-OK`).html(btnOK);
// if curValue not null
if (curValue)
{
initialValues = JSON.parse(atob(curValue));
}
outputHtml = "";
if (Array.isArray(popupFormJson)) {
popupFormJson.forEach((field, index) => {
// You'll need to define these or map them from `field`
const setKey = field.function || `field_${index}`;
const setName = getString(`${parentSettingKey}_popupform_${setKey}_name`);
const labelClasses = "col-sm-2"; // example, or from your obj.labelClasses
const inputClasses = "col-sm-10"; // example, or from your obj.inputClasses
let initialValue = '';
if (curValue && Array.isArray(initialValues)) {
const match = initialValues.find(
v => v[1] == setKey
);
if (match) {
initialValue = match[3];
}
}
const fieldOptionsOverride = field.type?.elements[0]?.elementOptions || [];
const setValue = initialValue;
const setType = JSON.stringify(field.type);
const setEvents = field.events || []; // default to empty array if missing
const setObj = { setKey, setValue, setType, setEvents };
// Generate the input field HTML
const inputFormHtml = `
<div class="form-group col-xs-12">
<label id="${setKey}_label" class="${labelClasses}"> ${setName}
<i my-set-key="${parentSettingKey}_popupform_${setKey}"
title="${getString("Settings_Show_Description")}"
class="fa fa-circle-info pointer helpIconSmallTopRight"
onclick="showDescriptionPopup(this)">
</i>
</label>
<div class="${inputClasses}">
${generateFormHtml(
null, // settingsData only required for datatables
setObj,
null,
fieldOptionsOverride,
null
)}
</div>
</div>
`;
// Append to result
outputHtml += inputFormHtml;
});
}
$(`#modal-form-plc`).html(outputHtml);
// Bind OK button click event
$(`#${prefix}-OK`).off("click").on("click", function() {
let settingsArray = [];
if (Array.isArray(popupFormJson)) {
popupFormJson.forEach(field => {
collectSetting(
`${parentSettingKey}_popupform`, // prefix
field.function, // setCodeName
field.type, // setType (object)
settingsArray
);
});
}
// Encode settings
const jsonData = JSON.stringify(settingsArray);
const encodedValue = btoa(jsonData);
// Get label from the FIRST field (value in 4th column)
const label = settingsArray[0][3]
// Add new option to target select
const selectId = parentSettingKey;
// If triggered by an option, update it; otherwise append new
if (triggeredBy && $(triggeredBy).is("option")) {
// Update existing option
$(triggeredBy)
.attr("value", encodedValue)
.text(label);
} else {
const newOption = $("<option class='interactable-option'></option>")
.attr("value", encodedValue)
.text(label);
$("#" + selectId).append(newOption);
initListInteractionOptions(newOption);
}
console.log("Collected popup form settings:", settingsArray);
if (typeof modalCallbackFunction === "function") {
modalCallbackFunction(settingsArray);
}
$(`#${prefix}`).modal("hide");
});
// Show modal
$(`#${prefix}`).modal("show");
}
// -----------------------------------------------------------------------------
function modalDefaultOK() {
// Hide modal
@@ -224,7 +357,7 @@ function modalWarningOK() {
} else if (typeof modalCallbackFunction === "string" && typeof window[modalCallbackFunction] === "function") {
window[modalCallbackFunction](); // Call via window
} else {
console.error("Invalid callback function");
console.error("Invalid callback function: " + modalCallbackFunction);
}
}, 100);
}

View File

@@ -67,6 +67,15 @@ function getPluginConfig(pluginsData, prefix) {
return result;
}
// ----------------------------------------
// Show the description of a setting
function showDescriptionPopup(e) {
console.log($(e).attr("my-set-key"));
showModalOK("Info", getString($(e).attr("my-set-key") + '_description'))
}
// -------------------------------------------------------------------
// Generate plugin HTML card based on prefixes in an array
function pluginCards(prefixesOfEnabledPlugins, includeSettings) {
@@ -237,13 +246,6 @@ function settingsCollectedCorrectly(settingsArray, settingsJSON_DB) {
// Manipulating Editable List options
// -------------------------------------------------------------------
// ---------------------------------------------------------
// Add row to datatable
function addDataTableRow(el)
{
alert("a")
}
// ---------------------------------------------------------
// Clone datatable row
function cloneDataTableRow(el){
@@ -299,6 +301,33 @@ function removeDataTableRow(el) {
}
}
// ---------------------------------------------------------
// Add item via pop up form dialog
function addViaPopupForm(element) {
console.log(element);
const toId = $(element).attr("my-input-to");
const curValue = $(`#${toId}`).val();
const parsed = JSON.parse(atob($(`#${toId}`).data("elementoptionsbase64")));
const popupFormJson = parsed.find(obj => "popupForm" in obj)?.popupForm ?? null;
console.log(`toId | curValue: ${toId} | ${curValue}`);
showModalPopupForm(
`<i class="fa-solid fa-square-plus"></i> ${getString("Gen_Add")}`, // title
"", // message
getString("Gen_Cancel"), // btnCancel
getString("Gen_Add"), // btnOK
null, // curValue
popupFormJson, // popupform
toId, // parentSettingKey
element // triggeredBy
);
// flag something changes to prevent navigating from page
settingsChanged();
}
// ---------------------------------------------------------
// Add item to list
function addList(element, clearInput = true) {
@@ -427,18 +456,41 @@ function initListInteractionOptions(element) {
// Perform action based on click count
if (clickCounter === 1) {
// Single-click action
showModalFieldInput(
`<i class="fa fa-pen-to-square"></i> ${getString(
"Gen_Update_Value"
)}`,
getString("settings_update_item_warning"),
getString("Gen_Cancel"),
getString("Gen_Update"),
$option.html(),
function () {
updateOptionItem($option, $(`#modal-field-input-field`).val());
}
);
const $parent = $option.parent();
const transformers = $parent.attr("my-transformers");
if (transformers && transformers === "name|base64") {
// Parent has my-transformers="name|base64"
const toId = $parent.attr("id");
const curValue = $option.val();
const parsed = JSON.parse(atob($parent.data("elementoptionsbase64")));
const popupFormJson = parsed.find(obj => "popupForm" in obj)?.popupForm ?? null;
showModalPopupForm(
`<i class="fa fa-pen-to-square"></i> ${getString("Gen_Update_Value")}`, // title
"", // message
getString("Gen_Cancel"), // btnCancel
getString("Gen_Update"), // btnOK
curValue, // curValue
popupFormJson, // popupform
toId, // parentSettingKey
this // triggeredBy
);
} else {
// Fallback to normal field input
showModalFieldInput(
`<i class="fa fa-pen-to-square"></i> ${getString("Gen_Update_Value")}`,
getString("settings_update_item_warning"),
getString("Gen_Cancel"),
getString("Gen_Update"),
$option.html(),
function () {
updateOptionItem($option, $(`#modal-field-input-field`).val());
}
);
}
} else if (clickCounter === 2) {
// Double-click action
removeOptionItem($option);
@@ -622,8 +674,6 @@ function generateOptionsOrSetOptions(
// obj.push({ id: item, name: item })
options = arrayToObject(createArray(overrideOptions ? overrideOptions : getSettingOptions(setKey)))
// Call to render lists
renderList(
options,
@@ -633,8 +683,6 @@ function generateOptionsOrSetOptions(
targetField,
transformers
);
}
@@ -655,6 +703,13 @@ function applyTransformers(val, transformers) {
val = btoa(val);
}
break;
case "name|base64":
// // Implement base64 logic
// if (!isBase64(val)) {
// val = btoa(val);
// }
val = val; // probably TODO ⚠
break;
case "getString":
// no change
val = val;
@@ -667,7 +722,7 @@ function applyTransformers(val, transformers) {
}
// ------------------------------------------------------------
// Function to reverse transformers applied to a value
// Function to reverse transformers applied to a value - returns the LABEL
function reverseTransformers(val, transformers) {
transformers.reverse().forEach((transformer) => {
switch (transformer) {
@@ -681,6 +736,13 @@ function reverseTransformers(val, transformers) {
val = atob(val);
}
break;
case "name|base64":
// Implement base64 decoding logic
if (isBase64(val)) {
val = JSON.parse(atob(val))[0][3];
}
val = val; // probably TODO ⚠
break;
case "getString":
// retrieve string
val = getString(val);
@@ -720,8 +782,8 @@ const handleElementOptions = (setKey, elementOptions, transformers, val) => {
let customParams = "";
let customId = "";
let columns = [];
let base64Regex = "";
let base64Regex = "";
let elementOptionsBase64 = btoa(JSON.stringify(elementOptions));
elementOptions.forEach((option) => {
if (option.prefillValue) {
@@ -804,7 +866,8 @@ const handleElementOptions = (setKey, elementOptions, transformers, val) => {
customParams,
customId,
columns,
base64Regex
base64Regex,
elementOptionsBase64
};
};
@@ -934,13 +997,102 @@ function genListWithInputSet(options, valuesArray, targetField, transformers, pl
$("#" + placeholder).replaceWith(listHtml);
}
// -----------------------------------------------------------------
// Collects a setting based on code name
function collectSetting(prefix, setCodeName, setType, settingsArray) {
// Parse setType if it's a JSON string
const setTypeObject = (typeof setType === "string")
? JSON.parse(processQuotes(setType))
: setType;
const dataType = setTypeObject.dataType;
// Pick element with input value
let elements = setTypeObject.elements.filter(el => el.elementHasInputValue === 1);
let elementWithInputValue = elements.length === 0
? setTypeObject.elements[setTypeObject.elements.length - 1]
: elements[0];
const { elementType, elementOptions = [], transformers = [] } = elementWithInputValue;
const opts = handleElementOptions('none', elementOptions, transformers, val = "");
// Map of handlers
const handlers = {
datatableString: () => {
const value = collectTableData(`#${setCodeName}_table`);
return btoa(JSON.stringify(value));
},
simpleValue: () => {
let value = $(`#${setCodeName}`).val();
return applyTransformers(value, transformers);
},
checkbox: () => {
let value = $(`#${setCodeName}`).is(':checked') ? 1 : 0;
if (dataType === "boolean") {
value = value === 1 ? "True" : "False";
}
return applyTransformers(value, transformers);
},
array: () => {
let temps = [];
if (opts.isOrdeable) {
temps = $(`#${setCodeName}`).val();
} else {
const sel = $(`#${setCodeName}`).attr("my-editable") === "true" ? "" : ":selected";
$(`#${setCodeName} option${sel}`).each(function() {
const vl = $(this).val();
if (vl !== '') {
temps.push(applyTransformers(vl, transformers));
}
});
}
return JSON.stringify(temps);
},
none: () => "",
json: () => {
let value = $(`#${setCodeName}`).val();
value = applyTransformers(value, transformers);
return JSON.stringify(value, null, 2);
},
fallback: () => {
console.error(`[collectSetting] Couldn't determine how to handle (${setCodeName}|${dataType}|${opts.inputType})`);
let value = $(`#${setCodeName}`).val();
return applyTransformers(value, transformers);
}
};
// Select handler key
let handlerKey;
if (dataType === "string" && elementType === "datatable") {
handlerKey = "datatableString";
} else if (dataType === "string" ||
(dataType === "integer" && (opts.inputType === "number" || opts.inputType === "text"))) {
handlerKey = "simpleValue";
} else if (opts.inputType === "checkbox") {
handlerKey = "checkbox";
} else if (dataType === "array") {
handlerKey = "array";
} else if (dataType === "none") {
handlerKey = "none";
} else if (dataType === "json") {
handlerKey = "json";
} else {
handlerKey = "fallback";
}
const value = handlers[handlerKey]();
settingsArray.push([prefix, setCodeName, dataType, value]);
return settingsArray;
}
// ------------------------------------------------------------------------------
// Generate the form control for setting
function generateFormHtml(settingsData, set, overrideValue, overrideOptions, originalSetKey) {
let inputHtml = '';
isEmpty(overrideValue) ? inVal = set['setValue'] : inVal = overrideValue;
const setKey = set['setKey'];
const setType = set['setType'];
@@ -955,6 +1107,8 @@ function generateFormHtml(settingsData, set, overrideValue, overrideOptions, ori
// }
// Parse the setType JSON string
// console.log(processQuotes(setType));
const setTypeObject = JSON.parse(processQuotes(setType))
const dataType = setTypeObject.dataType;
const elements = setTypeObject.elements || [];
@@ -982,7 +1136,8 @@ function generateFormHtml(settingsData, set, overrideValue, overrideOptions, ori
customParams,
customId,
columns,
base64Regex
base64Regex,
elementOptionsBase64
} = handleElementOptions(setKey, elementOptions, transformers, inVal);
// Override value
@@ -1014,6 +1169,7 @@ function generateFormHtml(settingsData, set, overrideValue, overrideOptions, ori
my-customparams="${customParams}"
my-customid="${customId}"
my-originalSetKey="${originalSetKey}"
data-elementoptionsbase64="${elementOptionsBase64}"
${multi}
${readOnly ? "disabled" : ""}>
<option value="" id="${setKey + "_temp_"}"></option>
@@ -1051,6 +1207,7 @@ function generateFormHtml(settingsData, set, overrideValue, overrideOptions, ori
my-originalSetKey="${originalSetKey}"
my-input-from="${sourceIds}"
my-input-to="${setKey}"
data-elementoptionsbase64="${elementOptionsBase64}"
onclick="${onClick}">
${getString(getStringKey)}
</button>`;

View File

@@ -336,7 +336,8 @@ function execute_settingEvent(element) {
getString('DevDetail_button_OverwriteIcons_Warning'),
getString('Gen_Cancel'),
getString('Gen_Okay'),
'overwriteIconType'
'overwriteIconType',
feSourceId // triggered by id
);
} else if (["go_to_device"].includes(feEvent)) {
@@ -347,9 +348,43 @@ function execute_settingEvent(element) {
} else {
console.warn(`🔺Not implemented: ${feEvent}`)
}
}
// -----------------------------------------------------------------------------
// Go to the correct network node in the Network section
function overwriteIconType()
{
const mac = getMac();
if (!isValidMac(mac)) {
showModalOK("Error", getString("Gen_InvalidMac"))
return;
}
// Construct SQL query
const rawSql = `
UPDATE Devices
SET devIcon = (
SELECT devIcon FROM Devices WHERE devMac = "${mac}"
)
WHERE devType IN (
SELECT devType FROM Devices WHERE devMac = "${mac}"
)
`;
const apiUrl = `php/server/dbHelper.php?action=write&rawSql=${btoa(encodeURIComponent(rawSql))}`;
$.get(apiUrl, function(response) {
if (response === 'OK') {
showMessage (response);
updateApi("devices")
} else {
showMessage (response, 3000, "modal_red");
}
});
}
@@ -680,45 +715,7 @@ function initSelect2() {
{
var selectEl = $(this).select2({
templateSelection: function (data, container) {
if (!data.id) return data.text; // default for placeholder etc.
const device = getDevDataByMac(data.id);
const badge = getStatusBadgeParts(
device.devPresentLastScan,
device.devAlertDown,
device.devMac
)
$(container).addClass(badge.cssClass);
// Custom HTML
const html = $(`
<a href="${badge.url}" target="_blank">
<span class="custom-chip hover-node-info"
data-name="${device.devName}"
data-ip="${device.devLastIP}"
data-mac="${device.devMac}"
data-vendor="${device.devVendor}"
data-type="${device.devType}"
data-lastseen="${device.devLastConnection}"
data-firstseen="${device.devFirstConnection}"
data-relationship="${device.devParentRelType}"
data-status="${device.devStatus}"
data-present="${device.devPresentLastScan}"
data-alert="${device.devAlertDown}"
data-icon="${device.devIcon}"
>
<span class="iconPreview">${atob(device.devIcon)}</span>
${data.text}
<span>
(${badge.iconHtml})
</span
</span>
</a>
`);
return html;
return $(renderDeviceLink(data, container));
},
escapeMarkup: function (m) {
return m; // Allow HTML
@@ -782,6 +779,57 @@ function initSelect2() {
}
}
// ------------------------------------------
// Render a device link with hover-over functionality
function renderDeviceLink(data, container, useName = false) {
// If no valid MAC, return placeholder text
if (!data.id || !isValidMac(data.id)) {
return `<span>${data.text}<span/>`;
}
const device = getDevDataByMac(data.id);
if (!device) {
return data.text;
}
// Build and return badge parts
const badge = getStatusBadgeParts(
device.devPresentLastScan,
device.devAlertDown,
device.devMac
);
// badge class and hover-info class to container
$(container)
.addClass(`${badge.cssClass} hover-node-info`)
.attr({
'data-name': device.devName,
'data-ip': device.devLastIP,
'data-mac': device.devMac,
'data-vendor': device.devVendor,
'data-type': device.devType,
'data-lastseen': device.devLastConnection,
'data-firstseen': device.devFirstConnection,
'data-relationship': device.devParentRelType,
'data-status': device.devStatus,
'data-present': device.devPresentLastScan,
'data-alert': device.devAlertDown,
'data-icon': device.devIcon
});
return `
<a href="${badge.url}" target="_blank">
<span class="custom-chip">
<span class="iconPreview">${atob(device.devIcon)}</span>
${useName ? device.devName : data.text}
<span>
(${badge.iconHtml})
</span>
</span>
</a>
`;
}
// ------------------------------------------
// Display device info on hover (attach only once)
function initHoverNodeInfo() {

View File

@@ -1,6 +1,6 @@
<?php
require 'php/templates/header.php';
require 'php/templates/notification.php';
require 'php/templates/modals.php';
?>
<!-- Page ------------------------------------------------------------------ -->
@@ -8,7 +8,10 @@
<!-- Main content ---------------------------------------------------------- -->
<section class="content">
<script>
showSpinner();
</script>
<?php
@@ -143,7 +146,7 @@ $db->close();
</a>
</li>
</ul>
<div class="tab-content">
<div class="tab-content spinnerTarget">
<div class="tab-pane active" id="tab_DBTools">
<div class="db_info_table">
<div class="db_info_table_row">
@@ -182,6 +185,12 @@ $db->close();
</div>
<div class="db_tools_table_cell_b"><?= lang('Maintenance_Tool_del_ActHistory_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="btnRestartServer" onclick="askRestartBackend()"><?= lang('Maint_RestartServer');?></button>
</div>
<div class="db_tools_table_cell_b"><?= lang('Maint_Restart_Server_noti_text');?></div>
</div>
</div>
</div>
@@ -709,6 +718,8 @@ window.onload = function asyncFooter() {
</script>
<script>
hideSpinner();
</script>

View File

@@ -136,7 +136,8 @@
customParams,
customId,
columns,
base64Regex
base64Regex,
elementOptionsBase64
} = handleElementOptions('none', elementOptions, transformers, val = "");
// render based on element type

View File

@@ -1,6 +1,6 @@
<?php
require 'php/templates/header.php';
require 'php/templates/notification.php';
require 'php/templates/modals.php';
?>
<script>
@@ -197,7 +197,7 @@
<div class="col-sm-9">
${isRootNode ? '' : `<a class="anonymize" href="#">`}
<span my-data-mac="${node.parent_mac}" data-mac="${node.parent_mac}" data-devIsNetworkNodeDynamic="1" onclick="handleNodeClick(this)">
${isRootNode ? getString('Network_Root') : getNameByMacAddress(node.parent_mac)}
${isRootNode ? getString('Network_Root') : getDevDataByMac(node.parent_mac, "devName")}
</span>
${isRootNode ? '' : `</a>`}
</div>

View File

@@ -7,6 +7,10 @@
# Puche 2022+ jokob jokob@duck.com GNU GPLv3
//------------------------------------------------------------------------------
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
// check server/api_server/api_server_start.py for equivalents
// equivalent: /dbquery
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
//------------------------------------------------------------------------------
// External files
@@ -76,6 +80,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
switch ($action) {
case 'create': create($defaultValue, $expireMinutes, $dbtable, $columns, $values ); break;
case 'read' : read($rawSql); break;
case 'write' : write($rawSql); break;
case 'update': update($columnName, $id, $defaultValue, $expireMinutes, $dbtable, $columns, $values); break;
case 'delete': delete($columnName, $id, $dbtable); break;
case 'lockDatabase': lockDatabase($delay); break;
@@ -120,6 +125,31 @@ function read($rawSql) {
}
}
//------------------------------------------------------------------------------
// write
//------------------------------------------------------------------------------
function write($rawSql) {
global $db;
// Construct the SQL query to select values
$sql = $rawSql;
// Execute the SQL query
$result = $db->query($sql);
// Check if the query executed successfully
if (! $result == TRUE) {
// Output an error message if the query failed
echo "Error writing data\n\n " .$sql." \n\n". $db->lastErrorMsg();
return;
} else
{
// Output
echo "OK";
return;
}
}
//------------------------------------------------------------------------------
// update

View File

@@ -8,6 +8,10 @@
# Puche 2021 / 2022+ jokob jokob@duck.com GNU GPLv3
//------------------------------------------------------------------------------
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
// check server/api_server/api_server_start.py for equivalents
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
// External files
require dirname(__FILE__).'/init.php';
@@ -26,36 +30,31 @@
if (isset ($_REQUEST['action']) && !empty ($_REQUEST['action'])) {
$action = $_REQUEST['action'];
switch ($action) {
case 'getServerDeviceData': getServerDeviceData(); break;
case 'setDeviceData': setDeviceData(); break;
case 'deleteDevice': deleteDevice(); break;
case 'deleteAllWithEmptyMACs': deleteAllWithEmptyMACs(); break;
// check server/api_server/api_server_start.py for equivalents
case 'getServerDeviceData': getServerDeviceData(); break; // equivalent: get_device_data
case 'setDeviceData': setDeviceData(); break; // equivalent: set_device_data
case 'deleteDevice': deleteDevice(); break; // equivalent: delete_device(mac)
case 'deleteAllWithEmptyMACs': deleteAllWithEmptyMACs(); break; // equivalent: delete_all_with_empty_macs
case 'deleteAllDevices': deleteAllDevices(); break;
case 'deleteUnknownDevices': deleteUnknownDevices(); break;
case 'deleteEvents': deleteEvents(); break;
case 'deleteEvents30': deleteEvents30(); break;
case 'deleteActHistory': deleteActHistory(); break;
case 'deleteDeviceEvents': deleteDeviceEvents(); break;
case 'resetDeviceProps': resetDeviceProps(); break;
case 'PiaBackupDBtoArchive': PiaBackupDBtoArchive(); break;
case 'PiaRestoreDBfromArchive': PiaRestoreDBfromArchive(); break;
case 'PiaPurgeDBBackups': PiaPurgeDBBackups(); break;
case 'ExportCSV': ExportCSV(); break;
case 'ImportCSV': ImportCSV(); break;
case 'deleteAllDevices': deleteAllDevices(); break; // equivalent: delete_devices(macs)
case 'deleteUnknownDevices': deleteUnknownDevices(); break; // equivalent: delete_unknown_devices
case 'deleteEvents': deleteEvents(); break; // equivalent: delete_events
case 'deleteEvents30': deleteEvents30(); break; // equivalent: delete_events_30
case 'deleteActHistory': deleteActHistory(); break; // equivalent: delete_online_history
case 'deleteDeviceEvents': deleteDeviceEvents(); break; // equivalent: delete_device_events(mac)
case 'resetDeviceProps': resetDeviceProps(); break; // equivalent: reset_device_props
case 'ExportCSV': ExportCSV(); break; // equivalent: export_devices
case 'ImportCSV': ImportCSV(); break; // equivalent: import_csv
case 'getDevicesTotals': getDevicesTotals(); break;
case 'getDevicesListCalendar': getDevicesListCalendar(); break; //todo: slowly deprecate this
case 'getDevicesTotals': getDevicesTotals(); break; // equivalent: devices_totals
case 'getDevicesListCalendar': getDevicesListCalendar(); break; // equivalent: devices_by_status
case 'updateNetworkLeaf': updateNetworkLeaf(); break;
case 'overwriteIconType': overwriteIconType(); break;
case 'getIcons': getIcons(); break;
case 'getActions': getActions(); break;
case 'getDevices': getDevices(); break;
case 'copyFromDevice': copyFromDevice(); break;
case 'wakeonlan': wakeonlan(); break;
case 'updateNetworkLeaf': updateNetworkLeaf(); break; // equivalent: update_device_column(mac, column_name, column_value)
default: logServerConsole ('Action: '. $action); break;
case 'copyFromDevice': copyFromDevice(); break; // equivalent: copy_device(mac_from, mac_to)
case 'wakeonlan': wakeonlan(); break; // equivalent: wakeonlan
default: logServerConsole ('Action: '. $action); break; // equivalent:
}
}
@@ -518,92 +517,6 @@ function deleteActHistory() {
}
}
//------------------------------------------------------------------------------
// Backup DB to Archiv
//------------------------------------------------------------------------------
function PiaBackupDBtoArchive() {
// prepare fast Backup
$dbfilename = 'app.db';
$file = '../../../db/'.$dbfilename;
$newfile = '../../../db/'.$dbfilename.'.latestbackup';
// copy files as a fast Backup
if (!copy($file, $newfile)) {
echo lang('BackDevices_Backup_CopError');
} else {
// Create archive with actual date
$Pia_Archive_Name = 'appdb_'.date("Ymd_His").'.zip';
$Pia_Archive_Path = '../../../db/';
exec('zip -j '.$Pia_Archive_Path.$Pia_Archive_Name.' ../../../db/'.$dbfilename, $output);
// chheck if archive exists
if (file_exists($Pia_Archive_Path.$Pia_Archive_Name) && filesize($Pia_Archive_Path.$Pia_Archive_Name) > 0) {
echo lang('BackDevices_Backup_okay').': ('.$Pia_Archive_Name.')';
unlink($newfile);
echo("<meta http-equiv='refresh' content='1'>");
} else {
echo lang('BackDevices_Backup_Failed').' ('.$dbfilename.'.latestbackup)';
}
}
}
//------------------------------------------------------------------------------
// Restore DB from Archiv
//------------------------------------------------------------------------------
function PiaRestoreDBfromArchive() {
// prepare fast Backup
$file = '../../../db/'.$dbfilename;
$oldfile = '../../../db/'.$dbfilename.'.prerestore';
// copy files as a fast Backup
if (!copy($file, $oldfile)) {
echo lang('BackDevices_Restore_CopError');
} else {
// extract latest archive and overwrite the actual .db
$Pia_Archive_Path = '../../../db/';
exec('/bin/ls -Art '.$Pia_Archive_Path.'*.zip | /bin/tail -n 1 | /usr/bin/xargs -n1 /bin/unzip -o -d ../../../db/', $output);
// check if the .db exists
if (file_exists($file)) {
echo lang('BackDevices_Restore_okay');
unlink($oldfile);
echo("<meta http-equiv='refresh' content='1'>");
} else {
echo lang('BackDevices_Restore_Failed');
}
}
}
//------------------------------------------------------------------------------
// Purge Backups
//------------------------------------------------------------------------------
function PiaPurgeDBBackups() {
$Pia_Archive_Path = '../../../db';
$Pia_Backupfiles = array();
$files = array_diff(scandir($Pia_Archive_Path, SCANDIR_SORT_DESCENDING), array('.', '..', $dbfilename, 'netalertxdb-reset.zip'));
foreach ($files as &$item)
{
$item = $Pia_Archive_Path.'/'.$item;
if (stristr($item, 'setting_') == '') {array_push($Pia_Backupfiles, $item);}
}
if (sizeof($Pia_Backupfiles) > 3)
{
rsort($Pia_Backupfiles);
unset($Pia_Backupfiles[0], $Pia_Backupfiles[1], $Pia_Backupfiles[2]);
$Pia_Backupfiles_Purge = array_values($Pia_Backupfiles);
for ($i = 0; $i < sizeof($Pia_Backupfiles_Purge); $i++)
{
unlink($Pia_Backupfiles_Purge[$i]);
}
}
echo lang('BackDevices_DBTools_Purge');
echo("<meta http-equiv='refresh' content='1'>");
}
//------------------------------------------------------------------------------
// Export CSV of devices
//------------------------------------------------------------------------------
@@ -828,75 +741,6 @@ function getDevicesListCalendar() {
// Query Device Data
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
function getIcons() {
global $db;
// Device Data
$sql = 'select devIcon from Devices group by devIcon';
$result = $db->query($sql);
// arrays of rows
$tableData = array();
while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
$icon = handleNull($row['devIcon'], "<i class='fa fa-laptop'></i>");
// Push row data
$tableData[] = array('id' => $icon,
'name' => $icon );
}
// Control no rows
if (empty($tableData)) {
$tableData = [];
}
// Return json
echo (json_encode ($tableData));
}
//------------------------------------------------------------------------------
function getActions() {
$tableData = array(
array('id' => 'wake-on-lan',
'name' => lang('DevDetail_WOL_Title'))
);
// Return json
echo (json_encode ($tableData));
}
//------------------------------------------------------------------------------
function getDevices() {
global $db;
// Device Data
$sql = 'select devMac, devName from Devices';
$result = $db->query($sql);
// arrays of rows
$tableData = array();
while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
$name = handleNull($row['devName'], "(unknown)");
$mac = handleNull($row['devMac'], "(unknown)");
// Push row data
$tableData[] = array('id' => $mac,
'name' => $name );
}
// Control no rows
if (empty($tableData)) {
$tableData = [];
}
// Return json
echo (json_encode ($tableData));
}
// ----------------------------------------------------------------------------------------
function updateNetworkLeaf()
{
@@ -924,33 +768,6 @@ function updateNetworkLeaf()
}
// ----------------------------------------------------------------------------------------
function overwriteIconType()
{
$mac = $_REQUEST['mac'];
$icon = $_REQUEST['icon'];
if ((false === filter_var($mac , FILTER_VALIDATE_MAC) && $mac != "Internet" && $mac != "") ) {
throw new Exception('Invalid mac address');
}
else
{
global $db;
// sql
$sql = 'UPDATE Devices SET "devIcon" = "'. $icon .'" where devType in (select devType from Devices where devMac = "' . $mac.'")' ;
// update Data
$result = $db->query($sql);
// check result
if ($result == TRUE) {
echo 'OK';
} else {
echo lang('BackDevices_Device_UpdDevError');
}
}
}
//------------------------------------------------------------------------------
// Wake-on-LAN
// Inspired by @leiweibau: https://github.com/leiweibau/Pi.Alert/commit/30427c7fea180670c71a2b790699e5d9e9e88ffd

View File

@@ -8,6 +8,12 @@
# Puche 2021 / 2022+ jokob jokob@duck.com GNU GPLv3
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
// check server/api_server/api_server_start.py for equivalents
// equivalent: /sessions /events
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
// External files
require dirname(__FILE__).'/init.php';

View File

@@ -12,6 +12,12 @@
# cvc90 2023 https://github.com/cvc90 GNU GPLv3 #
###################################################################################
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
// check server/api_server/api_server_start.py for equivalents
// equivalent: /nettools
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
// Get init.php
require dirname(__FILE__).'/../server/init.php';

View File

@@ -1,5 +1,10 @@
<?php
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
// check server/api_server/api_server_start.py for equivalents
// equivalent: /nettools
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
require 'util.php';
//------------------------------------------------------------------------------

View File

@@ -12,6 +12,11 @@
# cvc90 2023 https://github.com/cvc90 GNU GPLv3 #
###################################################################################
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
// check server/api_server/api_server_start.py for equivalents
// equivalent: /nettools
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
// Get init.php
require dirname(__FILE__).'/../server/init.php';

View File

@@ -1,6 +1,11 @@
<?php
require dirname(__FILE__).'/../server/init.php';
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
// check server/api_server/api_server_start.py for equivalents
// equivalent: /nettools
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
//------------------------------------------------------------------------------
// check if authenticated
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';

View File

@@ -12,6 +12,11 @@
# cvc90 2023 https://github.com/cvc90 GNU GPLv3 #
###################################################################################
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
// check server/api_server/api_server_start.py for equivalents
// equivalent: /nettools
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
// Get init.php
require dirname(__FILE__).'/../server/init.php';
@@ -19,6 +24,8 @@ require dirname(__FILE__).'/../server/init.php';
// check if authenticated
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
// NEW ENDPOINT EQUIVALENT: /nettools/traceroute
// Get IP
$ip = $_GET['ip'];

View File

@@ -139,8 +139,8 @@
<body class="hold-transition fixed <?php echo $pia_skin_selected;?> theme-<?php echo $UI_THEME;?> sidebar-mini" onLoad="update_servertime();" >
<div id="loadingSpinner">
<div class="pa_semitransparent-panel"></div>
<div class="panel panel-default pa_spinner">
<div class="nax_semitransparent-panel"></div>
<div class="panel panel-default nax_spinner">
<table>
<td id="loadingSpinnerText" width="130px" ></td>
<td><i class="fa-solid fa-spinner fa-spin-pulse"></i></td>
@@ -160,7 +160,7 @@
<a href="devices.php" class="logo">
<!-- mini logo for sidebar mini 50x50 pixels -->
<span class="logo-mini">
<img src="img/NetAlertX_logo.png" class="pia-top-left-logo" alt="NetAlertX Logo"/>
<img src="img/NetAlertX_logo.png" class="top-left-logo" alt="NetAlertX Logo"/>
</span>
<!-- logo for regular state and mobile devices -->
<span class="logo-lg">Net<b>Alert</b><sup>x</sup>
@@ -436,8 +436,24 @@
</li>
<!-- system info menu item -->
<li class=" <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('systeminfo.php') ) ){ echo 'active'; } ?>">
<a href="systeminfo.php"><i class="fa fa-fw fa-info-circle"></i> <span><?= lang('Navigation_SystemInfo');?></span></a>
<li class=" treeview <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('systeminfo.php') ) ){ echo 'active menu-open'; } ?>">
<a href="#">
<i class="fa fa-fw fa-info-circle"></i> <span><?= lang('Navigation_SystemInfo');?></span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu " style="display: <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('systeminfo.php') ) ){ echo 'block'; } else {echo 'none';} ?>;">
<li>
<a href="systeminfo.php#panServer" onclick="setCache('activeSysinfoTab','tabServer');initializeTabs()"><?= lang('Systeminfo_System');?></a>
</li>
<li>
<a href="systeminfo.php#panNetwork" onclick="setCache('activeSysinfoTab','tabNetwork');initializeTabs()"><?= lang('Systeminfo_Network');?></a>
</li>
<li>
<a href="systeminfo.php#panStorage" onclick="setCache('activeSysinfoTab','tabStorage');initializeTabs()"><?= lang('Systeminfo_Storage');?></a>
</li>
</ul>
</li>
</ul>
@@ -450,24 +466,6 @@
<script defer>
// Generate work-in-progress icons
function workInProgress() {
if($(".work-in-progress").length > 0 && $(".work-in-progress").html().trim() == "")
{
$(".work-in-progress").append(`
<a href="https://github.com/jokob-sk/NetAlertX/issues" target="_blank">
<b class="pointer" title="${getString("Gen_Work_In_Progress")}">🦺</b>
</a>
`)
}
}
//--------------------------------------------------------------
//--------------------------------------------------------------
function toggleFullscreen() {
if (document.fullscreenElement) {
@@ -485,6 +483,5 @@ function workInProgress() {
// Update server state in the header
updateState()
workInProgress()
</script>

View File

@@ -301,6 +301,7 @@
"Gen_Cancel": "إلغاء",
"Gen_Change": "تغيير",
"Gen_Copy": "نسخ",
"Gen_CopyToClipboard": "",
"Gen_DataUpdatedUITakesTime": "تم تحديث البيانات. قد يستغرق تحديث واجهة المستخدم بعض الوقت",
"Gen_Delete": "حذف",
"Gen_DeleteAll": "حذف الكل",
@@ -308,6 +309,7 @@
"Gen_Error": "خطأ",
"Gen_Filter": "تصفية",
"Gen_Generate": "إنشاء",
"Gen_InvalidMac": "",
"Gen_LockedDB": "قاعدة البيانات مقفلة",
"Gen_NetworkMask": "",
"Gen_Offline": "غير متصل",

View File

@@ -40,7 +40,7 @@
"BackDevices_Backup_Failed": "La còpia de seguretat s'ha executat parcialment. L'arxiu no es pot crear o està buit.",
"BackDevices_Backup_okay": "La còpia de seguretat s'ha executat en un nou arxiu",
"BackDevices_DBTools_DelDevError_a": "Error esborrant el Dispositiu",
"BackDevices_DBTools_DelDevError_b": "Error esborrant els Dispositius",
"BackDevices_DBTools_DelDevError_b": "Error esborrant dispositius",
"BackDevices_DBTools_DelDev_a": "Dispositiu esborrat",
"BackDevices_DBTools_DelDev_b": "Dispositius esborrats",
"BackDevices_DBTools_DelEvents": "Esdeveniments esborrats",
@@ -66,7 +66,7 @@
"DAYS_TO_KEEP_EVENTS_name": "Esborrar esdeveniments més vells de",
"DISCOVER_PLUGINS_description": "Desactiva aquesta opció per accelerar la inicialització i l'estalvi de configuració. Quan està desactivat, els connectors no es descobreixen, i no podeu afegir nous connectors a la configuració <code>LOADED_PLUGINS</code>.",
"DISCOVER_PLUGINS_name": "Descobreix els plugins",
"DevDetail_Children_Title": "",
"DevDetail_Children_Title": "Relacions filles",
"DevDetail_Copy_Device_Title": "<i class=\"fa fa-copy\"></i> Copiar detalls des del dispositiu",
"DevDetail_Copy_Device_Tooltip": "Copiar detalls del dispositius des de la llista desplegable. Tot el d'aquesta pàgina es sobre-escriurà",
"DevDetail_CustomProperties_Title": "Propietats personalitzades",
@@ -75,7 +75,7 @@
"DevDetail_EveandAl_AlertAllEvents": "Alertes",
"DevDetail_EveandAl_AlertDown": "Cancel·lar alerta",
"DevDetail_EveandAl_Archived": "Arxivat",
"DevDetail_EveandAl_NewDevice": "Nou Dispositiu",
"DevDetail_EveandAl_NewDevice": "Nou dispositiu",
"DevDetail_EveandAl_NewDevice_Tooltip": "Es mostrarà el nou estat del dispositiu i s'inclourà a les llistes quan el filtre New Devices estigui actiu. No afecta les notificacions.",
"DevDetail_EveandAl_RandomMAC": "MAC aleatori",
"DevDetail_EveandAl_ScanCycle": "Dispositiu d'escaneig",
@@ -87,7 +87,7 @@
"DevDetail_GoToNetworkNode": "Navegació a la pàgina de la Xarxa del node donat.",
"DevDetail_Icon": "Icona",
"DevDetail_Icon_Descr": "Si us plau, introdueix dins de la caixa de text els caràcters que veu a la imatge de sota. Això és requerit per evitar enviaments automàtics.",
"DevDetail_Loading": "Carregant ...",
"DevDetail_Loading": "Carregant ",
"DevDetail_MainInfo_Comments": "Comentaris",
"DevDetail_MainInfo_Favorite": "Favorit",
"DevDetail_MainInfo_Group": "Grup",
@@ -103,11 +103,11 @@
"DevDetail_MainInfo_Type": "Tipus",
"DevDetail_MainInfo_Vendor": "Venedor",
"DevDetail_MainInfo_mac": "MAC",
"DevDetail_NavToChildNode": "",
"DevDetail_NavToChildNode": "Obrir un node fill",
"DevDetail_Network_Node_hover": "Seleccioneu el dispositiu de xarxa al qual aquest dispositiu està connectat, per poder omplir l'arbre de xarxa.",
"DevDetail_Network_Port_hover": "El port on el dispositiu està connectat al dispositiu de xarxa del pare. Si es deixa buit, sortirà una icona wifi a la representació de la Xarxa.",
"DevDetail_Nmap_Scans": "Escaneig manual Nmap",
"DevDetail_Nmap_Scans_desc": "Aquí podeu executar les exploracions NMAP manuals. També podeu programar les exploracions NMAP automàtiques a través del connector Serveis i Ports (NMAP). Ves a <a href='/settings.php' target='_blank'>Configuració</a> per saber-ne més",
"DevDetail_Nmap_Scans_desc": "Aquí podeu executar les exploracions NMAP manuals. També podeu programar les exploracions NMAP automàtiques a través del connector Serveis i Ports (NMAP). Ves a <a href=\"https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/nmap_scan\" target='_blank'>Docs</a> per saber-ne més",
"DevDetail_Nmap_buttonDefault": "Escaneig predeterminat",
"DevDetail_Nmap_buttonDefault_text": "Escaneig predeterminat: Nmap escaneja els 1000 ports superiors per a cada protocol d'exploració sol·licitat. El 93% dels ports TCP i el 49% dels ports UDP. (uns 5 segons)",
"DevDetail_Nmap_buttonDetail": "Escaneig Detallat",
@@ -196,13 +196,13 @@
"DevDetail_button_Save": "Guardar",
"DeviceEdit_ValidMacIp": "Entra una adreça <b>IP</b> i <b>Mac</b> vàlides.",
"Device_MultiEdit": "Multi-edició",
"Device_MultiEdit_Backup": "Atenció, entrar valors incorrectes a continuació trencarà la configuració. Si us plau, abans feu còpia de seguretat la vostra base de dades o configuració de Dispositius (<a href=\"php/server/devices.php?action=ExportCSV\">clic per descarregar <i class=\"fa-solid fa-download fa-bounce\"></i></a>). Llegiu com per recuperar Dispositius des d'aquest fitxer al <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/BACKUPS.md#scenario-2-corrupted-database\" target=\"_blank\">documentació de Còpies de seguretat</a>.",
"Device_MultiEdit_Backup": "Atenció, entrar valors incorrectes a continuació trencarà la configuració. Si us plau, abans feu còpia de seguretat la vostra base de dades o configuració de Dispositius (<a href=\"php/server/devices.php?action=ExportCSV\">clic per descarregar <i class=\"fa-solid fa-download fa-bounce\"></i></a>). Llegiu com per recuperar Dispositius des d'aquest fitxer al <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/BACKUPS.md#scenario-2-corrupted-database\" target=\"_blank\">documentació de Còpies de seguretat</a>. Per aplicar els canvis, feu click a la <b>Save<i class=\"fa-solid fa-save\"></i></b> icona de cada camp que volgueu actualitzar.",
"Device_MultiEdit_Fields": "Editar camps:",
"Device_MultiEdit_MassActions": "Accions massives:",
"Device_MultiEdit_Tooltip": "Atenció. Si feu clic a això s'aplicarà el valor de l'esquerra a tots els dispositius seleccionats a dalt.",
"Device_Searchbox": "Cerca",
"Device_Shortcut_AllDevices": "Els meus dispositius",
"Device_Shortcut_AllNodes": "",
"Device_Shortcut_AllNodes": "Tots els nodes",
"Device_Shortcut_Archived": "Arxivat",
"Device_Shortcut_Connected": "Connectat",
"Device_Shortcut_Devices": "Dispositius",
@@ -229,11 +229,11 @@
"Device_TableHead_Name": "Nom",
"Device_TableHead_NetworkSite": "Network Site",
"Device_TableHead_Owner": "Propietari",
"Device_TableHead_ParentRelType": "",
"Device_TableHead_ParentRelType": "Tipus de relació",
"Device_TableHead_Parent_MAC": "Node pare de xarxa",
"Device_TableHead_Port": "Port",
"Device_TableHead_PresentLastScan": "Presència",
"Device_TableHead_ReqNicsOnline": "",
"Device_TableHead_ReqNicsOnline": "Requereix NICs En línia",
"Device_TableHead_RowID": "ID de fila",
"Device_TableHead_Rowid": "ID de fila",
"Device_TableHead_SSID": "SSID",
@@ -256,7 +256,7 @@
"ENCRYPTION_KEY_name": "Clau d'encriptació",
"Email_display_name": "Correu electrònic",
"Email_icon": "<i class=\"fa fa-at\"></i>",
"Events_Loading": "Carregant ...",
"Events_Loading": "Carregant ",
"Events_Periodselect_All": "Tota la informació",
"Events_Periodselect_LastMonth": "Darrer Mes",
"Events_Periodselect_LastWeek": "Darrera setmana",
@@ -301,6 +301,7 @@
"Gen_Cancel": "Cancel·lar",
"Gen_Change": "Canviar",
"Gen_Copy": "Executar",
"Gen_CopyToClipboard": "Copia a portapapers",
"Gen_DataUpdatedUITakesTime": "D'acord - Pot passar una estona perquè la interfície d'usuari s'actualitzi si s'està executant una exploració.",
"Gen_Delete": "Esborrar",
"Gen_DeleteAll": "Esborrar tot",
@@ -308,8 +309,9 @@
"Gen_Error": "Error",
"Gen_Filter": "Filtrar",
"Gen_Generate": "Generar",
"Gen_InvalidMac": "Mac address invàlida.",
"Gen_LockedDB": "ERROR - DB podria estar bloquejada - Fes servir F12 Eines desenvolupament -> Consola o provar-ho més tard.",
"Gen_NetworkMask": "",
"Gen_NetworkMask": "Màscara de xarxa",
"Gen_Offline": "Fora de línia",
"Gen_Okay": "Ok",
"Gen_Online": "En línia",
@@ -327,7 +329,7 @@
"Gen_SelectIcon": "<i class=\"fa-solid fa-chevron-down fa-fade\"></i>",
"Gen_SelectToPreview": "Seleccioneu la vista prèvia",
"Gen_Selected_Devices": "Dispositius seleccionats:",
"Gen_Subnet": "",
"Gen_Subnet": "Subxarxa",
"Gen_Switch": "Switch",
"Gen_Upd": "Actualitzat correctament",
"Gen_Upd_Fail": "Actualització fallida",
@@ -348,7 +350,7 @@
"LOADED_PLUGINS_name": "Connectors carregats",
"LOG_LEVEL_description": "Aquest paràmetre permetrà un registre més detallat. Útil per a la depuració d'esdeveniments d'escriptura a la base de dades.",
"LOG_LEVEL_name": "Imprimeix el registre addicional",
"Loading": "Carregant ...",
"Loading": "Carregant",
"Login_Box": "Introduïu la vostra contrasenya",
"Login_Default_PWD": "Contrasenya per defecte \"123456\" encara és activa.",
"Login_Info": "Les contrasenyes es canvien al connector(plugin) Configurar Contrasenya. Comprova el <a target=\"_blank\" href=\"https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/set_password\">SETPWD docs</a> si tens dubtes fent logging.",
@@ -417,7 +419,7 @@
"Maintenance_Tool_del_allevents": "Elimina deteccions (presència)",
"Maintenance_Tool_del_allevents30": "Suprimeix tots els esdeveniments anteriors a 30 dies",
"Maintenance_Tool_del_allevents30_noti": "Eliminar Esdeveniments",
"Maintenance_Tool_del_allevents30_noti_text": "T'és segur vols eliminar tot els successos més vells que 30 dies? Això elimina tots els dispositius presents.",
"Maintenance_Tool_del_allevents30_noti_text": "Estàs segur que vols eliminar tot els successos més vells que 30 dies? Això elimina tots els dispositius presents.",
"Maintenance_Tool_del_allevents30_text": "Abans d'utilitzar aquesta funció, feu una còpia de seguretat. La supressió no es pot desfer. S'eliminaran tots els esdeveniments mes vells de 30 dies a la base de dades. També es restablirà la detecció de presència de tots els dispositius. Això pot portar a sessions no vàlides. Això significa que els dispositius es mostren com a \"presents/detectats\" encara que estiguin fora de línia. Una anàlisi mentre el dispositiu en qüestió està en línia resol el problema.",
"Maintenance_Tool_del_allevents_noti": "Eliminar Esdeveniments",
"Maintenance_Tool_del_allevents_noti_text": "Estàs segur que vols eliminar tots els esdeveniments? Això reinicialitza la detecció de presència de tots els dispositius.",
@@ -453,7 +455,7 @@
"Maintenance_Tools_Tab_UISettings": "Configuració UI",
"Maintenance_arp_status": "Estat de Scan",
"Maintenance_arp_status_off": "actualment està desactivat",
"Maintenance_arp_status_on": "scan(s) actualment en execució",
"Maintenance_arp_status_on": "s'està fent un scan",
"Maintenance_built_on": "Construït",
"Maintenance_current_version": "Ets actual. Dona un cop d'ull al que <a href=\"https://github.com/jokob-sk/NetAlertX/issues/138\" target=\"_blank\">estic treballant</a>.",
"Maintenance_database_backup": "Còpies de seguretat de BBDD",
@@ -493,10 +495,10 @@
"Navigation_Workflows": "Workflows",
"Network_Assign": "Connecta el <i class=\"fa fa-server\"></i> node de Xarxa",
"Network_Cant_Assign": "No es pot assignar el node arrel d'Internet com a node fill.",
"Network_Cant_Assign_No_Node_Selected": "",
"Network_Cant_Assign_No_Node_Selected": "No es pot assignar, no s'ha seleccionat cap node pare.",
"Network_Configuration_Error": "Error de configuració",
"Network_Connected": "Dispositius connectats",
"Network_Devices": "",
"Network_Devices": "Dispositius de xarxa",
"Network_ManageAdd": "Afegir dispositiu",
"Network_ManageAdd_Name": "Nom del dispositiu",
"Network_ManageAdd_Name_text": "Nom sense caràcters especials",
@@ -531,8 +533,8 @@
"Network_Root": "Node arrel",
"Network_Root_Not_Configured": "Seleccioneu un tipus de dispositiu de xarxa, per exemple un tipus <b>Gateway</b>, al camp <b>Tipus</b>del <a href=\"deviceDetails.php?mac=Internet\">dispositiu arrel d'Internet</a> per començar a configurar aquesta pantalla. <br/><br/>. Podeu trobar més documentació a la <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/NETWORK_TREE.md\" target=\"_blank\">Guia de com configurar la vostra pàgina de xarxa</a>",
"Network_Root_Unconfigurable": "Arrel no configurable",
"Network_ShowArchived": "",
"Network_ShowOffline": "",
"Network_ShowArchived": "Mostra els arxivats",
"Network_ShowOffline": "Mostra fora de línia",
"Network_Table_Hostname": "Hostname",
"Network_Table_IP": "IP",
"Network_Table_State": "Estat",
@@ -567,7 +569,7 @@
"Presence_Key_OnlinePastMiss": "Anteriorment en línia (miss-match)",
"Presence_Key_OnlinePastMiss_desc": "El dispositiu estava en línia anteriorment, i actualment està fora de línia. podria ser que a la sessió d'inici li faltin dades conflictives (o podria ser un bug) - si us plau, envií una petició de canvi dins de la caixa de text si sap com solucionar-ho (millor en anglès)",
"Presence_Key_OnlinePast_desc": "Dispositiu en línia en el passat, però actualment fora de línia.",
"Presence_Loading": "Carregant...",
"Presence_Loading": "Carregant",
"Presence_Shortcut_AllDevices": "Els meus Dispositius",
"Presence_Shortcut_Archived": "Arxivat",
"Presence_Shortcut_Connected": "Connectat",
@@ -575,7 +577,7 @@
"Presence_Shortcut_DownAlerts": "Aturar alertes",
"Presence_Shortcut_Favorites": "Favorits",
"Presence_Shortcut_NewDevices": "Nous dispositius",
"Presence_Title": "Detecció de dispositius",
"Presence_Title": "Presència per dispositiu",
"REFRESH_FQDN_description": "Re-escaneja tots dispositius i refresca el seu (FQDN). Si està desactivat, nomes s'escanegen els noms coneguts per fer-ho més ràpid. En aquest cas, FQDN s'actualitza només durant descoberta inicial de dispositius.",
"REFRESH_FQDN_name": "Refresc FQDN",
"REPORT_DASHBOARD_URL_description": "Aquesta URL s'utilitza com a base per generar enllaços en informes HTML (per exemple: correus electrònics). Introduïu la URL completa començant per <code>http://</code> incloent el número de port (sense barra inicial <code>/</code>).",
@@ -596,7 +598,7 @@
"Settings_device_Scanners_desync": "⚠ Els horaris d'escàner de dispositius no estan en sincronia.",
"Settings_device_Scanners_desync_popup": "Els horaris dels escàners de dispositius (<code>*_RUN_SCHD</code>) no són iguals. Això donarà lloc a notificacions inconsistents del dispositiu en línia / fora de línia. Si no és intencionat, utilitzeu el mateix horari per a tots els <b>🔍 escàners de dispositius</b>.",
"Speedtest_Results": "Speedtest Resultats",
"Systeminfo_AvailableIps": "",
"Systeminfo_AvailableIps": "IPs disponibles",
"Systeminfo_CPU": "CPU",
"Systeminfo_CPU_Cores": "Nuclis de CPU:",
"Systeminfo_CPU_Name": "Nom de CPU:",
@@ -681,7 +683,7 @@
"UI_LANG_description": "Seleccioneu l'idioma d'usuari preferit. Ajudeu a traduir o suggerir idiomes al portal en línia de <a href=\"https://hosted.weblate.org/projects/pialert/core/\" target=\"_blank\">Weblate</a>.",
"UI_LANG_name": "Llenguatge UI",
"UI_MY_DEVICES_description": "Els dispositius dels quals s'han de mostrar en la vista predeterminada <b> Els meus dispositius</b>.",
"UI_MY_DEVICES_name": "Veure a la vista els meus dispositius",
"UI_MY_DEVICES_name": "Veure a la vista Els Meus Dispositius",
"UI_NOT_RANDOM_MAC_description": "Prefixos MAC que no s'han de marcar com a dispositius aleatoris. Introduïu per exemple <code> 52</code> per excloure els dispositius començant per <code> 52:xx:xx:xx:xx:xx</code> de ser marcats com a dispositius amb una adreça MAC aleatòria.",
"UI_NOT_RANDOM_MAC_name": "No marqueu com a aleatori",
"UI_PRESENCE_description": "Seleccioneu quins estats s'han de mostrar al gràfic <b> Presència de dispositius</b> a la pàgina <a href=\"/devices.php\" target=\"_blank\">Dispositius</a>.",
@@ -719,10 +721,10 @@
"add_icon_event_tooltip": "Afegir nova icona",
"add_option_event_tooltip": "Afegir nou valor",
"copy_icons_event_tooltip": "Sobreescriure icones de tots els dispositius amb el mateix tipus de dispositiu",
"devices_old": "Refrescant...",
"devices_old": "Refrescant",
"general_event_description": "L'esdeveniment que has desencadenat pot trigar un temps fins que acabin els processos de fons. L'execució acabarà una cop buida la cua d'execució (Comprova el registre d'errors <a href='/maintenance.php#tab_Logging'></a> si hi ha problemes). <br/> <br/> Cua d'execució:",
"general_event_title": "Execució d'un esdeveniment ad-hoc",
"go_to_device_event_tooltip": "",
"go_to_device_event_tooltip": "Navegar al dispositiu",
"go_to_node_event_tooltip": "Navegació a la pàgina de la Xarxa del node donat",
"new_version_available": "Ja està disponible una nova versió.",
"report_guid": "Notificació guid:",
@@ -730,7 +732,7 @@
"report_select_format": "Seleccioneu Format:",
"report_time": "Data de recepció:",
"run_event_tooltip": "Habiliteu la configuració i deseu els canvis al principi abans d'executar-lo.",
"select_icon_event_tooltip": "",
"select_icon_event_tooltip": "Selecciona la icona",
"settings_core_icon": "fa-solid fa-gem",
"settings_core_label": "Nucli",
"settings_device_scanners": "Escàners de dispositius utilitzats per descobrir dispositius que escriuen a la taula de base de dades CurrentScan.",
@@ -744,7 +746,7 @@
"settings_imported_label": "Configuració importada",
"settings_missing": "No tots els paràmetres carregats! Alta càrrega en la seqüència d'inici de la base de dades o aplicació. Feu clic al botó de recarregar 🔄 a la part superior.",
"settings_missing_block": "Error: els paràmetres no carregats correctament. Fer clic el botó recarregar 🔄 dalt de tot, o també comprova el registre de navegador per a detalls (F12).",
"settings_old": "Importar la configuració i re-inicialitzar ...",
"settings_old": "Importar la configuració i re-iniciar…",
"settings_other_scanners": "Uns altres plugins no relacionats amb dispositius que estan actualment activats.",
"settings_other_scanners_icon": "fa-solid fa-recycle",
"settings_other_scanners_label": "Altres escàners",
@@ -758,4 +760,4 @@
"settings_system_label": "Sistema",
"settings_update_item_warning": "Actualitza el valor sota. Sigues curós de seguir el format anterior. <b>No hi ha validació.</b>",
"test_event_tooltip": "Deseu els canvis primer abans de comprovar la configuració."
}
}

View File

@@ -301,6 +301,7 @@
"Gen_Cancel": "Zrušit",
"Gen_Change": "Změnit",
"Gen_Copy": "Spustit",
"Gen_CopyToClipboard": "",
"Gen_DataUpdatedUITakesTime": "OK - může zabrat chvíli aktualizovat rozhraní, pokud běží scan.",
"Gen_Delete": "Smazat",
"Gen_DeleteAll": "Smazat vše",
@@ -308,6 +309,7 @@
"Gen_Error": "Chyba",
"Gen_Filter": "Filtr",
"Gen_Generate": "Vygenerovat",
"Gen_InvalidMac": "",
"Gen_LockedDB": "CHYBA - Databáze je možná zamčená - Zkontrolujte F12 -> Nástroje pro vývojáře -> Konzole. nebo to zkuste později.",
"Gen_NetworkMask": "",
"Gen_Offline": "Offline",

View File

@@ -211,7 +211,7 @@
"Device_Shortcut_Connected": "Verbunden",
"Device_Shortcut_Devices": "Geräte",
"Device_Shortcut_DownAlerts": "Nicht erreichbar & offline",
"Device_Shortcut_DownOnly": "Offline",
"Device_Shortcut_DownOnly": "Nicht erreichbar",
"Device_Shortcut_Favorites": "Favoriten",
"Device_Shortcut_NewDevices": "Neue Geräte",
"Device_Shortcut_OnlineChart": "Gerätepräsenz im Laufe der Zeit",
@@ -305,6 +305,7 @@
"Gen_Cancel": "Abbrechen",
"Gen_Change": "Ändern",
"Gen_Copy": "Ausführen",
"Gen_CopyToClipboard": "",
"Gen_DataUpdatedUITakesTime": "OK Es kann einen Moment dauern, bis die Benutzeroberfläche aktualisiert wird, während ein Scan ausgeführt wird.",
"Gen_Delete": "Löschen",
"Gen_DeleteAll": "Alles löschen",
@@ -312,6 +313,7 @@
"Gen_Error": "Fehler",
"Gen_Filter": "Filter",
"Gen_Generate": "Generieren",
"Gen_InvalidMac": "Ungültige MAC-Adresse.",
"Gen_LockedDB": "ERROR - DB eventuell gesperrt - Nutze die Konsole in den Entwickler Werkzeugen (F12) zur Überprüfung oder probiere es später erneut.",
"Gen_NetworkMask": "",
"Gen_Offline": "Offline",
@@ -831,4 +833,4 @@
"settings_system_label": "System",
"settings_update_item_warning": "",
"test_event_tooltip": "Speichere die Änderungen, bevor Sie die Einstellungen testen."
}
}

View File

@@ -301,6 +301,7 @@
"Gen_Cancel": "Cancel",
"Gen_Change": "Change",
"Gen_Copy": "Run",
"Gen_CopyToClipboard": "Copy to clipboard",
"Gen_DataUpdatedUITakesTime": "OK - It may take a while for the UI to update if a scan is running.",
"Gen_Delete": "Delete",
"Gen_DeleteAll": "Delete all",
@@ -308,6 +309,7 @@
"Gen_Error": "Error",
"Gen_Filter": "Filter",
"Gen_Generate": "Generate",
"Gen_InvalidMac": "Invalid Mac address.",
"Gen_LockedDB": "ERROR - DB might be locked - Check F12 Dev tools -> Console or try later.",
"Gen_NetworkMask": "Network mask",
"Gen_Offline": "Offline",

View File

@@ -303,6 +303,7 @@
"Gen_Cancel": "Cancelar",
"Gen_Change": "Cambiar",
"Gen_Copy": "Ejecutar",
"Gen_CopyToClipboard": "",
"Gen_DataUpdatedUITakesTime": "Correcto - La interfaz puede tardar en actualizarse si se está ejecutando un escaneo.",
"Gen_Delete": "Eliminar",
"Gen_DeleteAll": "Eliminar todo",
@@ -310,6 +311,7 @@
"Gen_Error": "Error",
"Gen_Filter": "Filtro",
"Gen_Generate": "Generar",
"Gen_InvalidMac": "",
"Gen_LockedDB": "Fallo - La base de datos puede estar bloqueada - Pulsa F1 -> Ajustes de desarrolladores -> Consola o prueba más tarde.",
"Gen_NetworkMask": "",
"Gen_Offline": "Desconectado",

View File

@@ -301,6 +301,7 @@
"Gen_Cancel": "Annuler",
"Gen_Change": "Changement",
"Gen_Copy": "Lancer",
"Gen_CopyToClipboard": "Copier vers le presse-papier",
"Gen_DataUpdatedUITakesTime": "OK - cela peut prendre du temps à l'interface pour se mettre à jour si un scan est en cours.",
"Gen_Delete": "Supprimer",
"Gen_DeleteAll": "Supprimer tous",
@@ -308,8 +309,9 @@
"Gen_Error": "Erreur",
"Gen_Filter": "Filtrer",
"Gen_Generate": "Générer",
"Gen_InvalidMac": "Adresse MAC invalide.",
"Gen_LockedDB": "Erreur - La base de données est peut-être verrouillée - Vérifier avec les outils de dév via F12 -> Console ou essayer plus tard.",
"Gen_NetworkMask": "",
"Gen_NetworkMask": "Masque réseau",
"Gen_Offline": "Hors ligne",
"Gen_Okay": "OK",
"Gen_Online": "En ligne",
@@ -327,7 +329,7 @@
"Gen_SelectIcon": "<i class=\"fa-solid fa-chevron-down fa-fade\"></i>",
"Gen_SelectToPreview": "Sélectionnez pour prévisualiser",
"Gen_Selected_Devices": "Appareils sélectionnés :",
"Gen_Subnet": "",
"Gen_Subnet": "Sous-réseau",
"Gen_Switch": "Basculer",
"Gen_Upd": "Mise à jour réussie",
"Gen_Upd_Fail": "Échec de la mise à jour",
@@ -596,7 +598,7 @@
"Settings_device_Scanners_desync": "⚠ La planification des différents scanners d'appareils est désynchronisée.",
"Settings_device_Scanners_desync_popup": "La planification des scanners (<code>*_RUN_SCHD</code>) n'est pas identique entre scanners. Cela va entraîner des notifications en ligne/hors-ligne non cohérentes. À moins que cela soit attendu, utilisez la même planification pour tous les <b>🔍scanners d'appareils</b> activés.",
"Speedtest_Results": "Résultats du test de débit",
"Systeminfo_AvailableIps": "",
"Systeminfo_AvailableIps": "Adresses IP disponibles",
"Systeminfo_CPU": "Processeur",
"Systeminfo_CPU_Cores": "Cœurs de processeur:",
"Systeminfo_CPU_Name": "Nom du processeur:",

View File

@@ -301,6 +301,7 @@
"Gen_Cancel": "Annulla",
"Gen_Change": "Modifica",
"Gen_Copy": "Esegui",
"Gen_CopyToClipboard": "Copia negli appunti",
"Gen_DataUpdatedUITakesTime": "OK: l'aggiornamento dell'interfaccia utente potrebbe richiedere del tempo se è in esecuzione una scansione.",
"Gen_Delete": "Elimina",
"Gen_DeleteAll": "Elimina tutti",
@@ -308,8 +309,9 @@
"Gen_Error": "Errore",
"Gen_Filter": "Filtro",
"Gen_Generate": "Genera",
"Gen_InvalidMac": "Indirizzo Mac non valido.",
"Gen_LockedDB": "ERRORE: il DB potrebbe essere bloccato, controlla F12 Strumenti di sviluppo -> Console o riprova più tardi.",
"Gen_NetworkMask": "",
"Gen_NetworkMask": "Maschera di rete",
"Gen_Offline": "Offline",
"Gen_Okay": "Ok",
"Gen_Online": "Online",
@@ -327,7 +329,7 @@
"Gen_SelectIcon": "<i class=\"fa-solid fa-chevron-down fa-fade\"></i>",
"Gen_SelectToPreview": "Seleziona per anteprima",
"Gen_Selected_Devices": "Dispositivi selezionati:",
"Gen_Subnet": "",
"Gen_Subnet": "Sottorete",
"Gen_Switch": "Cambia",
"Gen_Upd": "Aggiornato correttamente",
"Gen_Upd_Fail": "Aggiornamento fallito",
@@ -596,7 +598,7 @@
"Settings_device_Scanners_desync": "⚠ Le pianificazioni dello scanner del dispositivo non sono sincronizzate.",
"Settings_device_Scanners_desync_popup": "Gli orari degli scanner dei dispositivi (<code>*_RUN_SCHD</code>) non sono gli stessi. Questo comporterà notifiche online/offline incoerenti del dispositivo. A meno che ciò non sia previsto, utilizza la stessa pianificazione per tutti gli <b>🔍 scanner dispositivi</b> abilitati.",
"Speedtest_Results": "Risultati test di velocità",
"Systeminfo_AvailableIps": "",
"Systeminfo_AvailableIps": "IP disponibili",
"Systeminfo_CPU": "CPU",
"Systeminfo_CPU_Cores": "Core CPU:",
"Systeminfo_CPU_Name": "Nome CPU:",

View File

@@ -5,7 +5,7 @@
// ###################################
$defaultLang = "en_us";
$allLanguages = ["en_us", "es_es", "de_de", "fr_fr", "it_it", "ru_ru", "nb_no", "pl_pl", "pt_br", "tr_tr", "zh_cn", "cs_cz", "ar_ar", "ca_ca", "uk_ua"];
$allLanguages = ["en_us", "es_es", "de_de", "fr_fr", "it_it", "ru_ru", "nb_no", "pl_pl", "pt_br", "pt_pt", "tr_tr", "zh_cn", "cs_cz", "ar_ar", "ca_ca", "uk_ua"];
global $db;
@@ -19,6 +19,7 @@ switch($result){
case 'Norwegian': $pia_lang_selected = 'nb_no'; break;
case 'Polish (pl_pl)': $pia_lang_selected = 'pl_pl'; break;
case 'Portuguese (pt_br)': $pia_lang_selected = 'pt_br'; break;
case 'Portuguese (pt_pt)': $pia_lang_selected = 'pt_pt'; break;
case 'Italian (it_it)': $pia_lang_selected = 'it_it'; break;
case 'Russian': $pia_lang_selected = 'ru_ru'; break;
case 'Turkish (tr_tr)': $pia_lang_selected = 'tr_tr'; break;

View File

@@ -33,6 +33,6 @@ def merge_translations(main_file, other_files):
if __name__ == "__main__":
current_path = os.path.dirname(os.path.abspath(__file__))
# language codes can be found here: http://www.lingoes.net/en/translator/langcode.htm
json_files = ["en_us.json", "de_de.json", "es_es.json", "fr_fr.json", "nb_no.json", "ru_ru.json", "it_it.json", "pt_br.json", "pl_pl.json", "zh_cn.json", "tr_tr.json", "cs_cz.json", "ar_ar.json", "ca_ca.json", "uk_ua.json"]
json_files = ["en_us.json", "de_de.json", "es_es.json", "fr_fr.json", "nb_no.json", "ru_ru.json", "it_it.json", "pt_br.json", "pt_pt.json", "pl_pl.json", "zh_cn.json", "tr_tr.json", "cs_cz.json", "ar_ar.json", "ca_ca.json", "uk_ua.json"]
file_paths = [os.path.join(current_path, file) for file in json_files]
merge_translations(file_paths[0], file_paths[1:])

View File

@@ -301,6 +301,7 @@
"Gen_Cancel": "Avbryt",
"Gen_Change": "",
"Gen_Copy": "Kjør",
"Gen_CopyToClipboard": "",
"Gen_DataUpdatedUITakesTime": "OK - Det kan ta litt tid før brukergrensesnittet oppdateres hvis en skanning kjøres.",
"Gen_Delete": "Slett",
"Gen_DeleteAll": "Slett alle",
@@ -308,6 +309,7 @@
"Gen_Error": "Feil",
"Gen_Filter": "Filter",
"Gen_Generate": "",
"Gen_InvalidMac": "",
"Gen_LockedDB": "FEIL - DB kan være låst - Sjekk F12 Dev tools -> Konsoll eller prøv senere.",
"Gen_NetworkMask": "",
"Gen_Offline": "Frakoblet",

View File

@@ -301,6 +301,7 @@
"Gen_Cancel": "Anuluj",
"Gen_Change": "Zmiana",
"Gen_Copy": "Wykonaj",
"Gen_CopyToClipboard": "",
"Gen_DataUpdatedUITakesTime": "OK Może to potrwać chwilę, zanim interfejs użytkownika się zaktualizuje, jeśli trwa skan.",
"Gen_Delete": "Usuń",
"Gen_DeleteAll": "Usuń wszystko",
@@ -308,6 +309,7 @@
"Gen_Error": "Błąd",
"Gen_Filter": "Filtr",
"Gen_Generate": "Wygeneruj",
"Gen_InvalidMac": "",
"Gen_LockedDB": "Błąd - Baza danych może być zablokowana - Sprawdź narzędzia deweloperskie F12 -> Konsola lub spróbuj później.",
"Gen_NetworkMask": "",
"Gen_Offline": "Niedostępne",

View File

@@ -301,6 +301,7 @@
"Gen_Cancel": "Cancelar",
"Gen_Change": "Alterar",
"Gen_Copy": "Executar",
"Gen_CopyToClipboard": "",
"Gen_DataUpdatedUITakesTime": "OK - Pode levar um tempo para a interface do usuário ser atualizada se uma verificação estiver em execução.",
"Gen_Delete": "Excluir",
"Gen_DeleteAll": "Excluir todos",
@@ -308,6 +309,7 @@
"Gen_Error": "Erro",
"Gen_Filter": "Filtro",
"Gen_Generate": "Gerar",
"Gen_InvalidMac": "",
"Gen_LockedDB": "ERRO - O banco de dados pode estar bloqueado - Verifique F12 Ferramentas de desenvolvimento -> Console ou tente mais tarde.",
"Gen_NetworkMask": "",
"Gen_Offline": "Offline",

View File

@@ -0,0 +1,763 @@
{
"API_CUSTOM_SQL_description": "Pode especificar uma consulta SQL personalizada que irá gerar um ficheiro JSON e, em seguida, expô-lo por meio do <a href=\"/php/server/query_json.php?file=table_custom_endpoint.json\" target=\"_blank\"><code>table_custom_endpoint.json</code> endpoint do ficheiro</a>.",
"API_CUSTOM_SQL_name": "Endpoint customizado",
"API_TOKEN_description": "Token de API para comunicação segura. Gere um ou insira qualquer valor. Ele é enviado no cabeçalho da solicitação e usado no plugin <code>SYNC</code>, no servidor GraphQL e em outros endpoints de API. Pode usar os endpoints de API para criar integrações personalizadas, conforme descrito na <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/API.md\" target=\"_blank\">documentação da API</a>.",
"API_TOKEN_name": "API token",
"API_display_name": "API",
"API_icon": "<i class=\"fa fa-arrow-down-up-across-line\"></i>",
"About_Design": "Desenvolvido por:",
"About_Exit": "Sair",
"About_Title": "Analisador de segurança de rede & framework de notificação",
"AppEvents_AppEventProcessed": "Processado",
"AppEvents_DateTimeCreated": "Registado em",
"AppEvents_Extra": "Adicional",
"AppEvents_GUID": "Evento de aplicação GUID",
"AppEvents_Helper1": "Auxiliar 1",
"AppEvents_Helper2": "Auxiliar 2",
"AppEvents_Helper3": "Auxiliar 3",
"AppEvents_ObjectForeignKey": "Chave Estrangeira",
"AppEvents_ObjectIndex": "Índice",
"AppEvents_ObjectIsArchived": "Foi arquivado (no horário do log)",
"AppEvents_ObjectIsNew": "É novo (no horário do log)",
"AppEvents_ObjectPlugin": "Plugin Associado",
"AppEvents_ObjectPrimaryID": "ID Primário",
"AppEvents_ObjectSecondaryID": "ID Secundário",
"AppEvents_ObjectStatus": "Estado registado",
"AppEvents_ObjectStatusColumn": "Coluna de Estado",
"AppEvents_ObjectType": "Tipo de Objeto",
"AppEvents_Plugin": "Plugin",
"AppEvents_Type": "Tipo",
"BackDevDetail_Actions_Ask_Run": "Deseja executar esta ação?",
"BackDevDetail_Actions_Not_Registered": "Ação não registada: ",
"BackDevDetail_Actions_Title_Run": "Executar ação",
"BackDevDetail_Copy_Ask": "Copiar pormenores de dispositivos da lista suspenda (Tudo nesta página será substituído)?",
"BackDevDetail_Copy_Title": "Copiar pormenores",
"BackDevDetail_Tools_WOL_error": "O comando NÃO foi executado.",
"BackDevDetail_Tools_WOL_okay": "O comando foi executado.",
"BackDevices_Arpscan_disabled": "Análise Arp Desativada",
"BackDevices_Arpscan_enabled": "Análise ARP Ativada",
"BackDevices_Backup_CopError": "A base da dados original não pode ser gravada.",
"BackDevices_Backup_Failed": "A copia de segurança foi parcialmente executada. O arquivo não pode ser criado ou está vazio.",
"BackDevices_Backup_okay": "A copia de segurança foi feita executado corretamente com o novo arquivo",
"BackDevices_DBTools_DelDevError_a": "Erro ao apagar o dispositivo",
"BackDevices_DBTools_DelDevError_b": "Erro ao apagar dispositivos",
"BackDevices_DBTools_DelDev_a": "Dispositivo apagado",
"BackDevices_DBTools_DelDev_b": "Dispositivos apagados",
"BackDevices_DBTools_DelEvents": "Eventos apagados",
"BackDevices_DBTools_DelEventsError": "Erro ao apagar os eventos",
"BackDevices_DBTools_ImportCSV": "Os dispositivos do ficheiro CSV foram importados com sucesso.",
"BackDevices_DBTools_ImportCSVError": "O ficheiro CSV não pode ser importado. Assegure que o formato está correto.",
"BackDevices_DBTools_ImportCSVMissing": "O ficheiro CSV não foi localizado em <b>/config/devices.csv.</b>",
"BackDevices_DBTools_Purge": "As copias de segurança antigas foram apagadas",
"BackDevices_DBTools_UpdDev": "Dispositivo atualizado com sucesso. A lista de dispositivos principais pode levar algum tempo para recarregar se uma varredura estiver em andamento.",
"BackDevices_DBTools_UpdDevError": "Erro atualizando o dispositivo",
"BackDevices_DBTools_Upgrade": "Base de dados atualizada com sucesso",
"BackDevices_DBTools_UpgradeError": "A atualização da base de dados falhou",
"BackDevices_Device_UpdDevError": "Erro atualizando os dispositivos, tente novamente mais tarde. A base de dados provavelmente está travado com uma tarefa em andamento.",
"BackDevices_Restore_CopError": "A base de dados original não pode ser gradava.",
"BackDevices_Restore_Failed": "A restauração falhou. Por favor restaure a copia de segurança manualmente.",
"BackDevices_Restore_okay": "Restauração executada com sucesso.",
"BackDevices_darkmode_disabled": "Modo Noturno Desativado",
"BackDevices_darkmode_enabled": "Modo Noturno Ativado",
"CLEAR_NEW_FLAG_description": "Se ativado (<code>0</code> está desativado), dispositivos marcados como<b>Novo Dispositivo</b> serão desmarcados se o limite (especificado em horas) exceder o tempo da <b>Primeira Sessão </b>.",
"CLEAR_NEW_FLAG_name": "",
"CustProps_cant_remove": "Não é possível remover, é necessária pelo menos uma propriedade.",
"DAYS_TO_KEEP_EVENTS_description": "Esta é uma definição de manutenção. Especifica o número de dias de entradas de eventos que serão mantidas. Todos os eventos mais antigos serão apagados periodicamente. Também se aplica ao Histórico de eventos do plug-in.",
"DAYS_TO_KEEP_EVENTS_name": "Apagar eventos mais antigos que",
"DISCOVER_PLUGINS_description": "Desative esta opção para acelerar a inicialização e a gravação de definições. Quando desativada, os plug-ins não são descobertos e não é possível adicionar novos plug-ins à definição<code>LOADED_PLUGINS</code>.",
"DISCOVER_PLUGINS_name": "Descobrir plugins",
"DevDetail_Children_Title": "Relacionamentos de crianças",
"DevDetail_Copy_Device_Title": "Copiar pormenores do dispositivo",
"DevDetail_Copy_Device_Tooltip": "Copiar pormenores do dispositivo a partir da lista pendente. Tudo o que se encontra nesta página será substituído",
"DevDetail_CustomProperties_Title": "Propriedades personalizadas",
"DevDetail_CustomProps_reset_info": "Isto irá remover as suas propriedades personalizadas neste dispositivo e repô-las para o valor predefinido.",
"DevDetail_DisplayFields_Title": "Visualização",
"DevDetail_EveandAl_AlertAllEvents": "Eventos de alerta",
"DevDetail_EveandAl_AlertDown": "",
"DevDetail_EveandAl_Archived": "Arquivado",
"DevDetail_EveandAl_NewDevice": "Novo dispositivo",
"DevDetail_EveandAl_NewDevice_Tooltip": "",
"DevDetail_EveandAl_RandomMAC": "MAC Aleatório",
"DevDetail_EveandAl_ScanCycle": "Rastrear dispositivo",
"DevDetail_EveandAl_ScanCycle_a": "Rastear dispositivo",
"DevDetail_EveandAl_ScanCycle_z": "Não rastear dispositivo",
"DevDetail_EveandAl_Skip": "Pular notificações repetidas para",
"DevDetail_EveandAl_Title": "Configuração de Eventos & Alertas",
"DevDetail_Events_CheckBox": "Ocultar eventos de conexão",
"DevDetail_GoToNetworkNode": "Navega para a página Rede do nó indicado.",
"DevDetail_Icon": "Icone",
"DevDetail_Icon_Descr": "Introduza o nome de um ícone fantástico do tipo de letra sem o prefixo fa- ou com a classe completa, por exemplo: fa fa-brands fa-apple.",
"DevDetail_Loading": "A carregar…",
"DevDetail_MainInfo_Comments": "Comentários",
"DevDetail_MainInfo_Favorite": "Favorito",
"DevDetail_MainInfo_Group": "Grupo",
"DevDetail_MainInfo_Location": "Localização",
"DevDetail_MainInfo_Name": "Nome",
"DevDetail_MainInfo_Network": "<i class=\"fa fa-server\"> </i> Node (MAC)",
"DevDetail_MainInfo_Network_Port": "<i class=\"fa fa-ethernet\"></i>Porta",
"DevDetail_MainInfo_Network_Site": "Site",
"DevDetail_MainInfo_Network_Title": "Rede",
"DevDetail_MainInfo_Owner": "Proprietário",
"DevDetail_MainInfo_SSID": "SSID",
"DevDetail_MainInfo_Title": "Informações principais",
"DevDetail_MainInfo_Type": "Tipo",
"DevDetail_MainInfo_Vendor": "Fornecedor",
"DevDetail_MainInfo_mac": "MAC",
"DevDetail_NavToChildNode": "",
"DevDetail_Network_Node_hover": "Selecione o dispositivo de rede principal ao qual o dispositivo atual está conectado, para preencher a árvore Rede.",
"DevDetail_Network_Port_hover": "A porta a que este dispositivo está ligado no dispositivo de rede principal. Se for deixado vazio, é apresentado um ícone wifi na árvore Rede.",
"DevDetail_Nmap_Scans": "Varreduras manuais do Nmap",
"DevDetail_Nmap_Scans_desc": "",
"DevDetail_Nmap_buttonDefault": "Verificação predefinida",
"DevDetail_Nmap_buttonDefault_text": "Scan padrão: Nmap verifica as 1.000 portas superiores para cada protocolo de digitalização solicitado. Isto atinge cerca de 93% das portas TCP e 49% das portas UDP. (cerca de 5 segundos)",
"DevDetail_Nmap_buttonDetail": "Verificação Detalhada",
"DevDetail_Nmap_buttonDetail_text": "Verificação detalhada: Verificação predefinida com deteção de SO ativada, deteção de versão, verificação de scripts e traceroute (até 30 segundos ou mais)",
"DevDetail_Nmap_buttonFast": "Verificação rápida",
"DevDetail_Nmap_buttonFast_text": "Verificação rápida: Verifica menos portas (100) do que a verificação predefinida (alguns segundos)",
"DevDetail_Nmap_buttonSkipDiscovery": "Saltar descoberta do host",
"DevDetail_Nmap_buttonSkipDiscovery_text": "Ignorar a descoberta do host (-Pn opção): Verificação padrão sem descoberta do host",
"DevDetail_Nmap_resultsLink": "Pode deixar esta página depois de iniciar uma varredura. Os resultados também estarão disponíveis no ficheiro <code>app_front.log</code>.",
"DevDetail_Owner_hover": "Quem é o dono deste dispositivo. Campo de texto gratuito.",
"DevDetail_Periodselect_All": "Todas as informações",
"DevDetail_Periodselect_LastMonth": "Último mês",
"DevDetail_Periodselect_LastWeek": "Semana passada",
"DevDetail_Periodselect_LastYear": "Ano passado",
"DevDetail_Periodselect_today": "Hoje",
"DevDetail_Run_Actions_Title": "<i class=\"fa fa-play\"></i> Executar ação no dispositivo",
"DevDetail_Run_Actions_Tooltip": "Execute uma ação no dispositivo atual da lista suspensa.",
"DevDetail_SessionInfo_FirstSession": "Primeira sessão",
"DevDetail_SessionInfo_LastIP": "Último IP",
"DevDetail_SessionInfo_LastSession": "Último offline",
"DevDetail_SessionInfo_StaticIP": "IP estático",
"DevDetail_SessionInfo_Status": "Estado",
"DevDetail_SessionInfo_Title": "Informações de Sessão",
"DevDetail_SessionTable_Additionalinfo": "Informações adicionais",
"DevDetail_SessionTable_Connection": "Conexão",
"DevDetail_SessionTable_Disconnection": "Desconexão",
"DevDetail_SessionTable_Duration": "Duração",
"DevDetail_SessionTable_IP": "IP",
"DevDetail_SessionTable_Order": "Ordem",
"DevDetail_Shortcut_CurrentStatus": "Estado atual",
"DevDetail_Shortcut_DownAlerts": "Alertas para baixo",
"DevDetail_Shortcut_Presence": "Presença",
"DevDetail_Shortcut_Sessions": "Sessões",
"DevDetail_Tab_Details": "Pormenores",
"DevDetail_Tab_Events": "Eventos",
"DevDetail_Tab_EventsTableDate": "Data",
"DevDetail_Tab_EventsTableEvent": "Tipo de evento",
"DevDetail_Tab_EventsTableIP": "IP",
"DevDetail_Tab_EventsTableInfo": "Informações adicionais",
"DevDetail_Tab_Nmap": "<i class=\"fa fa-ethernet\"> </i> Nmap",
"DevDetail_Tab_NmapEmpty": "Nenhuma porta detetada com Nmap neste dispositivo.",
"DevDetail_Tab_NmapTableExtra": "Adicional",
"DevDetail_Tab_NmapTableHeader": "Resultados da verificação programada",
"DevDetail_Tab_NmapTableIndex": "Índice",
"DevDetail_Tab_NmapTablePort": "Porta",
"DevDetail_Tab_NmapTableService": "Serviço",
"DevDetail_Tab_NmapTableState": "Estado",
"DevDetail_Tab_NmapTableText": "",
"DevDetail_Tab_NmapTableTime": "Tempo",
"DevDetail_Tab_Plugins": "Plugins",
"DevDetail_Tab_Presence": "Presença",
"DevDetail_Tab_Sessions": "Sessões",
"DevDetail_Tab_Tools": "Ferramentas",
"DevDetail_Tab_Tools_Internet_Info_Description": "",
"DevDetail_Tab_Tools_Internet_Info_Error": "Ocorreu um erro",
"DevDetail_Tab_Tools_Internet_Info_Start": "",
"DevDetail_Tab_Tools_Internet_Info_Title": "",
"DevDetail_Tab_Tools_Nslookup_Description": "",
"DevDetail_Tab_Tools_Nslookup_Error": "",
"DevDetail_Tab_Tools_Nslookup_Start": "",
"DevDetail_Tab_Tools_Nslookup_Title": "",
"DevDetail_Tab_Tools_Speedtest_Description": "",
"DevDetail_Tab_Tools_Speedtest_Start": "",
"DevDetail_Tab_Tools_Speedtest_Title": "",
"DevDetail_Tab_Tools_Traceroute_Description": "",
"DevDetail_Tab_Tools_Traceroute_Error": "",
"DevDetail_Tab_Tools_Traceroute_Start": "",
"DevDetail_Tab_Tools_Traceroute_Title": "",
"DevDetail_Tools_WOL": "",
"DevDetail_Tools_WOL_noti": "",
"DevDetail_Tools_WOL_noti_text": "",
"DevDetail_Type_hover": "",
"DevDetail_Vendor_hover": "",
"DevDetail_WOL_Title": "",
"DevDetail_button_AddIcon": "",
"DevDetail_button_AddIcon_Help": "Cole uma tag HTML SVG ou um ícone de tag HTML Font Awesome. Leia a <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/ICONS.md\" target=\"_blank\">documentação sobre ícones</a> para obter pormenores.",
"DevDetail_button_AddIcon_Tooltip": "Adicione um novo ícone a este dispositivo que ainda não esteja disponível no menu suspenso.",
"DevDetail_button_Delete": "Apagar dispositivo",
"DevDetail_button_DeleteEvents": "Apagar eventos",
"DevDetail_button_DeleteEvents_Warning": "Tem certeza de que deseja apagar todos os eventos deste dispositivo?<br><br>(isto limpará o <b>Histórico de eventos</b> e as <b>sessões</b> e poderá ajudar com constantes (persistentes) notificações)",
"DevDetail_button_Delete_ask": "Tem a certeza de que pretende apagar este dispositivo? Em vez disso, também o pode arquivar.",
"DevDetail_button_OverwriteIcons": "Substituir ícones",
"DevDetail_button_OverwriteIcons_Tooltip": "Substituir ícones de todos os dispositivos pelo mesmo tipo de dispositivo",
"DevDetail_button_OverwriteIcons_Warning": "Tem certeza de que deseja substituir todos os ícones de todos os dispositivos pelo mesmo tipo de dispositivo do tipo de dispositivo atual?",
"DevDetail_button_Reset": "Redefinir alterações",
"DevDetail_button_Save": "Gravar",
"DeviceEdit_ValidMacIp": "Insira um endereço <b>Mac</b> e <b>IP</b> válidos.",
"Device_MultiEdit": "Edição múltipla",
"Device_MultiEdit_Backup": "",
"Device_MultiEdit_Fields": "Editar campos:",
"Device_MultiEdit_MassActions": "Ações em massa:",
"Device_MultiEdit_Tooltip": "Cuidadoso. Clicar aqui aplicará o valor à esquerda a todos os dispositivos selecionados acima.",
"Device_Searchbox": "Procurar",
"Device_Shortcut_AllDevices": "",
"Device_Shortcut_AllNodes": "",
"Device_Shortcut_Archived": "Arquivado",
"Device_Shortcut_Connected": "Conectado",
"Device_Shortcut_Devices": "Dispositivos",
"Device_Shortcut_DownAlerts": "Inativo e off-line",
"Device_Shortcut_DownOnly": "Inativo",
"Device_Shortcut_Favorites": "Favoritos",
"Device_Shortcut_NewDevices": "",
"Device_Shortcut_OnlineChart": "Presença do dispositivo",
"Device_TableHead_AlertDown": "Alerta em baixo",
"Device_TableHead_Connected_Devices": "Conexões",
"Device_TableHead_CustomProps": "",
"Device_TableHead_FQDN": "",
"Device_TableHead_Favorite": "Favorito",
"Device_TableHead_FirstSession": "Primeira sessão",
"Device_TableHead_GUID": "GUID",
"Device_TableHead_Group": "Grupo",
"Device_TableHead_Icon": "Ícone",
"Device_TableHead_LastIP": "Último IP",
"Device_TableHead_LastIPOrder": "Último pedido de IP",
"Device_TableHead_LastSession": "Último off-line",
"Device_TableHead_Location": "Localização",
"Device_TableHead_MAC": "MAC aleatório",
"Device_TableHead_MAC_full": "MAC completo",
"Device_TableHead_Name": "Nome",
"Device_TableHead_NetworkSite": "Site da rede",
"Device_TableHead_Owner": "Proprietário",
"Device_TableHead_ParentRelType": "",
"Device_TableHead_Parent_MAC": "",
"Device_TableHead_Port": "Porta",
"Device_TableHead_PresentLastScan": "Presença",
"Device_TableHead_ReqNicsOnline": "",
"Device_TableHead_RowID": "ID da linha",
"Device_TableHead_Rowid": "ID da linha",
"Device_TableHead_SSID": "SSID",
"Device_TableHead_SourcePlugin": "Plugin de fonte",
"Device_TableHead_Status": "Estado",
"Device_TableHead_SyncHubNodeName": "Nó de sincronização",
"Device_TableHead_Type": "Tipo",
"Device_TableHead_Vendor": "Fornecedor",
"Device_Table_Not_Network_Device": "Não configurado como um dispositivo de rede",
"Device_Table_info": "A mostrar _START_ to _END_ of _TOTAL_ entradas",
"Device_Table_nav_next": "Próximo",
"Device_Table_nav_prev": "Anterior",
"Device_Tablelenght": "Mostrar entradas do _MENU_",
"Device_Tablelenght_all": "Todos",
"Device_Title": "Dispositivos",
"Devices_Filters": "Filtros",
"ENABLE_PLUGINS_description": "Ativa a funcionalidade de <a target=\"_blank\" href=\"https://github.com/jokob-sk/NetAlertX/tree/main/docs/PLUGINS.md\">plugins</a>. Carregar plug-ins requer mais recursos de hardware, então podedesativá-los em sistemas de baixa potência.",
"ENABLE_PLUGINS_name": "Ativar plug-ins",
"ENCRYPTION_KEY_description": "Chave de encriptação de dados.",
"ENCRYPTION_KEY_name": "Chave de encriptação",
"Email_display_name": "Email",
"Email_icon": "<i class=\"fa fa-at\"></i>",
"Events_Loading": "",
"Events_Periodselect_All": "Todas as informações",
"Events_Periodselect_LastMonth": "Mês passado",
"Events_Periodselect_LastWeek": "Semana passada",
"Events_Periodselect_LastYear": "Ano passado",
"Events_Periodselect_today": "Hoje",
"Events_Searchbox": "Procurar",
"Events_Shortcut_AllEvents": "Todos os eventos",
"Events_Shortcut_DownAlerts": "Alertas de queda",
"Events_Shortcut_Events": "Eventos",
"Events_Shortcut_MissSessions": "Sessões ausentes",
"Events_Shortcut_NewDevices": "",
"Events_Shortcut_Sessions": "Sessões",
"Events_Shortcut_VoidSessions": "Sessões anuladas",
"Events_TableHead_AdditionalInfo": "Informação adicional",
"Events_TableHead_Connection": "Conexão",
"Events_TableHead_Date": "Data",
"Events_TableHead_Device": "Dispositivo",
"Events_TableHead_Disconnection": "Desconexão",
"Events_TableHead_Duration": "Duração",
"Events_TableHead_DurationOrder": "Duração do pedido",
"Events_TableHead_EventType": "",
"Events_TableHead_IP": "IP",
"Events_TableHead_IPOrder": "Pedido de IP",
"Events_TableHead_Order": "Ordem",
"Events_TableHead_Owner": "Proprietário",
"Events_TableHead_PendingAlert": "Alerta Pendente",
"Events_Table_info": "A mostrar_START_ to _END_ of _TOTAL_ entradas",
"Events_Table_nav_next": "Próxima",
"Events_Table_nav_prev": "Anterior",
"Events_Tablelenght": "Mostrar entradas do _MENU_",
"Events_Tablelenght_all": "Todos",
"Events_Title": "Eventos",
"GRAPHQL_PORT_description": "O número da porta do servidor GraphQL. Certifique-se de que a porta seja exclusiva em todas as suas aplicações neste host e nas instâncias do NetAlertX.",
"GRAPHQL_PORT_name": "Porta GraphQL",
"Gen_Action": "Ação",
"Gen_Add": "Adicionar",
"Gen_AddDevice": "",
"Gen_Add_All": "Adicionar todos",
"Gen_All_Devices": "",
"Gen_AreYouSure": "Tem certeza?",
"Gen_Backup": "Executar backup",
"Gen_Cancel": "Cancelar",
"Gen_Change": "Alterar",
"Gen_Copy": "Executar",
"Gen_CopyToClipboard": "",
"Gen_DataUpdatedUITakesTime": "OK - Pode levar um tempo para a interface do utilizador ser atualizada se uma verificação estiver em execução.",
"Gen_Delete": "Apagar",
"Gen_DeleteAll": "Apagar todos",
"Gen_Description": "Descrição",
"Gen_Error": "Erro",
"Gen_Filter": "Filtro",
"Gen_Generate": "Gerar",
"Gen_InvalidMac": "",
"Gen_LockedDB": "ERRO - A base de dados pode estar bloqueada - Verifique F12 Ferramentas de desenvolvimento -> Console ou tente mais tarde.",
"Gen_NetworkMask": "",
"Gen_Offline": "Offline",
"Gen_Okay": "Ok",
"Gen_Online": "Online",
"Gen_Purge": "Purge",
"Gen_ReadDocs": "Leia mais em documentos.",
"Gen_Remove_All": "Remover tudo",
"Gen_Remove_Last": "Remover o último",
"Gen_Reset": "Repor",
"Gen_Restore": "Executar restauração",
"Gen_Run": "Executar",
"Gen_Save": "Gravar",
"Gen_Saved": "Gravado",
"Gen_Search": "Procurar",
"Gen_Select": "Selecionar",
"Gen_SelectIcon": "<i class=\"fa-solid fa-chevron-down fa-fade\"></i>",
"Gen_SelectToPreview": "Selecionar para pré-visualizar",
"Gen_Selected_Devices": "",
"Gen_Subnet": "",
"Gen_Switch": "Trocar",
"Gen_Upd": "Atualizado com sucesso",
"Gen_Upd_Fail": "A atualização falhou",
"Gen_Update": "Atualizar",
"Gen_Update_Value": "Atualizar valor",
"Gen_ValidIcon": "<i class=\"fa-solid fa-chevron-right \"></i>",
"Gen_Warning": "Aviso",
"Gen_Work_In_Progress": "Trabalho em andamento, um bom momento para enviar feedback em https://github.com/jokob-sk/NetAlertX/issues",
"Gen_create_new_device": "Novo dispositivo",
"Gen_create_new_device_info": "Os dispositivos são normalmente descobertos usando <a target=\"_blank\" href=\"https://github.com/jokob-sk/NetAlertX/tree/main/docs/PLUGINS.md\">plugins</a>. No entanto, em certos casos, pode ser necessário adicionar dispositivos manualmente. Para explorar cenários específicos, verifique a <a target=\"_blank\" href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/REMOTE_NETWORKS.md\">documentação de Redes Remotas</a>.",
"General_display_name": "Geral",
"General_icon": "<i class=\"fa fa-gears\"></i>",
"HRS_TO_KEEP_NEWDEV_description": "",
"HRS_TO_KEEP_NEWDEV_name": "",
"HRS_TO_KEEP_OFFDEV_description": "",
"HRS_TO_KEEP_OFFDEV_name": "Apagar dispositivos offline após",
"LOADED_PLUGINS_description": "Quais plugins carregar. Adicionar plugins pode deixar a aplicação lenta. Leia mais sobre quais plugins precisam ser ativados, tipos ou opções de escaneamento na <a target=\"_blank\" href=\"https://github.com/jokob-sk/NetAlertX/tree/main/docs/PLUGINS.md\">documentação de plugins</a>. Plugins descarregados perderão as suas configurações. Somente plugins <code>desativados</code> podem ser descarregados.",
"LOADED_PLUGINS_name": "Plugins carregados",
"LOG_LEVEL_description": "Esta definição permite um registo mais detalhado. Útil para depurar eventos gravados na base de dados.",
"LOG_LEVEL_name": "Imprimir registo adicional",
"Loading": "",
"Login_Box": "Introduza a sua palavra-passe",
"Login_Default_PWD": "A palavra-passe predefinida “123456” ainda está ativa.",
"Login_Info": "As palavra-passes são definidas por meio do plugin Definir palavra-passe. Verifique a <a target=\"_blank\" href=\"https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/set_password\">documentação do SETPWD</a> se tiver problemas para fazer login.",
"Login_Psw-box": "Palavra-passe",
"Login_Psw_alert": "Alerta de palavra-passe!",
"Login_Psw_folder": "na pasta de configuração.",
"Login_Psw_new": "nova_palavra-passe",
"Login_Psw_run": "Para alterar a palavra-passe, executar:",
"Login_Remember": "Lembrar",
"Login_Remember_small": "(válido por 7 dias)",
"Login_Submit": "Iniciar sessão",
"Login_Toggle_Alert_headline": "Alerta de palavra-passe!",
"Login_Toggle_Info": "Informações sobre a palavra-passe",
"Login_Toggle_Info_headline": "Informações sobre a palavra-passe",
"Maint_PurgeLog": "Limpar o registo",
"Maint_RestartServer": "Reiniciar o servidor",
"Maint_Restart_Server_noti_text": "Tem certeza de que deseja reiniciar o servidor backend? Isto pode causar inconsistência na app. Faça primeiro um backup da sua configuração. <br/> <br/> Nota: Isto pode levar alguns minutos.",
"Maintenance_InitCheck": "",
"Maintenance_InitCheck_Checking": "",
"Maintenance_InitCheck_QuickSetupGuide": "",
"Maintenance_InitCheck_Success": "",
"Maintenance_ReCheck": "",
"Maintenance_Running_Version": "Versão instalada",
"Maintenance_Status": "Situação",
"Maintenance_Title": "Ferramentas de manutenção",
"Maintenance_Tool_DownloadConfig": "",
"Maintenance_Tool_DownloadConfig_text": "Descarregue um backup completo da configuração das Configurações armazenada no ficheiro <code>app.conf</code>.",
"Maintenance_Tool_DownloadWorkflows": "",
"Maintenance_Tool_DownloadWorkflows_text": "",
"Maintenance_Tool_ExportCSV": "",
"Maintenance_Tool_ExportCSV_noti": "",
"Maintenance_Tool_ExportCSV_noti_text": "Tem a certeza de que pretende gerar um ficheiro CSV?",
"Maintenance_Tool_ExportCSV_text": "Gere um ficheiro CSV (valor separado por vírgula) contendo a lista de dispositivos, incluindo os relacionamentos de rede entre os nós de rede e os dispositivos conectados. Também pode acionar isto a aceder esta URL <code>your_NetAlertX_url/php/server/devices.php?action=ExportCSV</code> ou ativando o plugin <a href=\"settings.php#CSVBCKP_header\">CSV Backup</a>.",
"Maintenance_Tool_ImportCSV": "Importação de dispositivos (csv)",
"Maintenance_Tool_ImportCSV_noti": "Importação de dispositivos (csv)",
"Maintenance_Tool_ImportCSV_noti_text": "Tem certeza de que deseja importar o ficheiro CSV? Isto <b>sobrescreverá</b> completamente os dispositivos na sua base de dados.",
"Maintenance_Tool_ImportCSV_text": "Antes de usar esta função, faça um backup. Importe um ficheiro CSV (valores separados por vírgula) contendo a lista de dispositivos, incluindo os relacionamentos de rede entre os nós de rede e os dispositivos conectados. Para fazer isto, ponha o ficheiro CSV chamado <b>devices.csv</b> na sua pasta <b>/config</b>.",
"Maintenance_Tool_ImportConfig_noti": "Importação de configurações (app.conf)",
"Maintenance_Tool_ImportPastedCSV": "Importação de dispositivos (csv) (colar)",
"Maintenance_Tool_ImportPastedCSV_noti_text": "Tem certeza de que deseja importar o CSV colado? Isto <b>sobrescreverá</b> completamente os dispositivos na sua base de dados.",
"Maintenance_Tool_ImportPastedCSV_text": "Antes de usar esta função, faça um backup. Importe um ficheiro CSV (valor separado por vírgula) contendo a lista de Dispositivos, incluindo os relacionamentos de Rede entre os Nós de Rede e os dispositivos conectados.",
"Maintenance_Tool_ImportPastedConfig": "Configurações Importar (colar)",
"Maintenance_Tool_ImportPastedConfig_noti_text": "Tem certeza de que deseja importar as configurações coladas? Isto irá <b>sobrescrever</b> completamente o ficheiro <code>app.conf</code>.",
"Maintenance_Tool_ImportPastedConfig_text": "Importa o ficheiro <code>app.conf</code> contendo todas as configurações da aplicação. Pode descarregar primeiro o ficheiro <code>app.conf</code> com a <b>Exportação de configurações</b>.",
"Maintenance_Tool_arpscansw": "Alternar arp-Scan (ligado/desligado)",
"Maintenance_Tool_arpscansw_noti": "Ativar ou desativar o arp-Scan",
"Maintenance_Tool_arpscansw_noti_text": "Quando a análise é desligada, permanece desligada até ser novamente ativada.",
"Maintenance_Tool_arpscansw_text": "Ligar ou desligar o arp-scan. Quando o scan é desligado, permanece desligado até ser novamente ativado. As varreduras ativas não são canceladas.",
"Maintenance_Tool_backup": "Cópia de segurança da BD",
"Maintenance_Tool_backup_noti": "Cópia de segurança da BD",
"Maintenance_Tool_backup_noti_text": "Tem a certeza de que pretende executar a cópia de segurança da BD? Certifique-se de que não está a ser executada nenhuma verificação.",
"Maintenance_Tool_backup_text": "Os backups da base de dados estão localizadas no diretório da base de dados como um arquivo zip, nomeado com a data de criação. Não há nenhum número máximo de backups.",
"Maintenance_Tool_check_visible": "Desmarque para esconder a coluna.",
"Maintenance_Tool_darkmode": "Modos de alternância (escuro/claro)",
"Maintenance_Tool_darkmode_noti": "Modos de alternância",
"Maintenance_Tool_darkmode_noti_text": "Após a mudança de tema, a página tenta recarregar-se para ativar a alteração. Se necessário, a cache deve ser limpa.",
"Maintenance_Tool_darkmode_text": "Alternar entre o modo escuro e o modo claro. Se a alternância não funcionar corretamente, tente limpar a cache do browser. A alteração ocorre no lado do servidor, pelo que afecta todos os dispositivos em utilização.",
"Maintenance_Tool_del_ActHistory": "A apagar a atividade da rede",
"Maintenance_Tool_del_ActHistory_noti": "Apagar atividade de rede",
"Maintenance_Tool_del_ActHistory_noti_text": "Tem certeza de que deseja redefinir a atividade da rede?",
"Maintenance_Tool_del_ActHistory_text": "O gráfico de atividade da rede é redefinido. Isto não afeta os eventos.",
"Maintenance_Tool_del_alldev": "",
"Maintenance_Tool_del_alldev_noti": "",
"Maintenance_Tool_del_alldev_noti_text": "Tem certeza de que deseja apagar todos os dispositivos?",
"Maintenance_Tool_del_alldev_text": "Antes de usar esta função, faça um backup. Apagar não pode ser desfeito. Todos os dispositivos serão apagados da base de dados.",
"Maintenance_Tool_del_allevents": "Apagar eventos (Repor presença)",
"Maintenance_Tool_del_allevents30": "Apagar todos os eventos com mais que 30 dias",
"Maintenance_Tool_del_allevents30_noti": "Apagar eventos",
"Maintenance_Tool_del_allevents30_noti_text": "",
"Maintenance_Tool_del_allevents30_text": "Antes de utilizar esta função, faça uma cópia de segurança. Apagar não pode ser anulado. Todos os eventos com mais que 30 dias na base de dados serão eliminados. Nesse momento, a presença de todos os dispositivos será reiniciada. Este facto pode dar origem a sessões inválidas. Isto significa que os dispositivos são apresentados como “presentes” apesar de estarem offline. Uma verificação enquanto o dispositivo em questão está online resolve o problema.",
"Maintenance_Tool_del_allevents_noti": "Apagar eventos",
"Maintenance_Tool_del_allevents_noti_text": "",
"Maintenance_Tool_del_allevents_text": "Antes de usar esta função, faça um backup. Apagar não pode ser desfeito. Todos os eventos na base de dados serão apagados. Nesse momento, a presença de todos os dispositivos será redefinida. Isto pode levar a sessões inválidas. Isto significa que os dispositivos são exibidos como \"presente\" embora estejam offline. Uma varredura enquanto o dispositivo em questão é on-line resolve o problema.",
"Maintenance_Tool_del_empty_macs": "",
"Maintenance_Tool_del_empty_macs_noti": "",
"Maintenance_Tool_del_empty_macs_noti_text": "Tem certeza que deseja apagar todos os dispositivos com endereços MAC vazios?<br>(talvez prefira arquivá-los)",
"Maintenance_Tool_del_empty_macs_text": "Antes de usar esta função, faça um backup. Apagar não pode ser desfeito. Todos os dispositivos sem MAC serão apagados da base de dados.",
"Maintenance_Tool_del_selecteddev": "Apagar dispositivos selecionados",
"Maintenance_Tool_del_selecteddev_text": "Antes de usar esta função, faça um backup. Apagar não pode ser desfeito. Dispositivos selecionados serão apagados da base de dados.",
"Maintenance_Tool_del_unknowndev": "",
"Maintenance_Tool_del_unknowndev_noti": "",
"Maintenance_Tool_del_unknowndev_noti_text": "Tem certeza que deseja apagar todos (desconhecidos) e (nome não encontrados) dispositivos?",
"Maintenance_Tool_del_unknowndev_text": "Antes de usar esta função, faça um backup. Apagar não pode ser desfeito. Todos os dispositivos nomeados (não conhecidos) serão apagados da base de dados.",
"Maintenance_Tool_displayed_columns_text": "Altere a visibilidade e a ordem das colunas na página <a href=\"devices.php\"><b> <i class=\"fa fa-portátil\"></i> Dispositivos</b></a>.",
"Maintenance_Tool_drag_me": "Arraste-me para reordenar colunas.",
"Maintenance_Tool_order_columns_text": "",
"Maintenance_Tool_purgebackup": "Limpar cópias de segurança",
"Maintenance_Tool_purgebackup_noti": "Limpar cópias de segurança",
"Maintenance_Tool_purgebackup_noti_text": "Tem certeza que deseja apagar todos os backups exceto os últimos 3?",
"Maintenance_Tool_purgebackup_text": "Todos os outros backups serão apagados exceto para os últimos 3 backups.",
"Maintenance_Tool_restore": "Restauração de DB",
"Maintenance_Tool_restore_noti": "Restauração de DB",
"Maintenance_Tool_restore_noti_text": "Tem a certeza de que quer executar a Restauração DB? Certifique-se de que nenhuma varredura funciona atualmente.",
"Maintenance_Tool_restore_text": "O backup mais recente pode ser restaurado através do botão, mas os backups mais antigos só podem ser restaurados manualmente. Após a restauração, faça uma verificação de integridade na base de dados para segurança, caso o db estivesse atualmente em acesso de gravação quando o backup foi criado.",
"Maintenance_Tool_upgrade_database_noti": "Atualizar a base de dados",
"Maintenance_Tool_upgrade_database_noti_text": "Tem certeza de que deseja atualizar a base de dados?<br>(talvez prefira arquivá-la)",
"Maintenance_Tool_upgrade_database_text": "Este botão atualizará a base de dados para ativar o gráfico Atividade de rede nas últimas 12 horas. Faça uma cópia de segurança da sua base de dados em caso de problemas.",
"Maintenance_Tools_Tab_BackupRestore": "Backup / Restauração",
"Maintenance_Tools_Tab_Logging": "",
"Maintenance_Tools_Tab_Settings": "Configurações",
"Maintenance_Tools_Tab_Tools": "Ferramentas",
"Maintenance_Tools_Tab_UISettings": "Configurações de interface",
"Maintenance_arp_status": "Estado de digitalização",
"Maintenance_arp_status_off": "está atualmente desativado",
"Maintenance_arp_status_on": "",
"Maintenance_built_on": "Construído em",
"Maintenance_current_version": "Você está atualizado. Confira o que <a href=\"https://github.com/jokob-sk/NetAlertX/issues/138\" target=\"_blank\"> estou a trabalhar em</a>.",
"Maintenance_database_backup": "Backups DB",
"Maintenance_database_backup_found": "foram encontrados backups",
"Maintenance_database_backup_total": "uso total do disco",
"Maintenance_database_lastmod": "Última modificação",
"Maintenance_database_path": "Caminho da base de dados",
"Maintenance_database_rows": "Tabela (linhas)",
"Maintenance_database_size": "Tamanho da base de dados",
"Maintenance_lang_selector_apply": "Aplicar",
"Maintenance_lang_selector_empty": "",
"Maintenance_lang_selector_lable": "",
"Maintenance_lang_selector_text": "A mudança ocorre no lado do cliente, por isso afeta apenas o navegador atual.",
"Maintenance_new_version": "Uma nova versão está disponível. Confira as <a href=\"https://github.com/jokob-sk/NetAlertX/releases\" target=\"_blank\">notas de lançamento</a>.",
"Maintenance_themeselector_apply": "Aplicar",
"Maintenance_themeselector_empty": "Escolha uma Skin",
"Maintenance_themeselector_lable": "Selecionar Skin",
"Maintenance_themeselector_text": "A mudança ocorre no lado do servidor, por isso afeta todos os dispositivos em uso.",
"Maintenance_version": "Atualizações de apps",
"NETWORK_DEVICE_TYPES_description": "",
"NETWORK_DEVICE_TYPES_name": "Tipos de dispositivo de rede",
"Navigation_About": "Sobre a",
"Navigation_AppEvents": "",
"Navigation_Devices": "Dispositivos",
"Navigation_Donations": "Doações",
"Navigation_Events": "Eventos",
"Navigation_Integrations": "Integrações",
"Navigation_Maintenance": "Manutenção",
"Navigation_Monitoring": "Acompanhamento",
"Navigation_Network": "Rede",
"Navigation_Notifications": "Notificações",
"Navigation_Plugins": "Plugins",
"Navigation_Presence": "",
"Navigation_Report": "",
"Navigation_Settings": "",
"Navigation_SystemInfo": "",
"Navigation_Workflows": "",
"Network_Assign": "",
"Network_Cant_Assign": "",
"Network_Cant_Assign_No_Node_Selected": "",
"Network_Configuration_Error": "",
"Network_Connected": "",
"Network_Devices": "",
"Network_ManageAdd": "",
"Network_ManageAdd_Name": "",
"Network_ManageAdd_Name_text": "",
"Network_ManageAdd_Port": "",
"Network_ManageAdd_Port_text": "",
"Network_ManageAdd_Submit": "",
"Network_ManageAdd_Type": "",
"Network_ManageAdd_Type_text": "",
"Network_ManageAssign": "",
"Network_ManageDel": "",
"Network_ManageDel_Name": "",
"Network_ManageDel_Name_text": "",
"Network_ManageDel_Submit": "",
"Network_ManageDevices": "",
"Network_ManageEdit": "",
"Network_ManageEdit_ID": "",
"Network_ManageEdit_ID_text": "",
"Network_ManageEdit_Name": "",
"Network_ManageEdit_Name_text": "",
"Network_ManageEdit_Port": "",
"Network_ManageEdit_Port_text": "",
"Network_ManageEdit_Submit": "",
"Network_ManageEdit_Type": "",
"Network_ManageEdit_Type_text": "",
"Network_ManageLeaf": "",
"Network_ManageUnassign": "",
"Network_NoAssignedDevices": "",
"Network_NoDevices": "",
"Network_Node": "",
"Network_Node_Name": "",
"Network_Parent": "",
"Network_Root": "",
"Network_Root_Not_Configured": "",
"Network_Root_Unconfigurable": "",
"Network_ShowArchived": "",
"Network_ShowOffline": "",
"Network_Table_Hostname": "",
"Network_Table_IP": "",
"Network_Table_State": "",
"Network_Title": "",
"Network_UnassignedDevices": "",
"Notifications_All": "",
"Notifications_Mark_All_Read": "",
"PIALERT_WEB_PASSWORD_description": "",
"PIALERT_WEB_PASSWORD_name": "",
"PIALERT_WEB_PROTECTION_description": "",
"PIALERT_WEB_PROTECTION_name": "",
"PLUGINS_KEEP_HIST_description": "",
"PLUGINS_KEEP_HIST_name": "",
"Plugins_DeleteAll": "",
"Plugins_Filters_Mac": "",
"Plugins_History": "",
"Plugins_Obj_DeleteListed": "",
"Plugins_Objects": "",
"Plugins_Out_of": "",
"Plugins_Unprocessed_Events": "",
"Plugins_no_control": "",
"Presence_CalHead_day": "",
"Presence_CalHead_lang": "",
"Presence_CalHead_month": "",
"Presence_CalHead_quarter": "",
"Presence_CalHead_week": "",
"Presence_CalHead_year": "",
"Presence_CallHead_Devices": "",
"Presence_Key_OnlineNow": "",
"Presence_Key_OnlineNow_desc": "",
"Presence_Key_OnlinePast": "",
"Presence_Key_OnlinePastMiss": "",
"Presence_Key_OnlinePastMiss_desc": "",
"Presence_Key_OnlinePast_desc": "",
"Presence_Loading": "",
"Presence_Shortcut_AllDevices": "",
"Presence_Shortcut_Archived": "",
"Presence_Shortcut_Connected": "",
"Presence_Shortcut_Devices": "",
"Presence_Shortcut_DownAlerts": "",
"Presence_Shortcut_Favorites": "",
"Presence_Shortcut_NewDevices": "",
"Presence_Title": "",
"REFRESH_FQDN_description": "",
"REFRESH_FQDN_name": "",
"REPORT_DASHBOARD_URL_description": "",
"REPORT_DASHBOARD_URL_name": "",
"REPORT_ERROR": "",
"REPORT_MAIL_description": "",
"REPORT_MAIL_name": "",
"REPORT_TITLE": "",
"RandomMAC_hover": "",
"Reports_Sent_Log": "",
"SCAN_SUBNETS_description": "",
"SCAN_SUBNETS_name": "",
"SYSTEM_TITLE": "",
"Setting_Override": "",
"Setting_Override_Description": "",
"Settings_Metadata_Toggle": "",
"Settings_Show_Description": "",
"Settings_device_Scanners_desync": "",
"Settings_device_Scanners_desync_popup": "",
"Speedtest_Results": "",
"Systeminfo_AvailableIps": "",
"Systeminfo_CPU": "",
"Systeminfo_CPU_Cores": "",
"Systeminfo_CPU_Name": "",
"Systeminfo_CPU_Speed": "",
"Systeminfo_CPU_Temp": "",
"Systeminfo_CPU_Vendor": "",
"Systeminfo_Client_Resolution": "",
"Systeminfo_Client_User_Agent": "",
"Systeminfo_General": "",
"Systeminfo_General_Date": "",
"Systeminfo_General_Date2": "",
"Systeminfo_General_Full_Date": "",
"Systeminfo_General_TimeZone": "",
"Systeminfo_Memory": "",
"Systeminfo_Memory_Total_Memory": "",
"Systeminfo_Memory_Usage": "",
"Systeminfo_Memory_Usage_Percent": "",
"Systeminfo_Motherboard": "",
"Systeminfo_Motherboard_BIOS": "",
"Systeminfo_Motherboard_BIOS_Date": "",
"Systeminfo_Motherboard_BIOS_Vendor": "",
"Systeminfo_Motherboard_Manufactured": "",
"Systeminfo_Motherboard_Name": "",
"Systeminfo_Motherboard_Revision": "",
"Systeminfo_Network": "",
"Systeminfo_Network_Accept_Encoding": "",
"Systeminfo_Network_Accept_Language": "",
"Systeminfo_Network_Connection_Port": "",
"Systeminfo_Network_HTTP_Host": "",
"Systeminfo_Network_HTTP_Referer": "",
"Systeminfo_Network_HTTP_Referer_String": "",
"Systeminfo_Network_Hardware": "",
"Systeminfo_Network_Hardware_Interface_Mask": "",
"Systeminfo_Network_Hardware_Interface_Name": "",
"Systeminfo_Network_Hardware_Interface_RX": "",
"Systeminfo_Network_Hardware_Interface_TX": "",
"Systeminfo_Network_IP": "",
"Systeminfo_Network_IP_Connection": "",
"Systeminfo_Network_IP_Server": "",
"Systeminfo_Network_MIME": "",
"Systeminfo_Network_Request_Method": "",
"Systeminfo_Network_Request_Time": "",
"Systeminfo_Network_Request_URI": "",
"Systeminfo_Network_Secure_Connection": "",
"Systeminfo_Network_Secure_Connection_String": "",
"Systeminfo_Network_Server_Name": "",
"Systeminfo_Network_Server_Name_String": "",
"Systeminfo_Network_Server_Query": "",
"Systeminfo_Network_Server_Query_String": "",
"Systeminfo_Network_Server_Version": "",
"Systeminfo_Services": "",
"Systeminfo_Services_Description": "",
"Systeminfo_Services_Name": "",
"Systeminfo_Storage": "",
"Systeminfo_Storage_Device": "",
"Systeminfo_Storage_Mount": "",
"Systeminfo_Storage_Size": "",
"Systeminfo_Storage_Type": "",
"Systeminfo_Storage_Usage": "",
"Systeminfo_Storage_Usage_Free": "",
"Systeminfo_Storage_Usage_Mount": "",
"Systeminfo_Storage_Usage_Total": "",
"Systeminfo_Storage_Usage_Used": "",
"Systeminfo_System": "",
"Systeminfo_System_AVG": "",
"Systeminfo_System_Architecture": "",
"Systeminfo_System_Kernel": "",
"Systeminfo_System_OSVersion": "",
"Systeminfo_System_Running_Processes": "",
"Systeminfo_System_System": "",
"Systeminfo_System_Uname": "",
"Systeminfo_System_Uptime": "",
"Systeminfo_This_Client": "",
"Systeminfo_USB_Devices": "",
"TICKER_MIGRATE_TO_NETALERTX": "",
"TIMEZONE_description": "",
"TIMEZONE_name": "",
"UI_DEV_SECTIONS_description": "",
"UI_DEV_SECTIONS_name": "",
"UI_ICONS_description": "",
"UI_ICONS_name": "",
"UI_LANG_description": "",
"UI_LANG_name": "",
"UI_MY_DEVICES_description": "",
"UI_MY_DEVICES_name": "",
"UI_NOT_RANDOM_MAC_description": "",
"UI_NOT_RANDOM_MAC_name": "",
"UI_PRESENCE_description": "",
"UI_PRESENCE_name": "",
"UI_REFRESH_description": "",
"UI_REFRESH_name": "",
"VERSION_description": "",
"VERSION_name": "",
"WF_Action_Add": "",
"WF_Action_field": "",
"WF_Action_type": "",
"WF_Action_value": "",
"WF_Actions": "",
"WF_Add": "",
"WF_Add_Condition": "",
"WF_Add_Group": "",
"WF_Condition_field": "",
"WF_Condition_operator": "",
"WF_Condition_value": "",
"WF_Conditions": "",
"WF_Conditions_logic_rules": "",
"WF_Duplicate": "",
"WF_Enabled": "",
"WF_Export": "",
"WF_Export_Copy": "",
"WF_Import": "",
"WF_Import_Copy": "",
"WF_Name": "",
"WF_Remove": "",
"WF_Remove_Copy": "",
"WF_Save": "",
"WF_Trigger": "",
"WF_Trigger_event_type": "",
"WF_Trigger_type": "",
"add_icon_event_tooltip": "",
"add_option_event_tooltip": "",
"copy_icons_event_tooltip": "",
"devices_old": "",
"general_event_description": "",
"general_event_title": "",
"go_to_device_event_tooltip": "",
"go_to_node_event_tooltip": "",
"new_version_available": "",
"report_guid": "",
"report_guid_missing": "",
"report_select_format": "",
"report_time": "",
"run_event_tooltip": "",
"select_icon_event_tooltip": "",
"settings_core_icon": "",
"settings_core_label": "",
"settings_device_scanners": "",
"settings_device_scanners_icon": "",
"settings_device_scanners_info": "",
"settings_device_scanners_label": "",
"settings_enabled": "",
"settings_enabled_icon": "",
"settings_expand_all": "",
"settings_imported": "",
"settings_imported_label": "",
"settings_missing": "",
"settings_missing_block": "",
"settings_old": "",
"settings_other_scanners": "",
"settings_other_scanners_icon": "",
"settings_other_scanners_label": "",
"settings_publishers": "",
"settings_publishers_icon": "",
"settings_publishers_info": "",
"settings_publishers_label": "",
"settings_readonly": "",
"settings_saved": "",
"settings_system_icon": "",
"settings_system_label": "",
"settings_update_item_warning": "",
"test_event_tooltip": "Guarde as alterações antes de testar as definições."
}

View File

@@ -107,7 +107,7 @@
"DevDetail_Network_Node_hover": "Выберите родительское сетевое устройство, к которому подключено текущее устройство, чтобы заполнить дерево сети.",
"DevDetail_Network_Port_hover": "Порт, к которому подключено это устройство на родительском сетевом устройстве. Если оставить пустым, в дереве сети отобразится значок Wi-Fi.",
"DevDetail_Nmap_Scans": "Ручные сканеры Nmap",
"DevDetail_Nmap_Scans_desc": "Здесь вы можете выполнить сканирование NMAP вручную. Вы также можете запланировать регулярное автоматическое сканирование NMAP с помощью плагина «Службы и порты» (NMAP). Чтобы узнать больше, перейдите в <a href='/settings.php' target='_blank'>Настройки</a>",
"DevDetail_Nmap_Scans_desc": "Здесь вы можете выполнить сканирование NMAP вручную. Вы также можете запланировать регулярное автоматическое сканирование NMAP с помощью плагина «Службы и порты» (NMAP). Чтобы узнать больше, перейдите в <a href=\"https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/nmap_scan\" target=\"_blank\">Документацию</a>",
"DevDetail_Nmap_buttonDefault": "Сканирование по умолчанию",
"DevDetail_Nmap_buttonDefault_text": "Сканирование по умолчанию: Nmap сканирует 1000 верхних портов для каждого запрошенного протокола сканирования. Это перехватывает примерно 93% портов TCP и 49% портов UDP. (около 5 секунд)",
"DevDetail_Nmap_buttonDetail": "Детальное сканирование",
@@ -301,6 +301,7 @@
"Gen_Cancel": "Отмена",
"Gen_Change": "Изменить",
"Gen_Copy": "Запустить",
"Gen_CopyToClipboard": "Копировать в буфер обмена",
"Gen_DataUpdatedUITakesTime": "ОК - Обновление UI может занять некоторое время, если сканирование выполняется.",
"Gen_Delete": "Удалить",
"Gen_DeleteAll": "Удалить все",
@@ -308,8 +309,9 @@
"Gen_Error": "Ошибка",
"Gen_Filter": "Фильтр",
"Gen_Generate": "Генерировать",
"Gen_InvalidMac": "Неверный Mac-адрес.",
"Gen_LockedDB": "ОШИБКА - Возможно, база данных заблокирована. Проверьте инструменты разработчика F12 -> Консоль или повторите попытку позже.",
"Gen_NetworkMask": "",
"Gen_NetworkMask": "Маска сети",
"Gen_Offline": "Оффлайн",
"Gen_Okay": "OK",
"Gen_Online": "Онлайн",
@@ -327,7 +329,7 @@
"Gen_SelectIcon": "<i class=\"fa-solid fa-chevron-down fa-fade\"></i>",
"Gen_SelectToPreview": "Выберите для предварительного просмотра",
"Gen_Selected_Devices": "Выбранные устройства:",
"Gen_Subnet": "",
"Gen_Subnet": "Подсеть",
"Gen_Switch": "Переключить",
"Gen_Upd": "Успешное обновление",
"Gen_Upd_Fail": "Не удалось обновить",
@@ -565,7 +567,7 @@
"Presence_Key_OnlineNow_desc": "Устройство, обнаруженное при последнем сканировании как подключенное к сети.",
"Presence_Key_OnlinePast": "В прошлом в сети",
"Presence_Key_OnlinePastMiss": "В прошлом в сети (несовпадение)",
"Presence_Key_OnlinePastMiss_desc": "Устройство в прошлом было подключено к сети, но сейчас находится в автономном режиме, однако стартовый сеанс может отсутствовать или иметь противоречивые данные. (Возможно, это ошибка — отправьте PR, если знаете, как это исправить — здесь я немного запутался в коде)",
"Presence_Key_OnlinePastMiss_desc": "Устройство в прошлом было подключено к сети, но сейчас находится в автономном режиме, однако стартовый сеанс может отсутствовать или иметь противоречивые данные.",
"Presence_Key_OnlinePast_desc": "Устройство раньше было в сети, но в настоящее время не в сети.",
"Presence_Loading": "Загрузка…",
"Presence_Shortcut_AllDevices": "Мои устройства",
@@ -596,7 +598,7 @@
"Settings_device_Scanners_desync": "⚠ Расписания сканера устройств не синхронизированы.",
"Settings_device_Scanners_desync_popup": "Расписания сканеров устройств (<code>*_RUN_SCHD</code>) не совпадают. Это приведет к несогласованным онлайн/оффлайн уведомлениям устройства. Если это не предусмотрено, используйте одно и то же расписание для всех включенных <b>🔍Сканеров устройств</b>.",
"Speedtest_Results": "Результаты теста скорости",
"Systeminfo_AvailableIps": "",
"Systeminfo_AvailableIps": "Доступные IP-адреса",
"Systeminfo_CPU": "CPU",
"Systeminfo_CPU_Cores": "Ядра CPU:",
"Systeminfo_CPU_Name": "Имя CPU:",
@@ -675,7 +677,7 @@
"TIMEZONE_description": "Часовой пояс для корректного отображения статистики. Найдите свой часовой пояс <a target=\"_blank\" href=\"https://en.wikipedia.org/wiki/List_of_tz_database_time_zones\" rel=\"nofollow\">здесь</a>.",
"TIMEZONE_name": "Часовой пояс",
"UI_DEV_SECTIONS_description": "Выберите, какие элементы интерфейса нужно скрыть на страницах «Устройства».",
"UI_DEV_SECTIONS_name": "Скрыть разделы Устройств",
"UI_DEV_SECTIONS_name": "Скрыть разделы устройств",
"UI_ICONS_description": "Список предопределенных значков. Действуйте осторожно: предпочтительный способ добавления значков описан в разделе <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/ICONS.md\" target=\"_blank\"> документации по значкам</a>. Вы можете добавить HTML-тег SVG в кодировке Base64 или HTML-тег Font-awesome.",
"UI_ICONS_name": "Предопределенные значки",
"UI_LANG_description": "Выберите предпочтительный язык пользовательского интерфейса. Помогите перевести или предложите языки на онлайн-портале <a href=\"https://hosted.weblate.org/projects/pialert/core/\" target=\"_blank\">Weblate</a>.",
@@ -735,7 +737,7 @@
"settings_core_label": "Основные",
"settings_device_scanners": "Сканеры устройств, используемые для обнаружения устройств, записывающих данные в таблицу базы данных CurrentScan.",
"settings_device_scanners_icon": "fa-solid fa-magnifying-glass-plus",
"settings_device_scanners_info": "Загрузите еще больше сканеров устройств с помощью параметра <a href=\"/settings.php#LOADED_PLUGINS\">LOADED_PLUGINS</a>",
"settings_device_scanners_info": "Загрузите больше сканеров устройств с помощью параметра <a href=\"/settings.php#LOADED_PLUGINS\">LOADED_PLUGINS</a>",
"settings_device_scanners_label": "Сканеры устройств",
"settings_enabled": "Вкл. настройки",
"settings_enabled_icon": "fa-solid fa-toggle-on",
@@ -758,4 +760,4 @@
"settings_system_label": "Система",
"settings_update_item_warning": "Обновить значение ниже. Будьте осторожны, следуя предыдущему формату. <b>Проверка не выполняется.</b>",
"test_event_tooltip": "Сначала сохраните изменения, прежде чем проверять настройки."
}
}

View File

@@ -301,6 +301,7 @@
"Gen_Cancel": "İptal",
"Gen_Change": "Değiştir",
"Gen_Copy": "Çalıştır",
"Gen_CopyToClipboard": "",
"Gen_DataUpdatedUITakesTime": "TAMAM - Eğer bir tarama çalışıyorsa arayüzün güncellenmesi biraz zaman alabilir.",
"Gen_Delete": "Sil",
"Gen_DeleteAll": "Tümünü sil",
@@ -308,6 +309,7 @@
"Gen_Error": "Hata",
"Gen_Filter": "Filtre",
"Gen_Generate": "Oluştur",
"Gen_InvalidMac": "",
"Gen_LockedDB": "HATA - Veritabanı kilitlenmiş olabilir - F12 Geliştirici araçlarını -> Konsol kısmını kontrol edin veya daha sonra tekrar deneyin.",
"Gen_NetworkMask": "",
"Gen_Offline": "Çevrimdışı",

8
front/php/templates/language/uk_ua.json Normal file → Executable file
View File

@@ -301,6 +301,7 @@
"Gen_Cancel": "Скасувати",
"Gen_Change": "Зміна",
"Gen_Copy": "Запустити",
"Gen_CopyToClipboard": "Копіювати в буфер обміну",
"Gen_DataUpdatedUITakesTime": "Добре. Оновлення інтерфейсу може зайняти деякий час, якщо сканування виконується.",
"Gen_Delete": "Видалити",
"Gen_DeleteAll": "Видалити все",
@@ -308,8 +309,9 @@
"Gen_Error": "Помилка",
"Gen_Filter": "Фільтр",
"Gen_Generate": "Генерувати",
"Gen_InvalidMac": "Недійсна Mac-адреса.",
"Gen_LockedDB": "ПОМИЛКА БД може бути заблоковано перевірте F12 Інструменти розробника -> Консоль або спробуйте пізніше.",
"Gen_NetworkMask": "",
"Gen_NetworkMask": "Маска мережі",
"Gen_Offline": "Офлайн",
"Gen_Okay": "Гаразд",
"Gen_Online": "Онлайн",
@@ -629,7 +631,7 @@
"Systeminfo_Network_HTTP_Referer": "HTTP реферер:",
"Systeminfo_Network_HTTP_Referer_String": "Немає реферера HTTP",
"Systeminfo_Network_Hardware": "Мережеве обладнання",
"Systeminfo_Network_Hardware_Interface_Mask": "Маска мережі",
"Systeminfo_Network_Hardware_Interface_Mask": "Маска Мережі",
"Systeminfo_Network_Hardware_Interface_Name": "Назва інтерфейсу",
"Systeminfo_Network_Hardware_Interface_RX": "Отримано",
"Systeminfo_Network_Hardware_Interface_TX": "Передано",
@@ -758,4 +760,4 @@
"settings_system_label": "Система",
"settings_update_item_warning": "Оновіть значення нижче. Слідкуйте за попереднім форматом. <b>Перевірка не виконана.</b>",
"test_event_tooltip": "Перш ніж перевіряти налаштування, збережіть зміни."
}
}

View File

@@ -301,6 +301,7 @@
"Gen_Cancel": "取消",
"Gen_Change": "修改",
"Gen_Copy": "运行",
"Gen_CopyToClipboard": "复制到剪贴板",
"Gen_DataUpdatedUITakesTime": "好的 - 如果扫描正在运行UI 可能需要一段时间才能更新。",
"Gen_Delete": "删除",
"Gen_DeleteAll": "全部删除",
@@ -308,8 +309,9 @@
"Gen_Error": "错误",
"Gen_Filter": "筛选",
"Gen_Generate": "生成",
"Gen_InvalidMac": "无效的 Mac 地址。",
"Gen_LockedDB": "错误 - DB 可能被锁定 - 检查 F12 开发工具 -> 控制台或稍后重试。",
"Gen_NetworkMask": "",
"Gen_NetworkMask": "网络掩码",
"Gen_Offline": "离线",
"Gen_Okay": "Ok",
"Gen_Online": "在线",
@@ -327,7 +329,7 @@
"Gen_SelectIcon": "<i class=\"fa-solid fa-chevron-down fa-fade\"></i>",
"Gen_SelectToPreview": "选择预览",
"Gen_Selected_Devices": "选定的设备:",
"Gen_Subnet": "",
"Gen_Subnet": "子网",
"Gen_Switch": "交换",
"Gen_Upd": "已成功更新",
"Gen_Upd_Fail": "更新失败",
@@ -496,7 +498,7 @@
"Network_Cant_Assign_No_Node_Selected": "无法分配,未选择父节点。",
"Network_Configuration_Error": "配置错误",
"Network_Connected": "联网设备",
"Network_Devices": "",
"Network_Devices": "网络设备",
"Network_ManageAdd": "添加设备",
"Network_ManageAdd_Name": "设备名称",
"Network_ManageAdd_Name_text": "名称不包含特殊字符",
@@ -565,7 +567,7 @@
"Presence_Key_OnlineNow_desc": "该设备在上次扫描中被检测为在线。",
"Presence_Key_OnlinePast": "之前在线",
"Presence_Key_OnlinePastMiss": "之前在线(不匹配)",
"Presence_Key_OnlinePastMiss_desc": "设备曾在线,但目前离线初始会话可能缺失或存在数据冲突",
"Presence_Key_OnlinePastMiss_desc": "设备曾在线,但目前离线初始会话可能缺失或存在数据冲突",
"Presence_Key_OnlinePast_desc": "设备曾在线,但目前离线。",
"Presence_Loading": "加载中…",
"Presence_Shortcut_AllDevices": "我的设备",
@@ -596,7 +598,7 @@
"Settings_device_Scanners_desync": "⚠ 设备扫描计划不同步。",
"Settings_device_Scanners_desync_popup": "设备扫描 (<code>*_RUN_SCHD</code>) 的时间表并不相同。这将导致设备在线/离线通知不一致。除非有意为之,否则请对所有启用的 <b>🔍设备扫描</b> 使用相同的时间表。",
"Speedtest_Results": "Speedtest 结果",
"Systeminfo_AvailableIps": "",
"Systeminfo_AvailableIps": "可用 IP",
"Systeminfo_CPU": "CPU",
"Systeminfo_CPU_Cores": "CPU 核心:",
"Systeminfo_CPU_Name": "CPU 名称:",
@@ -758,4 +760,4 @@
"settings_system_label": "系统",
"settings_update_item_warning": "更新下面的值。请注意遵循先前的格式。<b>未执行验证。</b>",
"test_event_tooltip": "在测试设置之前,请先保存更改。"
}
}

View File

@@ -1,6 +1,6 @@
<?php
require 'php/templates/notification.php';
require 'php/templates/modals.php';
//------------------------------------------------------------------------------
// check if authenticated
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';

View File

@@ -117,6 +117,29 @@
</div>
</div>
<!-- Modal form input -->
<div class="modal fade" id="modal-form" data-myparam-triggered-by="" style="display: none;">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 id="modal-form-title" class="modal-title"> Modal Title </h4>
</div>
<div id="modal-form-message" class="modal-body"> Modal message </div>
<div id="modal-form-plc"></div>
<div class="modal-footer">
<button id="modal-form-cancel" type="button" class="btn btn-outline pull-left" style="min-width: 80px;" data-dismiss="modal"> Cancel </button>
<button id="modal-form-OK" type="button" class="btn btn-outline btn-modal-submit" style="min-width: 80px;" > OK </button>
</div>
</div>
</div>
</div>
<!-- Modal field input -->
<div class="modal modal-warning fade" id="modal-field-input" data-myparam-triggered-by="" style="display: none;">

View File

@@ -1,7 +1,7 @@
<?php
require 'php/templates/header.php';
require 'php/templates/notification.php';
require 'php/templates/modals.php';
?>
<!-- Page ------------------------------------------------------------------ -->

View File

@@ -642,7 +642,7 @@
{ "elementType": "input", "elementOptions": [], "transformers": [] }
]
},
"default_value": "user@gmail.com",
"default_value": "to@email.com",
"options": [],
"localized": ["name", "description"],
"name": [
@@ -674,7 +674,31 @@
{ "elementType": "input", "elementOptions": [], "transformers": [] }
]
},
"default_value": "NetAlertX <user@gmail.com>",
"default_value": "NetAlertX <from@email.com>",
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "From email"
}
],
"description": [
{
"language_code": "en_us",
"string": "From which email the notification is sent <code>Name &lt;email&gt;</code>. Some SMTP servers need this to be an email only."
}
]
},
{
"function": "SUBJECT",
"type": {
"dataType": "string",
"elements": [
{ "elementType": "input", "elementOptions": [], "transformers": [] }
]
},
"default_value": "NetAlertX Report",
"options": [],
"localized": ["name", "description"],
"name": [
@@ -690,7 +714,7 @@
"description": [
{
"language_code": "en_us",
"string": "Notification email subject line. Some SMTP servers need this to be an email."
"string": "Notification email subject line."
},
{
"language_code": "es_es",

View File

@@ -115,8 +115,7 @@ def send(pHTML, pText):
mylog('debug', [f'[{pluginName}] SMTP_REPORT_TO: {hide_email(str(get_setting_value("SMTP_REPORT_TO")))} SMTP_USER: {hide_email(str(get_setting_value("SMTP_USER")))}'])
subject, from_email, to_email, message_html, message_text = sanitize_email_content('NetAlertX Report', get_setting_value("SMTP_REPORT_FROM"), get_setting_value("SMTP_REPORT_TO"), pHTML, pText)
subject, from_email, to_email, message_html, message_text = sanitize_email_content(str(get_setting_value("SMTP_SUBJECT")), get_setting_value("SMTP_REPORT_FROM"), get_setting_value("SMTP_REPORT_TO"), pHTML, pText)
emails = []

View File

@@ -4,6 +4,7 @@ from pytz import timezone, all_timezones, UnknownTimeZoneError
import sys
import re
import base64
import json
from datetime import datetime
INSTALL_PATH = "/app"
@@ -116,6 +117,51 @@ def decodeBase64(inputParamBase64):
return result
# -------------------------------------------------------------------
def decode_settings_base64(encoded_str, convert_types=True):
"""
Decodes a base64-encoded JSON list of settings into a dict.
Each setting entry format:
[group, key, type, value]
Example:
[
["group", "name", "string", "Home - local"],
["group", "base_url", "string", "https://..."],
["group", "api_version", "integer", "2"],
["group", "verify_ssl", "boolean", "False"]
]
Returns:
{
"name": "Home - local",
"base_url": "https://...",
"api_version": 2,
"verify_ssl": False
}
"""
decoded_json = base64.b64decode(encoded_str).decode("utf-8")
settings_list = json.loads(decoded_json)
settings_dict = {}
for _, key, _type, value in settings_list:
if convert_types:
_type_lower = _type.lower()
if _type_lower == "boolean":
settings_dict[key] = value.lower() == "true"
elif _type_lower == "integer":
settings_dict[key] = int(value)
elif _type_lower == "float":
settings_dict[key] = float(value)
else:
settings_dict[key] = value
else:
settings_dict[key] = value
return settings_dict
# -------------------------------------------------------------------
def normalize_mac(mac):
# Split the MAC address by colon (:) or hyphen (-) and convert each part to uppercase

View File

@@ -41,7 +41,7 @@ The plugin operates in three different modes based on the configuration settings
- **Schedule** `[n,h]`: `SYNC_RUN_SCHD`
- **Encryption Key** `[n,h]`: `SYNC_encryption_key`
- **Node Name** `[n]`: `SYNC_node_name`
- **Hub URL** `[n]`: `SYNC_hub_url`
- **Hub URL** `[n]`: `SYNC_hub_url` + `GRAPHQL_PORT` of the target
- **Sync Devices** `[n]`: `SYNC_devices`
- **Sync Plugins** `[n]`: `SYNC_plugins`
@@ -52,7 +52,7 @@ The plugin operates in three different modes based on the configuration settings
- **When to Run** `[n,h]`: `SYNC_RUN`
- **Schedule** `[n,h]`: `SYNC_RUN_SCHD`
- **Encryption Key** `[n,h]`: `SYNC_encryption_key`
- **Nodes to Pull From** `[h]`: `SYNC_nodes`
- **Nodes to Pull From** `[h]`: `SYNC_nodes` + `GRAPHQL_PORT` of the source nodes
### Usage
@@ -115,7 +115,7 @@ Initially, I had one virtual machine (VM) with 6 network cards, one for each VLA
2. Set the schedule (5 minutes works for me).
3. **API Token**: Use any string, but it must match the clients (e.g., `abc123`).
4. **Encryption Key**: Use any string, but it must match the clients (e.g., `abc123`).
5. Under **Nodes**, add the full URL for each client, e.g., `http://192.168.1.20.20211/`.
5. Under **Nodes**, add the full URL for each client, e.g., `http://192.168.1.20.20212/`, where the port `20212` is the value of the `GRAPHQL_PORT` setting of the given node (client)
6. **Node Name**: Leave blank.
7. Check **Sync Devices**.

View File

@@ -125,11 +125,11 @@
},
{
"language_code": "es_es",
"string": "Solo está habilitado si selecciona <code>schedule</code> en la configuración <a href=\"#SYNC_RUN\"><code>SYNC_RUN</code></a>. Asegúrese de ingresar la programación en el formato similar a cron correcto (por ejemplo, valide en <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). Por ejemplo, ingresar <code>0 4 * * *</code> ejecutará el escaneo después de las 4 a.m. en el <a onclick=\"toggleAllSettings()\" href=\"#TIMEZONE\"><code>TIMEZONE</ código> que configuró arriba</a>. Se ejecutará la PRÓXIMA vez que pase el tiempo."
"string": "Solo está habilitado si selecciona <code>schedule</code> en la configuración <a href=\"#SYNC_RUN\"><code>SYNC_RUN</code></a>. Asegúrese de ingresar la programación en el formato similar a cron correcto (por ejemplo, valide en <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). Por ejemplo, ingresar <code>0 4 * * *</code> ejecutará el escaneo después de las 4 a.m. en el <a onclick=\"toggleAllSettings()\" href=\"#TIMEZONE\"><code>TIMEZONE</código> que configuró arriba</a>. Se ejecutará la PRÓXIMA vez que pase el tiempo."
},
{
"language_code": "de_de",
"string": "Nur aktiviert, wenn Sie <code>schedule</code> in der <a href=\"#SYNC_RUN\"><code>SYNC_RUN</code>-Einstellung</a> auswählen. Stellen Sie sicher, dass Sie den Zeitplan im richtigen Cron-ähnlichen Format eingeben (z. B. validieren unter <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). Wenn Sie beispielsweise <code>0 4 * * *</code> eingeben, wird der Scan nach 4 Uhr morgens in der <a onclick=\"toggleAllSettings()\" href=\"#TIMEZONE\"><code>TIMEZONE</ ausgeführt. Code> den Sie oben festgelegt haben</a>. Wird das NÄCHSTE Mal ausgeführt, wenn die Zeit vergeht."
"string": "Nur aktiviert, wenn Sie <code>schedule</code> in der <a href=\"#SYNC_RUN\"><code>SYNC_RUN</code>-Einstellung</a> auswählen. Stellen Sie sicher, dass Sie den Zeitplan im richtigen Cron-ähnlichen Format eingeben (z. B. validieren unter <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). Wenn Sie beispielsweise <code>0 4 * * *</code> eingeben, wird der Scan nach 4 Uhr morgens in der <a onclick=\"toggleAllSettings()\" href=\"#TIMEZONE\"><code>TIMEZONE</code> ausgeführt, den Sie oben festgelegt haben</a>. Wird das NÄCHSTE Mal ausgeführt, wenn die Zeit vergeht."
}
]
},
@@ -245,7 +245,7 @@
"description": [
{
"language_code": "en_us",
"string": "If specified, the hub will pull Devices data from the listed nodes. The <code>API_TOKEN</code> and <code>SYNC_encryption_key</code> must be set to the same value across the hub and all the nodes to ensure proper authentication and communication."
"string": "If specified, the hub will pull Devices data from the listed nodes. The <code>API_TOKEN</code> and <code>SYNC_encryption_key</code> must be set to the same value across the hub and all the nodes to ensure proper authentication and communication. Add full host URL and use the value of the <code>GRAPHQL_PORT</code> setting of the target, as the port."
}
]
},
@@ -271,7 +271,7 @@
"description": [
{
"language_code": "en_us",
"string": "The URL of the hub (target instance). Set on the Node. Without a trailig slash, for example <code>http://192.168.1.82:20211</code>"
"string": "If specified, this node will push data to the <code>GRAPHQL_PORT</code> API of the target. Use the target device <code>GRAPHQL_PORT</code> URL Without a trailing slash, for example <code>http://192.168.1.82:20212</code>"
}
]
},
@@ -296,7 +296,7 @@
"description": [
{
"language_code": "en_us",
"string": "Use a unique node name, without spaces or special characters, such as <code>Node_Vlan01</code>"
"string": "The unique name of this node, without spaces or special characters, such as <code>Node_Vlan01</code>"
}
]
},

View File

@@ -28,7 +28,7 @@ from pytz import timezone
conf.tz = timezone(get_setting_value('TIMEZONE'))
# Make sure log level is initialized correctly
Logger(get_setting_value('LOG_LEVEL'))
lggr = Logger(get_setting_value('LOG_LEVEL'))
pluginName = 'SYNC'
@@ -148,7 +148,8 @@ def main():
message = f'[{pluginName}] Device data from node "{node_name}" written to {log_file_name}'
mylog('verbose', [message])
write_notification(message, 'info', timeNowTZ())
if lggr.isAbove('verbose'):
write_notification(message, 'info', timeNowTZ())
# Process any received data for the Device DB table (ONLY JSON)
@@ -265,66 +266,81 @@ def main():
return 0
# ------------------------------------------------------------------
# Data retrieval methods
api_endpoints = [
f"/sync", # New Python-based endpoint
f"/plugins/sync/hub.php" # Legacy PHP endpoint
]
# send data to the HUB
def send_data(api_token, file_content, encryption_key, file_path, node_name, pref, hub_url):
# Encrypt the log data using the encryption_key
"""Send encrypted data to HUB, preferring /sync endpoint and falling back to PHP version."""
encrypted_data = encrypt_data(file_content, encryption_key)
mylog('verbose', [f'[{pluginName}] Sending encrypted_data: "{encrypted_data}"'])
# Prepare the data payload for the POST request
data = {
'data': encrypted_data,
'file_path': file_path,
'plugin': pref,
'node_name': node_name
}
# Set the authorization header with the API token
headers = {'Authorization': f'Bearer {api_token}'}
api_endpoint = f"{hub_url}/plugins/sync/hub.php"
response = requests.post(api_endpoint, data=data, headers=headers)
mylog('verbose', [f'[{pluginName}] response: "{response}"'])
for endpoint in api_endpoints:
final_endpoint = hub_url + endpoint
try:
response = requests.post(final_endpoint, data=data, headers=headers, timeout=5)
mylog('verbose', [f'[{pluginName}] Tried endpoint: {final_endpoint}, status: {response.status_code}'])
if response.status_code == 200:
message = f'[{pluginName}] Data for "{file_path}" sent successfully via {final_endpoint}'
mylog('verbose', [message])
write_notification(message, 'info', timeNowTZ())
return True
except requests.RequestException as e:
mylog('verbose', [f'[{pluginName}] Error calling {final_endpoint}: {e}'])
# If all endpoints fail
message = f'[{pluginName}] Failed to send data for "{file_path}" via all endpoints'
mylog('verbose', [message])
write_notification(message, 'alert', timeNowTZ())
return False
if response.status_code == 200:
message = f'[{pluginName}] Data for "{file_path}" sent successfully'
mylog('verbose', [message])
write_notification(message, 'info', timeNowTZ())
else:
message = f'[{pluginName}] Failed to send data for "{file_path}" (Status code: {response.status_code})'
mylog('verbose', [message])
write_notification(message, 'alert', timeNowTZ())
# get data from the nodes to the HUB
def get_data(api_token, node_url):
"""Get data from NODE, preferring /sync endpoint and falling back to PHP version."""
mylog('verbose', [f'[{pluginName}] Getting data from node: "{node_url}"'])
# Set the authorization header with the API token
headers = {'Authorization': f'Bearer {api_token}'}
api_endpoint = f"{node_url}/plugins/sync/hub.php"
response = requests.get(api_endpoint, headers=headers)
# mylog('verbose', [f'[{pluginName}] response: "{response.text}"'])
for endpoint in api_endpoints:
final_endpoint = node_url + endpoint
if response.status_code == 200:
try:
# Parse JSON response
response_json = response.json()
return response_json
response = requests.get(final_endpoint, headers=headers, timeout=5)
mylog('verbose', [f'[{pluginName}] Tried endpoint: {final_endpoint}, status: {response.status_code}'])
except json.JSONDecodeError:
message = f'[{pluginName}] Failed to parse JSON response from "{node_url}"'
mylog('verbose', [message])
write_notification(message, 'alert', timeNowTZ())
return ""
if response.status_code == 200:
try:
return response.json()
except json.JSONDecodeError:
message = f'[{pluginName}] Failed to parse JSON from {final_endpoint}'
mylog('verbose', [message])
write_notification(message, 'alert', timeNowTZ())
return ""
except requests.RequestException as e:
mylog('verbose', [f'[{pluginName}] Error calling {final_endpoint}: {e}'])
else:
message = f'[{pluginName}] Failed to send data for "{node_url}" (Status code: {response.status_code})'
mylog('verbose', [message])
write_notification(message, 'alert', timeNowTZ())
return ""
# If all endpoints fail
message = f'[{pluginName}] Failed to get data from "{node_url}" via all endpoints'
mylog('verbose', [message])
write_notification(message, 'alert', timeNowTZ())
return ""

View File

@@ -0,0 +1,25 @@
## Overview
Unifi import plugin using the Site Manager API.
> [!TIP]
> The Site Manager API doesn't seems to have feature parity with the old API yet, so certain limitations apply.
### Quick setup guide
Navigate to your UniFi Site Manager _Settings -> Control Plane -> Integrations_.
- `UNIFIAPI_api_key` : You can generate your API key under the _Your API Keys_ section.
- `UNIFIAPI_base_url` : You can find your base url in the _API Request Format_ section, e.g. `https://192.168.100.1/proxy/network/integration/`
- `UNIFIAPI_api_version` : You can find your version as part of the url in the _API Request Format_ section, e.g. `v1`
- `UNIFIAPI_verify_ssl` : To skip SSL with you don't have an SSL certificate
### Usage
- Head to **Settings** > **Plugin name** to adjust the default values.
### Notes
- Version: 1.0.0
- Author: `jokob-sk`
- Release Date: `Aug 2025`

View File

@@ -0,0 +1,717 @@
{
"code_name": "unifi_api_import",
"unique_prefix": "UNIFIAPI",
"plugin_type": "device_scanner",
"execution_order": "Layer_0",
"enabled": true,
"data_source": "script",
"mapped_to_table": "CurrentScan",
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
"compare_use_quotes": true
}
],
"show_ui": true,
"localized": [
"display_name",
"description",
"icon"
],
"display_name": [
{
"language_code": "en_us",
"string": "UniFi import (API)"
}
],
"description": [
{
"language_code": "en_us",
"string": "This plugin is used to import devices from an UNIFI controller via the Site Manager API."
}
],
"icon": [
{
"language_code": "en_us",
"string": "<i class=\"fa-solid fa-shield-halved\"></i>"
}
],
"params": [],
"settings": [
{
"function": "RUN",
"events": [
"run"
],
"type": {
"dataType": "string",
"elements": [
{
"elementType": "select",
"elementOptions": [],
"transformers": []
}
]
},
"default_value": "disabled",
"options": [
"disabled",
"once",
"schedule"
],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "When to run"
}
],
"description": [
{
"language_code": "en_us",
"string": "When the plugin should run. Good options are <code>schedule</code>, <code>once</code>."
}
]
},
{
"function": "RUN_SCHD",
"type": {
"dataType": "string",
"elements": [
{
"elementType": "span",
"elementOptions": [
{
"cssClasses": "input-group-addon validityCheck"
},
{
"getStringKey": "Gen_ValidIcon"
}
],
"transformers": []
},
{
"elementType": "input",
"elementOptions": [
{
"onChange": "validateRegex(this)"
},
{
"base64Regex": "Xig/OlwqfCg/OlswLTldfFsxLTVdWzAtOV18WzAtOV0rLVswLTldK3xcKi9bMC05XSspKVxzKyg/OlwqfCg/OlswLTldfDFbMC05XXwyWzAtM118WzAtOV0rLVswLTldK3xcKi9bMC05XSspKVxzKyg/OlwqfCg/OlsxLTldfFsxMl1bMC05XXwzWzAxXXxbMC05XSstWzAtOV0rfFwqL1swLTldKykpXHMrKD86XCp8KD86WzEtOV18MVswLTJdfFswLTldKy1bMC05XSt8XCovWzAtOV0rKSlccysoPzpcKnwoPzpbMC02XXxbMC02XS1bMC02XXxcKi9bMC05XSspKSQ="
}
],
"transformers": []
}
]
},
"default_value": "*/5 * * * *",
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Schedule"
}
],
"description": [
{
"language_code": "en_us",
"string": "Only enabled if you select <code>schedule</code> in the <a href=\"#UNIFIAPI_RUN\"><code>UNIFIAPI_RUN</code> setting</a>. Make sure you enter the schedule in the correct cron-like format (e.g. validate at <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). For example entering <code>0 4 * * *</code> will run the scan after 4 am in the <a onclick=\"toggleAllSettings()\" href=\"#TIMEZONE\"><code>TIMEZONE</code> you set above</a>. Will be run NEXT time the time passes."
}
]
},
{
"function": "CMD",
"type": {
"dataType": "string",
"elements": [
{
"elementType": "input",
"elementOptions": [
{
"readonly": "true"
}
],
"transformers": []
}
]
},
"default_value": "python3 /app/front/plugins/unifi_api_import/unifi_api_import.py",
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Command"
}
],
"description": [
{
"language_code": "en_us",
"string": "Command to run. This can not be changed"
}
]
},
{
"function": "RUN_TIMEOUT",
"type": {
"dataType": "integer",
"elements": [
{
"elementType": "input",
"elementOptions": [
{
"type": "number"
}
],
"transformers": []
}
]
},
"default_value": 10,
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Run timeout"
}
],
"description": [
{
"language_code": "en_us",
"string": "Maximum time in seconds to wait for the script to finish. If this time is exceeded the script is aborted."
}
]
},
{
"function": "sites",
"type": {
"dataType": "array",
"elements": [
{
"elementType": "button",
"elementOptions": [
{
"sourceSuffixes": []
},
{
"separator": ""
},
{
"cssClasses": "col-xs-12"
},
{
"onClick": "addViaPopupForm(this)"
},
{
"getStringKey": "Gen_Add"
}
],
"transformers": []
},
{
"elementType": "select",
"elementHasInputValue": 1,
"elementOptions": [
{
"multiple": "true"
},
{
"readonly": "true"
},
{
"editable": "true"
},
{
"popupForm": [
{
"function": "UNIFIAPI_site_name",
"type": {
"dataType": "string",
"elements": [
{
"elementType": "input",
"elementOptions": [
{
"placeholder": "Enter value"
},
{
"cssClasses": "col-sm-10"
}
],
"transformers": []
}
]
},
"default_value": "default",
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Site name"
}
],
"description": [
{
"language_code": "en_us",
"string": "The name of your site. Use a descriptive name."
}
]
},
{
"function": "UNIFIAPI_base_url",
"type": {
"dataType": "string",
"elements": [
{
"elementType": "input",
"elementOptions": [
{
"placeholder": "https://host_ip/proxy/network/integration/"
},
{
"cssClasses": "col-sm-10"
}
],
"transformers": []
}
]
},
"default_value": "default",
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Base URL"
}
],
"description": [
{
"language_code": "en_us",
"string": "You can find your base url in the UniFi Site Manager in <i>Settings -> Control Plane -> Integrations</i> in the <i>API Request Format</i> section, (e.g. <code>https://host_ip/proxy/network/integration/</code>)."
}
]
},
{
"function": "UNIFIAPI_api_version",
"type": {
"dataType": "string",
"elements": [
{
"elementType": "input",
"elementOptions": [
{
"placeholder": "v1"
},
{
"cssClasses": "col-sm-10"
}
],
"transformers": []
}
]
},
"default_value": "default",
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "API version"
}
],
"description": [
{
"language_code": "en_us",
"string": "The version of the API (e.g.: <code>v1</code>)."
}
]
},
{
"function": "UNIFIAPI_api_key",
"type": {
"dataType": "string",
"elements": [
{
"elementType": "input",
"elementOptions": [
{
"placeholder": "Enter value"
},
{
"cssClasses": "col-sm-10"
}
],
"transformers": []
}
]
},
"default_value": "default",
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "API key"
}
],
"description": [
{
"language_code": "en_us",
"string": "You can get an API key in your UniFi Site Manager in <i>Settings -> Control Plane -> Integrations</i>."
}
]
},
{
"function": "UNIFIAPI_verify_ssl",
"type": {
"dataType": "boolean",
"elements": [
{
"elementType": "input",
"elementOptions": [
{
"type": "checkbox"
}
],
"transformers": []
}
]
},
"default_value": 1,
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Verify SSL"
}
],
"description": [
{
"language_code": "en_us",
"string": "Disable if you do not have an SSL certificate set up."
}
]
}
],
"transformers": []
}
],
"transformers": [
"name|base64"
]
},
{
"elementType": "button",
"elementOptions": [
{
"sourceSuffixes": []
},
{
"separator": ""
},
{
"cssClasses": "col-xs-6"
},
{
"onClick": "removeFromList(this)"
},
{
"getStringKey": "Gen_Remove_Last"
}
],
"transformers": []
},
{
"elementType": "button",
"elementOptions": [
{
"sourceSuffixes": []
},
{
"separator": ""
},
{
"cssClasses": "col-xs-6"
},
{
"onClick": "removeAllOptions(this)"
},
{
"getStringKey": "Gen_Remove_All"
}
],
"transformers": []
}
]
},
"default_value": [],
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Sites"
}
],
"description": [
{
"language_code": "en_us",
"string": "UniFi site configurations. Use a unique name for each site. You can find necessary details to configure this in your controller under <i>Settings -> Control Plane -> Integrations</i>."
}
]
}
],
"database_column_definitions": [
{
"column": "Index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Index"
}
]
},
{
"column": "Object_PrimaryID",
"mapped_to_column": "cur_MAC",
"css_classes": "col-sm-3",
"show": true,
"type": "device_name_mac",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "MAC (name)"
}
]
},
{
"column": "Object_SecondaryID",
"mapped_to_column": "cur_IP",
"css_classes": "col-sm-2",
"show": true,
"type": "device_ip",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "IP"
}
]
},
{
"column": "Watched_Value1",
"mapped_to_column": "cur_Name",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Name"
}
]
},
{
"column": "Watched_Value2",
"mapped_to_column": "cur_Type",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Device Type"
}
]
},
{
"column": "Watched_Value3",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Connected"
}
]
},
{
"column": "Watched_Value4",
"mapped_to_column": "cur_NetworkNodeMAC",
"css_classes": "col-sm-2",
"show": true,
"type": "device_mac",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Parent"
}
]
},
{
"column": "Dummy",
"mapped_to_column": "cur_ScanMethod",
"mapped_to_column_data": {
"value": "UNIFIAPI"
},
"css_classes": "col-sm-2",
"show": false,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Scan method"
}
]
},
{
"column": "DateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Created"
}
]
},
{
"column": "DateTimeChanged",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Changed"
}
]
},
{
"column": "Status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",
"default_value": "",
"options": [
{
"equals": "watched-not-changed",
"replacement": "<div style='text-align:center'><i class='fa-solid fa-square-check'></i><div></div>"
},
{
"equals": "watched-changed",
"replacement": "<div style='text-align:center'><i class='fa-solid fa-triangle-exclamation'></i></div>"
},
{
"equals": "new",
"replacement": "<div style='text-align:center'><i class='fa-solid fa-circle-plus'></i></div>"
},
{
"equals": "missing-in-last-scan",
"replacement": "<div style='text-align:center'><i class='fa-solid fa-question'></i></div>"
}
],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Status"
}
]
}
]
}

View File

@@ -0,0 +1,182 @@
#!/usr/bin/env python
import os
import pathlib
import sys
import json
import sqlite3
from pytz import timezone
from unifi_sm_api.api import SiteManagerAPI
# Define the installation path and extend the system path for plugin imports
INSTALL_PATH = "/app"
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
from plugin_helper import Plugin_Object, Plugin_Objects, decodeBase64, decode_settings_base64
from plugin_utils import get_plugins_configs
from logger import mylog, Logger
from const import pluginsPath, fullDbPath, logPath
from helper import timeNowTZ, get_setting_value
from messaging.in_app import write_notification
import conf
# Make sure the TIMEZONE for logging is correct
conf.tz = timezone(get_setting_value('TIMEZONE'))
# Make sure log level is initialized correctly
Logger(get_setting_value('LOG_LEVEL'))
pluginName = 'UNIFIAPI'
# Define the current path and log file paths
LOG_PATH = logPath + '/plugins'
LOG_FILE = os.path.join(LOG_PATH, f'script.{pluginName}.log')
RESULT_FILE = os.path.join(LOG_PATH, f'last_result.{pluginName}.log')
# Initialize the Plugin obj output file
plugin_objects = Plugin_Objects(RESULT_FILE)
def main():
mylog('verbose', [f'[{pluginName}] In script'])
# Retrieve configuration settings
unifi_sites_configs = get_setting_value('UNIFIAPI_sites')
mylog('verbose', [f'[{pluginName}] number of unifi_sites_configs: {len(unifi_sites_configs)}'])
for site_config in unifi_sites_configs:
siteDict = decode_settings_base64(site_config)
mylog('verbose', [f'[{pluginName}] siteDict: {json.dumps(siteDict)}'])
mylog('none', [f'[{pluginName}] Connecting to: {siteDict["UNIFIAPI_site_name"]}'])
api = SiteManagerAPI(
api_key=siteDict["UNIFIAPI_api_key"],
version=siteDict["UNIFIAPI_api_version"],
base_url=siteDict["UNIFIAPI_base_url"],
verify_ssl=siteDict["UNIFIAPI_verify_ssl"]
)
sites_resp = api.get_sites()
sites = sites_resp.get("data", [])
for site in sites:
# retrieve data
device_data = get_device_data(site, api)
# Process the data into native application tables
if len(device_data) > 0:
# insert devices into the lats_result.log
for device in device_data:
plugin_objects.add_object(
primaryId = device['dev_mac'], # mac
secondaryId = device['dev_ip'], # IP
watched1 = device['dev_name'], # name
watched2 = device['dev_type'], # device_type (AP/Switch etc)
watched3 = device['dev_connected'], # connectedAt or empty
watched4 = device['dev_parent_mac'],# parent_mac or "Internet"
extra = '',
foreignKey = device['dev_mac']
)
mylog('verbose', [f'[{pluginName}] New entries: "{len(device_data)}"'])
# log result
plugin_objects.write_result_file()
return 0
# retrieve data
def get_device_data(site, api):
device_data = []
mylog('verbose', [f'[{pluginName}] Site: {site} '])
site_id = site["id"]
site_name = site.get("name", "Unnamed Site")
mylog('verbose', [f'[{pluginName}] Site: {site_name} ({site_id})'])
# --- Devices ---
unifi_devices_resp = api.get_unifi_devices(site_id)
unifi_devices = unifi_devices_resp.get("data", [])
mylog('verbose', [f'[{pluginName}] Site: {site_name} unifi devices: {json.dumps(unifi_devices_resp, indent=2)}'])
# --- Clients ---
clients_resp = api.get_clients(site_id)
clients = clients_resp.get("data", [])
mylog('verbose', [f'[{pluginName}] Site: {site_name} clients: {json.dumps(clients_resp, indent=2)}'])
# Build a lookup for devices by their 'id' to find parent MAC easily
device_id_to_mac = {dev['id']: dev.get('macAddress', '') for dev in unifi_devices}
# Helper to resolve uplinkDeviceId to parent MAC, or "Internet" if no uplink
def resolve_parent_mac(uplink_id):
if not uplink_id:
return "Internet"
return device_id_to_mac.get(uplink_id, "Unknown")
# Process Unifi devices
for device in unifi_devices:
dev_mac = device.get('macAddress', '')
dev_ip = device.get('ipAddress', '')
dev_name = device.get('name', '')
# Determine device_type based on features and type
# If device has "accessPoint" feature => type "AP"
# Else if "switching" feature => type "Switch"
# fallback to "Unknown"
features = device.get('features', [])
if 'accessPoint' in features:
device_type = 'AP'
elif 'switching' in features:
device_type = 'Switch'
else:
device_type = 'Unknown'
dev_type = device_type
# No connectedAt for devices, so empty
dev_connected = ''
uplinkDeviceId = device.get('uplinkDeviceId', '')
dev_parent_mac = resolve_parent_mac(uplinkDeviceId)
device_data.append({
"dev_mac": dev_mac,
"dev_ip": dev_ip,
"dev_name": dev_name,
"dev_type": dev_type,
"dev_connected": dev_connected,
"dev_parent_mac": dev_parent_mac
})
# Process Clients (child devices connected to APs or switches)
for client in clients:
dev_mac = client.get('macAddress', '')
dev_ip = client.get('ipAddress', '')
dev_name = client.get('name', '')
device_type = ""
dev_type = device_type
dev_connected = client.get('connectedAt', '')
uplinkDeviceId = client.get('uplinkDeviceId', '')
dev_parent_mac = resolve_parent_mac(uplinkDeviceId)
device_data.append({
"dev_mac": dev_mac,
"dev_ip": dev_ip,
"dev_name": dev_name,
"dev_type": dev_type,
"dev_connected": dev_connected,
"dev_parent_mac": dev_parent_mac
})
return device_data
if __name__ == '__main__':
main()

View File

@@ -1,7 +1,7 @@
<?php
require 'php/templates/header.php';
require 'php/templates/notification.php';
require 'php/templates/modals.php';
?>
@@ -86,7 +86,7 @@
// Function to update the displayed data and timestamp based on the selected format and index
function updateData(format, index) {
// Fetch data from the API endpoint
fetch(`/php/server/query_json.php?file=table_notifications.json&nocache=${Date.now()}`)
fetch(`php/server/query_json.php?file=table_notifications.json&nocache=${Date.now()}`)
.then(response => response.json())
.then(data => {
if (index < 0) {

View File

@@ -58,6 +58,12 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
<div id="settingsPage" class="content-wrapper">
<a style="cursor:pointer">
<span>
<i id='toggleSettings' onclick="toggleAllSettings()" class="settings-expand-icon fa fa-angle-double-down"></i>
</span>
</a>
<!-- Content header--------------------------------------------------------- -->
<section class="content-header">
@@ -193,8 +199,8 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
console.log("Response:", response);
// Handle the successful response
if (response && response.settings) {
const settingsData = response.settings.settings;
if (response && response.data && response.data.settings && response.data.settings.settings) {
const settingsData = response.data.settings.settings;
console.log("Settings:", settingsData);
// Wrong number of settings processing
@@ -547,7 +553,7 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
}, 1500);
} else {
var settingsArray = [];
let settingsArray = [];
// collect values for each of the different input form controls
// get settings to determine setting type to store values appropriately
@@ -559,120 +565,123 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
setType = set["setType"]
setCodeName = set["setKey"]
// console.log(prefix);
settingsArray = collectSetting(prefix, setCodeName, setType, settingsArray)
const setTypeObject = JSON.parse(processQuotes(setType))
// console.log(setTypeObject);
// // console.log(prefix);
const dataType = setTypeObject.dataType;
// const setTypeObject = JSON.parse(processQuotes(setType))
// // console.log(setTypeObject);
// get the element with the input value(s)
let elements = setTypeObject.elements.filter(element => element.elementHasInputValue === 1);
// const dataType = setTypeObject.dataType;
// if none found, take last
if(elements.length == 0)
{
elementWithInputValue = setTypeObject.elements[setTypeObject.elements.length - 1]
} else
{
elementWithInputValue = elements[0]
}
// // get the element with the input value(s)
// let elements = setTypeObject.elements.filter(element => element.elementHasInputValue === 1);
const { elementType, elementOptions = [], transformers = [] } = elementWithInputValue;
const {
inputType,
readOnly,
isMultiSelect,
isOrdeable,
cssClasses,
placeholder,
suffix,
sourceIds,
separator,
editable,
valRes,
getStringKey,
onClick,
onChange,
customParams,
customId,
columns,
base64Regex
} = handleElementOptions('none', elementOptions, transformers, val = "");
// // if none found, take last
// if(elements.length == 0)
// {
// elementWithInputValue = setTypeObject.elements[setTypeObject.elements.length - 1]
// } else
// {
// elementWithInputValue = elements[0]
// }
let value;
// const { elementType, elementOptions = [], transformers = [] } = elementWithInputValue;
// const {
// inputType,
// readOnly,
// isMultiSelect,
// isOrdeable,
// cssClasses,
// placeholder,
// suffix,
// sourceIds,
// separator,
// editable,
// valRes,
// getStringKey,
// onClick,
// onChange,
// customParams,
// customId,
// columns,
// base64Regex,
// elementOptionsBase64
// } = handleElementOptions('none', elementOptions, transformers, val = "");
if (dataType === "string" && elementWithInputValue.elementType === "datatable" ) {
// let value;
value = collectTableData(`#${setCodeName}_table`)
settingsArray.push([prefix, setCodeName, dataType, btoa(JSON.stringify(value))]);
// if (dataType === "string" && elementWithInputValue.elementType === "datatable" ) {
} else if (dataType === "string" ||
(dataType === "integer" && (inputType === "number" || inputType === "text"))) {
// value = collectTableData(`#${setCodeName}_table`)
// settingsArray.push([prefix, setCodeName, dataType, btoa(JSON.stringify(value))]);
// } else if (dataType === "string" ||
// (dataType === "integer" && (inputType === "number" || inputType === "text"))) {
value = $('#' + setCodeName).val();
value = applyTransformers(value, transformers);
// value = $('#' + setCodeName).val();
// value = applyTransformers(value, transformers);
settingsArray.push([prefix, setCodeName, dataType, value]);
// settingsArray.push([prefix, setCodeName, dataType, value]);
} else if (inputType === 'checkbox') {
// } else if (inputType === 'checkbox') {
value = $(`#${setCodeName}`).is(':checked') ? 1 : 0;
// value = $(`#${setCodeName}`).is(':checked') ? 1 : 0;
if(dataType === "boolean")
{
value = value == 1 ? "True" : "False";
}
// if(dataType === "boolean")
// {
// value = value == 1 ? "True" : "False";
// }
value = applyTransformers(value, transformers);
settingsArray.push([prefix, setCodeName, dataType, value]);
// value = applyTransformers(value, transformers);
// settingsArray.push([prefix, setCodeName, dataType, value]);
} else if (dataType === "array" ) {
// } else if (dataType === "array" ) {
let temps = [];
// let temps = [];
if(isOrdeable)
{
temps = $(`#${setCodeName}`).val()
} else
{
// make sure to collect all if set as "editable" or selected only otherwise
$(`#${setCodeName}`).attr("my-editable") == "true" ? additionalSelector = "" : additionalSelector = ":selected";
// if(isOrdeable)
// {
// temps = $(`#${setCodeName}`).val()
// } else
// {
// // make sure to collect all if set as "editable" or selected only otherwise
// $(`#${setCodeName}`).attr("my-editable") == "true" ? additionalSelector = "" : additionalSelector = ":selected";
$(`#${setCodeName} option${additionalSelector}`).each(function() {
const vl = $(this).val();
if (vl !== '') {
temps.push(applyTransformers(vl, transformers));
}
});
}
// $(`#${setCodeName} option${additionalSelector}`).each(function() {
// const vl = $(this).val();
// if (vl !== '') {
// temps.push(applyTransformers(vl, transformers));
// }
// });
// }
value = JSON.stringify(temps);
// value = JSON.stringify(temps);
settingsArray.push([prefix, setCodeName, dataType, value]);
// settingsArray.push([prefix, setCodeName, dataType, value]);
} else if (dataType === "none") {
// no value to save
value = ""
settingsArray.push([prefix, setCodeName, dataType, value]);
// } else if (dataType === "none") {
// // no value to save
// value = ""
// settingsArray.push([prefix, setCodeName, dataType, value]);
} else if (dataType === "json") {
// } else if (dataType === "json") {
value = $('#' + setCodeName).val();
value = applyTransformers(value, transformers);
value = JSON.stringify(value, null, 2)
settingsArray.push([prefix, setCodeName, dataType, value]);
// value = $('#' + setCodeName).val();
// value = applyTransformers(value, transformers);
// value = JSON.stringify(value, null, 2)
// settingsArray.push([prefix, setCodeName, dataType, value]);
} else {
// } else {
console.error(`[saveSettings] Couldn't determine how to handle (setCodeName|dataType|inputType):(${setCodeName}|${dataType}|${inputType})`);
// console.error(`[saveSettings] Couldn't determine how to handle (setCodeName|dataType|inputType):(${setCodeName}|${dataType}|${inputType})`);
value = $('#' + setCodeName).val();
value = applyTransformers(value, transformers);
console.error(`[saveSettings] Saving value "${value}"`);
settingsArray.push([prefix, setCodeName, dataType, value]);
}
// value = $('#' + setCodeName).val();
// value = applyTransformers(value, transformers);
// console.error(`[saveSettings] Saving value "${value}"`);
// settingsArray.push([prefix, setCodeName, dataType, value]);
// }
});
// sanity check to make sure settings were loaded & collected correctly

View File

@@ -13,7 +13,7 @@
require 'php/templates/header.php';
?>
<?php require 'php/templates/notification.php'; ?>
<?php require 'php/templates/modals.php'; ?>
<!-- ----------------------------------------------------------------------- -->
@@ -21,6 +21,7 @@
// show spinning icon
showSpinner()
</script>
<!-- Page ------------------------------------------------------------------ -->
@@ -28,674 +29,112 @@
<!-- Main content ---------------------------------------------------------- -->
<section class="content">
<?php
//General stats
// Date & Time
$date = new DateTime();
$formatted_date = $date->format('l, F j, Y H:i:s'); // Get date
$formatted_date2 = $date->format('d/m/Y H:i:s'); // Get date2
$formatted_date3 = $date->format('Y/m/d H:i:s'); // Get date3
//System stats
// OS-Version
$os_version = '';
// Raspbian
if ($os_version == '') {$os_version = exec('cat /etc/os-release | grep PRETTY_NAME');}
// Dietpi
if ($os_version == '') {$os_version = exec('uname -o');}
//$os_version_arr = explode("\n", trim($os_version));
$stat['os_version'] = str_replace('"', '', str_replace('PRETTY_NAME=', '', $os_version));
$stat['uptime'] = str_replace('up ', '', shell_exec("uptime -p")); // Get system uptime
$system_namekernel = shell_exec("uname"); // Get system name kernel
$system_namesystem = shell_exec("uname -o"); // Get name system
$system_full = shell_exec("uname -a"); // Get system full
$system_architecture = shell_exec("uname -m"); // Get system Architecture
$load_average = sys_getloadavg(); // Get load average
$system_process_count = shell_exec("ps -e --no-headers | wc -l"); // Count processes
//Motherboard stats
$motherboard_name = shell_exec('cat /sys/class/dmi/id/board_name'); // Get the Motherboard name
$motherboard_manufactured = shell_exec('cat /sys/class/dmi/id/board_vendor'); // Get the Motherboard manufactured
$motherboard_revision = shell_exec('cat /sys/class/dmi/id/board_version'); // Get the Motherboard revision
$motherboard_bios = shell_exec('cat /sys/class/dmi/id/bios_version'); // Get the Motherboard BIOS
$motherboard_biosdate = shell_exec('cat /sys/class/dmi/id/bios_date'); // Get the Motherboard BIOS date
$motherboard_biosvendor = shell_exec('cat /sys/class/dmi/id/bios_vendor'); // Get the Motherboard BIOS vendor
//CPU stats
$prevVal = shell_exec("cat /proc/cpuinfo | grep processor");
$prevArr = explode("\n", trim($prevVal));
$stat['cpu'] = sizeof($prevArr);
$cpu_result = shell_exec("cat /proc/cpuinfo | grep Model");
$stat['cpu_model'] = strstr($cpu_result, "\n", true);
$stat['cpu_model'] = str_replace(":", "", trim(str_replace("Model", "", $stat['cpu_model'])));
if ($stat['cpu_model'] == '') {
$cpu_result = shell_exec("cat /proc/cpuinfo | grep model\ name");
$stat['cpu_model'] = strstr($cpu_result, "\n", true);
$stat['cpu_model'] = str_replace(":", "", trim(str_replace("model name", "", $stat['cpu_model'])));
}
if (file_exists('/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq')) {
// RaspbianOS
$stat['cpu_frequ'] = exec('cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq') / 1000;
} elseif (is_numeric(str_replace(',', '.', exec('lscpu | grep "MHz" | awk \'{print $3}\'')))) {
// Ubuntu Server, DietPi event. others
$stat['cpu_frequ'] = round(exec('lscpu | grep "MHz" | awk \'{print $3}\''), 0);
} elseif (is_numeric(str_replace(',', '.', exec('lscpu | grep "max MHz" | awk \'{print $4}\'')))) {
// RaspbianOS and event. others
$stat['cpu_frequ'] = round(str_replace(',', '.', exec('lscpu | grep "max MHz" | awk \'{print $4}\'')), 0);
} else {
// Fallback
$stat['cpu_frequ'] = "unknown";
}
$cpu_temp = shell_exec('cat /sys/class/hwmon/hwmon0/temp1_input'); // Get the CPU temperature
$cpu_temp = floatval($cpu_temp) / 1000; // Convert the temperature to degrees Celsius
$cpu_vendor = exec('cat /proc/cpuinfo | grep "vendor_id" | cut -d ":" -f 2' ); // Get the CPU vendor
//Memory stats
$total_memorykb = shell_exec("cat /proc/meminfo | grep MemTotal | awk '{print $2}'");
$total_memorykb = trim($total_memorykb);
$total_memorykb = number_format($total_memorykb, 0, '.', '.');
$total_memorymb = shell_exec("cat /proc/meminfo | grep MemTotal | awk '{print $2/1024}'");
$total_memorymb = trim($total_memorymb);
$total_memorymb = number_format($total_memorymb, 0, '.', '.');
$mem_used = round(memory_get_usage() / 1048576 * 100, 2);
$memory_usage_percent = round(($mem_used / $total_memorymb), 2);
//HDD stats
$hdd_result = shell_exec(" df -P | awk '{print $1}'");
$hdd_devices = explode("\n", trim($hdd_result));
$hdd_result = shell_exec(" df -P | awk '{print $2}'");
$hdd_devices_total = explode("\n", trim($hdd_result));
$hdd_result = shell_exec(" df -P | awk '{print $3}'");
$hdd_devices_used = explode("\n", trim($hdd_result));
$hdd_result = shell_exec(" df -P | awk '{print $4}'");
$hdd_devices_free = explode("\n", trim($hdd_result));
$hdd_result = shell_exec(" df -P | awk '{print $5}'");
$hdd_devices_percent = explode("\n", trim($hdd_result));
$hdd_result = shell_exec(" df -P | awk '{print $6}'");
$hdd_devices_mount = explode("\n", trim($hdd_result));
//Network stats
// Check Server name
if (!empty(gethostname())) { $network_NAME = gethostname(); } else { $network_NAME = lang('Systeminfo_Network_Server_Name_String'); }
// Check HTTPS
if (isset($_SERVER['HTTPS'])) { $network_HTTPS = 'Yes (HTTPS)'; } else { $network_HTTPS = lang('Systeminfo_Network_Secure_Connection_String'); }
// Check Query String
if (empty($_SERVER['QUERY_STRING'])) { $network_QueryString = lang('Systeminfo_Network_Server_Query_String'); } else { $network_QueryString = $_SERVER['QUERY_STRING']; }
// Check HTTP referer
if (empty($_SERVER['HTTP_REFERER'])) { $network_referer = lang('Systeminfo_Network_HTTP_Referer_String'); } else { $network_referer = $_SERVER['HTTP_REFERER']; }
//Network Hardware stat
$network_result = shell_exec("cat /proc/net/dev | tail -n +3 | awk '{print $1}'");
$net_interfaces = explode("\n", trim($network_result));
$network_result = shell_exec("cat /proc/net/dev | tail -n +3 | awk '{print $2}'");
$net_interfaces_rx = explode("\n", trim($network_result));
$network_result = shell_exec("cat /proc/net/dev | tail -n +3 | awk '{print $10}'");
$net_interfaces_tx = explode("\n", trim($network_result));
//USB devices
$usb_result = shell_exec("lsusb");
$usb_devices_mount = explode("\n", trim($usb_result));
// General ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title sysinfo_headline"><i class="fa fa-info-circle"></i> ' . lang('Systeminfo_General') . '</h3>
<div class="row">
<div class="col-lg-12 col-sm-12 col-xs-12">
<!-- <div class="box-transparent"> -->
<div id="navSysInfo" class="nav-tabs-custom">
<ul class="nav nav-tabs" style="font-size:16px;">
<li>
<a id="tabServer" href="#panServer" data-toggle="tab">
<i class="fa fa-info-circle"></i>
<span class="dev-detail-tab-name">
<?= lang('Systeminfo_System');?>
</span>
</a>
</li>
<li>
<a id="tabNetwork" href="#panNetwork" data-toggle="tab">
<i class="fa fa-sitemap fa-rotate-270"></i>
<span class="dev-detail-tab-name">
<?= lang('Systeminfo_Network');?>
</span>
</a>
</li>
<li>
<a id="tabStorage" href="#panStorage" data-toggle="tab">
<i class="fa fa-hdd"></i>
<span class="dev-detail-tab-name">
<?= lang('Systeminfo_Storage');?>
</span>
</a>
</li>
</ul>
<div class="tab-content spinnerTarget" style="min-height: 430px;">
<div class="tab-pane fade" data-php-file="systeminfoServer.php" id="panServer">
<!-- PLACEHOLDER -->
</div>
<div class="tab-pane fade" data-php-file="systeminfoNetwork.php" id="panNetwork">
<!-- PLACEHOLDER -->
</div>
<div class="tab-pane fade table-responsive" data-php-file="systeminfoStorage.php" id="panStorage">
<!-- PLACEHOLDER -->
</div>
</div>
<div class="box-body">
<div class="row">
<div class="col-sm-3 sysinfo_general_a">' . lang('Systeminfo_General_Full_Date') . '</div>
<div class="col-sm-9 sysinfo_general_b">' . $formatted_date . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_general_a">' . lang('Systeminfo_General_Date') . '</div>
<div class="col-sm-9 sysinfo_general_b">' . $formatted_date2 . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_general_a">' . lang('Systeminfo_General_Date2') . '</div>
<div class="col-sm-9 sysinfo_general_b">' . $formatted_date3 . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_general_a">' . lang('Systeminfo_General_TimeZone') . '</div>
<div class="col-sm-9 sysinfo_general_b">' . $timeZone . '</div>
</div>
</div>
</div>';
// Network Hardware ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title sysinfo_headline"><i class="fa fa-sitemap fa-rotate-270"></i> ' . lang('Systeminfo_Network_Hardware') . '</h3>
<!-- /.tab-content -->
</div>
<!-- /.nav-tabs-custom -->
<!-- </div> -->
</div>
<div class="box-body">
<table id="networkTable" class="table table-bordered table-hover">
<thead>
<tr>
<th>' . lang('Systeminfo_Network_Hardware_Interface_Name') . '</th>
<th>' . lang('Systeminfo_Network_Hardware_Interface_Mask') . '</th>
<th>' . lang('Systeminfo_Network_Hardware_Interface_RX') . '</th>
<th>' . lang('Systeminfo_Network_Hardware_Interface_TX') . '</th>
</tr>
</thead>
<tbody>';
<!-- /.col -->
</div>
for ($x = 0; $x < sizeof($net_interfaces); $x++) {
$interface_name = str_replace(':', '', $net_interfaces[$x]);
$interface_ip_temp = exec('ip addr show ' . $interface_name . ' | grep "inet "');
$interface_ip_arr = explode(' ', trim($interface_ip_temp));
if (!isset($interface_ip_arr[1])) {
$interface_ip_arr[1] = '--';
}
if ($net_interfaces_rx[$x] == 0) {
$temp_rx = 0;
} else {
$temp_rx = number_format(round(($net_interfaces_rx[$x] / 1024 / 1024), 2), 2, ',', '.');
}
if ($net_interfaces_tx[$x] == 0) {
$temp_tx = 0;
} else {
$temp_tx = number_format(round(($net_interfaces_tx[$x] / 1024 / 1024), 2), 2, ',', '.');
}
echo '<tr>';
echo '<td>' . $interface_name . '</td>';
echo '<td>' . $interface_ip_arr[1] . '</td>';
echo '<td>' . $temp_rx . ' MB</td>';
echo '<td>' . $temp_tx . ' MB</td>';
echo '</tr>';
}
echo ' </tbody>
</table>
</div>
</div>';
// Available IPs ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title availableips_headline"><i class="fa fa-list"></i> ' . lang('Systeminfo_AvailableIps') . '</h3>
</div>
<div class="box-body">
<table id="availableIpsTable" class="display" style="width:100%"></table>
</div>
</div>';
// Client ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title sysinfo_headline"><i class="fa fa-globe"></i> ' . lang('Systeminfo_This_Client') . '</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-sm-3 sysinfo_client_a">' . lang('Systeminfo_Client_User_Agent') . '</div>
<div class="col-sm-9 sysinfo_client_b">' . $_SERVER['HTTP_USER_AGENT'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_client_a">' . lang('Systeminfo_Client_Resolution') . '</div>
<div class="col-sm-9 sysinfo_client_b" id="resolution"></div>
</div>
</div>
</div>';
echo '<script>
var ratio = window.devicePixelRatio || 1;
var w = window.innerWidth;
var h = window.innerHeight;
var rw = window.innerWidth * ratio;
var rh = window.innerHeight * ratio;
var resolutionDiv = document.getElementById("resolution");
resolutionDiv.innerHTML = "Width: " + w + "px / Height: " + h + "px<br> " + "Width: " + rw + "px / Height: " + rh + "px (native)";
</script>';
// System ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title sysinfo_headline"><i class="fa fa-computer"></i> ' . lang('Systeminfo_System') . '</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_Uptime') . '</div>
<div class="col-sm-9 sysinfo_system_b">' . $stat['uptime'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_Kernel') . '</div>
<div class="col-sm-9 sysinfo_system_b">' . $system_namekernel . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_System') . '</div>
<div class="col-sm-9 sysinfo_system_b">' . $system_namesystem . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_OSVersion') . '</div>
<div class="col-sm-9 sysinfo_system_b">' . $stat['os_version'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_Uname') . '</div>
<div class="col-sm-9 sysinfo_system_b">' . $system_full . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_Architecture') . '</div>
<div class="col-sm-9 sysinfo_system_b">' . $system_architecture . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_AVG') . '</div>
<div class="col-sm-9 sysinfo_system_b">'. $load_average[0] .' '. $load_average[1] .' '. $load_average[2] .'</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_Running_Processes') . '</div>
<div class="col-sm-9 sysinfo_system_b">' . $system_process_count . '</div>
</div>
</div>
</div>';
// Motherboard ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title sysinfo_headline"><i class="fa fa-laptop-code"></i> ' . lang('Systeminfo_Motherboard') . '</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-sm-3 sysinfo_motherboard_a">' . lang('Systeminfo_Motherboard_Name') . '</div>
<div class="col-sm-9 sysinfo_motherboard_b">' . $motherboard_name . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_motherboard_a">' . lang('Systeminfo_Motherboard_Manufactured') . '</div>
<div class="col-sm-9 sysinfo_motherboard_b">' . $motherboard_manufactured . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_motherboard_a">' . lang('Systeminfo_Motherboard_Revision') . '</div>
<div class="col-sm-9 sysinfo_motherboard_b">' . $motherboard_revision. '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_motherboard_a">' . lang('Systeminfo_Motherboard_BIOS') . '</div>
<div class="col-sm-9 sysinfo_motherboard_b">' . $motherboard_bios . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_motherboard_a">' . lang('Systeminfo_Motherboard_BIOS_Date') . '</div>
<div class="col-sm-9 sysinfo_motherboard_b">' . $motherboard_biosdate . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_motherboard_a">' . lang('Systeminfo_Motherboard_BIOS_Vendor') . '</div>
<div class="col-sm-9 sysinfo_motherboard_b">' . $motherboard_biosvendor . '</div>
</div>
</div>
</div>';
// CPU ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title sysinfo_headline"><i class="fa fa-microchip"></i> ' . lang('Systeminfo_CPU') . '</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-sm-3 sysinfo_cpu_a">' . lang('Systeminfo_CPU_Vendor') . '</div>
<div class="col-sm-9 sysinfo_cpu_b">' . $cpu_vendor . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_cpu_a">' . lang('Systeminfo_CPU_Name') . '</div>
<div class="col-sm-9 sysinfo_cpu_b">' . $stat['cpu_model'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_cpu_a">' . lang('Systeminfo_CPU_Cores') . '</div>
<div class="col-sm-9 sysinfo_cpu_b">' . $stat['cpu'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_cpu_a">' . lang('Systeminfo_CPU_Speed') . '</div>
<div class="col-sm-9 sysinfo_cpu_b">' . $stat['cpu_frequ'] . ' MHz</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_cpu_a">' . lang('Systeminfo_CPU_Temp') . '</div>
<div class="col-sm-9 sysinfo_cpu_b">'. $cpu_temp .' °C</div>
</div>';
// Get the number of CPU cores
$num_cpus = $stat['cpu'];
$num_cpus = $num_cpus +2;
// Iterate over the CPU cores
for ($i = 2,$a = 0; $i < $num_cpus; $i++,$a++) {
// Get the CPU temperature
$cpu_tempxx = shell_exec('cat /sys/class/hwmon/hwmon0/temp' . $i . '_input');
// Convert the temperature to degrees Celsius
$cpu_tempxx = floatval($cpu_tempxx) / 1000;
// Print the CPU temperature
echo '<div class="row">
<div class="col-sm-3 sysinfo_cpu_a">CPU Temp ' . $a . ':</div>
<div class="col-sm-9 sysinfo_cpu_b">' . $cpu_tempxx . ' °C</div>
</div>';
}
echo '
</div>
</div>';
// Memory ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title sysinfo_headline"><i class="fa fa-memory"></i> ' . lang('Systeminfo_Memory') . '</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-sm-3 sysinfo_memory_a">' . lang('Systeminfo_Memory_Usage_Percent') . '</div>
<div class="col-sm-9 sysinfo_memory_b">' . $memory_usage_percent . ' %</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_memory_a">' . lang('Systeminfo_Memory_Usage') . '</div>
<div class="col-sm-9 sysinfo_memory_b">' . $mem_used . ' MB / ' . $total_memorymb . ' MB</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_memory_a">' . lang('Systeminfo_Memory_Total_Memory') . '</div>
<div class="col-sm-9 sysinfo_memory_b">' . $total_memorymb . ' MB (' . $total_memorykb . ' KB)</div>
</div>
</div>
</div>';
// Storage ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title sysinfo_headline"><i class="fa fa-hdd"></i> ' . lang('Systeminfo_Storage') . '</h3>
</div>
<div class="box-body">';
$storage_lsblk = shell_exec("lsblk -io NAME,SIZE,TYPE,MOUNTPOINT,MODEL --list | tail -n +2 | awk '{print $1\"#\"$2\"#\"$3\"#\"$4\"#\"$5}'");
$storage_lsblk_line = explode("\n", $storage_lsblk);
$storage_lsblk_line = array_filter($storage_lsblk_line);
for ($x = 0; $x < sizeof($storage_lsblk_line); $x++) {
$temp = array();
$temp = explode("#", $storage_lsblk_line[$x]);
$storage_lsblk_line[$x] = $temp;
}
for ($x = 0; $x < sizeof($storage_lsblk_line); $x++) {
echo '<div class="row">';
if (preg_match('~[0-9]+~', $storage_lsblk_line[$x][0])) {
echo '<div class="col-sm-4 sysinfo_storage_a">"' . lang('Systeminfo_Storage_Mount') . ' ' . $storage_lsblk_line[$x][3] . '"</div>';
} else {
echo '<div class="col-sm-4 sysinfo_storage_a">"' . str_replace('_', ' ', $storage_lsblk_line[$x][3]) . '"</div>';
}
echo '<div class="col-sm-3 sysinfo_storage_b">' . lang('Systeminfo_Storage_Device') . ' /dev/' . $storage_lsblk_line[$x][0] . '</div>';
echo '<div class="col-sm-2 sysinfo_storage_b">' . lang('Systeminfo_Storage_Size') . ' ' . $storage_lsblk_line[$x][1] . '</div>';
echo '<div class="col-sm-2 sysinfo_storage_b">' . lang('Systeminfo_Storage_Type') . ' ' . $storage_lsblk_line[$x][2] . '</div>';
echo '</div>';
}
echo ' </div>
</div>';
// Storage usage ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title sysinfo_headline"><i class="fa fa-hdd"></i> ' . lang('Systeminfo_Storage_Usage') . '</h3>
</div>
<div class="box-body">';
for ($x = 0; $x < sizeof($hdd_devices); $x++) {
if (stristr($hdd_devices[$x], '/dev/')) {
if (!stristr($hdd_devices[$x], '/loop')) {
if ($hdd_devices_total[$x] == 0 || $hdd_devices_total[$x] == '') {$temp_total = 0;} else { $temp_total = number_format(round(($hdd_devices_total[$x] / 1024 / 1024), 2), 2, ',', '.'); $temp_total = trim($temp_total);}
if ($hdd_devices_used[$x] == 0 || $hdd_devices_used[$x] == '') {$temp_used = 0;} else { $temp_used = number_format(round(($hdd_devices_used[$x] / 1024 / 1024), 2), 2, ',', '.'); $temp_used = trim($temp_total);}
if ($hdd_devices_free[$x] == 0 || $hdd_devices_free[$x] == '') {$temp_free = 0;} else { $temp_free = number_format(round(($hdd_devices_free[$x] / 1024 / 1024), 2), 2, ',', '.'); $temp_free = trim($temp_total);}
echo '<div class="row">';
echo '<div class="col-sm-4 sysinfo_storage_usage_a">"' . lang('Systeminfo_Storage_Usage_Mount') . ' ' . $hdd_devices_mount[$x] . '"</div>';
echo '<div class="col-sm-2 sysinfo_storage_usage_b">' . lang('Systeminfo_Storage_Usage_Total') . ' ' . $temp_total . ' GB</div>';
echo '<div class="col-sm-3 sysinfo_storage_usage_b">' . lang('Systeminfo_Storage_Usage_Used') . ' ' . $temp_used . ' GB (' . $hdd_devices_percent[$x]. ')</div>';
echo '<div class="col-sm-2 sysinfo_storage_usage_b">' . lang('Systeminfo_Storage_Usage_Free') . ' ' . $temp_free . ' GB</div>';
echo '</div>';
}
}
}
#echo '<br>' . $lang['SysInfo_storage_note'];
echo ' </div>
</div>';
// Network ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title sysinfo_headline"><i class="fas fa-ethernet"></i> ' . lang('Systeminfo_Network') . '</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_IP') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . shell_exec("curl https://ifconfig.co") . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_IP_Connection') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['REMOTE_ADDR'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_IP_Server') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['SERVER_ADDR'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Server_Name') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $network_NAME . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Connection_Port') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['REMOTE_PORT'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Secure_Connection') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $network_HTTPS . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Server_Version') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['SERVER_SOFTWARE'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_gerneral_a">' . lang('Systeminfo_Network_Request_URI') . '</div>
<div class="col-sm-9 sysinfo_gerneral_b">' . $_SERVER['REQUEST_URI'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Server_Query') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $network_QueryString . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_HTTP_Host') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['HTTP_HOST'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_HTTP_Referer') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $network_referer . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_MIME') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['HTTP_ACCEPT'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Accept_Language') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['HTTP_ACCEPT_LANGUAGE'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Accept_Encoding') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['HTTP_ACCEPT_ENCODING'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Request_Method') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['REQUEST_METHOD'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Request_Time') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['REQUEST_TIME'] . '</div>
</div>
</div>
</div>';
// Services ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title sysinfo_headline"><i class="fa fa-database"></i> ' . lang('Systeminfo_Services') . '</h3>
</div>
<div class="box-body">';
echo '<div style="height: 300px; overflow: scroll;">';
exec('systemctl --type=service --state=running', $running_services);
echo '<table class="table table-bordered table-hover table-striped dataTable no-footer" style="margin-bottom: 10px;">';
echo '<thead>
<tr role="row">
<th style="padding: 8px;">' . lang('Systeminfo_Services_Name') . '</th>
<th style="padding: 8px;">' . lang('Systeminfo_Services_Description') . '</th>
</tr>
</thead>';
$table_color = 'odd';
for ($x = 0; $x < sizeof($running_services); $x++) {
if (stristr($running_services[$x], '.service')) {
$temp_services_arr = array_values(array_filter(explode(' ', trim($running_services[$x]))));
$servives_name = $temp_services_arr[0];
unset($temp_services_arr[0], $temp_services_arr[1], $temp_services_arr[2], $temp_services_arr[3]);
$servives_description = implode(" ", $temp_services_arr);
if ($table_color == 'odd') {$table_color = 'even';} else { $table_color = 'odd';}
echo '<tr class="' . $table_color . '"><td style="padding: 3px; padding-left: 10px;">' . substr($servives_name, 0, -8) . '</td><td style="padding: 3px; padding-left: 10px;">' . $servives_description . '</td></tr>';
}
}
echo '</table>';
echo '</div>';
echo ' </div>
</div>';
// USB ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title sysinfo_headline"><i class="fab fa-usb"></i> ' . lang('Systeminfo_USB_Devices') . '</h3>
</div>
<div class="box-body">';
echo ' <table class="table table-bordered table-hover table-striped dataTable no-footer" style="margin-bottom: 10px;">';
$table_color = 'odd';
sort($usb_devices_mount);
for ($x = 0; $x < sizeof($usb_devices_mount); $x++) {
$cut_pos = strpos($usb_devices_mount[$x], ':');
$usb_bus = substr($usb_devices_mount[$x], 0, $cut_pos);
$usb_dev = substr($usb_devices_mount[$x], $cut_pos + 1);
if ($table_color == 'odd') {$table_color = 'even';} else { $table_color = 'odd';}
echo '<tr class="' . $table_color . '"><td style="padding: 3px; padding-left: 10px; width: 150px;"><b>' . str_replace('Device', 'Dev.', $usb_bus) . '</b></td><td style="padding: 3px; padding-left: 10px;">' . $usb_dev . '</td></tr>';
}
echo ' </table>';
echo ' </div>
</div>';
// ----------------------------------------------------------
echo '<br>';
?>
</div>
</section>
</section>
<!-- /.content -->
<?php
require 'php/templates/footer.php';
?>
</div>
<!-- /.content-wrapper -->
<?php
require 'php/templates/footer.php';
?>
<!-- ----------------------------------------------------------------------- -->
<!-- DataTable initialization -->
<script>
// --------------------------------------------------------
// Available free IPS functionality
function inferNetworkRange(usedIps) {
if (!usedIps || usedIps.length === 0) return [];
function loadTabContent(target) {
const $tab = $(target);
const phpFile = $tab.data('php-file');
const subnetMap = {};
if (phpFile && !$tab.data('loaded')) {
showSpinner();
$tab.load(phpFile, function () {
$tab.data('loaded', true);
});
}
}
// Group IPs by /24 subnet
for (const ip of usedIps) {
const parts = ip.split('.');
if (parts.length !== 4) continue;
const subnet = `${parts[0]}.${parts[1]}.${parts[2]}`;
if (!subnetMap[subnet]) subnetMap[subnet] = [];
subnetMap[subnet].push(ip);
function initializeTabs() {
const key = "activeSysinfoTab";
let selectedTab = "tabServer"; // fallback default
const cached = getCache(key);
if (!emptyArr.includes(cached)) {
selectedTab = cached;
}
const result = [];
// Activate the correct tab
const $tabLink = $('.nav-tabs a[id="' + selectedTab + '"]');
$tabLink.tab('show');
for (const [subnet, ips] of Object.entries(subnetMap)) {
if (ips.length > 5) {
for (let i = 2; i < 255; i++) {
const ip = `${subnet}.${i}`;
result.push({ subnet, ip });
}
}
}
// Get the pane's ID from the tab's href attribute
const targetSelector = $tabLink.attr("href");
loadTabContent(targetSelector);
return result;
}
// On tab change
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
const newTabId = $(e.target).attr('id');
setCache(key, newTabId);
function fetchUsedIps(callback) {
$.ajax({
url: 'php/server/query_graphql.php',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
query: `
query devices($options: PageQueryOptionsInput) {
devices(options: $options) {
devices {
devLastIP
}
}
}
`,
variables: {
options: {
status: "all_devices"
}
}
}),
success: function(response) {
console.log(response);
const usedIps = (response?.devices?.devices || [])
.map(d => d.devLastIP)
.filter(ip => ip && ip.includes('.'));
callback(usedIps);
},
error: function(err) {
console.error("Error fetching IPs:", err);
callback([]);
}
const newTarget = $(e.target).attr("href");
loadTabContent(newTarget);
});
}
function renderAvailableIpsTable(allIps, usedIps) {
const availableIps = allIps.filter(row => !usedIps.includes(row.ip));
console.log(allIps);
console.log(usedIps);
console.log(availableIps);
$('#availableIpsTable').DataTable({
destroy: true,
data: availableIps,
columns: [
{ title: getString("Gen_Subnet"), data: "subnet" },
{ title: getString("Systeminfo_AvailableIps"), data: "ip" }
],
pageLength: 10
});
window.onload = function async() {
initializeTabs();
}
// INIT
$(document).ready(function() {
fetchUsedIps(usedIps => {
const allIps = inferNetworkRange(usedIps);
renderAvailableIpsTable(allIps, usedIps);
});
setTimeout(() => {
$('#networkTable').DataTable({
searching: true,
order: [[0, "desc"]],
initComplete: function(settings, json) {
hideSpinner(); // Called after the DataTable is fully initialized
}
});
}, 200);
});
</script>

316
front/systeminfoNetwork.php Executable file
View File

@@ -0,0 +1,316 @@
<?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';
?>
<?php
function getExternalIp() {
$ch = curl_init('https://api64.ipify.org?format=json');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch);
if (curl_errno($ch)) {
curl_close($ch);
return 'ERROR: ' . curl_error($ch);
}
curl_close($ch);
$data = json_decode($response, true);
if (isset($data['ip'])) {
return htmlspecialchars($data['ip']);
}
return 'ERROR: Invalid response';
}
// ----------------------------------------------------------
// Network
// ----------------------------------------------------------
//Network stats
// Server IP
$externalIp = getExternalIp();
// Check Server name
if (!empty(gethostname())) { $network_NAME = gethostname(); } else { $network_NAME = lang('Systeminfo_Network_Server_Name_String'); }
// Check HTTPS
if (isset($_SERVER['HTTPS'])) { $network_HTTPS = 'Yes (HTTPS)'; } else { $network_HTTPS = lang('Systeminfo_Network_Secure_Connection_String'); }
// Check Query String
if (empty($_SERVER['QUERY_STRING'])) { $network_QueryString = lang('Systeminfo_Network_Server_Query_String'); } else { $network_QueryString = $_SERVER['QUERY_STRING']; }
// Check HTTP referer
if (empty($_SERVER['HTTP_REFERER'])) { $network_referer = lang('Systeminfo_Network_HTTP_Referer_String'); } else { $network_referer = $_SERVER['HTTP_REFERER']; }
//Network Hardware stat
$network_result = shell_exec("cat /proc/net/dev | tail -n +3 | awk '{print $1}'");
$net_interfaces = explode("\n", trim($network_result));
$network_result = shell_exec("cat /proc/net/dev | tail -n +3 | awk '{print $2}'");
$net_interfaces_rx = explode("\n", trim($network_result));
$network_result = shell_exec("cat /proc/net/dev | tail -n +3 | awk '{print $10}'");
$net_interfaces_tx = explode("\n", trim($network_result));
// Network Hardware ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title sysinfo_headline"><i class="fa fa-sitemap fa-rotate-270"></i> ' . lang('Systeminfo_Network_Hardware') . '</h3>
</div>
<div class="box-body">
<table id="networkTable" class="table table-bordered table-hover">
<thead>
<tr>
<th>' . lang('Systeminfo_Network_Hardware_Interface_Name') . '</th>
<th>' . lang('Systeminfo_Network_Hardware_Interface_Mask') . '</th>
<th>' . lang('Systeminfo_Network_Hardware_Interface_RX') . '</th>
<th>' . lang('Systeminfo_Network_Hardware_Interface_TX') . '</th>
</tr>
</thead>
<tbody>';
for ($x = 0; $x < sizeof($net_interfaces); $x++) {
$interface_name = str_replace(':', '', $net_interfaces[$x]);
$interface_ip_temp = exec('ip addr show ' . $interface_name . ' | grep "inet "');
$interface_ip_arr = explode(' ', trim($interface_ip_temp));
if (!isset($interface_ip_arr[1])) {
$interface_ip_arr[1] = '--';
}
if ($net_interfaces_rx[$x] == 0) {
$temp_rx = 0;
} else {
$temp_rx = number_format(round(($net_interfaces_rx[$x] / 1024 / 1024), 2), 2, ',', '.');
}
if ($net_interfaces_tx[$x] == 0) {
$temp_tx = 0;
} else {
$temp_tx = number_format(round(($net_interfaces_tx[$x] / 1024 / 1024), 2), 2, ',', '.');
}
echo '<tr>';
echo '<td>' . $interface_name . '</td>';
echo '<td>' . $interface_ip_arr[1] . '</td>';
echo '<td>' . $temp_rx . ' MB</td>';
echo '<td>' . $temp_tx . ' MB</td>';
echo '</tr>';
}
echo ' </tbody>
</table>
</div>
</div>';
// Available IPs ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title availableips_headline"><i class="fa fa-list"></i> ' . lang('Systeminfo_AvailableIps') . '</h3>
</div>
<div class="box-body">
<table id="availableIpsTable" class="display table table-bordered table-hover dataTable no-footer" style="width:100%"></table>
</div>
</div>';
// Network ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title sysinfo_headline"><i class="fas fa-ethernet"></i> ' . lang('Systeminfo_Network') . '</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_IP') . '</div>
<div class="col-sm-9 sysinfo_network_b" id="external-ip">' .$externalIp. '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_IP_Connection') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['REMOTE_ADDR'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_IP_Server') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['SERVER_ADDR'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Server_Name') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $network_NAME . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Connection_Port') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['REMOTE_PORT'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Secure_Connection') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $network_HTTPS . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Server_Version') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['SERVER_SOFTWARE'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_gerneral_a">' . lang('Systeminfo_Network_Request_URI') . '</div>
<div class="col-sm-9 sysinfo_gerneral_b">' . $_SERVER['REQUEST_URI'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Server_Query') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $network_QueryString . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_HTTP_Host') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['HTTP_HOST'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_HTTP_Referer') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $network_referer . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_MIME') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['HTTP_ACCEPT'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Accept_Language') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['HTTP_ACCEPT_LANGUAGE'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Accept_Encoding') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['HTTP_ACCEPT_ENCODING'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Request_Method') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['REQUEST_METHOD'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Request_Time') . '</div>
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['REQUEST_TIME'] . '</div>
</div>
</div>
</div>';
?>
<script>
// --------------------------------------------------------
// Available free IPS functionality
function inferNetworkRange(usedIps) {
if (!usedIps || usedIps.length === 0) return [];
const subnetMap = {};
// Group IPs by /24 subnet
for (const ip of usedIps) {
const parts = ip.split('.');
if (parts.length !== 4) continue;
const subnet = `${parts[0]}.${parts[1]}.${parts[2]}`;
if (!subnetMap[subnet]) subnetMap[subnet] = [];
subnetMap[subnet].push(ip);
}
const result = [];
for (const [subnet, ips] of Object.entries(subnetMap)) {
if (ips.length > 5) {
for (let i = 2; i < 255; i++) {
const ip = `${subnet}.${i}`;
result.push({ subnet, ip });
}
}
}
return result;
}
function fetchUsedIps(callback) {
$.ajax({
url: 'php/server/query_graphql.php',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
query: `
query devices($options: PageQueryOptionsInput) {
devices(options: $options) {
devices {
devLastIP
}
}
}
`,
variables: {
options: {
status: "all_devices"
}
}
}),
success: function(response) {
console.log(response);
const usedIps = (response?.data?.devices?.devices || [])
.map(d => d.devLastIP)
.filter(ip => ip && ip.includes('.'));
callback(usedIps);
},
error: function(err) {
console.error("Error fetching IPs:", err);
callback([]);
}
});
}
function renderAvailableIpsTable(allIps, usedIps) {
const availableIps = allIps.filter(row => !usedIps.includes(row.ip));
console.log(availableIps);
$('#availableIpsTable').DataTable({
destroy: true,
data: availableIps,
columns: [
{
title: getString("Gen_Subnet"),
data: "subnet"
},
{
title: getString("Systeminfo_AvailableIps"),
data: "ip",
render: function (data, type, row, meta) {
return `
<span>${data}</span>
<button class="copy-btn btn btn-sm btn-info ml-2 alignRight" data-text="${data}" title="${getString("Gen_CopyToClipboard")}" onclick="copyToClipboard(this)">
<i class="fa-solid fa-copy"></i>
</button>
`;
}
}
],
pageLength: 10
});
}
// INIT
$(document).ready(function() {
// available IPs
fetchUsedIps(usedIps => {
const allIps = inferNetworkRange(usedIps);
renderAvailableIpsTable(allIps, usedIps);
});
setTimeout(() => {
// Available IPs datatable
$('#networkTable').DataTable({
searching: true,
order: [[0, "desc"]],
initComplete: function(settings, json) {
hideSpinner(); // Called after the DataTable is fully initialized
}
});
}, 200);
});
</script>

327
front/systeminfoServer.php Executable file
View File

@@ -0,0 +1,327 @@
<?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';
?>
<?php
// ----------------------------------------------------------
// Server
// ----------------------------------------------------------
//General stats
// Date & Time
$date = new DateTime();
$formatted_date = $date->format('l, F j, Y H:i:s'); // Get date
$formatted_date2 = $date->format('d/m/Y H:i:s'); // Get date2
$formatted_date3 = $date->format('Y/m/d H:i:s'); // Get date3
//System stats
// OS-Version
$os_version = '';
// Raspbian
if ($os_version == '') {$os_version = exec('cat /etc/os-release | grep PRETTY_NAME');}
// Dietpi
if ($os_version == '') {$os_version = exec('uname -o');}
//$os_version_arr = explode("\n", trim($os_version));
$stat['os_version'] = str_replace('"', '', str_replace('PRETTY_NAME=', '', $os_version));
$stat['uptime'] = str_replace('up ', '', shell_exec("uptime -p")); // Get system uptime
$system_namekernel = shell_exec("uname"); // Get system name kernel
$system_namesystem = shell_exec("uname -o"); // Get name system
$system_full = shell_exec("uname -a"); // Get system full
$system_architecture = shell_exec("uname -m"); // Get system Architecture
$load_average = sys_getloadavg(); // Get load average
$system_process_count = shell_exec("ps -e --no-headers | wc -l"); // Count processes
//Motherboard stats
$motherboard_name = shell_exec('cat /sys/class/dmi/id/board_name'); // Get the Motherboard name
$motherboard_manufactured = shell_exec('cat /sys/class/dmi/id/board_vendor'); // Get the Motherboard manufactured
$motherboard_revision = shell_exec('cat /sys/class/dmi/id/board_version'); // Get the Motherboard revision
$motherboard_bios = shell_exec('cat /sys/class/dmi/id/bios_version'); // Get the Motherboard BIOS
$motherboard_biosdate = shell_exec('cat /sys/class/dmi/id/bios_date'); // Get the Motherboard BIOS date
$motherboard_biosvendor = shell_exec('cat /sys/class/dmi/id/bios_vendor'); // Get the Motherboard BIOS vendor
//CPU stats
$prevVal = shell_exec("cat /proc/cpuinfo | grep processor");
$prevArr = explode("\n", trim($prevVal));
$stat['cpu'] = sizeof($prevArr);
$cpu_result = shell_exec("cat /proc/cpuinfo | grep Model");
$stat['cpu_model'] = strstr($cpu_result, "\n", true);
$stat['cpu_model'] = str_replace(":", "", trim(str_replace("Model", "", $stat['cpu_model'])));
if ($stat['cpu_model'] == '') {
$cpu_result = shell_exec("cat /proc/cpuinfo | grep model\ name");
$stat['cpu_model'] = strstr($cpu_result, "\n", true);
$stat['cpu_model'] = str_replace(":", "", trim(str_replace("model name", "", $stat['cpu_model'])));
}
if (file_exists('/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq')) {
// RaspbianOS
$stat['cpu_frequ'] = exec('cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq') / 1000;
} elseif (is_numeric(str_replace(',', '.', exec('lscpu | grep "MHz" | awk \'{print $3}\'')))) {
// Ubuntu Server, DietPi event. others
$stat['cpu_frequ'] = round(exec('lscpu | grep "MHz" | awk \'{print $3}\''), 0);
} elseif (is_numeric(str_replace(',', '.', exec('lscpu | grep "max MHz" | awk \'{print $4}\'')))) {
// RaspbianOS and event. others
$stat['cpu_frequ'] = round(str_replace(',', '.', exec('lscpu | grep "max MHz" | awk \'{print $4}\'')), 0);
} else {
// Fallback
$stat['cpu_frequ'] = "unknown";
}
$cpu_temp = shell_exec('cat /sys/class/hwmon/hwmon0/temp1_input'); // Get the CPU temperature
$cpu_temp = floatval($cpu_temp) / 1000; // Convert the temperature to degrees Celsius
$cpu_vendor = exec('cat /proc/cpuinfo | grep "vendor_id" | cut -d ":" -f 2' ); // Get the CPU vendor
//Memory stats
$total_memorykb = shell_exec("cat /proc/meminfo | grep MemTotal | awk '{print $2}'");
$total_memorykb = trim($total_memorykb);
$total_memorykb = number_format($total_memorykb, 0, '.', '.');
$total_memorymb = shell_exec("cat /proc/meminfo | grep MemTotal | awk '{print $2/1024}'");
$total_memorymb = trim($total_memorymb);
$total_memorymb = number_format($total_memorymb, 0, '.', '.');
$mem_used = round(memory_get_usage() / 1048576 * 100, 2);
$memory_usage_percent = round(($mem_used / $total_memorymb), 2);
// General ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title sysinfo_headline"><i class="fa fa-info-circle"></i> ' . lang('Systeminfo_General') . '</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-sm-3 sysinfo_general_a">' . lang('Systeminfo_General_Full_Date') . '</div>
<div class="col-sm-9 sysinfo_general_b">' . $formatted_date . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_general_a">' . lang('Systeminfo_General_Date') . '</div>
<div class="col-sm-9 sysinfo_general_b">' . $formatted_date2 . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_general_a">' . lang('Systeminfo_General_Date2') . '</div>
<div class="col-sm-9 sysinfo_general_b">' . $formatted_date3 . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_general_a">' . lang('Systeminfo_General_TimeZone') . '</div>
<div class="col-sm-9 sysinfo_general_b">' . $timeZone . '</div>
</div>
</div>
</div>';
// Client ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title sysinfo_headline"><i class="fa fa-globe"></i> ' . lang('Systeminfo_This_Client') . '</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-sm-3 sysinfo_client_a">' . lang('Systeminfo_Client_User_Agent') . '</div>
<div class="col-sm-9 sysinfo_client_b">' . $_SERVER['HTTP_USER_AGENT'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_client_a">' . lang('Systeminfo_Client_Resolution') . '</div>
<div class="col-sm-9 sysinfo_client_b" id="resolution"></div>
</div>
</div>
</div>';
echo '<script>
var ratio = window.devicePixelRatio || 1;
var w = window.innerWidth;
var h = window.innerHeight;
var rw = window.innerWidth * ratio;
var rh = window.innerHeight * ratio;
var resolutionDiv = document.getElementById("resolution");
resolutionDiv.innerHTML = "Width: " + w + "px / Height: " + h + "px<br> " + "Width: " + rw + "px / Height: " + rh + "px (native)";
</script>';
// System ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title sysinfo_headline"><i class="fa fa-computer"></i> ' . lang('Systeminfo_System') . '</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_Uptime') . '</div>
<div class="col-sm-9 sysinfo_system_b">' . $stat['uptime'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_Kernel') . '</div>
<div class="col-sm-9 sysinfo_system_b">' . $system_namekernel . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_System') . '</div>
<div class="col-sm-9 sysinfo_system_b">' . $system_namesystem . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_OSVersion') . '</div>
<div class="col-sm-9 sysinfo_system_b">' . $stat['os_version'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_Uname') . '</div>
<div class="col-sm-9 sysinfo_system_b">' . $system_full . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_Architecture') . '</div>
<div class="col-sm-9 sysinfo_system_b">' . $system_architecture . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_AVG') . '</div>
<div class="col-sm-9 sysinfo_system_b">'. $load_average[0] .' '. $load_average[1] .' '. $load_average[2] .'</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_Running_Processes') . '</div>
<div class="col-sm-9 sysinfo_system_b">' . $system_process_count . '</div>
</div>
</div>
</div>';
// Motherboard ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title sysinfo_headline"><i class="fa fa-laptop-code"></i> ' . lang('Systeminfo_Motherboard') . '</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-sm-3 sysinfo_motherboard_a">' . lang('Systeminfo_Motherboard_Name') . '</div>
<div class="col-sm-9 sysinfo_motherboard_b">' . $motherboard_name . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_motherboard_a">' . lang('Systeminfo_Motherboard_Manufactured') . '</div>
<div class="col-sm-9 sysinfo_motherboard_b">' . $motherboard_manufactured . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_motherboard_a">' . lang('Systeminfo_Motherboard_Revision') . '</div>
<div class="col-sm-9 sysinfo_motherboard_b">' . $motherboard_revision. '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_motherboard_a">' . lang('Systeminfo_Motherboard_BIOS') . '</div>
<div class="col-sm-9 sysinfo_motherboard_b">' . $motherboard_bios . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_motherboard_a">' . lang('Systeminfo_Motherboard_BIOS_Date') . '</div>
<div class="col-sm-9 sysinfo_motherboard_b">' . $motherboard_biosdate . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_motherboard_a">' . lang('Systeminfo_Motherboard_BIOS_Vendor') . '</div>
<div class="col-sm-9 sysinfo_motherboard_b">' . $motherboard_biosvendor . '</div>
</div>
</div>
</div>';
// CPU ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title sysinfo_headline"><i class="fa fa-microchip"></i> ' . lang('Systeminfo_CPU') . '</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-sm-3 sysinfo_cpu_a">' . lang('Systeminfo_CPU_Vendor') . '</div>
<div class="col-sm-9 sysinfo_cpu_b">' . $cpu_vendor . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_cpu_a">' . lang('Systeminfo_CPU_Name') . '</div>
<div class="col-sm-9 sysinfo_cpu_b">' . $stat['cpu_model'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_cpu_a">' . lang('Systeminfo_CPU_Cores') . '</div>
<div class="col-sm-9 sysinfo_cpu_b">' . $stat['cpu'] . '</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_cpu_a">' . lang('Systeminfo_CPU_Speed') . '</div>
<div class="col-sm-9 sysinfo_cpu_b">' . $stat['cpu_frequ'] . ' MHz</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_cpu_a">' . lang('Systeminfo_CPU_Temp') . '</div>
<div class="col-sm-9 sysinfo_cpu_b">'. $cpu_temp .' °C</div>
</div>';
// Get the number of CPU cores
$num_cpus = $stat['cpu'];
$num_cpus = $num_cpus +2;
// Iterate over the CPU cores
for ($i = 2,$a = 0; $i < $num_cpus; $i++,$a++) {
// Get the CPU temperature
$cpu_tempxx = shell_exec('cat /sys/class/hwmon/hwmon0/temp' . $i . '_input');
// Convert the temperature to degrees Celsius
$cpu_tempxx = floatval($cpu_tempxx) / 1000;
// Print the CPU temperature
echo '<div class="row">
<div class="col-sm-3 sysinfo_cpu_a">CPU Temp ' . $a . ':</div>
<div class="col-sm-9 sysinfo_cpu_b">' . $cpu_tempxx . ' °C</div>
</div>';
}
echo '
</div>
</div>';
// Memory ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title sysinfo_headline"><i class="fa fa-memory"></i> ' . lang('Systeminfo_Memory') . '</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-sm-3 sysinfo_memory_a">' . lang('Systeminfo_Memory_Usage_Percent') . '</div>
<div class="col-sm-9 sysinfo_memory_b">' . $memory_usage_percent . ' %</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_memory_a">' . lang('Systeminfo_Memory_Usage') . '</div>
<div class="col-sm-9 sysinfo_memory_b">' . $mem_used . ' MB / ' . $total_memorymb . ' MB</div>
</div>
<div class="row">
<div class="col-sm-3 sysinfo_memory_a">' . lang('Systeminfo_Memory_Total_Memory') . '</div>
<div class="col-sm-9 sysinfo_memory_b">' . $total_memorymb . ' MB (' . $total_memorykb . ' KB)</div>
</div>
</div>
</div>';
// Services ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title sysinfo_headline"><i class="fa fa-database"></i> ' . lang('Systeminfo_Services') . '</h3>
</div>
<div class="box-body">';
echo '<div style="height: 300px; overflow: scroll;">';
exec('systemctl --type=service --state=running', $running_services);
echo '<table class="table table-bordered table-hover table-striped dataTable no-footer" style="margin-bottom: 10px;">';
echo '<thead>
<tr role="row">
<th style="padding: 8px;">' . lang('Systeminfo_Services_Name') . '</th>
<th style="padding: 8px;">' . lang('Systeminfo_Services_Description') . '</th>
</tr>
</thead>';
$table_color = 'odd';
for ($x = 0; $x < sizeof($running_services); $x++) {
if (stristr($running_services[$x], '.service')) {
$temp_services_arr = array_values(array_filter(explode(' ', trim($running_services[$x]))));
$servives_name = $temp_services_arr[0];
unset($temp_services_arr[0], $temp_services_arr[1], $temp_services_arr[2], $temp_services_arr[3]);
$servives_description = implode(" ", $temp_services_arr);
if ($table_color == 'odd')
{
$table_color = 'even';
} else
{
$table_color = 'odd';
}
echo '<tr class="' . $table_color . '"><td style="padding: 3px; padding-left: 10px;">' . substr($servives_name, 0, -8) . '</td><td style="padding: 3px; padding-left: 10px;">' . $servives_description . '</td></tr>';
}
}
echo '</table>';
echo '</div>';
echo ' </div>
</div>';
?>
<script>
hideSpinner();
</script>

124
front/systeminfoStorage.php Executable file
View File

@@ -0,0 +1,124 @@
<?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';
?>
<?php
// ----------------------------------------------------------
// Storage
// ----------------------------------------------------------
//HDD stats
$hdd_result = shell_exec(" df -P | awk '{print $1}'");
$hdd_devices = explode("\n", trim($hdd_result));
$hdd_result = shell_exec(" df -P | awk '{print $2}'");
$hdd_devices_total = explode("\n", trim($hdd_result));
$hdd_result = shell_exec(" df -P | awk '{print $3}'");
$hdd_devices_used = explode("\n", trim($hdd_result));
$hdd_result = shell_exec(" df -P | awk '{print $4}'");
$hdd_devices_free = explode("\n", trim($hdd_result));
$hdd_result = shell_exec(" df -P | awk '{print $5}'");
$hdd_devices_percent = explode("\n", trim($hdd_result));
$hdd_result = shell_exec(" df -P | awk '{print $6}'");
$hdd_devices_mount = explode("\n", trim($hdd_result));
// Storage ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title sysinfo_headline"><i class="fa fa-hdd"></i> ' . lang('Systeminfo_Storage') . '</h3>
</div>
<div class="box-body">';
$storage_lsblk = shell_exec("lsblk -io NAME,SIZE,TYPE,MOUNTPOINT,MODEL --list | tail -n +2 | awk '{print $1\"#\"$2\"#\"$3\"#\"$4\"#\"$5}'");
$storage_lsblk_line = explode("\n", $storage_lsblk);
$storage_lsblk_line = array_filter($storage_lsblk_line);
for ($x = 0; $x < sizeof($storage_lsblk_line); $x++) {
$temp = array();
$temp = explode("#", $storage_lsblk_line[$x]);
$storage_lsblk_line[$x] = $temp;
}
for ($x = 0; $x < sizeof($storage_lsblk_line); $x++) {
echo '<div class="row">';
if (preg_match('~[0-9]+~', $storage_lsblk_line[$x][0])) {
echo '<div class="col-sm-4 sysinfo_storage_a">"' . lang('Systeminfo_Storage_Mount') . ' ' . $storage_lsblk_line[$x][3] . '"</div>';
} else {
echo '<div class="col-sm-4 sysinfo_storage_a">"' . str_replace('_', ' ', $storage_lsblk_line[$x][3]) . '"</div>';
}
echo '<div class="col-sm-3 sysinfo_storage_b">' . lang('Systeminfo_Storage_Device') . ' /dev/' . $storage_lsblk_line[$x][0] . '</div>';
echo '<div class="col-sm-2 sysinfo_storage_b">' . lang('Systeminfo_Storage_Size') . ' ' . $storage_lsblk_line[$x][1] . '</div>';
echo '<div class="col-sm-2 sysinfo_storage_b">' . lang('Systeminfo_Storage_Type') . ' ' . $storage_lsblk_line[$x][2] . '</div>';
echo '</div>';
}
echo ' </div>
</div>';
// Storage usage ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title sysinfo_headline"><i class="fa fa-hdd"></i> ' . lang('Systeminfo_Storage_Usage') . '</h3>
</div>
<div class="box-body">';
for ($x = 0; $x < sizeof($hdd_devices); $x++) {
if (stristr($hdd_devices[$x], '/dev/')) {
if (!stristr($hdd_devices[$x], '/loop')) {
if ($hdd_devices_total[$x] == 0 || $hdd_devices_total[$x] == '') {$temp_total = 0;} else { $temp_total = number_format(round(($hdd_devices_total[$x] / 1024 / 1024), 2), 2, ',', '.'); $temp_total = trim($temp_total);}
if ($hdd_devices_used[$x] == 0 || $hdd_devices_used[$x] == '') {$temp_used = 0;} else { $temp_used = number_format(round(($hdd_devices_used[$x] / 1024 / 1024), 2), 2, ',', '.'); $temp_used = trim($temp_total);}
if ($hdd_devices_free[$x] == 0 || $hdd_devices_free[$x] == '') {$temp_free = 0;} else { $temp_free = number_format(round(($hdd_devices_free[$x] / 1024 / 1024), 2), 2, ',', '.'); $temp_free = trim($temp_total);}
echo '<div class="row">';
echo '<div class="col-sm-4 sysinfo_storage_usage_a">"' . lang('Systeminfo_Storage_Usage_Mount') . ' ' . $hdd_devices_mount[$x] . '"</div>';
echo '<div class="col-sm-2 sysinfo_storage_usage_b">' . lang('Systeminfo_Storage_Usage_Total') . ' ' . $temp_total . ' GB</div>';
echo '<div class="col-sm-3 sysinfo_storage_usage_b">' . lang('Systeminfo_Storage_Usage_Used') . ' ' . $temp_used . ' GB (' . $hdd_devices_percent[$x]. ')</div>';
echo '<div class="col-sm-2 sysinfo_storage_usage_b">' . lang('Systeminfo_Storage_Usage_Free') . ' ' . $temp_free . ' GB</div>';
echo '</div>';
}
}
}
#echo '<br>' . $lang['SysInfo_storage_note'];
echo ' </div>
</div>';
// ----------------------------------------------------------
// USB devices
// ----------------------------------------------------------
$usb_result = shell_exec("lsusb");
$usb_devices_mount = explode("\n", trim($usb_result));
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title sysinfo_headline"><i class="fab fa-usb"></i> ' . lang('Systeminfo_USB_Devices') . '</h3>
</div>
<div class="box-body">';
echo ' <table class="table table-bordered table-hover table-striped dataTable no-footer" style="margin-bottom: 10px;">';
$table_color = 'odd';
sort($usb_devices_mount);
for ($x = 0; $x < sizeof($usb_devices_mount); $x++) {
$cut_pos = strpos($usb_devices_mount[$x], ':');
$usb_bus = substr($usb_devices_mount[$x], 0, $cut_pos);
$usb_dev = substr($usb_devices_mount[$x], $cut_pos + 1);
if ($table_color == 'odd') {$table_color = 'even';} else { $table_color = 'odd';}
echo '<tr class="' . $table_color . '"><td style="padding: 3px; padding-left: 10px; width: 150px;"><b>' . str_replace('Device', 'Dev.', $usb_bus) . '</b></td><td style="padding: 3px; padding-left: 10px;">' . $usb_dev . '</td></tr>';
}
echo ' </table>';
echo ' </div>
</div>';
// ----------------------------------------------------------
echo '<br>';
?>
<script>
hideSpinner();
</script>

View File

@@ -1,7 +1,7 @@
<?php
require 'php/templates/header.php';
require 'php/templates/notification.php';
require 'php/templates/modals.php';
?>
<!-- ----------------------------------------------------------------------- -->

View File

@@ -30,5 +30,5 @@ source myenv/bin/activate
update-alternatives --install /usr/bin/python python /usr/bin/python3 10
# install packages thru pip3
pip3 install openwrt-luci-rpc asusrouter asyncio aiohttp graphene flask tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros yattag git+https://github.com/foreign-sub/aiofreepybox.git
pip3 install openwrt-luci-rpc asusrouter asyncio aiohttp graphene flask flask-cors unifi-sm-api tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros yattag git+https://github.com/foreign-sub/aiofreepybox.git

View File

@@ -19,6 +19,7 @@ nav:
- Docker Updates: UPDATES.md
- Other:
- Synology Guide: SYNOLOGY_GUIDE.md
- Portainer Stacks: DOCKER_PORTAINER.md
- Community Guides: COMMUNITY_GUIDES.md
- Bare-metal (Experimental): HW_INSTALL.md
- Migration Guide: MIGRATION.md
@@ -64,6 +65,7 @@ nav:
- Debugging Tips: DEBUG_TIPS.md
- Debugging GraphQL: DEBUG_GRAPHQL.md
- Debugging Invalid JSON: DEBUG_INVALID_JSON.md
- Debugging PHP: DEBUG_PHP.md
- Debugging Plugins: DEBUG_PLUGINS.md
- Debugging Web UI Port: WEB_UI_PORT_DEBUG.md
- Debugging Workflows: WORKFLOWS_DEBUGGING.md
@@ -75,9 +77,24 @@ nav:
- Database: DATABASE.md
- Settings: SETTINGS_SYSTEM.md
- Versions: VERSIONS.md
- Icon and Type guessing: DEVICE_HEURISTICS.md
- API:
- Overview: API.md
- Devices Collection: API_DEVICES.md
- Device: API_DEVICE.md
- Sessions: API_SESSIONS.md
- Settings: API_SETTINGS.md
- Events: API_EVENTS.md
- Metrics: API_METRICS.md
- Net Tools: API_NETTOOLS.md
- Online History: API_ONLINEHISTORY.md
- Sync: API_SYNC.md
- GraphQL: API_GRAPHQL.md
- DB query: API_DBQUERY.md
- Tests: API_TESTS.md
- SUPERSEDED OLD API Overview: API_OLD.md
- Integrations:
- Webhook Secret: WEBHOOK_SECRET.md
- API: API.md
- Webhook Secret: WEBHOOK_SECRET.md
- Helper scripts: HELPER_SCRIPTS.md

View File

@@ -20,6 +20,7 @@ import time
import datetime
import multiprocessing
import subprocess
from pathlib import Path
# Register NetAlertX modules
import conf
@@ -29,7 +30,7 @@ from helper import filePermissions, timeNowTZ, get_setting_value
from app_state import updateState
from api import update_api
from scan.session_events import process_scan
from initialise import importConfigs
from initialise import importConfigs, renameSettings
from database import DB
from messaging.reporting import get_notifications
from models.notification_instance import NotificationInstance
@@ -44,9 +45,10 @@ from workflows.manager import WorkflowManager
#===============================================================================
#===============================================================================
"""
main structure of Pi Alert
main structure of NetAlertX
Initialise All
Rename old settings
start Loop forever
initialise loop
(re)import config
@@ -91,14 +93,17 @@ def main ():
mylog('debug', '[MAIN] Starting loop')
all_plugins = None
pm = None
# -- SETTINGS BACKWARD COMPATIBILITY START --
# rename settings that have changed names due to code cleanup or migration to plugins
renameSettings(Path(fullConfPath))
# -- SETTINGS BACKWARD COMPATIBILITY END --
while True:
# re-load user configuration and plugins
all_plugins, imported = importConfigs(db, all_plugins)
# initiate plugin manager
pm = plugin_manager(db, all_plugins)
pm, all_plugins, imported = importConfigs(pm, db, all_plugins)
# update time started
conf.loop_start_time = timeNowTZ()

View File

@@ -13,7 +13,7 @@ from models.user_events_queue_instance import UserEventsQueueInstance
from messaging.in_app import write_notification
# Import the start_server function
from graphql_server.graphql_server_start import start_server
from api_server.api_server_start import start_server
apiEndpoints = []

View File

@@ -0,0 +1,556 @@
import threading
from flask import Flask, request, jsonify, Response
from flask_cors import CORS
from .graphql_endpoint import devicesSchema
from .device_endpoint import get_device_data, set_device_data, delete_device, delete_device_events, reset_device_props, copy_device, update_device_column
from .devices_endpoint import get_all_devices, delete_unknown_devices, delete_all_with_empty_macs, delete_devices, export_devices, import_csv, devices_totals, devices_by_status
from .events_endpoint import delete_events, delete_events_older_than, get_events, create_event, get_events_totals
from .history_endpoint import delete_online_history
from .prometheus_endpoint import get_metric_stats
from .sessions_endpoint import get_sessions, delete_session, create_session, get_sessions_calendar, get_device_sessions, get_session_events
from .nettools_endpoint import wakeonlan, traceroute, speedtest, nslookup, nmap_scan, internet_info
from .dbquery_endpoint import read_query, write_query, update_query, delete_query
from .sync_endpoint import handle_sync_post, handle_sync_get
import sys
# Register NetAlertX directories
INSTALL_PATH = "/app"
sys.path.extend([f"{INSTALL_PATH}/server"])
from logger import mylog
from helper import get_setting_value, timeNowTZ
from db.db_helper import get_date_from_period
from app_state import updateState
from messaging.in_app import write_notification
# Flask application
app = Flask(__name__)
CORS(
app,
resources={
r"/metrics": {"origins": "*"},
r"/device/*": {"origins": "*"},
r"/devices/*": {"origins": "*"},
r"/history/*": {"origins": "*"},
r"/nettools/*": {"origins": "*"},
r"/sessions/*": {"origins": "*"},
r"/settings/*": {"origins": "*"},
r"/dbquery/*": {"origins": "*"},
r"/events/*": {"origins": "*"}
},
supports_credentials=True,
allow_headers=["Authorization", "Content-Type"]
)
# --------------------------
# GraphQL Endpoints
# --------------------------
# Endpoint used when accessed via browser
@app.route("/graphql", methods=["GET"])
def graphql_debug():
# Handles GET requests
return "NetAlertX GraphQL server running."
# Endpoint for GraphQL queries
@app.route("/graphql", methods=["POST"])
def graphql_endpoint():
# Check for API token in headers
if not is_authorized():
msg = '[graphql_server] Unauthorized access attempt - make sure your GRAPHQL_PORT and API_TOKEN settings are correct.'
mylog('verbose', [msg])
return jsonify({"error": msg}), 401
# Retrieve and log request data
data = request.get_json()
mylog('verbose', [f'[graphql_server] data: {data}'])
# Execute the GraphQL query
result = devicesSchema.execute(data.get("query"), variables=data.get("variables"))
# Initialize response
response = {}
if result.errors:
response["errors"] = [str(e) for e in result.errors]
if result.data:
response["data"] = result.data
return jsonify(response)
# --------------------------
# Settings Endpoints
# --------------------------
@app.route("/settings/<setKey>", methods=["GET"])
def api_get_setting(setKey):
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
value = get_setting_value(setKey)
return jsonify({"success": True, "value": value})
# --------------------------
# Device Endpoints
# --------------------------
@app.route("/device/<mac>", methods=["GET"])
def api_get_device(mac):
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
return get_device_data(mac)
@app.route("/device/<mac>", methods=["POST"])
def api_set_device(mac):
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
return set_device_data(mac, request.json)
@app.route("/device/<mac>/delete", methods=["DELETE"])
def api_delete_device(mac):
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
return delete_device(mac)
@app.route("/device/<mac>/events/delete", methods=["DELETE"])
def api_delete_device_events(mac):
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
return delete_device_events(mac)
@app.route("/device/<mac>/reset-props", methods=["POST"])
def api_reset_device_props(mac):
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
return reset_device_props(mac, request.json)
@app.route("/device/copy", methods=["POST"])
def api_copy_device():
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
data = request.get_json() or {}
mac_from = data.get("macFrom")
mac_to = data.get("macTo")
if not mac_from or not mac_to:
return jsonify({"success": False, "error": "macFrom and macTo are required"}), 400
return copy_device(mac_from, mac_to)
@app.route("/device/<mac>/update-column", methods=["POST"])
def api_update_device_column(mac):
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
data = request.get_json() or {}
column_name = data.get("columnName")
column_value = data.get("columnValue")
if not column_name or not column_value:
return jsonify({"success": False, "error": "columnName and columnValue are required"}), 400
return update_device_column(mac, column_name, column_value)
# --------------------------
# Devices Collections
# --------------------------
@app.route("/devices", methods=["GET"])
def api_get_devices():
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
return get_all_devices()
@app.route("/devices", methods=["DELETE"])
def api_delete_devices():
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
macs = request.json.get("macs") if request.is_json else None
return delete_devices(macs)
@app.route("/devices/empty-macs", methods=["DELETE"])
def api_delete_all_empty_macs():
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
return delete_all_with_empty_macs()
@app.route("/devices/unknown", methods=["DELETE"])
def api_delete_unknown_devices():
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
return delete_unknown_devices()
@app.route("/devices/export", methods=["GET"])
@app.route("/devices/export/<format>", methods=["GET"])
def api_export_devices(format=None):
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
export_format = (format or request.args.get("format", "csv")).lower()
return export_devices(export_format)
@app.route("/devices/import", methods=["POST"])
def api_import_csv():
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
return import_csv(request.files.get("file"))
@app.route("/devices/totals", methods=["GET"])
def api_devices_totals():
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
return devices_totals()
@app.route("/devices/by-status", methods=["GET"])
def api_devices_by_status():
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
status = request.args.get("status", "") if request.args else None
return devices_by_status(status)
# --------------------------
# Net tools
# --------------------------
@app.route("/nettools/wakeonlan", methods=["POST"])
def api_wakeonlan():
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
mac = request.json.get("devMac")
return wakeonlan(mac)
@app.route("/nettools/traceroute", methods=["POST"])
def api_traceroute():
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
ip = request.json.get("devLastIP")
return traceroute(ip)
@app.route("/nettools/speedtest", methods=["GET"])
def api_speedtest():
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
return speedtest()
@app.route("/nettools/nslookup", methods=["POST"])
def api_nslookup():
"""
API endpoint to handle nslookup requests.
Expects JSON with 'devLastIP'.
"""
if not is_authorized():
return jsonify({"success": False, "error": "Forbidden"}), 403
data = request.get_json(silent=True)
if not data or "devLastIP" not in data:
return jsonify({"success": False, "error": "Missing 'devLastIP'"}), 400
ip = data["devLastIP"]
return nslookup(ip)
@app.route("/nettools/nmap", methods=["POST"])
def api_nmap():
"""
API endpoint to handle nmap scan requests.
Expects JSON with 'scan' (IP address) and 'mode' (scan mode).
"""
if not is_authorized():
return jsonify({"success": False, "error": "Forbidden"}), 403
data = request.get_json(silent=True)
if not data or "scan" not in data or "mode" not in data:
return jsonify({"success": False, "error": "Missing 'scan' or 'mode'"}), 400
ip = data["scan"]
mode = data["mode"]
return nmap_scan(ip, mode)
@app.route("/nettools/internetinfo", methods=["GET"])
def api_internet_info():
if not is_authorized():
return jsonify({"success": False, "error": "Forbidden"}), 403
return internet_info()
# --------------------------
# DB query
# --------------------------
@app.route("/dbquery/read", methods=["POST"])
def dbquery_read():
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
data = request.get_json() or {}
raw_sql_b64 = data.get("rawSql")
if not raw_sql_b64:
return jsonify({"error": "rawSql is required"}), 400
return read_query(raw_sql_b64)
@app.route("/dbquery/write", methods=["POST"])
def dbquery_write():
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
data = request.get_json() or {}
raw_sql_b64 = data.get("rawSql")
if not raw_sql_b64:
return jsonify({"error": "rawSql is required"}), 400
return write_query(raw_sql_b64)
@app.route("/dbquery/update", methods=["POST"])
def dbquery_update():
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
data = request.get_json() or {}
required = ["columnName", "id", "dbtable", "columns", "values"]
if not all(data.get(k) for k in required):
return jsonify({"error": "Missing required parameters"}), 400
return update_query(
column_name=data["columnName"],
ids=data["id"],
dbtable=data["dbtable"],
columns=data["columns"],
values=data["values"],
)
@app.route("/dbquery/delete", methods=["POST"])
def dbquery_delete():
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
data = request.get_json() or {}
required = ["columnName", "id", "dbtable"]
if not all(data.get(k) for k in required):
return jsonify({"error": "Missing required parameters"}), 400
return delete_query(
column_name=data["columnName"],
ids=data["id"],
dbtable=data["dbtable"],
)
# --------------------------
# Online history
# --------------------------
@app.route("/history", methods=["DELETE"])
def api_delete_online_history():
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
return delete_online_history()
# --------------------------
# Device Events
# --------------------------
@app.route("/events/create/<mac>", methods=["POST"])
def api_create_event(mac):
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
data = request.json or {}
ip = data.get("ip", "0.0.0.0")
event_type = data.get("event_type", "Device Down")
additional_info = data.get("additional_info", "")
pending_alert = data.get("pending_alert", 1)
event_time = data.get("event_time", None)
# Call the helper to insert into DB
create_event(mac, ip, event_type, additional_info, pending_alert, event_time)
# Return consistent JSON response
return jsonify({"success": True, "message": f"Event created for {mac}"})
@app.route("/events/<mac>", methods=["DELETE"])
def api_events_by_mac(mac):
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
return delete_device_events(mac)
@app.route("/events", methods=["DELETE"])
def api_delete_all_events():
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
return delete_events()
@app.route("/events", methods=["GET"])
def api_get_events():
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
mac = request.args.get("mac")
return get_events(mac)
@app.route("/events/<int:days>", methods=["DELETE"])
def api_delete_old_events(days: int):
"""
Delete events older than <days> days.
Example: DELETE /events/30
"""
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
return delete_events_older_than(days)
@app.route("/sessions/totals", methods=["GET"])
def api_get_events_totals():
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
period = get_date_from_period(request.args.get("period", "7 days"))
return get_events_totals(period)
# --------------------------
# Sessions
# --------------------------
@app.route("/sessions/create", methods=["POST"])
def api_create_session():
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
data = request.json
mac = data.get("mac")
ip = data.get("ip")
start_time = data.get("start_time")
end_time = data.get("end_time")
event_type_conn = data.get("event_type_conn", "Connected")
event_type_disc = data.get("event_type_disc", "Disconnected")
if not mac or not ip or not start_time:
return jsonify({"success": False, "error": "Missing required parameters"}), 400
return create_session(mac, ip, start_time, end_time, event_type_conn, event_type_disc)
@app.route("/sessions/delete", methods=["DELETE"])
def api_delete_session():
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
mac = request.json.get("mac") if request.is_json else None
if not mac:
return jsonify({"success": False, "error": "Missing MAC parameter"}), 400
return delete_session(mac)
@app.route("/sessions/list", methods=["GET"])
def api_get_sessions():
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
mac = request.args.get("mac")
start_date = request.args.get("start_date")
end_date = request.args.get("end_date")
return get_sessions(mac, start_date, end_date)
@app.route("/sessions/calendar", methods=["GET"])
def api_get_sessions_calendar():
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
# Query params: /sessions/calendar?start=2025-08-01&end=2025-08-21
start_date = request.args.get("start")
end_date = request.args.get("end")
return get_sessions_calendar(start_date, end_date)
@app.route("/sessions/<mac>", methods=["GET"])
def api_device_sessions(mac):
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
period = request.args.get("period", "1 day")
return get_device_sessions(mac, period)
@app.route("/sessions/session-events", methods=["GET"])
def api_get_session_events():
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
session_event_type = request.args.get("type", "all")
period = get_date_from_period(request.args.get("period", "7 days"))
return get_session_events(session_event_type, period)
# --------------------------
# Prometheus metrics endpoint
# --------------------------
@app.route("/metrics")
def metrics():
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
# Return Prometheus metrics as plain text
return Response(get_metric_stats(), mimetype="text/plain")
# --------------------------
# SYNC endpoint
# --------------------------
@app.route("/sync", methods=["GET", "POST"])
def sync_endpoint():
if not is_authorized():
return jsonify({"error": "Forbidden"}), 403
if request.method == "GET":
return handle_sync_get()
elif request.method == "POST":
return handle_sync_post()
else:
msg = "[sync endpoint] Method Not Allowed"
write_notification(msg, "alert")
mylog("verbose", [msg])
return jsonify({"error": "Method Not Allowed"}), 405
# --------------------------
# Background Server Start
# --------------------------
def is_authorized():
token = request.headers.get("Authorization")
is_authorized = token == f"Bearer {get_setting_value('API_TOKEN')}"
if not is_authorized:
msg = f"[api] Unauthorized access attempt - make sure your GRAPHQL_PORT and API_TOKEN settings are correct."
write_notification(msg, "alert")
mylog("verbose", [msg])
return is_authorized
def start_server(graphql_port, app_state):
"""Start the GraphQL server in a background thread."""
if app_state.graphQLServerStarted == 0:
mylog('verbose', [f'[graphql endpoint] Starting on port: {graphql_port}'])
# Start Flask app in a separate thread
thread = threading.Thread(
target=lambda: app.run(
host="0.0.0.0",
port=graphql_port,
debug=True,
use_reloader=False
)
)
thread.start()
# Update the state to indicate the server has started
app_state = updateState("Process: Idle", None, None, None, 1)

View File

@@ -0,0 +1,103 @@
#!/usr/bin/env python
import json
import argparse
import os
import pathlib
import base64
import re
import sys
from datetime import datetime
from flask import jsonify, request, Response
import csv
import io
from io import StringIO
# Register NetAlertX directories
INSTALL_PATH="/app"
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
from database import get_temp_db_connection
def read_query(raw_sql_b64):
"""Execute a read-only query (SELECT)."""
try:
raw_sql = base64.b64decode(raw_sql_b64).decode("utf-8")
conn = get_temp_db_connection()
cur = conn.cursor()
cur.execute(raw_sql)
rows = cur.fetchall()
# Convert rows → dict list
columns = [col[0] for col in cur.description] if cur.description else []
results = [dict(zip(columns, row)) for row in rows]
conn.close()
return jsonify({"success": True, "results": results})
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 400
def write_query(raw_sql_b64):
"""Execute a write query (INSERT/UPDATE/DELETE)."""
try:
raw_sql = base64.b64decode(raw_sql_b64).decode("utf-8")
conn = get_temp_db_connection()
cur = conn.cursor()
cur.execute(raw_sql)
conn.commit()
affected = cur.rowcount
conn.close()
return jsonify({"success": True, "affected_rows": affected})
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 400
def update_query(column_name, ids, dbtable, columns, values):
"""Update rows in dbtable based on column_name + ids."""
try:
conn = get_temp_db_connection()
cur = conn.cursor()
if not isinstance(ids, list):
ids = [ids]
updated_count = 0
for i in range(len(ids)):
set_clause = ", ".join([f"{col} = ?" for col in columns])
sql = f"UPDATE {dbtable} SET {set_clause} WHERE {column_name} = ?"
params = list(values) + [ids[i]]
cur.execute(sql, params)
updated_count += cur.rowcount
conn.commit()
conn.close()
return jsonify({"success": True, "updated_count": updated_count})
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 400
def delete_query(column_name, ids, dbtable):
"""Delete rows in dbtable based on column_name + ids."""
try:
conn = get_temp_db_connection()
cur = conn.cursor()
if not isinstance(ids, list):
ids = [ids]
deleted_count = 0
for id_val in ids:
sql = f"DELETE FROM {dbtable} WHERE {column_name} = ?"
cur.execute(sql, (id_val,))
deleted_count += cur.rowcount
conn.commit()
conn.close()
return jsonify({"success": True, "deleted_count": deleted_count})
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 400

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