Compare commits

..

166 Commits

Author SHA1 Message Date
Jokob-sk
508b0c2d83 dig utility name discovery work #534 2024-01-18 17:10:07 +11:00
Jokob-sk
b354e72489 update issue template 2024-01-16 07:25:17 +11:00
Jokob-sk
90bfa70d1b update_vendors work #533 🔃 2024-01-13 08:56:05 +11:00
Jokob-sk
7561a8478d update_vendors work #533 🔃 2024-01-13 08:42:31 +11:00
Jokob-sk
b5afdb2bce docs📚 2024-01-08 08:08:51 +11:00
Jokob-sk
5207162d0a clickable settings cards ⚙ 2024-01-07 11:40:09 +11:00
Jokob-sk
8eecc54217 docs 📚 2024-01-07 11:18:52 +11:00
Jokob-sk
ddfd0d3cb3 docs 📚, links to plugin docs 2024-01-07 10:26:08 +11:00
Jokob-sk
bcc5b2f28a Issue templates 2024-01-06 18:59:09 +11:00
Jokob-sk
2e6be21cd9 Issue templates 2024-01-06 13:12:00 +11:00
Jokob-sk
abb28c4e5b Issue templates 2024-01-06 12:48:30 +11:00
Jokob-sk
44f0ba0924 MQTT settings to select what is send #364📩 2024-01-06 12:09:59 +11:00
Jokob-sk
a6f5e6c499 MQTT enable scheduler #522 2024-01-06 10:26:39 +11:00
Jokob-sk
d992edf6b4 MQTT more logging #522 2024-01-05 18:42:32 +11:00
Jokob-sk
d7ba540377 renamed DIG_GET_IP_ARG to INTRNT_DIG_GET_IP_ARG v0.2 2024-01-05 08:33:30 +11:00
Jokob-sk
30c95f0d5e renamed DIG_GET_IP_ARG to INTRNT_DIG_GET_IP_ARG v0.1 2024-01-05 08:29:27 +11:00
Jokob-sk
b670e3a8b1 renamed DIG_GET_IP_ARG to INTRNT_DIG_GET_IP_ARG 2024-01-05 08:18:52 +11:00
Jokob-sk
467e24d167 ARMv6 test #527 - added to prod image 2024-01-05 08:00:54 +11:00
Jokob-sk
7b70d61dd8 ARMv6 test #527 2024-01-05 07:42:43 +11:00
Jokob-sk
d530576e9b NTFPRCS work + docs 2024-01-04 15:28:43 +11:00
Jokob-sk
cff6f6393d PIHOLE_CMD_OLD bug 2024-01-04 14:37:22 +11:00
Jokob-sk
f33d753cc1 work #504 - New Dev + Events filter setting⚙ 2024-01-04 14:33:26 +11:00
Jokob-sk
6809688623 docs 📚 2024-01-04 13:26:28 +11:00
Jokob-sk
68de633143 work on #523 - ip v6 ordering support 0.1 2024-01-04 09:03:35 +11:00
Jokob-sk
a3e21ac17d work on #523 - ip v6 ordering support 2024-01-03 07:31:32 +11:00
Jokob-sk
c8267f75fa Re-initialize PIHOLE_CMD + docs 2024-01-02 08:06:00 +11:00
Jokob-sk
9e6a52ca4b work #520 - install logging 2024-01-01 10:15:25 +11:00
Jokob-sk
13c68efb8a PiHole work #513 2023-12-21 21:17:05 +11:00
Jokob-sk
47a3f7073b _meta unbound #519 2023-12-18 07:55:41 +11:00
Jokob-sk
8e0eb6a480 Notifications refactor and #242 📩 2023-12-17 22:43:50 +11:00
Jokob-sk
911c897b00 Notifications refactor 🏗 2023-12-17 10:39:14 +11:00
Jokob-sk
b9650d3cf5 Disabling cache to fix build issues 🩹 2023-12-16 20:11:01 +11:00
Jokob-sk
3c959a7920 Clenup & prune attempt 8 🧪 2023-12-16 20:06:35 +11:00
Jokob-sk
5e170da542 Clenup & prune attempt 7 🧪 2023-12-16 19:47:42 +11:00
Jokob-sk
63932fb5bc Clenup & prune attempt 6 🧪 2023-12-16 17:05:20 +11:00
Jokob-sk
741c0f9ede Clenup & prune attempt 5 🧪 2023-12-16 17:04:12 +11:00
Jokob-sk
08abbabaad Clenup & prune attempt 4 🧪 2023-12-16 17:01:50 +11:00
Jokob-sk
65c8f81afd Clenup & prune attempt 3 🧪 2023-12-16 17:00:18 +11:00
Jokob-sk
80958c2e3f Clenup & prune attempt 2 🧪 2023-12-16 16:58:08 +11:00
Jokob-sk
233873704d Clenup & prune attempt 🧪 2023-12-16 16:50:54 +11:00
Jokob-sk
90322c4747 Devices view spinner #509🔃 2023-12-16 16:33:01 +11:00
jokob-sk
57e6a330be Merge pull request #518 from LouisOb/main
FIX unable to send mail with publisher mail plugin - thanks to @LouisOb 🙏
2023-12-15 20:48:05 +00:00
loberer
0f86b05ce5 FIX email_smtp.py: smtp_timeout was undefined in scope of send_mail 2023-12-15 13:26:33 +01:00
Jokob-sk
9dd3a0a2d1 skip invalid dhcp.leases entries #516🩹 2023-12-11 11:12:08 +11:00
Jokob-sk
20f847c6d8 fix MQTT entity names for Home Assistant #514🩹 2023-12-08 07:48:59 +11:00
Jokob-sk
8cd20ab343 Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2023-12-08 07:33:27 +11:00
Jokob-sk
de5dfa9d06 fix SNMP discovery + other #512🩹 2023-12-08 07:32:50 +11:00
jokob-sk
19fe6d53d5 Merge pull request #511 from mscreations/version-check-fix
Fix date parsing for release check - thanks to @mscreations - appreciate it - I've been flat out IRL these days 🙏
2023-11-29 00:25:18 +00:00
Jon
37fa7fe8a8 Fix date parsing for release check
Fixes an issue with date parsing for update check
2023-11-28 19:07:50 -05:00
Jokob-sk
5ec13d89ec fix 2 vendor overwrite #509🩹 2023-11-22 19:22:07 +11:00
Jokob-sk
a0a5410af9 fix 1 cycling thru devices #509🩹 2023-11-22 19:00:52 +11:00
Jokob-sk
b234e1c859 fix Unconfigurable root #507🩹 2023-11-22 08:16:07 +11:00
Jokob-sk
cd761a058f Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2023-11-22 08:07:52 +11:00
Jokob-sk
bf137a9755 fix UNFIMP #508🩹 2023-11-22 08:07:31 +11:00
jokob-sk
81cfa72b72 Merge pull request #505 from lorki97/fix/hw-install
fix: Hardware installation - thanks so much @lorki97 🙏
2023-11-17 21:28:39 +11:00
Markus Lorenz
c15b5bba5c Merge branch 'main' into fix/hw-install 2023-11-16 10:17:53 +01:00
Markus Lorenz
c7913c389f Extend HW install docs 2023-11-16 10:14:18 +01:00
Jokob-sk
fc8d17788a docs 📚 2023-11-16 07:43:10 +11:00
Jokob-sk
ff72b45f7c docs 📚 2023-11-16 07:41:59 +11:00
Markus Lorenz
692cf9305d More refactoring 2023-11-15 15:18:48 +01:00
Markus Lorenz
790e98d8a7 Remove empty buildtimestamp.txt 2023-11-15 14:40:26 +01:00
Markus Lorenz
0bd985282f Refactor shell scripts 2023-11-15 14:35:34 +01:00
Markus Lorenz
1e75eeab4c Create buildtimestamp.txt if not exists, fix shellcheck warnings 2023-11-15 14:09:52 +01:00
Markus Lorenz
a0d34876cc Fix web root 2023-11-15 12:38:07 +01:00
Markus Lorenz
c14fa5606d Disable default NGINX site 2023-11-15 12:25:17 +01:00
Markus Lorenz
aab910f68a Change default port to 20211 as in docker container 2023-11-15 11:56:57 +01:00
Markus Lorenz
b9a7516eb8 Change NGINX config file name and install directory 2023-11-15 11:44:51 +01:00
Markus Lorenz
5cf453d4fb Change web files install directory 2023-11-15 11:08:06 +01:00
jokob-sk
ff40a5acc0 Merge pull request #502 from jasonehines/main
fixed typos by @jasonehines 🙏
2023-11-11 23:42:49 +11:00
Jokob-sk
7e2559c229 Cleanup & docs 📚 2023-11-11 12:27:21 +11:00
Jason Hines
64d6f8be92 fixed typos 2023-11-10 17:29:57 -05:00
Jokob-sk
c91e428e77 Cleanup and MAINT plugin v0.1 🔌 2023-11-11 08:48:10 +11:00
jokob-sk
ee35f35794 Merge pull request #498 from jhonderson/bug-db-cleanup-deleting-new-devices
Fix DB Clean plugin new devices deletion bug - thanks @jhonderson 🙏
2023-11-11 07:32:48 +11:00
Jhon Cardenas
7492a07244 Fix DB Clean plugin device deletion bug 2023-11-09 12:18:43 -08:00
jokob-sk
3f3143452e Merge pull request #497 from jasonehines/patch-1
Update config.json - thanks @jasonehines 🙏
2023-11-09 18:36:53 +11:00
Jason Hines
e2e5a10e7e Update config.json
Changed sql query to sort by dev_MAC. Should resolve https://github.com/jokob-sk/Pi.Alert/issues/496
2023-11-08 17:30:46 -05:00
Jokob-sk
85335bcdbb Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2023-11-09 07:05:07 +11:00
Jokob-sk
93420b1f86 Settinsg work ⚒ 2023-11-09 07:04:30 +11:00
jokob-sk
b7d60ea818 Merge pull request #490 from silverbios/set-listener-address
Add option to set IP Address for web interface in Docker env - thanks to @silverbios 🙏
2023-11-09 07:03:19 +11:00
silverbios
0551cd1eea Updated README file for Docker 2023-11-08 00:07:34 +03:30
Jokob-sk
b86f1d75b5 Name matching fixes 🩹 2023-11-05 10:17:35 +11:00
Jokob-sk
89fb5c9b3b Online history work 👷 2023-11-05 09:40:04 +11:00
Jokob-sk
f5d8a9fc8c Setting icons work 👷‍♀️ 2023-11-04 12:46:38 +11:00
Jokob-sk
973cd60893 Name matching fixes 🩹 2023-11-04 12:06:12 +11:00
silverbios
da0130da4b Add ip enviroment for docker version 2023-11-01 13:25:56 +03:30
Jokob-sk
c8e494596e Install rewrite v2.1 2023-10-29 22:33:15 +11:00
Jokob-sk
baec65fde7 Install rewrite v2 2023-10-29 22:10:53 +11:00
Jokob-sk
9f5884c4e7 MQTT fix 🩹 2023-10-28 08:27:29 +11:00
Jokob-sk
4767dec6b8 Network fixes #475 2023-10-28 07:49:22 +11:00
Jokob-sk
536ef9ec46 Notification Report page rewrite v0.2 + cleanup📩 2023-10-26 20:30:51 +11:00
Jokob-sk
fd162ff98a Notification Report page rewrite v0.1📩 2023-10-25 22:35:07 +11:00
Jokob-sk
0ed24dac0a Frontend user events rewrite v0.1 2023-10-25 08:11:57 +11:00
Jokob-sk
e434a686c6 Settings overview dashboard + #462 work 2023-10-24 20:38:44 +11:00
Jokob-sk
138a899e34 Settings overview dashboard 2023-10-22 22:15:22 +11:00
Jokob-sk
ae7533cec0 Feature request - configurable arp-scan args #486 🎁 2023-10-22 09:41:38 +11:00
Jokob-sk
55e398dd10 WEBHOOK conversion + cleanup work🎣 2023-10-22 09:29:25 +11:00
Jokob-sk
fdd199935a PUSHSAFER + cleanup work⤵ 2023-10-19 21:59:06 +11:00
Jokob-sk
346a22f2f6 NTFY work⤵ 2023-10-19 21:26:03 +11:00
Jokob-sk
5d64433be0 PLUGINS, NTFY, handleEmpty work⤵ 2023-10-19 08:08:24 +11:00
Jokob-sk
1a3cf49c00 PERMISSIONS, MQTT, Maintenance work⤵ 2023-10-18 22:35:36 +11:00
Jokob-sk
9dd456bd2c MQTT, INSTALL scripts work⤵ 2023-10-17 07:38:49 +11:00
Jokob-sk
1b9d4223c5 MQTT, DHCPLSS work🔌 2023-10-16 20:28:30 +11:00
Jokob-sk
2a4ac2f2be MQTT, DHCPLSS work🔌 2023-10-15 22:39:21 +11:00
Jokob-sk
a2f3666134 install scripts rework 📦 2023-10-15 16:56:41 +11:00
Jokob-sk
1435ecac67 install scripts rework 📦 2023-10-15 16:46:04 +11:00
Jokob-sk
897112e466 MQTT rework v0.4, install scripts rework, Traefik docs 📦 2023-10-15 16:37:32 +11:00
Jokob-sk
31e1116483 MQTT rework v0.3 📩 2023-10-14 23:02:43 +11:00
Jokob-sk
7da9bf03a3 Settings UI improvements ⚙ 2023-10-14 18:57:16 +11:00
Jokob-sk
8ad63ba07d MQTT rework v0.1 + Settings UI improvements ⚙ 2023-10-14 15:35:09 +11:00
Jokob-sk
a3702fed94 Debug output for #474 2023-10-14 11:38:44 +11:00
Jokob-sk
3e3e8fa797 Device list rework v0.4 🔨 2023-10-13 22:16:31 +11:00
Jokob-sk
f3b64748aa #479 work 🔨 2023-10-13 21:30:08 +11:00
Jokob-sk
257e46df55 Docs + Device list rework v0.3 + #479 work 🔨 2023-10-13 20:53:04 +11:00
jokob-sk
3c856c010a Merge pull request #470 from lorki97/feat/german-translation
Add missing German translations - thanks to @lorki97 🙏
2023-10-13 20:48:54 +11:00
Jokob-sk
f87ea210c7 Docs + Device list rework v0.2 🔨 2023-10-12 21:45:05 +11:00
Jokob-sk
d433d8e956 Docs + Device list rework 🔨 2023-10-11 21:02:07 +11:00
Jokob-sk
879d7b674b Notification rework - SMTP v0.3 - working 2023-10-10 19:15:52 +11:00
Markus Lorenz
21d47f5d0d Fix spacing 2023-10-10 09:53:04 +02:00
Markus Lorenz
e7d5c1e5fe Add german translation to dhcp_leases plugin 2023-10-10 09:52:36 +02:00
Markus Lorenz
5c08b06ace Format dhcp_leases config file 2023-10-09 16:44:55 +02:00
Markus Lorenz
bb10b865f9 Add german translation to ddns_update plugin 2023-10-09 16:13:30 +02:00
Markus Lorenz
557eb8d09e Format ddns_update config file 2023-10-09 15:56:19 +02:00
Markus Lorenz
a69ce7b85d Add german translation to internet_speedtest plugin 2023-10-09 13:46:33 +02:00
Markus Lorenz
b5649e3c7b Format internet_speedtest config file 2023-10-09 13:32:34 +02:00
Markus Lorenz
1ebae57f48 Add german translation to undiscoverables plugin 2023-10-09 13:20:42 +02:00
Markus Lorenz
6c619bf6f7 Format undiscoverables config file 2023-10-09 12:55:50 +02:00
Markus Lorenz
cfb4bbe907 Add german readme to internet_ip plugin, format config file 2023-10-09 11:38:25 +02:00
Markus Lorenz
c708718e78 Fix grammar 2023-10-09 11:36:06 +02:00
Markus Lorenz
1a02d34e85 Add german readme to arp_scan plugin, format config file 2023-10-09 11:32:55 +02:00
Markus Lorenz
dcf785b900 Add german translation to vendor_update plugin 2023-10-09 11:20:15 +02:00
Markus Lorenz
88bbae7c84 Add german translation to internet_ip plugin 2023-10-09 10:57:44 +02:00
Markus Lorenz
9485b5adfb Add german translation to arp_scan plugin 2023-10-09 10:22:18 +02:00
Markus Lorenz
e2d475100e Merge branch 'main' into feat/german-translation 2023-10-09 09:20:49 +02:00
Markus Lorenz
22d3169d07 Reorder keys 2023-10-09 09:17:23 +02:00
Markus Lorenz
ebe7b9e9e6 Add translations for settings general section 2023-10-09 08:47:26 +02:00
Jokob-sk
78c18aa100 Notification rework - SMTP v0.3 - WIP👷‍♂️ 2023-10-08 22:49:50 +11:00
Jokob-sk
bd9f68bb27 Notification rework - SMTP v0.3 - WIP👷‍♂️ 2023-10-08 22:19:54 +11:00
Jokob-sk
1e693abfc4 Notification rework - SMTP v0.2 - WIP👷‍♂️ 2023-10-08 22:00:24 +11:00
Jokob-sk
e4a64a11bd Notification rework - SMTP v0.1 - WIP👷‍♂️ 2023-10-08 16:54:13 +11:00
Jokob-sk
43c57f00d0 Notification rework + docs + devDetails 2023-10-08 16:28:15 +11:00
jokob-sk
122bb29e99 Merge pull request #476 from ScottRoach/show-device-icon
Show current device icon as it changes - this is nice - thanks @ScottRoach 🙏
2023-10-08 04:51:06 +00:00
jokob-sk
bc8f95d30c Merge pull request #475 from ScottRoach/network-cleanup
Network cleanup - thanks @ScottRoach 🙏
2023-10-08 03:53:59 +00:00
Jokob-sk
be4e0acdfc Notification rework - Apprise v1 - working 2023-10-08 14:52:22 +11:00
Jokob-sk
79c47015f4 Notification rework v0.5 2023-10-08 11:15:10 +11:00
Jokob-sk
d4b590a9fc Notification rework v0.4 2023-10-07 18:04:33 +11:00
Scott Roach
e018fe2995 Related CSS for network icon/text alignment 2023-10-06 23:21:34 -07:00
Scott Roach
4aad8c12f8 Space out network icons, fix invalid markup, and overall slight cleanup 2023-10-06 23:20:22 -07:00
Scott Roach
93c45d7157 Show current device icon as it changes 2023-10-06 23:18:45 -07:00
Jokob-sk
695f1593c6 Notification rework v0.3 2023-10-07 13:00:28 +11:00
Jokob-sk
eb7b7b57ab Notification rework v0.2 2023-10-06 22:53:15 +11:00
Markus Lorenz
e8e8260856 WIP: Add translations for settings general section 2023-10-06 13:22:02 +02:00
Markus Lorenz
50b576134a Added missing translation keys, translated network, maintenance tabs 2023-10-06 10:20:11 +02:00
Jokob-sk
2476a36661 Notification rework v0.1 2023-10-06 08:16:45 +11:00
Jokob-sk
2aa984b147 Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2023-10-06 08:10:31 +11:00
Jokob-sk
16de261477 Notification rework v0.1 2023-10-06 08:10:18 +11:00
jokob-sk
9dfc574bde Merge pull request #468 from ScottRoach/main
Include device vendor in event notifications by @ScottRoach 🙏
2023-10-05 05:17:31 +00:00
Scott Roach
9072c37589 Merge branch 'jokob-sk:main' into main 2023-10-04 17:54:07 -07:00
Scott Roach
095a71bc8f Include vendor in event notifications 2023-10-04 17:53:46 -07:00
Jokob-sk
2b057d339c Network bug - selectable Internet parent #467 2023-10-05 07:15:00 +11:00
Jokob-sk
1e0552cc13 Network bug #465 2023-10-04 21:45:35 +11:00
Jokob-sk
eea0bf66db Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2023-10-04 21:20:27 +11:00
jokob-sk
0741b396ef Merge pull request #466 from lorki97/main
Fix device types not loading in device details page
2023-10-04 10:04:09 +00:00
Markus Lorenz
865f3eabd8 Fix call to unused and removed getNetworkTypes 2023-10-04 11:43:50 +02:00
Jokob-sk
f5ba9b524d Docs 2023-10-03 20:43:22 +11:00
Jokob-sk
654253c953 Docs 2023-10-03 20:32:39 +11:00
Jokob-sk
1711cbfe2d Plugin:Speedtest v0.1 2023-10-03 20:18:34 +11:00
152 changed files with 61867 additions and 4849 deletions

View File

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

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

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

View File

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

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

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

View File

@@ -74,13 +74,18 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# # Disable this after use
# - name: Prune Docker Builder
# run: docker builder prune --force
- name: Build and push
uses: docker/build-push-action@v3
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=ghcr.io/jokob-sk/pi.alert:buildcache
cache-to: type=registry,ref=ghcr.io/jokob-sk/pi.alert:buildcache,mode=max
# # ⚠ disable cache if build is failing to download debian packages
# cache-from: type=registry,ref=ghcr.io/jokob-sk/pi.alert:buildcache
# cache-to: type=registry,ref=ghcr.io/jokob-sk/pi.alert:buildcache,mode=max

View File

@@ -76,9 +76,10 @@ jobs:
uses: docker/build-push-action@v3
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=ghcr.io/jokob-sk/pi.alert:buildcache
cache-to: type=registry,ref=ghcr.io/jokob-sk/pi.alert:buildcache,mode=max
# # ⚠ disable cache if build is failing to download debian packages
# cache-from: type=registry,ref=ghcr.io/jokob-sk/pi.alert:buildcache
# cache-to: type=registry,ref=ghcr.io/jokob-sk/pi.alert:buildcache,mode=max

5
.gitignore vendored
View File

@@ -1,5 +1,6 @@
.vscode
.DS_Store
config/*
config/pialert.conf
db/*
db/pialert.db
@@ -14,4 +15,6 @@ __pycache__/
*$py.class
**/last_result.log
**/script.log
**/script.log
**/pialert.conf_bak
**/pialert.db_bak

View File

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

View File

