Compare commits

...

163 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
73 changed files with 6035 additions and 2387 deletions

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 }}

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 }}

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

@@ -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) |
|----------------------|----------------------| ----------------------| ----------------------|
## 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] |
|----------------------|----------------------| ----------------------|
<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>
> 📧 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.
<hr>
- 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

@@ -52,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,6 +16,9 @@
<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:
@@ -43,14 +46,15 @@ docker run -d --rm --network=host \
### Docker paths
| 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 |
| | `:/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`)|
| | `:/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](/front/plugins/README.md). |
| | `:/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). |
### Modify the config (`pialert.conf`) only if UI is not available
@@ -86,9 +90,11 @@ There are 2 approaches how to get PiHole devices imported. Via the PiHole import
#### 🧭 Community guides
> 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 🙏
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)

View File

@@ -92,6 +92,14 @@ 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"
@@ -140,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

@@ -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 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)

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

@@ -25,17 +25,23 @@ There is also an in-app Help / FAQ section that should be answering frequently a
### 📚 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)
@@ -43,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 Icon configuration and support](/docs/ICONS.md)
#### 🔎 Examples

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

@@ -31,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: 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();
@@ -240,6 +242,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);
@@ -251,6 +255,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':
@@ -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 !== 0)
{
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';
}
return "Unknown status"
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"
}
// -----------------------------------------------------------------------------
@@ -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;
@@ -397,6 +452,9 @@ function initializeDatatable (status) {
$.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
@@ -482,7 +540,16 @@ function initializeDatatable (status) {
// Connected Devices
{targets: [mapIndx(15)],
'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: [{

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,10 +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)
{
console.log(data)
data = JSON.parse(sanitize(data));
return data.replace(/\[|\]/g, '').split(',').map(Number);
}
@@ -401,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,6 +547,22 @@ function getNameByMacAddress(macAddress) {
return getDeviceDataByMacAddress(macAddress, "dev_Name")
}
// -----------------------------------------------------------------------------
// 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;
}
}
// -----------------------------------------------------------------------------
// A function used to make the IP address orderable
function isValidIPv6(ipAddress) {
@@ -551,6 +601,65 @@ function formatIPlong(ipAddress) {
}
}
// -----------------------------------------------------------------------------
// 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
@@ -609,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
// -----------------------------------------------------------------------------
@@ -648,6 +774,7 @@ function hideSpinner()
cacheSettings()
cacheStrings()
initDeviceListAll_JSON()
workInProgress()
console.log("init pialert_common.js")

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++)
@@ -835,23 +837,29 @@ function initializeSelectedColumns () {
//Initialize Select2 Elements and make them sortable
$(function () {
selectEl = $('.select2').select2();
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)
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]
}})
if(rawData["data"] == "")
{
showModalOK (getString('Gen_Warning'), getString('Network_NoDevices'))
setCache('devicesListNew', JSON.stringify(devicesListnew))
return;
}
// init global variable
deviceListGlobal = devicesListnew;
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());
// create tree
initTree(getHierarchy());
// attach on-click events
attachTreeEvents();
});
// 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

