Compare commits

..

194 Commits

Author SHA1 Message Date
Jokob-sk
fa3949db05 weblate 2024-02-16 08:07:51 +11:00
jokob-sk
0fa7bd2486 Translated using Weblate (German)
Currently translated at 92.1% (586 of 636 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/de/
2024-02-15 22:06:43 +01:00
Hosted Weblate
c5101f6c2d Merge branch 'origin/main' into Weblate. 2024-02-15 22:02:12 +01:00
gallegonovato
ace3368b6a Translated using Weblate (Spanish)
Currently translated at 100.0% (636 of 636 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2024-02-15 22:02:09 +01:00
Jokob-sk
458cf609ab UNFIMP missing Vendor mapping #567 2024-02-16 07:28:49 +11:00
github-actions[bot]
ec923cfebc [🤖Automation] Update README with sponsors information 2024-02-15 11:53:58 +00:00
Hosted Weblate
75bcf5ba60 Merge branch 'origin/main' into Weblate. 2024-02-14 21:53:40 +01:00
gallegonovato
4efc94b8db Translated using Weblate (Spanish)
Currently translated at 100.0% (635 of 635 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2024-02-14 21:53:37 +01:00
Jokob-sk
af452adc56 Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-02-15 07:52:52 +11:00
Jokob-sk
0b45ead574 New Device checkbox tooltip 2024-02-15 07:52:35 +11:00
github-actions[bot]
a9947f17d5 [🤖Automation] Update README with sponsors information 2024-02-14 11:53:52 +00:00
Jokob-sk
356b9c6888 Weblate 2024-02-14 20:41:49 +11:00
gallegonovato
142e67df36 Translated using Weblate (Spanish)
Currently translated at 100.0% (633 of 633 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2024-02-14 10:34:42 +01:00
Jokob-sk
21ed86acc2 My Devices fix 2024-02-14 20:30:04 +11:00
Jokob-sk
3839732a64 Random MAC setting UI_NOT_RANDOM_MAC #565 2024-02-14 20:18:12 +11:00
Jokob-sk
a2d0794410 set pwd changes #565 2024-02-14 07:57:24 +11:00
Jokob-sk
4a88478efe Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-02-14 07:57:01 +11:00
Jokob-sk
1720e065cc set pwd changes #565 2024-02-14 07:56:45 +11:00
Hosted Weblate
5a08aeeb1d Merge branch 'origin/main' into Weblate. 2024-02-13 16:51:55 +01:00
gallegonovato
bc8d0091c3 Translated using Weblate (Spanish)
Currently translated at 99.5% (630 of 633 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2024-02-13 16:51:55 +01:00
Anonymous
ce1a325190 Translated using Weblate (French)
Currently translated at 45.1% (286 of 633 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/
2024-02-13 16:51:54 +01:00
Anonymous
e09d638c45 Translated using Weblate (Spanish)
Currently translated at 99.3% (629 of 633 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2024-02-13 16:51:54 +01:00
Anonymous
2469fd93c0 Translated using Weblate (German)
Currently translated at 90.8% (575 of 633 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/de/
2024-02-13 16:51:54 +01:00
github-actions[bot]
a9913d0759 [🤖Automation] Update README with sponsors information 2024-02-13 11:53:43 +00:00
Jokob-sk
0d756c9e70 docs 📚 + deviceDetails fixes 2024-02-13 08:16:00 +11:00
Jokob-sk
daa03e106b docs 📚 2024-02-13 07:43:47 +11:00
Jokob-sk
e99d26b044 docs 2024-02-12 23:13:31 +11:00
Jokob-sk
42594c0602 Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-02-12 22:55:15 +11:00
Jokob-sk
d96127c93e My Devices filter #548 2024-02-12 22:54:49 +11:00
github-actions[bot]
f4718b69c5 [🤖Automation] Update README with sponsors information 2024-02-12 11:53:42 +00:00
Jokob-sk
34af7b25e0 localization 🌍 2024-02-12 20:35:21 +11:00
Jokob-sk
11ba11e016 Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-02-12 20:34:10 +11:00
Jokob-sk
f5cfc365d3 docs 2024-02-12 20:34:06 +11:00
Gooseman
e2828c3869 Translated using Weblate (French)
Currently translated at 45.4% (287 of 631 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/
2024-02-12 10:32:47 +01:00
Hosted Weblate
1e54cd42ce Merge branch 'origin/main' into Weblate. 2024-02-12 01:43:50 +01:00
Gooseman
0a986a8571 Translated using Weblate (French)
Currently translated at 29.1% (184 of 631 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/
2024-02-12 01:43:50 +01:00
Gooseman
85c49398bc Translated using Weblate (French)
Currently translated at 29.1% (184 of 631 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/
2024-02-12 01:43:49 +01:00
ButterflyOfFire
0a47eca844 Translated using Weblate (French)
Currently translated at 29.1% (184 of 631 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/
2024-02-12 01:43:48 +01:00
gallegonovato
3aa16f8abf Translated using Weblate (Spanish)
Currently translated at 100.0% (631 of 631 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2024-02-12 01:43:47 +01:00
github-actions[bot]
c48596e865 [🤖Automation] Update README with sponsors information 2024-02-11 11:54:01 +00:00
Hosted Weblate
88982de4f4 Merge branch 'origin/main' into Weblate. 2024-02-11 12:20:00 +01:00
gallegonovato
b0c2c85584 Translated using Weblate (Spanish)
Currently translated at 99.6% (629 of 631 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2024-02-11 12:19:59 +01:00
Allan Nordhøy
65ee425645 Translated using Weblate (Norwegian Bokmål)
Currently translated at 1.7% (11 of 631 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/nb_NO/
2024-02-11 12:19:58 +01:00
gallegonovato
d26fdb7f50 Translated using Weblate (Spanish)
Currently translated at 99.5% (628 of 631 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2024-02-11 12:19:58 +01:00
Allan Nordhøy
6134324cfd Translated using Weblate (English (United States))
Currently translated at 99.8% (630 of 631 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/en_US/
2024-02-11 12:19:58 +01:00
Jokob-sk
61c99a3d8e translation work 🗣 2024-02-11 09:46:16 +11:00
Anonymous
821a166c61 Translated using Weblate (Spanish)
Currently translated at 90.6% (572 of 631 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2024-02-10 23:40:25 +01:00
Anonymous
5c4914eeec Translated using Weblate (German)
Currently translated at 91.9% (580 of 631 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/de/
2024-02-10 23:40:24 +01:00
Jokob-sk
b85e8e9f15 translation work 🗣 2024-02-11 09:39:37 +11:00
Jokob-sk
2a53298fda translation work 🗣 2024-02-11 09:34:40 +11:00
jokob-sk
4ac333e067 Translated using Weblate (German)
Currently translated at 92.0% (581 of 631 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/de/
2024-02-10 23:33:00 +01:00
Jokob-sk
001cb38924 translation work 🗣 2024-02-11 09:28:37 +11:00
Anonymous
10f93d40ff Translated using Weblate (French)
Currently translated at 0.4% (3 of 631 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/
2024-02-10 23:02:12 +01:00
gallegonovato
0d9b8807b3 Translated using Weblate (Spanish)
Currently translated at 90.8% (573 of 631 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2024-02-10 23:02:12 +01:00
Anonymous
1b0fb346ef Translated using Weblate (Spanish)
Currently translated at 90.8% (573 of 631 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2024-02-10 23:02:12 +01:00
Anonymous
700b9ebfec Translated using Weblate (German)
Currently translated at 91.9% (580 of 631 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/de/
2024-02-10 23:02:07 +01:00
Jokob-sk
2cdf6e9bf3 translation work 🗣 2024-02-11 08:48:50 +11:00
Jokob-sk
5a8d8f0828 Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-02-11 08:04:52 +11:00
Jokob-sk
246de74ad4 translation work 🗣 2024-02-11 08:04:29 +11:00
github-actions[bot]
ba6f7d5a60 [🤖Automation] Update README with sponsors information 2024-02-10 11:53:44 +00:00
Jokob-sk
f474561593 Weblate + docs + plugins cleanup ♻ 2024-02-10 14:10:43 +11:00
Jokob-sk
b4e292bf5c Weblate 2024-02-10 08:30:34 +11:00
Anonymous
7d32bbafad Translated using Weblate (French)
Currently translated at 100.0% (2 of 2 strings)

Translation: PiAlert/PiAlert-core
Translate-URL: https://hosted.weblate.org/projects/pialert/pialert-core/fr/
2024-02-09 22:26:46 +01:00
Anonymous
5e7d640e98 Translated using Weblate (English (United States))
Currently translated at 99.8% (629 of 630 strings)

Translation: PiAlert/PiAlert-core
Translate-URL: https://hosted.weblate.org/projects/pialert/pialert-core/en_US/
2024-02-09 22:26:45 +01:00
Anonymous
cefbf019c6 Translated using Weblate (Spanish)
Currently translated at 99.8% (639 of 640 strings)

Translation: PiAlert/PiAlert-core
Translate-URL: https://hosted.weblate.org/projects/pialert/pialert-core/es/
2024-02-09 22:26:45 +01:00
Anonymous
1b877ddcfb Translated using Weblate (German)
Currently translated at 99.6% (659 of 661 strings)

Translation: PiAlert/PiAlert-core
Translate-URL: https://hosted.weblate.org/projects/pialert/pialert-core/de/
2024-02-09 22:26:44 +01:00
Jokob-sk
61f740397b Weblate 2024-02-10 08:25:12 +11:00
Jokob-sk
ca389e3824 Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-02-10 08:08:33 +11:00
Jokob-sk
4eb955b19a Weblate 2024-02-10 08:07:32 +11:00
Anonymous
08364a26db Translated using Weblate (Spanish)
Currently translated at 99.8% (639 of 640 strings)

Translation: PiAlert/PiAlert-core
Translate-URL: https://hosted.weblate.org/projects/pialert/pialert-core/es/
2024-02-09 21:50:08 +01:00
github-actions[bot]
b70b395860 [🤖Automation] Update README with sponsors information 2024-02-09 11:53:49 +00:00
Jokob-sk
169617bdd6 404 error in the _front.log + work in progress icon #556 🚑 2024-02-09 20:50:55 +11:00
jokob-sk
3f75ead025 Merge pull request #559 from Schlump/main
Add Pushover Support by @Schlump 🙏
2024-02-09 07:24:42 +11:00
schlump
37396aad71 Add Pushover Support 2024-02-08 17:17:40 +01:00
github-actions[bot]
02de8f39b0 [🤖Automation] Update README with sponsors information 2024-02-08 11:53:46 +00:00
Jokob-sk
b33104fc5d Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-02-08 19:31:27 +11:00
Jokob-sk
13351a5db6 404 error in the browser log #556 🚑 2024-02-08 19:31:18 +11:00
github-actions[bot]
d566034421 [🤖Automation] Update README with sponsors information 2024-02-07 11:54:02 +00:00
github-actions[bot]
bd58885237 [🤖Automation] Update README with sponsors information 2024-02-06 11:53:37 +00:00
Jokob-sk
508a2d67b9 New Devices Columns #556 🚑 2024-02-06 09:01:55 +11:00
Jokob-sk
6b39a29838 Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-02-06 08:09:23 +11:00
Jokob-sk
50bcd8813a NSLOOKUP v0.1.8🚑 2024-02-06 08:08:56 +11:00
github-actions[bot]
7542761571 [🤖Automation] Update README with sponsors information 2024-02-05 11:53:47 +00:00
Jokob-sk
f80d4eef4a Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-02-05 20:51:19 +11:00
Jokob-sk
02771cf399 Workflows v0.1.13 🔀 2024-02-05 20:51:00 +11:00
Jokob-sk
6c5e0d4907 Workflows v0.1.12 2024-02-05 08:14:12 +11:00
github-actions[bot]
295f5af1eb [🤖Automation] Update README with sponsors information 2024-02-04 11:53:42 +00:00
Jokob-sk
c2446147f6 Network fixes 🚑 2024-02-04 19:05:23 +11:00
Jokob-sk
519cf9f69a Workflows v0.1.1 🆕 2024-02-04 13:17:41 +11:00
Jokob-sk
528caa900c Blank fr_f.json #547 🆕 2024-02-04 08:10:49 +11:00
Jokob-sk
fc371a6e92 NEWDEV IP filter + dark pach for overlay #539 🆕 2024-02-03 23:32:14 +11:00
Jokob-sk
476d646d72 Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-02-03 23:29:04 +11:00
Jokob-sk
a0e1d1a404 NEWDEV IP filter + dark pach for overlay #539 🆕 2024-02-03 23:24:41 +11:00
github-actions[bot]
3940761e9e [🤖Automation] Update README with sponsors information 2024-02-03 11:53:39 +00:00
Hosted Weblate
722800e9c3 Merge branch 'origin/main' into Weblate. 2024-02-03 04:34:34 +01:00
jokob-sk
cabc1ed81f Translated using Weblate (German)
Currently translated at 99.8% (660 of 661 strings)

Translation: PiAlert/PiAlert-core
Translate-URL: https://hosted.weblate.org/projects/pialert/pialert-core/de/
2024-02-03 04:34:31 +01:00
jokob-sk
b456748641 Translated using Weblate (English (United States))
Currently translated at 99.8% (604 of 605 strings)

Translation: PiAlert/PiAlert-core
Translate-URL: https://hosted.weblate.org/projects/pialert/pialert-core/en_US/
2024-02-03 04:08:51 +01:00
jokob-sk
091dc72f63 Translated using Weblate (German)
Currently translated at 99.8% (660 of 661 strings)

Translation: PiAlert/PiAlert-core
Translate-URL: https://hosted.weblate.org/projects/pialert/pialert-core/de/
2024-02-03 03:47:39 +01:00
Jokob-sk
36e143f262 Weblate setup 0.1 🆕 2024-02-03 12:53:29 +11:00
Jokob-sk
9dbf80ddcf NEWDEV MAC filter #539 🆕 2024-02-03 11:28:04 +11:00
Jokob-sk
47bdf65673 Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-02-03 07:46:20 +11:00
Jokob-sk
d58af0f4e0 NTFY priority #553 🚑 2024-02-03 07:45:57 +11:00
github-actions[bot]
fd98b52752 [🤖Automation] Update README with sponsors information 2024-02-02 11:53:52 +00:00
Jokob-sk
853a7eebd0 Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-02-02 07:55:49 +11:00
Jokob-sk
de41f02098 Maintenance -> Delete unknown devices improvement #551 🚑 2024-02-02 07:55:31 +11:00
jokob-sk
122edc8003 Merge pull request #542 from leiweibau/patch-1
Translation work de_de.json - thanks to @leiweibau 🙏
2024-02-02 07:45:39 +11:00
github-actions[bot]
6874d7c111 [🤖Automation] Update README with sponsors information 2024-02-01 11:53:40 +00:00
Jokob-sk
d14957c2b5 Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-02-01 07:57:17 +11:00
Jokob-sk
f5e39b8281 NSLOOKUP v0.1.7 2024-02-01 07:56:57 +11:00
github-actions[bot]
7137c6af8e [🤖Automation] Update README with sponsors information 2024-01-31 11:53:40 +00:00
Jokob-sk
a6ac7991e5 Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-01-30 23:09:27 +11:00
Jokob-sk
7250fb490f Sponsorship auto-generate v0.1.6 2024-01-30 23:09:24 +11:00
github-actions[bot]
b3e51f7318 [🤖Automation] Update README with sponsors information 2024-01-30 11:55:15 +00:00
Jokob-sk
f51040a589 Sponsorship auto-generate v0.1.5 2024-01-30 22:46:07 +11:00
Jokob-sk
d6e47541a5 Sponsorship auto-generate v0.1.5 2024-01-30 22:45:49 +11:00
Jokob-sk
8260759f12 NSLOOKUP v0.1.6 2024-01-30 22:26:03 +11:00
Jokob-sk
9e66ac78f8 NSLOOKUP v0.1.5 2024-01-30 22:24:01 +11:00
Jokob-sk
7b4b43463a NSLOOKUP v0.1.5 2024-01-30 22:19:59 +11:00
Jokob-sk
4e721fa8c6 Sponsorship auto-generate v0.1.3 2024-01-30 21:45:26 +11:00
Jokob-sk
635d285274 Sponsorship auto-generate v0.1.2 2024-01-30 21:43:43 +11:00
Jokob-sk
b2231a592d Reverse DNS docs + NEW status priority #549 2024-01-30 21:32:03 +11:00
github-actions[bot]
79f2c4a1b0 [🤖Automation] Update README with sponsors information 2024-01-29 20:53:21 +00:00
github-actions[bot]
84dbc37312 [🤖Automation] Update README with sponsors information 2024-01-28 20:56:07 +00:00
Jokob-sk
efef2da546 Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-01-29 07:44:45 +11:00
Jokob-sk
f05c90063c Sponsorship auto-generate v0.1.41 2024-01-29 07:44:32 +11:00
github-actions[bot]
e39c36b124 [🤖Automation] Update README with sponsors information 2024-01-28 12:49:39 +00:00
Jokob-sk
131c83826a NSLOOKUP v0.1.2 debug 2024-01-28 23:36:11 +11:00
Jokob-sk
5f5f52251a Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-01-28 23:32:33 +11:00
Jokob-sk
1f9fc71416 Sponsorship auto-generate v0.1.50 2024-01-28 23:32:27 +11:00
github-actions[bot]
3577c2143e [🤖Automation] Update README with sponsors information 2024-01-28 11:48:33 +00:00
Jokob-sk
8e603fd5f9 Sponsorship auto-generate v0.1.40 2024-01-28 22:37:44 +11:00
Jokob-sk
1c87fb1284 Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-01-28 22:37:22 +11:00
github-actions[bot]
bde1ef93cf [🤖Automation] Update README with sponsors information 2024-01-28 11:32:15 +00:00
Jokob-sk
132d6413b5 warning build fixes 2024-01-28 22:24:24 +11:00
Jokob-sk
ca82970070 Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-01-28 22:21:45 +11:00
Jokob-sk
d1e46b29d0 Sponsorship auto-generate v0.1.39 + columns fix in maintenance 2024-01-28 22:21:16 +11:00
github-actions[bot]
8fa5eb9725 [🤖Automation] Update README with sponsors information 2024-01-28 11:15:16 +00:00
Jokob-sk
bb25685691 Sponsorship auto-generate v0.1.38 2024-01-28 22:07:47 +11:00
Jokob-sk
75316a70b9 Sponsorship auto-generate v0.1.37 2024-01-28 21:46:37 +11:00
Jokob-sk
194d996f22 Sponsorship auto-generate v0.1.36 2024-01-28 21:30:57 +11:00
Jokob-sk
64418d11fc Sponsorship auto-generate v0.1.35 2024-01-28 21:10:15 +11:00
Jokob-sk
815c140f11 Sponsorship auto-generate v0.1.34 2024-01-28 20:48:54 +11:00
Jokob-sk
5bc8b51633 Sponsorship auto-generate v0.1.33 2024-01-28 14:57:59 +11:00
Jokob-sk
d807c6d0e5 Sponsorship auto-generate v0.1.32 2024-01-28 14:55:55 +11:00
Jokob-sk
a986b8c3dd Sponsorship auto-generate v0.1.31 2024-01-28 13:49:37 +11:00
Jokob-sk
ee5cf9baa4 Sponsorship auto-generate v0.1.3 2024-01-28 13:48:50 +11:00
Jokob-sk
a6a495d242 Sponsorship auto-generate v0.1.2 2024-01-28 13:47:49 +11:00
Jokob-sk
4486c57b48 Sponsorship auto-generate v0.1.1 2024-01-28 12:37:58 +11:00
Jokob-sk
7347a63f37 docs📚 2024-01-28 11:27:22 +11:00
Jokob-sk
edb3ee8c86 docs📚 2024-01-28 11:25:28 +11:00
Jokob-sk
a7227ca715 Sponsorship auto-generate v0.1 + docs 2024-01-28 11:08:32 +11:00
Jokob-sk
5cfe0bf713 NSLOOKUP v0.1.1 + docs 2024-01-27 11:11:19 +11:00
Jokob-sk
d18a59944b NSLOOKUP v0.1 2024-01-26 09:23:55 +11:00
leiweibau
ba6b971bc4 Update de_de.json 04 2024-01-23 21:30:46 +01:00
leiweibau
f772bb0e26 Update de_de.json 03 2024-01-23 16:43:24 +01:00
leiweibau
556ee04eac Update de_de.json 02 2024-01-23 13:29:56 +01:00
leiweibau
09fc16d873 Update de_de.json 01 2024-01-23 13:20:01 +01:00
Jokob-sk
f40f99aac9 Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-01-21 08:10:40 +11:00
jokob-sk
7e3cc88f2b Merge pull request #536 from leiweibau/patch-2
Update jokob-sk graph_online_history.js - thank you @leiweibau 🙏
2024-01-21 08:08:23 +11:00
jokob-sk
4b556ae8b4 Merge pull request #537 from leiweibau/patch-1
Update jokob-sk de_de.json 01 - thank you @leiweibau 🙏
2024-01-21 08:07:38 +11:00
leiweibau
8e8d61a0e0 Update jokob-sk graph_online_history.js
Depending on how many devices are contained in the list, the y-axis may contain floating point numbers. This does not make sense.
2024-01-20 15:00:47 +01:00
leiweibau
d0b7ed4b85 Update jokob-sk de_de.json 01 2024-01-20 14:53:38 +01:00
Jokob-sk
4b1c184338 docs📚 2024-01-18 17:47:01 +11:00
Jokob-sk
508b0c2d83 dig utility name discovery work #534 2024-01-18 17:10:07 +11:00
Jokob-sk
b354e72489 update issue template 2024-01-16 07:25:17 +11:00
Jokob-sk
90bfa70d1b update_vendors work #533 🔃 2024-01-13 08:56:05 +11:00
Jokob-sk
7561a8478d update_vendors work #533 🔃 2024-01-13 08:42:31 +11:00
Jokob-sk
b5afdb2bce docs📚 2024-01-08 08:08:51 +11:00
Jokob-sk
5207162d0a clickable settings cards ⚙ 2024-01-07 11:40:09 +11:00
Jokob-sk
8eecc54217 docs 📚 2024-01-07 11:18:52 +11:00
Jokob-sk
ddfd0d3cb3 docs 📚, links to plugin docs 2024-01-07 10:26:08 +11:00
Jokob-sk
bcc5b2f28a Issue templates 2024-01-06 18:59:09 +11:00
Jokob-sk
2e6be21cd9 Issue templates 2024-01-06 13:12:00 +11:00
Jokob-sk
abb28c4e5b Issue templates 2024-01-06 12:48:30 +11:00
Jokob-sk
44f0ba0924 MQTT settings to select what is send #364📩 2024-01-06 12:09:59 +11:00
Jokob-sk
a6f5e6c499 MQTT enable scheduler #522 2024-01-06 10:26:39 +11:00
Jokob-sk
d992edf6b4 MQTT more logging #522 2024-01-05 18:42:32 +11:00
Jokob-sk
d7ba540377 renamed DIG_GET_IP_ARG to INTRNT_DIG_GET_IP_ARG v0.2 2024-01-05 08:33:30 +11:00
Jokob-sk
30c95f0d5e renamed DIG_GET_IP_ARG to INTRNT_DIG_GET_IP_ARG v0.1 2024-01-05 08:29:27 +11:00
Jokob-sk
b670e3a8b1 renamed DIG_GET_IP_ARG to INTRNT_DIG_GET_IP_ARG 2024-01-05 08:18:52 +11:00
Jokob-sk
467e24d167 ARMv6 test #527 - added to prod image 2024-01-05 08:00:54 +11:00
Jokob-sk
7b70d61dd8 ARMv6 test #527 2024-01-05 07:42:43 +11:00
Jokob-sk
d530576e9b NTFPRCS work + docs 2024-01-04 15:28:43 +11:00
Jokob-sk
cff6f6393d PIHOLE_CMD_OLD bug 2024-01-04 14:37:22 +11:00
Jokob-sk
f33d753cc1 work #504 - New Dev + Events filter setting⚙ 2024-01-04 14:33:26 +11:00
Jokob-sk
6809688623 docs 📚 2024-01-04 13:26:28 +11:00
Jokob-sk
68de633143 work on #523 - ip v6 ordering support 0.1 2024-01-04 09:03:35 +11:00
Jokob-sk
a3e21ac17d work on #523 - ip v6 ordering support 2024-01-03 07:31:32 +11:00
Jokob-sk
c8267f75fa Re-initialize PIHOLE_CMD + docs 2024-01-02 08:06:00 +11:00
Jokob-sk
9e6a52ca4b work #520 - install logging 2024-01-01 10:15:25 +11:00
Jokob-sk
13c68efb8a PiHole work #513 2023-12-21 21:17:05 +11:00
Jokob-sk
47a3f7073b _meta unbound #519 2023-12-18 07:55:41 +11:00
Jokob-sk
8e0eb6a480 Notifications refactor and #242 📩 2023-12-17 22:43:50 +11:00
Jokob-sk
911c897b00 Notifications refactor 🏗 2023-12-17 10:39:14 +11:00
107 changed files with 7199 additions and 3047 deletions

View File

@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

38
.github/ISSUE_TEMPLATE/feature_request.yml vendored Executable file
View File

@@ -0,0 +1,38 @@
name: Feature Request
description: 'Suggest an idea for PiAlert'
labels: ['Feature request']
body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an open or closed issue already exists for the feature you are requesting.
options:
- label: I have searched the existing open and closed issues
required: true
- type: textarea
attributes:
label: Is your feature request related to a problem? Please describe
description: A clear and concise description of what the problem is.
validations:
required: true
- type: textarea
attributes:
label: Describe the solution you'd like
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
attributes:
label: Describe alternatives you've considered
description: A clear and concise description of any alternative solutions or features you've considered.
validations:
required: true
- type: textarea
attributes:
label: Anything else?
description: |
Links? References? Mockups? Anything that will give us more context about the feature you are encountering!
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
required: true

View File

@@ -1,46 +0,0 @@
---
name: I have an issue
about: Describe this issue template's purpose here.
title: ''
labels: ''
assignees: ''
---
## Describe the issue
> When submitting an issue ❗[enable debug](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/DEBUG_TIPS.md)❗ and [have a look at the docs](https://github.com/jokob-sk/Pi.Alert/tree/main/docs)
[describe your issue]
## Paste your `pialert.conf` (remove personal info)
```
paste_here
```
## Paste your `docker-compose.yml` and `.env` (remove personal info)
`docker-compose.yml`
```
paste_here
```
`.env`
```
paste_here
```
## Screenshots
[If applicable, add screenshots to help explain your problem.]
## Paste last few lines from `pialert.log`
> You can use `tail -100 /home/pi/pialert/front/log/pialert.log`
```bash
# paste code below

76
.github/ISSUE_TEMPLATE/i-have-an-issue.yml vendored Executable file
View File

@@ -0,0 +1,76 @@
name: Bug Report
description: 'When submitting an issue enable debug and have a look at the docs.'
labels: ['bug 🐛']
body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an open or closed issue already exists for the bug you encountered.
options:
- label: I have searched the existing open and closed issues and I checked the docs https://github.com/jokob-sk/Pi.Alert/tree/main/docs
required: true
- type: textarea
attributes:
label: Current Behavior
description: A concise description of what you're experiencing.
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: A concise description of what you expected to happen.
validations:
required: true
- type: textarea
attributes:
label: Steps To Reproduce
description: Steps to reproduce the behavior.
placeholder: |
1. With these settings...
2. With this config...
3. Run '...'
4. See error...
validations:
required: false
- type: textarea
attributes:
label: pialert.conf
description: |
Paste your `pialert.conf` (remove personal info)
render: python
validations:
required: false
- type: textarea
attributes:
label: docker-compose.yml
description: |
Paste your `docker-compose.yml`
render: python
validations:
required: false
- type: dropdown
attributes:
label: What branch are you running?
options:
- Production
- Dev
validations:
required: true
- type: textarea
attributes:
label: pialert.log
description: |
Logs with debug enabled (https://github.com/jokob-sk/Pi.Alert/blob/main/docs/DEBUG_TIPS.md) ⚠
***Generally speaking, all bug reports should have logs provided.***
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
Additionally, any additional info? Screenshots? References? Anything that will give us more context about the issue you are encountering!
You can use `tail -100 /home/pi/pialert/front/log/pialert.log` in teh container if you have troubles getting to the log files.
validations:
required: false
- type: checkboxes
attributes:
label: Debug enabled
description: I confirm I enabled `debug`
options:
- label: I have read and followed the steps in the wiki link above and provided the required debug logs and the log section covers the time when the issue occurs.
required: true

View File

@@ -1,4 +1,4 @@
name: ci-package-cleaner
name: 🤖Automation - ci-package-cleaner
on:

View File

@@ -23,13 +23,13 @@ jobs:
github.repository == 'jokob-sk/Pi.Alert'
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Set up dynamic build ARGs
id: getargs
@@ -61,7 +61,7 @@ jobs:
type=sha
- name: Log in to Github Container registry
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ghcr.io
username: jokob-sk
@@ -69,7 +69,7 @@ jobs:
- name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -82,7 +82,7 @@ jobs:
uses: docker/build-push-action@v3
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7
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

@@ -26,10 +26,10 @@ jobs:
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Set up dynamic build ARGs
id: getargs
@@ -59,7 +59,7 @@ jobs:
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') }}
- name: Log in to Github Container registry
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ghcr.io
username: jokob-sk
@@ -67,7 +67,7 @@ jobs:
- name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -76,7 +76,7 @@ jobs:
uses: docker/build-push-action@v3
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7
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 }}

29
.github/workflows/update_sponsors_table.yml vendored Executable file
View File

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

View File

@@ -6,7 +6,7 @@ The issue tracker is the preferred channel for bug reports, features requests an
Before submitting a new issue please spend a couple of minutes on research:
* Check [🛑 Common issues](https://github.com/jokob-sk/Pi.Alert/tree/main/dockerfiles#-common-issues)
* Check [🛑 Common issues](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/DEBUG_TIPS.md#common-issues)
* Check [💡 Closed issues](https://github.com/jokob-sk/Pi.Alert/issues?q=is%3Aissue+is%3Aclosed) if a similar issue was solved in the past.
## Pull-requests (PRs)

View File

@@ -2,40 +2,53 @@
Get visibility of what's going on on your WIFI/LAN network. Scan for devices, port changes and get alerts if unknown devices or changes are found. Write your own [Plugins](https://github.com/jokob-sk/Pi.Alert/tree/main/front/plugins#readme) with auto-generated UI and in-build notification system.
[![Docker](https://img.shields.io/github/actions/workflow/status/jokob-sk/Pi.Alert/docker_prod.yml?label=Build&logo=GitHub)](https://github.com/jokob-sk/Pi.Alert/actions/workflows/docker_prod.yml)
[![GitHub Committed](https://img.shields.io/github/last-commit/jokob-sk/Pi.Alert?color=40ba12&label=Committed&logo=GitHub&logoColor=fff)](https://github.com/jokob-sk/Pi.Alert)
[![Docker Size](https://img.shields.io/docker/image-size/jokobsk/pi.alert?label=Size&logo=Docker&color=0aa8d2&logoColor=fff)](https://hub.docker.com/r/jokobsk/pi.alert)
[![Docker Pulls](https://img.shields.io/docker/pulls/jokobsk/pi.alert?label=Pulls&logo=docker&color=0aa8d2&logoColor=fff)](https://hub.docker.com/r/jokobsk/pi.alert)
[![Docker Pushed](https://img.shields.io/badge/dynamic/json?color=0aa8d2&logoColor=fff&label=Pushed&query=last_updated&url=https%3A%2F%2Fhub.docker.com%2Fv2%2Frepositories%2Fjokobsk%2Fpi.alert%2F&logo=docker&link=http://left&link=https://hub.docker.com/repository/docker/jokobsk/pi.alert)](https://hub.docker.com/r/jokobsk/pi.alert)
![GitHub Release](https://img.shields.io/github/v/release/jokob-sk/Pi.Alert?color=0aa8d2&logoColor=fff&logo=GitHub)
[![GitHub Sponsors](https://img.shields.io/github/sponsors/jokob-sk?style=social)](https://github.com/sponsors/jokob-sk)
| 🐳 [Docker hub](https://registry.hub.docker.com/r/jokobsk/pi.alert) | 📑 [Docker guide](https://github.com/jokob-sk/Pi.Alert/blob/main/dockerfiles/README.md) |🆕 [Release notes](https://github.com/jokob-sk/Pi.Alert/releases) | 📚 [All Docs](https://github.com/jokob-sk/Pi.Alert/tree/main/docs) |
| 🐳 [Docker hub](https://registry.hub.docker.com/r/jokobsk/pi.alert) | 📑 [Docker guide](https://github.com/jokob-sk/Pi.Alert/blob/main/dockerfiles/README.md) |🆕 [Release notes](https://github.com/jokob-sk/Pi.Alert/releases) | 📚 [All Docs](https://github.com/jokob-sk/Pi.Alert/tree/main/docs) |
|----------------------|----------------------| ----------------------| ----------------------|
## Why PiAlert❓
Most of us don't know what's going on on our home network, but we want our family and data to be safe. _Command-line tools_ are great, but the output can be _hard to understand_ and action if you are not a network specialist.
PiAlert gives you peace of mind. _Visualize and immediately report 📬_ what is going on in your network - this is the first step to enhance your _network security 🔐_.
_PiAlert combines several network and other scanning tools 🔍 with notifications 📧 into one user-friendly package 📦_. You get an overview of network device Sessions, Connected devices, Events, Presence, Down alerts, and IPs. You can schedule Nmap scans to detect changes in device ports and visualize your Network topology (even with undetectable, dummy devices).
Setup a _kill switch ☠_ for your network via a smart plug with the available [Home Assistant](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/HOME_ASSISTANT.md) integration. Implement custom automations with the [CSV device Exports 📤](https://github.com/jokob-sk/Pi.Alert/tree/main/front/plugins/csv_backup), [Webhooks](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/WEBHOOK_N8N.md), or [API endpoints](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/API.md) features.
Extend the app if you want to create your own scanner [Plugin](https://github.com/jokob-sk/Pi.Alert/tree/main/front/plugins#readme) and handle the results and notifications in PiAlert.
Looking forward to your contributions if you decide to share your work with the community ❤.
| ![Main screen][main] | ![Screen 1][screen1] | ![Screen 5][screen5] |
|----------------------|----------------------| ----------------------|
| ![Screen 3][screen3] | ![Screen 4][screen4] | ![Screen 6][screen6] |
| ![Screen 8][screen8] | ![Report 2][report2] | ![Screen 9][screen9] |
<details>
<summary>📷 Click for more screenshots</summary>
| ![Screen 3][screen3] | ![Screen 4][screen4] | ![Screen 6][screen6] |
|----------------------|----------------------|----------------------|
| ![Screen 8][screen8] | ![Report 2][report2] | ![Screen 9][screen9] |
</details>
<details>
<summary>❓ Why use PiAlert?</summary>
<hr>
Most of us don't know what's going on on our home network, but we want our family and data to be safe. _Command-line tools_ are great, but the output can be _hard to understand_ and action if you are not a network specialist.
PiAlert gives you peace of mind. _Visualize and immediately report 📬_ what is going on in your network - this is the first step to enhance your _network security 🔐_.
PiAlert combines several network and other scanning tools 🔍 with notifications 📧 into one user-friendly package 📦.
Setup a _kill switch ☠_ for your network via a smart plug with the available [Home Assistant](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/HOME_ASSISTANT.md) integration. Implement custom automations with the [CSV device Exports 📤](https://github.com/jokob-sk/Pi.Alert/tree/main/front/plugins/csv_backup), [Webhooks](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/WEBHOOK_N8N.md), or [API endpoints](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/API.md) features.
Extend the app if you want to create your own scanner [Plugin](https://github.com/jokob-sk/Pi.Alert/tree/main/front/plugins#readme) and handle the results and notifications in PiAlert.
Looking forward to your contributions if you decide to share your work with the community ❤.
</details>
## Scan Methods, Notifications, Integration, Extension system
| Features | Details |
|-------------|-------------|
| 🔍 | The app scans your network for, **New devices**, **New connections** (re-connections), **Disconnections**, **"Always Connected" devices down**, Devices **IP changes** and **Internet IP address changes**. Discovery & scan methods include: **arp-scan**. **Pi-hole - DB import**, **Pi-hole - DHCP leases import**, **Generic DHCP leases import**. **UNIFI controller import**, **SNMP-enabled router import**. Check the [Plugins](https://github.com/jokob-sk/Pi.Alert/tree/main/front/plugins#readme) docs for more info on individual scans. |
|📧 | Send notifications to more than 80+ services, including Telegram via [Apprise](https://hub.docker.com/r/caronc/apprise), or use [Pushsafer](https://www.pushsafer.com/), or [NTFY](https://ntfy.sh/). |
|📧 | Send notifications to more than 80+ services, including Telegram via [Apprise](https://hub.docker.com/r/caronc/apprise), or use [Pushsafer](https://www.pushsafer.com/), [Pushover](https://www.pushover.net/), or [NTFY](https://ntfy.sh/). |
|🧩 | Feed your data and device changes into [Home Assistant](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/HOME_ASSISTANT.md), read [API endpoints](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/API.md), or use [Webhooks](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/WEBHOOK_N8N.md) to setup custom automation flows. |
| | Build your own scanners with the [Plugin system](https://github.com/jokob-sk/Pi.Alert/tree/main/front/plugins#readme) |
@@ -56,26 +69,58 @@ Looking forward to your contributions if you decide to share your work with the
> - [WatchYourLAN](https://github.com/aceberg/WatchYourLAN) - Lightweight network IP scanner with web GUI (Open source)
> - [Fing](https://www.fing.com/) - Network scanner app for your Internet security (Commercial, Phone App, Proprietary hardware)
## ❤ Support me
## ❤ Support me for...
Get:
- I don't get burned out and the app survives longer🔥🤯
- Regular updates to keep your data and family safe 🔄
- Better and more functionality
- I don't get burned out and the app survives longer🔥🤯
- Quicker and better support with issues 🆘
- Less grumpy me 😄
| [![GitHub](https://i.imgur.com/emsRCPh.png)](https://github.com/sponsors/jokob-sk) | [![Buy Me A Coffee](https://i.imgur.com/pIM6YXL.png)](https://www.buymeacoffee.com/jokobsk) | [![Patreon](https://i.imgur.com/MuYsrq1.png)](https://www.patreon.com/user?u=84385063) |
| --- | --- | --- |
- Bitcoin: `1N8tupjeCK12qRVU2XrV17WvKK7LCawyZM`
- Ethereum: `0x6e2749Cb42F4411bc98501406BdcD82244e3f9C7`
<details>
<summary>Click for more ways to donate</summary>
<hr>
> 📧 Email me at [jokob@duck.com](mailto:jokob@duck.com?subject=PiAlert) if you want to get in touch or if I should add other sponsorship platforms.
- Bitcoin: `1N8tupjeCK12qRVU2XrV17WvKK7LCawyZM`
- Ethereum: `0x6e2749Cb42F4411bc98501406BdcD82244e3f9C7`
📧 Email me at [jokob@duck.com](mailto:jokob@duck.com?subject=PiAlert) if you want to get in touch or if I should add other sponsorship platforms.
</details>
### ⭐ Sponsors
[![GitHub Sponsors](https://img.shields.io/github/sponsors/jokob-sk?style=social)](https://github.com/sponsors/jokob-sk)
Thank you to all the wonderful people who are sponsoring this project (=preventing my burnout🔥🤯):
<!-- SPONSORS-LIST DO NOT MODIFY BELOW -->
| All Sponsors |
|---|
| [dtech77pl](https://github.com/dtech77pl) |
| [Tony Hanratty](https://github.com/thanratty) |
<!-- SPONSORS-LIST DO NOT MODIFY ABOVE -->
## Everything else
<!--- --------------------------------------------------------------------- --->
### 🌍 Translations
Proudly using [Weblate](https://hosted.weblate.org/projects/pialert/).
<a href="https://hosted.weblate.org/engage/pialert/">
<img src="https://hosted.weblate.org/widget/pialert/core/multi-auto.svg" alt="Translation status" />
</a>
Help out and suggest languages in the [online portal of Weblate](https://hosted.weblate.org/projects/pialert/core/).
### License
> GPL 3.0 | [Read more here](LICENSE.txt) | Source of the [animated GIF (Loading Animation)](https://commons.wikimedia.org/wiki/File:Loading_Animation.gif) | Source of the [selfhosted Fonts](https://github.com/adobe-fonts/source-sans)

View File

@@ -21,7 +21,6 @@ SCAN_SUBNETS=['192.168.1.0/24 --interface=eth1']
TIMEZONE='Europe/Berlin'
PIALERT_WEB_PROTECTION=False
PIALERT_WEB_PASSWORD='8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92'
INCLUDED_SECTIONS=['internet','new_devices','down_devices','events']
DAYS_TO_KEEP_EVENTS=90
# Used for generating links in emails. Make sure not to add a trailing slash!
REPORT_DASHBOARD_URL='http://pi.alert'

View File

@@ -29,10 +29,7 @@ sudo cp -- *.csv 2_backup
echo ""
echo Download Start
echo ""
sudo curl "$1" -LO https://standards-oui.ieee.org/iab/iab.csv \
-LO https://standards-oui.ieee.org/iab/iab.txt \
-LO https://standards-oui.ieee.org/oui28/mam.csv \
-LO https://standards-oui.ieee.org/iab/iab.txt \
sudo curl "$1" -LO https://standards-oui.ieee.org/oui28/mam.csv \
-LO https://standards-oui.ieee.org/oui28/mam.csv \
-LO https://standards-oui.ieee.org/oui28/mam.txt \
-LO https://standards-oui.ieee.org/oui36/oui36.csv \

View File

@@ -17,7 +17,17 @@
"title": "Pi.Alert Notifications",
"title_link": "",
"text": {
"internet": [],
"new_devices_meta": {
"title": "New devices",
"columnNames": [
"MAC",
"Datetime",
"IP",
"Event Type",
"Device name",
"Comments"
]
},
"new_devices": [
{
"MAC": "74:ac:74:ac:74:ac",
@@ -29,7 +39,29 @@
"Device Vendor": null
}
],
"down_devices_meta": {
"title": "Down devices",
"columnNames": [
"MAC",
"Datetime",
"IP",
"Event Type",
"Device name",
"Comments"
]
},
"down_devices": [],
"events_meta": {
"title": "Events",
"columnNames": [
"MAC",
"Datetime",
"IP",
"Event Type",
"Device name",
"Comments"
]
},
"events": [
{
"MAC": "74:ac:74:ac:74:ac",
@@ -50,6 +82,20 @@
"Device Vendor": null
}
],
"plugins_meta": {
"title": "Plugins",
"columnNames": [
"Plugin",
"Object_PrimaryID",
"Object_SecondaryID",
"DateTimeChanged",
"Watched_Value1",
"Watched_Value2",
"Watched_Value3",
"Watched_Value4",
"Status"
]
},
"plugins": [
{
"Index": 138,

View File

@@ -11,10 +11,10 @@ services:
network_mode: host
# restart: unless-stopped
volumes:
# - ${APP_DATA_LOCATION}/pialert_dev/config:/home/pi/pialert/config
- ${APP_DATA_LOCATION}/pialert/config:/home/pi/pialert/config
# - ${APP_DATA_LOCATION}/pialert_dev/db:/home/pi/pialert/db
- ${APP_DATA_LOCATION}/pialert/db:/home/pi/pialert/db
- ${APP_DATA_LOCATION}/pialert_dev/config:/home/pi/pialert/config
# - ${APP_DATA_LOCATION}/pialert/config:/home/pi/pialert/config
- ${APP_DATA_LOCATION}/pialert_dev/db:/home/pi/pialert/db
# - ${APP_DATA_LOCATION}/pialert/db:/home/pi/pialert/db
# (optional) useful for debugging if you have issues setting up the container
- ${LOGS_LOCATION}:/home/pi/pialert/front/log
# ---------------------------------------------------------------------------
@@ -29,6 +29,7 @@ services:
- ${APP_DATA_LOCATION}/pialert/php.ini:/etc/php/8.2/fpm/php.ini
- ${DEV_LOCATION}/install:/home/pi/pialert/install
- ${DEV_LOCATION}/front/css:/home/pi/pialert/front/css
- ${DEV_LOCATION}/back/update_vendors.sh:/home/pi/pialert/back/update_vendors.sh
- ${DEV_LOCATION}/front/lib/AdminLTE:/home/pi/pialert/front/lib/AdminLTE
- ${DEV_LOCATION}/front/js:/home/pi/pialert/front/js
- ${DEV_LOCATION}/dockerfiles/start.sh:/home/pi/pialert/dockerfiles/start.sh
@@ -51,7 +52,8 @@ services:
- ${DEV_LOCATION}/front/settings.php:/home/pi/pialert/front/settings.php
- ${DEV_LOCATION}/front/systeminfo.php:/home/pi/pialert/front/systeminfo.php
- ${DEV_LOCATION}/front/report.php:/home/pi/pialert/front/report.php
- ${DEV_LOCATION}/front/flows.php:/home/pi/pialert/front/flows.php
- ${DEV_LOCATION}/front/workflows.php:/home/pi/pialert/front/workflows.php
- ${DEV_LOCATION}/front/appEventsCore.php:/home/pi/pialert/front/appEventsCore.php
- ${DEV_LOCATION}/front/donations.php:/home/pi/pialert/front/donations.php
- ${DEV_LOCATION}/front/plugins:/home/pi/pialert/front/plugins
# DELETE END anyone trying to use this file: comment out / delete ABOVE lines, they are only for development purposes

View File

@@ -1,8 +1,8 @@
[![Docker](https://img.shields.io/github/actions/workflow/status/jokob-sk/Pi.Alert/docker_prod.yml?label=Build&logo=GitHub)](https://github.com/jokob-sk/Pi.Alert/actions/workflows/docker_prod.yml)
[![GitHub Committed](https://img.shields.io/github/last-commit/jokob-sk/Pi.Alert?color=40ba12&label=Committed&logo=GitHub&logoColor=fff)](https://github.com/jokob-sk/Pi.Alert)
[![Docker Size](https://img.shields.io/docker/image-size/jokobsk/pi.alert?label=Size&logo=Docker&color=0aa8d2&logoColor=fff)](https://hub.docker.com/r/jokobsk/pi.alert)
[![Docker Pulls](https://img.shields.io/docker/pulls/jokobsk/pi.alert?label=Pulls&logo=docker&color=0aa8d2&logoColor=fff)](https://hub.docker.com/r/jokobsk/pi.alert)
[![Docker Pushed](https://img.shields.io/badge/dynamic/json?color=0aa8d2&logoColor=fff&label=Pushed&query=last_updated&url=https%3A%2F%2Fhub.docker.com%2Fv2%2Frepositories%2Fjokobsk%2Fpi.alert%2F&logo=docker&link=http://left&link=https://hub.docker.com/repository/docker/jokobsk/pi.alert)](https://hub.docker.com/r/jokobsk/pi.alert)
![GitHub Release](https://img.shields.io/github/v/release/jokob-sk/Pi.Alert?color=0aa8d2&logoColor=fff&logo=GitHub)
[![GitHub Sponsors](https://img.shields.io/github/sponsors/jokob-sk?style=social)](https://github.com/sponsors/jokob-sk)
# PiAlert 💻🔍 Network security scanner & notification framework
@@ -16,9 +16,12 @@
<img src="https://raw.githubusercontent.com/jokob-sk/Pi.Alert/main/docs/img/network.png" width="300px" />
</a>
> [!NOTE]
> There is also an experimental 🧪 [bare-metal install](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/HW_INSTALL.md) method available.
## 📕 Basic Usage
- You will have to run the container on the host network, e.g:
- You will have to run the container on the `host` network, e.g:
```yaml
docker run -d --rm --network=host \
@@ -27,8 +30,8 @@ docker run -d --rm --network=host \
-e TZ=Europe/Berlin \
-e PORT=20211 \
jokobsk/pi.alert:latest
```
- The initial scan can take up-to 15min (with 50 devices and MQTT). Subsequent ones 3 and 5 minutes so wait that long for all of the scans to run.
```
- The initial scan can take up to 15min (with 50 devices and MQTT). Subsequent ones 3 and 5 minutes so wait that long for all of the scans to run.
### Docker environment variables
@@ -42,22 +45,23 @@ docker run -d --rm --network=host \
### Docker paths
| | Path | Description |
| :------------- | :------------- |:-------------|
| **Required** | `:/home/pi/pialert/config` | Folder which will contain the `pialert.conf` file (see below for details) |
| **Required** | `:/home/pi/pialert/db` | Folder which will contain the `pialert.db` file |
|Optional| `:/home/pi/pialert/front/log` | Logs folder useful for debugging if you have issues setting up the container |
|Optional| `:/etc/pihole/pihole-FTL.db` | PiHole's `pihole-FTL.db` database file. Required if you want to use PiHole |
|Optional| `:/etc/pihole/dhcp.leases` | PiHole's `dhcp.leases` file. Required if you want to use PiHole `dhcp.leases` file. This has to be matched with a corresponding `DHCPLSS_paths_to_check` setting entry. (the path in the container must contain `pihole`)|
|Optional| `:/home/pi/pialert/front/api` | A simple [API endpoint](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/API.md) containing static (but regularly updated) json and other files. |
|Optional| `:/home/pi/pialert/front/plugins/<plugin>/ignore_plugin` | Map a file `ignore_plugin` to ignore a plugin. Plugins can be soft-disabled via settings. More in the [Plugin docs](/front/plugins/README.md). |
| Required | Path | Description |
| :------------- | :------------- | :-------------|
| ✅ | `:/home/pi/pialert/config` | Folder which will contain the `pialert.conf` file (see below for details) |
| ✅ | `:/home/pi/pialert/db` | Folder which will contain the `pialert.db` file |
| | `:/home/pi/pialert/front/log` | Logs folder useful for debugging if you have issues setting up the container |
| | `:/etc/pihole/pihole-FTL.db` | PiHole's `pihole-FTL.db` database file. Required if you want to use PiHole DB mapping. |
| | `:/etc/pihole/dhcp.leases` | PiHole's `dhcp.leases` file. Required if you want to use PiHole `dhcp.leases` file. This has to be matched with a corresponding `DHCPLSS_paths_to_check` setting entry (the path in the container must contain `pihole`)|
| | `:/home/pi/pialert/front/api` | A simple [API endpoint](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/API.md) containing static (but regularly updated) json and other files. |
| | `:/home/pi/pialert/front/plugins/<plugin>/ignore_plugin` | Map a file `ignore_plugin` to ignore a plugin. Plugins can be soft-disabled via settings. More in the [Plugin docs](https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/README.md). |
| | `:/etc/resolv.conf` | Use a custom `resolv.conf` file for [better name resolution](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/REVERSE_DNS.md). |
### Config (`pialert.conf`)
### Modify the config (`pialert.conf`) only if UI is not available
- If unavailable, the app generates a default `pialert.conf` and `pialert.db` file on the first run.
- The preferred way is to manage the configuration via the Settings section in the UI.
- You can modify [pialert.conf](https://github.com/jokob-sk/Pi.Alert/tree/main/config) directly, if needed.
- If unavailable, the app generates a default `pialert.conf` and `pialert.db` file on the first run.
#### Important settings
@@ -86,10 +90,13 @@ There are 2 approaches how to get PiHole devices imported. Via the PiHole import
#### 🧭 Community guides
> Primarily use the official installation guides in this document and use community content as suplementary material. Open an issue if you'd like to add your link to the list 🙏
Use the official installation guides at first and use community content as suplementary material. Open an issue if you'd like to add your link to the list 🙏
- 📄 [How to Install Pi.Alert on Your Synology NAS - Marius hosting (English)](https://mariushosting.com/how-to-install-pi-alert-on-your-synology-nas/) (Updated frequently)
- 📄 [Using the PiAlert Network Security Scanner on a Raspberry Pi - PiMyLifeUp (English)](https://pimylifeup.com/raspberry-pi-pialert/)
- ▶ [How to Setup Pi.Alert on Your Synology NAS - Digital Aloha (English)](https://www.youtube.com/watch?v=M4YhpuRFaUg)
- 📄 [시놀/헤놀에서 네트워크 스캐너 Pi.Alert Docker로 설치 및 사용하기 (Korean)](https://blog.dalso.org/article/%EC%8B%9C%EB%86%80-%ED%97%A4%EB%86%80%EC%97%90%EC%84%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%8A%A4%EC%BA%90%EB%84%88-pi-alert-docker%EB%A1%9C-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EC%82%AC%EC%9A%A9) (July 2023)
- 📄 [网络入侵探测器Pi.Alert (Chinese)](https://codeantenna.com/a/VgUvIAjZ7J) (May 2023)
- ▶ [Pi.Alert auf Synology & Docker by - Jürgen Barth (German)](https://www.youtube.com/watch?v=-ouvA2UNu-A) (March 2023)
- ▶ [Top Docker Container for Home Server Security - VirtualizationHowto (English)](https://www.youtube.com/watch?v=tY-w-enLF6Q) (March 2023)
- ▶ [Pi.Alert or WatchYourLAN can alert you to unknown devices appearing on your WiFi or LAN network - Danie van der Merwe (English)](https://www.youtube.com/watch?v=v6an9QG2xF0) (November 2022)
@@ -229,11 +236,7 @@ Courtesy of [pbek](https://github.com/pbek). The volume `pialert_db` is used by
## 🏅 Recognitions
Big thanks to <a href="https://github.com/Macleykun">@Macleykun</a> for help and tips&tricks for Dockerfile(s):
<a href="https://github.com/Macleykun">
<img src="https://avatars.githubusercontent.com/u/26381427?size=50">
</a>
Big thanks to <a href="https://github.com/Macleykun">@Macleykun</a> for help and tips&tricks for Dockerfile(s).
## ❤ Support me

View File

@@ -11,6 +11,7 @@ INSTALL_DIR=/home/pi # Specify the installation directory here
WEB_UI_DIR=/var/www/html/pialert
NGINX_CONFIG_FILE=/etc/nginx/conf.d/pialert.conf
OUI_FILE="/usr/share/arp-scan/ieee-oui.txt" # Define the path to ieee-oui.txt and ieee-iab.txt
FILEDB=$INSTALL_DIR/pialert/db/pialert.db
# DO NOT CHANGE ANYTHING ABOVE THIS LINE!
# if custom variables not set we do not need to do anything
@@ -91,26 +92,37 @@ else
fi
fi
# Create an empty log files
# Create the execution_queue.log file if it doesn't exist
touch "$INSTALL_DIR/pialert/front/log/execution_queue.log"
# Create the pialert_front.log file if it doesn't exist
touch "$INSTALL_DIR/pialert/front/log/pialert_front.log"
# Fixing file permissions
echo "[INSTALL] Fixing file permissions"
echo "[INSTALL] Fixing WEB_UI_DIR: $WEB_UI_DIR"
chmod -R a+rwx $WEB_UI_DIR
echo "[INSTALL] Fixing INSTALL_DIR: $INSTALL_DIR"
chmod -R a+rw $INSTALL_DIR/pialert/front/log
chmod -R a+rwx $INSTALL_DIR
FILEDB=$INSTALL_DIR/pialert/db/pialert.db
if [ -f "$FILEDB" ]; then
chown -R www-data:www-data $INSTALL_DIR/pialert/db/pialert.db
fi
echo "[INSTALL] Copy starter pialert.db and pialert.conf if they don't exist"
# Copy starter pialert.db and pialert.conf if they don't exist
cp -n "$INSTALL_DIR/pialert/back/pialert.conf" "$INSTALL_DIR/pialert/config/pialert.conf"
cp -n "$INSTALL_DIR/pialert/back/pialert.db" "$INSTALL_DIR/pialert/db/pialert.db"
cp -n "$INSTALL_DIR/pialert/back/pialert.db" "$FILEDB"
echo "[INSTALL] Fixing permissions after copied starter config & DB"
if [ -f "$FILEDB" ]; then
chown -R www-data:www-data $FILEDB
fi
chmod -R a+rwx $INSTALL_DIR # second time after we copied the files
chmod -R a+rw $INSTALL_DIR/pialert/config
@@ -122,7 +134,6 @@ if [ ! -f "$INSTALL_DIR/pialert/front/buildtimestamp.txt" ]; then
date +%s > "$INSTALL_DIR/pialert/front/buildtimestamp.txt"
fi
# start PHP
/etc/init.d/php8.2-fpm start
/etc/init.d/nginx start
@@ -137,5 +148,7 @@ fi
# Activate the virtual python environment
source myenv/bin/activate
echo "[INSTALL] 🚀 Starting app - navigate to your <server IP>:$PORT"
# Start the PiAlert python script
python $INSTALL_DIR/pialert/pialert/

View File

@@ -12,7 +12,7 @@
| CurrentScan | Result of the current scan | ![Screen1][screen1] |
| Devices | The main devices database that also contains the Network tree mappings. If `ScanCycle` is set to `0` device is not scanned. | ![Screen2][screen2] |
| Events | Used to collect connection/disconnection events. | ![Screen4][screen4] |
| Online_History | Used to display the `Device presence over time` chart | ![Screen6][screen6] |
| Online_History | Used to display the `Device presence` chart | ![Screen6][screen6] |
| Parameters | Used to pass values between the frontend and backend. | ![Screen7][screen7] |
| Pholus_Scan | Scan results of the Pholus python network penetration script. | ![Screen8][screen8] |
| Plugins_Events | For capturing events exposed by a plugin via the `last_result.log` file. If unique then saved into the `Plugins_Objects` table. Entries are deleted once processed and stored in the `Plugins_History` and/or `Plugins_Objects` tables. | ![Screen10][screen10] |

View File

@@ -8,22 +8,27 @@ Check the the HTTP response of the failing backend call by following these steps
![F12DeveloperConsole][F12DeveloperConsole]
- Copy the URL causing the error and enter it in the address bar of your browser directly and hit enter. The copied URLs could look something like this (notice the query strings at the end):
- `http://<pialert URL>:20211/api/table_devices.json?nocache=1704141103121`
- `http://<pialert URL>:20211/php/server/devices.php?action=getDevicesTotals`
- `http://<pialert URL>:20211/php/server/devices.php?action=getDevicesList&status=all`
- `http://<pialert URL>:20211/php/server/devices.php?action=getDevicesList&status=all`
- Post the error response in the existing issue thread on GitHub or create a new issue and include the redacted response of the failing query.
For reference, the above queries should return results in the following format:
First URL:
## First URL:
- Should yield a valid JSON file
## Second URL:
![array][array]
Second URL:
## Third URL:
![json][json]
You can copy and paste any JSON result (result of the second query) into an online JSON checker, such as [this one](https://jsonchecker.com/) to check if it's valid.
You can copy and paste any JSON result (result of the First and Third query) into an online JSON checker, such as [this one](https://jsonchecker.com/) to check if it's valid.
[F12DeveloperConsole]: ./img/DEBUG/Invalid_JSON_repsonse_debug.png "F12DeveloperConsole"

74
docs/DEBUG_PLUGINS.md Executable file
View File

@@ -0,0 +1,74 @@
# Troubleshooting plugins
## High-level overview
If a Plugin supplies data to the main app it's doine either vie a SQL query or via a script that updates the `last_result.log` file in the plugin folder (`front/plugins/<plugin>`).
For a more in-depth overview on how plugins work check the [Plugins development docs](https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/README.md).
### Prerequisites
- Make sure you read and followed the specific plugin setup instructions.
- Ensure you have [debug enabled (see More Logging)](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/DEBUG_TIPS.md#1-more-logging-)
### Potential issues
- Bugs
- Unexpected input (e.g. special characters in names)
- Dependencies changed how data is output
#### Incorrect input data
Input data from the plugin might cause mapping issues in specific edge cases. Look for a corresponding section in the `pialert.log` file, for example notice the first line of the execution run of the `PIHOLE` plugin below:
```
17:31:05 [Scheduler] - Scheduler run for PIHOLE: YES
17:31:05 [Plugin utils] ---------------------------------------------
17:31:05 [Plugin utils] display_name: PiHole (Device sync)
17:31:05 [Plugins] CMD: SELECT n.hwaddr AS Object_PrimaryID, {s-quote}null{s-quote} AS Object_SecondaryID, datetime() AS DateTime, na.ip AS Watched_Value1, n.lastQuery AS Watched_Value2, na.name AS Watched_Value3, n.macVendor AS Watched_Value4, {s-quote}null{s-quote} AS Extra, n.hwaddr AS ForeignKey FROM EXTERNAL_PIHOLE.Network AS n LEFT JOIN EXTERNAL_PIHOLE.Network_Addresses AS na ON na.network_id = n.id WHERE n.hwaddr NOT LIKE {s-quote}ip-%{s-quote} AND n.hwaddr is not {s-quote}00:00:00:00:00:00{s-quote} AND na.ip is not null
17:31:05 [Plugins] setTyp: subnets
17:31:05 [Plugin utils] Flattening the below array
17:31:05 ['192.168.1.0/24 --interface=eth1']
17:31:05 [Plugin utils] isinstance(arr, list) : False | isinstance(arr, str) : True
17:31:05 [Plugins] Resolved value: 192.168.1.0/24 --interface=eth1
17:31:05 [Plugins] Convert to Base64: True
17:31:05 [Plugins] base64 value: b'MTkyLjE2OC4xLjAvMjQgLS1pbnRlcmZhY2U9ZXRoMQ=='
17:31:05 [Plugins] Timeout: 10
17:31:05 [Plugins] Executing: SELECT n.hwaddr AS Object_PrimaryID, 'null' AS Object_SecondaryID, datetime() AS DateTime, na.ip AS Watched_Value1, n.lastQuery AS Watched_Value2, na.name AS Watched_Value3, n.macVendor AS Watched_Value4, 'null' AS Extra, n.hwaddr AS ForeignKey FROM EXTERNAL_PIHOLE.Network AS n LEFT JOIN EXTERNAL_PIHOLE.Network_Addresses AS na ON na.network_id = n.id WHERE n.hwaddr NOT LIKE 'ip-%' AND n.hwaddr is not '00:00:00:00:00:00' AND na.ip is not null
17:31:05 [Plugins] SUCCESS, received 2 entries
17:31:05 [Plugins] sqlParam entries: [(0, 'PIHOLE', '01:01:01:01:01:01', 'null', 'null', '2023-12-25 06:31:05', '172.30.0.1', 0, 'aaaa', 'vvvvvvvvv', 'not-processed', 'null', 'null', '01:01:01:01:01:01'), (0, 'PIHOLE', '02:42:ac:1e:00:02', 'null', 'null', '2023-12-25 06:31:05', '172.30.0.2', 0, 'dddd', 'vvvvv2222', 'not-processed', 'null', 'null', '02:42:ac:1e:00:02')]
17:31:05 [Plugins] Processing : PIHOLE
17:31:05 [Plugins] Existing objects from Plugins_Objects: 4
17:31:05 [Plugins] Logged events from the plugin run : 2
17:31:05 [Plugins] pluginEvents count: 2
17:31:05 [Plugins] pluginObjects count: 4
17:31:05 [Plugins] events_to_insert count: 0
17:31:05 [Plugins] history_to_insert count: 4
17:31:05 [Plugins] objects_to_insert count: 0
17:31:05 [Plugins] objects_to_update count: 4
17:31:05 [Plugin utils] In pluginEvents there are 2 events with the status "watched-not-changed"
17:31:05 [Plugin utils] In pluginObjects there are 2 events with the status "missing-in-last-scan"
17:31:05 [Plugin utils] In pluginObjects there are 2 events with the status "watched-not-changed"
17:31:05 [Plugins] Mapping objects to database table: CurrentScan
17:31:05 [Plugins] SQL query for mapping: INSERT into CurrentScan ( "cur_MAC", "cur_IP", "cur_LastQuery", "cur_Name", "cur_Vendor", "cur_ScanMethod") VALUES ( ?, ?, ?, ?, ?, ?)
17:31:05 [Plugins] SQL sqlParams for mapping: [('01:01:01:01:01:01', '172.30.0.1', 0, 'aaaa', 'vvvvvvvvv', 'PIHOLE'), ('02:42:ac:1e:00:02', '172.30.0.2', 0, 'dddd', 'vvvvv2222', 'PIHOLE')]
17:31:05 [API] Update API starting
17:31:06 [API] Updating table_plugins_history.json file in /front/api
```
In the above output notice the section logging how many events are produced by the plugin:
```
17:31:05 [Plugins] Existing objects from Plugins_Objects: 4
17:31:05 [Plugins] Logged events from the plugin run : 2
17:31:05 [Plugins] pluginEvents count: 2
17:31:05 [Plugins] pluginObjects count: 4
17:31:05 [Plugins] events_to_insert count: 0
17:31:05 [Plugins] history_to_insert count: 4
17:31:05 [Plugins] objects_to_insert count: 0
17:31:05 [Plugins] objects_to_update count: 4
```
These values, if formatted correctly, will also show up in the UI:
![Plugins table](/docs/img/DEBUG_PLUGINS/plugin_objects_pihole.png)

View File

@@ -56,7 +56,7 @@ services:
* If facing issues (AJAX errors, can't write to DB, empty screen, etc,) make sure permissions are set correctly, and check the logs under `/home/pi/pialert/front/log`.
* To solve permission issues you can try setting the owner and group of the `pialert.db` by executing the following on the host system: `docker exec pialert chown -R www-data:www-data /home/pi/pialert/db/pialert.db`.
* Map to local User and Group IDs. Specify the enviroment variables `HOST_USER_ID` and `HOST_USER_GID` if needed.
* If still facing issues, try to map the pialert.db file (⚠ not folder) to `:/home/pi/pialert/db/pialert.db` (see Examples below for details)
* If still facing issues, try to map the pialert.db file (⚠ not folder) to `:/home/pi/pialert/db/pialert.db` (see [docker-compose Examples](https://github.com/jokob-sk/Pi.Alert/blob/main/dockerfiles/README.md#-docker-composeyml-examples) for details)
### Container restarts / crashes

View File

@@ -15,7 +15,7 @@
![Settings > CSV Backup](/docs/img/DEVICES_BULK_EDITING/CSV_BACKUP_SETTINGS.png)
> [!NOTE]
> Keep Linux line endings (sugegsted editors: Nano, Notepad++)
> Keep Linux line endings (suggested editors: Nano, Notepad++)
![Nodepad++ line endings](/docs/img/DEVICES_BULK_EDITING/NOTEPAD++.png)

View File

@@ -46,8 +46,7 @@ To edit device information:
- **Alert All Events**: Send a notification in each event (connection,
disconnection, IP Changed, ...)
- **Alert Down**: Send a notification when the device is down
- *(Userful with "always connected" devices: Router, AP, Camera, Alexa,
...)*
- *(Userful with "always connected" devices: Camera, Alexa,...)*
- **Skip repeated notifications during**: Do not send more than one
notification to this device for X hours
- *(Useful to avoid notification saturation on devices that frequently

View File

@@ -1,6 +1,6 @@
# How to install PiAlert on the server hardware
To download and install PiAlert on the hardware/server directly use `curl` or `wget` commands.
To download and install PiAlert on the hardware/server directly use the `curl` or `wget` commands at the bottom of this page.
> [!NOTE]
> This is an Experimental feature 🧪 and it relies on community support.
@@ -32,13 +32,13 @@ Some facts about what and where something will be changed/installed by the HW in
- Only tested to work on Debian Bookworm (Debian 12).
- **EXPERIMENTAL** and not recommended way to install PiAlert.
## CURL
## 📥 Installation via CURL
```bash
curl -o install.sh https://raw.githubusercontent.com/jokob-sk/Pi.Alert/main/install/install.sh && sudo chmod +x install.sh && sudo ./install.sh
```
## WGET
## 📥 Installation via WGET
```bash
wget https://raw.githubusercontent.com/jokob-sk/Pi.Alert/main/install/install.sh -O install.sh && sudo chmod +x install.sh && sudo ./install.sh

View File

@@ -1,6 +1,6 @@
## How to setup your Network page
Make sure you have a root device with the MAC `Internet` (No other MAC addresses are currently supported as the root node).
Make sure you have a root device with the MAC `Internet` (No other MAC addresses are currently supported as the root node) set to a network device type (e.g.: **Type**:`Router`).
> 💡 Tip: You can add dummy devices via the [Undiscoverables plugin](https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/undiscoverables/README.md)
@@ -8,12 +8,11 @@ Make sure you have a root device with the MAC `Internet` (No other MAC addresses
## ⚡Quick setup:
* Go to Devices > Device Details.
* Find the device(s) you want to use as network devices (network nodes).
* Set the Type of such a device to one of the following: AP, Firewall, Gateway, PLC, Powerline, Router, Switch, USB LAN Adapter, USB WIFI Adapter and WLAN.
* Go to a Device you want to use as network device (network nodes, such as a Switch).
* Set the **Type** of such a device to one of the following: AP, Firewall, Gateway, PLC, Powerline, Router, Switch, USB LAN Adapter, USB WIFI Adapter and WLAN (you can create a custom network type device with in Settings -> General -> `NETWORK_DEVICE_TYPES`).
* Save and go to Network where the devices you've marked as network devices (by selecting the Type as mentioned above) will show up as tabs.
* You can now assign the Unassigend devices to the correct network node.
* If port is empty or 0 a wifi icon is rendered, otherwise a ethernet port icon
* You can now assign the Unassigend devices to the network node.
* If port is empty or 0 a wifi icon is rendered, otherwise a ethernet port icon.
> [!NOTE]
@@ -46,7 +45,7 @@ In this example you will setup a device named `rapberrypi` as a `Switch` in our
![Network page](/docs/img/NETWORK_TREE/Network_Page.png)
- Notice the newly added `raspberrypi` (2) tab which now represents a network node, also showing up in the tree (3).
- As we asssigned the `raspberrypi` in the previous 1) Device details page section to the `Internet` parent network node in step (6), the link is also showing up in the tree diagram (4)
- As we asssigned the `raspberrypi` in the previous (1) Device details page section to the `Internet` parent network node in step (6), the link is also showing up in the tree diagram (4)
- We can now assign the device `(AppleTV)` (5) to this `raspberrypi` node, representing a network Switch in this example
### 3. Network page with 2 levels

View File

@@ -16,6 +16,8 @@ WIFI's**, in this way, Pi.Alert will be able to identify the device, and it
will not identify it as a new device every so often (every time IOS or Android
decides to change the MAC).
**Random MACs** are recognized by the characters "2", "6", "A", or "E" as the 2nd character in the Mac address. You can disable specific prefixes to be detected as random MAC addresses by specifying the `UI_NOT_RANDOM_MAC` setting.
## IOS
![ios][ios]

View File

@@ -1,29 +1,47 @@
## Documentation overview
In the app hover over settings or fields/labels or click blue in-app ❔ (question-mark) icons to get to relevant documentation pages.
<details>
<summary>:information_source: In the app hover over settings or fields/labels or click blue in-app ❔ (question-mark) icons to get to relevant documentation pages.</summary>
![In-app help](/docs/img/GENERAL/in-app-help.png)
![In-app help](/docs/img/GENERAL/in-app-help.png)
</details>
There is also an in-app Help / FAQ section that should be answering frequently asked questions.
### 📥 Installation
⚠ Only tested as a [docker container - follow these instructions here](https://github.com/jokob-sk/Pi.Alert/blob/main/dockerfiles/README.md).
> Check out [leiweibau's fork](https://github.com/leiweibau/Pi.Alert/) if you want to install Pi.Alert on the server directly or original instructions for [pucherot's original code](https://github.com/pucherot/Pi.Alert/)
#### 🐳 Docker (Fully supported)
- The main installation method is as a [docker container - follow these instructions here](https://github.com/jokob-sk/Pi.Alert/blob/main/dockerfiles/README.md).
#### 💻 Bare-metal / On-server (Experimental/community supported 🧪)
- [(Experimental 🧪) On-hardware instructions](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/HW_INSTALL.md)
- Alternative bare-metal install forks:
- [leiweibau's fork](https://github.com/leiweibau/Pi.Alert/) (maintained)
- [pucherot's original code](https://github.com/pucherot/Pi.Alert/) (un-maintained)
### 📚 Table of contents
#### 📥 Initial Setup
- [Subnets and VLANs configuration for arp-scan](/docs/SUBNETS.md)
- [SMTP server config](/docs/SMTP.md)
- [Custom Icon configuration and support](/docs/ICONS.md)
- [Better name resolution with Reverse DNS](/docs/REVERSE_DNS.md)
- [Network treemap configuration](/docs/NETWORK_TREE.md)
#### 🐛 Debugging help & tips
- [Debugging tips](/docs/DEBUG_TIPS.md)
- [Debugging UI not showing](/docs/WEB_UI_PORT_DEBUG.md)
- [Invalid JSON errors debug help](/docs/DEBUG_INVALID_JSON.md)
- [Troubleshooting Plugins](/docs/DEBUG_PLUGINS.md)
#### 🔝 Popular/Suggested
- [Network treemap configuration](/docs/NETWORK_TREE.md)
- [SMTP server config](/docs/SMTP.md)
- [Subnets and VLANs configuration for arp-scan](/docs/SUBNETS.md)
- [Home Assistant](/docs/HOME_ASSISTANT.md)
- [Bulk edit devices](/docs/DEVICES_BULK_EDITING.md)
@@ -31,7 +49,7 @@ There is also an in-app Help / FAQ section that should be answering frequently a
- [Manage devices (legacy docs)](/docs/DEVICE_MANAGEMENT.md)
- [Random MAC/MAC icon meaning (legacy docs)](/docs/RANDOM_MAC.md)
- [Custom Icons configuration and support](/docs/ICONS.md)
#### 🔎 Examples
@@ -96,7 +114,7 @@ Suggested test cases:
- Blank setup with no DB or config
- Existing DB / config
- Sending a notification (e. g. Delete a device and wait for a scan to run) and testing all notification gateways, especially:
- Email, Apprise (e.g. via Telegram), webhook (e.g. via Discord), MQTT (e.g. via HomeAssitant)
- Email, Apprise (e.g. via Telegram), webhook (e.g. via Discord), MQTT (e.g. via Home Assistant)
- Saving settings
- Test a couple of plugins
- Check the Error log for anything unusual
@@ -110,7 +128,7 @@ Some additional context:
Before submitting a new issue please spend a couple of minutes on research:
* Check [🛑 Common issues](https://github.com/jokob-sk/Pi.Alert/tree/main/dockerfiles#-common-issues)
* Check [🛑 Common issues](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/DEBUG_TIPS.md#common-issues)
* Check [💡 Closed issues](https://github.com/jokob-sk/Pi.Alert/issues?q=is%3Aissue+is%3Aclosed) if a similar issue was solved in the past.
* When submitting an issue ❗[enable debug](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/DEBUG_TIPS.md)❗

66
docs/REVERSE_DNS.md Executable file
View File

@@ -0,0 +1,66 @@
## Setting up better name discovery with Reverse DNS
If you are running a DNS server, such as **AdGuard**, set up **Private reverse DNS servers** for a better name resolution on your network. Enabling this setting will enable PiAlert to execute dig and nslookup commands to automatically resolve device names based on their IP addresses.
> Example 1: Reverse DNS `disabled`
>
> ```
> jokob@Synology-NAS:/$ nslookup 192.168.1.58
> ** server can't find 58.1.168.192.in-addr.arpa: NXDOMAIN
>
> ```
> Example 2: Reverse DNS `enabled`
>
> ```
> jokob@Synology-NAS:/$ nslookup 192.168.1.58
> 45.1.168.192.in-addr.arpa name = jokob-NUC.localdomain.
> ```
### Enabling reverse DNS in AdGuard
1. Navigate to **Settings** -> **DNS Settings**
2. Locate **Private reverse DNS servers**
3. Enter your router IP address, such as `192.168.1.1`
4. Make sure you have **Use private reverse DNS resolvers** ticked.
5. Click **Apply** to save your settings.
### Using a custom resolv.conf file
You can configure a custom **/etc/resolv.conf** file in **docker-compose.yml** and set the nameserver to your LAN DNS server (e.g.: Pi-Hole). See the relevant [resolv.conf man](https://www.man7.org/linux/man-pages/man5/resolv.conf.5.html) entry for details.
#### docker-compose.yml:
```yaml
version: "3"
services:
pialert:
container_name: pialert
image: "jokobsk/pi.alert:latest"
restart: unless-stopped
volumes:
- ./config/pialert.conf:/home/pi/pialert/config/pialert.conf
- ./pialert_db:/home/pi/pialert/db
- ./log:/home/pi/pialert/front/log
- ./config/resolv.conf:/etc/resolv.conf # Mapping the /resolv.conf file for better name resolution
environment:
- TZ=Europe/Berlin
- PORT=20211
- HOST_USER_ID=1000
- HOST_USER_GID=1000
ports:
- "20211:20211"
network_mode: host
```
#### ./config/resolv.conf:
The most important below is the `nameserver` entry (you can add multiple):
```
nameserver 192.168.178.11
options edns0 trust-ad
search example.com
```

View File

@@ -25,14 +25,14 @@ The json file is also cached on the client-side local storage of the browser.
> [!NOTE]
> This is the source of truth for settings. User-defined values in this files always override default values specified in the Plugin definition.
The App generates two `pialert.conf` entries for every setting (Since version 23.8+). One entry is the setting value, the second is the `__metadata` associated with the setting. This `__metadata` entry contains the full setting definition in JSON format. This should helps the future extensibility of the Settings system.
The App generates two `pialert.conf` entries for every setting (Since version 23.8+). One entry is the setting value, the second is the `__metadata` associated with the setting. This `__metadata` entry contains the full setting definition in JSON format. Currently unused, but intended to be used in future to extend the Settings system.
#### Plugin settings
> [!NOTE]
> This is the preferred way adding settings going forward. I'll be likely migrating all app settings into plugin-based settings.
Plugin settings are loaded dynamically from the `config.json` of individual plugins. If a setting isn't defined in the `pialert.conf` file, it is initialized via the `default_value` property of a setting from the `config.json` file. Check the [Plugins documentation](/front/plugins/README.md), section `⚙ Setting object structure` for details on the structure of the setting.
Plugin settings are loaded dynamically from the `config.json` of individual plugins. If a setting isn't defined in the `pialert.conf` file, it is initialized via the `default_value` property of a setting from the `config.json` file. Check the [Plugins documentation](https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/README.md#-setting-object-structure), section `⚙ Setting object structure` for details on the structure of the setting.
![Screen 1][screen1]

View File

@@ -4,6 +4,10 @@ You need to specify the network interface and the network mask. You can also con
## Examples
> [!NOTE]
> Please use the UI to configure settings as that ensures that the config file is in the correct format. Edit `pialert.conf` directly only when really necessary.
> ![settings](/front/plugins/arp_scan/arp-scan-settings.png)
* Examples for one and two subnets (❗ Note the `['...', '...']` format):
* One subnet: `SCAN_SUBNETS = ['192.168.1.0/24 --interface=eth0']`
* Two subnets: `SCAN_SUBNETS = ['192.168.1.0/24 --interface=eth0', '192.168.1.0/24 --interface=eth1 -vlan=107']`
@@ -27,7 +31,7 @@ Specify the network filter (which **significantly** speeds up the scan process).
The adapter will probably be `eth0` or `eth1`. (Check `System info` > `Network Hardware` or run `iwconfig` in the container to find your interface name(s))
> Run `iwconfig` in your container to find your interface name(s) (e.g.: `eth0`, `eth1`).
> Run `ip -o link show | awk -F': ' '!/lo|vir|docker/ {print $2}'` in your container to find your interface name(s) (e.g.: `eth0`, `eth1`).
### VLANs

12
docs/WEB_UI_PORT_DEBUG.md Executable file
View File

@@ -0,0 +1,12 @@
# Debugging inaccessible UI
When opening an issue please :
1. Include a screenshot of what you see when accessing `HTTP://<your rpi IP>/20211` (or your custom port)
1. [Follow steps 1, 2, 3, 4 on this page](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/DEBUG_TIPS.md)
1. Execute the following in the container to see the processes and their ports and submit a screenshot of the result:
1. `sudo apt-get install lsof`
1. `sudo lsof -i`
![lsof ports](/docs/img/WEB_UI_PORT_DEBUG/container_port.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

95
front/appEventsCore.php Executable file
View File

@@ -0,0 +1,95 @@
<section class="content">
<div class="nav-tabs-custom app-event-content" style="margin-bottom: 0px;">
<ul id="tabs-location" class="nav nav-tabs col-sm-2">
<li class="left-nav"><a class="col-sm-12" href="#" id="" data-toggle="tab">Events</a></li>
</ul>
<div id="tabs-content-location" class="tab-content col-sm-10">
<table class="table table-striped" id="appevents-table" data-my-dbtable="AppEvents"></table>
</div>
</div>
</section>
<script>
// show loading dialog
showSpinner()
$(document).ready(function() {
// Load JSON data from the provided URL
$.getJSON('/api/table_appevents.json', function(data) {
// Process the JSON data and generate UI dynamically
processData(data)
// hide loading dialog
hideSpinner()
});
});
function processData(data) {
// Create an object to store unique ObjectType values as app event identifiers
var appEventIdentifiers = {};
// Array to accumulate data for DataTable
var allData = [];
// Iterate through the data and generate tabs and content dynamically
$.each(data.data, function(index, item) {
// Accumulate data for DataTable
allData.push(item);
});
// Initialize DataTable for all app events
$('#appevents-table').DataTable({
data: allData,
paging: true,
lengthChange: true,
lengthMenu: [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, 'All']],
searching: true,
ordering: true,
info: true,
autoWidth: false,
pageLength: 25, // Set the default paging to 25
columns: [
{ data: 'DateTimeCreated', title: getString('AppEvents_DateTimeCreated') },
{ data: 'AppEventType', title: getString('AppEvents_Type') },
{ data: 'ObjectType', title: getString('AppEvents_ObjectType') },
{ data: 'ObjectPrimaryID', title: getString('AppEvents_ObjectPrimaryID') },
{ data: 'ObjectSecondaryID', title: getString('AppEvents_ObjectSecondaryID') },
{ data: 'ObjectStatus', title: getString('AppEvents_ObjectStatus') },
{ data: 'Extra', title: getString('AppEvents_Extra') },
{ data: 'ObjectPlugin', title: getString('AppEvents_Plugin') },
// Add other columns as needed
],
// Add column-specific configurations if needed
columnDefs: [
{ className: 'text-center', targets: [3] },
{ width: '80px', targets: [6] },
// ... Add other columnDefs as needed
// Full MAC
{targets: [3, 4],
'createdCell': function (td, cellData, rowData, row, col) {
if (!emptyArr.includes(cellData)){
$(td).html (createDeviceLink(cellData));
} else {
$(td).html ('');
}
} },
]
});
// Activate the first tab
$('#tabs-location li:first-child').addClass('active');
$('#tabs-content-location .tab-pane:first-child').addClass('active');
}
</script>
<!-- Datatable -->
<link rel="stylesheet" href="lib/AdminLTE/bower_components/datatables.net-bs/css/dataTables.bootstrap.min.css"/>
<script src="lib/AdminLTE/bower_components/datatables.net/js/jquery.dataTables.min.js"></script>
<script src="lib/AdminLTE/bower_components/datatables.net-bs/js/dataTables.bootstrap.min.js"></script>

View File

@@ -723,3 +723,6 @@ input[type="password"]::-webkit-caps-lock-indicator {
top: 0.01em;
font-size: 3.25em;
}
.pa_semitransparent-panel{
background-color: #000 !important;
}

View File

@@ -797,12 +797,8 @@ input[readonly] {
min-width: 18px;
}
.drp-edit
{
cursor: pointer;
}
.new-version
.info-icon-nav
{
top: -6px;
position: absolute;
@@ -813,7 +809,7 @@ input[readonly] {
.pointer
{
cursor:pointer;
cursor: pointer;
}
.drag

View File

@@ -412,10 +412,10 @@
</div>
<!-- New Device -->
<div class="form-group">
<div class="form-group" title="<?= lang('DevDetail_EveandAl_NewDevice_Tooltip');?>">
<label class="col-sm-5 control-label"><?= lang('DevDetail_EveandAl_NewDevice');?>:</label>
<div class="col-sm-7" style="padding-top:6px;">
<input class="checkbox orange hidden" id="chkNewDevice" type="checkbox">
<input class="checkbox orange hidden" id="chkNewDevice" type="checkbox">
</div>
</div>
@@ -842,6 +842,8 @@ function initializeCombo (dropdownId, queryAction, txtDataField, useCache) {
{
// get data from server
$.get('php/server/devices.php?action='+queryAction, function(data) {
console.log(data)
var listData = JSON.parse(data);
var order = 1;
@@ -1475,7 +1477,7 @@ function updateApi()
{
// value has to be in format event|param. e.g. run|ARPSCAN
action = `update_api|devices`
action = `update_api|devices,appevents`
$.ajax({
method: "POST",

View File

@@ -42,9 +42,9 @@
<!-- top small box 1 ------------------------------------------------------- -->
<div class="row">
<div class="col-lg-2 col-sm-4 col-xs-6">
<a href="#" onclick="javascript: initializeDatatable('all');">
<a href="#" onclick="javascript: initializeDatatable('my');">
<div class="small-box bg-aqua">
<div class="inner"><h3 id="devicesAll"> -- </h3>
<div class="inner"><h3 id="devicesMy"> -- </h3>
<p class="infobox_label"><?= lang('Device_Shortcut_AllDevices');?></p>
</div>
<div class="icon"><i class="fa fa-laptop text-aqua-40"></i></div>
@@ -203,25 +203,27 @@
var tableColumnOrder = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18];
var tableColumnVisible = tableColumnOrder;
//initialize the table headers in the correct order
var headersDefaultOrder = [ getString('Device_TableHead_Name'),
getString('Device_TableHead_Owner'),
getString('Device_TableHead_Type'),
getString('Device_TableHead_Icon'),
getString('Device_TableHead_Favorite'),
getString('Device_TableHead_Group'),
getString('Device_TableHead_FirstSession'),
getString('Device_TableHead_LastSession'),
getString('Device_TableHead_LastIP'),
getString('Device_TableHead_MAC'),
getString('Device_TableHead_Status'),
getString('Device_TableHead_MAC_full'),
getString('Device_TableHead_LastIPOrder'),
getString('Device_TableHead_Rowid'),
getString('Device_TableHead_Parent_MAC'),
getString('Device_TableHead_Connected_Devices'),
getString('Device_TableHead_Location'),
getString('Device_TableHead_Vendor')
];
var headersDefaultOrder = [
getString('Device_TableHead_Name'),
getString('Device_TableHead_Owner'),
getString('Device_TableHead_Type'),
getString('Device_TableHead_Icon'),
getString('Device_TableHead_Favorite'),
getString('Device_TableHead_Group'),
getString('Device_TableHead_FirstSession'),
getString('Device_TableHead_LastSession'),
getString('Device_TableHead_LastIP'),
getString('Device_TableHead_MAC'),
getString('Device_TableHead_Status'),
getString('Device_TableHead_MAC_full'),
getString('Device_TableHead_LastIPOrder'),
getString('Device_TableHead_Rowid'),
getString('Device_TableHead_Parent_MAC'),
getString('Device_TableHead_Connected_Devices'),
getString('Device_TableHead_Location'),
getString('Device_TableHead_Vendor'),
getString('Device_TableHead_Port')
];
// Read parameters & Initialize components
main();
@@ -239,6 +241,8 @@ function main () {
// get visible columns
$.get('php/server/parameters.php?action=get&expireMinutes=525600&defaultValue='+defaultValue+'&parameter=Front_Devices_Columns_Visible&skipcache', function(data) {
handle_locked_DB(data)
// save which columns are in the Devices page visible
tableColumnVisible = numberArrayFromString(data);
@@ -250,6 +254,8 @@ function main () {
// get the custom order specified by the user
$.get('php/server/parameters.php?action=get&expireMinutes=525600&defaultValue='+defaultValue+'&parameter=Front_Devices_Columns_Order&skipcache', function(data) {
handle_locked_DB(data)
// save the columns order in the Devices page
tableColumnOrder = numberArrayFromString(data);
@@ -285,10 +291,9 @@ function main () {
}
// Initialize components with parameters
initializeDatatable();
initializeDatatable('my');
// query data
getDevicesTotals();
// check if dat outdated and show spinner if so
handleLoadingDialog()
@@ -313,13 +318,62 @@ function mapIndx(oldIndex)
}
}
//------------------------------------------------------------------------------
// Query total numbers of Devices by status
//------------------------------------------------------------------------------
function getDevicesTotals(devicesData) {
let resultJSON = "";
if (getCache("getDevicesTotals") !== "") {
resultJSON = getCache("getDevicesTotals");
} else {
// combined query
const devices = filterDataByStatus(devicesData, 'my');
const connectedDevices = filterDataByStatus(devicesData, 'connected');
const favoritesDevices = filterDataByStatus(devicesData, 'favorites');
const newDevices = filterDataByStatus(devicesData, 'new');
const downDevices = filterDataByStatus(devicesData, 'down');
const archivedDevices = filterDataByStatus(devicesData, 'archived');
$('#devicesMy').html (devices.length);
$('#devicesConnected').html (connectedDevices.length);
$('#devicesFavorites').html (favoritesDevices.length);
$('#devicesNew').html (newDevices.length);
$('#devicesDown').html (downDevices.length);
$('#devicesArchived').html (archivedDevices.length);
// save to cache
setCache("getDevicesTotals", resultJSON);
}
console.log(resultJSON);
}
// -----------------------------------------------------------------------------
// Define a function to filter data based on deviceStatus
function filterDataByStatus(data, status) {
return data.filter(function(item) {
switch (status) {
case 'all':
return true; // Include all items for 'all' status
case 'my':
to_display = getSetting('UI_MY_DEVICES');
let result = false;
if (to_display.includes('online') && item.dev_PresentLastScan === 1) {
result = true;
} else if (to_display.includes('offline') && item.dev_PresentLastScan === 0) {
result = true;
} else if (to_display.includes('archived') && item.dev_Archived === 1) {
result = true;
} else if (to_display.includes('new') && item.dev_NewDevice === 1) {
result = true;
} else if (to_display.includes('down') && item.dev_PresentLastScan === 0 && item.dev_AlertDeviceDown !== 0) {
result = true;
}
return result; // Include all items for 'my' status
case 'connected':
return item.dev_PresentLastScan === 1;
case 'favorites':
@@ -327,7 +381,7 @@ function filterDataByStatus(data, status) {
case 'new':
return item.dev_NewDevice === 1;
case 'down':
return item.dev_PresentLastScan === 0 && item.dev_AlertDeviceDown === 1;
return item.dev_PresentLastScan === 0 && item.dev_AlertDeviceDown !== 0;
case 'archived':
return item.dev_Archived === 1;
default:
@@ -339,28 +393,29 @@ function filterDataByStatus(data, status) {
// -----------------------------------------------------------------------------
function getDeviceStatus(item)
{
if(item.dev_PresentLastScan === 1)
{
return 'On-line';
}
else if(item.dev_PresentLastScan === 0 && item.dev_AlertDeviceDown === 1)
{
return 'Down';
}
else if(item.dev_NewDevice === 1)
{
return 'New';
}
else if(item.dev_Archived === 1)
{
return 'Archived';
}
else if(item.dev_PresentLastScan === 0)
{
return 'Off-line';
}
if(item.dev_NewDevice === 1)
{
return 'New';
}
else if(item.dev_PresentLastScan === 1)
{
return 'On-line';
}
else if(item.dev_PresentLastScan === 0 && item.dev_AlertDeviceDown !== 0)
{
return 'Down';
}
else if(item.dev_Archived === 1)
{
return 'Archived';
}
else if(item.dev_PresentLastScan === 0)
{
return 'Off-line';
}
return "Unknown status"
return "Unknown status"
}
// -----------------------------------------------------------------------------
@@ -371,7 +426,7 @@ function initializeDatatable (status) {
// Define color & title for the status selected
switch (deviceStatus) {
case 'all': tableTitle = getString('Device_Shortcut_AllDevices'); color = 'aqua'; break;
case 'my': tableTitle = getString('Device_Shortcut_AllDevices'); color = 'aqua'; break;
case 'connected': tableTitle = getString('Device_Shortcut_Connected'); color = 'green'; break;
case 'favorites': tableTitle = getString('Device_Shortcut_Favorites'); color = 'yellow'; break;
case 'new': tableTitle = getString('Device_Shortcut_NewDevices'); color = 'yellow'; break;
@@ -395,7 +450,10 @@ function initializeDatatable (status) {
}
}
$.get('api/table_devices.json?nocache=' + Date.now(), function(result) {
$.get('api/table_devices.json?nocache=' + Date.now(), function(result) {
// query data
getDevicesTotals(result.data);
// Filter the data based on deviceStatus
var filteredData = filterDataByStatus(result.data, deviceStatus);
@@ -414,13 +472,13 @@ function initializeDatatable (status) {
item.dev_FirstConnection || "",
item.dev_LastConnection || "",
item.dev_LastIP || "",
(["2", "6", "A", "E", "a", "e"].includes(item.dev_MAC[1]) ? 1 : 0) || "", // Check if randomized MAC
(isRandomMAC(item.dev_MAC)) || "", // Check if randomized MAC
getDeviceStatus(item) || "",
item.dev_MAC || "", // hidden
formatIPlong(item.dev_LastIP) || "", // IP orderable
item.rowid || "",
item.dev_Network_Node_MAC_ADDR || "",
item.connected_devices || 0,
getNumberOfChildren(item.dev_MAC, result.data) || 0,
item.dev_Location || "",
item.dev_Vendor || "",
item.dev_Network_Node_port || 0
@@ -466,9 +524,9 @@ function initializeDatatable (status) {
'columnDefs' : [
{visible: false, targets: tableColumnHide },
{className: 'text-center', targets: [mapIndx(3), mapIndx(4), mapIndx(9), mapIndx(10), mapIndx(15)] },
{className: 'text-center', targets: [mapIndx(3), mapIndx(4), mapIndx(9), mapIndx(10), mapIndx(15), mapIndx(18)] },
{width: '80px', targets: [mapIndx(6), mapIndx(7), mapIndx(15)] },
{width: '30px', targets: [mapIndx(10), mapIndx(13)] },
{width: '30px', targets: [mapIndx(10), mapIndx(13), mapIndx(18)] },
{orderData: [mapIndx(12)], targets: mapIndx(8) },
// Device Name
@@ -481,8 +539,17 @@ function initializeDatatable (status) {
// Connected Devices
{targets: [mapIndx(15)],
'createdCell': function (td, cellData, rowData, row, col) {
'createdCell': function (td, cellData, rowData, row, col) {
// check if this is a network device
if(getSetting("NETWORK_DEVICE_TYPES").includes(`'${rowData[mapIndx(2)]}'`) )
{
$(td).html ('<b><a href="./network.php?mac='+ rowData[mapIndx(11)] +'" class="">'+ cellData +'</a></b>');
}
else
{
$(td).html (`<i class="fa-solid fa-xmark" title="${getString("Device_Table_Not_Network_Device")}"></i>`)
}
} },
// Icon
@@ -534,7 +601,7 @@ function initializeDatatable (status) {
// Random MAC
{targets: [mapIndx(9)],
'createdCell': function (td, cellData, rowData, row, col) {
console.log(cellData)
// console.log(cellData)
if (cellData == 1){
$(td).html ('<i data-toggle="tooltip" data-placement="right" title="Random MAC" style="font-size: 16px;" class="text-yellow glyphicon glyphicon-random"></i>');
} else {
@@ -622,25 +689,22 @@ function getDevicesFromTable(table)
return JSON.stringify (result)
}
// -----------------------------------------------------------------------------
function getDevicesTotals () {
// stop timer
stopTimerRefreshData();
function getNumberOfChildren(mac, devices)
{
childrenCount = 0;
// get totals and put in boxes
$.get('php/server/devices.php?action=getDevicesTotals', function(data) {
var totalsDevices = JSON.parse(data);
$.each(devices, function(index, dev) {
$('#devicesAll').html (totalsDevices[0].toLocaleString());
$('#devicesConnected').html (totalsDevices[1].toLocaleString());
$('#devicesFavorites').html (totalsDevices[2].toLocaleString());
$('#devicesNew').html (totalsDevices[3].toLocaleString());
$('#devicesDown').html (totalsDevices[4].toLocaleString());
$('#devicesArchived').html (totalsDevices[5].toLocaleString());
if(dev.dev_Network_Node_MAC_ADDR.trim() == mac.trim())
{
childrenCount++;
}
});
// Timer for refresh data
newTimerRefreshData (getDevicesTotals);
} );
return childrenCount;
}
// -----------------------------------------------------------------------------

View File

@@ -2,6 +2,7 @@ function pia_draw_graph_online_history(pia_js_graph_online_history_time, pia_js_
var xValues = pia_js_graph_online_history_time;
new Chart("OnlineChart", {
type: "bar",
scaleIntegersOnly: true,
data: {
labels: xValues,
datasets: [{
@@ -60,4 +61,4 @@ function pia_draw_graph_online_history(pia_js_graph_online_history_time, pia_js_
}
}
});
}
}

View File

@@ -143,24 +143,19 @@ function cacheStrings()
{
// handle core strings and translations
var allLanguages = ["en_us","es_es","de_de"]; // needs to be same as in lang.php
var allLanguages = ["en_us", "es_es", "de_de"]; // needs to be same as in lang.php
allLanguages.forEach(function (language_code) {
$.get(`php/templates/language/${language_code}.json`, function(res) {
Object.entries(res).forEach(([language, translations]) => {
Object.entries(translations).forEach(([key, value]) => {
// store as key - value pairs in session
setCache(`pia_lang_${key}_${language}`, value)
});
$.get(`php/templates/language/${language_code}.json`, function (res) {
// Iterate over each language
Object.entries(res).forEach(([key, value]) => {
// Store translations for each key-value pair
setCache(`pia_lang_${key}_${language_code}`, value)
});
})
});
});
// handle strings and translations from plugins
$.get('api/table_plugins_language_strings.json', function(res) {
@@ -206,7 +201,10 @@ function getString (key) {
// -----------------------------------------------------------------------------
// Modal dialog handling
// -----------------------------------------------------------------------------
function showModalOk (title, message, callbackFunction = null) {
function showModalOK (title, message, callbackFunction) {
showModalOk (title, message, callbackFunction)
}
function showModalOk (title, message, callbackFunction) {
// set captions
$('#modal-ok-title').html (title);
$('#modal-ok-message').html (message);
@@ -222,6 +220,8 @@ function showModalOk (title, message, callbackFunction = null) {
// Show modal
$('#modal-ok').modal('show');
}
// -----------------------------------------------------------------------------
function showModalDefault (title, message, btnCancel, btnOK, callbackFunction) {
// set captions
$('#modal-default-title').html (title);
@@ -253,13 +253,17 @@ function showModalDefaultStrParam (title, message, btnCancel, btnOK, callbackFun
}
// -----------------------------------------------------------------------------
function showModalWarning (title, message, btnCancel, btnOK, callbackFunction) {
function showModalWarning (title, message, btnCancel=getString('Gen_Cancel'), btnOK=getString('Gen_Okay'), callbackFunction=null) {
// set captions
$('#modal-warning-title').html (title);
$('#modal-warning-message').html (message);
$('#modal-warning-cancel').html (btnCancel);
$('#modal-warning-OK').html (btnOK);
modalCallbackFunction = callbackFunction;
if ( callbackFunction != null)
{
modalCallbackFunction = callbackFunction;
}
// Show modal
$('#modal-warning').modal('show');
@@ -346,9 +350,27 @@ function sanitize(data)
return data.replace(/(\r\n|\n|\r)/gm,"").replace(/[^\x00-\x7F]/g, "")
}
// -----------------------------------------------------------------------------
// Check and handle locked database
function handle_locked_DB(data)
{
if(data.includes('database is locked'))
{
console.log(data)
showSpinner()
setTimeout(function() {
location.reload();
}, 5000);
}
}
// -----------------------------------------------------------------------------
function numberArrayFromString(data)
{
data = JSON.parse(sanitize(data));
return data.replace(/\[|\]/g, '').split(',').map(Number);
}
@@ -400,6 +422,19 @@ function saveData(functionName, id, value) {
}
// -----------------------------------------------------------------------------
// create a link to the device
function createDeviceLink(input)
{
if(checkMacOrInternet(input))
{
return `<span class="anonymizeMac"><a href="/deviceDetails.php?mac=${input}" target="_blank">${getNameByMacAddress(input)}</a><span>`
}
return input;
}
// -----------------------------------------------------------------------------
// remove an item from an array
function removeItemFromArray(arr, value) {
@@ -513,18 +548,118 @@ function getNameByMacAddress(macAddress) {
}
// -----------------------------------------------------------------------------
// A function used to make the IP address orderable
function formatIPlong(ipAddress) {
const parts = ipAddress.split('.');
if (parts.length !== 4) {
throw new Error('Invalid IP address format');
// Check if MAC or Internet
function checkMacOrInternet(inputStr) {
// Regular expression pattern for matching a MAC address
const macPattern = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
if (inputStr.toLowerCase() === 'internet') {
return true;
} else if (macPattern.test(inputStr)) {
return true;
} else {
return false;
}
return (parseInt(parts[0]) << 24) |
(parseInt(parts[1]) << 16) |
(parseInt(parts[2]) << 8) |
parseInt(parts[3]);
}
// -----------------------------------------------------------------------------
// A function used to make the IP address orderable
function isValidIPv6(ipAddress) {
// Regular expression for IPv6 validation
const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}$|^([0-9a-fA-F]{1,4}:){1,7}:|^([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}$|^([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}$|^([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}$|^([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}$|^([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}$|^[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})$/;
return ipv6Regex.test(ipAddress);
}
function formatIPlong(ipAddress) {
if (ipAddress.includes(':') && isValidIPv6(ipAddress)) {
const parts = ipAddress.split(':');
return parts.reduce((acc, part, index) => {
if (part === '') {
const remainingGroups = 8 - parts.length + 1;
return acc << (16 * remainingGroups);
}
const hexValue = parseInt(part, 16);
return acc | (hexValue << (112 - index * 16));
}, 0);
} else {
// Handle IPv4 address
const parts = ipAddress.split('.');
if (parts.length !== 4) {
console.log("⚠ Invalid IPv4 address: " + ipAddress);
return -1; // or any other default value indicating an error
}
return (parseInt(parts[0]) << 24) |
(parseInt(parts[1]) << 16) |
(parseInt(parts[2]) << 8) |
parseInt(parts[3]);
}
}
// -----------------------------------------------------------------------------
// Check if MAC is a random one
function isRandomMAC(mac)
{
isRandom = false;
isRandom = ["2", "6", "A", "E", "a", "e"].includes(mac[1]);
// if detected as random, make sure it doesn't start with a prefix which teh suer doesn't want to mark as random
if(isRandom)
{
$.each(createArray(getSetting("UI_NOT_RANDOM_MAC")), function(index, prefix) {
if(mac.startsWith(prefix))
{
isRandom = false;
}
});
}
return isRandom;
}
// ---------------------------------------------------------
// Generate an array object from a string representation of an array
function createArray(input) {
// Empty array
if (input === '[]') {
return [];
}
// Regex patterns
const patternBrackets = /(^\s*\[)|(\]\s*$)/g;
const patternQuotes = /(^\s*')|('\s*$)/g;
const replacement = '';
// Remove brackets
const noBrackets = input.replace(patternBrackets, replacement);
const options = [];
// Create array
const optionsTmp = noBrackets.split(',');
// Handle only one item in array
if (optionsTmp.length === 0) {
return [noBrackets.replace(patternQuotes, replacement)];
}
// Remove quotes
optionsTmp.forEach(item => {
options.push(item.replace(patternQuotes, replacement).trim());
});
return options;
}
// -----------------------------------------------------------------------------
// A function to get a device property using the mac address as key and DB column nakme as parameter
// for the value to be returned
@@ -583,6 +718,23 @@ function getGuid() {
);
}
// -----------------------------------------------------------------------------
// UI
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// Genrate work-in-progress icons
function workInProgress() {
console.log()
if($(".work-in-progress").html().trim() == "")
{
$(".work-in-progress").append(`
<a href="https://github.com/jokob-sk/Pi.Alert/issues" target="_blank">
<b class="pointer" title="${getString("Gen_Work_In_Progress")}">🦺</b>
</a>
`)
}
}
// -----------------------------------------------------------------------------
// Loading Spinner overlay
// -----------------------------------------------------------------------------
@@ -622,6 +774,7 @@ function hideSpinner()
cacheSettings()
cacheStrings()
initDeviceListAll_JSON()
workInProgress()
console.log("init pialert_common.js")

View File

@@ -19,6 +19,25 @@
return result;
}
// -------------------------------------------------------------------
// Get plugin type base on prefix
function getPluginCodeName(pluginsData, prefix)
{
var result = ""
pluginsData.forEach((plug) => {
if (plug.unique_prefix == prefix ) {
id = plug.code_name;
// console.log(id)
result = plug.code_name;
}
});
return result;
}
// -------------------------------------------------------------------
// Get plugin type base on prefix
@@ -61,19 +80,22 @@
});
html += `
html += `
<div class="col-sm-4 ">
<div class="small-box bg-green " >
<div class="inner ">
<a href="#${prefix}_header" onclick="toggleAllSettings('open')">
<h5 class="card-title">
${getString(prefix+"_display_name")}
<b>${getString(prefix+"_display_name")}</b>
</h5>
${includeSettings_html}
</a>
${includeSettings_html}
</div>
<div class="icon"> ${getString(prefix+"_icon")} </div>
<a href="#${prefix}_header" onclick="toggleAllSettings('open')">
<div class="icon"> ${getString(prefix+"_icon")} </div>
</a>
</div>
</div>
`
});
@@ -81,6 +103,41 @@
return html;
}
// -----------------------------------------------------------------------------
// Open or close all settings
// -----------------------------------------------------------------------------
function toggleAllSettings(openOrClose = '')
{
inStr = ' in';
allOpen = true;
openIcon = 'fa-angle-double-down';
closeIcon = 'fa-angle-double-up';
$('.panel-collapse').each(function(){
if($(this).attr('class').indexOf(inStr) == -1)
{
allOpen = false;
}
})
if(allOpen == false || openOrClose == 'open')
{
// open all
$('div[data-myid="collapsible"]').each(function(){$(this).attr('class', 'panel-collapse collapse in')})
$('div[data-myid="collapsible"]').each(function(){$(this).attr('style', 'height:inherit')})
$('#toggleSettings').attr('class', $('#toggleSettings').attr('class').replace(openIcon, closeIcon))
}
else{
// close all
$('div[data-myid="collapsible"]').each(function(){$(this).attr('class', 'panel-collapse collapse ')})
$('#toggleSettings').attr('class', $('#toggleSettings').attr('class').replace(closeIcon, openIcon))
}
}
// -------------------------------------------------------------------
// Checks if all schedules are the same
function schedulesAreSynchronized(prefixesOfEnabledPlugins, pluginsData)

View File

@@ -816,6 +816,8 @@ function saveSelectedColumns () {
function initializeSelectedColumns () {
$.get('php/server/parameters.php?action=get&expireMinutes=525600&defaultValue='+colDefaultOrderTxt+'&parameter=Front_Devices_Columns_Visible', function(data) {
handle_locked_DB(data)
tableColumnShow = numberArrayFromString(data);
for(i=0; i < tableColumnShow.length; i++)
@@ -834,24 +836,30 @@ function initializeSelectedColumns () {
// --------------------------------------------------------
//Initialize Select2 Elements and make them sortable
$(function () {
selectEl = $('.select2').select2();
$(function () {
var selectEl = $('.select2').select2();
selectEl.next().children().children().children().sortable({
containment: 'parent', stop: function (event, ui) {
ui.item.parent().children('[title]').each(function () {
var title = $(this).attr('title');
var original = $( 'option:contains(' + title + ')', selectEl ).first();
original.detach();
selectEl.append(original)
});
selectEl.change();
}
containment: 'parent',
update: function () {
var sortedValues = $(this).children().map(function() {
return $(this).attr('title');
}).get();
var sortedOptions = selectEl.find('option').sort(function(a, b) {
return sortedValues.indexOf($(a).text()) - sortedValues.indexOf($(b).text());
});
// Replace all options in selectEl
selectEl.empty().append(sortedOptions);
// Trigger change event on Select2
selectEl.trigger('change');
}
});
});
// --------------------------------------------------------
// General initialization
// --------------------------------------------------------

View File

@@ -124,7 +124,10 @@
</td>
<td>
<a href="./network.php?mac='.$idParentMac.'">
<b class="anonymize">'.$idParentMac.' <i class="fa fa-square-up-right"></i></b>
<b class="anonymize">
<span class="mac-to-name" my-data-mac="'.$node_parent_mac.'">'.$node_parent_mac.' </span>
<i class="fa fa-square-up-right"></i>
</b>
</a>
</td>
</tr>
@@ -456,32 +459,39 @@
<script>
$.get('php/server/devices.php?action=getDevicesList&status=all&forceDefaultOrder', function(data) {
rawData = JSON.parse (data)
devicesListnew = rawData["data"].map(item => { return {
"name":item[0],
"type":item[2],
"icon":item[3],
"mac":item[11],
"parentMac":item[14],
"rowid":item[13],
"status":item[10],
"childrenQty":item[15],
"port":item[18]
}})
setCache('devicesListNew', JSON.stringify(devicesListnew))
// init global variable
deviceListGlobal = devicesListnew;
rawData = JSON.parse (data)
if(rawData["data"] == "")
{
showModalOK (getString('Gen_Warning'), getString('Network_NoDevices'))
// create tree
initTree(getHierarchy());
return;
}
// attach on-click events
attachTreeEvents();
});
devicesListnew = rawData["data"].map(item => { return {
"name":item[0],
"type":item[2],
"icon":item[3],
"mac":item[11],
"parentMac":item[14],
"rowid":item[13],
"status":item[10],
"childrenQty":item[15],
"port":item[18]
}})
setCache('devicesListNew', JSON.stringify(devicesListnew))
// init global variable
deviceListGlobal = devicesListnew;
// create tree
initTree(getHierarchy());
// attach on-click events
attachTreeEvents();
});
</script>
@@ -612,13 +622,22 @@
// ---------------------------------------------------------------------------
var myTree;
var treeAreaHeight = 800;
var visibleTreeArea = $(window).height()-135;
var treeAreaHeight = visibleTreeArea > 800 ? 800 : visibleTreeArea;
var emSize;
var nodeHeight;
var sizeCoefficient = 1
function initTree(myHierarchy)
{
if(myHierarchy.type == "")
{
showModalOk(getString('Network_Configuration_Error'), getString('Network_Root_Not_Configured'))
return;
}
// calculate the font size of the leaf nodes to fit everything into the tree area
leafNodesCount == 0 ? 1 : leafNodesCount;
emSize = ((treeAreaHeight/(25*leafNodesCount)).toFixed(2));
@@ -752,6 +771,25 @@
}
// ---------------------------------------------------------------------------
function initDeviceNamesFromMACs()
{
$('.mac-to-name').each(function() {
var dataMacValue = $(this).attr('my-data-mac');
if(dataMacValue =="" )
{
$(this).html(getString("Network_Root"))
}
else{
$(this).html(getNameByMacAddress(dataMacValue));
}
});
}
// ---------------------------------------------------------------------------
function initButtons()
{
@@ -775,12 +813,12 @@
// init the Assign buttons
$('#unassignedDevices button[data-myleafmac]').each(function(){
$(this).attr('onclick', 'updateLeaf("'+$(this).attr('data-myleafmac')+'","'+currentNodeMac+'")')
$(this).attr('onclick', `updateLeaf("${$(this).attr('data-myleafmac')}","${currentNodeMac}")`)
});
// init Unassign buttons
$('#assignedDevices button[data-myleafmac]').each(function(){
$(this).attr('onclick', 'updateLeaf("'+$(this).attr('data-myleafmac')+'","")')
$(this).attr('onclick', `updateLeaf("${$(this).attr('data-myleafmac')}","")`)
});
}
@@ -789,9 +827,10 @@
{
console.log(leafMac) // child
console.log(nodeMac) // parent
console.log(nodeMac != "") // parent
// prevent the assignment of the Internet root node avoiding recursion when generating the network tree topology
if(leafMac.toLowerCase().includes('internet'))
if(leafMac.toLowerCase().includes('internet') && nodeMac != "")
{
showMessage(getString('Network_Cant_Assign'))
}
@@ -802,6 +841,9 @@
}
// init device names where macs are used
initDeviceNamesFromMACs();
// init selected (first) tab
initTab();

View File

@@ -77,7 +77,7 @@ function getDeviceData() {
// Device Data
$sql = 'SELECT rowid, *,
CASE WHEN dev_AlertDeviceDown=1 AND dev_PresentLastScan=0 THEN "Down"
CASE WHEN dev_AlertDeviceDown !=0 AND dev_PresentLastScan=0 THEN "Down"
WHEN dev_PresentLastScan=1 THEN "On-line"
ELSE "Off-line" END as dev_Status
FROM Devices
@@ -92,7 +92,7 @@ function getDeviceData() {
$deviceData['dev_FirstConnection'] = formatDate ($row['dev_FirstConnection']); // Date formated
$deviceData['dev_LastConnection'] = formatDate ($row['dev_LastConnection']); // Date formated
$deviceData['dev_RandomMAC'] = ( in_array($mac[1], array("2","6","A","E","a","e")) ? 1 : 0);
$deviceData['dev_RandomMAC'] = isRandomMAC($mac);
// Count Totals
$condition = ' WHERE eve_MAC="'. $mac .'" AND eve_DateTime >= '. $periodDate;
@@ -225,7 +225,7 @@ function deleteUnknownDevices() {
global $db;
// sql
$sql = 'DELETE FROM Devices WHERE dev_Name="(unknown)"';
$sql = 'DELETE FROM Devices WHERE dev_Name="(unknown)" OR dev_Name="(name not found)"';
// execute sql
$result = $db->query($sql);
@@ -545,7 +545,7 @@ function getDevicesTotals() {
// combined query
$result = $db->query(
'SELECT
(SELECT COUNT(*) FROM Devices '. getDeviceCondition ('all').') as devices,
(SELECT COUNT(*) FROM Devices '. getDeviceCondition ('my').') as devices,
(SELECT COUNT(*) FROM Devices '. getDeviceCondition ('connected').') as connected,
(SELECT COUNT(*) FROM Devices '. getDeviceCondition ('favorites').') as favorites,
(SELECT COUNT(*) FROM Devices '. getDeviceCondition ('new').') as new,
@@ -626,7 +626,7 @@ function getDevicesList() {
$sql = 'SELECT * FROM (
SELECT rowid, *, CASE
WHEN t1.dev_AlertDeviceDown=1 AND t1.dev_PresentLastScan=0 THEN "Down"
WHEN t1.dev_AlertDeviceDown !=0 AND t1.dev_PresentLastScan=0 THEN "Down"
WHEN t1.dev_NewDevice=1 THEN "New"
WHEN t1.dev_PresentLastScan=1 THEN "On-line"
ELSE "Off-line" END AS dev_Status
@@ -657,7 +657,7 @@ function getDevicesList() {
formatDate ($row['dev_FirstConnection']),
formatDate ($row['dev_LastConnection']),
$row['dev_LastIP'],
( in_array($row['dev_MAC'][1], array("2","6","A","E","a","e")) ? 1 : 0),
( isRandomMAC($row['dev_MAC']) ),
$row['dev_Status'],
$row['dev_MAC'], // MAC (hidden)
formatIPlong ($row['dev_LastIP']), // IP orderable
@@ -690,6 +690,32 @@ function getDevicesList() {
}
//------------------------------------------------------------------------------
// Determine if Random MAC
//------------------------------------------------------------------------------
function isRandomMAC($mac) {
$isRandom = false;
// if detected as random, make sure it doesn't start with a prefix which teh suer doesn't want to mark as random
$setting = getSettingValue("UI_NOT_RANDOM_MAC");
$prefixes = createArray($setting);
$isRandom = in_array($mac[1], array("2", "6", "A", "E", "a", "e"));
// If detected as random, make sure it doesn't start with a prefix which the user doesn't want to mark as random
if ($isRandom) {
foreach ($prefixes as $prefix) {
if (strpos($mac, $prefix) === 0) {
$isRandom = false;
break;
}
}
}
return $isRandom;
}
//------------------------------------------------------------------------------
// Query the List of devices for calendar
//------------------------------------------------------------------------------
@@ -1133,14 +1159,15 @@ function copyFromDevice() {
//------------------------------------------------------------------------------
function getDeviceCondition ($deviceStatus) {
switch ($deviceStatus) {
case 'all': return 'WHERE dev_Archived=0'; break;
case 'connected': return 'WHERE dev_Archived=0 AND dev_PresentLastScan=1'; break;
case 'favorites': return 'WHERE dev_Archived=0 AND dev_Favorite=1'; break;
case 'new': return 'WHERE dev_Archived=0 AND dev_NewDevice=1'; break;
case 'down': return 'WHERE dev_Archived=0 AND dev_AlertDeviceDown=1 AND dev_PresentLastScan=0'; break;
case 'archived': return 'WHERE dev_Archived=1'; break;
default: return 'WHERE 1=0'; break;
}
case 'all': return 'WHERE dev_Archived=0'; break;
case 'my': return 'WHERE dev_Archived=0'; break;
case 'connected': return 'WHERE dev_Archived=0 AND dev_PresentLastScan=1'; break;
case 'favorites': return 'WHERE dev_Archived=0 AND dev_Favorite=1'; break;
case 'new': return 'WHERE dev_Archived=0 AND dev_NewDevice=1'; break;
case 'down': return 'WHERE dev_Archived=0 AND dev_AlertDeviceDown !=0 AND dev_PresentLastScan=0'; break;
case 'archived': return 'WHERE dev_Archived=1'; break;
default: return 'WHERE 1=0'; break;
}
}

View File

@@ -209,8 +209,7 @@ if ($ENABLED_DARKMODE === True) {
<section class="sidebar">
<!-- Sidebar user panel (optional) -->
<div class="user-panel">
<a href="." class="logo">
<div class="user-panel"> <a href="." class="logo">
<img src="img/pialertLogoGray80.png" class="img-responsive" alt="Pi.Alert Logo"/>
</a>
</div>
@@ -246,15 +245,16 @@ if ($ENABLED_DARKMODE === True) {
</li>
<li class=" <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('maintenance.php') ) ){ echo 'active'; } ?>">
<div class="new-version myhidden" id="version" data-build-time="<?php echo file_get_contents( "buildtimestamp.txt");?>">🆕</div>
<div class="info-icon-nav myhidden" id="version" data-build-time="<?php echo file_get_contents( "buildtimestamp.txt");?>">🆕</div>
<a href="maintenance.php"><i class="fa fa-wrench "></i> <span><?= lang('Navigation_Maintenance');?></span></a>
</li>
<li class=" <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('settings.php') ) ){ echo 'active'; } ?>">
<a href="settings.php"><i class="fa fa-cog"></i> <span><?= lang('Navigation_Settings');?></span></a>
</li>
<!-- <li class=" <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('flows.php') ) ){ echo 'active'; } ?>">
<a href="flows.php"><i class="fa fa-shuffle"></i> <span><?= lang('Navigation_Flows');?></span></a>
</li> -->
<li class=" <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('workflows.php') ) ){ echo 'active'; } ?>">
<div class="info-icon-nav work-in-progress"> </div>
<a href="workflows.php"><i class="fa fa-shuffle"></i> <span><?= lang('Navigation_Workflows');?></span></a>
</li>
<li class=" <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('systeminfo.php') ) ){ echo 'active'; } ?>">
<a href="systeminfo.php"><i class="fa fa-microchip"></i> <span><?= lang('Navigation_SystemInfo');?></span></a>
</li>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,638 @@
{
"API_CUSTOM_SQL_description": "",
"API_CUSTOM_SQL_name": "Point de terminaison personnalis\u00e9",
"API_display_name": "API",
"API_icon": "",
"About_Design": "Con\u00e7u pour\u202f:",
"About_Exit": "Quitter",
"About_Title": "Open Source Network Guard",
"AppEvents_DateTimeCreated": "Journalis\u00e9",
"AppEvents_Extra": "Extra",
"AppEvents_GUID": "",
"AppEvents_Helper1": "",
"AppEvents_Helper2": "",
"AppEvents_Helper3": "",
"AppEvents_ObjectForeignKey": "Cl\u00e9 \u00e9trang\u00e8re",
"AppEvents_ObjectIndex": "Index",
"AppEvents_ObjectIsArchived": "Est archiv\u00e9 (au moment de l'enregistrement)",
"AppEvents_ObjectIsNew": "",
"AppEvents_ObjectPlugin": "Greffon li\u00e9",
"AppEvents_ObjectPrimaryID": "",
"AppEvents_ObjectSecondaryID": "",
"AppEvents_ObjectStatus": "Statut (au moment de l'enregistrement)",
"AppEvents_ObjectStatusColumn": "Colonne d'\u00e9tat",
"AppEvents_ObjectType": "Type d'objet",
"AppEvents_Plugin": "Greffon",
"AppEvents_Type": "Type",
"BackDevDetail_Actions_Ask_Run": "",
"BackDevDetail_Actions_Not_Registered": "",
"BackDevDetail_Actions_Title_Run": "",
"BackDevDetail_Copy_Ask": "",
"BackDevDetail_Copy_Title": "Copier les d\u00e9tails",
"BackDevDetail_Tools_WOL_error": "",
"BackDevDetail_Tools_WOL_okay": "",
"BackDevices_Arpscan_disabled": "",
"BackDevices_Arpscan_enabled": "",
"BackDevices_Backup_CopError": "",
"BackDevices_Backup_Failed": "",
"BackDevices_Backup_okay": "",
"BackDevices_DBTools_DelDevError_a": "Erreur lors de la suppression de l'appareil",
"BackDevices_DBTools_DelDevError_b": "Erreur lors de la suppression des appareils",
"BackDevices_DBTools_DelDev_a": "Appareil supprim\u00e9",
"BackDevices_DBTools_DelDev_b": "Appareils supprim\u00e9s",
"BackDevices_DBTools_DelEvents": "\u00c9v\u00e9nements supprim\u00e9s",
"BackDevices_DBTools_DelEventsError": "Erreur lors de la suppression des \u00e9v\u00e9nements",
"BackDevices_DBTools_ImportCSV": "Les appareils du fichier CSV ont \u00e9t\u00e9 import\u00e9s avec succ\u00e8s.",
"BackDevices_DBTools_ImportCSVError": "Le fichier CSV n'a pas pu \u00eatre import\u00e9. Assurez-vous que le format est correct.",
"BackDevices_DBTools_ImportCSVMissing": "Le fichier CSV est introuvable sous <b>/config/devices.csv.</b>",
"BackDevices_DBTools_Purge": "Les sauvegardes les plus anciennes ont \u00e9t\u00e9 supprim\u00e9es",
"BackDevices_DBTools_UpdDev": "Appareil mis \u00e0 jour avec succ\u00e8s",
"BackDevices_DBTools_UpdDevError": "Erreur lors de la mise \u00e0 jour de l'appareil",
"BackDevices_DBTools_Upgrade": "Base de donn\u00e9es mise \u00e0 niveau avec succ\u00e8s",
"BackDevices_DBTools_UpgradeError": "La mise \u00e0 niveau de la base de donn\u00e9es a \u00e9chou\u00e9",
"BackDevices_Device_UpdDevError": "Erreur de mise \u00e0 jour des appareils, essayez plus tard. La base de donn\u00e9es est probablement bloqu\u00e9e en raison d'une t\u00e2che en cours.",
"BackDevices_Restore_CopError": "La base de donn\u00e9es originale n'a pas pu \u00eatre sauvegard\u00e9e.",
"BackDevices_Restore_Failed": "\u00c9chec de la restauration. Veuillez restaurer la sauvegarde manuellement.",
"BackDevices_Restore_okay": "Restauration ex\u00e9cut\u00e9e avec succ\u00e8s.",
"BackDevices_darkmode_disabled": "Mode sombre d\u00e9sactiv\u00e9",
"BackDevices_darkmode_enabled": "Mode sombre activ\u00e9",
"DAYS_TO_KEEP_EVENTS_description": "",
"DAYS_TO_KEEP_EVENTS_name": "Supprimer les \u00e9v\u00e9nements plus anciens que",
"DevDetail_Copy_Device_Title": "",
"DevDetail_Copy_Device_Tooltip": "",
"DevDetail_EveandAl_AlertAllEvents": "Alerter tous les \u00e9v\u00e9nements",
"DevDetail_EveandAl_AlertDown": "",
"DevDetail_EveandAl_Archived": "Archiv\u00e9",
"DevDetail_EveandAl_NewDevice": "",
"DevDetail_EveandAl_NewDevice_Tooltip": "",
"DevDetail_EveandAl_RandomMAC": "MAC al\u00e9atoire",
"DevDetail_EveandAl_ScanCycle": "",
"DevDetail_EveandAl_ScanCycle_a": "",
"DevDetail_EveandAl_ScanCycle_z": "",
"DevDetail_EveandAl_Skip": "",
"DevDetail_EveandAl_Title": "",
"DevDetail_Events_CheckBox": "Masquer les \u00e9v\u00e9nements de connexion",
"DevDetail_GoToNetworkNode": "",
"DevDetail_Icon": "Ic\u00f4ne",
"DevDetail_Icon_Descr": "",
"DevDetail_Loading": "Chargement\u00a0\u2026",
"DevDetail_MainInfo_Comments": "Observations",
"DevDetail_MainInfo_Favorite": "Favori",
"DevDetail_MainInfo_Group": "Groupe",
"DevDetail_MainInfo_Location": "Emplacement",
"DevDetail_MainInfo_Name": "Nom",
"DevDetail_MainInfo_Network": "",
"DevDetail_MainInfo_Network_Port": "<i class=\"fa fa-ethernet\"></i> Port",
"DevDetail_MainInfo_Network_Title": "<i class=\"fa fa-network-wired\"></i> R\u00e9seau",
"DevDetail_MainInfo_Owner": "Propri\u00e9taire",
"DevDetail_MainInfo_Title": "<i class=\"fa fa-pencil\"></i> Informations principales",
"DevDetail_MainInfo_Type": "Type",
"DevDetail_MainInfo_Vendor": "Fabriquant",
"DevDetail_MainInfo_mac": "MAC",
"DevDetail_Network_Node_hover": "",
"DevDetail_Network_Port_hover": "",
"DevDetail_Nmap_Scans": "",
"DevDetail_Nmap_Scans_desc": "",
"DevDetail_Nmap_buttonDefault": "",
"DevDetail_Nmap_buttonDefault_text": "",
"DevDetail_Nmap_buttonDetail": "",
"DevDetail_Nmap_buttonDetail_text": "",
"DevDetail_Nmap_buttonFast": "",
"DevDetail_Nmap_buttonFast_text": "",
"DevDetail_Nmap_buttonSkipDiscovery": "",
"DevDetail_Nmap_buttonSkipDiscovery_text": "",
"DevDetail_Nmap_resultsLink": "",
"DevDetail_Owner_hover": "",
"DevDetail_Periodselect_All": "",
"DevDetail_Periodselect_LastMonth": "",
"DevDetail_Periodselect_LastWeek": "",
"DevDetail_Periodselect_LastYear": "",
"DevDetail_Periodselect_today": "Aujourd'hui",
"DevDetail_Run_Actions_Title": "",
"DevDetail_Run_Actions_Tooltip": "",
"DevDetail_SessionInfo_FirstSession": "Premi\u00e8re session",
"DevDetail_SessionInfo_LastIP": "Derni\u00e8re IP",
"DevDetail_SessionInfo_LastSession": "Derni\u00e8re session",
"DevDetail_SessionInfo_StaticIP": "IP statique",
"DevDetail_SessionInfo_Status": "\u00c9tat",
"DevDetail_SessionInfo_Title": "<i class=\"fa fa-calendar\"></i> Info de session",
"DevDetail_SessionTable_Additionalinfo": "Informations suppl\u00e9mentaires",
"DevDetail_SessionTable_Connection": "Connexion",
"DevDetail_SessionTable_Disconnection": "D\u00e9connection",
"DevDetail_SessionTable_Duration": "Dur\u00e9e",
"DevDetail_SessionTable_IP": "IP",
"DevDetail_SessionTable_Order": "",
"DevDetail_Shortcut_CurrentStatus": "\u00c9tat actuel",
"DevDetail_Shortcut_DownAlerts": "Alertes de panne",
"DevDetail_Shortcut_Presence": "Pr\u00e9sence",
"DevDetail_Shortcut_Sessions": "Sessions",
"DevDetail_Tab_Details": "",
"DevDetail_Tab_Events": "",
"DevDetail_Tab_EventsTableDate": "Date",
"DevDetail_Tab_EventsTableEvent": "Type d'\u00e9v\u00e9nement",
"DevDetail_Tab_EventsTableIP": "IP",
"DevDetail_Tab_EventsTableInfo": "Informations compl\u00e9mentaires",
"DevDetail_Tab_Nmap": "",
"DevDetail_Tab_NmapEmpty": "",
"DevDetail_Tab_NmapTableExtra": "Extra",
"DevDetail_Tab_NmapTableHeader": "",
"DevDetail_Tab_NmapTableIndex": "Index",
"DevDetail_Tab_NmapTablePort": "Port",
"DevDetail_Tab_NmapTableService": "Service",
"DevDetail_Tab_NmapTableState": "\u00c9tat",
"DevDetail_Tab_NmapTableText": "",
"DevDetail_Tab_NmapTableTime": "Heure",
"DevDetail_Tab_Plugins": "",
"DevDetail_Tab_Presence": "",
"DevDetail_Tab_Sessions": "<i class=\"fa fa-list-ol\"></i> Sessions",
"DevDetail_Tab_Tools": "",
"DevDetail_Tab_Tools_Internet_Info_Description": "",
"DevDetail_Tab_Tools_Internet_Info_Error": "",
"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": "Nslookup",
"DevDetail_Tab_Tools_Speedtest_Description": "",
"DevDetail_Tab_Tools_Speedtest_Start": "",
"DevDetail_Tab_Tools_Speedtest_Title": "Test de d\u00e9bit en ligne",
"DevDetail_Tab_Tools_Traceroute_Description": "",
"DevDetail_Tab_Tools_Traceroute_Error": "",
"DevDetail_Tab_Tools_Traceroute_Start": "",
"DevDetail_Tab_Tools_Traceroute_Title": "Traceroute",
"DevDetail_Tools_WOL": "",
"DevDetail_Tools_WOL_noti": "",
"DevDetail_Tools_WOL_noti_text": "",
"DevDetail_Type_hover": "",
"DevDetail_Vendor_hover": "",
"DevDetail_WOL_Title": "",
"DevDetail_button_Delete": "Supprimer l'appareil",
"DevDetail_button_DeleteEvents": "Supprimer les \u00e9v\u00e9nements",
"DevDetail_button_DeleteEvents_Warning": "",
"DevDetail_button_OverwriteIcons": "",
"DevDetail_button_OverwriteIcons_Tooltip": "",
"DevDetail_button_OverwriteIcons_Warning": "",
"DevDetail_button_Reset": "",
"DevDetail_button_Save": "Enregistrer",
"Device_Searchbox": "Rechercher",
"Device_Shortcut_AllDevices": "Tous les appareils",
"Device_Shortcut_Archived": "Archiv\u00e9",
"Device_Shortcut_Connected": "Connect\u00e9",
"Device_Shortcut_Devices": "Appareils",
"Device_Shortcut_DownAlerts": "",
"Device_Shortcut_Favorites": "Favoris",
"Device_Shortcut_NewDevices": "Nouveaux appareils",
"Device_Shortcut_OnlineChart": "Pr\u00e9sence de l'appareil",
"Device_TableHead_Connected_Devices": "Connexions",
"Device_TableHead_Favorite": "Favori",
"Device_TableHead_FirstSession": "Premi\u00e8re session",
"Device_TableHead_Group": "Groupe",
"Device_TableHead_Icon": "Ic\u00f4ne",
"Device_TableHead_LastIP": "",
"Device_TableHead_LastIPOrder": "",
"Device_TableHead_LastSession": "Derni\u00e8re session",
"Device_TableHead_Location": "Emplacement",
"Device_TableHead_MAC": "",
"Device_TableHead_MAC_full": "Adresse MAC",
"Device_TableHead_Name": "Nom",
"Device_TableHead_Owner": "Propri\u00e9taire",
"Device_TableHead_Parent_MAC": "",
"Device_TableHead_Port": "Port",
"Device_TableHead_RowID": "",
"Device_TableHead_Rowid": "",
"Device_TableHead_Status": "\u00c9tat",
"Device_TableHead_Type": "Type",
"Device_TableHead_Vendor": "Fabriquant",
"Device_Table_Not_Network_Device": "",
"Device_Table_info": "",
"Device_Table_nav_next": "Suivant",
"Device_Table_nav_prev": "Pr\u00e9c\u00e9dent",
"Device_Tablelenght": "",
"Device_Tablelenght_all": "",
"Device_Title": "Appareils",
"Donations_Others": "Autres",
"Donations_Platforms": "Plateformes de sponsoring",
"Donations_Text": "",
"Donations_Title": "Dons",
"ENABLE_PLUGINS_description": "",
"ENABLE_PLUGINS_name": "",
"Email_display_name": "Messagerie",
"Email_icon": "",
"Events_Loading": "Chargement\u00a0\u2026",
"Events_Periodselect_All": "Toutes les informations",
"Events_Periodselect_LastMonth": "Le mois dernier",
"Events_Periodselect_LastWeek": "La semaine derni\u00e8re",
"Events_Periodselect_LastYear": "L'ann\u00e9e derni\u00e8re",
"Events_Periodselect_today": "Aujourd'hui",
"Events_Searchbox": "Rechercher",
"Events_Shortcut_AllEvents": "Tous les \u00e9v\u00e8nements",
"Events_Shortcut_DownAlerts": "Alertes de panne",
"Events_Shortcut_Events": "\u00c9v\u00e8nements",
"Events_Shortcut_MissSessions": "Sessions manquantes",
"Events_Shortcut_NewDevices": "Nouveaux appareils",
"Events_Shortcut_Sessions": "Sessions",
"Events_Shortcut_VoidSessions": "Sessions annul\u00e9es",
"Events_TableHead_AdditionalInfo": "Informations compl\u00e9mentaires",
"Events_TableHead_Connection": "Connexion",
"Events_TableHead_Date": "Date",
"Events_TableHead_Device": "Dispositif",
"Events_TableHead_Disconnection": "D\u00e9connexion",
"Events_TableHead_Duration": "Dur\u00e9e",
"Events_TableHead_DurationOrder": "Ordre de dur\u00e9e",
"Events_TableHead_EventType": "Type d'\u00e9v\u00e9nement",
"Events_TableHead_IP": "IP",
"Events_TableHead_IPOrder": "",
"Events_TableHead_Order": "",
"Events_TableHead_Owner": "Propri\u00e9taire",
"Events_Table_info": "",
"Events_Table_nav_next": "Suivant",
"Events_Table_nav_prev": "Pr\u00e9c\u00e9dent",
"Events_Tablelenght": "",
"Events_Tablelenght_all": "",
"Events_Title": "\u00c9v\u00e8nements",
"Gen_Action": "Action",
"Gen_AreYouSure": "",
"Gen_Backup": "",
"Gen_Cancel": "Annuler",
"Gen_Copy": "Lancer",
"Gen_DataUpdatedUITakesTime": "",
"Gen_Delete": "Supprimer",
"Gen_DeleteAll": "",
"Gen_Error": "Erreur",
"Gen_LockedDB": "",
"Gen_Okay": "OK",
"Gen_Purge": "Purger",
"Gen_ReadDocs": "",
"Gen_Restore": "",
"Gen_Run": "Lancer",
"Gen_Save": "Enregistrer",
"Gen_Saved": "Enregistr\u00e9",
"Gen_Switch": "Basculer",
"Gen_Upd": "",
"Gen_Upd_Fail": "",
"Gen_Warning": "Avertissement",
"Gen_Work_In_Progress": "",
"General_display_name": "G\u00e9n\u00e9ral",
"General_icon": "",
"HRS_TO_KEEP_NEWDEV_description": "",
"HRS_TO_KEEP_NEWDEV_name": "",
"HelpFAQ_Cat_Detail": "D\u00e9tails",
"HelpFAQ_Cat_Detail_300_head": "",
"HelpFAQ_Cat_Detail_300_text_a": "",
"HelpFAQ_Cat_Detail_300_text_b": "",
"HelpFAQ_Cat_Detail_301_head_a": "",
"HelpFAQ_Cat_Detail_301_head_b": "",
"HelpFAQ_Cat_Detail_301_text": "",
"HelpFAQ_Cat_Detail_302_head_a": "",
"HelpFAQ_Cat_Detail_302_head_b": "",
"HelpFAQ_Cat_Detail_302_text": "",
"HelpFAQ_Cat_Detail_303_head": "",
"HelpFAQ_Cat_Detail_303_text": "",
"HelpFAQ_Cat_Device_200_head": "",
"HelpFAQ_Cat_Device_200_text": "",
"HelpFAQ_Cat_General": "G\u00e9n\u00e9ral",
"HelpFAQ_Cat_General_100_head": "L'horloge en haut \u00e0 droite et les heures des \u00e9v\u00e9nements/pr\u00e9sence ne sont pas correctes (d\u00e9calage horaire).",
"HelpFAQ_Cat_General_100_text_a": "",
"HelpFAQ_Cat_General_100_text_b": "",
"HelpFAQ_Cat_General_100_text_c": "",
"HelpFAQ_Cat_General_101_head": "",
"HelpFAQ_Cat_General_101_text": "",
"HelpFAQ_Cat_General_102_head": "",
"HelpFAQ_Cat_General_102_text": "",
"HelpFAQ_Cat_General_102docker_head": "",
"HelpFAQ_Cat_General_102docker_text": "",
"HelpFAQ_Cat_General_103_head": "",
"HelpFAQ_Cat_General_103_text": "",
"HelpFAQ_Cat_Network_600_head": "",
"HelpFAQ_Cat_Network_600_text": "",
"HelpFAQ_Cat_Network_601_head": "",
"HelpFAQ_Cat_Network_601_text": "",
"HelpFAQ_Cat_Presence_400_head": "",
"HelpFAQ_Cat_Presence_400_text": "",
"HelpFAQ_Cat_Presence_401_head": "",
"HelpFAQ_Cat_Presence_401_text": "",
"HelpFAQ_Title": "Aide / FAQ",
"LOG_LEVEL_description": "",
"LOG_LEVEL_name": "",
"Loading": "Chargement\u00a0\u2026",
"Login_Box": "",
"Login_Default_PWD": "",
"Login_Psw-box": "Mot de passe",
"Login_Psw_alert": "",
"Login_Psw_folder": "",
"Login_Psw_new": "",
"Login_Psw_run": "",
"Login_Remember": "",
"Login_Remember_small": "",
"Login_Submit": "",
"Login_Toggle_Alert_headline": "",
"Login_Toggle_Info": "",
"Login_Toggle_Info_headline": "",
"Maintenance_Running_Version": "Version install\u00e9e",
"Maintenance_Status": "\u00c9tat",
"Maintenance_Title": "Outils d'entretien",
"Maintenance_Tool_ExportCSV": "Exportation CSV",
"Maintenance_Tool_ExportCSV_noti": "Exportation CSV",
"Maintenance_Tool_ExportCSV_noti_text": "\u00cates-vous s\u00fbr de vouloir g\u00e9n\u00e9rer un fichier CSV\u202f?",
"Maintenance_Tool_ExportCSV_text": "",
"Maintenance_Tool_ImportCSV": "Importation CSV",
"Maintenance_Tool_ImportCSV_noti": "Importation CSV",
"Maintenance_Tool_ImportCSV_noti_text": "\u00cates-vous s\u00fbr de vouloir importer le fichier CSV\u202f? Cela \u00e9crasera compl\u00e8tement les appareils de votre base de donn\u00e9es.",
"Maintenance_Tool_ImportCSV_text": "",
"Maintenance_Tool_arpscansw": "Basculer l'arp-Scan (activ\u00e9/d\u00e9sactiv\u00e9)",
"Maintenance_Tool_arpscansw_noti": "Activer ou d\u00e9sactiver l'arp-Scan",
"Maintenance_Tool_arpscansw_noti_text": "Une fois le scan d\u00e9sactiv\u00e9, il reste d\u00e9sactiv\u00e9 jusqu'\u00e0 ce qu'il soit r\u00e9activ\u00e9.",
"Maintenance_Tool_arpscansw_text": "",
"Maintenance_Tool_backup": "",
"Maintenance_Tool_backup_noti": "",
"Maintenance_Tool_backup_noti_text": "",
"Maintenance_Tool_backup_text": "",
"Maintenance_Tool_check_visible": "",
"Maintenance_Tool_darkmode": "",
"Maintenance_Tool_darkmode_noti": "",
"Maintenance_Tool_darkmode_noti_text": "",
"Maintenance_Tool_darkmode_text": "",
"Maintenance_Tool_del_ActHistory": "",
"Maintenance_Tool_del_ActHistory_noti": "",
"Maintenance_Tool_del_ActHistory_noti_text": "",
"Maintenance_Tool_del_ActHistory_text": "",
"Maintenance_Tool_del_alldev": "",
"Maintenance_Tool_del_alldev_noti": "",
"Maintenance_Tool_del_alldev_noti_text": "",
"Maintenance_Tool_del_alldev_text": "",
"Maintenance_Tool_del_allevents": "",
"Maintenance_Tool_del_allevents30": "",
"Maintenance_Tool_del_allevents30_noti": "",
"Maintenance_Tool_del_allevents30_noti_text": "",
"Maintenance_Tool_del_allevents30_text": "",
"Maintenance_Tool_del_allevents_noti": "",
"Maintenance_Tool_del_allevents_noti_text": "",
"Maintenance_Tool_del_allevents_text": "",
"Maintenance_Tool_del_empty_macs": "",
"Maintenance_Tool_del_empty_macs_noti": "",
"Maintenance_Tool_del_empty_macs_noti_text": "",
"Maintenance_Tool_del_empty_macs_text": "",
"Maintenance_Tool_del_unknowndev": "",
"Maintenance_Tool_del_unknowndev_noti": "",
"Maintenance_Tool_del_unknowndev_noti_text": "",
"Maintenance_Tool_del_unknowndev_text": "",
"Maintenance_Tool_displayed_columns_text": "",
"Maintenance_Tool_drag_me": "",
"Maintenance_Tool_order_columns_text": "",
"Maintenance_Tool_purgebackup": "",
"Maintenance_Tool_purgebackup_noti": "",
"Maintenance_Tool_purgebackup_noti_text": "",
"Maintenance_Tool_purgebackup_text": "",
"Maintenance_Tool_restore": "",
"Maintenance_Tool_restore_noti": "",
"Maintenance_Tool_restore_noti_text": "",
"Maintenance_Tool_restore_text": "",
"Maintenance_Tool_upgrade_database_noti": "",
"Maintenance_Tool_upgrade_database_noti_text": "",
"Maintenance_Tool_upgrade_database_text": "",
"Maintenance_Tools_Tab_BackupRestore": "",
"Maintenance_Tools_Tab_Logging": "Journaux",
"Maintenance_Tools_Tab_Settings": "Param\u00e8tres",
"Maintenance_Tools_Tab_Tools": "Outils",
"Maintenance_Tools_Tab_UISettings": "Param\u00e8tres de l'interface",
"Maintenance_arp_status": "",
"Maintenance_arp_status_off": "est actuellement d\u00e9sactiv\u00e9",
"Maintenance_arp_status_on": "",
"Maintenance_built_on": "Construit sur",
"Maintenance_current_version": "Vous \u00eates \u00e0 jour. D\u00e9couvrez sur quoi <a href=\"https://github.com/jokob-sk/Pi.Alert/issues/138\" target=\"_blank\">je travaille</a>.",
"Maintenance_database_backup": "Sauvegardes de base de donn\u00e9es",
"Maintenance_database_backup_found": "des sauvegardes ont \u00e9t\u00e9 trouv\u00e9es",
"Maintenance_database_backup_total": "utilisation totale du disque",
"Maintenance_database_lastmod": "Derni\u00e8re modification",
"Maintenance_database_path": "Chemin de la base de donn\u00e9es",
"Maintenance_database_rows": "",
"Maintenance_database_size": "",
"Maintenance_lang_de_de": "",
"Maintenance_lang_en_us": "",
"Maintenance_lang_es_es": "",
"Maintenance_lang_selector_apply": "Appliquer",
"Maintenance_lang_selector_empty": "",
"Maintenance_lang_selector_lable": "",
"Maintenance_lang_selector_text": "",
"Maintenance_new_version": "",
"Maintenance_themeselector_apply": "Appliquer",
"Maintenance_themeselector_empty": "",
"Maintenance_themeselector_lable": "",
"Maintenance_themeselector_text": "",
"Maintenance_version": "",
"NETWORK_DEVICE_TYPES_description": "",
"NETWORK_DEVICE_TYPES_name": "",
"Navigation_Devices": "Appareils",
"Navigation_Donations": "Dons",
"Navigation_Events": "\u00c9v\u00e8nements",
"Navigation_HelpFAQ": "Aide / FAQ",
"Navigation_Maintenance": "",
"Navigation_Network": "R\u00e9seau",
"Navigation_Plugins": "Greffons",
"Navigation_Presence": "Pr\u00e9sence",
"Navigation_Report": "",
"Navigation_Settings": "Param\u00e8tres",
"Navigation_SystemInfo": "Infos syst\u00e8me",
"Navigation_Workflows": "Flux de travail",
"Network_Assign": "",
"Network_Cant_Assign": "",
"Network_Configuration_Error": "",
"Network_Connected": "",
"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": "Assigner",
"Network_ManageDel": "",
"Network_ManageDel_Name": "",
"Network_ManageDel_Name_text": "",
"Network_ManageDel_Submit": "Supprimer",
"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_Table_Hostname": "Nom de h\u00f4te",
"Network_Table_IP": "IP",
"Network_Table_State": "\u00c9tat",
"Network_Title": "",
"Network_UnassignedDevices": "",
"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_Objects": "",
"Plugins_Out_of": "",
"Plugins_Unprocessed_Events": "\u00c9v\u00e9nements non trait\u00e9s",
"Plugins_no_control": "",
"Presence_CalHead_day": "jour",
"Presence_CalHead_lang": "",
"Presence_CalHead_month": "mois",
"Presence_CalHead_quarter": "trimestre",
"Presence_CalHead_week": "semaine",
"Presence_CalHead_year": "ann\u00e9e",
"Presence_CallHead_Devices": "Appareils",
"Presence_Loading": "Chargement\u00a0\u2026",
"Presence_Shortcut_AllDevices": "",
"Presence_Shortcut_Archived": "Archiv\u00e9",
"Presence_Shortcut_Connected": "Connect\u00e9",
"Presence_Shortcut_Devices": "Appareils",
"Presence_Shortcut_DownAlerts": "",
"Presence_Shortcut_Favorites": "Favoris",
"Presence_Shortcut_NewDevices": "",
"Presence_Title": "",
"REPORT_DASHBOARD_URL_description": "",
"REPORT_DASHBOARD_URL_name": "",
"REPORT_ERROR": "",
"REPORT_MAIL_description": "",
"REPORT_MAIL_name": "",
"REPORT_TITLE": "",
"RandomMAC_hover": "",
"SCAN_SUBNETS_description": "",
"SYSTEM_TITLE": "Informations syst\u00e8me",
"Setting_Override": "",
"Setting_Override_Description": "",
"Settings_Metadata_Toggle": "",
"Settings_device_Scanners_desync": "",
"Settings_device_Scanners_desync_popup": "",
"Speedtest_Results": "",
"Systeminfo_CPU": "Processeur",
"Systeminfo_CPU_Cores": "C\u0153urs de processeur\u202f:",
"Systeminfo_CPU_Name": "Nom du processeur\u202f:",
"Systeminfo_CPU_Speed": "Vitesse du CPU\u202f:",
"Systeminfo_CPU_Temp": "Temp\u00e9rature du processeur\u202f:",
"Systeminfo_CPU_Vendor": "Fabriquant du processeur\u202f:",
"Systeminfo_Client_Resolution": "R\u00e9solution du navigateur\u202f:",
"Systeminfo_Client_User_Agent": "Agent utilisateur\u202f:",
"Systeminfo_General": "G\u00e9n\u00e9ral",
"Systeminfo_General_Date": "Date\u202f:",
"Systeminfo_General_Date2": "Date 2\u202f:",
"Systeminfo_General_Full_Date": "Date compl\u00e8te\u202f:",
"Systeminfo_General_TimeZone": "Fuseau horaire\u202f:",
"Systeminfo_Memory": "M\u00e9moire",
"Systeminfo_Memory_Total_Memory": "M\u00e9moire totale\u202f:",
"Systeminfo_Memory_Usage": "Utilisation de la m\u00e9moire:",
"Systeminfo_Memory_Usage_Percent": "% de la m\u00e9moire\u202f:",
"Systeminfo_Motherboard": "Carte m\u00e8re",
"Systeminfo_Motherboard_BIOS": "BIOS\u202f:",
"Systeminfo_Motherboard_BIOS_Date": "Date du BIOS\u202f:",
"Systeminfo_Motherboard_BIOS_Vendor": "Fabriquant du BIOS\u202f:",
"Systeminfo_Motherboard_Manufactured": "Fabriqu\u00e9 par\u202f:",
"Systeminfo_Motherboard_Name": "Nom\u202f:",
"Systeminfo_Motherboard_Revision": "R\u00e9vision\u202f:",
"Systeminfo_Network": "R\u00e9seau",
"Systeminfo_Network_Accept_Encoding": "Accepter l'encodage\u202f:",
"Systeminfo_Network_Accept_Language": "Accepter la langue\u202f:",
"Systeminfo_Network_Connection_Port": "Port de connexion\u202f:",
"Systeminfo_Network_HTTP_Host": "H\u00f4te HTTP\u202f:",
"Systeminfo_Network_HTTP_Referer": "R\u00e9f\u00e9rent HTTP\u202f:",
"Systeminfo_Network_HTTP_Referer_String": "Pas de r\u00e9f\u00e9rent HTTP",
"Systeminfo_Network_Hardware": "Mat\u00e9riel r\u00e9seau",
"Systeminfo_Network_IP": "IP Internet\u202f:",
"Systeminfo_Network_IP_Connection": "Connexion IP\u202f:",
"Systeminfo_Network_IP_Server": "IP du serveur\u202f:",
"Systeminfo_Network_MIME": "MIME\u202f:",
"Systeminfo_Network_Request_Method": "M\u00e9thode de demande\u202f:",
"Systeminfo_Network_Request_Time": "Heure de la demande\u202f:",
"Systeminfo_Network_Request_URI": "URI de la demande\u202f:",
"Systeminfo_Network_Secure_Connection": "Connexion s\u00e9curis\u00e9e\u202f:",
"Systeminfo_Network_Secure_Connection_String": "",
"Systeminfo_Network_Server_Name": "Nom du serveur\u202f:",
"Systeminfo_Network_Server_Name_String": "Nom du serveur introuvable",
"Systeminfo_Network_Server_Query": "Requ\u00eate du serveur\u202f:",
"Systeminfo_Network_Server_Query_String": "Aucune cha\u00eene de requ\u00eate",
"Systeminfo_Network_Server_Version": "Version du serveur\u202f:",
"Systeminfo_Services": "Services",
"Systeminfo_Services_Description": "Description du service",
"Systeminfo_Services_Name": "Nom du service",
"Systeminfo_Storage": "Stockage",
"Systeminfo_Storage_Device": "Appareil\u202f:",
"Systeminfo_Storage_Mount": "Point de montage\u202f:",
"Systeminfo_Storage_Size": "Taille\u202f:",
"Systeminfo_Storage_Type": "Type\u202f:",
"Systeminfo_Storage_Usage": "",
"Systeminfo_Storage_Usage_Free": "Libre\u202f:",
"Systeminfo_Storage_Usage_Mount": "",
"Systeminfo_Storage_Usage_Total": "Total\u202f:",
"Systeminfo_Storage_Usage_Used": "Utilis\u00e9\u202f:",
"Systeminfo_System": "Syst\u00e8me",
"Systeminfo_System_AVG": "",
"Systeminfo_System_Architecture": "Architecture\u202f:",
"Systeminfo_System_Kernel": "Noyau\u202f:",
"Systeminfo_System_OSVersion": "",
"Systeminfo_System_Running_Processes": "Processus en cours\u202f:",
"Systeminfo_System_System": "Syst\u00e8me\u202f:",
"Systeminfo_System_Uname": "",
"Systeminfo_System_Uptime": "",
"Systeminfo_This_Client": "",
"Systeminfo_USB_Devices": "",
"TIMEZONE_description": "",
"TIMEZONE_name": "",
"UI_LANG_description": "S\u00e9lectionnez votre langue pr\u00e9f\u00e9r\u00e9 de l\u2019interface. Aidez \u00e0 traduire ou sugg\u00e9rez des langues dans le portail en ligne de <a href=\"https://hosted.weblate.org/projects/pialert/core/\" target=\"_blank\">Weblate</a>.",
"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": "",
"devices_old": "",
"general_event_description": "",
"general_event_title": "",
"report_guid": "",
"report_guid_missing": "",
"report_select_format": "",
"report_time": "",
"run_event_icon": "",
"run_event_tooltip": "",
"settings_core_icon": "",
"settings_core_label": "",
"settings_device_scanners": "",
"settings_device_scanners_icon": "",
"settings_device_scanners_label": "Scanners d'appareils",
"settings_enabled": "Param\u00e8tres activ\u00e9s",
"settings_enabled_icon": "",
"settings_expand_all": "Tout d\u00e9velopper",
"settings_imported": "",
"settings_imported_label": "Param\u00e8tres import\u00e9s",
"settings_missing": "",
"settings_missing_block": "",
"settings_old": "Importation des param\u00e8tres et r\u00e9initialisation...",
"settings_other_scanners": "",
"settings_other_scanners_icon": "",
"settings_other_scanners_label": "",
"settings_publishers": "",
"settings_publishers_icon": "",
"settings_publishers_label": "\u00c9diteurs",
"settings_saved": "",
"settings_system_icon": "",
"settings_system_label": "Syst\u00e8me",
"test_event_icon": "",
"test_event_tooltip": ""
}

View File

@@ -31,33 +31,28 @@ function getLanguageDataFromJson()
{
global $allLanguages;
// Default language
$defaultLanguage = 'en_us';
// Array to hold the language data from the JSON files
$languageData = [];
foreach ($allLanguages as $language) {
// Load and parse the JSON data from .json files
$jsonFilePath = dirname(__FILE__) . '/' . $language . '.json';
if (file_exists($jsonFilePath)) {
$data = json_decode(file_get_contents($jsonFilePath), true);
// Use the default language if the key is not found
$languageData[$language] = $data[$language] ?? $data[$defaultLanguage] ?? [];
// Adjusting for the changed JSON format
$languageData[$language] = $data;
} else {
// Handle the case where the JSON file doesn't exist
// For example, you might want to log an error message
echo 'File not found: '.$jsonFilePath;
echo 'File not found: ' . $jsonFilePath;
}
}
return $languageData;
}
// Merge the JSON data with the SQL data, giving priority to SQL data for overlapping keys
function mergeLanguageData($jsonLanguageData, $sqlLanguageData)
{
@@ -76,30 +71,31 @@ function mergeLanguageData($jsonLanguageData, $sqlLanguageData)
function lang($key)
{
global $pia_lang_selected, $lang, $defaultLang, $strings, $db;
// Get the data from JSON files
$languageData = getLanguageDataFromJson();
global $pia_lang_selected, $strings;
// Get the data from SQL query
$sqlLanguageData = $strings;
// Get the data from JSON files
$languageData = getLanguageDataFromJson();
// Merge JSON data with SQL data
$mergedLanguageData = mergeLanguageData($languageData, $sqlLanguageData);
// Get the data from SQL query
$sqlLanguageData = $strings;
// Check if the key exists in the selected language
if (isset($mergedLanguageData[$pia_lang_selected][$key])) {
$result = $mergedLanguageData[$pia_lang_selected][$key];
} else {
// If key not found in selected language, use "en_us" as fallback
if (isset($mergedLanguageData['en_us'][$key])) {
$result = $mergedLanguageData['en_us'][$key];
// Merge JSON data with SQL data
$mergedLanguageData = mergeLanguageData($languageData, $sqlLanguageData);
// Check if the key exists in the selected language
if (isset($mergedLanguageData[$pia_lang_selected][$key]) && $mergedLanguageData[$pia_lang_selected][$key] != '') {
$result = $mergedLanguageData[$pia_lang_selected][$key];
} else {
// If key not found in "en_us" either, use a default string
$result = "String Not found for key " . $key;
// If key not found in selected language, use "en_us" as fallback
if (isset($mergedLanguageData['en_us'][$key])) {
$result = $mergedLanguageData['en_us'][$key];
} else {
// If key not found in "en_us" either, use a default string
$result = "String Not found for key " . $key;
}
}
}
return $result;
return $result;
}

View File

@@ -0,0 +1,37 @@
import json
import os
import sys
def merge_translations(main_file, other_files):
# Load main file
with open(main_file, 'r') as f:
main_data = json.load(f)
# Get keys and sort them alphabetically
keys = sorted(main_data.keys())
# Sort the keys alphabetically in the main file
main_data = {k: main_data[k] for k in keys}
# Rewrite sorted main file
with open(main_file, 'w') as f:
json.dump(main_data, f, indent=4)
# Merge keys into other files
for file_name in other_files:
with open(file_name, 'r+') as f:
data = json.load(f)
for key in keys:
if key not in data:
data[key] = ""
# Sort the keys alphabetically for each language
data = {k: data[k] for k in sorted(data.keys())}
f.seek(0)
json.dump(data, f, indent=4)
f.truncate()
if __name__ == "__main__":
current_path = os.path.dirname(os.path.abspath(__file__))
json_files = ["en_us.json", "de_de.json", "es_es.json", "fr_fr.json", "nb_no.json"]
file_paths = [os.path.join(current_path, file) for file in json_files]
merge_translations(file_paths[0], file_paths[1:])

View File

@@ -0,0 +1,638 @@
{
"API_CUSTOM_SQL_description": "",
"API_CUSTOM_SQL_name": "",
"API_display_name": "",
"API_icon": "",
"About_Design": "",
"About_Exit": "",
"About_Title": "",
"AppEvents_DateTimeCreated": "",
"AppEvents_Extra": "",
"AppEvents_GUID": "",
"AppEvents_Helper1": "",
"AppEvents_Helper2": "",
"AppEvents_Helper3": "",
"AppEvents_ObjectForeignKey": "",
"AppEvents_ObjectIndex": "",
"AppEvents_ObjectIsArchived": "",
"AppEvents_ObjectIsNew": "",
"AppEvents_ObjectPlugin": "",
"AppEvents_ObjectPrimaryID": "Prim\u00e6r-ID",
"AppEvents_ObjectSecondaryID": "Sekund\u00e6r-ID",
"AppEvents_ObjectStatus": "Status (ved loggf\u00f8ringstidspunkt)",
"AppEvents_ObjectStatusColumn": "Statuskolonne",
"AppEvents_ObjectType": "Objekttype",
"AppEvents_Plugin": "Programtillegg",
"AppEvents_Type": "Type",
"BackDevDetail_Actions_Ask_Run": "Utf\u00f8r handlingen?",
"BackDevDetail_Actions_Not_Registered": "",
"BackDevDetail_Actions_Title_Run": "Utf\u00f8r handling",
"BackDevDetail_Copy_Ask": "",
"BackDevDetail_Copy_Title": "Kopier detaljer",
"BackDevDetail_Tools_WOL_error": "Kommandoen ble IKKE kj\u00f8rt.",
"BackDevDetail_Tools_WOL_okay": "",
"BackDevices_Arpscan_disabled": "",
"BackDevices_Arpscan_enabled": "",
"BackDevices_Backup_CopError": "",
"BackDevices_Backup_Failed": "",
"BackDevices_Backup_okay": "",
"BackDevices_DBTools_DelDevError_a": "",
"BackDevices_DBTools_DelDevError_b": "",
"BackDevices_DBTools_DelDev_a": "Enhet slettet",
"BackDevices_DBTools_DelDev_b": "",
"BackDevices_DBTools_DelEvents": "",
"BackDevices_DBTools_DelEventsError": "",
"BackDevices_DBTools_ImportCSV": "",
"BackDevices_DBTools_ImportCSVError": "",
"BackDevices_DBTools_ImportCSVMissing": "",
"BackDevices_DBTools_Purge": "",
"BackDevices_DBTools_UpdDev": "",
"BackDevices_DBTools_UpdDevError": "",
"BackDevices_DBTools_Upgrade": "",
"BackDevices_DBTools_UpgradeError": "",
"BackDevices_Device_UpdDevError": "",
"BackDevices_Restore_CopError": "",
"BackDevices_Restore_Failed": "",
"BackDevices_Restore_okay": "",
"BackDevices_darkmode_disabled": "",
"BackDevices_darkmode_enabled": "",
"DAYS_TO_KEEP_EVENTS_description": "",
"DAYS_TO_KEEP_EVENTS_name": "",
"DevDetail_Copy_Device_Title": "",
"DevDetail_Copy_Device_Tooltip": "",
"DevDetail_EveandAl_AlertAllEvents": "",
"DevDetail_EveandAl_AlertDown": "",
"DevDetail_EveandAl_Archived": "",
"DevDetail_EveandAl_NewDevice": "",
"DevDetail_EveandAl_NewDevice_Tooltip": "",
"DevDetail_EveandAl_RandomMAC": "",
"DevDetail_EveandAl_ScanCycle": "",
"DevDetail_EveandAl_ScanCycle_a": "",
"DevDetail_EveandAl_ScanCycle_z": "",
"DevDetail_EveandAl_Skip": "",
"DevDetail_EveandAl_Title": "",
"DevDetail_Events_CheckBox": "",
"DevDetail_GoToNetworkNode": "",
"DevDetail_Icon": "",
"DevDetail_Icon_Descr": "",
"DevDetail_Loading": "",
"DevDetail_MainInfo_Comments": "",
"DevDetail_MainInfo_Favorite": "",
"DevDetail_MainInfo_Group": "",
"DevDetail_MainInfo_Location": "",
"DevDetail_MainInfo_Name": "",
"DevDetail_MainInfo_Network": "",
"DevDetail_MainInfo_Network_Port": "",
"DevDetail_MainInfo_Network_Title": "",
"DevDetail_MainInfo_Owner": "",
"DevDetail_MainInfo_Title": "",
"DevDetail_MainInfo_Type": "",
"DevDetail_MainInfo_Vendor": "",
"DevDetail_MainInfo_mac": "",
"DevDetail_Network_Node_hover": "",
"DevDetail_Network_Port_hover": "",
"DevDetail_Nmap_Scans": "",
"DevDetail_Nmap_Scans_desc": "",
"DevDetail_Nmap_buttonDefault": "",
"DevDetail_Nmap_buttonDefault_text": "",
"DevDetail_Nmap_buttonDetail": "",
"DevDetail_Nmap_buttonDetail_text": "",
"DevDetail_Nmap_buttonFast": "",
"DevDetail_Nmap_buttonFast_text": "",
"DevDetail_Nmap_buttonSkipDiscovery": "",
"DevDetail_Nmap_buttonSkipDiscovery_text": "",
"DevDetail_Nmap_resultsLink": "",
"DevDetail_Owner_hover": "",
"DevDetail_Periodselect_All": "",
"DevDetail_Periodselect_LastMonth": "",
"DevDetail_Periodselect_LastWeek": "",
"DevDetail_Periodselect_LastYear": "",
"DevDetail_Periodselect_today": "",
"DevDetail_Run_Actions_Title": "",
"DevDetail_Run_Actions_Tooltip": "",
"DevDetail_SessionInfo_FirstSession": "",
"DevDetail_SessionInfo_LastIP": "",
"DevDetail_SessionInfo_LastSession": "",
"DevDetail_SessionInfo_StaticIP": "",
"DevDetail_SessionInfo_Status": "",
"DevDetail_SessionInfo_Title": "",
"DevDetail_SessionTable_Additionalinfo": "",
"DevDetail_SessionTable_Connection": "",
"DevDetail_SessionTable_Disconnection": "",
"DevDetail_SessionTable_Duration": "",
"DevDetail_SessionTable_IP": "",
"DevDetail_SessionTable_Order": "",
"DevDetail_Shortcut_CurrentStatus": "",
"DevDetail_Shortcut_DownAlerts": "",
"DevDetail_Shortcut_Presence": "",
"DevDetail_Shortcut_Sessions": "",
"DevDetail_Tab_Details": "",
"DevDetail_Tab_Events": "",
"DevDetail_Tab_EventsTableDate": "",
"DevDetail_Tab_EventsTableEvent": "",
"DevDetail_Tab_EventsTableIP": "",
"DevDetail_Tab_EventsTableInfo": "",
"DevDetail_Tab_Nmap": "",
"DevDetail_Tab_NmapEmpty": "",
"DevDetail_Tab_NmapTableExtra": "",
"DevDetail_Tab_NmapTableHeader": "",
"DevDetail_Tab_NmapTableIndex": "",
"DevDetail_Tab_NmapTablePort": "",
"DevDetail_Tab_NmapTableService": "",
"DevDetail_Tab_NmapTableState": "",
"DevDetail_Tab_NmapTableText": "",
"DevDetail_Tab_NmapTableTime": "",
"DevDetail_Tab_Plugins": "",
"DevDetail_Tab_Presence": "",
"DevDetail_Tab_Sessions": "",
"DevDetail_Tab_Tools": "",
"DevDetail_Tab_Tools_Internet_Info_Description": "",
"DevDetail_Tab_Tools_Internet_Info_Error": "",
"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_Delete": "",
"DevDetail_button_DeleteEvents": "",
"DevDetail_button_DeleteEvents_Warning": "",
"DevDetail_button_OverwriteIcons": "",
"DevDetail_button_OverwriteIcons_Tooltip": "",
"DevDetail_button_OverwriteIcons_Warning": "",
"DevDetail_button_Reset": "",
"DevDetail_button_Save": "",
"Device_Searchbox": "",
"Device_Shortcut_AllDevices": "",
"Device_Shortcut_Archived": "",
"Device_Shortcut_Connected": "",
"Device_Shortcut_Devices": "",
"Device_Shortcut_DownAlerts": "",
"Device_Shortcut_Favorites": "",
"Device_Shortcut_NewDevices": "",
"Device_Shortcut_OnlineChart": "",
"Device_TableHead_Connected_Devices": "",
"Device_TableHead_Favorite": "",
"Device_TableHead_FirstSession": "",
"Device_TableHead_Group": "",
"Device_TableHead_Icon": "",
"Device_TableHead_LastIP": "",
"Device_TableHead_LastIPOrder": "",
"Device_TableHead_LastSession": "",
"Device_TableHead_Location": "",
"Device_TableHead_MAC": "",
"Device_TableHead_MAC_full": "",
"Device_TableHead_Name": "",
"Device_TableHead_Owner": "",
"Device_TableHead_Parent_MAC": "",
"Device_TableHead_Port": "",
"Device_TableHead_RowID": "",
"Device_TableHead_Rowid": "",
"Device_TableHead_Status": "",
"Device_TableHead_Type": "",
"Device_TableHead_Vendor": "",
"Device_Table_Not_Network_Device": "",
"Device_Table_info": "",
"Device_Table_nav_next": "",
"Device_Table_nav_prev": "",
"Device_Tablelenght": "",
"Device_Tablelenght_all": "",
"Device_Title": "",
"Donations_Others": "",
"Donations_Platforms": "",
"Donations_Text": "",
"Donations_Title": "",
"ENABLE_PLUGINS_description": "",
"ENABLE_PLUGINS_name": "",
"Email_display_name": "",
"Email_icon": "",
"Events_Loading": "",
"Events_Periodselect_All": "",
"Events_Periodselect_LastMonth": "",
"Events_Periodselect_LastWeek": "",
"Events_Periodselect_LastYear": "",
"Events_Periodselect_today": "",
"Events_Searchbox": "",
"Events_Shortcut_AllEvents": "",
"Events_Shortcut_DownAlerts": "",
"Events_Shortcut_Events": "",
"Events_Shortcut_MissSessions": "",
"Events_Shortcut_NewDevices": "",
"Events_Shortcut_Sessions": "",
"Events_Shortcut_VoidSessions": "",
"Events_TableHead_AdditionalInfo": "",
"Events_TableHead_Connection": "",
"Events_TableHead_Date": "",
"Events_TableHead_Device": "",
"Events_TableHead_Disconnection": "",
"Events_TableHead_Duration": "",
"Events_TableHead_DurationOrder": "",
"Events_TableHead_EventType": "",
"Events_TableHead_IP": "",
"Events_TableHead_IPOrder": "",
"Events_TableHead_Order": "",
"Events_TableHead_Owner": "",
"Events_Table_info": "",
"Events_Table_nav_next": "",
"Events_Table_nav_prev": "",
"Events_Tablelenght": "",
"Events_Tablelenght_all": "",
"Events_Title": "",
"Gen_Action": "",
"Gen_AreYouSure": "",
"Gen_Backup": "",
"Gen_Cancel": "",
"Gen_Copy": "",
"Gen_DataUpdatedUITakesTime": "",
"Gen_Delete": "",
"Gen_DeleteAll": "",
"Gen_Error": "",
"Gen_LockedDB": "",
"Gen_Okay": "",
"Gen_Purge": "",
"Gen_ReadDocs": "",
"Gen_Restore": "",
"Gen_Run": "",
"Gen_Save": "",
"Gen_Saved": "",
"Gen_Switch": "",
"Gen_Upd": "",
"Gen_Upd_Fail": "",
"Gen_Warning": "",
"Gen_Work_In_Progress": "",
"General_display_name": "",
"General_icon": "",
"HRS_TO_KEEP_NEWDEV_description": "",
"HRS_TO_KEEP_NEWDEV_name": "",
"HelpFAQ_Cat_Detail": "",
"HelpFAQ_Cat_Detail_300_head": "",
"HelpFAQ_Cat_Detail_300_text_a": "",
"HelpFAQ_Cat_Detail_300_text_b": "",
"HelpFAQ_Cat_Detail_301_head_a": "",
"HelpFAQ_Cat_Detail_301_head_b": "",
"HelpFAQ_Cat_Detail_301_text": "",
"HelpFAQ_Cat_Detail_302_head_a": "",
"HelpFAQ_Cat_Detail_302_head_b": "",
"HelpFAQ_Cat_Detail_302_text": "",
"HelpFAQ_Cat_Detail_303_head": "",
"HelpFAQ_Cat_Detail_303_text": "",
"HelpFAQ_Cat_Device_200_head": "",
"HelpFAQ_Cat_Device_200_text": "",
"HelpFAQ_Cat_General": "",
"HelpFAQ_Cat_General_100_head": "",
"HelpFAQ_Cat_General_100_text_a": "",
"HelpFAQ_Cat_General_100_text_b": "",
"HelpFAQ_Cat_General_100_text_c": "",
"HelpFAQ_Cat_General_101_head": "",
"HelpFAQ_Cat_General_101_text": "",
"HelpFAQ_Cat_General_102_head": "",
"HelpFAQ_Cat_General_102_text": "",
"HelpFAQ_Cat_General_102docker_head": "",
"HelpFAQ_Cat_General_102docker_text": "",
"HelpFAQ_Cat_General_103_head": "",
"HelpFAQ_Cat_General_103_text": "",
"HelpFAQ_Cat_Network_600_head": "",
"HelpFAQ_Cat_Network_600_text": "",
"HelpFAQ_Cat_Network_601_head": "",
"HelpFAQ_Cat_Network_601_text": "",
"HelpFAQ_Cat_Presence_400_head": "",
"HelpFAQ_Cat_Presence_400_text": "",
"HelpFAQ_Cat_Presence_401_head": "",
"HelpFAQ_Cat_Presence_401_text": "",
"HelpFAQ_Title": "",
"LOG_LEVEL_description": "",
"LOG_LEVEL_name": "",
"Loading": "",
"Login_Box": "",
"Login_Default_PWD": "",
"Login_Psw-box": "",
"Login_Psw_alert": "",
"Login_Psw_folder": "",
"Login_Psw_new": "",
"Login_Psw_run": "",
"Login_Remember": "",
"Login_Remember_small": "",
"Login_Submit": "",
"Login_Toggle_Alert_headline": "",
"Login_Toggle_Info": "",
"Login_Toggle_Info_headline": "",
"Maintenance_Running_Version": "",
"Maintenance_Status": "",
"Maintenance_Title": "",
"Maintenance_Tool_ExportCSV": "",
"Maintenance_Tool_ExportCSV_noti": "",
"Maintenance_Tool_ExportCSV_noti_text": "",
"Maintenance_Tool_ExportCSV_text": "",
"Maintenance_Tool_ImportCSV": "",
"Maintenance_Tool_ImportCSV_noti": "",
"Maintenance_Tool_ImportCSV_noti_text": "",
"Maintenance_Tool_ImportCSV_text": "",
"Maintenance_Tool_arpscansw": "",
"Maintenance_Tool_arpscansw_noti": "",
"Maintenance_Tool_arpscansw_noti_text": "",
"Maintenance_Tool_arpscansw_text": "",
"Maintenance_Tool_backup": "",
"Maintenance_Tool_backup_noti": "",
"Maintenance_Tool_backup_noti_text": "",
"Maintenance_Tool_backup_text": "",
"Maintenance_Tool_check_visible": "",
"Maintenance_Tool_darkmode": "",
"Maintenance_Tool_darkmode_noti": "",
"Maintenance_Tool_darkmode_noti_text": "",
"Maintenance_Tool_darkmode_text": "",
"Maintenance_Tool_del_ActHistory": "",
"Maintenance_Tool_del_ActHistory_noti": "",
"Maintenance_Tool_del_ActHistory_noti_text": "",
"Maintenance_Tool_del_ActHistory_text": "",
"Maintenance_Tool_del_alldev": "",
"Maintenance_Tool_del_alldev_noti": "",
"Maintenance_Tool_del_alldev_noti_text": "",
"Maintenance_Tool_del_alldev_text": "",
"Maintenance_Tool_del_allevents": "",
"Maintenance_Tool_del_allevents30": "",
"Maintenance_Tool_del_allevents30_noti": "",
"Maintenance_Tool_del_allevents30_noti_text": "",
"Maintenance_Tool_del_allevents30_text": "",
"Maintenance_Tool_del_allevents_noti": "",
"Maintenance_Tool_del_allevents_noti_text": "",
"Maintenance_Tool_del_allevents_text": "",
"Maintenance_Tool_del_empty_macs": "",
"Maintenance_Tool_del_empty_macs_noti": "",
"Maintenance_Tool_del_empty_macs_noti_text": "",
"Maintenance_Tool_del_empty_macs_text": "",
"Maintenance_Tool_del_unknowndev": "",
"Maintenance_Tool_del_unknowndev_noti": "",
"Maintenance_Tool_del_unknowndev_noti_text": "",
"Maintenance_Tool_del_unknowndev_text": "",
"Maintenance_Tool_displayed_columns_text": "",
"Maintenance_Tool_drag_me": "",
"Maintenance_Tool_order_columns_text": "",
"Maintenance_Tool_purgebackup": "",
"Maintenance_Tool_purgebackup_noti": "",
"Maintenance_Tool_purgebackup_noti_text": "",
"Maintenance_Tool_purgebackup_text": "",
"Maintenance_Tool_restore": "",
"Maintenance_Tool_restore_noti": "",
"Maintenance_Tool_restore_noti_text": "",
"Maintenance_Tool_restore_text": "",
"Maintenance_Tool_upgrade_database_noti": "",
"Maintenance_Tool_upgrade_database_noti_text": "",
"Maintenance_Tool_upgrade_database_text": "",
"Maintenance_Tools_Tab_BackupRestore": "",
"Maintenance_Tools_Tab_Logging": "",
"Maintenance_Tools_Tab_Settings": "",
"Maintenance_Tools_Tab_Tools": "",
"Maintenance_Tools_Tab_UISettings": "",
"Maintenance_arp_status": "",
"Maintenance_arp_status_off": "",
"Maintenance_arp_status_on": "",
"Maintenance_built_on": "",
"Maintenance_current_version": "",
"Maintenance_database_backup": "",
"Maintenance_database_backup_found": "",
"Maintenance_database_backup_total": "",
"Maintenance_database_lastmod": "",
"Maintenance_database_path": "",
"Maintenance_database_rows": "",
"Maintenance_database_size": "",
"Maintenance_lang_de_de": "",
"Maintenance_lang_en_us": "",
"Maintenance_lang_es_es": "",
"Maintenance_lang_selector_apply": "",
"Maintenance_lang_selector_empty": "",
"Maintenance_lang_selector_lable": "",
"Maintenance_lang_selector_text": "",
"Maintenance_new_version": "",
"Maintenance_themeselector_apply": "",
"Maintenance_themeselector_empty": "",
"Maintenance_themeselector_lable": "",
"Maintenance_themeselector_text": "",
"Maintenance_version": "",
"NETWORK_DEVICE_TYPES_description": "",
"NETWORK_DEVICE_TYPES_name": "",
"Navigation_Devices": "",
"Navigation_Donations": "",
"Navigation_Events": "",
"Navigation_HelpFAQ": "",
"Navigation_Maintenance": "",
"Navigation_Network": "",
"Navigation_Plugins": "",
"Navigation_Presence": "",
"Navigation_Report": "",
"Navigation_Settings": "",
"Navigation_SystemInfo": "",
"Navigation_Workflows": "",
"Network_Assign": "",
"Network_Cant_Assign": "",
"Network_Configuration_Error": "",
"Network_Connected": "",
"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_Table_Hostname": "",
"Network_Table_IP": "",
"Network_Table_State": "",
"Network_Title": "",
"Network_UnassignedDevices": "",
"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_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_Loading": "",
"Presence_Shortcut_AllDevices": "",
"Presence_Shortcut_Archived": "",
"Presence_Shortcut_Connected": "",
"Presence_Shortcut_Devices": "",
"Presence_Shortcut_DownAlerts": "",
"Presence_Shortcut_Favorites": "",
"Presence_Shortcut_NewDevices": "",
"Presence_Title": "",
"REPORT_DASHBOARD_URL_description": "",
"REPORT_DASHBOARD_URL_name": "",
"REPORT_ERROR": "",
"REPORT_MAIL_description": "",
"REPORT_MAIL_name": "",
"REPORT_TITLE": "",
"RandomMAC_hover": "",
"SCAN_SUBNETS_description": "",
"SYSTEM_TITLE": "",
"Setting_Override": "",
"Setting_Override_Description": "",
"Settings_Metadata_Toggle": "",
"Settings_device_Scanners_desync": "",
"Settings_device_Scanners_desync_popup": "",
"Speedtest_Results": "",
"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_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": "",
"TIMEZONE_description": "",
"TIMEZONE_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": "",
"devices_old": "",
"general_event_description": "",
"general_event_title": "",
"report_guid": "",
"report_guid_missing": "",
"report_select_format": "",
"report_time": "",
"run_event_icon": "",
"run_event_tooltip": "",
"settings_core_icon": "",
"settings_core_label": "",
"settings_device_scanners": "",
"settings_device_scanners_icon": "",
"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_label": "",
"settings_saved": "",
"settings_system_icon": "",
"settings_system_label": "",
"test_event_icon": "",
"test_event_tooltip": ""
}

View File

@@ -1,47 +1,42 @@
## 📚 Docs for individual plugins
> Community translations of this file (might be out-of-date): <a href="https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/README_ES.md">Spanish(<img src="https://github.com/lipis/flag-icons/blob/main/flags/4x3/es.svg" alt="README_ES.md" style="height: 16px !important;width: 20px !important;padding-inline:3px !important;">)</a>, <a href="https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/README_DE.md">German(<img src="https://github.com/lipis/flag-icons/blob/main/flags/4x3/de.svg" alt="README_DE.md" style="height: 16px !important;width: 20px !important;padding-inline:3px !important;">)</a>
### 🏴 Community translations of this file
# 📚 Docs for individual plugins
> Please note there might be a delay between English and community translations.
>[!NOTE]
> Please check this [Plugins debugging guide](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/DEBUG_PLUGINS.md) and the corresponding Plugin documentation in the below table if you are facing issues.
* <a href="https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/README_ES.md">
<img src="https://github.com/lipis/flag-icons/blob/main/flags/4x3/es.svg" alt="README_ES.md" style="height: 20px !important;width: 20px !important;"> Spanish (Spain)
</a>
## 🔌 Plugins & 📚 Docs
* <a href="https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/README_DE.md">
<img src="https://github.com/lipis/flag-icons/blob/main/flags/4x3/de.svg" alt="README_DE.md" style="height: 20px !important;width: 20px !important;"> German (Germany)
</a>
### 🔌 Plugins & 📚 Docs
| Required | CurrentScan | Unique Prefix | Data source | Type | Link + Docs |
|----------|-------------|---------------|--------------------|----------------|------------------------------------------------------------------|
| | | APPRISE | Script | 💬 publisher | 📚[_publisher_apprise](/front/plugins/_publisher_apprise/) |
| | Yes | ARPSCAN | Script | 🔍dev scanner | 📚[arp_scan](/front/plugins/arp_scan/) |
| | | CSVBCKP | Script | ⚙ system | 📚[csv_backup](/front/plugins/csv_backup/) |
| Yes* | | DBCLNP | Script | ⚙ system | 📚[db_cleanup](/front/plugins/db_cleanup/) |
| | | DDNS | Script | ⚙ system | 📚[ddns_update](/front/plugins/ddns_update/) |
| | Yes | DHCPLSS | Script | 🔍dev scanner | 📚[dhcp_leases](/front/plugins/dhcp_leases/) |
| | | DHCPSRVS | Script | ♻ other | 📚[dhcp_servers](/front/plugins/dhcp_servers/) |
| | Yes | INTRNT | Script | 🔍dev scanner | 📚[internet_ip](/front/plugins/internet_ip/) |
| | | INTRSPD | Script | ♻ other | 📚[internet_speedtest](/front/plugins/internet_speedtest/) |
| | | MAINT | Script | ⚙ system | 📚[maintenance](/front/plugins/maintenance/) |
| | | MQTT | Script | 💬 publisher | 📚[_publisher_mqtt](/front/plugins/_publisher_mqtt/) |
| Yes | | NEWDEV | Template | ⚙ system | 📚[newdev_template](/front/plugins/newdev_template/) |
| | | NMAP | Script | ♻ other | 📚[nmap_scan](/front/plugins/nmap_scan/) |
| | | NTFY | Script | 💬 publisher | 📚[_publisher_ntfy](/front/plugins/_publisher_ntfy/) |
| | | PHOLUS | Script | ♻ other | 📚[pholus_scan](/front/plugins/pholus_scan/) |
| | Yes | PIHOLE | External SQLite DB | 🔍dev scanner | 📚[pihole_scan](/front/plugins/pihole_scan/) |
| | | PUSHSAFER | Script | 💬 publisher | 📚[_publisher_pushsafer](/front/plugins/_publisher_pushsafer/) |
| | | SETPWD | Script | ⚙ system | 📚[set_password](/front/plugins/set_password/) |
| | | SMTP | Script | 💬 publisher | 📚[_publisher_email](/front/plugins/_publisher_email/) |
| | Yes | SNMPDSC | Script | 🔍dev scanner | 📚[snmp_discovery](/front/plugins/snmp_discovery/) |
| | Yes** | UNDIS | Script | ♻ other | 📚[undiscoverables](/front/plugins/undiscoverables/) |
| | Yes | UNFIMP | Script | 🔍dev scanner | 📚[unifi_import](/front/plugins/unifi_import/) |
| | | VNDRPDT | Script | ⚙ system | 📚[vendor_update](/front/plugins/vendor_update/) |
| | | WEBHOOK | Script | 💬 publisher | 📚[_publisher_webhook](/front/plugins/_publisher_webhook/) |
| | | WEBMON | Script | ♻ other | 📚[website_monitor](/front/plugins/website_monitor/) |
| N/A | | N/A | SQL query | | N/A, but the External SQLite DB plugins work similar |
| Required | CurrentScan | Unique Prefix | Data source | Type | Link + Docs |
|----------|-------------|---------------|--------------------|----------------|---------------------------------------------------------------------|
| | | APPRISE | Script | 💬 publisher | 📚[_publisher_apprise](/front/plugins/_publisher_apprise/) |
| | Yes | ARPSCAN | Script | 🔍dev scanner | 📚[arp_scan](/front/plugins/arp_scan/) |
| | | CSVBCKP | Script | ⚙ system | 📚[csv_backup](/front/plugins/csv_backup/) |
| Yes* | | DBCLNP | Script | ⚙ system | 📚[db_cleanup](/front/plugins/db_cleanup/) |
| | | DDNS | Script | ⚙ system | 📚[ddns_update](/front/plugins/ddns_update/) |
| | Yes | DHCPLSS | Script | 🔍dev scanner | 📚[dhcp_leases](/front/plugins/dhcp_leases/) |
| | | DHCPSRVS | Script | ♻ other | 📚[dhcp_servers](/front/plugins/dhcp_servers/) |
| | Yes | INTRNT | Script | 🔍dev scanner | 📚[internet_ip](/front/plugins/internet_ip/) |
| | | INTRSPD | Script | ♻ other | 📚[internet_speedtest](/front/plugins/internet_speedtest/) |
| | | MAINT | Script | ⚙ system | 📚[maintenance](/front/plugins/maintenance/) |
| | | MQTT | Script | 💬 publisher | 📚[_publisher_mqtt](/front/plugins/_publisher_mqtt/) |
| Yes | | NEWDEV | Template | ⚙ system | 📚[newdev_template](/front/plugins/newdev_template/) |
| | | NMAP | Script | ♻ other | 📚[nmap_scan](/front/plugins/nmap_scan/) |
| | | NSLOOKUP | Script | ♻ other | 📚[nslookup_scan](/front/plugins/nslookup_scan/) |
| Yes | | NTFPRCS | Template | ⚙ system | 📚[notification_processing](/front/plugins/notification_processing/)|
| | | NTFY | Script | 💬 publisher | 📚[_publisher_ntfy](/front/plugins/_publisher_ntfy/) |
| | | PHOLUS | Script | ♻ other | 📚[pholus_scan](/front/plugins/pholus_scan/) |
| | Yes | PIHOLE | External SQLite DB | 🔍dev scanner | 📚[pihole_scan](/front/plugins/pihole_scan/) |
| | | PUSHSAFER | Script | 💬 publisher | 📚[_publisher_pushsafer](/front/plugins/_publisher_pushsafer/) |
| | | SETPWD | Script | ⚙ system | 📚[set_password](/front/plugins/set_password/) |
| | | SMTP | Script | 💬 publisher | 📚[_publisher_email](/front/plugins/_publisher_email/) |
| | Yes | SNMPDSC | Script | 🔍dev scanner | 📚[snmp_discovery](/front/plugins/snmp_discovery/) |
| | Yes** | UNDIS | Script | ♻ other | 📚[undiscoverables](/front/plugins/undiscoverables/) |
| | Yes | UNFIMP | Script | 🔍dev scanner | 📚[unifi_import](/front/plugins/unifi_import/) |
| | | VNDRPDT | Script | ⚙ system | 📚[vendor_update](/front/plugins/vendor_update/) |
| | | WEBHOOK | Script | 💬 publisher | 📚[_publisher_webhook](/front/plugins/_publisher_webhook/) |
| | | WEBMON | Script | ♻ other | 📚[website_monitor](/front/plugins/website_monitor/) |
| N/A | | N/A | SQL query | | N/A, but the External SQLite DB plugins work similarly |
> \* The database cleanup plugin (`DBCLNP`) is not _required_ but the app will become unusable after a while if not executed.
@@ -116,7 +111,10 @@ More on specifics below.
### Column order and values
| Order | Represented Column | Required | Description |
> [!IMPORTANT]
> Spend some time reading and trying to understand the below table. This is the interface between the Plugins and the core application.
| Order | Represented Column | Value Required | Description |
|----------------------|----------------------|----------------------|----------------------|
| 0 | `Object_PrimaryID` | yes | The primary ID used to group Events under. |
| 1 | `Object_SecondaryID` | no | Optional secondary ID to create a relationship beween other entities, such as a MAC address |
@@ -133,11 +131,13 @@ More on specifics below.
# config.json structure
The `config.json` file is the manifest of the plugin. It contains mainly settings definitions and the mapping of Plugin objects to PiAlert objects.
## Supported data sources
Currently, these data sources are supported (valid `data_source` value).
| Name | `data_source` value | Needs to return a "table" | Overview (more details on this page below) |
| Name | `data_source` value | Needs to return a "table"* | Overview (more details on this page below) |
|----------------------|----------------------|----------------------|----------------------|
| Script | `script` | no | Executes any linux command in the `CMD` setting. |
| Pialert DB query | `pialert-db-query` | yes | Executes a SQL query on the PiAlert database in the `CMD` setting. |
@@ -145,6 +145,7 @@ Currently, these data sources are supported (valid `data_source` value).
| External SQLite DB query | `sqlite-db-query` | yes | Executes a SQL query from the `CMD` setting on an external SQLite database mapped in the `DB_PATH` setting. |
| Plugin type | `plugin_type` | no | Specifies the type of the plugin and in which section the Plugin settings are displayed (`<general|system|scanner|other|publisher>`). |
> * "Needs to return a "table"" means that the application expects a `last_result.log` file with some results. It's not a blocker, however warnings in the `pialert.log` might be logged.
> 🔎Example
>```json
@@ -161,7 +162,7 @@ You can show or hide the UI on the "Plugins" page and "Plugins" tab for a plugin
### "data_source": "script"
If the `data_source` is set to `script` the `CMD` setting (that you specify in the `settings` array section in the `config.json`) contains an executable Linux command, that usually generates a `last_result.log` file (not required if you don't import any data into the app). This file needs to be stored in the same folder as the plugin.
If the `data_source` is set to `script` the `CMD` setting (that you specify in the `settings` array section in the `config.json`) contains an executable Linux command, that usually generates a `last_result.log` file (not required if you don't import any data into the app). The `last_result.log` file needs to be saved in the same folder as the plugin.
> [!IMPORTANT]
> A lot of the work is taken care of by the [`plugin_helper.py` library](/front/plugins/plugin_helper.py). You don't need to manage the `last_result.log` file if using the helper objects. Check other `script.py` of other plugins for details (The [Undicoverables plugins `script.py` file](/front/plugins/undiscoverables/script.py) is a good example).
@@ -202,7 +203,7 @@ https://www.google.com|null|2023-01-02 15:56:30|200|0.7898|
### "data_source": "pialert-db-query"
If the `data_source` is set to `pialert-db-query` the `CMD` setting needs to contain a SQL query rendering the columns as defined in the "Column order and values" section above. The order of columns is important.
If the `data_source` is set to `pialert-db-query`, the `CMD` setting needs to contain a SQL query rendering the columns as defined in the "Column order and values" section above. The order of columns is important.
This SQL query is executed on the `pialert.db` SQLite database file.
@@ -249,11 +250,13 @@ This SQL query is executed on the `pialert.db` SQLite database file.
### "data_source": "template"
Used to initialize internal settings. Check the `newdev_template` plugin for details.
In most cases, it is used to initialize settings. Check the `newdev_template` plugin for details.
### "data_source": "sqlite-db-query"
You can execute a SQL query on an external database connected to the current PiALert database via a temporary `EXTERNAL_<unique prefix>.` prefix. For example for `PIHOLE` (`"unique_prefix": "PIHOLE"`) it is `EXTERNAL_PIHOLE.`. The external SQLite database file has to be mapped in the container to the path specified in the `DB_PATH` setting:
You can execute a SQL query on an external database connected to the current PiALert database via a temporary `EXTERNAL_<unique prefix>.` prefix.
For example for `PIHOLE` (`"unique_prefix": "PIHOLE"`) it is `EXTERNAL_PIHOLE.`. The external SQLite database file has to be mapped in the container to the path specified in the `DB_PATH` setting:
> 🔎Example
>
@@ -277,7 +280,7 @@ You can execute a SQL query on an external database connected to the current PiA
> ...
>```
The actual SQL query you want to execute is then stored as a `CMD` setting, similar to the `pialert-db-query` plugin type The format has to adhere to the format outlined in the "Column order and values" section above.
The actual SQL query you want to execute is then stored as a `CMD` setting, similar to a Plugin of the `pialert-db-query` plugin type. The format has to adhere to the format outlined in the "Column order and values" section above.
> 🔎Example
>
@@ -303,7 +306,7 @@ The actual SQL query you want to execute is then stored as a `CMD` setting, simi
## 🕳 Filters
Plugin entries can be filtered in the UI based on values entered into filter fields. The `txtMacFilter` textbox/field contains the Mac address of the currently viewed device or simply a Mac address that's available in the `mac` query string.
Plugin entries can be filtered in the UI based on values entered into filter fields. The `txtMacFilter` textbox/field contains the Mac address of the currently viewed device, or simply a Mac address that's available in the `mac` query string (`<url>?mac=aa:22:aa:22:aa:22:aa`).
| Property | Required | Description |
|----------------------|----------------------|----------------------|
@@ -313,7 +316,7 @@ Plugin entries can be filtered in the UI based on values entered into filter fie
| `compare_js_template` | yes | JavaScript code used to convert left and right side of the equation. `{value}` is replaced with input values. |
| `compare_use_quotes` | yes | If `true` then the end result of the `compare_js_template` i swrapped in `"` quotes. Use to compare strings. |
Filters are only applied if a filter is specified and the `txtMacFilter` is not `undefined` or empty (`--`).
Filters are only applied if a filter is specified, and the `txtMacFilter` is not `undefined`, or empty (`--`).
> 🔎Example:
>
@@ -329,7 +332,7 @@ Plugin entries can be filtered in the UI based on values entered into filter fie
> ],
> ```
>
>1. On the `pluginsCore.php` page is an input field with the `txtMacFilter` id:
>1. On the `pluginsCore.php` page is an input field with the id `txtMacFilter`:
>
>```html
><input class="form-control" id="txtMacFilter" type="text" value="--">
@@ -346,7 +349,7 @@ Plugin entries can be filtered in the UI based on values entered into filter fie
>6. This results in for example this code:
>
>```javascript
> // left part of teh expression coming from compare_column and right from the input field
> // left part of the expression coming from compare_column and right from the input field
> // notice the added quotes ()") around the left and right part of teh expression
> "eval('ac:82:ac:82:ac:82".toString()')" == "eval('ac:82:ac:82:ac:82".toString()')"
>```
@@ -355,7 +358,7 @@ Plugin entries can be filtered in the UI based on values entered into filter fie
### 🗺 Mapping the plugin results into a database table
Plugin results are always inserted into the standard `Plugin_Objects` database table. Optionally, PiAlert can take the results of the plugin execution and insert these results into an additional database table. This is enabled by with the property `"mapped_to_table"` in the `config.json` file. The mapping of the columns is defined in the `database_column_definitions` array.
Plugin results are always inserted into the standard `Plugin_Objects` database table. Optionally, PiAlert can take the results of the plugin execution, and insert these results into an additional database table. This is enabled by with the property `"mapped_to_table"` in the `config.json` file. The mapping of the columns is defined in the `database_column_definitions` array.
> [!NOTE]
> If results are mapped to the `CurrentScan` table, the data is then included into the regular scan loop, so for example notification for devices are sent out.
@@ -363,7 +366,7 @@ Plugin results are always inserted into the standard `Plugin_Objects` database t
>🔍 Example:
>
>For example, this approach is used to implement the `DHCPLSS` plugin. The script parses all supplied "dhcp.leases" files, gets the results in the generic table format outlined in the "Column order and values" section above and takes individual values and inserts them into the `CurrentScan` database table in the PiAlert database. All this is achieved by:
>For example, this approach is used to implement the `DHCPLSS` plugin. The script parses all supplied "dhcp.leases" files, gets the results in the generic table format outlined in the "Column order and values" section above, takes individual values, and inserts them into the `CurrentScan` database table in the PiAlert database. All this is achieved by:
>
>1. Specifying the database table into which the results are inserted by defining `"mapped_to_table": "CurrentScan"` in the root of the `config.json` file as shown below:
>
@@ -378,7 +381,7 @@ Plugin results are always inserted into the standard `Plugin_Objects` database t
> ...
>}
>```
>2. Defining the target column with the `mapped_to_column` property for individual columns in the `database_column_definitions` array of the `config.json` file. For example in the `DHCPLSS` plugin, I needed to map the value of the `Object_PrimaryID` column returned by the plugin, to the `cur_MAC` column in the PiAlert database `CurrentScan` table. Notice the `"mapped_to_column": "cur_MAC"` key-value pair in the sample below.
>2. Defining the target column with the `mapped_to_column` property for individual columns in the `database_column_definitions` array of the `config.json` file. For example in the `DHCPLSS` plugin, I needed to map the value of the `Object_PrimaryID` column returned by the plugin, to the `cur_MAC` column in the PiAlert database table `CurrentScan`. Notice the `"mapped_to_column": "cur_MAC"` key-value pair in the sample below.
>
>```json
>{
@@ -397,10 +400,10 @@ Plugin results are always inserted into the standard `Plugin_Objects` database t
> }
>```
>
>3. That's it. PiAlert takes care of the rest. It loops thru the objects discovered by the plugin, takes the results line, by line and inserts them into the database table specified in `"mapped_to_table"`. The columns are translated from the generic plugin columns to the target table via the `"mapped_to_column"` property in the column definitions.
>3. That's it. PiAlert takes care of the rest. It loops thru the objects discovered by the plugin, takes the results line-by-line, and inserts them into the database table specified in `"mapped_to_table"`. The columns are translated from the generic plugin columns to the target table columns via the `"mapped_to_column"` property in the column definitions.
> [!NOTE]
> You can create a column mapping with a default value via the `mapped_to_column_data` property. This means that the value of the given column will always be this value. Taht also menas that the `"column": "NameDoesntMatter"` is not important as there is no database source column.
> You can create a column mapping with a default value via the `mapped_to_column_data` property. This means that the value of the given column will always be this value. That also menas that the `"column": "NameDoesntMatter"` is not important as there is no database source column.
>🔍 Example:
@@ -427,6 +430,17 @@ Plugin results are always inserted into the standard `Plugin_Objects` database t
#### params
> [!IMPORTANT]
> An esier way to access settings in scripts is the `get_setting_value` method.
> ```python
> from helper import get_setting_value
>
> ...
> NTFY_TOPIC = get_setting_value('NTFY_TOPIC')
> ...
>
> ```
The `params` array in the `config.json` is used to enable the user to change the parameters of the executed script. For example, the user wants to monitor a specific URL.
> 🔎 Example:
@@ -565,15 +579,16 @@ You can have any `"function": "my_custom_name"` custom name, however, the ones l
| ------- | ----------- |
| `RUN` | (required) Specifies when the service is executed. |
| | Supported Options: |
| | - "disabled" - not run |
| | - "disabled" - do not run |
| | - "once" - run on app start or on settings saved |
| | - "schedule" - if included, then a `RUN_SCHD` setting needs to be specified to determine the schedule |
| | - "always_after_scan" - run always after a scan is finished |
| | - "before_name_updates" - run before device names are updated (for name discovery plugins) |
| | - "on_new_device" - run when a new device is detected |
| | - "before_config_save" - run before the config is marked as saved. Useful if your plugin needs to modify the `pialert.conf` file. |
| `RUN_SCHD` | (required if you include the above `RUN` function) Cron-like scheduling is used if the `RUN` setting is set to `schedule`. |
| `RUN_SCHD` | (required if you include "schedule" in the above `RUN` function) Cron-like scheduling is used if the `RUN` setting is set to `schedule`. |
| `CMD` | (required) Specifies the command that should be executed. |
| `API_SQL` | (optional) Generates a `table_` + `code_name` + `.json` file as per [API docs](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/API.md). |
| `API_SQL` | (not implemented) Generates a `table_` + `code_name` + `.json` file as per [API docs](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/API.md). |
| `RUN_TIMEOUT` | (optional) Specifies the maximum execution time of the script. If not specified, a default value of 10 seconds is used to prevent hanging. |
| `WATCH` | (optional) Specifies which database columns are watched for changes for this particular plugin. If not specified, no notifications are sent. |
| `REPORT_ON` | (optional) Specifies when to send a notification. Supported options are: |

View File

@@ -554,7 +554,7 @@ You can have any `"function": "my_custom_name"` custom name, however, the ones l
| | - "before_config_save" - run before the config is marked as saved. Useful if your plugin needs to modify the `pialert.conf` file. |
| `RUN_SCHD` | (required if you include the above `RUN` function) Cron-like scheduling is used if the `RUN` setting is set to `schedule`. |
| `CMD` | (required) Specifies the command that should be executed. |
| `API_SQL` | (optional) Generates a `table_` + `code_name` + `.json` file as per [API docs](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/API.md). |
| `API_SQL` | (not implemented) Generates a `table_` + `code_name` + `.json` file as per [API docs](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/API.md). |
| `RUN_TIMEOUT` | (optional) Specifies the maximum execution time of the script. If not specified, a default value of 10 seconds is used to prevent hanging. |
| `WATCH` | (optional) Specifies which database columns are watched for changes for this particular plugin. If not specified, no notifications are sent. |
| `REPORT_ON` | (optional) Specifies when to send a notification. Supported options are: |

View File

@@ -254,7 +254,7 @@
"events": ["test"],
"type": "text.select",
"default_value":"disabled",
"options": ["disabled", "on_notification" ],
"options": ["disabled", "on_notification", "once", "schedule", "always_after_scan", "on_new_device" ],
"localized": ["name", "description"],
"name" :[{
"language_code": "en_us",
@@ -267,7 +267,7 @@
"description": [
{
"language_code": "en_us",
"string" : "Enable sending notifications via <a target=\"_blank\" href=\"https://www.home-assistant.io/integrations/mqtt/\">MQTT</a> to your Home Assistance instance."
"string" : "Enable sending notifications via <a target=\"_blank\" href=\"https://www.home-assistant.io/integrations/mqtt/\">MQTT</a> to your Home Assistance instance. Usually, <code>on_notification</code> is recommended. See the <a target=\"_blank\" href=\"https://github.com/jokob-sk/Pi.Alert/blob/main/docs/HOME_ASSISTANT.md\">PiAlert Home Assistant guide</a> for details."
},
{
"language_code": "es_es",
@@ -298,6 +298,37 @@
"string" : "Comando a ejecutar"
}]
},
{
"function": "RUN_SCHD",
"type": "text",
"default_value":"0 2 * * 3",
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code":"en_us",
"string" : "Schedule"
},
{
"language_code":"es_es",
"string" : "Schedule"
},
{
"language_code":"de_de",
"string" : "Schedule"
}],
"description": [{
"language_code":"en_us",
"string" : "Only enabled if you select <code>schedule</code> in the <a href=\"#MQTT_RUN\"><code>MQTT_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."
},
{
"language_code":"es_es",
"string" : "Solo está habilitado si selecciona <code>schedule</code> en la configuración <a href=\"#MQTT_RUN\"><code>MQTT_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=\"#MQTT_RUN\"><code>MQTT_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."
}]
},
{
"function": "RUN_TIMEOUT",
"type": "integer",
@@ -462,6 +493,40 @@
"language_code": "es_es",
"string" : "Un pequeño truco: retrase la adición a la cola en caso de que el proceso se reinicie y los procesos de publicación anteriores se anulen (se necesitan ~<code>2</code>s para actualizar la configuración de un sensor en el intermediario). Probado con <code>2</code>-<code>3</code> segundos de retraso. Este retraso solo se aplica cuando se crean dispositivos (durante el primer bucle de notificación). No afecta los escaneos o notificaciones posteriores."
}]
},
{
"function": "SEND_STATS",
"type": "boolean",
"default_value":true,
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code":"en_us",
"string" : "Send stats"
}
],
"description": [{
"language_code":"en_us",
"string" : "Check to send overal device stats, such as number of Online and Offline devices."
}
]
},
{
"function": "SEND_DEVICES",
"type": "boolean",
"default_value":true,
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code":"en_us",
"string" : "Send devices"
}
],
"description": [{
"language_code":"en_us",
"string" : "Check to send individual devices to the broker with details, such as <code>is_new</code>, <code>is_present</code>, or <code>mac_address</code> of the devices."
}
]
}
]
}

View File

@@ -132,6 +132,12 @@ class sensor_config:
def publish_mqtt(client, topic, message):
status = 1
mylog('verbose', [f"[{pluginName}] Sending MQTT topic: {topic}"])
mylog('verbose', [f"[{pluginName}] Sending MQTT message: {message}"])
while status != 0:
result = client.publish(
topic=topic,
@@ -251,76 +257,80 @@ def mqtt_start(db):
# General stats
# Create a generic device for overal stats
create_generic_device(client)
if get_setting_value('MQTT_SEND_STATS') == True:
# Create a new device representing overall PiAlert stats
create_generic_device(client)
# Get the data
row = get_device_stats(db)
# Get the data
row = get_device_stats(db)
columns = ["online","down","all","archived","new","unknown"]
columns = ["online","down","all","archived","new","unknown"]
payload = ""
payload = ""
# Update the values
for column in columns:
payload += '"'+column+'": ' + str(row[column]) +','
# Update the values
for column in columns:
payload += '"'+column+'": ' + str(row[column]) +','
# Publish (warap into {} and remove last ',' from above)
publish_mqtt(client, "system-sensors/sensor/pialert/state",
'{ \
'+ payload[:-1] +'\
}'
)
# Publish (wrap into {} and remove last ',' from above)
publish_mqtt(client, "system-sensors/sensor/pialert/state",
'{ \
'+ payload[:-1] +'\
}'
)
# Generate device-specific MQTT messages if enabled
if get_setting_value('MQTT_SEND_DEVICES') == True:
# Specific devices
# Specific devices
# Get all devices
devices = get_all_devices(db)
# Get all devices
devices = get_all_devices(db)
sec_delay = len(devices) * int(get_setting_value('MQTT_DELAY_SEC'))*5
sec_delay = len(devices) * int(get_setting_value('MQTT_DELAY_SEC'))*5
mylog('minimal', [f"[{pluginName}] Estimated delay: ", (sec_delay), 's ', '(', round(sec_delay/60,1) , 'min)' ])
mylog('minimal', [f"[{pluginName}] Estimated delay: ", (sec_delay), 's ', '(', round(sec_delay/60,1) , 'min)' ])
for device in devices:
for device in devices:
# Create devices in Home Assistant - send config messages
deviceId = 'mac_' + device["dev_MAC"].replace(" ", "").replace(":", "_").lower()
deviceNameDisplay = re.sub('[^a-zA-Z0-9-_\s]', '', device["dev_Name"])
# Create devices in Home Assistant - send config messages
deviceId = 'mac_' + device["dev_MAC"].replace(" ", "").replace(":", "_").lower()
deviceNameDisplay = re.sub('[^a-zA-Z0-9-_\s]', '', device["dev_Name"])
create_sensor(client, deviceId, deviceNameDisplay, 'sensor', 'last_ip', 'ip-network', device["dev_MAC"])
create_sensor(client, deviceId, deviceNameDisplay, 'binary_sensor', 'is_present', 'wifi', device["dev_MAC"])
create_sensor(client, deviceId, deviceNameDisplay, 'sensor', 'mac_address', 'folder-key-network', device["dev_MAC"])
create_sensor(client, deviceId, deviceNameDisplay, 'sensor', 'is_new', 'bell-alert-outline', device["dev_MAC"])
create_sensor(client, deviceId, deviceNameDisplay, 'sensor', 'vendor', 'cog', device["dev_MAC"])
# update device sensors in home assistant
create_sensor(client, deviceId, deviceNameDisplay, 'sensor', 'last_ip', 'ip-network', device["dev_MAC"])
create_sensor(client, deviceId, deviceNameDisplay, 'binary_sensor', 'is_present', 'wifi', device["dev_MAC"])
create_sensor(client, deviceId, deviceNameDisplay, 'sensor', 'mac_address', 'folder-key-network', device["dev_MAC"])
create_sensor(client, deviceId, deviceNameDisplay, 'sensor', 'is_new', 'bell-alert-outline', device["dev_MAC"])
create_sensor(client, deviceId, deviceNameDisplay, 'sensor', 'vendor', 'cog', device["dev_MAC"])
# update device sensors in home assistant
publish_mqtt(client, 'system-sensors/sensor/'+deviceId+'/state',
'{ \
"last_ip": "' + device["dev_LastIP"] +'", \
"is_new": "' + str(device["dev_NewDevice"]) +'", \
"vendor": "' + sanitize_string(device["dev_Vendor"]) +'", \
"mac_address": "' + str(device["dev_MAC"]) +'" \
}'
)
publish_mqtt(client, 'system-sensors/sensor/'+deviceId+'/state',
'{ \
"last_ip": "' + device["dev_LastIP"] +'", \
"is_new": "' + str(device["dev_NewDevice"]) +'", \
"vendor": "' + sanitize_string(device["dev_Vendor"]) +'", \
"mac_address": "' + str(device["dev_MAC"]) +'" \
}'
)
publish_mqtt(client, 'system-sensors/binary_sensor/'+deviceId+'/state',
'{ \
"is_present": "' + to_binary_sensor(str(device["dev_PresentLastScan"])) +'"\
}'
)
publish_mqtt(client, 'system-sensors/binary_sensor/'+deviceId+'/state',
'{ \
"is_present": "' + to_binary_sensor(str(device["dev_PresentLastScan"])) +'"\
}'
)
# delete device / topic
# homeassistant/sensor/mac_44_ef_bf_c4_b1_af/is_present/config
# client.publish(
# topic="homeassistant/sensor/"+deviceId+"/is_present/config",
# payload="",
# qos=1,
# retain=True,
# )
# time.sleep(10)
# delete device / topic
# homeassistant/sensor/mac_44_ef_bf_c4_b1_af/is_present/config
# client.publish(
# topic="homeassistant/sensor/"+deviceId+"/is_present/config",
# payload="",
# qos=1,
# retain=True,
# )
# time.sleep(10)
#===============================================================================

View File

@@ -385,6 +385,21 @@
"language_code": "es_es",
"string" : "Ingrese la contraseña si necesita (host) una instancia con autenticación habilitada."
}]
},
{
"function": "PRIORITY",
"type": "text.select",
"default_value":"urgent",
"options": ["urgent", "high", "default" , "low" , "min" ],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "Message priority"
}],
"description": [{
"language_code": "en_us",
"string" : "All NTFY messages have a priority, which defines how urgently your phone notifies you. On Android, you can set custom notification sounds and vibration patterns on your phone to map to these priorities (see <a href=\"https://docs.ntfy.sh/subscribe/phone/\">Android config</a>)."
}]
}
]
}

View File

@@ -88,7 +88,7 @@ def send(html, text):
headers = {
"Title": "Pi.Alert Notification",
"Actions": "view, Open Dashboard, "+ get_setting_value('REPORT_DASHBOARD_URL'),
"Priority": "urgent",
"Priority": get_setting_value('NTFY_PRIORITY'),
"Tags": "warning"
}

View File

@@ -0,0 +1,8 @@
## Overview
A plugin to publish a notification via the Pushover gateway. Enable sending notifications via <a target="_blank" href="https://www.pushover.net/">Pushover</a>.
### Usage
- Go to settings and fill in relevant details.

View File

@@ -0,0 +1,409 @@
{
"code_name": "_publisher_pushover",
"unique_prefix": "PUSHOVER",
"plugin_type": "publisher",
"enabled": true,
"data_source": "script",
"show_ui": true,
"localized": [
"display_name",
"description",
"icon"
],
"display_name": [
{
"language_code": "en_us",
"string": "Pushover publisher"
},
{
"language_code": "es_es",
"string": "Habilitar Pushover"
}
],
"icon": [
{
"language_code": "en_us",
"string": "<i class=\"fa-solid fa-bell\"></i>"
}
],
"description": [
{
"language_code": "en_us",
"string": "A plugin to publish a notification via the pushover.net"
}
],
"params": [],
"database_column_definitions": [
{
"column": "Index",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "N/A"
},
{
"language_code": "es_es",
"string": "N/A"
}
]
},
{
"column": "Plugin",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "N/A"
},
{
"language_code": "es_es",
"string": "N/A"
}
]
},
{
"column": "Object_PrimaryID",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "N/A"
}
]
},
{
"column": "Object_SecondaryID",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Sent when"
}
]
},
{
"column": "Watched_Value1",
"css_classes": "col-sm-3",
"show": true,
"type": "eval",
"default_value": "",
"options": [
{
"type": "eval",
"param": "`<a href='/report.php?guid=${value}'>${value}</a>`"
}
],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Notification GUID"
}
]
},
{
"column": "Watched_Value2",
"css_classes": "col-sm-2",
"show": true,
"type": "textarea_readonly",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Response"
}
]
},
{
"column": "Watched_Value3",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Response code"
}
]
},
{
"column": "Watched_Value4",
"css_classes": "col-sm-2",
"show": false,
"type": "device_mac",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Device"
}
]
},
{
"column": "UserData",
"css_classes": "col-sm-2",
"show": false,
"type": "textbox_save",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Comments"
},
{
"language_code": "es_es",
"string": "Comentarios"
}
]
},
{
"column": "Status",
"css_classes": "col-sm-1",
"show": false,
"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"
},
{
"language_code": "es_es",
"string": "Estado"
}
]
},
{
"column": "Extra",
"css_classes": "col-sm-3",
"show": false,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Extra"
},
{
"language_code": "es_es",
"string": "Extra"
}
]
}
],
"settings": [
{
"function": "RUN",
"events": [
"test"
],
"type": "text.select",
"default_value": "disabled",
"options": [
"disabled",
"on_notification"
],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "When to run"
},
{
"language_code": "es_es",
"string": "Cuando ejecuta"
}
],
"description": [
{
"language_code": "en_us",
"string": "Enable sending notifications via <a target=\"_blank\" href=\"https://www.pushover.net/\">Pushover</a>."
},
{
"language_code": "es_es",
"string": "Habilitar el envío de notificaciones a través de <a target=\"_blank\" href=\"https://www.pushover.net/\">Pushover</a>."
}
]
},
{
"function": "CMD",
"type": "readonly",
"default_value": "python3 /home/pi/pialert/front/plugins/_publisher_pushover/pushover.py",
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Command"
},
{
"language_code": "es_es",
"string": "Comando"
}
],
"description": [
{
"language_code": "en_us",
"string": "Command to run"
},
{
"language_code": "es_es",
"string": "Comando a ejecutar"
}
]
},
{
"function": "RUN_TIMEOUT",
"type": "integer",
"default_value": 10,
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Run timeout"
},
{
"language_code": "es_es",
"string": "Tiempo de espera de ejecución"
},
{
"language_code": "de_de",
"string": "Wartezeit"
}
],
"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": "USER_KEY",
"type": "text",
"default_value": "USER_KEY",
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Pushover USER key"
}
],
"description": [
{
"language_code": "en_us",
"string": "Your secret Pushsafer USER key."
}
]
},
{
"function": "APP_TOKEN",
"type": "text",
"default_value": "APP_TOKEN",
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Pushover APP Token"
}
],
"description": [
{
"language_code": "en_us",
"string": "Your Pushover APP Token."
}
]
}
]
}

View File

@@ -0,0 +1,105 @@
#!/usr/bin/env python3
import os
import pathlib
import sys
import json
import requests
# Replace these paths with the actual paths to your Pi.Alert directories
sys.path.extend(["/home/pi/pialert/front/plugins", "/home/pi/pialert/pialert"])
from plugin_helper import Plugin_Objects, handleEmpty # noqa: E402
from logger import mylog # noqa: E402
from helper import timeNowTZ, get_setting_value, hide_string # noqa: E402
from notification import Notification_obj # noqa: E402
from database import DB # noqa: E402
CUR_PATH = str(pathlib.Path(__file__).parent.resolve())
RESULT_FILE = os.path.join(CUR_PATH, "last_result.log")
pluginName = "PUSHOVER"
def main():
mylog("verbose", f"[{pluginName}](publisher) In script")
# Check if basic config settings supplied
if not validate_config():
mylog(
"none",
f"[{pluginName}] ⚠ ERROR: Publisher notification gateway not set up correctly. "
f"Check your pialert.conf {pluginName}_* variables.",
)
return
# Create a database connection
db = DB() # instance of class DB
db.open()
# Initialize the Plugin obj output file
plugin_objects = Plugin_Objects(RESULT_FILE)
# Create a Notification_obj instance
notifications = Notification_obj(db)
# Retrieve new notifications
new_notifications = notifications.getNew()
# Process the new notifications
for notification in new_notifications:
# Send notification
response_text, response_status_code = send(notification["Text"])
# Log result
plugin_objects.add_object(
primaryId=pluginName,
secondaryId=timeNowTZ(),
watched1=notification["GUID"],
watched2=handleEmpty(response_text),
watched3=response_status_code,
watched4="null",
extra="null",
foreignKey=notification["GUID"],
)
plugin_objects.write_result_file()
def send(text):
response_text = ""
response_status_code = ""
user_key = get_setting_value("PUSHOVER_USER_KEY")
app_token = get_setting_value("PUSHOVER_APP_TOKEN")
mylog("verbose", f'[{pluginName}] PUSHOVER_USER_KEY: "{hide_string(user_key)}"')
mylog("verbose", f'[{pluginName}] PUSHOVER_APP_TOKEN: "{hide_string(app_token)}"')
try:
response = requests.post(
"https://api.pushover.net/1/messages.json",
data={"token": app_token, "user": user_key, "message": text},
)
# Check if the request was successful (status code 200)
if response.status_code == 200:
response_text = response.text # This captures the response body/message
else:
response_text = json.dumps(response.text)
except requests.exceptions.RequestException as e:
mylog("none", f"[{pluginName}] ⚠ ERROR: {e}")
response_text = str(e)
return response_text, response_status_code
def validate_config():
user_key = get_setting_value("PUSHOVER_USER_KEY")
app_token = get_setting_value("PUSHOVER_APP_TOKEN")
return user_key != "USER_KEY" and app_token != "APP_TOKEN"
if __name__ == "__main__":
sys.exit(main())

View File

@@ -12,3 +12,9 @@ Arp-scan is a command-line tool that uses the ARP protocol to discover and finge
- SAVE
- Wait for the next scan to finish
#### Examples
Settings:
![settings](/front/plugins/arp_scan/arp-scan-settings.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

View File

@@ -106,7 +106,7 @@
"description": [
{
"language_code": "en_us",
"string": "Specify when your Network-discovery scan will run. Typical setting would be <code>schedule</code> and then you specify a cron-like schedule in the <a href=\"#ARPSCAN_RUN_SCHD\"><code>ARPSCAN_RUN_SCHD</code>setting</a> "
"string": "Specify when your Network-discovery scan will run. Typical setting would be <code>schedule</code> and then you specify a cron-like schedule in the <a href=\"#ARPSCAN_RUN_SCHD\"><code>ARPSCAN_RUN_SCHD</code>setting</a>. ⚠ Use the same schedule if you have multiple <i class=\"fa-solid fa-magnifying-glass-plus\"></i> Device scanners enabled."
},
{
"language_code": "es_es",

View File

@@ -16,4 +16,4 @@ Plugin generating CSV backups of your Devices database table, including the netw
### Usage
- If the devices.csv file can be overwritten or the date and time timestamp added to the name. This is toggled with the `CSVBCKP_overwrite` setting.
- The `devices.csv` file can be overwritten or the date and time timestamp added to the name. This is toggled with the `CSVBCKP_overwrite` setting.

View File

@@ -18,7 +18,7 @@ sys.path.append('/home/pi/pialert/pialert')
from plugin_helper import Plugin_Object, Plugin_Objects, decodeBase64
from logger import mylog, append_line_to_file
from helper import timeNowTZ
from const import logPath, pialertPath
from const import logPath, pialertPath, fullDbPath
CUR_PATH = str(pathlib.Path(__file__).parent.resolve())
@@ -43,7 +43,7 @@ def main():
mylog('verbose', ['[CSVBCKP] In script'])
# Connect to the PiAlert SQLite database
conn = sqlite3.connect('/home/pi/pialert/db/pialert.db')
conn = sqlite3.connect(fullDbPath)
cursor = conn.cursor()
# Execute your SQL query

View File

@@ -172,6 +172,25 @@
"string": "Maximale Zeit in Sekunden, die auf den Abschluss des Skripts gewartet werden soll. Bei Überschreitung dieser Zeit wird das Skript abgebrochen."
}
]
},
{
"function": "NOTIFI_HIST",
"type": "integer",
"default_value": 100,
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Notifications History"
}
],
"description": [
{
"language_code": "en_us",
"string": "How many historical entries of Notifications should be kept. This influences how many entries are also available in the Report section in the UI"
}
]
}
],

View File

@@ -18,13 +18,15 @@ sys.path.append('/home/pi/pialert/pialert')
from plugin_helper import Plugin_Object, Plugin_Objects, decodeBase64
from logger import mylog, append_line_to_file
from helper import timeNowTZ, get_setting_value
from const import logPath, pialertPath
from const import logPath, pialertPath, fullDbPath
CUR_PATH = str(pathlib.Path(__file__).parent.resolve())
LOG_FILE = os.path.join(CUR_PATH, 'script.log')
RESULT_FILE = os.path.join(CUR_PATH, 'last_result.log')
pluginName = 'DBCLNP'
def main():
parser = argparse.ArgumentParser(description='DB cleanup tasks')
@@ -40,13 +42,13 @@ def main():
DAYS_TO_KEEP_EVENTS = int(values.daystokeepevents.split('=')[1])
PHOLUS_DAYS_DATA = int(values.pholuskeepdays.split('=')[1])
mylog('verbose', ['[DBCLNP] In script'])
mylog('verbose', [f'[{pluginName}] In script'])
# Execute cleanup/upkeep
cleanup_database('/home/pi/pialert/db/pialert.db', DAYS_TO_KEEP_EVENTS, PHOLUS_DAYS_DATA, HRS_TO_KEEP_NEWDEV, PLUGINS_KEEP_HIST)
cleanup_database(fullDbPath, DAYS_TO_KEEP_EVENTS, PHOLUS_DAYS_DATA, HRS_TO_KEEP_NEWDEV, PLUGINS_KEEP_HIST)
mylog('verbose', ['[DBCLNP] Cleanup complete file '])
mylog('verbose', [f'[{pluginName}] Cleanup complete'])
return 0
@@ -58,25 +60,28 @@ def cleanup_database (dbPath, DAYS_TO_KEEP_EVENTS, PHOLUS_DAYS_DATA, HRS_TO_KEEP
Cleaning out old records from the tables that don't need to keep all data.
"""
mylog('verbose', ['[DBCLNP] Upkeep Database:' ])
mylog('verbose', [f'[{pluginName}] Upkeep Database:' ])
# Connect to the PiAlert SQLite database
conn = sqlite3.connect(dbPath)
cursor = conn.cursor()
# -----------------------------------------------------
# Cleanup Online History
mylog('verbose', ['[DBCLNP] Online_History: Delete all but keep latest 150 entries'])
mylog('verbose', [f'[{pluginName}] Online_History: Delete all but keep latest 150 entries'])
cursor.execute ("""DELETE from Online_History where "Index" not in (
SELECT "Index" from Online_History
order by Scan_Date desc limit 150)""")
mylog('verbose', ['[DBCLNP] Optimize Database'])
# -----------------------------------------------------
# Cleanup Events
mylog('verbose', [f'[DBCLNP] Events: Delete all older than {str(DAYS_TO_KEEP_EVENTS)} days (DAYS_TO_KEEP_EVENTS setting)'])
mylog('verbose', [f'[{pluginName}] Events: Delete all older than {str(DAYS_TO_KEEP_EVENTS)} days (DAYS_TO_KEEP_EVENTS setting)'])
cursor.execute (f"""DELETE FROM Events
WHERE eve_DateTime <= date('now', '-{str(DAYS_TO_KEEP_EVENTS)} day')""")
# -----------------------------------------------------
# Trim Plugins_History entries to less than PLUGINS_KEEP_HIST setting per unique "Plugin" column entry
mylog('verbose', [f'[DBCLNP] Plugins_History: Trim Plugins_History entries to less than {str(PLUGINS_KEEP_HIST)} per Plugin (PLUGINS_KEEP_HIST setting)'])
mylog('verbose', [f'[{pluginName}] Plugins_History: Trim Plugins_History entries to less than {str(PLUGINS_KEEP_HIST)} per Plugin (PLUGINS_KEEP_HIST setting)'])
# Build the SQL query to delete entries that exceed the limit per unique "Plugin" column entry
delete_query = f"""DELETE FROM Plugins_History
@@ -92,12 +97,12 @@ def cleanup_database (dbPath, DAYS_TO_KEEP_EVENTS, PHOLUS_DAYS_DATA, HRS_TO_KEEP
cursor.execute(delete_query)
# -----------------------------------------------------
# Trim Notifications entries to less than DBCLNP_NOTIFI_HIST setting
histCount = get_setting_value('DBCLNP_NOTIFI_HIST')
mylog('verbose', [f'[DBCLNP] Plugins_History: Trim Notifications entries to less than {histCount}'])
mylog('verbose', [f'[{pluginName}] Plugins_History: Trim Notifications entries to less than {histCount}'])
# Build the SQL query to delete entries
delete_query = f"""DELETE FROM Notifications
@@ -113,22 +118,61 @@ def cleanup_database (dbPath, DAYS_TO_KEEP_EVENTS, PHOLUS_DAYS_DATA, HRS_TO_KEEP
cursor.execute(delete_query)
# Cleanup Pholus_Scan
if PHOLUS_DAYS_DATA != 0:
mylog('verbose', ['[DBCLNP] Pholus_Scan: Delete all older than ' + str(PHOLUS_DAYS_DATA) + ' days (PHOLUS_DAYS_DATA setting)'])
# todo: improvement possibility: keep at least N per mac
cursor.execute (f"""DELETE FROM Pholus_Scan
WHERE Time <= date('now', '-{str(PHOLUS_DAYS_DATA)} day')""")
# -----------------------------------------------------
# Trim Workflow entries to less than WORKFLOWS_AppEvents_hist setting
histCount = get_setting_value('WORKFLOWS_AppEvents_hist')
mylog('verbose', [f'[{pluginName}] Trim AppEvents to less than {histCount}'])
# Build the SQL query to delete entries
delete_query = f"""DELETE FROM AppEvents
WHERE "Index" NOT IN (
SELECT "Index"
FROM (
SELECT "Index",
ROW_NUMBER() OVER(PARTITION BY "AppEvents" ORDER BY DateTimeCreated DESC) AS row_num
FROM AppEvents
) AS ranked_objects
WHERE row_num <= {histCount}
);"""
cursor.execute(delete_query)
# -----------------------------------------------------
# Cleanup New Devices
if HRS_TO_KEEP_NEWDEV != 0:
mylog('verbose', [f'[DBCLNP] Devices: Delete all New Devices older than {str(HRS_TO_KEEP_NEWDEV)} hours (HRS_TO_KEEP_NEWDEV setting)'])
mylog('verbose', [f'[{pluginName}] Devices: Delete all New Devices older than {str(HRS_TO_KEEP_NEWDEV)} hours (HRS_TO_KEEP_NEWDEV setting)'])
cursor.execute (f"""DELETE FROM Devices
WHERE dev_NewDevice = 1 AND dev_FirstConnection < date('now', '+{str(HRS_TO_KEEP_NEWDEV)} hour')""")
# -----------------------------------------------------
# Cleanup Pholus_Scan
if PHOLUS_DAYS_DATA != 0:
mylog('verbose', [f'[{pluginName}] Pholus_Scan: Delete all older than ' + str(PHOLUS_DAYS_DATA) + ' days (PHOLUS_DAYS_DATA setting)'])
# todo: improvement possibility: keep at least N per mac
cursor.execute (f"""DELETE FROM Pholus_Scan
WHERE Time <= date('now', '-{str(PHOLUS_DAYS_DATA)} day')""")
# -----------------------------------------------------
# De-Dupe (de-duplicate - remove duplicate entries) from the Pholus_Scan table
mylog('verbose', [f'[{pluginName}] Pholus_Scan: Delete all duplicates'])
cursor.execute ("""DELETE FROM Pholus_Scan
WHERE rowid > (
SELECT MIN(rowid) FROM Pholus_Scan p2
WHERE Pholus_Scan.MAC = p2.MAC
AND Pholus_Scan.Value = p2.Value
AND Pholus_Scan.Record_Type = p2.Record_Type
);""")
# -----------------------------------------------------
# De-dupe (de-duplicate) from the Plugins_Objects table
# TODO This shouldn't be necessary - probably a concurrency bug somewhere in the code :(
mylog('verbose', ['[DBCLNP] Plugins_Objects: Delete all duplicates'])
mylog('verbose', [f'[{pluginName}] Plugins_Objects: Delete all duplicates'])
cursor.execute("""
DELETE FROM Plugins_Objects
WHERE rowid > (
@@ -140,20 +184,11 @@ def cleanup_database (dbPath, DAYS_TO_KEEP_EVENTS, PHOLUS_DAYS_DATA, HRS_TO_KEEP
)
""")
# De-Dupe (de-duplicate - remove duplicate entries) from the Pholus_Scan table
mylog('verbose', ['[DBCLNP] Pholus_Scan: Delete all duplicates'])
cursor.execute ("""DELETE FROM Pholus_Scan
WHERE rowid > (
SELECT MIN(rowid) FROM Pholus_Scan p2
WHERE Pholus_Scan.MAC = p2.MAC
AND Pholus_Scan.Value = p2.Value
AND Pholus_Scan.Record_Type = p2.Record_Type
);""")
conn.commit()
# Shrink DB
mylog('verbose', ['[DBCLNP] Shrink Database'])
mylog('verbose', [f'[{pluginName}] Shrink Database'])
cursor.execute ("VACUUM;")
# Close the database connection

View File

@@ -478,7 +478,7 @@
"description": [
{
"language_code": "en_us",
"string": "Enable import of devices from <code>dhcp.leases</code> files. If you select <code>schedule</code> the scheduling settings from below are applied. If you select <code>once</code> the scan is run only once on start of the application (container) or after you update your settings."
"string": "Enable import of devices from <code>dhcp.leases</code> files. If you select <code>schedule</code> the scheduling settings from below are applied. If you select <code>once</code> the scan is run only once on start of the application (container) or after you update your settings. ⚠ Use the same schedule if you have multiple <i class=\"fa-solid fa-magnifying-glass-plus\"></i> Device scanners enabled."
},
{
"language_code": "es_es",

View File

@@ -290,7 +290,7 @@
}],
"description": [{
"language_code":"en_us",
"string" : "Enable a regular scan of rogue DHCP servers. If you select <code>schedule</code> the scheduling settings from below are applied. If you select <code>once</code> the scan is run only once on start of the application (container) or after you update your settings."
"string" : "Enable a regular scan of rogue DHCP servers. If you select <code>schedule</code> the scheduling settings from below are applied. If you select <code>once</code> the scan is run only once on start of the application (container) or after you update your settings. ⚠ Use the same schedule if you have multiple <i class=\"fa-solid fa-magnifying-glass-plus\"></i> Device scanners enabled."
},
{
"language_code":"es_es",

View File

@@ -57,9 +57,9 @@
"value": "SELECT dev_LastIP FROM Devices WHERE dev_MAC = 'Internet' "
},
{
"name": "DIG_GET_IP_ARG",
"name": "INTRNT_DIG_GET_IP_ARG",
"type": "setting",
"value": "DIG_GET_IP_ARG",
"value": "INTRNT_DIG_GET_IP_ARG",
"base64": true
}
],
@@ -109,7 +109,7 @@
{
"function": "CMD",
"type": "readonly",
"default_value": "python3 /home/pi/pialert/front/plugins/internet_ip/script.py prev_ip={prev_ip} DIG_GET_IP_ARG={DIG_GET_IP_ARG}",
"default_value": "python3 /home/pi/pialert/front/plugins/internet_ip/script.py prev_ip={prev_ip} INTRNT_DIG_GET_IP_ARG={INTRNT_DIG_GET_IP_ARG}",
"options": [],
"localized": [
"name",
@@ -144,6 +144,44 @@
}
]
},
{
"function": "DIG_GET_IP_ARG",
"type": "text",
"default_value": "-4 myip.opendns.com @resolver1.opendns.com",
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Internet IP discovery"
},
{
"language_code": "es_es",
"string": "Descubrir de IP de Internet"
},
{
"language_code": "de_de",
"string": "Erkennung externer IP (\"Internet IP\")"
}
],
"description": [
{
"language_code": "en_us",
"string": "Change the <a href=\"https://linux.die.net/man/1/dig\" target=\"_blank\">dig utility</a> arguments if you have issues resolving your Internet IP. Arguments are added at the end of the following command: <code>dig +short </code>."
},
{
"language_code": "es_es",
"string": "Cambie los argumentos de la <a href=\"https://linux.die.net/man/1/dig\" target=\"_blank\">utilidad de dig</a> si tiene problemas para resolver su IP de Internet. Los argumentos se agregan al final del siguiente comando: <code>dig +short </code>."
},
{
"language_code": "de_de",
"string": "Ändere die Argumente des <a href=\"https://linux.die.net/man/1/dig\" target=\"_blank\">dig Dienstprogramms</a>, wenn Probleme beim Auflösen der externen IP auftreten. Argumente werden an das Ende des folgenden Befehls angehängt: <code>dig +short </code>."
}
]
},
{
"function": "RUN_SCHD",
"type": "text",

View File

@@ -20,7 +20,7 @@ sys.path.append('/home/pi/pialert/pialert')
from plugin_helper import Plugin_Object, Plugin_Objects, decodeBase64
from logger import mylog, append_line_to_file
from helper import timeNowTZ, check_IP_format
from helper import timeNowTZ, check_IP_format, get_setting_value
from const import logPath, pialertPath, fullDbPath
@@ -37,19 +37,19 @@ def main():
parser = argparse.ArgumentParser(description='Check internet connectivity and IP')
parser.add_argument('prev_ip', action="store", help="Previous IP address to compare against the current IP")
parser.add_argument('DIG_GET_IP_ARG', action="store", help="Arguments for the 'dig' command to retrieve the IP address")
parser.add_argument('DIG_GET_IP_ARG', action="store", help="Arguments for the 'dig' command to retrieve the IP address") # unused
values = parser.parse_args()
PREV_IP = values.prev_ip.split('=')[1]
DIG_GET_IP_ARG = values.DIG_GET_IP_ARG.split('=b')[1] # byte64 encoded
DIG_GET_IP_ARG = get_setting_value("INTRNT_DIG_GET_IP_ARG")
mylog('verbose', [f'[{pluginName}] DIG_GET_IP_ARG: ', DIG_GET_IP_ARG])
mylog('verbose', [f'[{pluginName}] INTRNT_DIG_GET_IP_ARG: ', DIG_GET_IP_ARG])
# Decode the base64-encoded value to get the actual value in ASCII format.
DIG_GET_IP_ARG = base64.b64decode(DIG_GET_IP_ARG).decode('ascii')
# DIG_GET_IP_ARG = base64.b64decode(DIG_GET_IP_ARG).decode('ascii')
mylog('verbose', [f'[{pluginName}] DIG_GET_IP_ARG resolved: {DIG_GET_IP_ARG} '])
# mylog('verbose', [f'[{pluginName}] DIG_GET_IP_ARG resolved: {DIG_GET_IP_ARG} '])
# perform the new IP lookup
new_internet_IP, cmd_output = check_internet_IP( PREV_IP, DIG_GET_IP_ARG)

View File

@@ -1,5 +1,5 @@
{
"code_name": "Devices.new",
"code_name": "newdev_template",
"template_type": "database-entry",
"unique_prefix": "NEWDEV",
"plugin_type": "system",
@@ -22,7 +22,47 @@
}
],
"settings":[
{
{
"function": "ignored_MACs",
"type": "list",
"default_value": [
],
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Ignored MACs"
}
],
"description": [
{
"language_code": "en_us",
"string": "List of MACs to ignore. Use <code>%</code> as a wildcard. Ignored devices will not be shown anywhere in the UI or notifications. <br/><br/>For example <code>02:42:ac:%</code> to filter out docker containers."
}
]
},
{
"function": "ignored_IPs",
"type": "list",
"default_value": [
],
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Ignored IPs"
}
],
"description": [
{
"language_code": "en_us",
"string": "List of IPs to ignore. Use <code>%</code> as a wildcard. Ignored devices will not be shown anywhere in the UI or notifications. <br/><br/>For example <code>192.168.3.%</code> to filter out a subnet."
}
]
},
{
"function": "dev_MAC",
"type": "readonly",
"maxLength": 50,
@@ -395,7 +435,7 @@
{
"function": "dev_NewDevice",
"type": "integer.checkbox",
"default_value": true,
"default_value": 1,
"options": [],
"localized": ["name", "description"],
"name": [

View File

@@ -0,0 +1,7 @@
## Overview
Plugin supplying settings for Notification Processing.
### Usage
- Check the Settings page for details.

View File

@@ -0,0 +1,129 @@
{
"code_name": "notification_processing",
"unique_prefix": "NTFPRCS",
"plugin_type": "system",
"enabled": true,
"data_source": "script",
"show_ui": false,
"localized": ["display_name", "description", "icon"],
"display_name": [
{
"language_code": "en_us",
"string": "Notification Processing"
}
],
"icon": [
{
"language_code": "en_us",
"string": "<i class=\"fa-solid fa-envelopes-bulk\"></i>"
}
],
"description": [
{
"language_code": "en_us",
"string": "A plugin to for advanced notification processing."
}
],
"params" : [
],
"settings": [
{
"function": "INCLUDED_SECTIONS",
"type": "text.multiselect",
"default_value": ["new_devices", "down_devices", "events"],
"options": ["new_devices", "down_devices", "events", "plugins"],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Notify on"
},
{
"language_code": "de_de",
"string": "Benachrichtigungen"
},
{
"language_code": "es_es",
"string": "Notificar en"
}
],
"description": [
{
"language_code": "en_us",
"string": "Specifies which events trigger notifications. Remove the event type(s) you do not want to get notified on. This setting overrides device-specific settings in the UI. (<code>CTRL + Click</code> to select/deselect)."
},
{
"language_code": "de_de",
"string": "Spezifiziert, bei welchen Events Benachrichtigungen versendet werden. Entfernen Sie die Eventtypen, bei welchen Sie nicht benachrichtigt werden wollen. Diese Einstellung überschreibt gerätespezifische Einstellungen im UI. (<code>STRG + klicken</code> zum aus-/abwählen)."
},
{
"language_code": "es_es",
"string": "Especifica que eventos envían notificaciones. Elimina los tipos de eventos de los que no quieras recibir notificaciones. Este ajuste sobreescribe los ajustes específicos de los dispositivos en la interfaz. (<code>CTRL + Clic</code> para seleccionar / deseleccionar)."
}
]
},
{
"function": "alert_down_time",
"type": "integer",
"default_value": 5,
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Alert Down After"
}
],
"description": [
{
"language_code": "en_us",
"string": "After how many minutes a device is reported as down and a notification is sent."
}
]
},
{
"function": "new_dev_condition",
"type": "text",
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "New Devices Filter"
}
],
"description": [
{
"language_code": "en_us",
"string": "You can specify a SQL where condition to filter out New Devices from notifications. For example <code>AND dev_LastIP NOT LIKE '192.168.3.%'</code> will always exlude New Device notifications for all devices with the IP starting with <code>192.168.3.%</code>."
}
]
},
{
"function": "event_condition",
"type": "text",
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Events Filter"
}
],
"description": [
{
"language_code": "en_us",
"string": "You can specify a SQL where condition to filter out Events from notifications. For example <code>AND dev_LastIP NOT LIKE '192.168.3.%'</code> will always exlude New Device notifications for all devices with the IP starting with <code>192.168.3.%</code>."
}
]
}
],
"database_column_definitions":
[
]
}

View File

@@ -0,0 +1,7 @@
## Overview
Plugin for device name discovery via the [nslookup](https://linux.die.net/man/1/nslookup) network utility.
### Usage
- Check the Settings page for details.

View File

@@ -0,0 +1,321 @@
{
"code_name": "nslookup_scan",
"unique_prefix": "NSLOOKUP",
"plugin_type": "other",
"enabled": true,
"data_source": "script",
"show_ui": true,
"localized": ["display_name", "description", "icon"],
"display_name": [
{
"language_code": "en_us",
"string": "NSLOOKUP (Name discovery)"
}
],
"icon": [
{
"language_code": "en_us",
"string": "<i class=\"fa-solid fa-search\"></i>"
}
],
"description": [
{
"language_code": "en_us",
"string": "A plugin to discover device names."
}
],
"params" : [
{
"name" : "ips",
"type" : "sql",
"value" : "SELECT dev_LastIP from DEVICES order by dev_MAC",
"timeoutMultiplier" : true
}
],
"settings": [
{
"function": "RUN",
"events": ["run"],
"type": "text.select",
"default_value":"before_name_updates",
"options": ["disabled", "before_name_updates", "on_new_device", "once", "schedule", "always_after_scan"],
"localized": ["name", "description"],
"name" :[{
"language_code":"en_us",
"string" : "When to run"
},
{
"language_code":"es_es",
"string" : "Cuándo ejecutar"
},
{
"language_code":"de_de",
"string" : "Wann laufen"
}],
"description": [{
"language_code":"en_us",
"string" : "When the plugin should be executed. If enabled this will execute the scan until there are no <code>(unknown)</code> or <code>(name not found)</code> devices. Setting this to <code>on_new_device</code> or a daily <code>schedule</code> is recommended."
}]
},
{
"function": "CMD",
"type": "readonly",
"default_value": "python3 /home/pi/pialert/front/plugins/nslookup_scan/nslookup.py",
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Command"
},
{
"language_code": "es_es",
"string": "Comando"
},
{
"language_code": "de_de",
"string": "Befehl"
}
],
"description": [
{
"language_code": "en_us",
"string": "Command to run. This can not be changed"
},
{
"language_code": "es_es",
"string": "Comando a ejecutar. Esto no se puede cambiar"
},
{
"language_code": "de_de",
"string": "Befehl zum Ausführen. Dies kann nicht geändert werden"
}
]
},
{
"function": "RUN_SCHD",
"type": "text",
"default_value":"*/30 * * * *",
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code":"en_us",
"string" : "Schedule"
},
{
"language_code":"es_es",
"string" : "Schedule"
},
{
"language_code":"de_de",
"string" : "Schedule"
}],
"description": [{
"language_code":"en_us",
"string" : "Only enabled if you select <code>schedule</code> in the <a href=\"#NSLOOKUP_RUN\"><code>NSLOOKUP_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."
},
{
"language_code":"es_es",
"string" : "Solo está habilitado si selecciona <code>schedule</code> en la configuración <a href=\"#NSLOOKUP_RUN\"><code>NSLOOKUP_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=\"#NSLOOKUP_RUN\"><code>NSLOOKUP_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."
}]
},
{
"function": "RUN_TIMEOUT",
"type": "integer",
"default_value": 10,
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Run timeout"
},
{
"language_code": "es_es",
"string": "Tiempo límite de ejecución"
},
{
"language_code": "de_de",
"string": "Zeitüberschreitung"
}
],
"description": [
{
"language_code": "en_us",
"string": "Maximum time in seconds to wait for the script to finish. If this time is exceeded the script is aborted."
},
{
"language_code": "es_es",
"string": "Tiempo máximo en segundos para esperar a que finalice el script. Si se supera este tiempo, el script se cancela."
},
{
"language_code": "de_de",
"string": "Maximale Zeit in Sekunden, die auf den Abschluss des Skripts gewartet werden soll. Bei Überschreitung dieser Zeit wird das Skript abgebrochen."
}
]
}
],
"database_column_definitions":
[
{
"column": "Object_PrimaryID",
"css_classes": "col-sm-2",
"show": true,
"type": "device_name_mac",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "MAC"
},
{
"language_code": "es_es",
"string": "MAC"
}
]
},
{
"column": "Object_SecondaryID",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "IP"
},
{
"language_code": "es_es",
"string": "IP"
}
]
},
{
"column": "Watched_Value1",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Server"
}
]
},
{
"column": "Watched_Value2",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Name"
}
]
},
{
"column": "DateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Created"
},
{
"language_code": "es_es",
"string": "Creado"
}
]
},
{
"column": "DateTimeChanged",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Changed"
},
{
"language_code": "es_es",
"string": "Cambiado"
}
]
},
{
"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"
},
{
"language_code": "es_es",
"string": "Estado"
}
]
}
]
}

View File

@@ -0,0 +1,139 @@
#!/usr/bin/env python
# test script by running:
# tbc
import os
import pathlib
import argparse
import subprocess
import sys
import hashlib
import csv
import sqlite3
import re
from io import StringIO
from datetime import datetime
sys.path.append("/home/pi/pialert/front/plugins")
sys.path.append('/home/pi/pialert/pialert')
from plugin_helper import Plugin_Object, Plugin_Objects, decodeBase64
from logger import mylog, append_line_to_file
from helper import timeNowTZ, get_setting_value
from const import logPath, pialertPath, fullDbPath
from database import DB
from device import Device_obj
CUR_PATH = str(pathlib.Path(__file__).parent.resolve())
LOG_FILE = os.path.join(CUR_PATH, 'script.log')
RESULT_FILE = os.path.join(CUR_PATH, 'last_result.log')
pluginName = 'NSLOOKUP'
def main():
mylog('verbose', [f'[{pluginName}] In script'])
timeout = get_setting_value('NSLOOKUP_RUN_TIMEOUT')
# Create a database connection
db = DB() # instance of class DB
db.open()
# Initialize the Plugin obj output file
plugin_objects = Plugin_Objects(RESULT_FILE)
# Create a Device_obj instance
device_handler = Device_obj(db)
# Retrieve devices
unknown_devices = device_handler.getUnknown()
mylog('verbose', [f'[{pluginName}] Unknown devices count: {len(unknown_devices)}'])
for device in unknown_devices:
domain_name, dns_server = execute_nslookup(device['dev_LastIP'], timeout)
if domain_name != '':
plugin_objects.add_object(
# "MAC", "IP", "Server", "Name"
primaryId = device['dev_MAC'],
secondaryId = device['dev_LastIP'],
watched1 = dns_server,
watched2 = domain_name,
watched3 = '',
watched4 = '',
extra = '',
foreignKey = device['dev_MAC'])
plugin_objects.write_result_file()
mylog('verbose', [f'[{pluginName}] Script finished'])
return 0
#===============================================================================
# Execute scan
#===============================================================================
def execute_nslookup (ip, timeout):
"""
Execute the NSLOOKUP command on IP.
"""
nslookup_args = ['nslookup', ip]
# Execute command
output = ""
try:
# try runnning a subprocess with a forced (timeout) in case the subprocess hangs
output = subprocess.check_output (nslookup_args, universal_newlines=True, stderr=subprocess.STDOUT, timeout=(timeout), text=True)
domain_name = ''
dns_server = ''
mylog('verbose', [f'[{pluginName}] DEBUG OUTPUT : {output}'])
# Parse output using case-insensitive regular expressions
domain_pattern = re.compile(r'name\s*=\s*([^\s]+)', re.IGNORECASE)
server_pattern = re.compile(r'Server:\s+(.+)', re.IGNORECASE)
domain_match = domain_pattern.search(output)
server_match = server_pattern.search(output)
if domain_match:
domain_name = domain_match.group(1)
mylog('verbose', [f'[{pluginName}] Domain Name: {domain_name}'])
if server_match:
dns_server = server_match.group(1)
mylog('verbose', [f'[{pluginName}] DNS Server: {dns_server}'])
return domain_name, dns_server
except subprocess.CalledProcessError as e:
# An error occured, handle it
mylog('verbose', [f'[{pluginName}]', e.output])
mylog('verbose', [f'[{pluginName}] ⚠ ERROR - check logs'])
except subprocess.TimeoutExpired as timeErr:
mylog('verbose', [f'[{pluginName}] TIMEOUT - the process forcefully terminated as timeout reached'])
if output == "": # check if the subprocess failed
mylog('verbose', [f'[{pluginName}] Scan: FAIL - check logs'])
else:
mylog('verbose', [f'[{pluginName}] Scan: SUCCESS'])
return '', ''
#===============================================================================
# BEGIN
#===============================================================================
if __name__ == '__main__':
main()

View File

@@ -4,7 +4,7 @@ A plugin to resolve `(unknown)` device names. It uses the [Pholus](https://githu
### Usage
- Go to settings and find Pholus-Scan (Name discovery) in the list of settings.
- Go to settings and find Pholus (Name discovery) in the list of settings.
- Enable the plugin by changing the `RUN` parameter from disabled to one you prefer (`schedule`, `always_after_scan`, `on_new_device`).
- Specify the `PHOLUS_RUN_TIMEOUT` (Will be divided by the number of subnets specified in the Arp-Scan (Network scan) plugin setting `SCAN_SUBNETS`)
- SAVE

View File

@@ -23,11 +23,11 @@
"display_name": [
{
"language_code": "en_us",
"string": "Pholus-Scan (Name discovery)"
"string": "Pholus (Name discovery)"
},
{
"language_code": "es_es",
"string": "Pholus-Scan (Descubrimiento de nombre)"
"string": "Pholus (Descubrimiento de nombre)"
}
],
"icon": [
@@ -43,7 +43,7 @@
"description": [
{
"language_code": "en_us",
"string": "This plugin is to execute a Pholus-scan (name discovery) on the local network"
"string": "This plugin is to execute a Pholus (name discovery) on the local network"
},
{
"language_code": "es_es",

View File

@@ -23,6 +23,7 @@ LOG_FILE = os.path.join(CUR_PATH, 'script.log')
RESULT_FILE = os.path.join(CUR_PATH, 'last_result.log')
fullPholusPath = os.path.join(CUR_PATH, 'pholus/pholus3.py')
pluginName = 'PHOLUS'
def main():
# sample
@@ -40,13 +41,13 @@ def main():
plugin_objects = Plugin_Objects(RESULT_FILE)
# Print a message to indicate that the script is starting.
mylog('verbose',['[PHOLUS] In script'])
mylog('verbose',[f'[{pluginName}] In script'])
# Assuming 'values' is a dictionary or object that contains a key 'userSubnets'
# which holds a list of user-submitted subnets.
# Printing the userSubnets list to check its content.
mylog('verbose',['[PHOLUS] Subnets: ', values.userSubnets])
mylog('verbose',['[PHOLUS] len Subnets: ', len(values.userSubnets)])
mylog('verbose',[f'[{pluginName}] Subnets: ', values.userSubnets])
mylog('verbose',[f'[{pluginName}] len Subnets: ', len(values.userSubnets)])
# Extract the base64-encoded subnet information from the first element of the userSubnets list.
# The format of the element is assumed to be like 'userSubnets=b<base64-encoded-data>'.
@@ -54,14 +55,14 @@ def main():
timeoutSec = values.timeoutSec[0].split('=')[1]
# Printing the extracted base64-encoded subnet information.
mylog('verbose', [f'[PHOLUS] { userSubnetsParamBase64 }'])
mylog('verbose', [f'[PHOLUS] { timeoutSec }'])
mylog('verbose', [f'[{pluginName}] { userSubnetsParamBase64 }'])
mylog('verbose', [f'[{pluginName}] { timeoutSec }'])
# Decode the base64-encoded subnet information to get the actual subnet information in ASCII format.
userSubnetsParam = base64.b64decode(userSubnetsParamBase64).decode('ascii')
# Print the decoded subnet information.
mylog('verbose', [f'[PHOLUS] userSubnetsParam { userSubnetsParam } '])
mylog('verbose', [f'[{pluginName}] userSubnetsParam { userSubnetsParam } '])
# Check if the decoded subnet information contains multiple subnets separated by commas.
# If it does, split the string into a list of individual subnets.
@@ -99,16 +100,15 @@ def execute_pholus_scan(userSubnets, timeoutSec):
timeoutPerSubnet = float(timeoutSec) / len(userSubnets)
mylog('verbose', [f'[PHOLUS] { timeoutPerSubnet } '])
mylog('verbose', [f'[{pluginName}] { timeoutPerSubnet } '])
# scan each interface
# scan each interface
for interface in userSubnets:
temp = interface.split("--interface=")
if len(temp) != 2:
mylog('none', ["[PHOLUS] Skip scan (need interface in format '192.168.1.0/24 --inteface=eth0'), got: ", interface])
mylog('verbose', [f'[{pluginName}] Skip scan (need interface in format "192.168.1.0/24 --inteface=eth0"), got: ', interface])
return
mask = temp[0].strip()
@@ -116,14 +116,14 @@ def execute_pholus_scan(userSubnets, timeoutSec):
pholus_output_list = execute_pholus_on_interface (interface, timeoutPerSubnet, mask)
mylog('verbose', [f'[PHOLUS] { pholus_output_list } '])
mylog('verbose', [f'[{pluginName}] { pholus_output_list } '])
result_list += pholus_output_list
mylog('verbose', ["[PHOLUS] Pholus output number of entries:", len(result_list)])
mylog('verbose', ["[PHOLUS] List:", result_list])
mylog('verbose', [f'[{pluginName}] Pholus output number of entries:', len(result_list)])
mylog('verbose', [f'[{pluginName}] List:', result_list])
return result_list
@@ -132,8 +132,8 @@ def execute_pholus_on_interface(interface, timeoutSec, mask):
# logging & updating app state
mylog('verbose', ['[PHOLUS] Scan: Pholus for ', str(timeoutSec), 's ('+ str(round(int(timeoutSec) / 60, 1)) +'min)'])
mylog('verbose', ["[PHOLUS] Pholus scan on [interface] ", interface, " [mask] " , mask])
mylog('verbose', [f'[{pluginName}] Scan: Pholus for ', str(timeoutSec), 's ('+ str(round(int(timeoutSec) / 60, 1)) +'min)'])
mylog('verbose', [f'[{pluginName}] Pholus scan on [interface] ', interface, ' [mask] ' , mask])
# the scan always lasts 2x as long, so the desired user time from settings needs to be halved
adjustedTimeout = str(round(int(timeoutSec) / 2, 0))
@@ -149,15 +149,15 @@ def execute_pholus_on_interface(interface, timeoutSec, mask):
output = subprocess.check_output (pholus_args, universal_newlines=True, stderr=subprocess.STDOUT, timeout=(timeoutSec + 30))
except subprocess.CalledProcessError as e:
# An error occured, handle it
mylog('none', ['[PHOLUS]', e.output])
mylog('none', ["[PHOLUS] ⚠ ERROR - Pholus Scan - check logs"])
mylog('verbose', [f'[{pluginName}]', e.output])
mylog('verbose', [f'[{pluginName}] ⚠ ERROR - Pholus Scan - check logs'])
except subprocess.TimeoutExpired as timeErr:
mylog('none', ['[PHOLUS] Pholus TIMEOUT - the process forcefully terminated as timeout reached'])
mylog('verbose', [f'[{pluginName}] Pholus TIMEOUT - the process forcefully terminated as timeout reached'])
if output == "": # check if the subprocess failed
mylog('none', ['[PHOLUS] Scan: Pholus FAIL - check logs'])
mylog('verbose', [f'[{pluginName}] Scan: Pholus FAIL - check logs'])
else:
mylog('verbose', ['[PHOLUS] Scan: Pholus SUCCESS'])
mylog('verbose', [f'[{pluginName}] Scan: Pholus SUCCESS'])
# check the last run output
f = open(logPath + '/pialert_pholus_lastrun.log', 'r+')

View File

@@ -73,7 +73,7 @@
}],
"description": [{
"language_code":"en_us",
"string" : "Specify when your PiHole device import from the PiHole database will run. The typical setting would be <code>schedule</code> and then you specify a cron-like schedule in the <a href=\"#PIHOLE_RUN_SCHD\"><code>PIHOLE_RUN_SCHD</code>setting</a>. If enabled, you must map the pihole db into your container to the <code>:/etc/pihole/pihole-FTL.db</code> mount path as specified in the <code>DB_PATH</code> setting."
"string" : "Specify when your PiHole device import from the PiHole database will run. The typical setting would be <code>schedule</code> and then you specify a cron-like schedule in the <a href=\"#PIHOLE_RUN_SCHD\"><code>PIHOLE_RUN_SCHD</code>setting</a>. If enabled, you must map the pihole db into your container to the <code>:/etc/pihole/pihole-FTL.db</code> mount path as specified in the <code>DB_PATH</code> setting. ⚠ Use the same schedule if you have multiple <i class=\"fa-solid fa-magnifying-glass-plus\"></i> Device scanners enabled."
},
{
"language_code":"es_es",
@@ -83,7 +83,7 @@
{
"function": "CMD",
"type": "text",
"default_value":"SELECT n.hwaddr AS Object_PrimaryID, {s-quote}null{s-quote} AS Object_SecondaryID, datetime() AS DateTime, na.ip AS Watched_Value1, n.lastQuery AS Watched_Value2, na.name AS Watched_Value3, n.macVendor AS Watched_Value4, {s-quote}null{s-quote} AS Extra, n.hwaddr AS ForeignKey FROM EXTERNAL_PIHOLE.Network AS n LEFT JOIN EXTERNAL_PIHOLE.Network_Addresses AS na ON na.network_id = n.id WHERE n.hwaddr NOT LIKE {s-quote}ip-%{s-quote} AND n.hwaddr <> {s-quote}00:00:00:00:00:00{s-quote} AND na.ip <> null;",
"default_value":"SELECT n.hwaddr AS Object_PrimaryID, {s-quote}null{s-quote} AS Object_SecondaryID, datetime() AS DateTime, na.ip AS Watched_Value1, n.lastQuery AS Watched_Value2, na.name AS Watched_Value3, n.macVendor AS Watched_Value4, {s-quote}null{s-quote} AS Extra, n.hwaddr AS ForeignKey FROM EXTERNAL_PIHOLE.Network AS n LEFT JOIN EXTERNAL_PIHOLE.Network_Addresses AS na ON na.network_id = n.id WHERE n.hwaddr NOT LIKE {s-quote}ip-%{s-quote} AND n.hwaddr is not {s-quote}00:00:00:00:00:00{s-quote} AND na.ip is not null",
"options": [],
"localized": ["name", "description"],
"name" : [{

View File

@@ -43,7 +43,7 @@
"settings":[
{
"function": "RUN",
"events": ["run"],
"events": [],
"type": "text.select",
"default_value":"disabled",
"options": ["disabled", "before_config_save"],
@@ -58,7 +58,7 @@
}],
"description": [{
"language_code":"en_us",
"string" : "Set to <code>before_config_save</code> and specify password to reset your pasword in <code>SETPWD_password</code>. You can set to <code>disabled</code> once the password is changed."
"string" : "Set to <code>before_config_save</code> and specify password to reset your pasword in <code>SETPWD_password</code>."
},
{
"language_code":"es_es",
@@ -67,7 +67,7 @@
},
{
"function": "CMD",
"type": "text",
"type": "readonly",
"default_value":"/home/pi/pialert/back/pialert-cli set_password {password}",
"options": [],
"localized": ["name", "description"],
@@ -108,7 +108,7 @@
"description": [
{
"language_code": "en_us",
"string": "The default password is <code>123456</code>. To change the password run <code>/home/pi/pialert/back/pialert-cli set_password {password}</code> in the container"
"string": "The default password is <code>123456</code>. To change it, you can either use this plugin (follow the instructions in the <code>SETPWD_RUN</code> setting) or run <code>/home/pi/pialert/back/pialert-cli set_password {password}</code> in the container."
},
{
"language_code": "es_es",

View File

@@ -322,7 +322,7 @@
}],
"description": [{
"language_code":"en_us",
"string" : "Enable import of devices from a SNMP enabled device. If you select <code>schedule</code> the scheduling settings from below are applied. If you select <code>once</code> the scan is run only once on start of the application (container) or after you update your settings."
"string" : "Enable import of devices from a SNMP enabled device. If you select <code>schedule</code> the scheduling settings from below are applied. If you select <code>once</code> the scan is run only once on start of the application (container) or after you update your settings. ⚠ Use the same schedule if you have multiple <i class=\"fa-solid fa-magnifying-glass-plus\"></i> Device scanners enabled."
},
{
"language_code":"es_es",

View File

@@ -264,6 +264,7 @@
},
{
"column": "Watched_Value2",
"mapped_to_column": "cur_Vendor",
"css_classes": "col-sm-2",
"default_value": "",
"localized": [
@@ -479,7 +480,7 @@
"description": [
{
"language_code": "en_us",
"string": "Enable import of devices from a UNIFI controller. If you select <code>schedule</code> the scheduling settings from below are applied. If you select <code>once</code> the scan is run only once on start of the application (container) or after you update your settings."
"string": "Enable import of devices from a UNIFI controller. If you select <code>schedule</code> the scheduling settings from below are applied. If you select <code>once</code> the scan is run only once on start of the application (container) or after you update your settings. ⚠ Use the same schedule if you have multiple <i class=\"fa-solid fa-magnifying-glass-plus\"></i> Device scanners enabled."
},
{
"language_code": "es_es",

View File

@@ -19,7 +19,7 @@ sys.path.append('/home/pi/pialert/pialert')
from plugin_helper import Plugin_Object, Plugin_Objects, decodeBase64, handleEmpty
from logger import mylog, append_line_to_file
from helper import timeNowTZ
from const import logPath, pialertPath
from const import logPath, pialertPath, fullDbPath
from device import query_MAC_vendor
@@ -37,7 +37,7 @@ def main():
# Resolve missing vendors
plugin_objects = Plugin_Objects(RESULT_FILE)
plugin_objects = update_vendors('/home/pi/pialert/db/pialert.db', plugin_objects)
plugin_objects = update_vendors(fullDbPath, plugin_objects)
plugin_objects.write_result_file()
@@ -60,8 +60,8 @@ def update_vendor_database():
update_output = subprocess.check_output (update_args)
except subprocess.CalledProcessError as e:
# An error occured, handle it
mylog('none', [' FAILED: Updating vendors DB, set LOG_LEVEL=debug for more info'])
mylog('none', [e.output])
mylog('verbose', [' FAILED: Updating vendors DB, set LOG_LEVEL=debug for more info'])
mylog('verbose', [e.output])
# ------------------------------------------------------------------------------
# resolve missing vendors

View File

@@ -0,0 +1,7 @@
## Overview
TBC
### Usage
- Check the Settings page for details.

View File

@@ -0,0 +1,57 @@
{
"code_name": "workflows",
"unique_prefix": "WORKFLOWS",
"plugin_type": "system",
"enabled": true,
"data_source": "script",
"show_ui": false,
"localized": ["display_name", "description", "icon"],
"display_name": [
{
"language_code": "en_us",
"string": "Workflows"
}
],
"icon": [
{
"language_code": "en_us",
"string": "<i class=\"fa-solid fa-shuffle\"></i>"
}
],
"description": [
{
"language_code": "en_us",
"string": "A plugin to adjust behavior of workflows."
}
],
"params" : [
],
"settings": [
{
"function": "AppEvents_hist",
"type": "integer",
"default_value": 5000,
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "App Events History"
}
],
"description": [
{
"language_code": "en_us",
"string": "How many historical entries of Application Events should be kept. This influences how many entries are also available in the Workflows section in the UI."
}
]
}
],
"database_column_definitions":
[
]
}

View File

@@ -123,7 +123,7 @@ function processColumnValue(dbColumnDef, value, index, type) {
<span>`;
break;
case 'device_name_mac':
value = `<span class="anonymizeMac"><a href="/deviceDetails.php?mac=${value}" target="_blank">${getNameByMacAddress(value)}</a><span>`;
value = createDeviceLink(value);
break;
case 'device_mac':
value = `<span class="anonymizeMac"><a href="/deviceDetails.php?mac=${value}" target="_blank">${value}</a><span>`;
@@ -465,49 +465,33 @@ function generateTabs()
// --------------------------------------------------------
// Handle active / selected tabs
// handle first tab (objectsTarget_) display
function initTabs()
{
// events on tab change
function initTabs() {
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
var target = $(e.target).attr("href") // activated tab
// save the last prefix
if(target.includes('_') == false )
{
pref = target.split('#')[1]
} else
{
pref = target.split('_')[1]
}
var target = $(e.target).attr("href").split('_').pop();
everythingHidden = false;
var pref = target.includes('_') ? target.split('_')[1] : target.split('#')[1];
var everythingHidden = true;
if($('#objectsTarget_'+ pref) != undefined && $('#historyTarget_'+ pref) != undefined && $('#eventsTarget_'+ pref) != undefined)
{
if ($('#objectsTarget_' + pref) && $('#historyTarget_' + pref) && $('#eventsTarget_' + pref)) {
var isObjectsInactive = !$('#objectsTarget_' + pref).hasClass('active');
var isHistoryInactive = !$('#historyTarget_' + pref).hasClass('active');
var isEventsInactive = !$('#eventsTarget_' + pref).hasClass('active');
var everythingHidden = isObjectsInactive && isHistoryInactive && isEventsInactive;
everythingHidden = isObjectsInactive && isHistoryInactive && isEventsInactive;
}
// show the objectsTarget if no specific pane selected or if selected is hidden
if (target === '#' + pref && everythingHidden) {
var objectsTarget = $('#objectsTarget_' + pref);
if (objectsTarget.length > 0) {
var classTmp = objectsTarget.attr('class');
if (!classTmp.includes('active')) {
classTmp += ' active';
objectsTarget.attr('class', classTmp);
}
}
if (objectsTarget.length && !objectsTarget.hasClass('active')) {
objectsTarget.addClass('active');
}
}
});
}
// --------------------------------------------------------
// Filter method that determines if an entry should be shown
function shouldBeShown(entry, pluginObj)

View File

@@ -245,21 +245,11 @@ while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
`)
}
// Start constructing the main settings HTML
let pluginHtml = `
<div class="row table_row">
<div class="table_cell bold">
<i class="fa-regular fa-book fa-sm"></i>
<a href="https://github.com/jokob-sk/Pi.Alert/tree/main/front/plugins" target="_blank">
${getString('Gen_ReadDocs')}
</a>
</div>
</div>
`;
let isIn = ' in '; // to open the active panel in AdminLTE
for (const group of settingGroups) {
for (const group of settingGroups) {
// enabled / disabled icons
enabledHtml = ''
@@ -277,7 +267,20 @@ while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
`
}
headerHtml = `<div class="box box-solid box-primary panel panel-default">
// Start constructing the main settings HTML
let pluginHtml = `
<div class="row table_row">
<div class="table_cell bold">
<i class="fa-regular fa-book fa-sm"></i>
<a href="https://github.com/jokob-sk/Pi.Alert/tree/main/front/plugins/${getPluginCodeName(pluginsData, group)}" target="_blank">
${getString('Gen_ReadDocs')}
</a>
</div>
</div>
`;
// Plugin HEADER
headerHtml = `<div class="box box-solid box-primary panel panel-default" id="${group}_header">
<a data-toggle="collapse" data-parent="#accordion_gen" href="#${group}">
<div class="panel-heading">
<h4 class="panel-title">
@@ -568,40 +571,6 @@ while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
}
// ---------------------------------------------------------
// Generate an array object from a string representation of an array
function createArray(input) {
// Empty array
if (input === '[]') {
return [];
}
// Regex patterns
const patternBrackets = /(^\s*\[)|(\]\s*$)/g;
const patternQuotes = /(^\s*')|('\s*$)/g;
const replacement = '';
// Remove brackets
const noBrackets = input.replace(patternBrackets, replacement);
const options = [];
// Create array
const optionsTmp = noBrackets.split(',');
// Handle only one item in array
if (optionsTmp.length === 0) {
return [noBrackets.replace(patternQuotes, replacement)];
}
// Remove quotes
optionsTmp.forEach(item => {
options.push(item.replace(patternQuotes, replacement).trim());
});
return options;
}
// number of settings has to be equal to
// display the name of the first person
@@ -787,37 +756,6 @@ while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
}
// -----------------------------------------------------------------------------
function toggleAllSettings()
{
inStr = ' in';
allOpen = true;
openIcon = 'fa-angle-double-down';
closeIcon = 'fa-angle-double-up';
$('.panel-collapse').each(function(){
if($(this).attr('class').indexOf(inStr) == -1)
{
allOpen = false;
}
})
if(allOpen)
{
// close all
$('div[data-myid="collapsible"]').each(function(){$(this).attr('class', 'panel-collapse collapse ')})
$('#toggleSettings').attr('class', $('#toggleSettings').attr('class').replace(closeIcon, openIcon))
}
else{
// open all
$('div[data-myid="collapsible"]').each(function(){$(this).attr('class', 'panel-collapse collapse in')})
$('div[data-myid="collapsible"]').each(function(){$(this).attr('style', 'height:inherit')})
$('#toggleSettings').attr('class', $('#toggleSettings').attr('class').replace(openIcon, closeIcon))
}
}
getData()

View File

@@ -13,14 +13,14 @@
<section class="content-header">
<h1 id="pageTitle">
<i class="fa fa-fw fa-plug"></i> <?= lang('Navigation_Flows');?>
<i class="fa fa-fw fa-plug"></i> <?= lang('Navigation_Workflows');?>
<span class="pageHelp"> <a target="_blank" href="https://github.com/jokob-sk/Pi.Alert/tree/main/front/plugins"><i class="fa fa-circle-question"></i></a><span>
</h1>
</section>
<?php
// require 'pluginsCore.php';
require 'appEventsCore.php';
?>

View File

@@ -1,5 +1,8 @@
#!/usr/bin/env bash
# 🛑 Important: This is only used for the bare-metal install 🛑
# Update /dockerfiles/start.sh in most cases is preferred
echo "---------------------------------------------------------"
echo "[INSTALL] Run install.sh"
echo "---------------------------------------------------------"

View File

@@ -7,18 +7,22 @@ The original pilaert.py code is now moved to this new folder and split into diff
|```__main__.py```| The MAIN program of Pi.Alert|
|```__init__.py```| an empty init file|
|```README.md```| this readme file|
|**publishers**| a folder containing all modules used to publish the results|
|```api.py```| updating the API endpoints with the relevant data. (Should move to publishers)|
|```../front/plugins ```| a folder containing all [plugins](/front/plugins/) that publish notifications or scan for devices|
|```api.py```| updating the API endpoints with the relevant data. |
|```appevent.py```| TBC |
|```const.py```| A place to define the constants for Pi.Alert like log path or config path.|
|```conf.py```| conf.py holds the configuration variables and makes them available for all modules. It is also the <b>workaround</b> for global variables that need to be resolved at some point|
|```database.py```| This module connects to the DB, makes sure the DB is up to date and defines some standard queries and interfaces. |
|```device.py```| The device module looks after the devices and saves the scan results into the devices |
|```flows.py```| TBC |
|```helper.py```| Helper as the name suggest contains multiple little functions and methods used in many of the other modules and helps keep things clean |
|```initialise.py```| Initiatlise sets up the environment and makes everything ready to go |
|```logger.py```| Logger is there the keep all the logs organised and looking identical. |
|```networscan.py```| Networkscan orchestrates the actual scanning of the network, calling the individual scanners and managing the results |
|```networscan.py```| Networkscan collects the scan results (maybe to merge with `reporting.py`) |
|```notification.py```| Creates and handles the notification object and generates ther HTML and text variants of the message |
|```plugin.py```| This is where the plugins get integrated into the backend of Pi.Alert |
|```reporting.py```| Reporting generates the email, html and json reports to be sent by the publishers |
|```plugin_utils.py```| Helper utilities for `plugin.py` |
|```reporting.py```| Reporting collects the data for the notification reports |
|```scheduler.py```| All things scheduling |

View File

@@ -32,6 +32,7 @@ from database import DB
from reporting import get_notifications
from notification import Notification_obj
from plugin import run_plugin_scripts, check_and_run_user_event
from device import update_devices_names
#===============================================================================
@@ -135,8 +136,15 @@ def main ():
pluginsState.processScan = False
process_scan(db)
# --------
# Reporting
# run plugins before notification processing (e.g. Plugins to discover device names)
pluginsState = run_plugin_scripts(db, 'before_name_updates', pluginsState)
# Resolve devices names
mylog('debug','[Main] Resolve devices names')
update_devices_names(db)
# Check if new devices found
sql.execute (sql_new_devices)
newDevices = sql.fetchall()
@@ -151,32 +159,19 @@ def main ():
# ----------------------------------------
# send all configured notifications
notiStructure = get_notifications(db)
final_json = get_notifications(db)
# Write the notifications into the DB
notification = Notification_obj(db)
notificationObj = notification.create(notiStructure.json, notiStructure.text, notiStructure.html, "")
notificationObj = notification.create(final_json, "")
# run all enabled publisher gateways
if notificationObj.HasNotifications:
pluginsState = run_plugin_scripts(db, 'on_notification', pluginsState)
notification.setAllProcessed()
notification.clearPendingEmailFlag()
# Clean Pending Alert Events
sql.execute ("""UPDATE Devices SET dev_LastNotification = ?
WHERE dev_MAC IN (
SELECT eve_MAC FROM Events
WHERE eve_PendingAlertEmail = 1
)
""", (timeNowTZ(),) )
sql.execute ("""UPDATE Events SET eve_PendingAlertEmail = 0
WHERE eve_PendingAlertEmail = 1""")
# clear plugin events
sql.execute ("DELETE FROM Plugins_Events")
# DEBUG - print number of rows updated
mylog('minimal', ['[Notification] Notifications changes: ', sql.rowcount])
else:
mylog('verbose', ['[Notification] No changes to report'])
@@ -187,8 +182,10 @@ def main ():
updateState("Process: Wait")
mylog('verbose', ['[MAIN] Process: Wait'])
else:
# do something
mylog('verbose', ['[MAIN] waiting to start next loop'])
# do something
# mylog('verbose', ['[MAIN] Waiting to start next loop'])
dummyVariable = 1
#loop
time.sleep(5) # wait for N seconds

View File

@@ -3,7 +3,7 @@ import json
# pialert modules
import conf
from const import (apiPath, sql_devices_all, sql_events_pending_alert, sql_settings, sql_plugins_events, sql_plugins_history, sql_plugins_objects,sql_language_strings, sql_notifications_all)
from const import (apiPath, sql_appevents, sql_devices_all, sql_events_pending_alert, sql_settings, sql_plugins_events, sql_plugins_history, sql_plugins_objects,sql_language_strings, sql_notifications_all)
from logger import mylog
from helper import write_file
@@ -23,6 +23,7 @@ def update_api(db, isNotification = False, updateOnlyDataSources = []):
# prepare database tables we want to expose
dataSourcesSQLs = [
["appevents", sql_appevents],
["devices", sql_devices_all],
["events_pending_alert", sql_events_pending_alert],
["settings", sql_settings],

View File

@@ -16,6 +16,19 @@ class AppEvent_obj:
def __init__(self, db):
self.db = db
# drop table
self.db.sql.execute("""DROP TABLE IF EXISTS "AppEvents" """)
# Drop all triggers
self.db.sql.execute('DROP TRIGGER IF EXISTS trg_create_device;')
self.db.sql.execute('DROP TRIGGER IF EXISTS trg_read_device;')
self.db.sql.execute('DROP TRIGGER IF EXISTS trg_update_device;')
self.db.sql.execute('DROP TRIGGER IF EXISTS trg_delete_device;')
self.db.sql.execute('DROP TRIGGER IF EXISTS trg_delete_plugin_object;')
self.db.sql.execute('DROP TRIGGER IF EXISTS trg_create_plugin_object;')
self.db.sql.execute('DROP TRIGGER IF EXISTS trg_update_plugin_object;')
# Create AppEvent table if missing
self.db.sql.execute("""CREATE TABLE IF NOT EXISTS "AppEvents" (
"Index" INTEGER,
@@ -24,23 +37,222 @@ class AppEvent_obj:
"ObjectType" TEXT, -- ObjectType (Plugins, Notifications, Events)
"ObjectGUID" TEXT,
"ObjectPlugin" TEXT,
"ObjectMAC" TEXT,
"ObjectIP" TEXT,
"ObjectPrimaryID" TEXT,
"ObjectSecondaryID" TEXT,
"ObjectForeignKey" TEXT,
"ObjectIndex" TEXT,
"ObjectRowID" TEXT,
"ObjectIndex" TEXT,
"ObjectIsNew" BOOLEAN,
"ObjectIsArchived" BOOLEAN,
"ObjectStatusColumn" TEXT, -- Status (Notifications, Plugins), eve_EventType (Events)
"ObjectStatus" TEXT, -- new_devices, down_devices, events, new, watched-changed, watched-not-changed, missing-in-last-scan, Device down, New Device, IP Changed, Connected, Disconnected, VOIDED - Disconnected, VOIDED - Connected, <missing event>
"AppEventStatus" TEXT, -- TBD "new", "used", "cleanup-next"
"Extra" TEXT,
"ObjectStatus" TEXT, -- new_devices, down_devices, events, new, watched-changed, watched-not-changed, missing-in-last-scan, Device down, New Device, IP Changed, Connected, Disconnected, VOIDED - Disconnected, VOIDED - Connected, <missing event>
"AppEventType" TEXT, -- "create", "update", "delete" (+TBD)
"Helper1" TEXT,
"Helper2" TEXT,
"Helper3" TEXT,
"Extra" TEXT,
PRIMARY KEY("Index" AUTOINCREMENT)
);
""")
# Generate a GUID
sql_generateGuid = '''
lower(
hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-' || '4' ||
substr(hex( randomblob(2)), 2) || '-' ||
substr('AB89', 1 + (abs(random()) % 4) , 1) ||
substr(hex(randomblob(2)), 2) || '-' ||
hex(randomblob(6))
)
'''
# -------------
# Device events
sql_devices_mappedColumns = '''
"GUID",
"DateTimeCreated",
"ObjectType",
"ObjectPrimaryID",
"ObjectSecondaryID",
"ObjectStatus",
"ObjectStatusColumn",
"ObjectIsNew",
"ObjectIsArchived",
"ObjectForeignKey",
"AppEventType"
'''
# Trigger for create event
self.db.sql.execute(f'''
CREATE TRIGGER IF NOT EXISTS "trg_create_device"
AFTER INSERT ON "Devices"
BEGIN
INSERT INTO "AppEvents" (
{sql_devices_mappedColumns}
)
VALUES (
{sql_generateGuid},
DATETIME('now'),
'Devices',
NEW.dev_MAC,
NEW.dev_LastIP,
CASE WHEN NEW.dev_PresentLastScan = 1 THEN 'online' ELSE 'offline' END,
'dev_PresentLastScan',
NEW.dev_NewDevice,
NEW.dev_Archived,
NEW.dev_MAC,
'create'
);
END;
''')
# 🔴 This would generate too many events, disabled for now
# # Trigger for read event
# self.db.sql.execute('''
# TODO
# ''')
# Trigger for update event
self.db.sql.execute(f'''
CREATE TRIGGER IF NOT EXISTS "trg_update_device"
AFTER UPDATE ON "Devices"
BEGIN
INSERT INTO "AppEvents" (
{sql_devices_mappedColumns}
)
VALUES (
{sql_generateGuid},
DATETIME('now'),
'Devices',
NEW.dev_MAC,
NEW.dev_LastIP,
CASE WHEN NEW.dev_PresentLastScan = 1 THEN 'online' ELSE 'offline' END,
'dev_PresentLastScan',
NEW.dev_NewDevice,
NEW.dev_Archived,
NEW.dev_MAC,
'update'
);
END;
''')
# Trigger for delete event
self.db.sql.execute(f'''
CREATE TRIGGER IF NOT EXISTS "trg_delete_device"
AFTER DELETE ON "Devices"
BEGIN
INSERT INTO "AppEvents" (
{sql_devices_mappedColumns}
)
VALUES (
{sql_generateGuid},
DATETIME('now'),
'Devices',
OLD.dev_MAC,
OLD.dev_LastIP,
CASE WHEN OLD.dev_PresentLastScan = 1 THEN 'online' ELSE 'offline' END,
'dev_PresentLastScan',
OLD.dev_NewDevice,
OLD.dev_Archived,
OLD.dev_MAC,
'delete'
);
END;
''')
# -------------
# Plugins_Objects events
sql_plugins_objects_mappedColumns = '''
"GUID",
"DateTimeCreated",
"ObjectType",
"ObjectPlugin",
"ObjectPrimaryID",
"ObjectSecondaryID",
"ObjectForeignKey",
"ObjectStatusColumn",
"ObjectStatus",
"AppEventType"
'''
# Create trigger for update event on Plugins_Objects
self.db.sql.execute(f'''
CREATE TRIGGER IF NOT EXISTS trg_update_plugin_object
AFTER UPDATE ON Plugins_Objects
BEGIN
INSERT INTO AppEvents (
{sql_plugins_objects_mappedColumns}
)
VALUES (
{sql_generateGuid},
DATETIME('now'),
'Plugins_Objects',
NEW.Plugin,
NEW.Object_PrimaryID,
NEW.Object_SecondaryID,
NEW.ForeignKey,
'Status',
NEW.Status,
'update'
);
END;
''')
# Create trigger for CREATE event on Plugins_Objects
self.db.sql.execute(f'''
CREATE TRIGGER IF NOT EXISTS trg_create_plugin_object
AFTER INSERT ON Plugins_Objects
BEGIN
INSERT INTO AppEvents (
{sql_plugins_objects_mappedColumns}
)
VALUES (
{sql_generateGuid},
DATETIME('now'),
'Plugins_Objects',
NEW.Plugin,
NEW.Object_PrimaryID,
NEW.Object_SecondaryID,
NEW.ForeignKey,
'Status',
NEW.Status,
'create'
);
END;
''')
# Create trigger for DELETE event on Plugins_Objects
self.db.sql.execute(f'''
CREATE TRIGGER IF NOT EXISTS trg_delete_plugin_object
AFTER DELETE ON Plugins_Objects
BEGIN
INSERT INTO AppEvents (
{sql_plugins_objects_mappedColumns}
)
VALUES (
{sql_generateGuid},
DATETIME('now'),
'Plugins_Objects',
OLD.Plugin,
OLD.Object_PrimaryID,
OLD.Object_SecondaryID,
OLD.ForeignKey,
'Status',
OLD.Status,
'delete'
);
END;
''')
self.save()
# -------------------------------------------------------------------------------
# -------------------------------------------------------------------------------
# below code is unused
# -------------------------------------------------------------------------------
# Create a new DB entry if new notifications are available, otherwise skip
def create(self, Extra="", **kwargs):
# Check if nothing to report, end
@@ -72,11 +284,6 @@ class AppEvent_obj:
return True
# Update the status of the entry
def updateStatus(self, newStatus):
self.ObjectStatus = newStatus
self.upsert()
def upsert(self):
self.db.sql.execute("""
INSERT OR REPLACE INTO AppEvents (

View File

@@ -32,12 +32,12 @@ arpscan_devices = []
SCAN_SUBNETS = ['192.168.1.0/24 --interface=eth1', '192.168.1.0/24 --interface=eth0']
LOG_LEVEL = 'verbose'
TIMEZONE = 'Europe/Berlin'
DIG_GET_IP_ARG = '-4 myip.opendns.com @resolver1.opendns.com'
UI_LANG = 'English'
UI_PRESENCE = ['online', 'offline', 'archived']
UI_PRESENCE = ['online', 'offline', 'archived']
UI_MY_DEVICES = ['online', 'offline', 'archived', 'new', 'down']
UI_NOT_RANDOM_MAC = []
PIALERT_WEB_PROTECTION = False
PIALERT_WEB_PASSWORD = '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92'
INCLUDED_SECTIONS = ['new_devices', 'down_devices', 'events']
DAYS_TO_KEEP_EVENTS = 90
REPORT_DASHBOARD_URL = 'http://pi.alert/'

View File

@@ -23,6 +23,7 @@ vendorsPath = '/usr/share/arp-scan/ieee-oui.txt'
# SQL queries
#===============================================================================
sql_devices_all = """select rowid, * from Devices"""
sql_appevents = """select * from AppEvents"""
sql_devices_stats = """SELECT Online_Devices as online, Down_Devices as down, All_Devices as 'all', Archived_Devices as archived,
(select count(*) from Devices a where dev_NewDevice = 1 ) as new,
(select count(*) from Devices a where dev_Name = '(unknown)' or dev_Name = '(name not found)' ) as unknown

View File

@@ -7,6 +7,7 @@ from const import fullDbPath, sql_devices_stats, sql_devices_all
from logger import mylog
from helper import json_obj, initOrSetParam, row_to_json, timeNowTZ #, updateState
from appevent import AppEvent_obj
@@ -83,8 +84,8 @@ class DB():
# indicates, if Online_History table is available
onlineHistoryAvailable = self.sql.execute("""
SELECT name FROM sqlite_master WHERE type='table'
AND name='Online_History';
SELECT name FROM sqlite_master WHERE type='table'
AND name='Online_History';
""").fetchall() != []
# Check if it is incompatible (Check if table has all required columns)
@@ -92,8 +93,8 @@ class DB():
if onlineHistoryAvailable :
isIncompatible = self.sql.execute ("""
SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Online_History') WHERE name='Archived_Devices'
""").fetchone()[0] == 0
SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Online_History') WHERE name='Archived_Devices'
""").fetchone()[0] == 0
# Drop table if available, but incompatible
if onlineHistoryAvailable and isIncompatible:
@@ -119,35 +120,35 @@ class DB():
# -------------------------------------------------------------------------
# dev_Network_Node_MAC_ADDR column
dev_Network_Node_MAC_ADDR_missing = self.sql.execute ("""
SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Devices') WHERE name='dev_Network_Node_MAC_ADDR'
SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Devices') WHERE name='dev_Network_Node_MAC_ADDR'
""").fetchone()[0] == 0
if dev_Network_Node_MAC_ADDR_missing :
mylog('verbose', ["[upgradeDB] Adding dev_Network_Node_MAC_ADDR to the Devices table"])
self.sql.execute("""
ALTER TABLE "Devices" ADD "dev_Network_Node_MAC_ADDR" TEXT
ALTER TABLE "Devices" ADD "dev_Network_Node_MAC_ADDR" TEXT
""")
# dev_Network_Node_port column
dev_Network_Node_port_missing = self.sql.execute ("""
SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Devices') WHERE name='dev_Network_Node_port'
SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Devices') WHERE name='dev_Network_Node_port'
""").fetchone()[0] == 0
if dev_Network_Node_port_missing :
mylog('verbose', ["[upgradeDB] Adding dev_Network_Node_port to the Devices table"])
self.sql.execute("""
ALTER TABLE "Devices" ADD "dev_Network_Node_port" INTEGER
ALTER TABLE "Devices" ADD "dev_Network_Node_port" INTEGER
""")
# dev_Icon column
dev_Icon_missing = self.sql.execute ("""
SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Devices') WHERE name='dev_Icon'
SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Devices') WHERE name='dev_Icon'
""").fetchone()[0] == 0
if dev_Icon_missing :
mylog('verbose', ["[upgradeDB] Adding dev_Icon to the Devices table"])
self.sql.execute("""
ALTER TABLE "Devices" ADD "dev_Icon" TEXT
ALTER TABLE "Devices" ADD "dev_Icon" TEXT
""")
@@ -263,61 +264,61 @@ class DB():
# Plugin state
sql_Plugins_Objects = """ CREATE TABLE IF NOT EXISTS Plugins_Objects(
"Index" INTEGER,
Plugin TEXT NOT NULL,
Object_PrimaryID TEXT NOT NULL,
Object_SecondaryID TEXT NOT NULL,
DateTimeCreated TEXT NOT NULL,
DateTimeChanged TEXT NOT NULL,
Watched_Value1 TEXT NOT NULL,
Watched_Value2 TEXT NOT NULL,
Watched_Value3 TEXT NOT NULL,
Watched_Value4 TEXT NOT NULL,
Status TEXT NOT NULL,
Extra TEXT NOT NULL,
UserData TEXT NOT NULL,
ForeignKey TEXT NOT NULL,
PRIMARY KEY("Index" AUTOINCREMENT)
"Index" INTEGER,
Plugin TEXT NOT NULL,
Object_PrimaryID TEXT NOT NULL,
Object_SecondaryID TEXT NOT NULL,
DateTimeCreated TEXT NOT NULL,
DateTimeChanged TEXT NOT NULL,
Watched_Value1 TEXT NOT NULL,
Watched_Value2 TEXT NOT NULL,
Watched_Value3 TEXT NOT NULL,
Watched_Value4 TEXT NOT NULL,
Status TEXT NOT NULL,
Extra TEXT NOT NULL,
UserData TEXT NOT NULL,
ForeignKey TEXT NOT NULL,
PRIMARY KEY("Index" AUTOINCREMENT)
); """
self.sql.execute(sql_Plugins_Objects)
# Plugin execution results
sql_Plugins_Events = """ CREATE TABLE IF NOT EXISTS Plugins_Events(
"Index" INTEGER,
Plugin TEXT NOT NULL,
Object_PrimaryID TEXT NOT NULL,
Object_SecondaryID TEXT NOT NULL,
DateTimeCreated TEXT NOT NULL,
DateTimeChanged TEXT NOT NULL,
Watched_Value1 TEXT NOT NULL,
Watched_Value2 TEXT NOT NULL,
Watched_Value3 TEXT NOT NULL,
Watched_Value4 TEXT NOT NULL,
Status TEXT NOT NULL,
Extra TEXT NOT NULL,
UserData TEXT NOT NULL,
ForeignKey TEXT NOT NULL,
PRIMARY KEY("Index" AUTOINCREMENT)
"Index" INTEGER,
Plugin TEXT NOT NULL,
Object_PrimaryID TEXT NOT NULL,
Object_SecondaryID TEXT NOT NULL,
DateTimeCreated TEXT NOT NULL,
DateTimeChanged TEXT NOT NULL,
Watched_Value1 TEXT NOT NULL,
Watched_Value2 TEXT NOT NULL,
Watched_Value3 TEXT NOT NULL,
Watched_Value4 TEXT NOT NULL,
Status TEXT NOT NULL,
Extra TEXT NOT NULL,
UserData TEXT NOT NULL,
ForeignKey TEXT NOT NULL,
PRIMARY KEY("Index" AUTOINCREMENT)
); """
self.sql.execute(sql_Plugins_Events)
# Plugin execution history
sql_Plugins_History = """ CREATE TABLE IF NOT EXISTS Plugins_History(
"Index" INTEGER,
Plugin TEXT NOT NULL,
Object_PrimaryID TEXT NOT NULL,
Object_SecondaryID TEXT NOT NULL,
DateTimeCreated TEXT NOT NULL,
DateTimeChanged TEXT NOT NULL,
Watched_Value1 TEXT NOT NULL,
Watched_Value2 TEXT NOT NULL,
Watched_Value3 TEXT NOT NULL,
Watched_Value4 TEXT NOT NULL,
Status TEXT NOT NULL,
Extra TEXT NOT NULL,
UserData TEXT NOT NULL,
ForeignKey TEXT NOT NULL,
PRIMARY KEY("Index" AUTOINCREMENT)
"Index" INTEGER,
Plugin TEXT NOT NULL,
Object_PrimaryID TEXT NOT NULL,
Object_SecondaryID TEXT NOT NULL,
DateTimeCreated TEXT NOT NULL,
DateTimeChanged TEXT NOT NULL,
Watched_Value1 TEXT NOT NULL,
Watched_Value2 TEXT NOT NULL,
Watched_Value3 TEXT NOT NULL,
Watched_Value4 TEXT NOT NULL,
Status TEXT NOT NULL,
Extra TEXT NOT NULL,
UserData TEXT NOT NULL,
ForeignKey TEXT NOT NULL,
PRIMARY KEY("Index" AUTOINCREMENT)
); """
self.sql.execute(sql_Plugins_History)
@@ -328,12 +329,12 @@ class DB():
# Dynamically generated language strings
self.sql.execute("DROP TABLE IF EXISTS Plugins_Language_Strings;")
self.sql.execute(""" CREATE TABLE IF NOT EXISTS Plugins_Language_Strings(
"Index" INTEGER,
Language_Code TEXT NOT NULL,
String_Key TEXT NOT NULL,
String_Value TEXT NOT NULL,
Extra TEXT NOT NULL,
PRIMARY KEY("Index" AUTOINCREMENT)
"Index" INTEGER,
Language_Code TEXT NOT NULL,
String_Key TEXT NOT NULL,
String_Value TEXT NOT NULL,
Extra TEXT NOT NULL,
PRIMARY KEY("Index" AUTOINCREMENT)
); """)
self.commitDB()
@@ -355,6 +356,9 @@ class DB():
);
""")
# Init the AppEvent database table
AppEvent_obj(self)
# -------------------------------------------------------------------------
# DELETING OBSOLETE TABLES - to remove with updated db file after 1/1/2024
# -------------------------------------------------------------------------

View File

@@ -3,13 +3,33 @@ import subprocess
import conf
import re
from helper import timeNowTZ, get_setting, get_setting_value,resolve_device_name_dig, resolve_device_name_pholus, check_IP_format
from helper import timeNowTZ, get_setting, get_setting_value, list_to_where, resolve_device_name_dig, resolve_device_name_pholus, get_device_name_nslookup, check_IP_format
from logger import mylog, print_log
from const import vendorsPath
#-------------------------------------------------------------------------------
# Device object handling (WIP)
#-------------------------------------------------------------------------------
class Device_obj:
def __init__(self, db):
self.db = db
# Get all
def getAll(self):
self.db.sql.execute("""
SELECT * FROM Devices
""")
return self.db.sql.fetchall()
# Get all with unknown names
def getUnknown(self):
self.db.sql.execute("""
SELECT * FROM Devices WHERE dev_Name in ("(unknown)", "(name not found)", "" )
""")
return self.db.sql.fetchall()
#-------------------------------------------------------------------------------
def save_scanned_devices (db):
sql = db.sql #TO-DO
@@ -41,8 +61,8 @@ def print_scan_stats(db):
SELECT
(SELECT COUNT(*) FROM CurrentScan) AS devices_detected,
(SELECT COUNT(*) FROM CurrentScan WHERE NOT EXISTS (SELECT 1 FROM Devices WHERE dev_MAC = cur_MAC)) AS new_devices,
(SELECT COUNT(*) FROM Devices WHERE dev_AlertDeviceDown = 1 AND NOT EXISTS (SELECT 1 FROM CurrentScan WHERE dev_MAC = cur_MAC)) AS down_alerts,
(SELECT COUNT(*) FROM Devices WHERE dev_AlertDeviceDown = 1 AND dev_PresentLastScan = 1 AND NOT EXISTS (SELECT 1 FROM CurrentScan WHERE dev_MAC = cur_MAC)) AS new_down_alerts,
(SELECT COUNT(*) FROM Devices WHERE dev_AlertDeviceDown != 0 AND NOT EXISTS (SELECT 1 FROM CurrentScan WHERE dev_MAC = cur_MAC)) AS down_alerts,
(SELECT COUNT(*) FROM Devices WHERE dev_AlertDeviceDown != 0 AND dev_PresentLastScan = 1 AND NOT EXISTS (SELECT 1 FROM CurrentScan WHERE dev_MAC = cur_MAC)) AS new_down_alerts,
(SELECT COUNT(*) FROM Devices WHERE dev_PresentLastScan = 0) AS new_connections,
(SELECT COUNT(*) FROM Devices WHERE dev_PresentLastScan = 1 AND NOT EXISTS (SELECT 1 FROM CurrentScan WHERE dev_MAC = cur_MAC)) AS disconnections,
(SELECT COUNT(*) FROM Devices, CurrentScan WHERE dev_MAC = cur_MAC AND dev_LastIP <> cur_IP) AS ip_changes,
@@ -106,21 +126,34 @@ def create_new_devices (db):
# Insert events for new devices from CurrentScan
mylog('debug','[New Devices] New devices - 1 Events')
sql.execute (f"""INSERT INTO Events (eve_MAC, eve_IP, eve_DateTime,
query = f"""INSERT INTO Events (eve_MAC, eve_IP, eve_DateTime,
eve_EventType, eve_AdditionalInfo,
eve_PendingAlertEmail)
SELECT cur_MAC, cur_IP, '{startTime}', 'New Device', cur_Vendor, 1
FROM CurrentScan
WHERE NOT EXISTS (SELECT 1 FROM Devices
WHERE dev_MAC = cur_MAC) """ )
WHERE dev_MAC = cur_MAC)
{list_to_where('OR', 'cur_MAC', 'NOT LIKE', get_setting_value('NEWDEV_ignored_MACs'))}
{list_to_where('OR', 'cur_IP', 'NOT LIKE', get_setting_value('NEWDEV_ignored_IPs'))}
"""
mylog('debug',f'[New Devices] Query: {query}')
sql.execute(query)
mylog('debug',f'[New Devices] Insert Connection into session table')
mylog('debug','[New Devices] Insert Connection into session table')
sql.execute (f"""INSERT INTO Sessions (ses_MAC, ses_IP, ses_EventTypeConnection, ses_DateTimeConnection,
ses_EventTypeDisconnection, ses_DateTimeDisconnection, ses_StillConnected, ses_AdditionalInfo)
SELECT cur_MAC, cur_IP,'Connected','{startTime}', NULL , NULL ,1, cur_Vendor
FROM CurrentScan
WHERE NOT EXISTS (SELECT 1 FROM Sessions
WHERE ses_MAC = cur_MAC) """)
WHERE ses_MAC = cur_MAC)
{list_to_where('OR', 'cur_MAC', 'NOT LIKE', get_setting_value('NEWDEV_ignored_MACs'))}
{list_to_where('OR', 'cur_IP', 'NOT LIKE', get_setting_value('NEWDEV_ignored_IPs'))}
""")
# Create new devices from CurrentScan
mylog('debug','[New Devices] 2 Create devices')
@@ -161,6 +194,8 @@ def create_new_devices (db):
'{get_setting_value('NEWDEV_dev_Icon')}'
"""
# Bulk-inserting devices from the CurrentScan table as new devices in the table Devices ...
# ... with new device defaults and ignoring specidfied IPs and MACs)
sqlQuery = f"""INSERT OR IGNORE INTO Devices (dev_MAC, dev_name, dev_Vendor,
dev_LastIP, dev_FirstConnection, dev_LastConnection,
{newDevColumns})
@@ -169,7 +204,11 @@ def create_new_devices (db):
ELSE '(unknown)' END,
cur_Vendor, cur_IP, ?, ?,
{newDevDefaults}
FROM CurrentScan"""
FROM CurrentScan
WHERE 1=1
{list_to_where('OR', 'cur_MAC', 'NOT LIKE', get_setting_value('NEWDEV_ignored_MACs'))}
{list_to_where('OR', 'cur_IP', 'NOT LIKE', get_setting_value('NEWDEV_ignored_IPs'))}
"""
mylog('debug',f'[New Devices] Create devices SQL: {sqlQuery}')
@@ -266,9 +305,10 @@ def update_devices_names (db):
notFound = 0
foundDig = 0
foundNsLookup = 0
foundPholus = 0
# BUGFIX #97 - Updating name of Devices w/o IP
# Gen unknown devices
sql.execute ("SELECT * FROM Devices WHERE dev_Name IN ('(unknown)','', '(name not found)') AND dev_LastIP <> '-'")
unknownDevices = sql.fetchall()
db.commitDB()
@@ -278,7 +318,7 @@ def update_devices_names (db):
return
# Devices without name
mylog('verbose', '[Update Device Name] Trying to resolve devices without name')
mylog('verbose', f'[Update Device Name] Trying to resolve devices without name. Unknown devices count: {len(unknownDevices)}')
# get names from Pholus scan
sql.execute ('SELECT * FROM Pholus_Scan where "Record_Type"="Answer"')
@@ -288,6 +328,7 @@ def update_devices_names (db):
# Number of entries from previous Pholus scans
mylog('verbose', ['[Update Device Name] Pholus entries from prev scans: ', len(pholusResults)])
for device in unknownDevices:
newName = nameNotFound
@@ -298,8 +339,16 @@ def update_devices_names (db):
if newName != nameNotFound:
foundDig += 1
# Resolve device name with NSLOOKUP plugin data
if newName == nameNotFound:
newName = get_device_name_nslookup(db, device['dev_MAC'], device['dev_LastIP'])
if newName != nameNotFound:
foundNsLookup += 1
# Resolve with Pholus
if newName == nameNotFound:
# Try MAC matching
newName = resolve_device_name_pholus (device['dev_MAC'], device['dev_LastIP'], pholusResults, nameNotFound, False)
# Try IP matching
@@ -310,8 +359,11 @@ def update_devices_names (db):
if newName != nameNotFound:
foundPholus += 1
# isf still not found update name so we can distinguish the devices where we tried already
# if still not found update name so we can distinguish the devices where we tried already
if newName == nameNotFound :
notFound += 1
# if dev_Name is the same as what we will change it to, take no action
# this mitigates a race condition which would overwrite a users edits that occured since the select earlier
if device['dev_Name'] != nameNotFound:
@@ -321,8 +373,8 @@ def update_devices_names (db):
recordsToUpdate.append ([newName, device['dev_MAC']])
# Print log
mylog('verbose', ['[Update Device Name] Names Found (DiG/Pholus): ', len(recordsToUpdate), " (",foundDig,"/",foundPholus ,")"] )
mylog('verbose', ['[Update Device Name] Names Not Found : ', len(recordsNotFound)] )
mylog('verbose', ['[Update Device Name] Names Found (DiG/NSLOOKUP/Pholus): ', len(recordsToUpdate), " (",foundDig,"/",foundNsLookup,"/",foundPholus ,")"] )
mylog('verbose', ['[Update Device Name] Names Not Found : ', notFound] )
# update not found devices with (name not found)
sql.executemany ("UPDATE Devices SET dev_Name = ? WHERE dev_MAC = ? ", recordsNotFound )
@@ -365,10 +417,15 @@ def query_MAC_vendor (pMAC):
try:
with open(vendorsPath, 'r') as f:
for line in f:
if line.startswith(mac_start_string6):
vendor = line.split(' ', 1)[1].strip()
mylog('debug', [f"[Vendor Check] Found '{vendor}' for '{pMAC}' in {vendorsPath}"])
return vendor
if line.startswith(mac_start_string6):
parts = line.split(' ', 1)
if len(parts) > 1:
vendor = parts[1].strip()
mylog('debug', [f"[Vendor Check] Found '{vendor}' for '{pMAC}' in {vendorsPath}"])
return vendor
else:
mylog('debug', [f'[Vendor Check] ⚠ ERROR: Match found, but line could not be processed: "{line}"'])
return -1
return -1 # MAC address not found in the database

View File

@@ -37,6 +37,12 @@ def timeNowTZ():
def timeNow():
return datetime.datetime.now().replace(microsecond=0)
def get_timezone_offset():
now = datetime.datetime.now(conf.tz)
offset_hours = now.utcoffset().total_seconds() / 3600
offset_formatted = "{:+03d}:{:02d}".format(int(offset_hours), int((offset_hours % 1) * 60))
return offset_formatted
#-------------------------------------------------------------------------------
# App state
@@ -271,6 +277,9 @@ def get_setting(key):
#-------------------------------------------------------------------------------
# Settings
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
# Return setting value
def get_setting_value(key):
@@ -306,8 +315,15 @@ def get_setting_value(key):
elif set_type in ['integer.select', 'integer']:
value = int(set_value)
elif set_type in ['text.multiselect', 'list', 'subnets']:
# Handle string
mylog('debug', [f'[SETTINGS] Handling set_type: "{set_type}", set_value: "{set_value}"'])
if isinstance(set_value, str):
value = json.loads(set_value.replace("'", "\""))
# Assuming set_value is a list in this case
value = set_value
elif isinstance(set_value, list):
value = set_value
elif set_type == '.template':
# Assuming set_value is a JSON object in this case
value = json.loads(set_value)
@@ -319,6 +335,36 @@ def get_setting_value(key):
return value
#-------------------------------------------------------------------------------
# Generate a WHERE condition for SQLite based on a list of values.
def list_to_where(logical_operator, column_name, condition_operator, values_list):
"""
Generate a WHERE condition for SQLite based on a list of values.
Parameters:
- logical_operator: The logical operator ('AND' or 'OR') to combine conditions.
- column_name: The name of the column to filter on.
- condition_operator: The condition operator ('LIKE', 'NOT LIKE', '=', '!=', etc.).
- values_list: A list of values to be included in the condition.
Returns:
- A string representing the WHERE condition.
"""
if not values_list:
return "" # Return an empty string if the list is empty to avoid breaking the SQL condition.
# Replace {s-quote} with single quote in values_list
values_list = [value.replace("{s-quote}", "'") for value in values_list]
# Build the WHERE condition
condition = f"{column_name} {condition_operator} '{values_list[0]}'"
for value in values_list[1:]:
condition += f" {logical_operator} {column_name} {condition_operator} '{value}'"
return f' AND ({condition}) '
#-------------------------------------------------------------------------------
@@ -355,6 +401,52 @@ def check_IP_format (pIP):
#-------------------------------------------------------------------------------
def get_device_name_nslookup(db, pMAC, pIP):
nameNotFound = "(name not found)"
sql = db.sql
name = nameNotFound
# get names from the NSLOOKUP plugin entries vased on MAC
sql.execute(
f"""
SELECT Watched_Value2 FROM Plugins_Objects
WHERE
Plugin = 'NSLOOKUP' AND
Object_PrimaryID = '{pMAC}'
"""
)
nslookupEntry = sql.fetchall()
db.commitDB()
if len(nslookupEntry) != 0:
name = cleanDeviceName(nslookupEntry[0][0], False)
return name
# get names from the NSLOOKUP plugin entries based on IP
sql.execute(
f"""
SELECT Watched_Value2 FROM Plugins_Objects
WHERE
Plugin = 'NSLOOKUP' AND
Object_SecondaryID = '{pIP}'
"""
)
nslookupEntry = sql.fetchall()
db.commitDB()
if len(nslookupEntry) != 0:
name = cleanDeviceName(nslookupEntry[0][0], True)
return name
return name
#-------------------------------------------------------------------------------
def resolve_device_name_dig (pMAC, pIP):
@@ -376,7 +468,7 @@ def resolve_device_name_dig (pMAC, pIP):
# Cleanup
newName = cleanDeviceName(newName, True)
if newName == "" or len(newName) == 0 or newName == '-1' or newName == -1 or "communications error" in newName:
if newName == "" or len(newName) == 0 or newName == '-1' or newName == -1 or "communications error" in newName or 'malformed message packet' in newName :
return nameNotFound
# all checks passed
@@ -470,58 +562,6 @@ def resolve_device_name_pholus (pMAC, pIP, allRes, nameNotFound, match_IP = Fals
if 'PTR Class:IN' in value and len(value.split('"')) > 1:
return cleanDeviceName(value.split('"')[1], match_IP)
# # airplay matches contain a lot of information
# # Matches for example:
# # Brand Tv (50)._airplay._tcp.local. TXT Class:32769 "acl=0 deviceid=66:66:66:66:66:66 features=0x77777,0x38BCB46 rsf=0x3 fv=p20.T-FFFFFF-03.1 flags=0x204 model=XXXX manufacturer=Brand serialNumber=XXXXXXXXXXX protovers=1.1 srcvers=777.77.77 pi=FF:FF:FF:FF:FF:FF psi=00000000-0000-0000-0000-FFFFFFFFFF gid=00000000-0000-0000-0000-FFFFFFFFFF gcgl=0 pk=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
# for i in pholusMatchesIndexes:
# if checkIPV4(allRes[i]['IP_v4_or_v6']) and '._airplay._tcp.local. TXT Class:32769' in str(allRes[i]["Value"]) :
# return cleanDeviceName(allRes[i]["Value"].split('._airplay._tcp.local. TXT Class:32769')[0], match_IP)
# # second best - contains airplay
# # Matches for example:
# # _airplay._tcp.local. PTR Class:IN "Brand Tv (50)._airplay._tcp.local."
# for i in pholusMatchesIndexes:
# if checkIPV4(allRes[i]['IP_v4_or_v6']) and '_airplay._tcp.local. PTR Class:IN' in allRes[i]["Value"] and ('._googlecast') not in allRes[i]["Value"]:
# return cleanDeviceName(allRes[i]["Value"].split('"')[1], match_IP)
# # Contains PTR Class:32769
# # Matches for example:
# # 3.1.168.192.in-addr.arpa. PTR Class:32769 "MyPc.local."
# for i in pholusMatchesIndexes:
# if checkIPV4(allRes[i]['IP_v4_or_v6']) and 'PTR Class:32769' in allRes[i]["Value"]:
# return cleanDeviceName(allRes[i]["Value"].split('"')[1], match_IP)
# # Contains AAAA Class:IN
# # Matches for example:
# # DESKTOP-SOMEID.local. AAAA Class:IN "fe80::fe80:fe80:fe80:fe80"
# for i in pholusMatchesIndexes:
# if checkIPV4(allRes[i]['IP_v4_or_v6']) and 'AAAA Class:IN' in allRes[i]["Value"]:
# return cleanDeviceName(allRes[i]["Value"].split('.local.')[0], match_IP)
# # Contains _googlecast._tcp.local. PTR Class:IN
# # Matches for example:
# # _googlecast._tcp.local. PTR Class:IN "Nest-Audio-ff77ff77ff77ff77ff77ff77ff77ff77._googlecast._tcp.local."
# for i in pholusMatchesIndexes:
# if checkIPV4(allRes[i]['IP_v4_or_v6']) and '_googlecast._tcp.local. PTR Class:IN' in allRes[i]["Value"] and ('Google-Cast-Group') not in allRes[i]["Value"]:
# return cleanDeviceName(allRes[i]["Value"].split('"')[1], match_IP)
# # Contains A Class:32769
# # Matches for example:
# # Android.local. A Class:32769 "192.168.1.6"
# for i in pholusMatchesIndexes:
# if checkIPV4(allRes[i]['IP_v4_or_v6']) and ' A Class:32769' in allRes[i]["Value"]:
# return cleanDeviceName(allRes[i]["Value"].split(' A Class:32769')[0], match_IP)
# # # Contains PTR Class:IN
# # Matches for example:
# # _esphomelib._tcp.local. PTR Class:IN "ceiling-light-1._esphomelib._tcp.local."
# for i in pholusMatchesIndexes:
# if checkIPV4(allRes[i]['IP_v4_or_v6']) and 'PTR Class:IN' in allRes[i]["Value"]:
# if allRes[i]["Value"] and len(allRes[i]["Value"].split('"')) > 1:
# return cleanDeviceName(allRes[i]["Value"].split('"')[1], match_IP)
return nameNotFound
@@ -531,7 +571,8 @@ def cleanDeviceName(str, match_IP):
# alternative str.split('.')[0]
str = str.replace("._airplay", "")
str = str.replace("._tcp", "")
str = str.replace(".local", "")
str = str.replace(".localdomain", "")
str = str.replace(".local", "")
str = str.replace("._esphomelib", "")
str = str.replace("._googlecast", "")
str = str.replace(".lan", "")

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