@@ -1,4 +1,4 @@
FROM debian:bullseye-slim
FROM debian:bookworm-slim
# default UID and GID
ENV USER=pi USER_ID=1000 USER_GID=1000 PORT=20211
@@ -7,15 +7,9 @@ ENV USER=pi USER_ID=1000 USER_GID=1000 PORT=20211
# Todo, figure out why using a workdir instead of full paths don't work
# Todo, do we still need all these packages? I can already see sudo which isn't needed
RUN apt-get update \
&& apt-get install --no-install-recommends tini snmp ca-certificates curl libwww-perl arp-scan perl apt-utils cron sudo nginx-light php php-cgi php-fpm php-sqlite3 php-curl sqlite3 dnsutils net-tools python3 iproute2 nmap python3-pip zip systemctl usbutils traceroute -y \
&& pip3 install requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi \
&& update-alternatives --install /usr/bin/python python /usr/bin/python3 10 \
&& apt-get clean autoclean \
&& apt-get autoremove \
&& rm -rf /var/lib/apt/lists/* \
&& rm -rf /var/www/html \
&& ln -s /home/pi/pialert/front /var/www/html
RUN apt-get update
RUN apt-get install sudo -y
# create pi user and group
# add root and www-data to pi group so they can r/w files and db
@@ -31,24 +25,26 @@ RUN groupadd --gid "${USER_GID}" "${USER}" && \
COPY --chmod=775 --chown=${USER_ID}:${USER_GID} . /home/pi/pialert/
# Pi.Alert
RUN rm /etc/nginx/sites-available/default \
&& ln -s /home/pi/pialert/install/default /etc/nginx/sites-available/default \
&& sed -ie 's/listen 80/listen '${PORT}'/g' /etc/nginx/sites-available/default \
# run the hardware vendors update
&& /home/pi/pialert/back/update_vendors.sh \
# Create a backup of the pialert.conf to be used if the user didn't supply a configuration file
&& cp /home/pi/pialert/config/pialert.conf /home/pi/pialert/back/pialert.conf_bak \
# Create a backup of the pialert.db to be used if the user didn't supply a database
&& cp /home/pi/pialert/db/pialert.db /home/pi/pialert/back/pialert.db_bak \
# Create a buildtimestamp.txt to later check if a new version was released
&& date +%s > /home/pi/pialert/front/buildtimestamp.txt
ENTRYPOINT ["tini", "--"]
# ❗ IMPORTANT - if you modify this file modify the /install/install_dependecies.sh file as well ❗
RUN apt-get install -y \
tini snmp ca-certificates curl libwww-perl arp-scan perl apt-utils cron sudo \
nginx-light php php-cgi php-fpm php-sqlite3 php-curl sqlite3 dnsutils net-tools \
python3 iproute2 nmap python3-pip zip systemctl usbutils traceroute
# Alternate dependencies
RUN apt-get install nginx nginx-core mtr php-fpm php8.2-fpm php-cli php8.2 php8.2-sqlite3 -y
RUN phpenmod -v 8.2 sqlite3
# Setup virtual python environment and use pip3 to install packages
RUN apt-get install -y python3-venv
RUN python3 -m venv myenv
RUN /bin/bash -c "source myenv/bin/activate && update-alternatives --install /usr/bin/python python /usr/bin/python3 10 && pip3 install requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet"
# Create a buildtimestamp.txt to later check if a new version was released
RUN date +%s > /home/pi/pialert/front/buildtimestamp.txt
CMD ["/home/pi/pialert/dockerfiles/start.sh"]
## command to build docker: DOCKER_BUILDKIT=1 docker build . --iidfile dockerID

View File

@@ -1,6 +1,6 @@
# 💻🔍 Network security scanner
# 💻🔍 Network security scanner & notification framework
Scans for devices, port changes on your WIFI/LAN and alerts you if unknown devices or changes are found.
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)
@@ -11,17 +11,19 @@ Scans for devices, port changes on your WIFI/LAN and alerts you if unknown devic
| 🐳 [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❓ Isn't this scary 👻...
## 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 😖.
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, Favorites, 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).
_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 and handle the results and notifications in PiAlert. Check available [Plugins & Instructions](https://github.com/jokob-sk/Pi.Alert/tree/main/front/plugins). Looking forward to your contributions if you decide to share your work with the community ❤.
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] |
|----------------------|----------------------| ----------------------|
@@ -32,11 +34,10 @@ Extend the app if you want to create your own scanner and handle the results and
| 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) docs for more info on individual scans. |
| 🔍 | 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/). |
|🧩 | 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) |
| | Build your own scanners with the [Plugin system](https://github.com/jokob-sk/Pi.Alert/tree/main/front/plugins#readme) |
## Installation & Documentation

View File

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

View File

@@ -46,6 +46,7 @@
<th width='120px' style='color:#F0F0F0' bgcolor='#909090' >Event Type</th>
<th width='120px' style='color:#F0F0F0' bgcolor='#909090' >Device name</th>
<th width='120px' style='color:#F0F0F0' bgcolor='#909090' >Comments</th>
<th width='120px' style='color:#F0F0F0' bgcolor='#909090' >Device Vendor</th>
</tr>
<tr>
<td><a href="http://192.168.1.1:20211/deviceDetails.php?mac=00:00:00:ef:a5:6c">00:00:00:ef:a5:6c</a></td>
@@ -54,6 +55,7 @@
<td>New Device</td>
<td>(name not found)</td>
<td></td>
<td></td>
</tr>
<tr>
<td><a href="http://192.168.1.1:20211/deviceDetails.php?mac=00:00:00:ef:a5:6c">00:00:00:ef:a5:6c</a></td>
@@ -62,6 +64,7 @@
<td>New Device</td>
<td>(name not found)</td>
<td></td>
<td></td>
</tr>
</table>
</td>
@@ -83,6 +86,7 @@
<th width='120px' style='color:#F0F0F0' bgcolor='#909090' >Event Type</th>
<th width='120px' style='color:#F0F0F0' bgcolor='#909090' >Device name</th>
<th width='120px' style='color:#F0F0F0' bgcolor='#909090' >Comments</th>
<th width='120px' style='color:#F0F0F0' bgcolor='#909090' >Device Vendor</th>
</tr>
<tr>
<td><a href="http://192.168.1.1:20211/deviceDetails.php?mac=00:00:00:ef:a5:6c">00:00:00:ef:a5:6c</a></td>

View File

@@ -1,7 +1,7 @@
Report Date: <REPORT_DATE>
Server: <SERVER_NAME>
<SECTION_NEW_DEVICES>
<SECTION_DEVICES_DOWN>
<SECTION_EVENTS>
<NEW_DEVICES_TABLE>
<DOWN_DEVICES_TABLE>
<EVENTS_TABLE>
<PLUGINS_TABLE>

View File

@@ -1,4 +1,5 @@
#!/bin/sh
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# Pi.Alert
# Open Source Network Guard / WIFI & LAN intrusion detector
@@ -14,22 +15,21 @@
# /usr/share/ieee-data
# /var/lib/ieee-data
# ----------------------------------------------------------------------
echo "---------------------------------------------------------"
echo "[INSTALL] Run update_vendors.sh"
echo "---------------------------------------------------------"
# ----------------------------------------------------------------------
echo Updating... /usr/share/ieee-data/
cd /usr/share/ieee-data/
cd /usr/share/ieee-data/ || { echo "could not enter /usr/share/ieee-data directory"; exit 1; }
sudo mkdir -p 2_backup
sudo cp *.txt 2_backup
sudo cp *.csv 2_backup
sudo cp -- *.txt 2_backup
sudo cp -- *.csv 2_backup
echo ""
echo Download Start
echo ""
sudo curl $1 -LO https://standards-oui.ieee.org/iab/iab.csv \
-LO https://standards-oui.ieee.org/iab/iab.txt \
-LO https://standards-oui.ieee.org/oui28/mam.csv \
-LO https://standards-oui.ieee.org/iab/iab.txt \
sudo curl "$1" -LO https://standards-oui.ieee.org/oui28/mam.csv \
-LO https://standards-oui.ieee.org/oui28/mam.csv \
-LO https://standards-oui.ieee.org/oui28/mam.txt \
-LO https://standards-oui.ieee.org/oui36/oui36.csv \
@@ -42,15 +42,18 @@ echo Download Finished
# ----------------------------------------------------------------------
echo ""
echo Updating... /usr/share/arp-scan/
cd /usr/share/arp-scan
cd /usr/share/arp-scan || { echo "could not enter /usr/share/arp-scan directory"; exit 1; }
sudo mkdir -p 2_backup
sudo cp *.txt 2_backup
sudo cp -- *.txt 2_backup
# Update from /usb/lib/ieee-data
sudo get-iab -v
sudo get-oui -v
# make files readable
sudo chmod +r /usr/share/arp-scan/ieee-oui.txt
# Update from ieee website
# sudo get-iab -v -u http://standards-oui.ieee.org/iab/iab.txt
# sudo get-oui -v -u http://standards-oui.ieee.org/oui/oui.txt

View File

@@ -1,87 +1,122 @@
[
{
"headers": {
"host": "192.168.1.82:5678",
"user-agent": "curl/7.74.0",
"accept": "*/*",
"content-type": "application/json",
"content-length": "872"
},
"params": {},
"query": {},
"body": {
"username": "Pi.Alert",
"text": "There are new notifications",
"attachments": [
{
"title": "Pi.Alert Notifications",
"title_link": "",
"text": {
"internet": [],
"new_devices": [{
"MAC": "74:ac:74:ac:74:ac",
"Datetime": "2023-01-30 22:15:09",
"IP": "192.168.1.1",
"Event Type": "New Device",
"Device name": "(name not found)",
"Comments": null
}],
"down_devices": [],
"events": [{
"MAC": "74:ac:74:ac:74:ac",
"Datetime": "2023-01-30 22:15:09",
"IP": "192.168.1.92",
"Event Type": "Disconnected",
"Device name": "(name not found)",
"Comments": null
}, {
"MAC": "74:ac:74:ac:74:ac",
"Datetime": "2023-01-30 22:15:09",
"IP": "192.168.1.150",
"Event Type": "Disconnected",
"Device name": "(name not found)",
"Comments": null
}],
"ports": [{
"new": {
"Name": "New device",
"MAC": "74:ac:74:ac:74:ac",
"Port": "22/tcp",
"State": "open",
"Service": "ssh",
"Extra": ""
}
}, {
"new": {
"Name": "New device",
"MAC": "74:ac:74:ac:74:ac",
"Port": "53/tcp",
"State": "open",
"Service": "domain",
"Extra": ""
}
}, {
"new": {
"Name": "New device",
"MAC": "74:ac:74:ac:74:ac",
"Port": "80/tcp",
"State": "open",
"Service": "http",
"Extra": ""
}
}, {
"new": {
"Name": "New device",
"MAC": "74:ac:74:ac:74:ac",
"Port": "443/tcp",
"State": "open",
"Service": "https",
"Extra": ""
}
}]
}
}
{
"headers": {
"host": "192.168.1.82:5678",
"user-agent": "curl/7.74.0",
"accept": "*/*",
"content-type": "application/json",
"content-length": "872"
},
"params": {},
"query": {},
"body": {
"username": "Pi.Alert",
"text": "There are new notifications",
"attachments": [
{
"title": "Pi.Alert Notifications",
"title_link": "",
"text": {
"new_devices_meta": {
"title": "New devices",
"columnNames": [
"MAC",
"Datetime",
"IP",
"Event Type",
"Device name",
"Comments"
]
},
"new_devices": [
{
"MAC": "74:ac:74:ac:74:ac",
"Datetime": "2023-01-30 22:15:09",
"IP": "192.168.1.1",
"Event Type": "New Device",
"Device name": "(name not found)",
"Comments": null,
"Device Vendor": null
}
],
"down_devices_meta": {
"title": "Down devices",
"columnNames": [
"MAC",
"Datetime",
"IP",
"Event Type",
"Device name",
"Comments"
]
},
"down_devices": [],
"events_meta": {
"title": "Events",
"columnNames": [
"MAC",
"Datetime",
"IP",
"Event Type",
"Device name",
"Comments"
]
},
"events": [
{
"MAC": "74:ac:74:ac:74:ac",
"Datetime": "2023-01-30 22:15:09",
"IP": "192.168.1.92",
"Event Type": "Disconnected",
"Device name": "(name not found)",
"Comments": null,
"Device Vendor": null
},
{
"MAC": "74:ac:74:ac:74:ac",
"Datetime": "2023-01-30 22:15:09",
"IP": "192.168.1.150",
"Event Type": "Disconnected",
"Device name": "(name not found)",
"Comments": null,
"Device Vendor": null
}
],
"plugins_meta": {
"title": "Plugins",
"columnNames": [
"Plugin",
"Object_PrimaryID",
"Object_SecondaryID",
"DateTimeChanged",
"Watched_Value1",
"Watched_Value2",
"Watched_Value3",
"Watched_Value4",
"Status"
]
},
"plugins": [
{
"Index": 138,
"Plugin": "INTRSPD",
"Object_PrimaryID": "Speedtest",
"Object_SecondaryID": "2023-10-08 02:01:16+02:00",
"DateTimeCreated": "2023-10-08 02:01:16",
"DateTimeChanged": "2023-10-08 02:32:15",
"Watched_Value1": "-1",
"Watched_Value2": "-1",
"Watched_Value3": "null",
"Watched_Value4": "null",
"Status": "missing-in-last-scan",
"Extra": "null",
"UserData": "null",
"ForeignKey": "null"
}
]
}
}
]
}
}
]

2
config/.gitignore vendored Executable file
View File

@@ -0,0 +1,2 @@
*
!.gitignore

2
db/.gitignore vendored Executable file
View File

@@ -0,0 +1,2 @@
*
!.gitignore

View File

@@ -22,19 +22,22 @@ services:
- ${APP_DATA_LOCATION}/pialert/dhcp_samples/dhcp1.leases:/mnt/dhcp1.leases
- ${APP_DATA_LOCATION}/pialert/dhcp_samples/dhcp2.leases:/mnt/dhcp2.leases
- ${APP_DATA_LOCATION}/pialert/dhcp_samples/pihole_dhcp_full.leases:/etc/pihole/dhcp.leases
- ${APP_DATA_LOCATION}/pialert/dhcp_samples/pihole_dhcp_2.leases:/etc/pihole/dhcp2.leases
- ${APP_DATA_LOCATION}/pihole/etc-pihole/pihole-FTL.db:/etc/pihole/pihole-FTL.db
- ${DEV_LOCATION}/pialert:/home/pi/pialert/pialert
- ${DEV_LOCATION}/back/report_template.html:/home/pi/pialert/back/report_template.html
- ${DEV_LOCATION}/back/report_template_new_version.html:/home/pi/pialert/back/report_template_new_version.html
- ${DEV_LOCATION}/back/report_template.txt:/home/pi/pialert/back/report_template.txt
- ${DEV_LOCATION}/pialert:/home/pi/pialert/pialert
- ${DEV_LOCATION}/dockerfiles:/home/pi/pialert/dockerfiles
- ${APP_DATA_LOCATION}/pialert/php.ini:/etc/php/7.4/fpm/php.ini
# - ${DEV_LOCATION}/front/api:/home/pi/pialert/front/api
- ${APP_DATA_LOCATION}/pialert/php.ini:/etc/php/8.2/fpm/php.ini
- ${DEV_LOCATION}/install:/home/pi/pialert/install
- ${DEV_LOCATION}/front/css:/home/pi/pialert/front/css
- ${DEV_LOCATION}/back/update_vendors.sh:/home/pi/pialert/back/update_vendors.sh
- ${DEV_LOCATION}/front/lib/AdminLTE:/home/pi/pialert/front/lib/AdminLTE
- ${DEV_LOCATION}/front/js:/home/pi/pialert/front/js
- ${DEV_LOCATION}/dockerfiles/start.sh:/home/pi/pialert/dockerfiles/start.sh
- ${DEV_LOCATION}/dockerfiles/user-mapping.sh:/home/pi/pialert/dockerfiles/user-mapping.sh
- ${DEV_LOCATION}/install/install.sh:/home/pi/pialert/install/install.sh
- ${DEV_LOCATION}/install/install_dependencies.sh:/home/pi/pialert/install/install_dependencies.sh
- ${DEV_LOCATION}/front/api:/home/pi/pialert/front/api
- ${DEV_LOCATION}/front/php:/home/pi/pialert/front/php
- ${DEV_LOCATION}/front/php:/home/pi/pialert/front/php
- ${DEV_LOCATION}/front/deviceDetails.php:/home/pi/pialert/front/deviceDetails.php
- ${DEV_LOCATION}/front/deviceDetailsTools.php:/home/pi/pialert/front/deviceDetailsTools.php
- ${DEV_LOCATION}/front/devices.php:/home/pi/pialert/front/devices.php

View File

@@ -4,7 +4,7 @@
[![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)
# PiAlert 💻🔍 Network security scanner
# PiAlert 💻🔍 Network security scanner & notification framework
| 🐳 [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) |
|----------------------|----------------------| ----------------------| ----------------------|
@@ -18,7 +18,7 @@
## 📕 Basic Usage
- You will have to run the container on the host network, e.g:
- You will have to run the container on the `host` network, e.g:
```yaml
docker run -d --rm --network=host \
@@ -27,36 +27,37 @@ docker run -d --rm --network=host \
-e TZ=Europe/Berlin \
-e PORT=20211 \
jokobsk/pi.alert:latest
```
- The initial scan can take up-to 15min (with 50 devices and MQTT). Subsequent ones 3 and 5 minutes so wait that long for all of the scans to run.
```
- The initial scan can take up to 15min (with 50 devices and MQTT). Subsequent ones 3 and 5 minutes so wait that long for all of the scans to run.
### Docker environment variables
| Variable | Description | Default |
| :------------- |:-------------| -----:|
| `PORT` |Port of the web interface | `20211` |
| `LISTEN_ADDR` |Set the specific IP Address for the listener address for the nginx webserver (web interface). This could be useful when using multiple subnets to hide the web interface from all untrusted networks. | `0.0.0.0` |
|`TZ` |Time zone to display stats correctly. Find your time zone [here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) | `Europe/Berlin` |
|`HOST_USER_GID` |User ID (UID) to map the user in the container to a server user with sufficient read&write permissions on the mapped files | `1000` |
|`HOST_USER_ID` |User Group ID (GID) to map the user group in the container to a server user group with sufficient read&write permissions on the mapped files | `1000` |
### Docker paths
| | Path | Description |
| Required | Path | Description |
| :------------- | :------------- |:-------------|
| **Required** | `:/home/pi/pialert/config` | Folder which will contain the `pialert.conf` file (see below for details) |
| **Required** | `:/home/pi/pialert/db` | Folder which will contain the `pialert.db` file |
|Optional| `:/home/pi/pialert/front/log` | Logs folder useful for debugging if you have issues setting up the container |
|Optional| `:/etc/pihole/pihole-FTL.db` | PiHole's `pihole-FTL.db` database file. Required if you want to use PiHole |
|Optional| `:/etc/pihole/dhcp.leases` | PiHole's `dhcp.leases` file. Required if you want to use PiHole `dhcp.leases` file. This has to be matched with a corresponding `DHCPLSS_paths_to_check` setting entry. (the path in the container must contain `pihole`)|
|Optional| `:/home/pi/pialert/front/api` | A simple [API endpoint](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/API.md) containing static (but regularly updated) json and other files. |
|Optional| `:/home/pi/pialert/front/plugins/<plugin>/ignore_plugin` | Map a file `ignore_plugin` to ignore a plugin. Plugins can be soft-disabled via settings. More in the [Plugin docs](/front/plugins/README.md). |
| ✅ | `:/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`)|
| | `:/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). |
### Config (`pialert.conf`)
### Modify the config (`pialert.conf`) only if UI is not available
- If unavailable, the app generates a default `pialert.conf` and `pialert.db` file on the first run.
- The preferred way is to manage the configuration via the Settings section in the UI.
- You can modify [pialert.conf](https://github.com/jokob-sk/Pi.Alert/tree/main/config) directly, if needed.
- If unavailable, the app generates a default `pialert.conf` and `pialert.db` file on the first run.
#### Important settings
@@ -81,6 +82,20 @@ There are 2 approaches how to get PiHole devices imported. Via the PiHole import
> [!NOTE]
> It's recommended to use the same schedule interval for all plugins responsible for discovering new devices.
#### 🧭 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 🙏
- 📄 [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)
- 📄 [시놀/헤놀에서 네트워크 스캐너 Pi.Alert Docker로 설치 및 사용하기 (Korean)](https://blog.dalso.org/article/%EC%8B%9C%EB%86%80-%ED%97%A4%EB%86%80%EC%97%90%EC%84%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%8A%A4%EC%BA%90%EB%84%88-pi-alert-docker%EB%A1%9C-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EC%82%AC%EC%9A%A9) (July 2023)
- 📄 [网络入侵探测器Pi.Alert (Chinese)](https://codeantenna.com/a/VgUvIAjZ7J) (May 2023)
- ▶ [Pi.Alert auf Synology & Docker by - Jürgen Barth (German)](https://www.youtube.com/watch?v=-ouvA2UNu-A) (March 2023)
- ▶ [Top Docker Container for Home Server Security - VirtualizationHowto (English)](https://www.youtube.com/watch?v=tY-w-enLF6Q) (March 2023)
- ▶ [Pi.Alert or WatchYourLAN can alert you to unknown devices appearing on your WiFi or LAN network - Danie van der Merwe (English)](https://www.youtube.com/watch?v=v6an9QG2xF0) (November 2022)
> Ordered by last update time.
### **Common issues**
@@ -88,7 +103,10 @@ There are 2 approaches how to get PiHole devices imported. Via the PiHole import
⚠ Check also common issues and [debugging tips](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/DEBUG_TIPS.md).
## 📄 Examples
> [!NOTE]
> You can bulk-update devices via the [CSV import method](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/DEVICES_BULK_EDITING.md).
## 📄 docker-compose.yml Examples
### Example 1
@@ -212,11 +230,7 @@ Courtesy of [pbek](https://github.com/pbek). The volume `pialert_db` is used by
## 🏅 Recognitions
Big thanks to <a href="https://github.com/Macleykun">@Macleykun</a> for help and tips&tricks for Dockerfile(s):
<a href="https://github.com/Macleykun">
<img src="https://avatars.githubusercontent.com/u/26381427?size=50">
</a>
Big thanks to <a href="https://github.com/Macleykun">@Macleykun</a> for help and tips&tricks for Dockerfile(s).
## ❤ Support me

View File

@@ -1,33 +1,144 @@
#!/bin/sh
/home/pi/pialert/dockerfiles/user-mapping.sh
#!/usr/bin/env bash
# # if custom variables not set we do not need to do anything
# if [ -n "${TZ}" ]; then
# FILECONF=/home/pi/pialert/config/pialert.conf
# if [ -f "$FILECONF" ]; then
# sed -ie "s|Europe/Berlin|${TZ}|g" /home/pi/pialert/config/pialert.conf
# else
# sed -ie "s|Europe/Berlin|${TZ}|g" /home/pi/pialert/back/pialert.conf_bak
# fi
# fi
echo "---------------------------------------------------------"
echo "[INSTALL] Run start.sh"
echo "---------------------------------------------------------"
if [ -n "${PORT}" ]; then
sed -ie 's/listen 20211/listen '${PORT}'/g' /etc/nginx/sites-available/default
fi
# I hope this will fix DB permission issues going forward
FILEDB=/home/pi/pialert/db/pialert.db
if [ -f "$FILEDB" ]; then
chown -R www-data:www-data /home/pi/pialert/db/pialert.db
INSTALL_DIR=/home/pi # Specify the installation directory here
# DO NOT CHANGE ANYTHING BELOW THIS LINE!
WEB_UI_DIR=/var/www/html/pialert
NGINX_CONFIG_FILE=/etc/nginx/conf.d/pialert.conf
OUI_FILE="/usr/share/arp-scan/ieee-oui.txt" # Define the path to ieee-oui.txt and ieee-iab.txt
FILEDB=$INSTALL_DIR/pialert/db/pialert.db
# DO NOT CHANGE ANYTHING ABOVE THIS LINE!
# if custom variables not set we do not need to do anything
if [ -n "${TZ}" ]; then
FILECONF=$INSTALL_DIR/pialert/config/pialert.conf
if [ -f "$FILECONF" ]; then
sed -ie "s|Europe/Berlin|${TZ}|g" $INSTALL_DIR/pialert/config/pialert.conf
else
sed -ie "s|Europe/Berlin|${TZ}|g" $INSTALL_DIR/pialert/back/pialert.conf_bak
fi
fi
chmod -R a+rw /home/pi/pialert/front/log
chmod -R a+rw /home/pi/pialert/config
# Check if script is run as root
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root. Please use 'sudo'."
exit 1
fi
/etc/init.d/php7.4-fpm start
# Run setup scripts
echo "[INSTALL] Run setup scripts"
"$INSTALL_DIR/pialert/dockerfiles/user-mapping.sh"
"$INSTALL_DIR/pialert/install/install_dependencies.sh" # if modifying this file transfer the chanegs into the root Dockerfile as well!
echo "[INSTALL] Setup NGINX"
# Remove default NGINX site if it is symlinked, or backup it otherwise
if [ -L /etc/nginx/sites-enabled/default ] ; then
echo "Disabling default NGINX site, removing sym-link in /etc/nginx/sites-enabled"
sudo rm /etc/nginx/sites-enabled/default
elif [ -f /etc/nginx/sites-enabled/default ]; then
echo "Disabling default NGINX site, moving config to /etc/nginx/sites-available"
sudo mv /etc/nginx/sites-enabled/default /etc/nginx/sites-available/default.bkp_pialert
fi
# Clear existing directories and files
if [ -d $WEB_UI_DIR ]; then
echo "Removing existing PiAlert web-UI"
sudo rm -R $WEB_UI_DIR
fi
if [ -f $NGINX_CONFIG_FILE ]; then
echo "Removing existing PiAlert NGINX config"
sudo rm $NGINX_CONFIG_FILE
fi
# create symbolic link to the pialert install directory
ln -s $INSTALL_DIR/pialert/front $WEB_UI_DIR
# create symbolic link to NGINX configuaration coming with PiAlert
sudo ln -s "$INSTALL_DIR/pialert/install/pialert.conf" /etc/nginx/conf.d/pialert.conf
# Use user-supplied port if set
if [ -n "${PORT}" ]; then
echo "Setting webserver to user-supplied port ($PORT)"
sudo sed -i 's/listen 20211/listen '"$PORT"'/g' /etc/nginx/conf.d/pialert.conf
fi
# Change web interface address if set
if [ -n "${LISTEN_ADDR}" ]; then
echo "Setting webserver to user-supplied address ($LISTEN_ADDR)"
sed -ie 's/listen /listen '"${LISTEN_ADDR}":'/g' /etc/nginx/conf.d/pialert.conf
fi
# Run the hardware vendors update at least once
echo "[INSTALL] Run the hardware vendors update"
# Check if ieee-oui.txt or ieee-iab.txt exist
if [ -f "$OUI_FILE" ]; then
echo "The file ieee-oui.txt exists. Skipping update_vendors.sh..."
else
echo "The file ieee-oui.txt does not exist. Running update_vendors..."
# Run the update_vendors.sh script
if [ -f "$INSTALL_DIR/pialert/back/update_vendors.sh" ]; then
"$INSTALL_DIR/pialert/back/update_vendors.sh"
else
echo "update_vendors.sh script not found in $INSTALL_DIR."
fi
fi
# Fixing file permissions
echo "[INSTALL] Fixing file permissions"
echo "[INSTALL] Fixing WEB_UI_DIR: $WEB_UI_DIR"
chmod -R a+rwx $WEB_UI_DIR
echo "[INSTALL] Fixing INSTALL_DIR: $INSTALL_DIR"
chmod -R a+rw $INSTALL_DIR/pialert/front/log
chmod -R a+rwx $INSTALL_DIR
echo "[INSTALL] Copy starter pialert.db and pialert.conf if they don't exist"
# Copy starter pialert.db and pialert.conf if they don't exist
cp -n "$INSTALL_DIR/pialert/back/pialert.conf" "$INSTALL_DIR/pialert/config/pialert.conf"
cp -n "$INSTALL_DIR/pialert/back/pialert.db" "$FILEDB"
echo "[INSTALL] Fixing permissions after copied starter config & DB"
if [ -f "$FILEDB" ]; then
chown -R www-data:www-data $FILEDB
fi
chmod -R a+rwx $INSTALL_DIR # second time after we copied the files
chmod -R a+rw $INSTALL_DIR/pialert/config
sudo chgrp -R www-data $INSTALL_DIR/pialert
# Check if buildtimestamp.txt doesn't exist
if [ ! -f "$INSTALL_DIR/pialert/front/buildtimestamp.txt" ]; then
# Create buildtimestamp.txt
date +%s > "$INSTALL_DIR/pialert/front/buildtimestamp.txt"
fi
# start PHP
/etc/init.d/php8.2-fpm start
/etc/init.d/nginx start
# cron -f
#python /home/pi/pialert/back/pialert.py
# echo "[DEBUG] DATA MONKEY VERSION ..."
python /home/pi/pialert/pialert/
# Start Nginx and your application to start at boot (if needed)
# systemctl start nginx
# systemctl enable nginx
# # systemctl enable pi-alert
# sudo systemctl restart nginx
# Activate the virtual python environment
source myenv/bin/activate
# Start the PiAlert python script
python $INSTALL_DIR/pialert/pialert/

View File

@@ -1,29 +1,39 @@
#!/bin/bash
#!/usr/bin/env bash
echo "---------------------------------------------------------"
echo "[INSTALL] Run user-mapping.sh"
echo "---------------------------------------------------------"
if [ -z "${USER}" ]; then
echo "We need USER to be set!"; exit 100
fi
# if both not set we do not need to do anything
if [ -z "${HOST_USER_ID}" -a -z "${HOST_USER_GID}" ]; then
if [ -z "${HOST_USER_ID}" ] && [ -z "${HOST_USER_GID}" ]; then
echo "Nothing to do here." ; exit 0
fi
# reset user_?id to either new id or if empty old (still one of above
# reset user_id to either new id or if empty old (still one of above
# might not be set)
USER_ID=${HOST_USER_ID:=$USER_ID}
USER_GID=${HOST_USER_GID:=$USER_GID}
LINE=$(grep -F "${USER}" /etc/passwd)
# replace all ':' with a space and create array
array=( ${LINE//:/ } )
array=( "${LINE//:/ }" )
# home is 5th element
USER_HOME=${array[4]}
# print debug output
echo USER_ID" ": "${USER_ID}";
echo USER_GID : "${USER_GID}";
echo USER_HOME: "${USER_HOME}";
echo TZ" ": "${TZ}";
sed -i -e "s/^${USER}:\([^:]*\):[0-9]*:[0-9]*/${USER}:\1:${USER_ID}:${USER_GID}/" /etc/passwd
sed -i -e "s/^${USER}:\([^:]*\):[0-9]*/${USER}:\1:${USER_GID}/" /etc/group
chown -R ${USER_ID}:${USER_GID} ${USER_HOME}
chown -R "${USER_ID}:${USER_GID} ${USER_HOME}"
exec su - "${USER}"
exec su - "${USER}"

View File

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

View File

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

74
docs/DEBUG_PLUGINS.md Executable file
View File

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

View File

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

View File

@@ -11,7 +11,7 @@ To edit device information:
> [!NOTE]
>
> [Bulk-edit devices](/docs/DEVICES_BULK_EDITING.md) by using the `CSV Export` functionality in the `Maintenance` section.
> [Bulk-edit devices](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/DEVICES_BULK_EDITING.md) by using the `CSV Export` functionality in the `Maintenance` section.
![Device Details][screen1]

38
docs/HW_INSTALL.md Normal file → Executable file
View File

@@ -1,20 +1,44 @@
# How to install PiAlert on the server hardware
To download and install PiAlert on the hardware/server directly use `curl` or `wget` commands.
To download and install PiAlert on the hardware/server directly use the `curl` or `wget` commands at the bottom of this page.
> [!NOTE]
> This is an Experimental feature 🧪 and it relies on community support.
> [!NOTE]
> This is an Experimental feature 🧪 and it relies on community support.
>
> There is no guarantee that the install script or any other script will gracefully handle other installed software.
> Data loss is a possibility, **it is recommended to install PiAlert using the supplied Docker image**.
A warning to the installation method below: Piping to bash is [controversial](https://pi-hole.net/2016/07/25/curling-and-piping-to-bash) and may
be dangerous, as you cannot see the code that's about to be executed on your system.
Alternatively you can download the installation script `install/install.sh` from the repository and check the code yourself (beware other scripts are
downloaded too - only from this repo).
PiAlert will be installed in `home/pi/pialert/` and run on port number `20211`.
## CURL
Some facts about what and where something will be changed/installed by the HW install setup (may not contain everything!):
- `/home/pi/pialert` directory will be deleted and newly created
- `/home/pi/pialert` will contain the whole repository (downloaded by `install/install.sh`)
- The default NGINX site `/etc/nginx/sites-enabled/default` will be disabled (sym-link deleted or backed up to `sites-available`)
- `/var/www/html/pialert` directory will be deleted and newly created
- `/etc/nginx/conf.d/pialert.conf` will be sym-linked to `/home/pi/pialert/install/pialert.conf`
- Some files (IEEE device vendors info, ...) will be created in the directory where the installation script is executed
## Limitations
- No system service is provided. PiAlert must be started using `/home/pi/pialert/dockerfiles/start.sh`.
- No checks for other running software is done.
- Only tested to work on Debian Bookworm (Debian 12).
- **EXPERIMENTAL** and not recommended way to install PiAlert.
## 📥 Installation via CURL
```bash
curl -o install.sh https://raw.githubusercontent.com/jokob-sk/Pi.Alert/main/install/install.sh && sudo chmod +x install.sh && sudo ./install.sh
```
## WGET
## 📥 Installation via WGET
```bash
wget https://raw.githubusercontent.com/jokob-sk/Pi.Alert/main/install/install.sh -O install.sh && sudo chmod +x install.sh && sudo ./install.sh
@@ -22,4 +46,4 @@ wget https://raw.githubusercontent.com/jokob-sk/Pi.Alert/main/install/install.sh
These commands will download the `install.sh` script from the GitHub repository, make it executable with `chmod`, and then run it using `./install.sh`.
Make sure you have the necessary permissions to execute the script.
Make sure you have the necessary permissions to execute the script.

View File

@@ -1,6 +1,6 @@
## Icons overview
Icons are used to visually distinguish devices in the app in most of the device listing tables and the [network tree](/docs/NETWORK_TREE.md). Currently only free [Font Awesome](https://fontawesome.com/search?o=r&m=free) icons (up-to v 6.4.0) are supported (I have an unblockable [sponsorship goal](https://github.com/sponsors/jokob-sk) to add the material design icon pack).
Icons are used to visually distinguish devices in the app in most of the device listing tables and the [network tree](/docs/NETWORK_TREE.md). Currently only free [Font Awesome](https://fontawesome.com/search?o=r&m=free) icons (up-to v 6.4.0) are supported.
![Raspberry Pi with a brand icon](/docs/img/ICONS/devices-icons.png)
@@ -8,6 +8,8 @@ Icons are used to visually distinguish devices in the app in most of the device
You can assign icons individually on each device in the Details tab.
![preview](/docs/img/ICONS/device_icons_preview.gif)
![Raspberry Pi device details](/docs/img/ICONS/device-icon.png)
- You can click into the `Icon` field or click the Pencil (2) icon in the above screenshot to enter any text. Only [free Font Awesome](https://fontawesome.com/search?o=r&m=free) icons in the following format will work:

View File

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

View File

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

View File

@@ -346,5 +346,134 @@ location ^~ /pialert/ {
```
## Traefik
> Submitted by [Isegrimm](https://github.com/Isegrimm) 🙏 (based on this [discussion](https://github.com/jokob-sk/Pi.Alert/discussions/449#discussioncomment-7281442))
Asuming the user already has a working Traefik setup, this is what's needed to make Pi.Alert work at a URL like www.domain.com/pialert/.
Note: Everything in these configs assumes '**www.domain.com**' as your domainname and '**section31**' as an arbitrary name for your certificate setup. You will have to substitute these with your own.
Also, I use the prefix '**pialert**'. If you want to use another prefix, change it in these files: dynamic.toml and default.
Content of my yaml-file (this is the generic Traefik config, which defines which ports to listen on, redirect http to https and sets up the certificate process).
It also contains Authelia, which I use for authentication.
This part contains nothing specific to Pi.Alert.
```yaml
version: '3.8'
services:
traefik:
image: traefik
container_name: traefik
command:
- "--api=true"
- "--api.insecure=true"
- "--api.dashboard=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.web.http.redirections.entryPoint.to=websecure"
- "--entrypoints.web.http.redirections.entryPoint.scheme=https"
- "--entrypoints.websecure.address=:443"
- "--providers.file.filename=/traefik-config/dynamic.toml"
- "--providers.file.watch=true"
- "--log.level=ERROR"
- "--certificatesresolvers.section31.acme.email=postmaster@domain.com"
- "--certificatesresolvers.section31.acme.storage=/traefik-config/acme.json"
- "--certificatesresolvers.section31.acme.httpchallenge=true"
- "--certificatesresolvers.section31.acme.httpchallenge.entrypoint=web"
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- /appl/docker/traefik/config:/traefik-config
depends_on:
- authelia
restart: unless-stopped
authelia:
container_name: authelia
image: authelia/authelia:latest
ports:
- "9091:9091"
volumes:
- /appl/docker/authelia:/config
restart: u
nless-stopped
```
Snippet of the dynamic.toml file (referenced in the yml-file above) that defines the config for Pi.Alert:
The following are self-defined keywords, everything else is traefik keywords:
- pialert-router
- pialert-service
- auth
- pialert-stripprefix
```toml
[http.routers]
[http.routers.pialert-router]
entryPoints = ["websecure"]
rule = "Host(`www.domain.com`) && PathPrefix(`/pialert`)"
service = "pialert-service"
middlewares = "auth,pialert-stripprefix"
[http.routers.pialert-router.tls]
certResolver = "section31"
[[http.routers.pialert-router.tls.domains]]
main = "www.domain.com"
[http.services]
[http.services.pialert-service]
[[http.services.pialert-service.loadBalancer.servers]]
url = "http://internal-ip-address:20211/"
[http.middlewares]
[http.middlewares.auth.forwardAuth]
address = "http://authelia:9091/api/verify?rd=https://www.domain.com/authelia/"
trustForwardHeader = true
authResponseHeaders = ["Remote-User", "Remote-Groups", "Remote-Name", "Remote-Email"]
[http.middlewares.pialert-stripprefix.stripprefix]
prefixes = "/pialert"
forceSlash = false
```
To make Pi.Alert work with this setup I modified the default file at `/etc/nginx/sites-available/default` in the docker container by copying it to my local filesystem, adding the changes as specified by [cvc90](https://github.com/cvc90) and mounting the new file into the docker container, overwriting the original one. By mapping the file instead of changing the file in-place, the changes persist if an updated dockerimage is pulled. This is also a downside when the default file is updated, so I only use this as a temporary solution, until the dockerimage is updated with this change.
Default-file:
```
server {
listen 80 default_server;
root /var/www/html;
index index.php;
#rewrite /pialert/(.*) / permanent;
add_header X-Forwarded-Prefix "/pialert" always;
proxy_set_header X-Forwarded-Prefix "/pialert";
location ~* \.php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_connect_timeout 75;
fastcgi_send_timeout 600;
fastcgi_read_timeout 600;
}
}
```
Mapping the updated file (on the local filesystem at `/appl/docker/pialert/default`) into the docker container:
```bash
docker run -d --rm --network=host \
--name=pi.alert \
-v /appl/docker/pialert/config:/home/pi/pialert/config \
-v /appl/docker/pialert/db:/home/pi/pialert/db \
-v /appl/docker/pialert/default:/etc/nginx/sites-available/default \
-e TZ=Europe/Amsterdam \
-e PORT=20211 \
jokobsk/pi.alert:latest
```

View File

@@ -10,7 +10,7 @@ The source of truth for user-defined values is the `pialert.conf` file. Editing
#### Settings database table
The `Settings` database table contains settings for App run purposes. The table is recreated every time the App restarts. The settings are loaded from the source-of-truth, that is the `pialert.conf` file. A high-level overview on the databse structure can be found in the [database documentation](/docs/DATABASE.md).
The `Settings` database table contains settings for App run purposes. The table is recreated every time the App restarts. The settings are loaded from the source-of-truth, that is the `pialert.conf` file. A high-level overview on the database structure can be found in the [database documentation](/docs/DATABASE.md).
#### table_settings.json

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

View File

@@ -173,6 +173,7 @@
@media (max-width: 767px) {
.main-header .logo {
width: 100%;
display:none;
}
.main-header .navbar {
@@ -584,8 +585,24 @@ height: 50px;
.infobox_label {
font-size: 16px !important;
}
/* --------------------------------------------------------- */
/* report */
/* --------------------------------------------------------- */
/*settings*/
#notificationData textarea{
width: 100%;
}
#notificationData pre {outline: 1px solid #ccc; padding: 5px; margin: 5px; }
.string { color: green; }
.number { color: darkorange; }
.boolean { color: blue; }
.null { color: magenta; }
.key { color: red; }
/* --------------------------------------------------------- */
/* settings */
/* --------------------------------------------------------- */
@media (max-width: 767px) {
/* hide on mobile */
@@ -623,6 +640,52 @@ height: 50px;
display: none;
}
.settingswrap .enabled-disabled-icon
{
float: right;
}
.settings-group
{
font-size: 20px;
padding-top: 7px;
padding-bottom: 9px;
}
.overview-section .small-box .icon
{
font-size: 38px;
top:0px;
}
.overview-section
{
border: solid;
border-width: medium;
border-width: medium;
border-width: 1px;
border-radius: 15px;
margin-bottom: 3px;
}
.settings-group i{
font-size: 16px;
}
.overview-group
{
font-size: 20px;
padding-top: 7px;
padding-bottom: 9px;
}
.overview-group i{
font-size: 16px;
}
.table_row {
padding: 3px;
width:100%;
@@ -672,6 +735,18 @@ height: 50px;
/* Settings */
#settingsPage .overview-setting-value{
display:unset;
}
#settingsPage .panel-title{
/* display: inline-block; */
/* width: 120px; */
white-space: nowrap;
overflow: hidden !important;
text-overflow: ellipsis;
}
.settings_content {
padding: 10px;
/* background-color: #272c30; */
@@ -718,6 +793,10 @@ input[readonly] {
}
/* Devices */
#txtIconFA {
min-width: 18px;
}
.drp-edit
{
cursor: pointer;
@@ -795,7 +874,8 @@ input[readonly] {
#networkTree .netPort
{
float:left;
display:inline;
display:inline;
text-align: center;
}
#networkTree .portBckgIcon
@@ -816,14 +896,14 @@ input[readonly] {
{
width: 25px;;
float:left;
display:inline;
display:inline;
text-align: center;
}
#networkTree .netCollapse
{
display: block;
position: absolute;
margin-left: 170px;
top: -3px;
margin-left: 170px;
font-size: large;
left: -15px;
}
@@ -883,30 +963,43 @@ input[readonly] {
/*Hidden special button*/
@media (max-width: 464px) {
@media (max-width: 365px) {
#back-button {
display: none;
}
}
@media (max-width: 432px) {
@media (max-width: 335px) {
#next-button {
display: none;
}
}
@media (max-width: 400px) {
@media (max-width: 300px) {
#reload-button {
display: none;
}
}
@media (max-width: 365px) {
@media (max-width: 300px) {
#fullscreen-button {
display: none;
}
}
@media (max-width: 500px) {
.header-server-time {
display: none;
}
}
#settingsPage .small-box .inner .card-title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* -----------------------------------------------------------------------------
Spin
----------------------------------------------------------------------------- */

View File

@@ -149,7 +149,7 @@
<div class="input-group">
<input class="form-control" id="txtName" type="text" value="--">
<span class="input-group-addon"><i class="fa fa-pencil pointer" onclick="editDrp('txtName');"></i></span>
</div>
</div>
</div>
</div>
@@ -196,9 +196,9 @@
</label>
<div class="col-sm-9">
<div class="input-group">
<input class="form-control" id="txtIcon" type="text" value="--">
<span class="input-group-addon"><i class="fa" id="txtIconFA" onclick="editDrp('txtIcon');"></i></span>
<input class="form-control" id="txtIcon" type="text" value="--">
<span class="input-group-addon" title='<?= lang('DevDetail_button_OverwriteIcons_Tooltip');?>'><i class="fa fa-copy pointer" onclick="askOverwriteIconType();"></i></span>
<span class="input-group-addon"><i class="fa fa-pencil pointer" onclick="editDrp('txtIcon');"></i></span>
<div class="input-group-btn">
<button type="button" class="btn btn-info dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
<span class="fa fa-caret-down"></span>
@@ -325,7 +325,7 @@
<div class="form-group" title="<?= lang('DevDetail_Network_Node_hover');?>">
<label class="col-sm-3 control-label"><?= lang('DevDetail_MainInfo_Network');?></label>
<div class="col-sm-9">
<div class="input-group">
<div class="input-group parentNetworkNode">
<input class="form-control" id="txtNetworkNodeMac" type="text" value="--">
<span class="input-group-addon"><i title="<?= lang('DevDetail_GoToNetworkNode');?>" class="fa fa-square-up-right pointer" onclick="goToNetworkNode('txtNetworkNodeMac');"></i></span>
@@ -644,14 +644,15 @@ if ($ENABLED_DARKMODE === True) {
// ------------------------------------------------------------
function getDevicesList()
{
// Read cache
devicesList = getCache('devicesList');
// Read cache (skip cookie expiry check)
devicesList = getCache('devicesListAll_JSON', true);
if (devicesList != '') {
devicesList = JSON.parse (devicesList);
} else {
devicesList = [];
}
return devicesList;
}
@@ -749,6 +750,11 @@ function main () {
}
});
// Show device icon as it changes
$('#txtIcon').on('change input', function() {
$('#txtIconFA').removeClass().addClass(`fa fa-${$(this).val()} pointer`)
});
});
});
@@ -1172,7 +1178,7 @@ function getDeviceData (readAllData=false) {
$("body").css ("cursor", "progress");
}
// get data from server
// get data from server
$.get('php/server/devices.php?action=getDeviceData&mac='+ mac + '&period='+ period, function(data) {
var deviceData = JSON.parse(data);
@@ -1278,14 +1284,15 @@ function getDeviceData (readAllData=false) {
history.pushState(null, '', newRelativePathQuery);
getSessionsPresenceEvents();
devicesList = getDevicesList();
devicesList = getDevicesList();
$('#txtMAC').val (deviceData['dev_MAC']);
$('#txtName').val (deviceData['dev_Name']);
$('#txtOwner').val (deviceData['dev_Owner']);
$('#txtDeviceType').val (deviceData['dev_DeviceType']);
$('#txtVendor').val (deviceData['dev_Vendor']);
$('#txtIcon').val (initDefault(deviceData['dev_Icon'], 'laptop'));
$('#txtIcon').val (initDefault(deviceData['dev_Icon'], 'laptop'));
$('#txtIcon').trigger('change')
if (deviceData['dev_Favorite'] == 1) {$('#chkFavorite').iCheck('check');} else {$('#chkFavorite').iCheck('uncheck');}
$('#txtGroup').val (deviceData['dev_Group']);
@@ -1295,8 +1302,8 @@ function getDeviceData (readAllData=false) {
$('#txtNetworkNodeMac').attr ('data-mynodemac', deviceData['dev_Network_Node_MAC_ADDR']);
$('#txtNetworkPort').val (deviceData['dev_Network_Node_port']);
// disabling network node configuration if root Internet node
$('#txtNetworkNodeMac').prop('readonly', mac == 'Internet' );
$('#txtNetworkPort').prop('readonly', mac == 'Internet' );
toggleNetworkConfiguration(mac == 'Internet')
$('#txtFirstConnection').val (deviceData['dev_FirstConnection']);
$('#txtLastConnection').val (deviceData['dev_LastConnection']);
@@ -1318,7 +1325,8 @@ function getDeviceData (readAllData=false) {
}
// Check if device is part of the devicesList
pos = devicesList.findIndex(item => item.rowid == deviceData['rowid']);
pos = devicesList.findIndex(item => item.rowid == deviceData['rowid']);
if (pos == -1) {
devicesList.push({"rowid" : deviceData['rowid'], "mac" : deviceData['dev_MAC'], "name": deviceData['dev_Name'], "type": deviceData['dev_DeviceType']});
pos=0;
@@ -1392,14 +1400,12 @@ function performSwitch(direction)
// get new mac from the devicesList. Don't change to the commented out line below, the mac query string in the URL isn't updated yet!
// mac = params.mac;
mac = devicesList[pos].mac.toString();
mac = devicesList[pos].dev_MAC.toString();
setCache("piaDeviceDetailsMac", mac);
getDeviceData (true);
// reload current tab
reloadTab()
}
// -----------------------------------------------------------------------------
@@ -1453,6 +1459,9 @@ function setDeviceData (direction='', refreshCallback='') {
window.onbeforeunload = null;
somethingChanged = false;
// refresh API
updateApi()
// Callback fuction
if (typeof refreshCallback == 'function') {
refreshCallback(direction);
@@ -1460,6 +1469,25 @@ function setDeviceData (direction='', refreshCallback='') {
});
}
// --------------------------------------------------------
// Calls a backend function to add a front-end event to an execution queue
function updateApi()
{
// value has to be in format event|param. e.g. run|ARPSCAN
action = `update_api|devices`
$.ajax({
method: "POST",
url: "php/server/util.php",
data: { function: "addToExecutionQueue", action: action },
success: function(data, textStatus) {
console.log(data)
}
})
}
// -----------------------------------------------------------------------------
function askSkipNotifications () {
// Check MAC
@@ -1626,36 +1654,10 @@ function deleteDevice () {
// Deactivate controls
$('#panDetails :input').attr('disabled', true);
// refresh API
updateApi()
}
// -----------------------------------------------------------------------------
function askDeleteDevice () {
// Check MAC
if (mac == '') {
return;
}
// Ask delete device
showModalWarning ('Delete Device', 'Are you sure you want to delete this device?<br>(maybe you prefer to archive it)',
'<?= lang('Gen_Cancel');?>', '<?= lang('Gen_Delete');?>', 'deleteDevice');
}
// -----------------------------------------------------------------------------
function deleteDevice () {
// Check MAC
if (mac == '') {
return;
}
// Delete device
$.get('php/server/devices.php?action=deleteDevice&mac='+ mac, function(msg) {
showMessage (msg);
});
// Deactivate controls
$('#panDetails :input').attr('disabled', true);
}
// -----------------------------------------------------------------------------
function getSessionsPresenceEvents () {
@@ -1702,6 +1704,7 @@ function setTextValue (textElement, textValue) {
$('#'+textElement).attr ('data-myvalue', textValue);
$('#'+textElement).val (textValue);
}
$('#'+textElement).trigger('change')
}
// -----------------------------------------------------------------------------
@@ -1796,15 +1799,27 @@ window.onload = function async()
{
initializeTabsNew();
reloadTab();
}
//-----------------------------------------------------------------------------------
function reloadTab()
// Disables network configuration for the root node
function toggleNetworkConfiguration(disable)
{
// tab loaded without switching
$('#txtNetworkNodeMac').prop('readonly', true ); // disable direct input as should only be selected via the dropdown
if(disable)
{
// $('#txtNetworkNodeMac').val(getString('Network_Root_Unconfigurable'));
// $('#txtNetworkPort').val(getString('Network_Root_Unconfigurable'));
$('#txtNetworkPort').prop('readonly', true );
$('.parentNetworkNode .input-group-btn').hide();
}
else
{
$('#txtNetworkPort').prop('readonly', false );
$('.parentNetworkNode .input-group-btn').show();
}
}
</script>

View File

@@ -42,7 +42,7 @@
<!-- top small box 1 ------------------------------------------------------- -->
<div class="row">
<div class="col-lg-2 col-sm-4 col-xs-6">
<a href="#" onclick="javascript: getDevicesList('all');">
<a href="#" onclick="javascript: initializeDatatable('all');">
<div class="small-box bg-aqua">
<div class="inner"><h3 id="devicesAll"> -- </h3>
<p class="infobox_label"><?= lang('Device_Shortcut_AllDevices');?></p>
@@ -54,7 +54,7 @@
<!-- top small box 2 ------------------------------------------------------- -->
<div class="col-lg-2 col-sm-4 col-xs-6">
<a href="#" onclick="javascript: getDevicesList('connected');">
<a href="#" onclick="javascript: initializeDatatable('connected');">
<div class="small-box bg-green">
<div class="inner"><h3 id="devicesConnected"> -- </h3>
<p class="infobox_label"><?= lang('Device_Shortcut_Connected');?></p>
@@ -66,7 +66,7 @@
<!-- top small box 3 ------------------------------------------------------- -->
<div class="col-lg-2 col-sm-4 col-xs-6">
<a href="#" onclick="javascript: getDevicesList('favorites');">
<a href="#" onclick="javascript: initializeDatatable('favorites');">
<div class="small-box bg-yellow">
<div class="inner"><h3 id="devicesFavorites"> -- </h3>
<p class="infobox_label"><?= lang('Device_Shortcut_Favorites');?></p>
@@ -78,7 +78,7 @@
<!-- top small box 4 ------------------------------------------------------- -->
<div class="col-lg-2 col-sm-4 col-xs-6">
<a href="#" onclick="javascript: getDevicesList('new');">
<a href="#" onclick="javascript: initializeDatatable('new');">
<div class="small-box bg-yellow">
<div class="inner"><h3 id="devicesNew"> -- </h3>
<p class="infobox_label"><?= lang('Device_Shortcut_NewDevices');?></p>
@@ -90,7 +90,7 @@
<!-- top small box 5 ------------------------------------------------------- -->
<div class="col-lg-2 col-sm-4 col-xs-6">
<a href="#" onclick="javascript: getDevicesList('down');">
<a href="#" onclick="javascript: initializeDatatable('down');">
<div class="small-box bg-red">
<div class="inner"><h3 id="devicesDown"> -- </h3>
<p class="infobox_label"><?= lang('Device_Shortcut_DownAlerts');?></p>
@@ -102,7 +102,7 @@
<!-- top small box 6 ------------------------------------------------------- -->
<div class="col-lg-2 col-sm-4 col-xs-6">
<a href="#" onclick="javascript: getDevicesList('archived');">
<a href="#" onclick="javascript: initializeDatatable('archived');">
<div class="small-box bg-gray top_small_box_gray_text">
<div class="inner"><h3 id="devicesArchived"> -- </h3>
<p class="infobox_label"><?= lang('Device_Shortcut_Archived');?></p>
@@ -198,9 +198,30 @@
var tableRows = 10;
var tableOrder = [[3,'desc'], [0,'asc']];
var tableColumnHide = [];
var columnsStr = '[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18]';
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')
];
// Read parameters & Initialize components
main();
@@ -233,29 +254,6 @@ function main () {
// save the columns order in the Devices page
tableColumnOrder = numberArrayFromString(data);
//initialize the table headers in the correct order
var headersDefaultOrder = [ '<?= lang('Device_TableHead_Name');?>',
'<?= lang('Device_TableHead_Owner');?>',
'<?= lang('Device_TableHead_Type');?>',
'<?= lang('Device_TableHead_Icon');?>',
'<?= lang('Device_TableHead_Favorite');?>',
'<?= lang('Device_TableHead_Group');?>',
'<?= lang('Device_TableHead_FirstSession');?>',
'<?= lang('Device_TableHead_LastSession');?>',
'<?= lang('Device_TableHead_LastIP');?>',
'<?= lang('Device_TableHead_MAC');?>',
'<?= lang('Device_TableHead_Status');?>',
'<?= lang('Device_TableHead_MAC_full');?>',
'<?= lang('Device_TableHead_LastIPOrder');?>',
'<?= lang('Device_TableHead_Rowid');?>',
'<?= lang('Device_TableHead_Parent_MAC');?>',
'<?= lang('Device_TableHead_Connected_Devices');?>',
'<?= lang('Device_TableHead_Location');?>',
'<?= lang('Device_TableHead_Vendor');?>'
];
html = '';
for(index = 0; index < tableColumnOrder.length; index++)
@@ -290,8 +288,12 @@ function main () {
initializeDatatable();
// query data
getDevicesTotals();
getDevicesList (deviceStatus);
getDevicesTotals();
// check if dat outdated and show spinner if so
handleLoadingDialog()
});
});
});
@@ -299,8 +301,6 @@ function main () {
}
// -----------------------------------------------------------------------------
var tableColumnHide = [];
// mapping the default order to the user specified one
function mapIndx(oldIndex)
{
@@ -314,8 +314,78 @@ function mapIndx(oldIndex)
}
// -----------------------------------------------------------------------------
// 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 'connected':
return item.dev_PresentLastScan === 1;
case 'favorites':
return item.dev_Favorite === 1;
case 'new':
return item.dev_NewDevice === 1;
case 'down':
return item.dev_PresentLastScan === 0 && item.dev_AlertDeviceDown !== 0;
case 'archived':
return item.dev_Archived === 1;
default:
return true; // Include all items for unknown statuses
}
});
}
// -----------------------------------------------------------------------------
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"
}
// -----------------------------------------------------------------------------
function initializeDatatable (status) {
// Save status selected
deviceStatus = status;
// Define color & title for the status selected
switch (deviceStatus) {
case 'all': 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;
case 'down': tableTitle = getString('Device_Shortcut_DownAlerts'); color = 'red'; break;
case 'archived': tableTitle = getString('Device_Shortcut_Archived'); color = 'gray'; break;
default: tableTitle = getString('Device_Shortcut_Devices'); color = 'gray'; break;
}
// Set title and color
$('#tableDevicesTitle')[0].className = 'box-title text-'+ color;
$('#tableDevicesBox')[0].className = 'box box-'+ color;
$('#tableDevicesTitle').html (tableTitle);
function initializeDatatable () {
for(i = 0; i < tableColumnOrder.length; i++)
{
// hide this column if not in the tableColumnVisible variable (we need to keep the MAC address (index 11) for functionality reasons)
@@ -324,143 +394,203 @@ function initializeDatatable () {
tableColumnHide.push(mapIndx(tableColumnOrder[i]));
}
}
var table=
$('#tableDevices').DataTable({
'paging' : true,
'lengthChange' : true,
'lengthMenu' : [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, '<?= lang('Device_Tablelenght_all');?>']],
'searching' : true,
'ordering' : true,
'info' : true,
'autoWidth' : false,
// Parameters
'pageLength' : tableRows,
'order' : tableOrder,
// 'order' : [[3,'desc'], [0,'asc']],
'columnDefs' : [
{visible: false, targets: tableColumnHide },
{className: 'text-center', targets: [mapIndx(3), mapIndx(4), mapIndx(9), mapIndx(10), mapIndx(15)] },
{width: '80px', targets: [mapIndx(6), mapIndx(7), mapIndx(15)] },
{width: '30px', targets: [mapIndx(10), mapIndx(13)] },
{orderData: [mapIndx(12)], targets: mapIndx(8) },
// Device Name
{targets: [mapIndx(0)],
'createdCell': function (td, cellData, rowData, row, col) {
$(td).html ('<b class="anonymizeDev"><a href="deviceDetails.php?mac='+ rowData[mapIndx(11)] +'" class="">'+ cellData +'</a></b>');
} },
// Connected Devices
{targets: [mapIndx(15)],
'createdCell': function (td, cellData, rowData, row, col) {
$(td).html ('<b><a href="./network.php?mac='+ rowData[mapIndx(11)] +'" class="">'+ cellData +'</a></b>');
} },
// Icon
{targets: [mapIndx(3)],
'createdCell': function (td, cellData, rowData, row, col) {
if (!emptyArr.includes(cellData)){
$(td).html ('<i class="fa fa-'+cellData+' " style="font-size:16px"></i>');
} else {
$(td).html ('');
}
} },
// Full MAC
{targets: [mapIndx(11)],
'createdCell': function (td, cellData, rowData, row, col) {
if (!emptyArr.includes(cellData)){
$(td).html ('<span class="anonymizeMac">'+cellData+'</span>');
} else {
$(td).html ('');
}
} },
// IP address
{targets: [mapIndx(12)],
'createdCell': function (td, cellData, rowData, row, col) {
if (!emptyArr.includes(cellData)){
$(td).html ('<span class="anonymizeIp">'+cellData+'</span>');
} else {
$(td).html ('');
}
} },
// Favorite
{targets: [mapIndx(4)],
'createdCell': function (td, cellData, rowData, row, col) {
if (cellData == 1){
$(td).html ('<i class="fa fa-star text-yellow" style="font-size:16px"></i>');
} else {
$(td).html ('');
}
} },
// Dates
{targets: [mapIndx(6), mapIndx(7)],
'createdCell': function (td, cellData, rowData, row, col) {
$(td).html (translateHTMLcodes (cellData));
} },
// Random MAC
{targets: [mapIndx(9)],
'createdCell': function (td, cellData, rowData, row, col) {
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 {
$(td).html ('');
}
} },
// Status color
{targets: [mapIndx(10)],
'createdCell': function (td, cellData, rowData, row, col) {
switch (cellData) {
case 'Down': color='red'; break;
case 'New': color='yellow'; break;
case 'On-line': color='green'; break;
case 'Off-line': color='gray text-white'; break;
case 'Archived': color='gray text-white'; break;
default: color='aqua'; break;
};
$(td).html ('<a href="deviceDetails.php?mac='+ rowData[mapIndx(11)] +'" class="badge bg-'+ color +'">'+ cellData.replace('-', '') +'</a>');
} },
],
$.get('api/table_devices.json?nocache=' + Date.now(), function(result) {
// Processing
'processing' : true,
'language' : {
processing: '<table> <td width="130px" align="middle">Loading...</td><td><i class="ion ion-ios-loop-strong fa-spin fa-2x fa-fw"></td> </table>',
emptyTable: 'No data',
"lengthMenu": "<?= lang('Device_Tablelenght');?>",
"search": "<?= lang('Device_Searchbox');?>: ",
"paginate": {
"next": "<?= lang('Device_Table_nav_next');?>",
"previous": "<?= lang('Device_Table_nav_prev');?>"
},
"info": "<?= lang('Device_Table_info');?>",
// Filter the data based on deviceStatus
var filteredData = filterDataByStatus(result.data, deviceStatus);
// Convert JSON data into the desired format
var dataArray = {
data: filteredData.map(function(item) {
var originalRow = [
item.dev_Name || "",
item.dev_Owner || "",
item.dev_DeviceType || "",
item.dev_Icon || "",
item.dev_Favorite || "",
item.dev_Group || "",
// ---
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
getDeviceStatus(item) || "",
item.dev_MAC || "", // hidden
formatIPlong(item.dev_LastIP) || "", // IP orderable
item.rowid || "",
item.dev_Network_Node_MAC_ADDR || "",
item.connected_devices || 0,
item.dev_Location || "",
item.dev_Vendor || "",
item.dev_Network_Node_port || 0
];
var newRow = [];
// reorder data based on user-defined columns order
for (index = 0; index < tableColumnOrder.length; index++) {
newRow.push(originalRow[tableColumnOrder[index]]);
}
return newRow;
})
};
// TODO displayed columns
// Check if the DataTable already exists
if ($.fn.dataTable.isDataTable('#tableDevices')) {
// The DataTable exists, so destroy it
var table = $('#tableDevices').DataTable();
table.clear().destroy();
}
});
// Save cookie Rows displayed, and Parameters rows & order
$('#tableDevices').on( 'length.dt', function ( e, settings, len ) {
setParameter (parTableRows, len);
} );
$('#tableDevices').on( 'order.dt', function () {
setParameter (parTableOrder, JSON.stringify (table.order()) );
setCache ('devicesList', getDevicesFromTable(table) );
} );
var table=
$('#tableDevices').DataTable({
'data' : dataArray["data"],
'paging' : true,
'lengthChange' : true,
'lengthMenu' : [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, getString('Device_Tablelenght_all')]],
'searching' : true,
$('#tableDevices').on( 'search.dt', function () {
setCache ('devicesList', getDevicesFromTable(table) );
} );
'ordering' : true,
'info' : true,
'autoWidth' : false,
// Parameters
'pageLength' : tableRows,
'order' : tableOrder,
'columnDefs' : [
{visible: false, targets: tableColumnHide },
{className: 'text-center', targets: [mapIndx(3), mapIndx(4), mapIndx(9), mapIndx(10), mapIndx(15)] },
{width: '80px', targets: [mapIndx(6), mapIndx(7), mapIndx(15)] },
{width: '30px', targets: [mapIndx(10), mapIndx(13)] },
{orderData: [mapIndx(12)], targets: mapIndx(8) },
// Device Name
{targets: [mapIndx(0)],
'createdCell': function (td, cellData, rowData, row, col) {
// console.log(cellData)
$(td).html ('<b class="anonymizeDev"><a href="deviceDetails.php?mac='+ rowData[mapIndx(11)] +'" class="">'+ cellData +'</a></b>');
} },
// Connected Devices
{targets: [mapIndx(15)],
'createdCell': function (td, cellData, rowData, row, col) {
$(td).html ('<b><a href="./network.php?mac='+ rowData[mapIndx(11)] +'" class="">'+ cellData +'</a></b>');
} },
// Icon
{targets: [mapIndx(3)],
'createdCell': function (td, cellData, rowData, row, col) {
if (!emptyArr.includes(cellData)){
$(td).html ('<i class="fa fa-'+cellData+' " style="font-size:16px"></i>');
} else {
$(td).html ('');
}
} },
// Full MAC
{targets: [mapIndx(11)],
'createdCell': function (td, cellData, rowData, row, col) {
if (!emptyArr.includes(cellData)){
$(td).html ('<span class="anonymizeMac">'+cellData+'</span>');
} else {
$(td).html ('');
}
} },
// IP address
{targets: [mapIndx(12)],
'createdCell': function (td, cellData, rowData, row, col) {
if (!emptyArr.includes(cellData)){
$(td).html ('<span class="anonymizeIp">'+cellData+'</span>');
} else {
$(td).html ('');
}
} },
// Favorite
{targets: [mapIndx(4)],
'createdCell': function (td, cellData, rowData, row, col) {
if (cellData == 1){
$(td).html ('<i class="fa fa-star text-yellow" style="font-size:16px"></i>');
} else {
$(td).html ('');
}
} },
// Dates
{targets: [mapIndx(6), mapIndx(7)],
'createdCell': function (td, cellData, rowData, row, col) {
$(td).html (translateHTMLcodes (cellData));
} },
// Random MAC
{targets: [mapIndx(9)],
'createdCell': function (td, cellData, rowData, row, col) {
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 {
$(td).html ('');
}
} },
// Status color
{targets: [mapIndx(10)],
'createdCell': function (td, cellData, rowData, row, col) {
// console.log(cellData)
switch (cellData) {
case 'Down': color='red'; break;
case 'New': color='yellow'; break;
case 'On-line': color='green'; break;
case 'Off-line': color='gray text-white'; break;
case 'Archived': color='gray text-white'; break;
default: color='aqua'; break;
};
$(td).html ('<a href="deviceDetails.php?mac='+ rowData[mapIndx(11)] +'" class="badge bg-'+ color +'">'+ cellData.replace('-', '') +'</a>');
} },
],
// Processing
'processing' : true,
'language' : {
processing: '<table> <td width="130px" align="middle">Loading...</td><td><i class="ion ion-ios-loop-strong fa-spin fa-2x fa-fw"></td> </table>',
emptyTable: 'No data',
"lengthMenu": "<?= lang('Device_Tablelenght');?>",
"search": "<?= lang('Device_Searchbox');?>: ",
"paginate": {
"next": "<?= lang('Device_Table_nav_next');?>",
"previous": "<?= lang('Device_Table_nav_prev');?>"
},
"info": "<?= lang('Device_Table_info');?>",
}
});
// Save cookie Rows displayed, and Parameters rows & order
$('#tableDevices').on( 'length.dt', function ( e, settings, len ) {
setParameter (parTableRows, len);
} );
$('#tableDevices').on( 'order.dt', function () {
setParameter (parTableOrder, JSON.stringify (table.order()) );
setCache ('devicesList', getDevicesFromTable(table) );
} );
$('#tableDevices').on( 'search.dt', function () {
setCache ('devicesList', getDevicesFromTable(table) );
} );
});
};
@@ -513,54 +643,25 @@ function getDevicesTotals () {
} );
}
// -----------------------------------------------------------------------------
function getDeviceColumns () {
}
// -----------------------------------------------------------------------------
function getDevicesList (status) {
// Save status selected
deviceStatus = status;
// Define color & title for the status selected
switch (deviceStatus) {
case 'all': tableTitle = '<?= lang('Device_Shortcut_AllDevices');?>'; color = 'aqua'; break;
case 'connected': tableTitle = '<?= lang('Device_Shortcut_Connected');?>'; color = 'green'; break;
case 'favorites': tableTitle = '<?= lang('Device_Shortcut_Favorites');?>'; color = 'yellow'; break;
case 'new': tableTitle = '<?= lang('Device_Shortcut_NewDevices');?>'; color = 'yellow'; break;
case 'down': tableTitle = '<?= lang('Device_Shortcut_DownAlerts');?>'; color = 'red'; break;
case 'archived': tableTitle = '<?= lang('Device_Shortcut_Archived');?>'; color = 'gray'; break;
default: tableTitle = '<?= lang('Device_Shortcut_Devices');?>'; color = 'gray'; break;
}
// Set title and color
$('#tableDevicesTitle')[0].className = 'box-title text-'+ color;
$('#tableDevicesBox')[0].className = 'box box-'+ color;
$('#tableDevicesTitle').html (tableTitle);
// Define new datasource URL and reload
$('#tableDevices').DataTable().ajax.url(
'php/server/devices.php?action=getDevicesList&status=' + deviceStatus).load();
};
function handleLoadingDialog()
{
$.get('api/app_state.json?nocache=' + Date.now(), function(appState) {
$.get('log/execution_queue.log?nocache=' + Date.now(), function(data) {
console.log(appState["showSpinner"])
if(appState["showSpinner"])
{
showSpinner("settings_old")
if(data.includes("update_api|devices"))
{
showSpinner("devices_old")
setTimeout("handleLoadingDialog()", 1000);
} else
} else if ($("#loadingSpinner").is(":visible"))
{
hideSpinner()
hideSpinner();
location.reload();
}
})
})
}

View File

@@ -304,6 +304,32 @@ function showMessage (textMessage="") {
}
// -----------------------------------------------------------------------------
// String utilities
// -----------------------------------------------------------------------------
function jsonSyntaxHighlight(json) {
if (typeof json != 'string') {
json = JSON.stringify(json, undefined, 2);
}
json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
var cls = 'number';
if (/^"/.test(match)) {
if (/:$/.test(match)) {
cls = 'key';
} else {
cls = 'string';
}
} else if (/true|false/.test(match)) {
cls = 'boolean';
} else if (/null/.test(match)) {
cls = 'null';
}
return '<span class="' + cls + '">' + match + '</span>';
});
}
// -----------------------------------------------------------------------------
// General utilities
// -----------------------------------------------------------------------------
@@ -323,6 +349,7 @@ function sanitize(data)
// -----------------------------------------------------------------------------
function numberArrayFromString(data)
{
console.log(data)
data = JSON.parse(sanitize(data));
return data.replace(/\[|\]/g, '').split(',').map(Number);
}
@@ -487,7 +514,46 @@ function getNameByMacAddress(macAddress) {
}
// -----------------------------------------------------------------------------
//
// A function used to make the IP address orderable
function isValidIPv6(ipAddress) {
// Regular expression for IPv6 validation
const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}$|^([0-9a-fA-F]{1,4}:){1,7}:|^([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}$|^([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}$|^([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}$|^([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}$|^([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}$|^[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})$/;
return ipv6Regex.test(ipAddress);
}
function formatIPlong(ipAddress) {
if (ipAddress.includes(':') && isValidIPv6(ipAddress)) {
const parts = ipAddress.split(':');
return parts.reduce((acc, part, index) => {
if (part === '') {
const remainingGroups = 8 - parts.length + 1;
return acc << (16 * remainingGroups);
}
const hexValue = parseInt(part, 16);
return acc | (hexValue << (112 - index * 16));
}, 0);
} else {
// Handle IPv4 address
const parts = ipAddress.split('.');
if (parts.length !== 4) {
console.log("⚠ Invalid IPv4 address: " + ipAddress);
return -1; // or any other default value indicating an error
}
return (parseInt(parts[0]) << 24) |
(parseInt(parts[1]) << 16) |
(parseInt(parts[2]) << 8) |
parseInt(parts[3]);
}
}
// -----------------------------------------------------------------------------
// 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
function getDeviceDataByMacAddress(macAddress, dbColumn) {
const sessionDataKey = 'devicesListAll_JSON';
@@ -534,6 +600,15 @@ function isEmpty(value)
return emptyArr.includes(value)
}
// -----------------------------------------------------------------------------
// Generate a GUID
function getGuid() {
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
}
// -----------------------------------------------------------------------------
// Loading Spinner overlay
// -----------------------------------------------------------------------------

166
front/js/settings_utils.js Executable file
View File

@@ -0,0 +1,166 @@
// -------------------------------------------------------------------
// Get all plugin prefixes of a given type
function getPluginsByType(pluginsData, pluginType, onlyEnabled)
{
var result = []
pluginsData.forEach((plug) => {
if(plug.plugin_type == pluginType)
{
// collect all, or if only enabled, check if NOT disabled
if (onlyEnabled == false || (onlyEnabled && getSetting(plug.unique_prefix + '_RUN') != 'disabled')) {
result.push(plug.unique_prefix)
}
}
});
return result;
}
// -------------------------------------------------------------------
// Get plugin type base on prefix
function getPluginCodeName(pluginsData, prefix)
{
var result = ""
pluginsData.forEach((plug) => {
if (plug.unique_prefix == prefix ) {
id = plug.code_name;
// console.log(id)
result = plug.code_name;
}
});
return result;
}
// -------------------------------------------------------------------
// Get plugin type base on prefix
function getPluginType(pluginsData, prefix)
{
var result = "core"
pluginsData.forEach((plug) => {
if (plug.unique_prefix == prefix ) {
id = plug.plugin_type;
// console.log(id)
result = plug.plugin_type;
}
});
return result;
}
// -------------------------------------------------------------------
// Generate plugin HTML card based on prefixes in an array
function pluginCards(prefixesOfEnabledPlugins, includeSettings)
{
html = ""
prefixesOfEnabledPlugins.forEach((prefix) => {
includeSettings_html = ''
includeSettings.forEach((set) => {
includeSettings_html += `
<a href="#${prefix + '_' + set}" onclick="toggleAllSettings()">
<div class="overview-setting-value pointer" title="${prefix + '_' + set}">
<code>${getSetting(prefix + '_' + set)}</code>
</div>
</a>
`
});
html += `
<div class="col-sm-4 ">
<div class="small-box bg-green " >
<div class="inner ">
<a href="#${prefix}_header" onclick="toggleAllSettings('open')">
<h5 class="card-title">
<b>${getString(prefix+"_display_name")}</b>
</h5>
</a>
${includeSettings_html}
</div>
<a href="#${prefix}_header" onclick="toggleAllSettings('open')">
<div class="icon"> ${getString(prefix+"_icon")} </div>
</a>
</div>
</div>
`
});
return html;
}
// -----------------------------------------------------------------------------
// Open or close all settings
// -----------------------------------------------------------------------------
function toggleAllSettings(openOrClose = '')
{
inStr = ' in';
allOpen = true;
openIcon = 'fa-angle-double-down';
closeIcon = 'fa-angle-double-up';
$('.panel-collapse').each(function(){
if($(this).attr('class').indexOf(inStr) == -1)
{
allOpen = false;
}
})
if(allOpen == false || openOrClose == 'open')
{
// open all
$('div[data-myid="collapsible"]').each(function(){$(this).attr('class', 'panel-collapse collapse in')})
$('div[data-myid="collapsible"]').each(function(){$(this).attr('style', 'height:inherit')})
$('#toggleSettings').attr('class', $('#toggleSettings').attr('class').replace(openIcon, closeIcon))
}
else{
// close all
$('div[data-myid="collapsible"]').each(function(){$(this).attr('class', 'panel-collapse collapse ')})
$('#toggleSettings').attr('class', $('#toggleSettings').attr('class').replace(closeIcon, openIcon))
}
}
// -------------------------------------------------------------------
// Checks if all schedules are the same
function schedulesAreSynchronized(prefixesOfEnabledPlugins, pluginsData)
{
plug_schedules = []
prefixesOfEnabledPlugins.forEach((prefix) => {
pluginsData.forEach((plug) => {
if (plug.unique_prefix == prefix) {
plug_schedules.push(getSetting(prefix+"_RUN_SCHD").replace(/\s/g, "")) // replace all white characters to compare them easier
}
});
});
// Check if all plug_schedules are the same
if (plug_schedules.length > 0) {
const firstSchedule = plug_schedules[0];
return plug_schedules.every((schedule) => schedule === firstSchedule);
}
return true; // Return true if no schedules are found
}

View File

@@ -395,7 +395,7 @@ $db->close();
<!-- ---------------------------Logging-------------------------------------------- -->
<div class="tab-pane" id="tab_Logging">
<div class="db_info_table">
<div class="log-area">
<div class="log-area box box-solid box-primary">
<div class="row logs-row">
<textarea id="pialert_log" class="logs" cols="70" rows="10" wrap='off' readonly >
<?php
@@ -420,8 +420,8 @@ $db->close();
</div>
</div>
</div>
</div>
<div class="log-area">
</div>
<div class="log-area box box-solid box-primary">
<div class="row logs-row">
<textarea id="pialert_front_log" class="logs" cols="70" rows="10" wrap='off' readonly><?php echo file_get_contents( "./log/pialert_front.log" ); ?>
</textarea>
@@ -437,7 +437,7 @@ $db->close();
</div>
</div>
</div>
<div class="log-area">
<div class="log-area box box-solid box-primary">
<div class="row logs-row">
<textarea id="pialert_php_log" class="logs" cols="70" rows="10" wrap='off' readonly><?php echo file_get_contents( "./log/pialert.php_errors.log" ); ?>
</textarea>
@@ -452,45 +452,19 @@ $db->close();
</div>
</div>
</div>
</div>
<div class="log-area">
<div class="row logs-row">
<textarea id="pialert_pholus_lastrun_log" class="logs logs-small" cols="70" rows="10" wrap='off' readonly><?php echo file_get_contents( "./log/pialert_pholus_lastrun.log" ); ?>
</textarea>
</div>
<div class="row logs-row" >
<div>
<div class="log-file">pialert_pholus_lastrun.log<div class="logs-size"><?php echo number_format((filesize("./log/pialert_pholus_lastrun.log") / 1000000),2,",",".") . ' MB';?>
<span class="span-padding"><a href="./log/pialert_pholus_lastrun.log"><i class="fa fa-download"></i> </a></span>
</div></div>
<div class="log-purge">
<button class="btn btn-primary" onclick="logManage('pialert_pholus_lastrun.log','cleanLog')"><?= lang('Gen_Purge');?></button>
</div>
</div>
</div>
</div>
<div class="log-area">
</div>
<div class="log-area box box-solid box-primary ">
<div class="row logs-row">
<textarea id="IP_changes_log" class="logs logs-small" cols="70" rows="10" readonly><?php echo file_get_contents( "./log/IP_changes.log" ); ?>
</textarea>
</div>
<div class="row logs-row" >
<div>
<div class="log-file">IP_changes.log<div class="logs-size"><?php echo number_format((filesize("./log/IP_changes.log") / 1000000),2,",",".") . ' MB';?>
<span class="span-padding"><a href="./log/IP_changes.log"><i class="fa fa-download"></i> </a></span>
</div></div>
<div class="log-purge">
<button class="btn btn-primary" onclick="logManage('IP_changes.log','cleanLog')"><?= lang('Gen_Purge');?></button>
</div>
</div>
</div>
</div>
<div class="log-area">
<textarea id="nginx_error_log" class="logs logs-small" cols="70" rows="10" wrap='off' readonly><?php echo file_get_contents( "/var/log/nginx/error.log" ); ?>
</textarea>
</div>
<div class="row logs-row" >
<div>
<div class="log-file" title="/var/log/nginx/error.log">nginx/error.log</div>
</div>
</div>
</div>
<div class="log-area box box-solid box-primary">
<div class="row logs-row">
<textarea id="stdout_log" class="logs logs-small" cols="70" rows="10" wrap='off' readonly><?php echo file_get_contents( "./log/stdout.log" ); ?>
</textarea>
@@ -507,7 +481,7 @@ $db->close();
</div>
</div>
<div class="log-area">
<div class="log-area box box-solid box-primary">
<div class="row logs-row">
<textarea id="stderr_log" class="logs logs-small" cols="70" rows="10" wrap='off' readonly><?php echo file_get_contents( "./log/stderr.log" ); ?>
</textarea>
@@ -786,7 +760,7 @@ function performLogManage() {
showModalOk ('Result', data );
}
})
}
}
// --------------------------------------------------------
function scrollDown()

View File

@@ -551,7 +551,8 @@
// ---------------------------------------------------------------------------
function getHierarchy()
{
{
for(i in deviceListGlobal)
{
if(deviceListGlobal[i].mac == 'Internet')
@@ -596,95 +597,114 @@
}
// ---------------------------------------------------------------------------
// Handle network node click - select correct tab in teh bottom table
function handleNodeClick(event)
{
console.log(event.target.offsetParent)
const targetTabMAC = $(event.target.offsetParent).attr("data-mytreemacmain");
console.log(event.target.offsetParent.offsetParent)
const targetTabMAC = $(event.target.offsetParent.offsetParent).attr("data-mytreemacmain");
var targetTab = $(`a[data-mytabmac="${targetTabMAC}"]`);
// Simulate a click event on the target tab
targetTab.click();
}
// ---------------------------------------------------------------------------
var myTree;
var treeAreaHeight = 800;
var emSize;
var nodeHeight;
var sizeCoefficient = 1
function initTree(myHierarchy)
{
{
// calculate the font size of the leaf nodes to fit everything into the tree area
leafNodesCount == 0 ? 1 : leafNodesCount;
emSize = ((treeAreaHeight/(25*leafNodesCount)).toFixed(2));
emSize = emSize > 1 ? 1 : emSize;
emSize = ((treeAreaHeight/(25*leafNodesCount)).toFixed(2));
emSize = emSize > 1 ? 1 : emSize;
// nodeHeight = ((emSize*100*0.30).toFixed(0))
nodeHeight = ((emSize*100*0.30).toFixed(0))
$("#networkTree").attr('style', `height:${treeAreaHeight}px; width:${$('.content-header').width()}px`)
console.log('here')
myTree = Treeviz.create({
htmlId: "networkTree",
renderNode: nodeData => {
renderNode: nodeData => {
var fontSize = "font-size:"+emSize+"em;";
(!emptyArr.includes(nodeData.data.port )) ? port = nodeData.data.port : port = "";
(port == "" || port == 0 ) ? portBckgIcon = `<i class="fa fa-wifi"></i>` : portBckgIcon = `<i class="fa fa-ethernet"></i>`;
// Build HTML for individual nodes in the network diagram
deviceIcon = (!emptyArr.includes(nodeData.data.icon )) ? "<div class='netIcon ' ><i class='fa fa-"+nodeData.data.icon +"'></i></div>" : "";
devicePort = `<div class='netPort ' style="width:${emSize*2.7}em;height:${emSize*2.7}em" >${port}</div> <div class="portBckgIcon" style="margin-left:-${emSize*2.5}em;">${portBckgIcon}</div>`;
collapseExpandIcon = nodeData.data.hiddenChildren ? "square-plus" :"square-minus";
collapseExpandHtml = (nodeData.data.hasChildren) ? "<div class='netCollapse' style='font-size:"+emSize*2.5+"em;' data-mytreepath='"+nodeData.data.path+"' data-mytreemac='"+nodeData.data.mac+"'><i class='fa fa-"+ collapseExpandIcon +" pointer'></i></div>" : "";
statusCss = " netStatus-" + nodeData.data.status;
// Build HTML for individual nodes in the network diagram
deviceIcon = (!emptyArr.includes(nodeData.data.icon )) ?
`<div class="netIcon">
<i class="fa fa-${nodeData.data.icon}"></i>
</div>` : "";
devicePort = `<div class="netPort"
style="width:${emSize*sizeCoefficient}em;height:${emSize*sizeCoefficient}em">
${port}</div>
<div class="portBckgIcon"
style="margin-left:-${emSize*sizeCoefficient}em;">
${portBckgIcon}
</div>`;
collapseExpandIcon = nodeData.data.hiddenChildren ?
"square-plus" : "square-minus";
// generate +/- icon if node has children nodes
collapseExpandHtml = nodeData.data.hasChildren ?
`<div class="netCollapse"
style="font-size:${emSize*sizeCoefficient}em;top:${1/2*emSize*sizeCoefficient}em"
data-mytreepath="${nodeData.data.path}"
data-mytreemac="${nodeData.data.mac}">
<i class="fa fa-${collapseExpandIcon} pointer"></i>
</div>` : "";
selectedNodeMac = $(".nav-tabs-custom .active a").attr('data-mytabmac')
highlightedCss = nodeData.data.mac == selectedNodeMac ? " highlightedNode" : "";
highlightedCss = nodeData.data.mac == selectedNodeMac ?
" highlightedNode" : "";
return result = `<div class='box ${(nodeData.data.hasChildren)? "pointer":""} ${statusCss} ${highlightedCss}'
data-mytreemacmain='${nodeData.data.mac}'
style='height:${nodeData.settings.nodeHeight}px;${fontSize}
// css indicating online/offline status
statusCss = ` netStatus-${nodeData.data.status}`;
return result = `<div class="box ${nodeData.data.hasChildren ? "pointer":""} ${statusCss} ${highlightedCss}"
data-mytreemacmain="${nodeData.data.mac}"
style="height:${nodeData.settings.nodeHeight}px;${fontSize}"
>
<div class='netNodeText '>\
<strong>${devicePort} ${deviceIcon}
<span class='spanNetworkTree anonymizeDev'>${nodeData.data.name}</span>\
</strong>
${collapseExpandHtml}
</div></div>`;
},
<div class="netNodeText">
<strong>${devicePort} ${deviceIcon}
<span class="spanNetworkTree anonymizeDev">${nodeData.data.name}</span>
</strong>
${collapseExpandHtml}
</div>
</div>`;
},
onNodeClick: nodeData => {
console.log(this)
},
onNodeClick: nodeData => {
console.log(this)
},
mainAxisNodeSpacing: 'auto',
// mainAxisNodeSpacing: 3,
secondaryAxisNodeSpacing: 0.3,
nodeHeight: nodeHeight.toString(),
nodeHeight: nodeHeight.toString(),
marginTop: '5',
hasZoom: false,
hasPan: false,
// marginLeft: '15',
idKey: "id",
hasFlatData: false,
hasFlatData: false,
linkWidth: (nodeData) => 3,
linkColor: (nodeData) => "#ffcc80",
onNodeClick: (nodeData) => handleNodeClick(nodeData),
relationnalField: "children",
relationnalField: "children",
});
console.log('vvvv')
console.log(myHierarchy)
console.log('^^^^^^^')
myTree.refresh(myHierarchy);
myTree.refresh(myHierarchy);
}
// ---------------------------------------------------------------------------

View File

@@ -77,7 +77,7 @@ function getDeviceData() {
// Device Data
$sql = 'SELECT rowid, *,
CASE WHEN dev_AlertDeviceDown=1 AND dev_PresentLastScan=0 THEN "Down"
CASE WHEN dev_AlertDeviceDown !=0 AND dev_PresentLastScan=0 THEN "Down"
WHEN dev_PresentLastScan=1 THEN "On-line"
ELSE "Off-line" END as dev_Status
FROM Devices
@@ -626,7 +626,7 @@ function getDevicesList() {
$sql = 'SELECT * FROM (
SELECT rowid, *, CASE
WHEN t1.dev_AlertDeviceDown=1 AND t1.dev_PresentLastScan=0 THEN "Down"
WHEN t1.dev_AlertDeviceDown !=0 AND t1.dev_PresentLastScan=0 THEN "Down"
WHEN t1.dev_NewDevice=1 THEN "New"
WHEN t1.dev_PresentLastScan=1 THEN "On-line"
ELSE "Off-line" END AS dev_Status
@@ -646,12 +646,14 @@ function getDevicesList() {
$tableData = array();
while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
$defaultOrder = array ($row['dev_Name'],
$defaultOrder = array (
$row['dev_Name'],
$row['dev_Owner'],
handleNull($row['dev_DeviceType']),
handleNull($row['dev_Icon'], "laptop"),
$row['dev_Favorite'],
$row['dev_Group'],
// ----
formatDate ($row['dev_FirstConnection']),
formatDate ($row['dev_LastConnection']),
$row['dev_LastIP'],
@@ -857,8 +859,6 @@ function getDevices() {
function getDeviceTypes() {
global $db;
$networkTypes = getNetworkTypes();
// SQL
$sql = 'SELECT DISTINCT 9 as dev_Order, dev_DeviceType
FROM Devices
@@ -1133,14 +1133,14 @@ function copyFromDevice() {
//------------------------------------------------------------------------------
function getDeviceCondition ($deviceStatus) {
switch ($deviceStatus) {
case 'all': return 'WHERE dev_Archived=0'; break;
case 'connected': return 'WHERE dev_Archived=0 AND dev_PresentLastScan=1'; break;
case 'favorites': return 'WHERE dev_Archived=0 AND dev_Favorite=1'; break;
case 'new': return 'WHERE dev_Archived=0 AND dev_NewDevice=1'; break;
case 'down': return 'WHERE dev_Archived=0 AND dev_AlertDeviceDown=1 AND dev_PresentLastScan=0'; break;
case 'archived': return 'WHERE dev_Archived=1'; break;
default: return 'WHERE 1=0'; break;
}
case 'all': return 'WHERE dev_Archived=0'; break;
case 'connected': return 'WHERE dev_Archived=0 AND dev_PresentLastScan=1'; break;
case 'favorites': return 'WHERE dev_Archived=0 AND dev_Favorite=1'; break;
case 'new': return 'WHERE dev_Archived=0 AND dev_NewDevice=1'; break;
case 'down': return 'WHERE dev_Archived=0 AND dev_AlertDeviceDown !=0 AND dev_PresentLastScan=0'; break;
case 'archived': return 'WHERE dev_Archived=1'; break;
default: return 'WHERE 1=0'; break;
}
}

View File

@@ -13,28 +13,47 @@ require dirname(__FILE__).'/../templates/skinUI.php';
$FUNCTION = [];
$SETTINGS = [];
$ACTION = "";
// init request params
if(array_key_exists('function', $_REQUEST) != FALSE)
{
$FUNCTION = $_REQUEST['function'];
}
if(array_key_exists('settings', $_REQUEST) != FALSE)
{
$SETTINGS = $_REQUEST['settings'];
}
// call functions based on requested params
if ($FUNCTION == 'savesettings')
{
saveSettings();
}
elseif ($FUNCTION == 'cleanLog')
{
cleanLog($SETTINGS);
switch ($FUNCTION) {
case 'savesettings':
saveSettings();
break;
case 'cleanLog':
cleanLog($SETTINGS);
break;
case 'addToExecutionQueue':
if(array_key_exists('action', $_REQUEST) != FALSE)
{
$ACTION = $_REQUEST['action'];
}
addToExecutionQueue($ACTION);
break;
default:
// Handle any other cases or errors if needed
break;
}
//------------------------------------------------------------------------------
// Formatting data functions
//------------------------------------------------------------------------------
@@ -195,6 +214,25 @@ function displayMessage($message, $logAlert = FALSE, $logConsole = TRUE, $logFil
}
// Adds an action to perform into the execution_queue.log file
function addToExecutionQueue($action)
{
global $logFolderPath, $timestamp;
$logFile = 'execution_queue.log';
$fullPath = $logFolderPath . $logFile;
// Open the file or skip if it can't be opened
if ($file = fopen($fullPath, 'a')) {
fwrite($file, "[" . $timestamp . "]|" . $action . PHP_EOL);
fclose($file);
displayMessage('Action "'.$action.'" added to the execution queue.', false, true, true, true);
} else {
displayMessage('Log file not found or couldn\'t be created.', false, true, true, true);
}
}
// ----------------------------------------------------------------------------------------
function cleanLog($logFile)
{
@@ -328,9 +366,16 @@ function saveSettings()
$txt = $txt."#-------------------IMPORTANT INFO-------------------#\n";
// open new file and write the new configuration
$newConfig = fopen($fullConfPath, "w") or die("Unable to open file!");
fwrite($newConfig, $txt);
fclose($newConfig);
// Create a temporary file
$tempConfPath = $fullConfPath . ".tmp";
// Write your changes to the temporary file
$tempConfig = fopen($tempConfPath, "w") or die("Unable to open file!");
fwrite($tempConfig, $txt);
fclose($tempConfig);
// Replace the original file with the temporary file
rename($tempConfPath, $fullConfPath);
displayMessage("<br/>Settings saved to the <code>pialert.conf</code> file.<br/><br/>A time-stamped backup of the previous file created. <br/><br/> Reloading...<br/>",
FALSE, TRUE, TRUE, TRUE);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -470,17 +470,13 @@
"PIALERT_WEB_PROTECTION_name" : "Habilitar inicio de sesión",
"PIALERT_WEB_PROTECTION_description" : "Cuando está habilitado, se muestra un cuadro de diálogo de inicio de sesión. Lea detenidamente a continuación si se le bloquea el acceso a su instancia.",
"PIALERT_WEB_PASSWORD_name" : "Contraseña de inicio de sesión",
"PIALERT_WEB_PASSWORD_description" : "La contraseña predeterminada es <code>123456</code>. Para cambiar la contraseña, ejecute <code>/home/pi/pialert/back/pialert-cli</code> en el contenedor",
"INCLUDED_SECTIONS_name" : "Notificar en",
"INCLUDED_SECTIONS_description" : "Especifica que eventos envían notificaciones. Elimina los tipos de eventos de los que no quieras recibir notificaciones. Este ajuste sobreescribe los ajustes específicos de los dispositivos en la interfaz. (<code>CTRL + Clic</code> para seleccionar / deseleccionar).",
"PIALERT_WEB_PASSWORD_description" : "La contraseña predeterminada es <code>123456</code>. Para cambiar la contraseña, ejecute <code>/home/pi/pialert/back/pialert-cli</code> en el contenedor",
"DAYS_TO_KEEP_EVENTS_name" : "Eliminar eventos anteriores a",
"DAYS_TO_KEEP_EVENTS_description" : "Esta es una configuración de mantenimiento. Esto especifica el número de días de entradas de eventos que se guardarán. Todos los eventos anteriores se eliminarán periódicamente.",
"HRS_TO_KEEP_NEWDEV_name": "Guardar nuevos dispositivos para",
"HRS_TO_KEEP_NEWDEV_description": "Esta es una configuración de mantenimiento. Si está habilitado (<code>0</code> está deshabilitado), los dispositivos marcados como <b>Nuevo dispositivo</b> se eliminarán si su <b>Primera sesión</ b> el tiempo era anterior a las horas especificadas en esta configuración. Utilice esta configuración si desea eliminar automáticamente <b>Nuevos dispositivos</b> después de <code>X</code> horas.",
"REPORT_DASHBOARD_URL_name" : "URL de Pi.Alert",
"REPORT_DASHBOARD_URL_description" : "Esta URL se utiliza como base para generar enlaces en los correos electrónicos. Ingrese la URL completa que comienza con <code>http://</code>, incluido el número de puerto (sin barra inclinada al final <code>/</code>).",
"DIG_GET_IP_ARG_name" : "Descubrir de IP de Internet",
"DIG_GET_IP_ARG_description" : "Cambie los argumentos de la <a href=\"https://linux.die.net/man/1/dig\" target=\"_blank\">utilidad de dig</a> si tiene problemas para resolver su IP de Internet. Los argumentos se agregan al final del siguiente comando: <code>dig +short </code>.",
"UI_LANG_name" : "Idioma de interfaz",
"UI_LANG_description" : "Seleccione el idioma de interfaz de usuario preferido.",
"UI_PRESENCE_name" : "Mostrar en el gráfico de presencia",

View File

@@ -1,45 +1,49 @@
## 📚 Docs for individual plugins
> Community translations of this file (might be out-of-date): <a href="https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/README_ES.md">Spanish(<img src="https://github.com/lipis/flag-icons/blob/main/flags/4x3/es.svg" alt="README_ES.md" style="height: 16px !important;width: 20px !important;padding-inline:3px !important;">)</a>, <a href="https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/README_DE.md">German(<img src="https://github.com/lipis/flag-icons/blob/main/flags/4x3/de.svg" alt="README_DE.md" style="height: 16px !important;width: 20px !important;padding-inline:3px !important;">)</a>
### 🏴 Community translations of this file
# 📚 Docs for individual plugins
> Please note there might be a delay between English and community translations.
>[!NOTE]
> Please check this [Plugins debugging guide](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/DEBUG_PLUGINS.md) and the corresponding Plugin documentation in the below table if you are facing issues.
* <a href="https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/README_ES.md">
<img src="https://github.com/lipis/flag-icons/blob/main/flags/4x3/es.svg" alt="README_ES.md" style="height: 20px !important;width: 20px !important;"> Spanish (Spain)
</a>
## 🔌 Plugins & 📚 Docs
* <a href="https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/README_DE.md">
<img src="https://github.com/lipis/flag-icons/blob/main/flags/4x3/de.svg" alt="README_DE.md" style="height: 20px !important;width: 20px !important;"> German (Germany)
</a>
| Required | CurrentScan | Unique Prefix | Data source | Type | Link + Docs |
|----------|-------------|---------------|--------------------|----------------|---------------------------------------------------------------------|
| | | APPRISE | Script | 💬 publisher | 📚[_publisher_apprise](/front/plugins/_publisher_apprise/) |
| | Yes | ARPSCAN | Script | 🔍dev scanner | 📚[arp_scan](/front/plugins/arp_scan/) |
| | | CSVBCKP | Script | ⚙ system | 📚[csv_backup](/front/plugins/csv_backup/) |
| Yes* | | DBCLNP | Script | ⚙ system | 📚[db_cleanup](/front/plugins/db_cleanup/) |
| | | DDNS | Script | ⚙ system | 📚[ddns_update](/front/plugins/ddns_update/) |
| | Yes | DHCPLSS | Script | 🔍dev scanner | 📚[dhcp_leases](/front/plugins/dhcp_leases/) |
| | | DHCPSRVS | Script | ♻ other | 📚[dhcp_servers](/front/plugins/dhcp_servers/) |
| | Yes | INTRNT | Script | 🔍dev scanner | 📚[internet_ip](/front/plugins/internet_ip/) |
| | | INTRSPD | Script | ♻ other | 📚[internet_speedtest](/front/plugins/internet_speedtest/) |
| | | MAINT | Script | ⚙ system | 📚[maintenance](/front/plugins/maintenance/) |
| | | MQTT | Script | 💬 publisher | 📚[_publisher_mqtt](/front/plugins/_publisher_mqtt/) |
| Yes | | NEWDEV | Template | ⚙ system | 📚[newdev_template](/front/plugins/newdev_template/) |
| | | NMAP | Script | ♻ other | 📚[nmap_scan](/front/plugins/nmap_scan/) |
| Yes | | NTFPRCS | Template | ⚙ system | 📚[notification_processing](/front/plugins/notification_processing/)|
| | | NTFY | Script | 💬 publisher | 📚[_publisher_ntfy](/front/plugins/_publisher_ntfy/) |
| | | PHOLUS | Script | ♻ other | 📚[pholus_scan](/front/plugins/pholus_scan/) |
| | Yes | PIHOLE | External SQLite DB | 🔍dev scanner | 📚[pihole_scan](/front/plugins/pihole_scan/) |
| | | PUSHSAFER | Script | 💬 publisher | 📚[_publisher_pushsafer](/front/plugins/_publisher_pushsafer/) |
| | | SETPWD | Script | ⚙ system | 📚[set_password](/front/plugins/set_password/) |
| | | SMTP | Script | 💬 publisher | 📚[_publisher_email](/front/plugins/_publisher_email/) |
| | Yes | SNMPDSC | Script | 🔍dev scanner | 📚[snmp_discovery](/front/plugins/snmp_discovery/) |
| | Yes** | UNDIS | Script | ♻ other | 📚[undiscoverables](/front/plugins/undiscoverables/) |
| | Yes | UNFIMP | Script | 🔍dev scanner | 📚[unifi_import](/front/plugins/unifi_import/) |
| | | VNDRPDT | Script | ⚙ system | 📚[vendor_update](/front/plugins/vendor_update/) |
| | | WEBHOOK | Script | 💬 publisher | 📚[_publisher_webhook](/front/plugins/_publisher_webhook/) |
| | | WEBMON | Script | ♻ other | 📚[website_monitor](/front/plugins/website_monitor/) |
| N/A | | N/A | SQL query | | N/A, but the External SQLite DB plugins work similarly |
### 🔌 Plugins & 📚 Docs
| Required | CurrentScan | Unique Prefix | Plugin Type | Link + Docs |
|-------------|-------------|-----------------------|------------------------|----------------------------------------------------------|
| | Yes | ARPSCAN | Script | 📚[arp_scan](/front/plugins/arp_scan/) |
| | | CSVBCKP | Script | 📚[csv_backup](/front/plugins/csv_backup/) |
| Yes* | | DBCLNP | Script | 📚[db_cleanup](/front/plugins/db_cleanup/) |
| | | DDNS | Script | 📚[ddns_update](/front/plugins/ddns_update/) |
| | Yes | DHCPLSS | Script | 📚[dhcp_leases](/front/plugins/dhcp_leases/) |
| | | DHCPSRVS | Script | 📚[dhcp_servers](/front/plugins/dhcp_servers/) |
| | Yes | INTRNT | Script | 📚[internet_ip](/front/plugins/internet_ip/) |
| Yes | | NEWDEV | Template | 📚[newdev_template](/front/plugins/newdev_template/) |
| | | NMAP | Script | 📚[nmap_scan](/front/plugins/nmap_scan/) |
| | Yes | PIHOLE | External SQLite DB | 📚[pihole_scan](/front/plugins/pihole_scan/) |
| | | SETPWD | Script | 📚[set_password](/front/plugins/set_password/) |
| | | SNMPDSC | Script | 📚[snmp_discovery](/front/plugins/snmp_discovery/) |
| | Yes* | UNDIS | Script | 📚[undiscoverables](/front/plugins/undiscoverables/) |
| | Yes | UNFIMP | Script | 📚[unifi_import](/front/plugins/unifi_import/) |
| | | VNDRPDT | Script | 📚[vendor_update](/front/plugins/vendor_update/) |
| | | WEBMON | Script | 📚[website_monitor](/front/plugins/website_monitor/) |
| N/A | | N/A | SQL query | N/A, but the External SQLite DB plugins work similar |
> \* The Undiscoverables plugin (`UNDIS`) inserts only user-specified dummy devices.
>
> \* The database cleanup plugin (`DBCLNP`) is not _required_ but the app will become unusable after a while if not executed.
>
> \** The Undiscoverables plugin (`UNDIS`) inserts only user-specified dummy devices.
> [!NOTE]
> You soft-disable plugins via Settings or completely ignore plugins by placing a `ignore_plugin` file into the plugin directory. The difference is that ignored plugins don't show up anywhere in the UI (Settings, Device details, Plugins pages). The app skips ignored plugins completely. Device-detecting plugins insert values into the `CurrentScan` database table. The plugins that are not required are safe to ignore, however it makes sense to have a least some device-detecting plugins (that insert entries into the `CurrentScan` table) enabled, such as ARPSCAN or PIHOLE.
> You soft-disable plugins via Settings or completely ignore plugins by placing a `ignore_plugin` file into the plugin directory. The difference is that ignored plugins don't show up anywhere in the UI (Settings, Device details, Plugins pages). The app skips ignored plugins completely. Device-detecting plugins insert values into the `CurrentScan` database table. The plugins that are not required are safe to ignore, however it makes sense to have a least some device-detecting plugins (that insert entries into the `CurrentScan` table) enabled, such as `ARPSCAN` or `PIHOLE`.
> It's recommended to use the same schedule interval for all plugins responsible for discovering new devices.
@@ -81,13 +85,13 @@ Follow the below very carefully and check example plugin(s) if you'd like to wri
* Adding form controls supported to display the data (Currently supported ones are listed in the section "UI settings in database_column_definitions" below)
* ...
## ❗ Known issues:
## ❗ Known limitations:
These issues will be hopefully fixed with time, so please don't report them. Instead, if you know how, feel free to investigate and submit a PR to fix the below. Keep the PRs small as it's easier to approve them:
* Existing plugin objects sometimes not interpreted correctly and a new object is created instead, resulting in duplicate entries. (race condition?)
* Existing plugin objects are sometimes not interpreted correctly and a new object is created instead, resulting in duplicate entries. (race condition?)
* Occasional (experienced twice) hanging of processing plugin script file.
UI displays outdated values until the API endpoints get refreshed.
* UI displays outdated values until the API endpoints get refreshed.
## Plugin file structure overview
@@ -133,6 +137,7 @@ Currently, these data sources are supported (valid `data_source` value).
| Pialert DB query | `pialert-db-query` | yes | Executes a SQL query on the PiAlert database in the `CMD` setting. |
| Template | `template` | no | Used to generate internal settings, such as default values. |
| 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>`). |
> 🔎Example
@@ -389,7 +394,7 @@ 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.
> [!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 databse 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. Taht also menas that the `"column": "NameDoesntMatter"` is not important as there is no database source column.
>🔍 Example:
@@ -540,8 +545,8 @@ Required attributes are:
| `"name"` | Displayed on the Settings page. An array of localized strings. See Localized strings below. |
| `"description"` | Displayed on the Settings page. An array of localized strings. See Localized strings below. |
| (optional) `"events"` | Specifies whether to generate an execution button next to the input field of the setting. Supported values: |
| | - `test` |
| | - `run` |
| | - `"test"` - For notification plugins testing |
| | - `"run"` - Regular plugins testing |
| (optional) `"override_value"` | Used to determine a user-defined override for the setting. Useful for template-based plugins, where you can choose to leave the current value or override it with the value defined in the setting. (Work in progress) |
| (optional) `"events"` | Used to trigger the plugin. Usually used on the `RUN` setting. Not fully tested in all scenarios. Will show a play button next to the setting. After clicking, an event is generated for the backend in the `Parameters` database table to process the front-end event on the next run. |
@@ -617,7 +622,8 @@ The UI will adjust how columns are displayed in the UI based on the resolvers de
| Supported Types | Description |
| -------------- | ----------- |
| `label` | Displays a column only. |
| `text` | Makes a column editable, and a save icon is displayed next to it. See below for information on `threshold`, `replace`. |
| `textarea_readonly` | Generates a read only text area and cleans up the text to display it somewhat formatted with new lines preserved. |
| See below for information on `threshold`, `replace`. | |
| | |
| `options` Property | Used in conjunction with types like `threshold`, `replace`, `regex`. |
| `threshold` | The `options` array contains objects ordered from the lowest `maximum` to the highest. The corresponding `hexColor` is used for the value background color if it's less than the specified `maximum` but more than the previous one in the `options` array. |
@@ -631,7 +637,8 @@ The UI will adjust how columns are displayed in the UI based on the resolvers de
| `url` | The value is considered to be a URL, so a link is generated. |
| `textbox_save` | Generates an editable and saveable text box that saves values in the database. Primarily intended for the `UserData` database column in the `Plugins_Objects` table. |
| `url_http_https` | Generates two links with the `https` and `http` prefix as lock icons. |
| `eval` | Evaluates as JavaScript. Use the variable `value` to use the given column value as input (e.g. `'<b>${value}<b>'` (replace ' with ` in your code) ) |
> [!NOTE]
> Supports chaining. You can chain multiple resolvers with `.`. For example `regex.url_http_https`. This will apply the `regex` resolver and then the `url_http_https` resolver.

View File

@@ -381,7 +381,7 @@ 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.
> [!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 databse 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. Taht also menas that the `"column": "NameDoesntMatter"` is not important as there is no database source column.
>🔍 Beispiel:

View File

@@ -0,0 +1,8 @@
## Overview
[Apprise](https://hub.docker.com/r/caronc/apprise) is a notification gateway/publisher that allows you to push notifications to 80+ different services.
### Usage
- Go to settings and fill in relevant details.

View File

@@ -0,0 +1,128 @@
#!/usr/bin/env python
import json
import subprocess
import argparse
import os
import pathlib
import sys
from datetime import datetime
# Replace these paths with the actual paths to your Pi.Alert directories
sys.path.extend(["/home/pi/pialert/front/plugins", "/home/pi/pialert/pialert"])
import conf
from plugin_helper import Plugin_Objects
from logger import mylog, append_line_to_file
from helper import timeNowTZ, get_setting_value
from notification import Notification_obj
from database import DB
CUR_PATH = str(pathlib.Path(__file__).parent.resolve())
RESULT_FILE = os.path.join(CUR_PATH, 'last_result.log')
pluginName = 'APPRISE'
def main():
mylog('verbose', [f'[{pluginName}](publisher) In script'])
# Check if basic config settings supplied
if check_config() == False:
mylog('none', [f'[{pluginName}] ⚠ ERROR: Publisher notification gateway not set up correctly. 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 (see the Notifications DB table for structure or check the /api/table_notifications.json endpoint)
for notification in new_notifications:
# Send notification
result = send(notification["HTML"], notification["Text"])
# Log result
plugin_objects.add_object(
primaryId = pluginName,
secondaryId = timeNowTZ(),
watched1 = notification["GUID"],
watched2 = result,
watched3 = 'null',
watched4 = 'null',
extra = 'null',
foreignKey = notification["GUID"]
)
plugin_objects.write_result_file()
#-------------------------------------------------------------------------------
def check_config():
if get_setting_value('APPRISE_URL') == '' or get_setting_value('APPRISE_HOST') == '':
return False
else:
return True
#-------------------------------------------------------------------------------
def send(html, text):
payloadData = ''
result = ''
# limit = 1024 * 1024 # 1MB limit (1024 bytes * 1024 bytes = 1MB)
limit = get_setting_value('APPRISE_SIZE')
# truncate size
if get_setting_value('APPRISE_PAYLOAD') == 'html':
if len(html) > limit:
payloadData = html[:limit] + "<h1>(text was truncated)</h1>"
else:
payloadData = html
if get_setting_value('APPRISE_PAYLOAD') == 'text':
if len(text) > limit:
payloadData = text[:limit] + " (text was truncated)"
else:
payloadData = text
# Define Apprise compatible payload (https://github.com/caronc/apprise-api#stateless-solution)
_json_payload = {
"urls": get_setting_value('APPRISE_URL'),
"title": "Pi.Alert Notifications",
"format": get_setting_value('APPRISE_PAYLOAD'),
"body": payloadData
}
try:
# try runnning a subprocess
p = subprocess.Popen(["curl","-i","-X", "POST" ,"-H", "Content-Type:application/json" ,"-d", json.dumps(_json_payload), get_setting_value('APPRISE_HOST')], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
stdout, stderr = p.communicate()
# write stdout and stderr into .log files for debugging if needed
# Log the stdout and stderr
mylog('debug', [stdout, stderr])
# log result
result = stdout
except subprocess.CalledProcessError as e:
# An error occurred, handle it
mylog('none', [e.output])
# log result
result = e.output
return result
if __name__ == '__main__':
sys.exit(main())

View File

@@ -0,0 +1,430 @@
{
"code_name": "_publisher_apprise",
"unique_prefix": "APPRISE",
"plugin_type": "publisher",
"enabled": true,
"data_source": "script",
"show_ui": true,
"localized": ["display_name", "description", "icon"],
"display_name" : [
{
"language_code": "en_us",
"string" : "Apprise publisher"
},
{
"language_code": "es_es",
"string" : "Habilitar Apprise"
}
],
"icon":[{
"language_code": "en_us",
"string" : "<i class=\"fa-solid fa-bullhorn\"></i>"
}],
"description": [
{
"language_code": "en_us",
"string" : "A plugin to publish a notification via the Apprise gateway."
}
],
"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": "url",
"default_value":"",
"options": [],
"localized": ["name"],
"name":[{
"language_code": "en_us",
"string" : "N/A"
}]
},
{
"column": "Object_SecondaryID",
"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": "DateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value":"",
"options": [],
"localized": ["name"],
"name":[{
"language_code": "en_us",
"string" : "Sent when"
}]
},
{
"column": "DateTimeChanged",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
"default_value":"",
"options": [],
"localized": ["name"],
"name":[{
"language_code": "en_us",
"string" : "Changed"
},
{
"language_code": "es_es",
"string" : "Cambiado"
}]
},
{
"column": "Watched_Value1",
"css_classes": "col-sm-2",
"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-8",
"show": true,
"type": "textarea_readonly",
"default_value":"",
"options": [],
"localized": ["name"],
"name":[{
"language_code": "en_us",
"string" : "Result"
}]
},
{
"column": "Watched_Value3",
"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": "Watched_Value4",
"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": "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://hub.docker.com/r/caronc/apprise\">Apprise</a>."
},
{
"language_code": "es_es",
"string" : "Habilitar el envío de notificaciones a través de <a target=\"_blank\" href=\"https://hub.docker.com/r/caronc/apprise\">Apprise</a>."
}
]
},
{
"function": "CMD",
"type": "readonly",
"default_value":"python3 /home/pi/pialert/front/plugins/_publisher_apprise/apprise.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."
},
{
"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."
}]
},
{
"function": "HOST",
"type": "text",
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "Apprise host URL"
},
{
"language_code": "es_es",
"string" : "URL del host de Apprise"
}],
"description": [{
"language_code": "en_us",
"string" : "Apprise host URL starting with <code>http://</code> or <code>https://</code>. (do not forget to include <code>/notify</code> at the end)"
},
{
"language_code": "es_es",
"string" : "URL del host de Apprise que comienza con <code>http://</code> o <code>https://</code>. (no olvide incluir <code>/notify</code> al final)"
}]
},
{
"function": "URL",
"type": "text",
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "Apprise notification URL"
},
{
"language_code": "es_es",
"string" : "URL de notificación de Apprise"
}],
"description": [{
"language_code": "en_us",
"string" : "Apprise notification target URL. For example for Telegram it would be <code>tgram://{bot_token}/{chat_id}</code>."
},
{
"language_code": "es_es",
"string" : "Informar de la URL de destino de la notificación. Por ejemplo, para Telegram sería <code>tgram://{bot_token}/{chat_id}</code>."
}]
},
{
"function": "PAYLOAD",
"type": "text.select",
"default_value": "html",
"options": ["html", "text"],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "Payload type"
},
{
"language_code": "es_es",
"string" : "Tipo de carga"
}],
"description": [{
"language_code": "en_us",
"string" : "Select the payoad type sent to Apprise. For example <code>html</code> works well with emails, <code>text</code> with chat apps, such as Telegram."
},
{
"language_code": "es_es",
"string" : "Seleccione el tipo de carga útil enviada a Apprise. Por ejemplo, <code>html</code> funciona bien con correos electrónicos, <code>text</code> con aplicaciones de chat, como Telegram."
}]
},
{
"function": "SIZE",
"type": "integer",
"default_value": 1024,
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "Max payload size"
},
{
"language_code": "es_es",
"string" : "Tamaño máximo de carga útil"
}],
"description": [{
"language_code": "en_us",
"string" : "The maximum size of the apprise payload as number of characters in the passed string. If above limit, it will be truncated and a <code>(text was truncated)</code> message is appended."
},
{
"language_code": "es_es",
"string" : "El tamaño máximo de la carga útil de información como número de caracteres en la cadena pasada. Si supera el límite, se truncará y se agregará un mensaje <code>(text was truncated)</code>."
}]
}
]
}

View File

@@ -0,0 +1,8 @@
## Overview
A simple EMail (SMTP) notification gateway publisher. Check the [SMTP docs](/docs/SMTP.md) for additional help.
### Usage
- Go to settings and fill in relevant details.

View File

@@ -0,0 +1,689 @@
{
"code_name": "_publisher_email",
"unique_prefix": "SMTP",
"plugin_type": "publisher",
"enabled": true,
"data_source": "script",
"show_ui": true,
"localized": [
"display_name",
"description",
"icon"
],
"display_name": [
{
"language_code": "en_us",
"string": "Email publisher (SMTP)"
},
{
"language_code": "es_es",
"string": "Habilitar email (SMTP)"
}
],
"icon": [
{
"language_code": "en_us",
"string": "<i class=\"fa-solid fa-at\"></i>"
}
],
"description": [
{
"language_code": "en_us",
"string": "A plugin to publish a notification via Email (SMTP) gateway."
}
],
"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": "url",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "N/A"
}
]
},
{
"column": "Object_SecondaryID",
"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": "DateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Sent when"
}
]
},
{
"column": "DateTimeChanged",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Changed"
},
{
"language_code": "es_es",
"string": "Cambiado"
}
]
},
{
"column": "Watched_Value1",
"css_classes": "col-sm-2",
"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-8",
"show": true,
"type": "textarea_readonly",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Result"
}
]
},
{
"column": "Watched_Value3",
"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": "Watched_Value4",
"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": "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 the Email (SMTP) gateway."
},
{
"language_code": "es_es",
"string": "Si está habilitado, se envía un correo electrónico con una lista de cambios a los que se ha suscrito. Complete también todas las configuraciones restantes relacionadas con la configuración de SMTP a continuación"
}
]
},
{
"function": "CMD",
"type": "readonly",
"default_value": "python3 /home/pi/pialert/front/plugins/_publisher_email/email_smtp.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": 20,
"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."
},
{
"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."
}
]
},
{
"function": "SERVER",
"type": "text",
"default_value": "",
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "SMTP server URL"
},
{
"language_code": "es_es",
"string": "URL del servidor SMTP"
}
],
"description": [
{
"language_code": "en_us",
"string": "The SMTP server host URL. For example <code>smtp-relay.sendinblue.com</code>. To use Gmail as an SMTP server <a target=\"_blank\" href=\"https://github.com/jokob-sk/Pi.Alert/blob/main/docs/SMTP.md\">follow this guide</a>"
},
{
"language_code": "es_es",
"string": "La URL del host del servidor SMTP. Por ejemplo, <code>smtp-relay.sendinblue.com</code>. Para utilizar Gmail como servidor SMTP <a target=\"_blank\" href=\"https://github.com/jokob-sk/Pi.Alert/blob/main/docs/SMTP.md\">siga esta guía</a >"
}
]
},
{
"function": "PORT",
"type": "integer",
"default_value": 587,
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "SMTP server PORT"
},
{
"language_code": "es_es",
"string": "Puerto del servidor SMTP"
}
],
"description": [
{
"language_code": "en_us",
"string": "Port number used for the SMTP connection. Set to <code>0</code> if you do not want to use a port when connecting to the SMTP server."
},
{
"language_code": "es_es",
"string": "Número de puerto utilizado para la conexión SMTP. Establézcalo en <code>0</code> si no desea utilizar un puerto al conectarse al servidor SMTP."
}
]
},
{
"function": "SKIP_LOGIN",
"type": "boolean",
"default_value": false,
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Skip authentication"
},
{
"language_code": "es_es",
"string": "Omitir autenticación"
}
],
"description": [
{
"language_code": "en_us",
"string": "Do not use authentication when connecting to the SMTP server."
},
{
"language_code": "es_es",
"string": "No utilice la autenticación cuando se conecte al servidor SMTP."
}
]
},
{
"function": "USER",
"type": "text",
"default_value": "",
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "SMTP user"
},
{
"language_code": "es_es",
"string": "Nombre de usuario SMTP"
}
],
"description": [
{
"language_code": "en_us",
"string": "The user name used to login into the SMTP server (sometimes a full email address)."
},
{
"language_code": "es_es",
"string": "El nombre de usuario utilizado para iniciar sesión en el servidor SMTP (a veces, una dirección de correo electrónico completa)."
}
]
},
{
"function": "PASS",
"type": "password",
"default_value": "",
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "SMTP password"
},
{
"language_code": "es_es",
"string": "Contraseña de SMTP"
}
],
"description": [
{
"language_code": "en_us",
"string": "The SMTP server password."
},
{
"language_code": "es_es",
"string": "La contraseña del servidor SMTP."
}
]
},
{
"function": "SKIP_TLS",
"type": "boolean",
"default_value": false,
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Do not use TLS"
},
{
"language_code": "es_es",
"string": "No usar TLS"
}
],
"description": [
{
"language_code": "en_us",
"string": "Disable TLS when connecting to your SMTP server."
},
{
"language_code": "es_es",
"string": "Deshabilite TLS cuando se conecte a su servidor SMTP."
}
]
},
{
"function": "FORCE_SSL",
"type": "boolean",
"default_value": false,
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Force SSL"
},
{
"language_code": "es_es",
"string": "Forzar SSL"
}
],
"description": [
{
"language_code": "en_us",
"string": "Force SSL when connecting to your SMTP server."
},
{
"language_code": "es_es",
"string": "Forzar SSL al conectarse a su servidor SMTP"
}
]
},
{
"function": "REPORT_TO",
"type": "text",
"default_value": "user@gmail.com",
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Send email to"
},
{
"language_code": "es_es",
"string": "Enviar el email a"
}
],
"description": [
{
"language_code": "en_us",
"string": "Email address to which the notification will be send to."
},
{
"language_code": "es_es",
"string": "Dirección de correo electrónico a la que se enviará la notificación."
}
]
},
{
"function": "REPORT_FROM",
"type": "text",
"default_value": "Pi.Alert <user@gmail.com>",
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Email subject"
},
{
"language_code": "es_es",
"string": "Asunto del email"
}
],
"description": [
{
"language_code": "en_us",
"string": "Notification email subject line. Some SMTP servers need this to be an email."
},
{
"language_code": "es_es",
"string": "Asunto del correo electrónico de notificación."
}
]
}
]
}