@@ -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,
@@ -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
//------------------------------------------------------------------------------
@@ -1134,6 +1160,7 @@ function copyFromDevice() {
function getDeviceCondition ($deviceStatus) {
switch ($deviceStatus) {
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;

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,9 +31,6 @@ function getLanguageDataFromJson()
{
global $allLanguages;
// Default language
$defaultLanguage = 'en_us';
// Array to hold the language data from the JSON files
$languageData = [];
@@ -44,20 +41,18 @@ function getLanguageDataFromJson()
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

@@ -7,7 +7,7 @@
## 🔌 Plugins & 📚 Docs
| Required | CurrentScan | Unique Prefix | Data source | Type | Link + 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/) |
@@ -22,6 +22,7 @@
| | | 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/) |
@@ -35,7 +36,7 @@
| | | 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 |
| 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.
@@ -110,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 |
@@ -127,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. |
@@ -139,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
@@ -155,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).
@@ -196,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.
@@ -243,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
>
@@ -271,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
>
@@ -297,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 |
|----------------------|----------------------|----------------------|
@@ -307,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:
>
@@ -323,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="--">
@@ -340,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()')"
>```
@@ -349,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.
@@ -357,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:
>
@@ -372,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
>{
@@ -391,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:
@@ -421,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:
@@ -559,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

@@ -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

@@ -188,7 +188,7 @@
"description": [
{
"language_code": "en_us",
"string": "How many historical entries of Notifications should be kept. This influences how mane entries are also available in the Report section in the UI"
"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

@@ -25,6 +25,8 @@ 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(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

@@ -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,

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
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

@@ -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",

View File

@@ -264,6 +264,7 @@
},
{
"column": "Watched_Value2",
"mapped_to_column": "cur_Vendor",
"css_classes": "col-sm-2",
"default_value": "",
"localized": [

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
var target = $(e.target).attr("href").split('_').pop();
// save the last prefix
if(target.includes('_') == false )
{
pref = target.split('#')[1]
} else
{
pref = target.split('_')[1]
}
var pref = target.includes('_') ? target.split('_')[1] : target.split('#')[1];
everythingHidden = false;
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

@@ -571,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

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

@@ -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()
@@ -175,7 +183,9 @@ def main ():
mylog('verbose', ['[MAIN] Process: Wait'])
else:
# do something
mylog('verbose', ['[MAIN] waiting to start next loop'])
# 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,
"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"
"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

@@ -33,7 +33,9 @@ SCAN_SUBNETS = ['192.168.1.0/24 --interface=eth1', '192.168.1.0/24 --interfac
LOG_LEVEL = 'verbose'
TIMEZONE = 'Europe/Berlin'
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'
DAYS_TO_KEEP_EVENTS = 90

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
@@ -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 )

View File

@@ -277,6 +277,9 @@ def get_setting(key):
#-------------------------------------------------------------------------------
# Settings
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
# Return setting value
def get_setting_value(key):
@@ -312,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)
@@ -325,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}) '
#-------------------------------------------------------------------------------
@@ -361,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):
@@ -485,6 +571,7 @@ def cleanDeviceName(str, match_IP):
# alternative str.split('.')[0]
str = str.replace("._airplay", "")
str = str.replace("._tcp", "")
str = str.replace(".localdomain", "")
str = str.replace(".local", "")
str = str.replace("._esphomelib", "")
str = str.replace("._googlecast", "")

View File

@@ -107,6 +107,8 @@ def importConfigs (db):
conf.REPORT_DASHBOARD_URL = ccd('REPORT_DASHBOARD_URL', 'http://pi.alert/' , c_d, 'PiAlert URL', 'text', '', 'General')
conf.UI_LANG = ccd('UI_LANG', 'English' , c_d, 'Language Interface', 'text.select', "['English', 'German', 'Spanish']", 'General')
conf.UI_PRESENCE = ccd('UI_PRESENCE', ['online', 'offline', 'archived'] , c_d, 'Include in presence', 'text.multiselect', "['online', 'offline', 'archived']", 'General')
conf.UI_MY_DEVICES = ccd('UI_MY_DEVICES', ['online', 'offline', 'archived', 'new', 'down'] , c_d, 'Include in My Devices', 'text.multiselect', "['online', 'offline', 'archived', 'new', 'down']", 'General')
conf.UI_NOT_RANDOM_MAC = ccd('UI_NOT_RANDOM_MAC', [] , c_d, 'Exlude from Random Prefix', 'list', "", 'General')
conf.DAYS_TO_KEEP_EVENTS = ccd('DAYS_TO_KEEP_EVENTS', 90 , c_d, 'Delete events days', 'integer', '', 'General')
conf.HRS_TO_KEEP_NEWDEV = ccd('HRS_TO_KEEP_NEWDEV', 0 , c_d, 'Keep new devices for', 'integer', "0", 'General')
conf.API_CUSTOM_SQL = ccd('API_CUSTOM_SQL', 'SELECT * FROM Devices WHERE dev_PresentLastScan = 0' , c_d, 'Custom endpoint', 'text', '', 'General')

View File

@@ -3,7 +3,7 @@
import conf
from database import insertOnlineHistory
from device import create_new_devices, print_scan_stats, save_scanned_devices, update_devices_data_from_scan, update_devices_names
from device import create_new_devices, print_scan_stats, save_scanned_devices, update_devices_data_from_scan
from helper import timeNowTZ
from logger import mylog
from reporting import skip_repeated_notifications
@@ -40,10 +40,6 @@ def process_scan (db):
mylog('verbose','[Process Scan] Updating Devices Info')
update_devices_data_from_scan (db)
# Resolve devices names
mylog('verbose','[Process Scan] Resolve devices names')
update_devices_names(db)
# Void false connection - disconnections
mylog('verbose','[Process Scan] Voiding false (ghost) disconnections')
void_ghost_disconnections (db)

View File

@@ -374,7 +374,7 @@ def execute_plugin(db, plugin, pluginsState = plugins_state() ):
pluginsState = process_plugin_events(db, plugin, pluginsState, sqlParams)
# update API endpoints
update_api(db, False, ["plugins_events","plugins_objects", "plugins_history"])
update_api(db, False, ["plugins_events","plugins_objects", "plugins_history", "appevents"])
return pluginsState

161
update_sponsors.py Executable file
View File

@@ -0,0 +1,161 @@
import os
import requests
import base64
from datetime import datetime
def fetch_sponsors():
global headers
graphql_url = "https://api.github.com/graphql"
headers = {
"Authorization": f"Bearer {os.environ.get('GH_TOKEN')}",
"Accept": "application/vnd.github.v4+json",
}
# GraphQL query to fetch public sponsors
graphql_query = """
{
user(login: "jokob-sk") {
sponsorshipsAsMaintainer(first: 100, orderBy: {field: CREATED_AT, direction: ASC}, includePrivate: true) {
totalCount
pageInfo {
endCursor
}
nodes {
sponsorEntity {
... on User {
name
login
url
}
... on Organization {
name
url
login
}
}
createdAt
privacyLevel
tier {
monthlyPriceInCents
}
}
}
}
}
"""
response = requests.post(graphql_url, json={"query": graphql_query}, headers=headers)
data = response.json()
print(f"Debug GraphQL query result: {data}")
if "errors" in data:
print(f"GraphQL query failed: {data['errors']}")
return {"sponsors": []}
sponsorships = data["data"]["user"]["sponsorshipsAsMaintainer"]["nodes"]
sponsors = []
for sponsorship in sponsorships:
privacy_level = sponsorship["privacyLevel"]
# Only include sponsors with privacyLevel set to "PUBLIC"
if privacy_level == "PUBLIC":
sponsor_entity = sponsorship["sponsorEntity"]
created_at = datetime.strptime(sponsorship["createdAt"], "%Y-%m-%dT%H:%M:%SZ")
# Check if tier is not None before accessing its properties
tier = sponsorship.get("tier", {})
monthly_price = tier.get("monthlyPriceInCents") if tier else None
sponsor = {
"name": sponsor_entity.get("name"),
"login": sponsor_entity["login"],
"url": sponsor_entity["url"],
"created_at": created_at,
"privacy_level": privacy_level,
"monthly_price": monthly_price,
}
sponsors.append(sponsor)
print("Public Sponsors:")
print(sponsors)
return {"sponsors": sponsors}
def generate_sponsors_table(sponsors):
sponsors_table = "| All Sponsors |\n|---|\n"
for sponsor in sponsors:
sponsors_table += f"| [{sponsor['name'] or sponsor['login']}]({sponsor['url']}) |\n"
return sponsors_table
def update_readme(sponsors_table):
global headers
repo_owner = "jokob-sk"
repo_name = "Pi.Alert"
# Update the README.md file in the GitHub repository
api_url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/contents/README.md"
# Fetch the current content of the README.md file
response = requests.get(api_url, headers=headers)
readme_data = response.json()
# Extract content from the dictionary
readme_content = base64.b64decode(readme_data['content']).decode()
# Find the start and end markers
start_marker = "<!-- SPONSORS-LIST DO NOT MODIFY BELOW -->"
end_marker = "<!-- SPONSORS-LIST DO NOT MODIFY ABOVE -->"
# Replace the content between markers with the generated sponsors table
start_index = readme_content.find(start_marker)
end_index = readme_content.find(end_marker, start_index + len(start_marker))
if start_index != -1 and end_index != -1:
updated_readme = (
readme_content[:start_index + len(start_marker)]
+ "\n"
+ sponsors_table
+ "\n"
+ readme_content[end_index:]
)
else:
print("Markers not found in README.md. Make sure they are correctly placed.")
return
updated_content_base64 = base64.b64encode(updated_readme.encode()).decode()
# Create a commit to update the README.md file
commit_message = "[🤖Automation] Update README with sponsors information"
commit_data = {
"message": commit_message,
"content": updated_content_base64,
"sha": readme_data["sha"],
"branch": "main", # Update the branch name as needed
}
commit_url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/contents/README.md"
commit_response = requests.put(commit_url, headers=headers, json=commit_data)
if commit_response.status_code == 200:
print("README.md updated successfully in the GitHub repository.")
else:
print(f"Failed to update README.md. Status code: {commit_response.status_code}")
print(commit_response.json())
print("README.md updated successfully with the sponsors table.")
def main():
sponsors_data = fetch_sponsors()
sponsors = sponsors_data.get("sponsors", [])
sponsors_table = generate_sponsors_table(sponsors)
update_readme(sponsors_table)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1 @@
requests>=2.0.0