View File

@@ -0,0 +1,199 @@
#!/usr/bin/env python
import json
import subprocess
import argparse
import os
import pathlib
import sys
import re
from datetime import datetime
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.header import Header
from email.utils import parseaddr
import smtplib
import socket
import ssl
sys.path.extend(["/home/pi/pialert/front/plugins", "/home/pi/pialert/pialert"])
# PiAlert modules
import conf
from plugin_helper import Plugin_Objects
from logger import mylog, append_line_to_file, print_log
from helper import timeNowTZ, get_setting_value, hide_email
from notification import Notification_obj
from database import DB
CUR_PATH = str(pathlib.Path(__file__).parent.resolve())
RESULT_FILE = os.path.join(CUR_PATH, 'last_result.log')
pluginName = 'SMTP'
def main():
mylog('verbose', [f'[{pluginName}](publisher) In script'])
# Check if basic config settings supplied
if check_config() == False:
mylog('none', [f'[{pluginName}] ⚠ ERROR: Publisher notification gateway not set up correctly. 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 (see the Notifications DB table for structure or check the /api/table_notifications.json endpoint)
for notification in new_notifications:
# Send notification
result = send(notification["HTML"], notification["Text"])
# Log result
plugin_objects.add_object(
primaryId = pluginName,
secondaryId = timeNowTZ(),
watched1 = notification["GUID"],
watched2 = result,
watched3 = 'null',
watched4 = 'null',
extra = 'null',
foreignKey = notification["GUID"]
)
plugin_objects.write_result_file()
#-------------------------------------------------------------------------------
def check_config ():
server = get_setting_value('SMTP_SERVER')
report_to = get_setting_value("SMTP_REPORT_TO")
report_from = get_setting_value("SMTP_REPORT_FROM")
if server == '' or report_from == '' or report_to == '':
mylog('none', ['[Email Check Config] ⚠ ERROR: Email service not set up correctly. Check your pialert.conf SMTP_*, SMTP_REPORT_FROM and SMTP_REPORT_TO variables.'])
return False
else:
return True
#-------------------------------------------------------------------------------
def send(pHTML, pText):
mylog('debug', [f'[{pluginName}] SMTP_REPORT_TO: {hide_email(str(get_setting_value("SMTP_REPORT_TO")))} SMTP_USER: {hide_email(str(get_setting_value("SMTP_USER")))}'])
subject, from_email, to_email, message_html, message_text = sanitize_email_content('Pi.Alert Report', get_setting_value("SMTP_REPORT_FROM"), get_setting_value("SMTP_REPORT_TO"), pHTML, pText)
# Compose email
msg = MIMEMultipart('alternative')
msg['Subject'] = subject
msg['From'] = from_email
msg['To'] = to_email
msg.attach (MIMEText (message_text, 'plain'))
msg.attach (MIMEText (message_html, 'html'))
# Set a timeout for the SMTP connection (in seconds)
smtp_timeout = 30
mylog('debug', ['Trying to open connection to ' + str(get_setting_value('SMTP_SERVER')) + ':' + str(get_setting_value('SMTP_PORT'))])
if get_setting_value("LOG_LEVEL") == 'debug':
send_email(msg,smtp_timeout)
else:
try:
send_email(msg,smtp_timeout)
except smtplib.SMTPAuthenticationError as e:
mylog('none', [' ERROR: Couldn\'t connect to the SMTP server (SMTPAuthenticationError)'])
mylog('none', [' ERROR: Double-check your SMTP_USER and SMTP_PASS settings.)'])
mylog('none', [' ERROR: ', str(e)])
except smtplib.SMTPServerDisconnected as e:
mylog('none', [' ERROR: Couldn\'t connect to the SMTP server (SMTPServerDisconnected)'])
mylog('none', [' ERROR: ', str(e)])
except socket.gaierror as e:
mylog('none', [' ERROR: Could not resolve hostname (socket.gaierror)'])
mylog('none', [' ERROR: ', str(e)])
except ssl.SSLError as e:
mylog('none', [' ERROR: Could not establish SSL connection (ssl.SSLError)'])
mylog('none', [' ERROR: Are you sure you need SMTP_FORCE_SSL enabled? Check your SMTP provider docs.'])
mylog('none', [' ERROR: ', str(e)])
# ----------------------------------------------------------------------------------
def send_email(msg,smtp_timeout):
# Send mail
if get_setting_value('SMTP_FORCE_SSL'):
mylog('debug', ['SMTP_FORCE_SSL == True so using .SMTP_SSL()'])
if get_setting_value("SMTP_PORT") == 0:
mylog('debug', ['SMTP_PORT == 0 so sending .SMTP_SSL(SMTP_SERVER)'])
smtp_connection = smtplib.SMTP_SSL(get_setting_value('SMTP_SERVER'))
else:
mylog('debug', ['SMTP_PORT == 0 so sending .SMTP_SSL(SMTP_SERVER, SMTP_PORT)'])
smtp_connection = smtplib.SMTP_SSL(get_setting_value('SMTP_SERVER'), get_setting_value('SMTP_PORT'), timeout=smtp_timeout)
else:
mylog('debug', ['SMTP_FORCE_SSL == False so using .SMTP()'])
if get_setting_value("SMTP_PORT") == 0:
mylog('debug', ['SMTP_PORT == 0 so sending .SMTP(SMTP_SERVER)'])
smtp_connection = smtplib.SMTP (get_setting_value('SMTP_SERVER'))
else:
mylog('debug', ['SMTP_PORT == 0 so sending .SMTP(SMTP_SERVER, SMTP_PORT)'])
smtp_connection = smtplib.SMTP (get_setting_value('SMTP_SERVER'), get_setting_value('SMTP_PORT'))
mylog('debug', ['Setting SMTP debug level'])
# Log level set to debug of the communication between SMTP server and client
if get_setting_value('LOG_LEVEL') == 'debug':
smtp_connection.set_debuglevel(1)
mylog('debug', [ 'Sending .ehlo()'])
smtp_connection.ehlo()
if not get_setting_value('SMTP_SKIP_TLS'):
mylog('debug', ['SMTP_SKIP_TLS == False so sending .starttls()'])
smtp_connection.starttls()
mylog('debug', ['SMTP_SKIP_TLS == False so sending .ehlo()'])
smtp_connection.ehlo()
if not get_setting_value('SMTP_SKIP_LOGIN'):
mylog('debug', ['SMTP_SKIP_LOGIN == False so sending .login()'])
smtp_connection.login (get_setting_value('SMTP_USER'), get_setting_value('SMTP_PASS'))
mylog('debug', ['Sending .sendmail()'])
smtp_connection.sendmail (get_setting_value("SMTP_REPORT_FROM"), get_setting_value("SMTP_REPORT_TO"), msg.as_string())
smtp_connection.quit()
# ----------------------------------------------------------------------------------
def sanitize_email_content(subject, from_email, to_email, message_html, message_text):
# Validate and sanitize subject
subject = Header(subject, 'utf-8').encode()
# Validate and sanitize sender's email address
from_name, from_address = parseaddr(from_email)
from_email = Header(from_name, 'utf-8').encode() + ' <' + from_address + '>'
# Validate and sanitize recipient's email address
to_name, to_address = parseaddr(to_email)
to_email = Header(to_name, 'utf-8').encode() + ' <' + to_address + '>'
# Validate and sanitize message content
# Remove potentially problematic characters
message_html = re.sub(r'[^\x00-\x7F]+', ' ', message_html)
message_text = re.sub(r'[^\x00-\x7F]+', ' ', message_text)
return subject, from_email, to_email, message_html, message_text
# ----------------------------------------------------------------------------------
if __name__ == '__main__':
sys.exit(main())

View File

@@ -0,0 +1,8 @@
## Overview
- Feed your data and device changes into [Home Assistant](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/HOME_ASSISTANT.md) via the MQTT Mosquito broker.
### Usage
- Go to settings and fill in relevant details.

View File

@@ -0,0 +1,532 @@
{
"code_name": "_publisher_mqtt",
"unique_prefix": "MQTT",
"plugin_type": "publisher",
"enabled": true,
"data_source": "script",
"show_ui": true,
"localized": ["display_name", "description", "icon"],
"display_name" : [
{
"language_code": "en_us",
"string" : "MQTT publisher"
},
{
"language_code": "es_es",
"string" : "Habilitar MQTT"
}
],
"icon":[{
"language_code": "en_us",
"string" : "<i class=\"fa-solid fa-square-rss\"></i>"
}],
"description": [
{
"language_code": "en_us",
"string" : "A plugin to publish a notification via the Apprise gateway."
}
],
"params" : [
{
"name" : "devices",
"type" : "sql",
"value" : "SELECT dev_LastIP from DEVICES",
"timeoutMultiplier" : true
}
],
"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": true,
"type": "label",
"default_value":"",
"options": [],
"localized": ["name"],
"name":[{
"language_code": "en_us",
"string" : "MQTT Device ID"
}]
},
{
"column": "Object_SecondaryID",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value":"",
"options": [],
"localized": ["name"],
"name":[{
"language_code": "en_us",
"string" : "Sensor Name"
}]
},
{
"column": "DateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value":"",
"options": [],
"localized": ["name"],
"name":[{
"language_code": "en_us",
"string" : "Sent when"
}]
},
{
"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": "Watched_Value1",
"css_classes": "col-sm-3",
"show": true,
"type": "label",
"default_value":"",
"options": [],
"localized": ["name"],
"name":[{
"language_code": "en_us",
"string" : "Device name"
}]
},
{
"column": "Watched_Value2",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value":"",
"options": [],
"localized": ["name"],
"name":[{
"language_code": "en_us",
"string" : "Sensor type"
}]
},
{
"column": "Watched_Value3",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value":"",
"options": [],
"localized": ["name"],
"name":[{
"language_code": "en_us",
"string" : "Hash"
}]
},
{
"column": "Watched_Value4",
"css_classes": "col-sm-2",
"show": true,
"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": 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"
}]
},
{
"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", "once", "schedule", "always_after_scan", "on_new_device" ],
"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.home-assistant.io/integrations/mqtt/\">MQTT</a> to your Home Assistance instance. Usually, <code>on_notification</code> is recommended. See the <a target=\"_blank\" href=\"https://github.com/jokob-sk/Pi.Alert/blob/main/docs/HOME_ASSISTANT.md\">PiAlert Home Assistant guide</a> for details."
},
{
"language_code": "es_es",
"string" : "Habilitar el envío de notificaciones a través de <a target=\"_blank\" href=\"https://www.home-assistant.io/integrations/mqtt/\">MQTT</a> a su Home Assistance."
}
]
},
{
"function": "CMD",
"type": "readonly",
"default_value":"python3 /home/pi/pialert/front/plugins/_publisher_mqtt/mqtt.py devices={devices}",
"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_SCHD",
"type": "text",
"default_value":"0 2 * * 3",
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code":"en_us",
"string" : "Schedule"
},
{
"language_code":"es_es",
"string" : "Schedule"
},
{
"language_code":"de_de",
"string" : "Schedule"
}],
"description": [{
"language_code":"en_us",
"string" : "Only enabled if you select <code>schedule</code> in the <a href=\"#MQTT_RUN\"><code>MQTT_RUN</code> setting</a>. Make sure you enter the schedule in the correct cron-like format (e.g. validate at <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). For example entering <code>0 4 * * *</code> will run the scan after 4 am in the <a onclick=\"toggleAllSettings()\" href=\"#TIMEZONE\"><code>TIMEZONE</code> you set above</a>. Will be run NEXT time the time passes."
},
{
"language_code":"es_es",
"string" : "Solo está habilitado si selecciona <code>schedule</code> en la configuración <a href=\"#MQTT_RUN\"><code>MQTT_RUN</code></a>. Asegúrese de ingresar la programación en el formato similar a cron correcto (por ejemplo, valide en <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). Por ejemplo, ingresar <code>0 4 * * *</code> ejecutará el escaneo después de las 4 a.m. en el <a onclick=\"toggleAllSettings()\" href=\"#TIMEZONE\"><code>TIMEZONE</ código> que configuró arriba</a>. Se ejecutará la PRÓXIMA vez que pase el tiempo."
},
{
"language_code":"de_de",
"string" : "Nur aktiviert, wenn Sie <code>schedule</code> in der <a href=\"#MQTT_RUN\"><code>MQTT_RUN</code>-Einstellung</a> auswählen. Stellen Sie sicher, dass Sie den Zeitplan im richtigen Cron-ähnlichen Format eingeben (z. B. validieren unter <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). Wenn Sie beispielsweise <code>0 4 * * *</code> eingeben, wird der Scan nach 4 Uhr morgens in der <a onclick=\"toggleAllSettings()\" href=\"#TIMEZONE\"><code>TIMEZONE</ ausgeführt. Code> den Sie oben festgelegt haben</a>. Wird das NÄCHSTE Mal ausgeführt, wenn die Zeit vergeht."
}]
},
{
"function": "RUN_TIMEOUT",
"type": "integer",
"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."
},
{
"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."
}]
},
{
"function": "BROKER",
"type": "text",
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "MQTT broker URL"
},
{
"language_code": "es_es",
"string" : "URL del broker MQTT"
}],
"description": [{
"language_code": "en_us",
"string" : "MQTT host URL (do not include <code>http://</code> or <code>https://</code>)."
},
{
"language_code": "es_es",
"string" : "URL del host MQTT (no incluya <code>http://</code> o <code>https://</code>)."
}]
},
{
"function": "PORT",
"type": "integer",
"default_value": 1883,
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "MQTT broker port"
},
{
"language_code": "es_es",
"string" : "Puerto del broker MQTT"
}],
"description": [{
"language_code": "en_us",
"string" : "Port number where the broker is listening. Usually <code>1883</code>."
},
{
"language_code": "es_es",
"string" : "Puerto donde escucha el broker MQTT. Normalmente <code>1883</code>."
}]
},
{
"function": "USER",
"type": "text",
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "MQTT user"
},
{
"language_code": "es_es",
"string" : "Usuario de MQTT"
}],
"description": [{
"language_code": "en_us",
"string" : "User name used to login into your MQTT broker instance."
},
{
"language_code": "es_es",
"string" : "Nombre de usuario utilizado para iniciar sesión en su instancia de agente de MQTT."
}]
},
{
"function": "PASSWORD",
"type": "password",
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "MQTT password"
},
{
"language_code": "es_es",
"string" : "Contraseña de MQTT"
}],
"description": [{
"language_code": "en_us",
"string" : "Password used to login into your MQTT broker instance."
},
{
"language_code": "es_es",
"string" : "Contraseña utilizada para iniciar sesión en su instancia de agente de MQTT."
}]
},
{
"function": "QOS",
"type": "integer.select",
"default_value": 0,
"options": [0, 1, 2],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "MQTT Quality of Service"
},
{
"language_code": "es_es",
"string" : "Calidad de servicio MQTT"
}],
"description": [{
"language_code": "en_us",
"string" : "Quality of service setting for MQTT message sending. <code>0</code> - Low quality to <code>2</code> - High quality. The higher the quality the longer the delay."
},
{
"language_code": "es_es",
"string" : "Configuración de calidad de servicio para el envío de mensajes MQTT. <code>0</code>: baja calidad a <code>2</code>: alta calidad. Cuanto mayor sea la calidad, mayor será el retraso."
}]
},
{
"function": "DELAY_SEC",
"type": "integer",
"default_value": 2,
"options": [2, 3, 4, 5],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "MQTT delay per device"
},
{
"language_code": "es_es",
"string" : "Retraso de MQTT por dispositivo"
}],
"description": [{
"language_code": "en_us",
"string" : "A little hack - delay adding to the queue in case the process is restarted and previous publish processes aborted (it takes ~<code>2</code>s to update a sensor config on the broker). Tested with <code>2</code>-<code>3</code> seconds of delay. This delay is only applied when devices are created (during the first notification loop). It doesn not affect subsequent scans or notifications."
},
{
"language_code": "es_es",
"string" : "Un pequeño truco: retrase la adición a la cola en caso de que el proceso se reinicie y los procesos de publicación anteriores se anulen (se necesitan ~<code>2</code>s para actualizar la configuración de un sensor en el intermediario). Probado con <code>2</code>-<code>3</code> segundos de retraso. Este retraso solo se aplica cuando se crean dispositivos (durante el primer bucle de notificación). No afecta los escaneos o notificaciones posteriores."
}]
},
{
"function": "SEND_STATS",
"type": "boolean",
"default_value":true,
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code":"en_us",
"string" : "Send stats"
}
],
"description": [{
"language_code":"en_us",
"string" : "Check to send overal device stats, such as number of Online and Offline devices."
}
]
},
{
"function": "SEND_DEVICES",
"type": "boolean",
"default_value":true,
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code":"en_us",
"string" : "Send devices"
}
],
"description": [{
"language_code":"en_us",
"string" : "Check to send individual devices to the broker with details, such as <code>is_new</code>, <code>is_present</code>, or <code>mac_address</code> of the devices."
}
]
}
]
}

View File

@@ -0,0 +1,366 @@
#!/usr/bin/env python
import json
import subprocess
import argparse
import os
import pathlib
import sys
from datetime import datetime
import time
import re
from paho.mqtt import client as mqtt_client
import hashlib
# Replace these paths with the actual paths to your Pi.Alert directories
sys.path.extend(["/home/pi/pialert/front/plugins", "/home/pi/pialert/pialert"])
# PiAlert modules
import conf
from const import apiPath
from plugin_utils import getPluginObject
from plugin_helper import Plugin_Objects
from logger import mylog, append_line_to_file
from helper import timeNowTZ, get_setting_value, bytes_to_string, sanitize_string
from notification import Notification_obj
from database import DB, get_all_devices, get_device_stats
CUR_PATH = str(pathlib.Path(__file__).parent.resolve())
RESULT_FILE = os.path.join(CUR_PATH, 'last_result.log')
# Initialize the Plugin obj output file
plugin_objects = Plugin_Objects(RESULT_FILE)
# Create an MD5 hash object
md5_hash = hashlib.md5()
pluginName = 'MQTT'
module_name = pluginName
# globals
mqtt_sensors = []
mqtt_connected_to_broker = False
client = None # mqtt client
def main():
mylog('verbose', [f'[{pluginName}](publisher) In script'])
# Check if basic config settings supplied
if check_config() == False:
mylog('none', [f'[{pluginName}] ⚠ ERROR: Publisher notification gateway not set up correctly. Check your pialert.conf {pluginName}_* variables.'])
return
# Create a database connection
db = DB() # instance of class DB
db.open()
mqtt_start(db)
plugin_objects.write_result_file()
#-------------------------------------------------------------------------------
# MQTT
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
def check_config():
if get_setting_value('MQTT_BROKER') == '' or get_setting_value('MQTT_PORT') == '' or get_setting_value('MQTT_USER') == '' or get_setting_value('MQTT_PASSWORD') == '':
mylog('none', ['[Check Config] ⚠ ERROR: MQTT service not set up correctly. Check your pialert.conf MQTT_* variables.'])
return False
else:
return True
#-------------------------------------------------------------------------------
class sensor_config:
def __init__(self, deviceId, deviceName, sensorType, sensorName, icon, mac):
self.deviceId = deviceId
self.deviceName = deviceName
self.sensorType = sensorType
self.sensorName = sensorName
self.icon = icon
# Define your input string
input_string = str(deviceId) + str(deviceName) + str(sensorType) + str(sensorName) + str(icon)
# Hash the input string and convert the hash to a string
# Update the hash object with the bytes of the input string
md5_hash.update(input_string.encode('utf-8'))
# Get the hexadecimal representation of the MD5 hash
md5_hash_hex = md5_hash.hexdigest()
hash_value = str(md5_hash_hex)
self.hash = hash_value
plugObj = getPluginObject({"Plugin":"MQTT", "Watched_Value3":hash_value})
# mylog('verbose', [f"[{pluginName}] Previous plugin object entry: {json.dumps(plugObj)}"])
if plugObj == {}:
self.isNew = True
mylog('verbose', [f"[{pluginName}] New sensor entry mac : {mac}"])
mylog('verbose', [f"[{pluginName}] New sensor entry input_string : {input_string}"])
mylog('verbose', [f"[{pluginName}] New sensor entry hash_value : {hash_value}"])
else:
self.isNew = False
# Log sensor
global plugin_objects
if mac == '':
mac = "N/A"
plugin_objects.add_object(
primaryId = deviceId,
secondaryId = sensorName,
watched1 = deviceName,
watched2 = sensorType,
watched3 = hash_value,
watched4 = mac,
extra = input_string,
foreignKey = mac
)
#-------------------------------------------------------------------------------
def publish_mqtt(client, topic, message):
status = 1
mylog('verbose', [f"[{pluginName}] Sending MQTT topic: {topic}"])
mylog('verbose', [f"[{pluginName}] Sending MQTT message: {message}"])
while status != 0:
result = client.publish(
topic=topic,
payload=message,
qos=get_setting_value('MQTT_QOS'),
retain=True,
)
status = result[0]
if status != 0:
mylog('minimal', [f"[{pluginName}] Waiting to reconnect to MQTT broker"])
time.sleep(0.1)
return True
#-------------------------------------------------------------------------------
def create_generic_device(client):
deviceName = 'PiAlert'
deviceId = 'pialert'
create_sensor(client, deviceId, deviceName, 'sensor', 'online', 'wifi-check')
create_sensor(client, deviceId, deviceName, 'sensor', 'down', 'wifi-cancel')
create_sensor(client, deviceId, deviceName, 'sensor', 'all', 'wifi')
create_sensor(client, deviceId, deviceName, 'sensor', 'archived', 'wifi-lock')
create_sensor(client, deviceId, deviceName, 'sensor', 'new', 'wifi-plus')
create_sensor(client, deviceId, deviceName, 'sensor', 'unknown', 'wifi-alert')
#-------------------------------------------------------------------------------
def create_sensor(client, deviceId, deviceName, sensorType, sensorName, icon, mac=""):
global mqtt_sensors
new_sensor_config = sensor_config(deviceId, deviceName, sensorType, sensorName, icon, mac)
# save if new
if new_sensor_config.isNew:
mylog('minimal', [f"[{pluginName}] Publishing sensor number {len(mqtt_sensors)}"])
publish_sensor(client, new_sensor_config)
#-------------------------------------------------------------------------------
def publish_sensor(client, sensorConfig):
global mqtt_sensors
message = '{ \
"name":"'+sensorConfig.sensorName+'", \
"state_topic":"system-sensors/'+sensorConfig.sensorType+'/'+sensorConfig.deviceId+'/state", \
"value_template":"{{value_json.'+sensorConfig.sensorName+'}}", \
"unique_id":"'+sensorConfig.deviceId+'_sensor_'+sensorConfig.sensorName+'", \
"device": \
{ \
"identifiers": ["'+sensorConfig.deviceId+'_sensor"], \
"manufacturer": "PiAlert", \
"name":"'+sensorConfig.deviceName+'" \
}, \
"icon":"mdi:'+sensorConfig.icon+'" \
}'
topic='homeassistant/'+sensorConfig.sensorType+'/'+sensorConfig.deviceId+'/'+sensorConfig.sensorName+'/config'
# add the sensor to the global list to keep track of succesfully added sensors
if publish_mqtt(client, topic, message):
# hack - delay adding to the queue in case the process is
time.sleep(get_setting_value('MQTT_DELAY_SEC')) # restarted and previous publish processes aborted
# (it takes ~2s to update a sensor config on the broker)
mqtt_sensors.append(sensorConfig)
#-------------------------------------------------------------------------------
def mqtt_create_client():
def on_disconnect(client, userdata, rc):
global mqtt_connected_to_broker
mqtt_connected_to_broker = False
# not sure is below line is correct / necessary
# client = mqtt_create_client()
def on_connect(client, userdata, flags, rc):
global mqtt_connected_to_broker
if rc == 0:
mylog('verbose', [f"[{pluginName}] Connected to broker"])
mqtt_connected_to_broker = True # Signal connection
else:
mylog('none', [f"[{pluginName}] Connection failed"])
mqtt_connected_to_broker = False
global client
client = mqtt_client.Client('PiAlert') # Set Connecting Client ID
client.username_pw_set(get_setting_value('MQTT_USER'), get_setting_value('MQTT_PASSWORD'))
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.connect(get_setting_value('MQTT_BROKER'), get_setting_value('MQTT_PORT'))
client.loop_start()
return client
#-------------------------------------------------------------------------------
def mqtt_start(db):
global client, mqtt_connected_to_broker
if mqtt_connected_to_broker == False:
mqtt_connected_to_broker = True
client = mqtt_create_client()
# General stats
# Create a generic device for overal stats
if get_setting_value('MQTT_SEND_STATS') == True:
# Create a new device representing overall PiAlert stats
create_generic_device(client)
# Get the data
row = get_device_stats(db)
columns = ["online","down","all","archived","new","unknown"]
payload = ""
# Update the values
for column in columns:
payload += '"'+column+'": ' + str(row[column]) +','
# Publish (wrap into {} and remove last ',' from above)
publish_mqtt(client, "system-sensors/sensor/pialert/state",
'{ \
'+ payload[:-1] +'\
}'
)
# Generate device-specific MQTT messages if enabled
if get_setting_value('MQTT_SEND_DEVICES') == True:
# Specific devices
# Get all devices
devices = get_all_devices(db)
sec_delay = len(devices) * int(get_setting_value('MQTT_DELAY_SEC'))*5
mylog('minimal', [f"[{pluginName}] Estimated delay: ", (sec_delay), 's ', '(', round(sec_delay/60,1) , 'min)' ])
for device in devices:
# Create devices in Home Assistant - send config messages
deviceId = 'mac_' + device["dev_MAC"].replace(" ", "").replace(":", "_").lower()
deviceNameDisplay = re.sub('[^a-zA-Z0-9-_\s]', '', device["dev_Name"])
create_sensor(client, deviceId, deviceNameDisplay, 'sensor', 'last_ip', 'ip-network', device["dev_MAC"])
create_sensor(client, deviceId, deviceNameDisplay, 'binary_sensor', 'is_present', 'wifi', device["dev_MAC"])
create_sensor(client, deviceId, deviceNameDisplay, 'sensor', 'mac_address', 'folder-key-network', device["dev_MAC"])
create_sensor(client, deviceId, deviceNameDisplay, 'sensor', 'is_new', 'bell-alert-outline', device["dev_MAC"])
create_sensor(client, deviceId, deviceNameDisplay, 'sensor', 'vendor', 'cog', device["dev_MAC"])
# update device sensors in home assistant
publish_mqtt(client, 'system-sensors/sensor/'+deviceId+'/state',
'{ \
"last_ip": "' + device["dev_LastIP"] +'", \
"is_new": "' + str(device["dev_NewDevice"]) +'", \
"vendor": "' + sanitize_string(device["dev_Vendor"]) +'", \
"mac_address": "' + str(device["dev_MAC"]) +'" \
}'
)
publish_mqtt(client, 'system-sensors/binary_sensor/'+deviceId+'/state',
'{ \
"is_present": "' + to_binary_sensor(str(device["dev_PresentLastScan"])) +'"\
}'
)
# delete device / topic
# homeassistant/sensor/mac_44_ef_bf_c4_b1_af/is_present/config
# client.publish(
# topic="homeassistant/sensor/"+deviceId+"/is_present/config",
# payload="",
# qos=1,
# retain=True,
# )
# time.sleep(10)
#===============================================================================
# Home Assistant UTILs
#===============================================================================
def to_binary_sensor(input):
# In HA a binary sensor returns ON or OFF
result = "OFF"
# bytestring
if isinstance(input, str):
if input == "1":
result = "ON"
elif isinstance(input, int):
if input == 1:
result = "ON"
elif isinstance(input, bool):
if input == True:
result = "ON"
elif isinstance(input, bytes):
if bytes_to_string(input) == "1":
result = "ON"
return result
# -------------INIT---------------------
if __name__ == '__main__':
sys.exit(main())

View File

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

View File

@@ -0,0 +1,390 @@
{
"code_name": "_publisher_ntfy",
"unique_prefix": "NTFY",
"plugin_type": "publisher",
"enabled": true,
"data_source": "script",
"show_ui": true,
"localized": ["display_name", "description", "icon"],
"display_name" : [
{
"language_code": "en_us",
"string" : "NTFY publisher"
},
{
"language_code": "es_es",
"string" : "Habilitar NTFY"
}
],
"icon":[{
"language_code": "en_us",
"string" : "<i class=\"fa-solid fa-terminal\"></i>"
}],
"description": [
{
"language_code": "en_us",
"string" : "A plugin to publish a notification via the NTFY gateway."
}
],
"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://ntfy.sh/\">NTFY</a>."
},
{
"language_code": "es_es",
"string" : "Habilitar el envío de notificaciones a través de <a target=\"_blank\" href=\"https://ntfy.sh/\">NTFY</a>."
}
]
},
{
"function": "CMD",
"type": "readonly",
"default_value":"python3 /home/pi/pialert/front/plugins/_publisher_ntfy/ntfy.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."
},
{
"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."
}]
},
{
"function": "HOST",
"type": "text",
"default_value": "https://ntfy.sh",
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "NTFY host URL"
},
{
"language_code": "es_es",
"string" : "URL del host NTFY"
}],
"description": [{
"language_code": "en_us",
"string" : "NTFY host URL starting with <code>http://</code> or <code>https://</code>. You can use the hosted instance on <a target=\"_blank\" href=\"https://ntfy.sh/\">https://ntfy.sh</a> by simply entering <code>https://ntfy.sh</code>."
},
{
"language_code": "es_es",
"string" : "URL de host NTFY que comienza con <code>http://</code> o <code>https://</code>. Puede usar la instancia alojada en <a target=\"_blank\" href=\"https://ntfy.sh/\">https://ntfy.sh</a> simplemente ingresando <code>https://ntfy. sh</código>."
}]
},
{
"function": "TOPIC",
"type": "text",
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "NTFY topic"
},
{
"language_code": "es_es",
"string" : "Tema de NTFY"
}],
"description": [{
"language_code": "en_us",
"string" : "Your secret topic."
},
{
"language_code": "es_es",
"string" : "Tu tema secreto."
}]
},
{
"function": "USER",
"type": "text",
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "NTFY user"
},
{
"language_code": "es_es",
"string" : "Usuario de NTFY"
}],
"description": [{
"language_code": "en_us",
"string" : "Enter user if you need (host) an instance with enabled authetication."
},
{
"language_code": "es_es",
"string" : "Ingrese usuario si necesita (alojar) una instancia con autenticación habilitada."
}]
},
{
"function": "PASSWORD",
"type": "password",
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "NTFY password"
},
{
"language_code": "es_es",
"string" : "Contraseña de NTFY"
}],
"description": [{
"language_code": "en_us",
"string" : "Enter password if you need (host) an instance with enabled authetication."
},
{
"language_code": "es_es",
"string" : "Ingrese la contraseña si necesita (host) una instancia con autenticación habilitada."
}]
}
]
}

View File

@@ -0,0 +1,132 @@
#!/usr/bin/env python
import json
import subprocess
import argparse
import os
import pathlib
import sys
import requests
from datetime import datetime
from base64 import b64encode
# Replace these paths with the actual paths to your Pi.Alert directories
sys.path.extend(["/home/pi/pialert/front/plugins", "/home/pi/pialert/pialert"])
import conf
from plugin_helper import Plugin_Objects, handleEmpty
from logger import mylog, append_line_to_file
from helper import timeNowTZ, get_setting_value
from notification import Notification_obj
from database import DB
CUR_PATH = str(pathlib.Path(__file__).parent.resolve())
RESULT_FILE = os.path.join(CUR_PATH, 'last_result.log')
pluginName = 'NTFY'
def main():
mylog('verbose', [f'[{pluginName}](publisher) In script'])
# Check if basic config settings supplied
if check_config() == False:
mylog('none', [f'[{pluginName}] ⚠ ERROR: Publisher notification gateway not set up correctly. 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 (see the Notifications DB table for structure or check the /api/table_notifications.json endpoint)
for notification in new_notifications:
# Send notification
response_text, response_status_code = send(notification["HTML"], 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 check_config():
if get_setting_value('NTFY_HOST') == '' or get_setting_value('NTFY_TOPIC') == '':
return False
else:
return True
#-------------------------------------------------------------------------------
def send(html, text):
response_text = ''
response_status_code = ''
headers = {
"Title": "Pi.Alert Notification",
"Actions": "view, Open Dashboard, "+ get_setting_value('REPORT_DASHBOARD_URL'),
"Priority": "urgent",
"Tags": "warning"
}
# if username and password are set generate hash and update header
if get_setting_value('NTFY_USER') != "" and get_setting_value('NTFY_PASSWORD') != "":
# Generate hash for basic auth
# usernamepassword = "{}:{}".format(get_setting_value('NTFY_USER'),get_setting_value('NTFY_PASSWORD'))
basichash = b64encode(bytes(get_setting_value('NTFY_USER') + ':' + get_setting_value('NTFY_PASSWORD'), "utf-8")).decode("ascii")
# add authorization header with hash
headers["Authorization"] = "Basic {}".format(basichash)
try:
response = requests.post("{}/{}".format( get_setting_value('NTFY_HOST'),
get_setting_value('NTFY_TOPIC')),
data = text,
headers = headers)
response_status_code = response.status_code
# 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 = e
return response_text, response_status_code
return response_text, response_status_code
if __name__ == '__main__':
sys.exit(main())

View File

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

View File

@@ -0,0 +1,321 @@
{
"code_name": "_publisher_pushsafer",
"unique_prefix": "PUSHSAFER",
"plugin_type": "publisher",
"enabled": true,
"data_source": "script",
"show_ui": true,
"localized": ["display_name", "description", "icon"],
"display_name" : [
{
"language_code": "en_us",
"string" : "Pushsafer publisher"
},
{
"language_code": "es_es",
"string" : "Habilitar Pushsafer"
}
],
"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 Pushsafer gateway."
}
],
"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.pushsafer.com/\">Pushsafer</a>."
},
{
"language_code": "es_es",
"string" : "Habilitar el envío de notificaciones a través de <a target=\"_blank\" href=\"https://www.pushsafer.com/\">Pushsafer</a>."
}
]
},
{
"function": "CMD",
"type": "readonly",
"default_value":"python3 /home/pi/pialert/front/plugins/_publisher_pushsafer/pushsafer.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."
},
{
"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."
}]
},
{
"function": "TOKEN",
"type": "text",
"default_value": "ApiKey",
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "Pushsafer token"
},
{
"language_code": "es_es",
"string" : "Token de Pushsafer"
}],
"description": [{
"language_code": "en_us",
"string" : "Your secret Pushsafer API key (token)."
},
{
"language_code": "es_es",
"string" : "Su clave secreta de la API de Pushsafer (token)."
}]
}
]
}

View File

@@ -0,0 +1,133 @@
#!/usr/bin/env python
import json
import subprocess
import argparse
import os
import pathlib
import sys
import requests
from datetime import datetime
from base64 import b64encode
# Replace these paths with the actual paths to your Pi.Alert directories
sys.path.extend(["/home/pi/pialert/front/plugins", "/home/pi/pialert/pialert"])
import conf
from plugin_helper import Plugin_Objects, handleEmpty
from logger import mylog, append_line_to_file
from helper import timeNowTZ, get_setting_value, hide_string
from notification import Notification_obj
from database import DB
CUR_PATH = str(pathlib.Path(__file__).parent.resolve())
RESULT_FILE = os.path.join(CUR_PATH, 'last_result.log')
pluginName = 'PUSHSAFER'
def main():
mylog('verbose', [f'[{pluginName}](publisher) In script'])
# Check if basic config settings supplied
if check_config() == False:
mylog('none', [f'[{pluginName}] ⚠ ERROR: Publisher notification gateway not set up correctly. 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 (see the Notifications DB table for structure or check the /api/table_notifications.json endpoint)
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 = ''
token = get_setting_value('PUSHSAFER_TOKEN')
mylog('verbose', [f'[{pluginName}] PUSHSAFER_TOKEN: "{hide_string(token)}"'])
try:
url = 'https://www.pushsafer.com/api'
post_fields = {
"t" : 'Pi.Alert Message',
"m" : text,
"s" : 11,
"v" : 3,
"i" : 148,
"c" : '#ef7f7f',
"d" : 'a',
"u" : get_setting_value('REPORT_DASHBOARD_URL'),
"ut" : 'Open Pi.Alert',
"k" : token,
}
response = requests.post(url, data=post_fields)
response_status_code = response.status_code
# 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 = e
return response_text, response_status_code
return response_text, response_status_code
#-------------------------------------------------------------------------------
def check_config():
if get_setting_value('PUSHSAFER_TOKEN') == 'ApiKey':
return False
else:
return True
# -------------------------------------------------------
if __name__ == '__main__':
sys.exit(main())

View File

@@ -0,0 +1,8 @@
## Overview
A plugin to publish a notification via the Webhook gateway. Webhooks help you to connect to a lot of 3rd party tools, such as IFTTT, Zapier or <a href="https://n8n.io/" target="_blank">n8n</a> to name a few. Check out this simple <a href="https://github.com/jokob-sk/Pi.Alert/blob/main/docs/WEBHOOK_N8N.md" target="_blank">n8n guide here</a> to get started. If enabled, configure related settings below.
### Usage
- Go to settings and fill in relevant details.

View File

@@ -0,0 +1,409 @@
{
"code_name": "_publisher_webhook",
"unique_prefix": "WEBHOOK",
"plugin_type": "publisher",
"enabled": true,
"data_source": "script",
"show_ui": true,
"localized": ["display_name", "description", "icon"],
"display_name" : [
{
"language_code": "en_us",
"string" : "Webhook publisher"
},
{
"language_code": "es_es",
"string" : "Habilitar Webhook"
}
],
"icon":[{
"language_code": "en_us",
"string" : "<i class=\"fa-solid fa-circle-nodes\"></i>"
}],
"description": [
{
"language_code": "en_us",
"string" : "A plugin to publish a notification via Webhooks."
}
],
"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 (stdout)"
}]
},
{
"column": "Watched_Value3",
"css_classes": "col-sm-2",
"show": true,
"type": "textarea_readonly",
"default_value":"",
"options": [],
"localized": ["name"],
"name":[{
"language_code": "en_us",
"string" : "Response (stderr)"
}]
},
{
"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 webhooks for notifications. Webhooks help you to connect to a lot of 3rd party tools, such as IFTTT, Zapier or <a href=\"https://n8n.io/\" target=\"_blank\">n8n</a> to name a few. Check out this simple <a href=\"https://github.com/jokob-sk/Pi.Alert/blob/main/docs/WEBHOOK_N8N.md\" target=\"_blank\">n8n guide here</a> to get started. If enabled, configure related settings below."
},
{
"language_code": "es_es",
"string" : "Habilite webhooks para notificaciones. Los webhooks lo ayudan a conectarse a muchas herramientas de terceros, como IFTTT, Zapier o <a href=\"https://n8n.io/\" target=\"_blank\">n8n</a>, por nombrar algunas. Consulte esta sencilla <a href=\"https://github.com/jokob-sk/Pi.Alert/blob/main/docs/WEBHOOK_N8N.md\" target=\"_blank\">guía de n8n aquí</a> para obtener comenzó. Si está habilitado, configure los ajustes relacionados a continuación."
}
]
},
{
"function": "CMD",
"type": "readonly",
"default_value":"python3 /home/pi/pialert/front/plugins/_publisher_webhook/webhook.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."
},
{
"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."
}]
},
{
"function": "URL",
"type": "text",
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "Target URL"
},
{
"language_code": "es_es",
"string" : "URL de destino"
}],
"description": [{
"language_code": "en_us",
"string" : "Target URL starting with <code>http://</code> or <code>https://</code>."
},
{
"language_code": "es_es",
"string" : "URL de destino comienza con <code>http://</code> o <code>https://</code>."
}]
},
{
"function": "PAYLOAD",
"type": "text.select",
"default_value": "json",
"options": ["json", "html", "text"],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "Payload type"
},
{
"language_code": "es_es",
"string" : "Tipo de carga"
}],
"description": [{
"language_code": "en_us",
"string" : "The Webhook payload data format for the <code>body</code> > <code>attachments</code> > <code>text</code> attribute in the payload json. See an example of the payload <a target=\"_blank\" href=\"https://github.com/jokob-sk/Pi.Alert/blob/main/back/webhook_json_sample.json\">here</a>. (e.g.: for discord use <code>text</code>)"
},
{
"language_code": "es_es",
"string" : "El formato de datos de carga de Webhook para el atributo <code>body</code> > <code>attachments</code> > <code>text</code> en el json de carga. Vea un ejemplo de la carga <a target=\"_blank\" href=\"https://github.com/jokob-sk/Pi.Alert/blob/main/back/webhook_json_sample.json\">aquí</a>. (por ejemplo: para discord use <code>text</code>)"
}]
},
{
"function": "REQUEST_METHOD",
"type": "text.select",
"default_value": "GET",
"options": ["GET", "POST", "PUT"],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "Request method"
},
{
"language_code": "es_es",
"string" : "Método de solicitud"
}],
"description": [{
"language_code": "en_us",
"string" : "The HTTP request method to be used for the webhook call."
},
{
"language_code": "es_es",
"string" : "El método de solicitud HTTP que se utilizará para la llamada de webhook."
}]
},
{
"function": "SIZE",
"type": "integer",
"default_value": 1024,
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "Max payload size"
},
{
"language_code": "es_es",
"string" : "Tamaño máximo de carga útil"
}],
"description": [{
"language_code": "en_us",
"string" : "The maximum size of the webhook payload as number of characters in the passed string. If above limit, it will be truncated and a <code>(text was truncated)</code> message is appended."
},
{
"language_code": "es_es",
"string" : "El tamaño máximo de la carga útil del webhook como número de caracteres en la cadena pasada. Si supera el límite, se truncará y se agregará un mensaje <code>(text was truncated)</code>."
}]
},
{
"function": "SECRET",
"type": "text",
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "HMAC Secret"
},
{
"language_code": "es_es",
"string" : ""
}],
"description": [{
"language_code": "en_us",
"string" : "When set, use this secret to generate the SHA256-HMAC hex digest value of the request body, which will be passed as the <code>X-Webhook-Signature</code> header to the request. You can find more information <a target=\"_blank\" href=\"https://github.com/jokob-sk/Pi.Alert/blob/main/docs/WEBHOOK_SECRET.md\">here</a>."
}]
}
]
}

View File

@@ -0,0 +1,190 @@
#!/usr/bin/env python
import json
import subprocess
import argparse
import os
import pathlib
import sys
import requests
from datetime import datetime
from base64 import b64encode
import hashlib
import hmac
# Replace these paths with the actual paths to your Pi.Alert directories
sys.path.extend(["/home/pi/pialert/front/plugins", "/home/pi/pialert/pialert"])
# pialert modules
import conf
from const import logPath
from plugin_helper import Plugin_Objects, handleEmpty
from logger import mylog, append_line_to_file
from helper import timeNowTZ, get_setting_value, hide_string, write_file
from notification import Notification_obj
from database import DB
CUR_PATH = str(pathlib.Path(__file__).parent.resolve())
RESULT_FILE = os.path.join(CUR_PATH, 'last_result.log')
pluginName = 'WEBHOOK'
def main():
mylog('verbose', [f'[{pluginName}](publisher) In script'])
# Check if basic config settings supplied
if check_config() == False:
mylog('none', [f'[{pluginName}] ⚠ ERROR: Publisher notification gateway not set up correctly. 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 (see the Notifications DB table for structure or check the /api/table_notifications.json endpoint)
for notification in new_notifications:
# Send notification
response_stdout, response_stderr = send(notification["Text"], notification["HTML"], notification["JSON"])
# Log result
plugin_objects.add_object(
primaryId = pluginName,
secondaryId = timeNowTZ(),
watched1 = notification["GUID"],
watched2 = handleEmpty(response_stdout),
watched3 = handleEmpty(response_stderr),
watched4 = 'null',
extra = 'null',
foreignKey = notification["GUID"]
)
plugin_objects.write_result_file()
#-------------------------------------------------------------------------------
def check_config():
if get_setting_value('WEBHOOK_URL') == '':
return False
else:
return True
#-------------------------------------------------------------------------------
def send (text_data, html_data, json_data):
response_stderr = ''
response_stdout = ''
# limit = 1024 * 1024 # 1MB limit (1024 bytes * 1024 bytes = 1MB)
limit = get_setting_value('WEBHOOK_SIZE')
payloadType = get_setting_value('WEBHOOK_PAYLOAD')
endpointUrl = get_setting_value('WEBHOOK_URL')
secret = get_setting_value('WEBHOOK_SECRET')
requestMethod = get_setting_value('WEBHOOK_REQUEST_METHOD')
# use data type based on specified payload type
if payloadType == 'json':
# In this code, the truncate_json function is used to recursively traverse the JSON object
# and remove nodes that exceed the size limit. It checks the size of each node's JSON representation
# using json.dumps and includes only the nodes that are within the limit.
json_str = json.dumps(json_data)
if len(json_str) <= limit:
payloadData = json_data
else:
def truncate_json(obj):
if isinstance(obj, dict):
return {
key: truncate_json(value)
for key, value in obj.items()
if len(json.dumps(value)) <= limit
}
elif isinstance(obj, list):
return [
truncate_json(item)
for item in obj
if len(json.dumps(item)) <= limit
]
else:
return obj
payloadData = truncate_json(json_data)
if payloadType == 'html':
if len(html_data) > limit:
payloadData = html_data[:limit] + " <h1>(text was truncated)</h1>"
else:
payloadData = html_data
if payloadType == 'text':
if len(text_data) > limit:
payloadData = text_data[:limit] + " (text was truncated)"
else:
payloadData = text_data
# Define slack-compatible payload
_json_payload = { "text": payloadData } if payloadType == 'text' else {
"username": "Pi.Alert",
"text": "There are new notifications",
"attachments": [{
"title": "Pi.Alert Notifications",
"title_link": get_setting_value('REPORT_DASHBOARD_URL'),
"text": payloadData
}]
}
# DEBUG - Write the json payload into a log file for debugging
write_file (logPath + '/webhook_payload.json', json.dumps(_json_payload))
# Using the Slack-Compatible Webhook endpoint for Discord so that the same payload can be used for both
# Consider: curl has the ability to load in data to POST from a file + piping
if(endpointUrl.startswith('https://discord.com/api/webhooks/') and not endpointUrl.endswith("/slack")):
_WEBHOOK_URL = f"{endpointUrl}/slack"
curlParams = ["curl","-i","-H", "Content-Type:application/json" ,"-d", json.dumps(_json_payload), _WEBHOOK_URL]
else:
_WEBHOOK_URL = endpointUrl
curlParams = ["curl","-i","-X", requestMethod , "-H", "Content-Type:application/json", "-d", json.dumps(_json_payload), _WEBHOOK_URL]
# Add HMAC signature if configured
if(secret != ''):
h = hmac.new(secret.encode("UTF-8"), json.dumps(_json_payload, separators=(',', ':')).encode(), hashlib.sha256).hexdigest()
curlParams.insert(4,"-H")
curlParams.insert(5,f"X-Webhook-Signature: sha256={h}")
try:
# Execute CURL call
mylog('debug', [f'[{pluginName}] curlParams: ', curlParams])
result = subprocess.run(curlParams, capture_output=True, text=True)
response_stderr = result.stderr
response_stdout = result.stdout
# Write stdout and stderr into .log files for debugging if needed
mylog('debug', [f'[{pluginName}] stdout: ', response_stdout])
mylog('debug', [f'[{pluginName}] stderr: ', response_stderr])
except subprocess.CalledProcessError as e:
# An error occurred, handle it
mylog('none', [f'[{pluginName}] ⚠ ERROR: ', e.output])
response_stderr = e.output
return response_stdout, response_stderr
# -------------------------------------------------------
if __name__ == '__main__':
sys.exit(main())

View File

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

View File

@@ -0,0 +1,12 @@
## Übersicht
ARP-Scan ist ein Kommandozeilen-Werkzeug, welches das ARP-Protokoll nutzt, um IP-Hosts im lokalen Netzwerk zu erkennen und identifizieren. Eine Alternative zum ARP-Scan ist die Aktivierung der PiHole-Integration (`PIHOLE_RUN`) in den Einstellungen. Die Dauer des ARP-Scan (und andere Netzwerkscan-Plugins, welche die `SCAN_SUBNETS`-Einstellung nutzen) ist abhängig von der Anzahl der zu prüfenden IP-Adressen. Daher ist es wichtig, dies mit größter Vorsicht und den korrekten Netzwerkmasken und -interfaces zu konfigurieren. Die [Subnetz-Dokumentation](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/SUBNETS.md) ansehen für mehr Hilfe zum Aufsetzen von VLANs, welche VLANs unterstützt werden und zum Herausfinden der Netzwerkmaske und -interfaces.
### Verwendung
- Zur Einstellungen-Seite gehen und die `SCAN_SUBNETS`-Einstellung anhand der [Subnetz-Dokumentation](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/SUBNETS.md) konfigurieren
- Das Plugin aktivieren, indem der `RUN`-Parameter von `disabled` auf den gewünschten Ausführzeitpunkt gesetzt wird (normalerweise: `schedule`)
- Zeitplan in der `ARPSCAN_RUN_SCHD`-Einstellung setzen
- Zeitlimit nach Bedarf in der `ARPSCAN_RUN_TIMEOUT`-Einstellung setzen
- SPEICHERN
- Auf Ausführung des nächsten Scans warten

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

View File

@@ -1,21 +1,25 @@
{
"code_name": "arp_scan",
"unique_prefix": "ARPSCAN",
"plugin_type": "device_scanner",
"enabled": true,
"data_source": "script",
"mapped_to_table": "CurrentScan",
"mapped_to_table": "CurrentScan",
"data_filters": [
{
"compare_column" : "Object_PrimaryID",
"compare_operator" : "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
"compare_use_quotes": true
"compare_column": "Object_PrimaryID",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
"compare_use_quotes": true
}
],
"show_ui": true,
"localized": ["display_name", "description", "icon"],
"localized": [
"display_name",
"description",
"icon"
],
"display_name": [
{
"language_code": "en_us",
@@ -24,7 +28,11 @@
{
"language_code": "es_es",
"string": "Arp-Scan (Escaneo de red)"
}
},
{
"language_code": "de_de",
"string": "ARP-Scan (Netzwerkscan)"
}
],
"icon": [
{
@@ -34,7 +42,11 @@
{
"language_code": "es_es",
"string": "<i class=\"fa-solid fa-search\"></i>"
}
},
{
"language_code": "de_de",
"string": "<i class=\"fa-solid fa-search\"></i>"
}
],
"description": [
{
@@ -44,48 +56,77 @@
{
"language_code": "es_es",
"string": "Este plugin es para ejecutar un escaneo arp en la red local."
}
],
"params" : [
},
{
"name" : "subnets",
"type" : "setting",
"value" : "SCAN_SUBNETS",
"base64": true
}],
"language_code": "de_de",
"string": "Dieses Plugin wird genutzt, um einen ARP-Scan auf dem lokalen Netzwerk durchzuführen"
}
],
"params": [
{
"name": "subnets",
"type": "setting",
"value": "SCAN_SUBNETS",
"base64": true
}
],
"settings": [
{
"function": "RUN",
"type": "text.select",
"default_value":"schedule",
"options": ["disabled", "once", "schedule", "always_after_scan", "on_new_device"],
"localized": ["name", "description"],
"events": ["run"],
"name" :[
{
"language_code":"en_us",
"string" : "When to run"
},
{
"language_code":"es_es",
"string" : "Cuando ejecutar"
}],
"description": [{
"language_code":"en_us",
"string" : "Specify when your Network-discovery scan will run. Typical setting would be <code>schedule</code> and then you specify a cron-like schedule in the <a href=\"#ARPSCAN_RUN_SCHD\"><code>ARPSCAN_RUN_SCHD</code>setting</a> "
},
{
"language_code":"es_es",
"string" : "Especifique cuándo se ejecutará su análisis de descubrimiento de red. La configuración típica sería <code>schedule</code> y luego se especifica una programación similar a cron en la configuración <a href=\"#ARPSCAN_RUN_SCHD\"><code>ARPSCAN_RUN_SCHD</code></a> "
}]
},
"function": "RUN",
"type": "text.select",
"default_value": "schedule",
"options": [
"disabled",
"once",
"schedule",
"always_after_scan",
"on_new_device"
],
"localized": [
"name",
"description"
],
"events": [
"run"
],
"name": [
{
"language_code": "en_us",
"string": "When to run"
},
{
"language_code": "es_es",
"string": "Cuando ejecutar"
},
{
"language_code": "de_de",
"string": "Wann ausführen"
}
],
"description": [
{
"language_code": "en_us",
"string": "Specify when your Network-discovery scan will run. Typical setting would be <code>schedule</code> and then you specify a cron-like schedule in the <a href=\"#ARPSCAN_RUN_SCHD\"><code>ARPSCAN_RUN_SCHD</code>setting</a>. ⚠ Use the same schedule if you have multiple <i class=\"fa-solid fa-magnifying-glass-plus\"></i> Device scanners enabled."
},
{
"language_code": "es_es",
"string": "Especifique cuándo se ejecutará su análisis de descubrimiento de red. La configuración típica sería <code>schedule</code> y luego se especifica una programación similar a cron en la configuración <a href=\"#ARPSCAN_RUN_SCHD\"><code>ARPSCAN_RUN_SCHD</code></a> "
},
{
"language_code": "de_de",
"string": "Auswählen wann der Netzwerkscan laufen soll. Typischerweise wird <code>schedule</code> ausgewählt und ein cron-Intervall in der <a href=\"#ARPSCAN_RUN_SCHD\"><code>ARPSCAN_RUN_SCHD</code>Einstellung</a> gesetzt."
}
]
},
{
"function": "CMD",
"type": "readonly",
"default_value": "python3 /home/pi/pialert/front/plugins/arp_scan/script.py userSubnets={subnets}",
"options": [],
"localized": ["name", "description"],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
@@ -94,7 +135,11 @@
{
"language_code": "es_es",
"string": "Comando"
}
},
{
"language_code": "de_de",
"string": "Befehl"
}
],
"description": [
{
@@ -104,16 +149,22 @@
{
"language_code": "es_es",
"string": "Comando para ejecutar. Esto no debe ser cambiado"
}
},
{
"language_code": "de_de",
"string": "Auszuführender Befehl. Dieser sollte nicht geändert werden"
}
]
},
{
"function": "RUN_TIMEOUT",
"type": "integer",
"default_value": 300,
"options": [],
"localized": ["name", "description"],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
@@ -122,7 +173,11 @@
{
"language_code": "es_es",
"string": "Tiempo límite de ejecución"
}
},
{
"language_code": "de_de",
"string": "Zeitlimit"
}
],
"description": [
{
@@ -132,70 +187,126 @@
{
"language_code": "es_es",
"string": "Tiempo máximo en segundos para esperar a que finalice el script. Si se supera este tiempo, se cancela el script."
}
},
{
"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."
}
]
},
{
"function": "RUN_SCHD",
"type": "text",
"default_value":"*/3 * * * *",
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code":"en_us",
"string" : "Schedule"
"function": "RUN_SCHD",
"type": "text",
"default_value": "*/5 * * * *",
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Schedule"
},
{
"language_code":"es_es",
"string" : "Schedule"
}],
"description": [{
"language_code":"en_us",
"string" : "Only enabled if you select <code>schedule</code> in the <a href=\"#ARPSCAN_RUN\"><code>ARPSCAN_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>*/3 * * * *</code> will run the scan every 3 minutes. Will be run NEXT time the time passes. <br/> It's recommended to use the same schedule interval for all plugins responsible for discovering new devices."
{
"language_code": "es_es",
"string": "Schedule"
},
{
"language_code":"es_es",
"string" : "Solo está habilitado si selecciona <code>schedule</code> en la configuración <a href=\"#ARPSCAN_RUN\"><code>ARPSCAN_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>*/3 * * * *</code> ejecutará el escaneo cada 3 minutos. Se ejecutará la PRÓXIMA vez que pase el tiempo. <br/> Se recomienda utilizar el mismo intervalo de programación para todos los complementos que analizan su red."
}]
{
"language_code": "de_de",
"string": "Zeitplan"
}
],
"description": [
{
"language_code": "en_us",
"string": "Only enabled if you select <code>schedule</code> in the <a href=\"#ARPSCAN_RUN\"><code>ARPSCAN_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>*/3 * * * *</code> will run the scan every 3 minutes. Will be run NEXT time the time passes. <br/> It's recommended to use the same schedule interval for all plugins responsible for discovering new devices."
},
{
"language_code": "es_es",
"string": "Solo está habilitado si selecciona <code>schedule</code> en la configuración <a href=\"#ARPSCAN_RUN\"><code>ARPSCAN_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>*/3 * * * *</code> ejecutará el escaneo cada 3 minutos. Se ejecutará la PRÓXIMA vez que pase el tiempo. <br/> Se recomienda utilizar el mismo intervalo de programación para todos los complementos que analizan su red."
},
{
"language_code": "de_de",
"string": "Nur aktiv, wenn <code>schedule</code> in der <a href=\"#ARPSCAN_RUN\"><code>ARPSCAN_RUN</code> Einstellung</a> ausgewählt wurde. Sichergehen, dass das Intervall in einem korrekten cron-ähnlichen Format angegeben wurde (z.B. auf <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a> testen). <code>*/3 * * * *</code> würde den Scan alle 3 Minuten starten. Wird erst beim NÄCHSTEN Intervall ausgeführt. <br/>Es wird empfohlen, das Intervall aller Plugins, welche nach neuen Geräten suchen, auf den gleichen Wert zu setzen."
}
]
},
{
"function": "WATCH",
"type": "text.multiselect",
"default_value":["Watched_Value1", "Watched_Value2"],
"options": ["Watched_Value1","Watched_Value2","Watched_Value3","Watched_Value4"],
"localized": ["name", "description"],
"name" :[{
"language_code":"en_us",
"string" : "Watched"
"function": "WATCH",
"type": "text.multiselect",
"default_value": [
"Watched_Value1",
"Watched_Value2"
],
"options": [
"Watched_Value1",
"Watched_Value2",
"Watched_Value3",
"Watched_Value4"
],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Watched"
},
{
"language_code":"es_es",
"string" : "Watched"
}] ,
"description":[{
"language_code":"en_us",
"string" : "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>Watched_Value1</code> is IP</li><li><code>Watched_Value2</code> is Vendor</li><li><code>Watched_Value3</code> is Interface </li><li><code>Watched_Value4</code> is N/A </li></ul>"
{
"language_code": "es_es",
"string": "Watched"
},
{
"language_code":"es_es",
"string" : "Envía una notificación si los valores seleccionados cambian. Utilice <code>CTRL + clic</code> para seleccionar/deseleccionar. <ul> <li><code>Valor_observado1</code> es IP</li><li><code>Valor_observado2</code> es Proveedor</li><li><code>Valor_observado3</code> es Interfaz </li><li><code>Valor_observado4</code> es N/A </li></ul>"
}]
{
"language_code": "de_de",
"string": "Überwacht"
}
],
"description": [
{
"language_code": "en_us",
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>Watched_Value1</code> is IP</li><li><code>Watched_Value2</code> is Vendor</li><li><code>Watched_Value3</code> is Interface </li><li><code>Watched_Value4</code> is N/A </li></ul>"
},
{
"language_code": "es_es",
"string": "Envía una notificación si los valores seleccionados cambian. Utilice <code>CTRL + clic</code> para seleccionar/deseleccionar. <ul> <li><code>Valor_observado1</code> es IP</li><li><code>Valor_observado2</code> es Proveedor</li><li><code>Valor_observado3</code> es Interfaz </li><li><code>Valor_observado4</code> es N/A </li></ul>"
},
{
"language_code": "de_de",
"string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. <code>STRG + klicken</code> zum aus-/abwählen. <ul> <li><code>Watched_Value1</code> ist die IP</li><li><code>Watched_Value2</code> ist der Hersteller</li><li><code>Watched_Value3</code> ist das Interface </li><li><code>Watched_Value4</code> ist nicht in Verwendung </li></ul>"
}
]
},
{
"function": "REPORT_ON",
"type": "text.multiselect",
"default_value": ["new"],
"options": ["new", "watched-changed", "watched-not-changed", "missing-in-last-scan"],
"localized": ["name", "description"],
"default_value": [
"new"
],
"options": [
"new",
"watched-changed",
"watched-not-changed",
"missing-in-last-scan"
],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Report on"
},
{
{
"language_code": "es_es",
"string": "Informar sobre"
}
},
{
"language_code": "de_de",
"string": "Benachrichtige wenn"
}
],
"description": [
{
@@ -205,157 +316,235 @@
{
"language_code": "es_es",
"string": "Cuándo debe enviarse una notificación."
}
},
{
"language_code": "de_de",
"string": "Wann Benachrichtigungen gesendet werden sollen."
}
]
},
{
"function": "ARGS",
"type": "text",
"default_value": "sudo arp-scan --ignoredups --retry=6",
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Arguments"
}
],
"description": [
{
"language_code": "en_us",
"string": "Arguments to run arps-scan with. Recommended and tested only with the setting: <br/> <code>sudo arp-scan --ignoredups --retry=6</code>."
}
]
}
],
"database_column_definitions":
[
"database_column_definitions": [
{
"column": "Object_PrimaryID",
"mapped_to_column": "cur_MAC",
"mapped_to_column": "cur_MAC",
"css_classes": "col-sm-2",
"show": true,
"type": "device_name_mac",
"default_value":"",
"type": "device_name_mac",
"default_value": "",
"options": [],
"localized": ["name"],
"name":[{
"language_code":"en_us",
"string" : "MAC"
},
{
"language_code":"es_es",
"string" : "MAC"
}]
},
{
"column": "Watched_Value1",
"mapped_to_column": "cur_IP",
"css_classes": "col-sm-2",
"show": true,
"type": "device_ip",
"default_value":"",
"options": [],
"localized": ["name"],
"name":[{
"language_code":"en_us",
"string" : "IP"
},
{
"language_code":"es_es",
"string" : "IP"
}]
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "MAC"
},
{
"language_code": "es_es",
"string": "MAC"
},
{
"language_code": "de_de",
"string": "MAC"
}
]
},
{
"column": "Watched_Value2",
"mapped_to_column": "cur_Vendor",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value":"",
"options": [],
"localized": ["name"],
"name":[{
"language_code":"en_us",
"string" : "Vendor"
},
{
"language_code":"es_es",
"string" : "Proveedor"
}]
} ,
{
"column": "Dummy",
"mapped_to_column": "cur_ScanMethod",
"mapped_to_column_data": {
"value": "arp-scan"
},
"column": "Watched_Value1",
"mapped_to_column": "cur_IP",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value":"",
"type": "device_ip",
"default_value": "",
"options": [],
"localized": ["name"],
"name":[{
"language_code":"en_us",
"string" : "Scan method"
},
{
"language_code":"es_es",
"string" : "Método de escaneo"
}]
} ,
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "IP"
},
{
"language_code": "es_es",
"string": "IP"
},
{
"language_code": "de_de",
"string": "IP"
}
]
},
{
"column": "Watched_Value2",
"mapped_to_column": "cur_Vendor",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Vendor"
},
{
"language_code": "es_es",
"string": "Proveedor"
},
{
"language_code": "de_de",
"string": "Hersteller"
}
]
},
{
"column": "Dummy",
"mapped_to_column": "cur_ScanMethod",
"mapped_to_column_data": {
"value": "arp-scan"
},
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Scan method"
},
{
"language_code": "es_es",
"string": "Método de escaneo"
},
{
"language_code": "de_de",
"string": "Scanmethode"
}
]
},
{
"column": "DateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value":"",
"type": "label",
"default_value": "",
"options": [],
"localized": ["name"],
"name":[{
"language_code":"en_us",
"string" : "Created"
},
{
"language_code":"es_es",
"string" : "Creado"
}]
},
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Created"
},
{
"language_code": "es_es",
"string": "Creado"
},
{
"language_code": "de_de",
"string": "Erstellt"
}
]
},
{
"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": "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"
},
{
"language_code": "de_de",
"string": "Geändert"
}
]
},
{
"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"
}]
}
"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"
},
{
"language_code": "de_de",
"string": "Status"
}
]
}
]
}
}

View File

@@ -12,9 +12,11 @@ from time import strftime
sys.path.append("/home/pi/pialert/front/plugins")
sys.path.append('/home/pi/pialert/pialert')
from plugin_helper import Plugin_Object, Plugin_Objects
# pialert modules
from database import DB
from plugin_helper import Plugin_Object, Plugin_Objects, handleEmpty
from logger import mylog, append_line_to_file
from helper import timeNowTZ
from helper import timeNowTZ, get_setting_value
from const import logPath, pialertPath
CUR_PATH = str(pathlib.Path(__file__).parent.resolve())
@@ -62,6 +64,11 @@ def main():
subnets_list = userSubnetsParam.split(',')
else:
subnets_list = [userSubnetsParam]
# Create a database connection
db = DB() # instance of class DB
db.open()
# Execute the ARP scanning process on the list of subnets (whether it's one or multiple subnets).
# The function 'execute_arpscan' is assumed to be defined elsewhere in the code.
@@ -70,14 +77,14 @@ def main():
for device in unique_devices:
plugin_objects.add_object(
primaryId=device['mac'], # MAC (Device Name)
secondaryId=device['ip'], # IP Address
watched1=device['ip'], # Device Name
watched2=device.get('hw', ''), # Vendor (assuming it's in the 'hw' field)
watched3=device.get('interface', ''), # Add the interface
watched4='',
extra='arp-scan',
foreignKey="")
primaryId = handleEmpty(device['mac']), # MAC (Device Name)
secondaryId = handleEmpty(device['ip']), # IP Address
watched1 = handleEmpty(device['ip']), # Device Name
watched2 = handleEmpty(device.get('hw', '')), # Vendor (assuming it's in the 'hw' field)
watched3 = handleEmpty(device.get('interface', '')), # Add the interface
watched4 = '',
extra = 'arp-scan',
foreignKey = "")
plugin_objects.write_result_file()
@@ -132,7 +139,7 @@ def execute_arpscan(userSubnets):
def execute_arpscan_on_interface(interface):
# Prepare command arguments
arpscan_args = ['sudo', 'arp-scan', '--ignoredups', '--retry=6'] + interface.split()
arpscan_args = get_setting_value('ARPSCAN_ARGS').split() + interface.split()
# Execute command
try:

View File

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

View File

@@ -1,6 +1,7 @@
{
"code_name": "csv_backup",
"unique_prefix": "CSVBCKP",
"plugin_type": "system",
"enabled": true,
"data_source": "script",
"show_ui": false,
@@ -24,15 +25,7 @@
{
"language_code": "en_us",
"string": "<i class=\"fa-solid fa-save\"></i>"
},
{
"language_code": "es_es",
"string": "<i class=\"fa-solid fa-save\"></i>"
},
{
"language_code": "de_de",
"string": "<i class=\"fa-solid fa-save\"></i>"
}
}
],
"description": [
{

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python
# test script by running:
# /home/pi/pialert/front/plugins/db_cleanup/script.py pluginskeephistory=250 hourstokeepnewdevice=48 daystokeepevents=90
# /home/pi/pialert/front/plugins/db_cleanup/script.py pluginskeephistory=250 hourstokeepnewdevice=48 daystokeepevents=90 pholuskeepdays=30
import os
import pathlib
@@ -17,8 +17,8 @@ sys.path.append('/home/pi/pialert/pialert')
from plugin_helper import Plugin_Object, Plugin_Objects, decodeBase64
from logger import mylog, append_line_to_file
from helper import timeNowTZ
from const import logPath, pialertPath
from helper import timeNowTZ, get_setting_value
from const import logPath, pialertPath, fullDbPath
CUR_PATH = str(pathlib.Path(__file__).parent.resolve())
@@ -35,16 +35,16 @@ def main():
values = parser.parse_args()
PLUGINS_KEEP_HIST = values.pluginskeephistory.split('=')[1]
HRS_TO_KEEP_NEWDEV = values.hourstokeepnewdevice.split('=')[1]
DAYS_TO_KEEP_EVENTS = values.daystokeepevents.split('=')[1]
PHOLUS_DAYS_DATA = values.pholuskeepdays.split('=')[1]
PLUGINS_KEEP_HIST = int(values.pluginskeephistory.split('=')[1])
HRS_TO_KEEP_NEWDEV = int(values.hourstokeepnewdevice.split('=')[1])
DAYS_TO_KEEP_EVENTS = int(values.daystokeepevents.split('=')[1])
PHOLUS_DAYS_DATA = int(values.pholuskeepdays.split('=')[1])
mylog('verbose', ['[DBCLNP] In script'])
# Execute cleanup/upkeep
cleanup_database('/home/pi/pialert/db/pialert.db', DAYS_TO_KEEP_EVENTS, PHOLUS_DAYS_DATA, HRS_TO_KEEP_NEWDEV, PLUGINS_KEEP_HIST)
cleanup_database(fullDbPath, DAYS_TO_KEEP_EVENTS, PHOLUS_DAYS_DATA, HRS_TO_KEEP_NEWDEV, PLUGINS_KEEP_HIST)
mylog('verbose', ['[DBCLNP] Cleanup complete file '])
@@ -92,6 +92,27 @@ 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}'])
# Build the SQL query to delete entries
delete_query = f"""DELETE FROM Notifications
WHERE "Index" NOT IN (
SELECT "Index"
FROM (
SELECT "Index",
ROW_NUMBER() OVER(PARTITION BY "Notifications" ORDER BY DateTimeCreated DESC) AS row_num
FROM Notifications
) AS ranked_objects
WHERE row_num <= {histCount}
);"""
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)'])

View File

@@ -1,6 +1,6 @@
## Overview
Plugin to run regular DDNS update tasks.
Plugin to run regular DDNS update tasks.
### Usage

View File

@@ -0,0 +1,7 @@
## Übersicht
Ein Plugin zur regelmäßigen Aktualisierung eines DynDNS-Eintrags.
### Verwendung
- Einstellungen-Seite für Details ansehen.

View File

@@ -1,7 +1,8 @@
{
"code_name": "ddns_update",
"unique_prefix": "DDNS",
"enabled": true,
"plugin_type": "system",
"enabled": true,
"data_filters": [
{
"compare_column": "Object_PrimaryID",
@@ -22,18 +23,30 @@
{
"language_code": "en_us",
"string": "DDNS update"
},
{
"language_code": "de_de",
"string": "DDNS-Aktualisierung"
}
],
"icon": [
{
"language_code": "en_us",
"string": "<i class=\"fa-solid fa-globe\"></i>"
},
{
"language_code": "de_de",
"string": "<i class=\"fa-solid fa-globe\"></i>"
}
],
"description": [
{
"language_code": "en_us",
"string": "A plugin update the DDNS record."
},
{
"language_code": "de_de",
"string": "Ein Plugin zur regelmäßigen Aktualisierung eines DynDNS-Eintrags."
}
],
"params": [
@@ -66,9 +79,11 @@
"settings": [
{
"function": "RUN",
"events": ["run"],
"events": [
"run"
],
"type": "text.select",
"default_value": "schedule",
"default_value": "disabled",
"options": [
"disabled",
"once",
@@ -90,13 +105,17 @@
},
{
"language_code": "de_de",
"string": "Wann laufen"
"string": "Wann ausführen"
}
],
"description": [
{
"language_code": "en_us",
"string": "When the plugin should run. An hourly or daily <code>SCHEDULE</code> is a good option."
},
{
"language_code": "de_de",
"string": "Wann das Plugin ausgeführt werden soll. Eine stündliche oder tägliche <code>SCHEDULE</code> wird empfohlen."
}
]
},
@@ -158,7 +177,7 @@
},
{
"language_code": "de_de",
"string": "Schedule"
"string": "Zeitplan"
}
],
"description": [
@@ -172,7 +191,7 @@
},
{
"language_code": "de_de",
"string": "Nur aktiviert, wenn Sie <code>schedule</code> in der <a href=\"#DDNS_RUN\"><code>DDNS_RUN</code>-Einstellung</a> auswählen. Stellen Sie sicher, dass Sie den Zeitplan im richtigen Cron-ähnlichen Format eingeben (z. B. validieren unter <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). Wenn Sie beispielsweise <code>0 4 * * *</code> eingeben, wird der Scan nach 4 Uhr morgens in der <a onclick=\"toggleAllSettings()\" href=\"#TIMEZONE\"><code>TIMEZONE</ ausgeführt. Code> den Sie oben festgelegt haben</a>. Wird das NÄCHSTE Mal ausgeführt, wenn die Zeit vergeht."
"string": "Nur aktiv, wenn <code>schedule</code> in der <a href=\"#DDNS_RUN\"><code>DDNS_RUN</code> Einstellung</a> ausgewählt wurde. Sichergehen, dass das Intervall in einem korrekten cron-ähnlichen Format angegeben wurde (z.B. auf <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a> testen). <code>0 4 * * *</code> rde den Scan täglich um 4 Uhr in der <a onclick=\"toggleAllSettings()\" href=\"#TIMEZONE\">oben ausgewählten <code>TIMEZONE</code></a> starten. Wird erst beim NÄCHSTEN Intervall ausgeführt."
}
]
},
@@ -196,7 +215,7 @@
},
{
"language_code": "de_de",
"string": "Zeitüberschreitung"
"string": "Zeitlimit"
}
],
"description": [
@@ -231,6 +250,10 @@
{
"language_code": "es_es",
"string": "URL del dominio DynDNS"
},
{
"language_code": "de_de",
"string": "DynDNS Domain URL"
}
],
"description": [
@@ -241,6 +264,10 @@
{
"language_code": "es_es",
"string": "URL del host DynDNS (no incluya http:// o https://)."
},
{
"language_code": "de_de",
"string": "DynDNS Host URL (do not include http:// or https://)."
}
]
},
@@ -261,6 +288,10 @@
{
"language_code": "es_es",
"string": "Usuario de DynDNS"
},
{
"language_code": "de_de",
"string": "DynDNS Benutzer"
}
],
"description": [
@@ -271,6 +302,10 @@
{
"language_code": "es_es",
"string": "El nombre de usuario utilizado para iniciar sesión en el servicio DynDNS (a veces, una dirección de correo electrónico completa)."
},
{
"language_code": "de_de",
"string": "Benutzername, welcher zum Login beim DynDNS-Service verwendet wird (manchmal die E-Mail-Adresse)."
}
]
},
@@ -291,6 +326,10 @@
{
"language_code": "es_es",
"string": "Contraseña de DynDNS"
},
{
"language_code": "de_de",
"string": "DynDNS Passwort"
}
],
"description": [
@@ -301,6 +340,10 @@
{
"language_code": "es_es",
"string": "La contraseña de acceso al servicio DynDNS."
},
{
"language_code": "de_de",
"string": "Passwort, welches zum Login beim DynDNS-Service verwendet wird."
}
]
},
@@ -321,6 +364,10 @@
{
"language_code": "es_es",
"string": "URL de actualización de DynDNS"
},
{
"language_code": "de_de",
"string": "DynDNS Aktualisierungs-URL"
}
],
"description": [
@@ -331,6 +378,10 @@
{
"language_code": "es_es",
"string": "Actualice la URL que comienza con <code>http://</code> o <code>https://</code>."
},
{
"language_code": "de_de",
"string": "Aktualisierungs-URL beginnend mit <code>http://</code> oder <code>https://</code>."
}
]
},
@@ -358,42 +409,73 @@
{
"language_code": "es_es",
"string": "Visto"
},
{
"language_code": "de_de",
"string": "Überwacht"
}
],
"description": [
{
"language_code": "en_us",
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>Watched_Value1</code> is Previous IP (not recommended)</li><li><code>Watched_Value2</code> unused</li><li><code>Watched_Value3</code> unused </li><li><code>Watched_Value4</code> unused </li></ul>"
},
{
"language_code": "de_de",
"string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. <code>STRG + klicken</code> zum aus-/abwählen. <ul> <li><code>Watched_Value1</code> ist die Vorige IP (nicht empfohlen)</li><li><code>Watched_Value2</code> ist nicht in Verwendung</li><li><code>Watched_Value3</code> ist nicht in Verwendung </li><li><code>Watched_Value4</code> ist nicht in Verwendung </li></ul>"
}
]
},
{
"function": "REPORT_ON",
"type": "text.multiselect",
"default_value":["new","watched-changed"],
"options": ["new","watched-changed","watched-not-changed", "missing-in-last-scan"],
"localized": ["name", "description"],
"name" :[{
"language_code":"en_us",
"string" : "Report on"
},
{
"language_code":"es_es",
"string" : "Informar sobre"
} ] ,
"description":[{
"language_code":"en_us",
"string" : "Send a notification only on these statuses. <code>new</code> means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. <code>watched-changed</code> means that selected <code>Watched_ValueN</code> columns changed."
},
{
"language_code":"es_es",
"string" : "Envíe una notificación solo en estos estados. <code>new</code> significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). <code>watched-changed</code> significa que las columnas <code>Watched_ValueN</code> seleccionadas cambiaron."
}]
}
"default_value": [
"new",
"watched-changed"
],
"options": [
"new",
"watched-changed",
"watched-not-changed",
"missing-in-last-scan"
],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Report on"
},
{
"language_code": "es_es",
"string": "Informar sobre"
},
{
"language_code": "de_de",
"string": "Benachrichtige wenn"
}
],
"description": [
{
"language_code": "en_us",
"string": "Send a notification only on these statuses. <code>new</code> means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. <code>watched-changed</code> means that selected <code>Watched_ValueN</code> columns changed."
},
{
"language_code": "es_es",
"string": "Envíe una notificación solo en estos estados. <code>new</code> significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). <code>watched-changed</code> significa que las columnas <code>Watched_ValueN</code> seleccionadas cambiaron."
},
{
"language_code": "de_de",
"string": "Benachrichtige nur bei diesen Status. <code>new</code> bedeutet ein neues eindeutiges (einzigartige Kombination aus PrimaryId und SecondaryId) Objekt wurde erkennt. <code>watched-changed</code> bedeutet eine ausgewählte <code>Watched_ValueN</code>-Spalte hat sich geändert."
}
]
}
],
"database_column_definitions": [
{
"column": "Object_PrimaryID",
"column": "Object_PrimaryID",
"css_classes": "col-sm-2",
"show": true,
"type": "device_name_mac",
@@ -410,11 +492,15 @@
{
"language_code": "es_es",
"string": "MAC"
},
{
"language_code": "de_de",
"string": "MAC"
}
]
},
{
"column": "Object_SecondaryID",
"column": "Object_SecondaryID",
"css_classes": "col-sm-2",
"show": true,
"type": "device_ip",
@@ -431,6 +517,10 @@
{
"language_code": "es_es",
"string": "IP"
},
{
"language_code": "de_de",
"string": "IP"
}
]
},
@@ -448,11 +538,15 @@
{
"language_code": "en_us",
"string": "Extra"
},
{
"language_code": "de_de",
"string": "Extra"
}
]
},
{
"column": "Dummy",
"column": "Dummy",
"mapped_to_column_data": {
"value": "DDNS"
},
@@ -472,6 +566,10 @@
{
"language_code": "es_es",
"string": "Método de escaneo"
},
{
"language_code": "de_de",
"string": "Scanmethode"
}
]
},
@@ -493,11 +591,15 @@
{
"language_code": "es_es",
"string": "Creado"
},
{
"language_code": "de_de",
"string": "Erstellt"
}
]
},
{
"column": "DateTimeChanged",
"column": "DateTimeChanged",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -514,6 +616,10 @@
{
"language_code": "es_es",
"string": "Cambiado"
},
{
"language_code": "de_de",
"string": "Geändert"
}
]
},
@@ -552,6 +658,10 @@
{
"language_code": "es_es",
"string": "Estado"
},
{
"language_code": "de_de",
"string": "Status"
}
]
}

View File

@@ -28,9 +28,11 @@ 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 = 'DDNS'
def main():
mylog('verbose', ['[DDNS] In script'])
mylog('verbose', [f'[{pluginName}] In script'])
parser = argparse.ArgumentParser(description='Check internet connectivity and IP')
@@ -52,7 +54,7 @@ def main():
# perform the new IP lookup and DDNS tasks if enabled
ddns_update( DDNS_UPDATE_URL, DDNS_USER, DDNS_PASSWORD, DDNS_DOMAIN, PREV_IP)
mylog('verbose', ['[DDNS] Finished '])
mylog('verbose', [f'[{pluginName}] Finished '])
return 0
@@ -65,20 +67,20 @@ def ddns_update ( DDNS_UPDATE_URL, DDNS_USER, DDNS_PASSWORD, DDNS_DOMAIN, PREV_I
# Update DDNS record if enabled and IP is different
# Get Dynamic DNS IP
mylog('verbose', ['[DDNS] Retrieving Dynamic DNS IP'])
mylog('verbose', [f'[{pluginName}] Retrieving Dynamic DNS IP'])
dns_IP = get_dynamic_DNS_IP(DDNS_DOMAIN)
# Check Dynamic DNS IP
if dns_IP == "" or dns_IP == "0.0.0.0" :
mylog('none', ['[DDNS] Error retrieving Dynamic DNS IP'])
mylog('none', [f'[{pluginName}] Error retrieving Dynamic DNS IP'])
mylog('none', ['[DDNS] ', dns_IP])
mylog('none', [f'[{pluginName}] ', dns_IP])
# Check DNS Change
if dns_IP != PREV_IP :
mylog('none', ['[DDNS] Updating Dynamic DNS IP'])
mylog('none', [f'[{pluginName}] Updating Dynamic DNS IP'])
message = set_dynamic_DNS_IP (DDNS_UPDATE_URL, DDNS_USER, DDNS_PASSWORD, DDNS_DOMAIN)
mylog('none', ['[DDNS] ', message])
mylog('none', [f'[{pluginName}] ', message])
# plugin_objects = Plugin_Objects(RESULT_FILE)
@@ -104,9 +106,10 @@ def get_dynamic_DNS_IP (DDNS_DOMAIN):
try:
# try runnning a subprocess
dig_output = subprocess.check_output (dig_args, universal_newlines=True)
mylog('none', [f'[{pluginName}] DIG output :', dig_output])
except subprocess.CalledProcessError as e:
# An error occured, handle it
mylog('none', ['[DDNS] ERROR - ', e.output])
mylog('none', [f'[{pluginName}] ⚠ ERROR - ', e.output])
dig_output = '' # probably no internet
# Check result is an IP
@@ -132,7 +135,7 @@ def set_dynamic_DNS_IP (DDNS_UPDATE_URL, DDNS_USER, DDNS_PASSWORD, DDNS_DOMAIN):
universal_newlines=True)
except subprocess.CalledProcessError as e:
# An error occured, handle it
mylog('none', ['[DDNS] ERROR - ',e.output])
mylog('none', [f'[{pluginName}] ⚠ ERROR - ',e.output])
curl_output = ""
return curl_output

View File

@@ -0,0 +1,51 @@
## Übersicht
Ein Plugin zum Importieren von Geräten aus dhcp.leases-Dateien.
### Verwendung
- Absolute Pfade der `dhcp.leases`-Dateien, welche importiert werden sollen, in der `DHCPLSS_paths_to_check`-Einstellung angeben.
- Angegebene Pfade in der `DHCPLSS_paths_to_check`-Einstellung in der `docker-compose.yml`-Datei mapppen.
#### Beispiel
Auszug aus `docker-compose.yml`:
```yaml
volumes:
...
# mapping different dhcp.leases files
- /first/location/dhcp.leases:/mnt/dhcp1.leases
- /second/location/dhcp.leases:/mnt/dhcp2.leases
...
```
`DHCPLSS_paths_to_check`-Einstellung:
```python
DHCPLSS_paths_to_check = ['/mnt/dhcp1.leases','/mnt/dhcp2.leases']
```
### Notizen
- Keine spezifische Konfiguration benötigt.
- Dieses Plugin erwartet dhcp.leases-Dateien im **dhcp.leases**-Format, welches sich vom von PiHole genutzten Format unterscheidet. [dhcpd.leases(5) - Linux man page]( https://linux.die.net/man/5/dhcpd.leases#:~:text=This%20database%20is%20a%20free,file%20is%20the%20current%20one.)
Beispiel Dateiformat: _(nicht alle Zeilen werden benötigt)_
```text
lease 192.168.79.15 {
starts 0 2016/08/21 13:25:45;
ends 0 2016/08/21 19:25:45;
cltt 0 2016/08/21 13:25:45;
binding state active;
next binding state free;
rewind binding state free;
hardware ethernet 8c:1a:bf:11:00:ea;
uid "\001\214\032\277\021\000\352";
option agent.circuit-id 0:17;
option agent.remote-id c0:a8:9:5;
client-hostname "android-8182e21c852776e7";
}
```

File diff suppressed because it is too large Load Diff

View File

@@ -6,11 +6,12 @@ import subprocess
import argparse
import os
import sys
import chardet
sys.path.append("/home/pi/pialert/front/plugins")
sys.path.append('/home/pi/pialert/pialert')
from plugin_helper import Plugin_Object, Plugin_Objects, handleEmpty
from plugin_helper import Plugin_Object, Plugin_Objects, handleEmpty, is_mac
from logger import mylog
from dhcp_leases import DhcpLeases
@@ -18,8 +19,12 @@ 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= 'DHCPLSS'
# -------------------------------------------------------------
def main():
mylog('verbose', ['[DHCPLSS] In script'])
mylog('verbose', [f'[{pluginName}] In script'])
last_run_logfile = open(RESULT_FILE, 'a')
last_run_logfile.write("")
@@ -32,42 +37,59 @@ def main():
if values.paths:
for path in values.paths.split('=')[1].split(','):
plugin_objects = get_entries(path, plugin_objects)
mylog('verbose', [f'[DHCPLSS] {len(plugin_objects)} Entries found in "{path}"'])
mylog('verbose', [f'[{pluginName}] {len(plugin_objects)} Entries found in "{path}"'])
plugin_objects.write_result_file()
# -------------------------------------------------------------
def get_entries(path, plugin_objects):
if 'pihole' in path:
with open(path, 'r') as f:
for line in f:
row = line.rstrip().split()
if len(row) == 5:
plugin_objects.add_object(
primaryId = handleEmpty(row[1]),
secondaryId = handleEmpty(row[2]),
watched1 = handleEmpty('True'),
watched2 = handleEmpty(row[3]),
watched3 = handleEmpty(row[4]),
watched4 = handleEmpty('True'),
extra = handleEmpty(path),
foreignKey = handleEmpty(row[1])
)
# Check if the path exists
if not os.path.exists(path):
mylog('none', [f'[{pluginName}] ⚠ ERROR: "{path}" does not exist.'])
else:
leases = DhcpLeases(path)
leasesList = leases.get()
for lease in leasesList:
plugin_objects.add_object(
primaryId = handleEmpty(lease.ethernet),
secondaryId = handleEmpty(lease.ip),
watched1 = handleEmpty(lease.active),
watched2 = handleEmpty(lease.hostname),
watched3 = handleEmpty(lease.hardware),
watched4 = handleEmpty(lease.binding_state),
extra = handleEmpty(path),
foreignKey = handleEmpty(lease.ethernet)
)
# Detect file encoding
with open(path, 'rb') as f:
result = chardet.detect(f.read())
# Use the detected encoding
encoding = result['encoding']
# Handle pihole-specific dhcp.leases files
if 'pihole' in path:
with open(path, 'r', encoding=encoding, errors='replace') as f:
for line in f:
row = line.rstrip().split()
if len(row) == 5:
plugin_objects.add_object(
primaryId = handleEmpty(row[1]),
secondaryId = handleEmpty(row[2]),
watched1 = handleEmpty('True'),
watched2 = handleEmpty(row[3]),
watched3 = handleEmpty(row[4]),
watched4 = handleEmpty('True'),
extra = handleEmpty(path),
foreignKey = handleEmpty(row[1])
)
else:
# Handle generic dhcp.leases files
leases = DhcpLeases(path)
leasesList = leases.get()
for lease in leasesList:
# filter out irrelevant entries (e.g. from OPNsense dhcp.leases files)
if is_mac(lease.ethernet):
plugin_objects.add_object(
primaryId = handleEmpty(lease.ethernet),
secondaryId = handleEmpty(lease.ip),
watched1 = handleEmpty(lease.active),
watched2 = handleEmpty(lease.hostname),
watched3 = handleEmpty(lease.hardware),
watched4 = handleEmpty(lease.binding_state),
extra = handleEmpty(path),
foreignKey = handleEmpty(lease.ethernet)
)
return plugin_objects
if __name__ == '__main__':

View File

@@ -1,6 +1,7 @@
{
"code_name": "dhcp_servers",
"unique_prefix": "DHCPSRVS",
"plugin_type": "other",
"enabled": true,
"data_source": "script",
"show_ui": true,
@@ -289,7 +290,7 @@
}],
"description": [{
"language_code":"en_us",
"string" : "Enable a regular scan of rogue DHCP servers. If you select <code>schedule</code> the scheduling settings from below are applied. If you select <code>once</code> the scan is run only once on start of the application (container) or after you update your settings."
"string" : "Enable a regular scan of rogue DHCP servers. If you select <code>schedule</code> the scheduling settings from below are applied. If you select <code>once</code> the scan is run only once on start of the application (container) or after you update your settings. ⚠ Use the same schedule if you have multiple <i class=\"fa-solid fa-magnifying-glass-plus\"></i> Device scanners enabled."
},
{
"language_code":"es_es",
@@ -345,7 +346,7 @@
{
"function": "RUN_TIMEOUT",
"type": "integer",
"default_value":5,
"default_value":10,
"options": [],
"localized": ["name", "description"],
"name" : [{

View File

@@ -0,0 +1,7 @@
## Übersicht
Ein Plugin zur regelmäßigen Prüfung der Internetverbindung und externen IP.
### Verwendung
- Einstellungen-Seite für Details ansehen.

View File

@@ -1,6 +1,7 @@
{
"code_name": "internet_ip",
"unique_prefix": "INTRNT",
"plugin_type": "device_scanner",
"enabled": true,
"mapped_to_table": "CurrentScan",
"data_filters": [
@@ -23,18 +24,30 @@
{
"language_code": "en_us",
"string": "Internet check"
},
{
"language_code": "en_us",
"string": "Internet-Check"
}
],
"icon": [
{
"language_code": "en_us",
"string": "<i class=\"fa-solid fa-globe\"></i>"
},
{
"language_code": "de_de",
"string": "<i class=\"fa-solid fa-globe\"></i>"
}
],
"description": [
{
"language_code": "en_us",
"string": "A plugin to check your internet connectivity and IP."
},
{
"language_code": "de_de",
"string": "Ein Plugin zur Prüfung der Internetverbindung und externen IP."
}
],
"params": [
@@ -44,16 +57,18 @@
"value": "SELECT dev_LastIP FROM Devices WHERE dev_MAC = 'Internet' "
},
{
"name": "DIG_GET_IP_ARG",
"name": "INTRNT_DIG_GET_IP_ARG",
"type": "setting",
"value": "DIG_GET_IP_ARG",
"value": "INTRNT_DIG_GET_IP_ARG",
"base64": true
}
],
"settings": [
{
"function": "RUN",
"events": ["run"],
"events": [
"run"
],
"type": "text.select",
"default_value": "schedule",
"options": [
@@ -77,20 +92,24 @@
},
{
"language_code": "de_de",
"string": "Wann laufen"
"string": "Wann ausführen"
}
],
"description": [
{
"language_code": "en_us",
"string": "When the plugin should run. An hourly or daily <code>SCHEDULE</code> is a good option."
},
{
"language_code": "de_de",
"string": "Wann das Plugin ausgeführt werden soll. Eine stündliche oder tägliche <code>SCHEDULE</code> wird empfohlen."
}
]
},
{
"function": "CMD",
"type": "readonly",
"default_value": "python3 /home/pi/pialert/front/plugins/internet_ip/script.py prev_ip={prev_ip} DIG_GET_IP_ARG={DIG_GET_IP_ARG}",
"default_value": "python3 /home/pi/pialert/front/plugins/internet_ip/script.py prev_ip={prev_ip} INTRNT_DIG_GET_IP_ARG={INTRNT_DIG_GET_IP_ARG}",
"options": [],
"localized": [
"name",
@@ -125,6 +144,44 @@
}
]
},
{
"function": "DIG_GET_IP_ARG",
"type": "text",
"default_value": "-4 myip.opendns.com @resolver1.opendns.com",
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Internet IP discovery"
},
{
"language_code": "es_es",
"string": "Descubrir de IP de Internet"
},
{
"language_code": "de_de",
"string": "Erkennung externer IP (\"Internet IP\")"
}
],
"description": [
{
"language_code": "en_us",
"string": "Change the <a href=\"https://linux.die.net/man/1/dig\" target=\"_blank\">dig utility</a> arguments if you have issues resolving your Internet IP. Arguments are added at the end of the following command: <code>dig +short </code>."
},
{
"language_code": "es_es",
"string": "Cambie los argumentos de la <a href=\"https://linux.die.net/man/1/dig\" target=\"_blank\">utilidad de dig</a> si tiene problemas para resolver su IP de Internet. Los argumentos se agregan al final del siguiente comando: <code>dig +short </code>."
},
{
"language_code": "de_de",
"string": "Ändere die Argumente des <a href=\"https://linux.die.net/man/1/dig\" target=\"_blank\">dig Dienstprogramms</a>, wenn Probleme beim Auflösen der externen IP auftreten. Argumente werden an das Ende des folgenden Befehls angehängt: <code>dig +short </code>."
}
]
},
{
"function": "RUN_SCHD",
"type": "text",
@@ -145,13 +202,13 @@
},
{
"language_code": "de_de",
"string": "Schedule"
"string": "Zeitplan"
}
],
"description": [
{
"language_code": "en_us",
"string": "Only enabled if you select <code>schedule</code> in the <a href=\"#INTRNT_RUN\"><code>INTRNT_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."
"string": "Only enabled if you select <code>schedule</code> in the <a href=\"#INTRNT_RUN\"><code>INTRNT_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. It's recommended to use the same schedule interval for all plugins responsible for discovering new devices."
},
{
"language_code": "es_es",
@@ -159,7 +216,7 @@
},
{
"language_code": "de_de",
"string": "Nur aktiviert, wenn Sie <code>schedule</code> in der <a href=\"#INTRNT_RUN\"><code>INTRNT_RUN</code>-Einstellung</a> auswählen. Stellen Sie sicher, dass Sie den Zeitplan im richtigen Cron-ähnlichen Format eingeben (z. B. validieren unter <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). Wenn Sie beispielsweise <code>0 4 * * *</code> eingeben, wird der Scan nach 4 Uhr morgens in der <a onclick=\"toggleAllSettings()\" href=\"#TIMEZONE\"><code>TIMEZONE</ ausgeführt. Code> den Sie oben festgelegt haben</a>. Wird das NÄCHSTE Mal ausgeführt, wenn die Zeit vergeht."
"string": "Nur aktiv, wenn <code>schedule</code> in der <a href=\"#INTRNT_RUN\"><code>INTRNT_RUN</code>Einstellung</a> ausgewählt wurde. Sichergehen, dass das Intervall in einem korrekten cron-ähnlichen Format angegeben wurde (z.B. auf <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a> testen). <code>0 4 * * *</code> rde den Scan täglich um 4 Uhr in der <a onclick=\"toggleAllSettings()\" href=\"#TIMEZONE\">oben ausgewählten <code>TIMEZONE</code></a> starten. Wird erst beim NÄCHSTEN Intervall ausgeführt. <br/>Es wird empfohlen, das Intervall aller Plugins, welche nach neuen Geräten suchen, auf den gleichen Wert zu setzen."
}
]
},
@@ -183,7 +240,7 @@
},
{
"language_code": "de_de",
"string": "Zeitüberschreitung"
"string": "Zeitlimit"
}
],
"description": [
@@ -225,38 +282,69 @@
{
"language_code": "es_es",
"string": "Visto"
},
{
"language_code": "de_de",
"string": "Überwacht"
}
],
"description": [
{
"language_code": "en_us",
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>Watched_Value1</code> is Previous IP (not recommended)</li><li><code>Watched_Value2</code> unused</li><li><code>Watched_Value3</code> unused </li><li><code>Watched_Value4</code> unused </li></ul>"
},
{
"language_code": "de_de",
"string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. <code>STRG + klicken</code> zum aus-/abwählen. <ul> <li><code>Watched_Value1</code> ist die Vorige IP (nicht empfohlen)</li><li><code>Watched_Value2</code> ist nicht in Verwendung</li><li><code>Watched_Value3</code> ist nicht in Verwendung </li><li><code>Watched_Value4</code> ist nicht in Verwendung </li></ul>"
}
]
},
{
"function": "REPORT_ON",
"type": "text.multiselect",
"default_value":["new","watched-changed"],
"options": ["new","watched-changed","watched-not-changed", "missing-in-last-scan"],
"localized": ["name", "description"],
"name" :[{
"language_code":"en_us",
"string" : "Report on"
},
{
"language_code":"es_es",
"string" : "Informar sobre"
} ] ,
"description":[{
"language_code":"en_us",
"string" : "Send a notification only on these statuses. <code>new</code> means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. <code>watched-changed</code> means that selected <code>Watched_ValueN</code> columns changed."
},
{
"language_code":"es_es",
"string" : "Envíe una notificación solo en estos estados. <code>new</code> significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). <code>watched-changed</code> significa que las columnas <code>Watched_ValueN</code> seleccionadas cambiaron."
}]
}
"default_value": [
"new",
"watched-changed"
],
"options": [
"new",
"watched-changed",
"watched-not-changed",
"missing-in-last-scan"
],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Report on"
},
{
"language_code": "es_es",
"string": "Informar sobre"
},
{
"language_code": "de_de",
"string": "Benachrichtige wenn"
}
],
"description": [
{
"language_code": "en_us",
"string": "Send a notification only on these statuses. <code>new</code> means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. <code>watched-changed</code> means that selected <code>Watched_ValueN</code> columns changed."
},
{
"language_code": "es_es",
"string": "Envíe una notificación solo en estos estados. <code>new</code> significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). <code>watched-changed</code> significa que las columnas <code>Watched_ValueN</code> seleccionadas cambiaron."
},
{
"language_code": "de_de",
"string": "Benachrichtige nur bei diesen Status. <code>new</code> bedeutet ein neues eindeutiges (einzigartige Kombination aus PrimaryId und SecondaryId) Objekt wurde erkennt. <code>watched-changed</code> bedeutet eine ausgewählte <code>Watched_ValueN</code>-Spalte hat sich geändert."
}
]
}
],
"database_column_definitions": [
{
@@ -278,6 +366,10 @@
{
"language_code": "es_es",
"string": "MAC"
},
{
"language_code": "de_de",
"string": "MAC"
}
]
},
@@ -300,6 +392,10 @@
{
"language_code": "es_es",
"string": "IP"
},
{
"language_code": "de_de",
"string": "IP"
}
]
},
@@ -317,6 +413,25 @@
{
"language_code": "en_us",
"string": "Extra"
},
{
"language_code": "de_de",
"string": "Extra"
}
]
},
{
"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"
}
]
},
@@ -342,6 +457,10 @@
{
"language_code": "es_es",
"string": "Método de escaneo"
},
{
"language_code": "de_de",
"string": "Scanmethode"
}
]
},
@@ -363,6 +482,10 @@
{
"language_code": "es_es",
"string": "Creado"
},
{
"language_code": "de_de",
"string": "Erstellt"
}
]
},
@@ -385,6 +508,10 @@
{
"language_code": "es_es",
"string": "Cambiado"
},
{
"language_code": "de_de",
"string": "Geändert"
}
]
},
@@ -423,6 +550,10 @@
{
"language_code": "es_es",
"string": "Estado"
},
{
"language_code": "de_de",
"string": "Status"
}
]
}

View File

@@ -20,7 +20,7 @@ sys.path.append('/home/pi/pialert/pialert')
from plugin_helper import Plugin_Object, Plugin_Objects, decodeBase64
from logger import mylog, append_line_to_file
from helper import timeNowTZ, check_IP_format
from helper import timeNowTZ, check_IP_format, get_setting_value
from const import logPath, pialertPath, fullDbPath
@@ -28,29 +28,31 @@ 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 = 'INTRNT'
def main():
mylog('verbose', ['[INTRNT] In script'])
mylog('verbose', [f'[{pluginName}] In script'])
parser = argparse.ArgumentParser(description='Check internet connectivity and IP')
parser.add_argument('prev_ip', action="store", help="Previous IP address to compare against the current IP")
parser.add_argument('DIG_GET_IP_ARG', action="store", help="Arguments for the 'dig' command to retrieve the IP address")
parser.add_argument('DIG_GET_IP_ARG', action="store", help="Arguments for the 'dig' command to retrieve the IP address") # unused
values = parser.parse_args()
PREV_IP = values.prev_ip.split('=')[1]
DIG_GET_IP_ARG = values.DIG_GET_IP_ARG.split('=b')[1] # byte64 encoded
DIG_GET_IP_ARG = get_setting_value("INTRNT_DIG_GET_IP_ARG")
mylog('verbose', ['[INTRNT] DIG_GET_IP_ARG: ', DIG_GET_IP_ARG])
mylog('verbose', [f'[{pluginName}] INTRNT_DIG_GET_IP_ARG: ', DIG_GET_IP_ARG])
# Decode the base64-encoded value to get the actual value in ASCII format.
DIG_GET_IP_ARG = base64.b64decode(DIG_GET_IP_ARG).decode('ascii')
# DIG_GET_IP_ARG = base64.b64decode(DIG_GET_IP_ARG).decode('ascii')
mylog('verbose', [f'[INTRNT] DIG_GET_IP_ARG resolved: {DIG_GET_IP_ARG} '])
# mylog('verbose', [f'[{pluginName}] DIG_GET_IP_ARG resolved: {DIG_GET_IP_ARG} '])
# perform the new IP lookup
new_internet_IP = check_internet_IP( PREV_IP, DIG_GET_IP_ARG)
new_internet_IP, cmd_output = check_internet_IP( PREV_IP, DIG_GET_IP_ARG)
plugin_objects = Plugin_Objects(RESULT_FILE)
@@ -58,7 +60,7 @@ def main():
primaryId = 'Internet', # MAC (Device Name)
secondaryId = new_internet_IP, # IP Address
watched1 = f'Previous IP: {PREV_IP}',
watched2 = '',
watched2 = cmd_output.replace('\n',''),
watched3 = '',
watched4 = '',
extra = f'Previous IP: {PREV_IP}',
@@ -66,7 +68,7 @@ def main():
plugin_objects.write_result_file()
mylog('verbose', ['[INTRNT] Finished '])
mylog('verbose', [f'[{pluginName}] Finished '])
return 0
@@ -77,10 +79,10 @@ def main():
def check_internet_IP ( PREV_IP, DIG_GET_IP_ARG ):
# Get Internet IP
mylog('verbose', ['[INTRNT] - Retrieving Internet IP'])
internet_IP = get_internet_IP(DIG_GET_IP_ARG)
mylog('verbose', [f'[{pluginName}] - Retrieving Internet IP'])
internet_IP, cmd_output = get_internet_IP(DIG_GET_IP_ARG)
mylog('verbose', [f'[INTRNT] Current internet_IP : {internet_IP}'])
mylog('verbose', [f'[{pluginName}] Current internet_IP : {internet_IP}'])
# Check previously stored IP
previous_IP = '0.0.0.0'
@@ -88,23 +90,26 @@ def check_internet_IP ( PREV_IP, DIG_GET_IP_ARG ):
if PREV_IP is not None and len(PREV_IP) > 0 :
previous_IP = PREV_IP
mylog('verbose', [f'[INTRNT] previous_IP : {previous_IP}'])
mylog('verbose', [f'[{pluginName}] previous_IP : {previous_IP}'])
# logging
append_line_to_file (logPath + '/IP_changes.log', '['+str(timeNowTZ()) +']\t'+ internet_IP +'\n')
return internet_IP
return internet_IP, cmd_output
#-------------------------------------------------------------------------------
def get_internet_IP (DIG_GET_IP_ARG):
cmd_output = ''
# Using 'dig'
dig_args = ['dig', '+short'] + DIG_GET_IP_ARG.strip().split()
try:
cmd_output = subprocess.check_output (dig_args, universal_newlines=True)
mylog('verbose', [f'[{pluginName}] DIG result : {cmd_output}'])
except subprocess.CalledProcessError as e:
mylog('none', [e.output])
mylog('verbose', [e.output])
cmd_output = '' # no internet
# Check result is an IP
@@ -114,7 +119,7 @@ def get_internet_IP (DIG_GET_IP_ARG):
if IP == '':
IP = '0.0.0.0'
return IP
return IP, cmd_output
#===============================================================================
# BEGIN

View File

@@ -0,0 +1,11 @@
## Overview
A simple plugin allowing for executing regular internet speed tests.
### Usage
- N/A
### Notes
- N/A

View File

@@ -0,0 +1,3 @@
## Übersicht
Ein Plugin zur periodischen Durchführung von Internetgeschwindigkeitstests.

View File

@@ -0,0 +1,646 @@
{
"code_name": "internet_speedtest",
"unique_prefix": "INTRSPD",
"plugin_type": "other",
"enabled": true,
"data_source": "script",
"show_ui": true,
"localized": [
"display_name",
"description",
"icon"
],
"display_name": [
{
"language_code": "en_us",
"string": "Internet speedtest"
},
{
"language_code": "de_de",
"string": "Internetgeschwindigkeitstest"
}
],
"icon": [
{
"language_code": "en_us",
"string": "<i class=\"fa-solid fa-gauge-high\"></i>"
},
{
"language_code": "de_de",
"string": "<i class=\"fa-solid fa-gauge-high\"></i>"
}
],
"description": [
{
"language_code": "en_us",
"string": "A plugin to perform a scheduled internet speedtest."
},
{
"language_code": "de_de",
"string": "Ein Plugin zur periodischen Durchführung von Internetgeschwindigkeitstests."
}
],
"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"
},
{
"language_code": "de_de",
"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"
},
{
"language_code": "de_de",
"string": "N/A"
}
]
},
{
"column": "Object_PrimaryID",
"css_classes": "col-sm-2",
"show": false,
"type": "url",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "N/A"
},
{
"language_code": "de_de",
"string": "N/A"
}
]
},
{
"column": "Object_SecondaryID",
"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"
},
{
"language_code": "de_de",
"string": "N/A"
}
]
},
{
"column": "DateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Test run on"
},
{
"language_code": "de_de",
"string": "Test durchgeführt am"
}
]
},
{
"column": "DateTimeChanged",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Changed"
},
{
"language_code": "es_es",
"string": "Cambiado"
},
{
"language_code": "de_de",
"string": "Geändert"
}
]
},
{
"column": "Watched_Value1",
"css_classes": "col-sm-2",
"show": true,
"type": "threshold",
"default_value": "",
"options": [
{
"maximum": 1,
"hexColor": "#D33115"
},
{
"maximum": 5,
"hexColor": "#792D86"
},
{
"maximum": 10,
"hexColor": "#7D862D"
},
{
"maximum": 100,
"hexColor": "#05483C"
}
],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Download"
},
{
"language_code": "de_de",
"string": "Download"
}
]
},
{
"column": "Watched_Value2",
"css_classes": "col-sm-2",
"show": true,
"type": "threshold",
"default_value": "",
"options": [
{
"maximum": 1,
"hexColor": "#D33115"
},
{
"maximum": 5,
"hexColor": "#792D86"
},
{
"maximum": 10,
"hexColor": "#7D862D"
},
{
"maximum": 100,
"hexColor": "#05483C"
}
],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Upload"
},
{
"language_code": "de_de",
"string": "Upload"
}
]
},
{
"column": "Watched_Value3",
"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"
},
{
"language_code": "de_de",
"string": "N/A"
}
]
},
{
"column": "Watched_Value4",
"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"
},
{
"language_code": "de_de",
"string": "N/A"
}
]
},
{
"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"
},
{
"language_code": "de_de",
"string": "Kommentare"
}
]
},
{
"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"
},
{
"language_code": "de_de",
"string": "Status"
}
]
},
{
"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"
},
{
"language_code": "de_de",
"string": "Extra"
}
]
}
],
"settings": [
{
"function": "RUN",
"events": [
"run"
],
"type": "text.select",
"default_value": "disabled",
"options": [
"disabled",
"once",
"schedule",
"always_after_scan"
],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "When to run"
},
{
"language_code": "es_es",
"string": "Cuando ejecuta"
},
{
"language_code": "de_de",
"string": "Wann ausführen"
}
],
"description": [
{
"language_code": "en_us",
"string": "Enable a regular internet speedtest. If you select <code>schedule</code> the scheduling settings from below are applied. If you select <code>once</code> the scan is run only once on start of the application (container) for the time specified in <a href=\"#INTRSPD_RUN_TIMEOUT\"><code>INTRSPD_RUN_TIMEOUT</code> setting</a>."
},
{
"language_code": "de_de",
"string": "Aktiviere periodische Internetgeschwindigkeitstests. Wenn <code>schedule</code> ausgewählt ist, werden die Einstellungen von unten genutzt. Bei <code>once</code> wird der Test nur einmal beim Start der Applikation (Container) für die unten in der <a href=\"#INTRSPD_RUN_TIMEOUT\"><code>INTRSPD_RUN_TIMEOUT</code> Einstellung</a> gesetzten Zeit durchgeführt."
}
]
},
{
"function": "CMD",
"type": "readonly",
"default_value": "python3 /home/pi/pialert/front/plugins/internet_speedtest/script.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"
},
{
"language_code": "es_es",
"string": "Comando a ejecutar"
},
{
"language_code": "de_de",
"string": "Auszuführender Befehl"
}
]
},
{
"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": "Zeitplan"
}
],
"description": [
{
"language_code": "en_us",
"string": "Only enabled if you select <code>schedule</code> in the <a href=\"#INTRSPD_RUN\"><code>INTRSPD_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 habilitado si selecciona <code>schedule</code> en la configuración <a href=\"#INTRSPD_RUN\"><code>INTRSPD_RUN</code></a>. Asegúrese de ingresar el schedule en el formato similar a cron correcto (por ejemplo, valide en <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). Por ejemplo, ingrese <code >0 4 * * *</code> ejecutará el escaneo después de las 4 am en el <a onclick=\"toggleAllSettings()\" href=\"#TIMEZONE\"><code>TIMEZONE</code> que configuró arriba </a>. Se ejecutará la PRÓXIMA vez que pase el tiempo."
},
{
"language_code": "de_de",
"string": "Nur aktiv, wenn <code>schedule</code> in der <a href=\"#INTRSPD_RUN\"><code>INTRSPD_RUN</code> Einstellung</a> ausgewählt wurde. Sichergehen, dass das Intervall in einem korrekten cron-ähnlichen Format angegeben wurde (z.B. auf <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a> testen). <code>0 4 * * *</code> würde den Scan täglich um 4 Uhr in der <a onclick=\"toggleAllSettings()\" href=\"#TIMEZONE\">oben ausgewählten <code>TIMEZONE</code></a> starten. Wird erst beim NÄCHSTEN Intervall ausgeführt. <br/>Es wird empfohlen, das Intervall aller Plugins, welche nach neuen Geräten suchen, auf den gleichen Wert zu setzen."
}
]
},
{
"function": "RUN_TIMEOUT",
"type": "integer",
"default_value": 60,
"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": "Zeitlimit"
}
],
"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."
}
]
},
{
"function": "WATCH",
"type": "text.multiselect",
"default_value": [],
"options": [
"Watched_Value1",
"Watched_Value2",
"Watched_Value3",
"Watched_Value4"
],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Watched"
},
{
"language_code": "es_es",
"string": "Visto"
},
{
"language_code": "de_de",
"string": "Überwacht"
}
],
"description": [
{
"language_code": "en_us",
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>Watched_Value1</code> is Download speed (not recommended)</li><li><code>Watched_Value2</code> is Upload speed (not recommended)</li><li><code>Watched_Value3</code> unused </li><li><code>Watched_Value4</code> unused </li></ul>"
},
{
"language_code": "de_de",
"string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. <code>STRG + klicken</code> zum aus-/abwählen. <ul> <li><code>Watched_Value1</code> ist die Download-Geschwindigkeit (nicht empfohlen)</li><li><code>Watched_Value2</code> ist die Upload-Geschwindigkeit (nicht empfohlen)</li><li><code>Watched_Value3</code> ist nicht in Verwendung </li><li><code>Watched_Value4</code> ist nicht in Verwendung </li></ul>"
}
]
},
{
"function": "REPORT_ON",
"type": "text.multiselect",
"default_value": [],
"options": [
"new",
"watched-changed",
"watched-not-changed",
"missing-in-last-scan"
],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Report on"
},
{
"language_code": "es_es",
"string": "Informar sobre"
},
{
"language_code": "de_de",
"string": "Benachrichtige wenn"
}
],
"description": [
{
"language_code": "en_us",
"string": "Send a notification only on these statuses. <code>new</code> means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. <code>watched-changed</code> means that selected <code>Watched_ValueN</code> columns changed."
},
{
"language_code": "es_es",
"string": "Envíe una notificación solo en estos estados. <code>new</code> significa que se descubrió un nuevo objeto único (combinación única de PrimaryId y SecondaryId). <code>watched-changed</code> significa que seleccionó <code>Watched_ValueN Las columnas </code> cambiaron."
},
{
"language_code": "de_de",
"string": "Benachrichtige nur bei diesen Status. <code>new</code> bedeutet ein neues eindeutiges (einzigartige Kombination aus PrimaryId und SecondaryId) Objekt wurde erkennt. <code>watched-changed</code> bedeutet eine ausgewählte <code>Watched_ValueN</code>-Spalte hat sich geändert."
}
]
}
]
}

View File

@@ -0,0 +1,60 @@
#!/usr/bin/env python
import argparse
import os
import pathlib
import sys
from datetime import datetime
import speedtest
# 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
from logger import mylog, append_line_to_file
from helper import timeNowTZ
CUR_PATH = str(pathlib.Path(__file__).parent.resolve())
RESULT_FILE = os.path.join(CUR_PATH, 'last_result.log')
def main():
mylog('verbose', ['[INTRSPD] In script'])
parser = argparse.ArgumentParser(description='Speedtest Plugin for Pi.Alert')
values = parser.parse_args()
plugin_objects = Plugin_Objects(RESULT_FILE)
speedtest_result = run_speedtest()
plugin_objects.add_object(
primaryId = 'Speedtest',
secondaryId = timeNowTZ(),
watched1 = speedtest_result['download_speed'],
watched2 = speedtest_result['upload_speed'],
watched3 = 'null',
watched4 = 'null',
extra = 'null',
foreignKey = 'null'
)
plugin_objects.write_result_file()
def run_speedtest():
try:
st = speedtest.Speedtest()
st.get_best_server()
download_speed = round(st.download() / 10**6, 2) # Convert to Mbps
upload_speed = round(st.upload() / 10**6, 2) # Convert to Mbps
return {
'download_speed': download_speed,
'upload_speed': upload_speed,
}
except Exception as e:
mylog('verbose', [f"Error running speedtest: {str(e)}"])
return {
'download_speed': -1,
'upload_speed': -1,
}
if __name__ == '__main__':
sys.exit(main())

View File

@@ -2,6 +2,7 @@
"code_name": "known_template",
"template_type": "database-entry",
"unique_prefix": "KNWN",
"plugin_type": "system",
"enabled": true,
"data_source": "template",
"show_ui": false,

View File

@@ -0,0 +1,9 @@
## Overview
A plugin responsible for general maintenance tasks. These currently include:
- pialert.log cleanup
### Usage
- N/A

View File

@@ -0,0 +1,176 @@
{
"code_name": "maintenance",
"unique_prefix": "MAINT",
"plugin_type": "system",
"enabled": true,
"data_source": "script",
"show_ui": false,
"localized": ["display_name", "description", "icon"],
"display_name": [
{
"language_code": "en_us",
"string": "Maintenance"
}
],
"icon": [
{
"language_code": "en_us",
"string": "<i class=\"fa-solid fa-wrench\"></i>"
}
],
"description": [
{
"language_code": "en_us",
"string": "A plugin for maintenance tasks."
}
],
"params" : [
],
"settings": [
{
"function": "RUN",
"events": ["run"],
"type": "text.select",
"default_value":"schedule",
"options": ["disabled", "once", "schedule", "always_after_scan", "on_new_device"],
"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 maintenance tasks should run. A daily or weekly <code>SCHEDULE</code> is a good option."
}]
},
{
"function": "CMD",
"type": "readonly",
"default_value": "python3 /home/pi/pialert/front/plugins/maintenance/maintenance.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":"0 2 * * 3",
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code":"en_us",
"string" : "Schedule"
},
{
"language_code":"es_es",
"string" : "Schedule"
},
{
"language_code":"de_de",
"string" : "Schedule"
}],
"description": [{
"language_code":"en_us",
"string" : "Only enabled if you select <code>schedule</code> in the <a href=\"#MAINT_RUN\"><code>MAINT_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=\"#MAINT_RUN\"><code>MAINT_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=\"#CSVBCKP_RUN\"><code>CSVBCKP_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": 30,
"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."
}
]
},
{
"function": "LOG_LENGTH",
"type": "integer",
"default_value": 250000,
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code":"en_us",
"string" : "Log length"
}],
"description": [{
"language_code":"en_us",
"string" : "How many last <code>pialert.log</code> lines to keep. If <code>LOG_LEVEL</code> is set to <code>debug</code> the app generates about 10000 lines per hour, so when debugging an issue the recommended setting should cover the bug occurence timeframe. For example for a bug with a 3 day periodical appearence the value <code>1000000</code> should be sufficient. Setting this value to <code>1000000</code> generates approximatelly a 50MB <code>pialert.log</code> file. Set to <code>0</code> to disable log purging."
}]
}
],
"database_column_definitions":
[
]
}

View File

@@ -0,0 +1,67 @@
#!/usr/bin/env python
# test script by running:
# /home/pi/pialert/front/plugins/maintenance/maintenance.py
import os
import pathlib
import argparse
import sys
import hashlib
import csv
import sqlite3
from io import StringIO
from datetime import datetime
from collections import deque
sys.path.extend(["/home/pi/pialert/front/plugins", "/home/pi/pialert/pialert"])
# pialert modules
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
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 = 'MAINT'
def main():
mylog('verbose', [f'[{pluginName}] In script'])
MAINT_LOG_LENGTH = int(get_setting_value('MAINT_LOG_LENGTH'))
# Check if set
if MAINT_LOG_LENGTH != 0:
mylog('verbose', [f'[{pluginName}] Cleaning file'])
logFile = logPath + "/pialert.log"
# Using a deque to efficiently keep the last N lines
lines_to_keep = deque(maxlen=MAINT_LOG_LENGTH)
with open(logFile, 'r') as file:
# Read lines from the file and store the last N lines
for line in file:
lines_to_keep.append(line)
with open(logFile, 'w') as file:
# Write the last N lines back to the file
file.writelines(lines_to_keep)
mylog('verbose', [f'[{pluginName}] Cleanup finished'])
return 0
#===============================================================================
# BEGIN
#===============================================================================
if __name__ == '__main__':
main()

View File

@@ -2,6 +2,7 @@
"code_name": "Devices.new",
"template_type": "database-entry",
"unique_prefix": "NEWDEV",
"plugin_type": "system",
"enabled": true,
"data_source": "template",
"show_ui": false,
@@ -394,7 +395,7 @@
{
"function": "dev_NewDevice",
"type": "integer.checkbox",
"default_value": true,
"default_value": 1,
"options": [],
"localized": ["name", "description"],
"name": [

View File

@@ -1,6 +1,7 @@
{
"code_name": "nmap_scan",
"unique_prefix": "NMAP",
"plugin_type": "other",
"enabled": true,
"data_source": "script",
"data_filters": [
@@ -42,13 +43,13 @@
{
"name" : "ips",
"type" : "sql",
"value" : "SELECT dev_LastIP from DEVICES",
"value" : "SELECT dev_LastIP from DEVICES order by dev_MAC",
"timeoutMultiplier" : true
},
{
"name" : "macs",
"type" : "sql",
"value" : "SELECT dev_MAC from DEVICES"
"value" : "SELECT dev_MAC from DEVICES order by dev_MAC"
},
{
"name" : "timeout",

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