Compare commits

..

78 Commits

Author SHA1 Message Date
Jokob-sk
5faf6fb419 fixes 2023-07-22 12:43:09 +10:00
Jokob-sk
b1f946e1e6 fixes 2023-07-22 12:37:53 +10:00
Jokob-sk
bcea3e1a97 fixes 2023-07-22 12:37:36 +10:00
Jokob-sk
bcda186cbe fixes 2023-07-22 12:34:12 +10:00
Jokob-sk
02215c87eb fixes 2023-07-22 12:33:16 +10:00
Jokob-sk
024e97f138 fixes 2023-07-22 12:32:01 +10:00
Jokob-sk
c56c7609cc Plugin tweaks 2023-07-22 12:28:27 +10:00
Jokob-sk
d723b37622 Docs 2023-07-22 12:07:07 +10:00
Jokob-sk
b461bf4ef7 Docs 2023-07-22 11:09:29 +10:00
Jokob-sk
6e8bb4c2ea Plugins move back 2023-07-22 11:06:01 +10:00
Jokob-sk
0446f6302e Docs 2023-07-22 10:35:54 +10:00
Jokob-sk
a0b0c0ba19 Docs 2023-07-22 10:18:11 +10:00
Jokob-sk
cb0a62396f Plugins UI improvements 2023-07-22 08:40:42 +10:00
Jokob-sk
3315994356 #253 2023-07-22 07:07:21 +10:00
Jokob-sk
c3798ff102 #253 2023-07-21 08:22:01 +10:00
Jokob-sk
43c0df086a Plugins filter + #253 2023-07-21 08:19:11 +10:00
Jokob-sk
98745805d3 Plugins filter 2023-07-20 19:11:43 +10:00
Jokob-sk
1dbdf425d6 Plugins filter 2023-07-20 08:14:37 +10:00
Jokob-sk
b3d05332e5 Plugins filter 2023-07-19 08:18:07 +10:00
Jokob-sk
98fb02282b Debug tips 2023-07-18 08:06:32 +10:00
Jokob-sk
536d789535 Content moved notice 2023-07-17 12:13:42 +10:00
Jokob-sk
d0284a0603 Filter plugin prep 2023-07-16 09:30:55 +10:00
Jokob-sk
4d433b633f Settings changes, NEWDEV work, DOCS 2023-07-15 09:35:28 +10:00
jokob-sk
388844f2bc Merge pull request #284 from Final-Hawk/main
Fix update check interval Thanks to @Final-Hawk 🙏
2023-07-14 12:13:12 +10:00
Joshua P
2fc63daf23 Fix update check 2023-07-14 12:00:40 +10:00
Jokob-sk
c9bc3e9447 Version check debug 2023-07-14 08:05:02 +10:00
Jokob-sk
4a754cdae5 new device defaults work 2023-07-13 10:35:40 +10:00
Jokob-sk
1bb1d528f4 Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2023-07-09 22:19:44 +10:00
Jokob-sk
770d9bfe4a moving plugins & NEWDEV 2023-07-09 22:19:40 +10:00
jokob-sk
8e3aa0407a Update README.md 2023-07-08 23:04:29 +10:00
Jokob-sk
acb756a871 moving plugins 2023-07-08 22:48:49 +10:00
jokob-sk
85e6319760 Update README.md 2023-07-08 20:51:34 +10:00
jokob-sk
66ffb5ebca Update SUBNETS.md 2023-07-08 20:49:55 +10:00
jokob-sk
e548138b9f Update README.md
docs
2023-07-08 14:38:18 +10:00
Jokob-sk
2a5a2693ce New Devices cleanup #250 work 2023-07-08 14:30:33 +10:00
Jokob-sk
efee89dcc1 Device copy server side #276 work 2023-07-08 13:27:24 +10:00
Jokob-sk
74894b519f docs+CSV import logging 2023-07-08 10:29:47 +10:00
Jokob-sk
1a08a88b9e docs/debug 2023-07-08 09:56:07 +10:00
Jokob-sk
e3e0e62d77 docs 2023-07-08 09:39:43 +10:00
Jokob-sk
235264ed1e docs 2023-07-08 09:35:24 +10:00
Jokob-sk
a5aa3d550d docs 2023-07-08 09:24:46 +10:00
Jokob-sk
79b5429a01 docs 2023-07-07 08:05:21 +10:00
Jokob-sk
45dd94e5d5 arp-scan debug #261 work 2023-07-07 07:48:45 +10:00
Jokob-sk
e0d5970643 docs 2023-07-06 19:29:49 +10:00
Jokob-sk
1fc11cd49f info on the REPORT_FROM setting #279 work 2023-07-06 19:19:41 +10:00
Jokob-sk
e5be488b3f arp-scan debug #261 work 2023-07-06 07:56:57 +10:00
Jokob-sk
77ba2e1362 arp-scan debug #261 work 2023-07-05 08:04:10 +10:00
Jokob-sk
89aa38ecc1 arp-scan debug #261 work 2023-07-05 08:01:42 +10:00
Jokob-sk
0c35577a68 arp-scan debug #261 work 2023-07-05 08:00:58 +10:00
Jokob-sk
cd9e244efd New dev defaults #274 work 2023-07-02 10:37:21 +10:00
Jokob-sk
ae876484a4 Network diagram #278 work 2023-07-02 09:25:22 +10:00
Jokob-sk
7720bba5dc webhook #271 work 2023-07-02 08:52:57 +10:00
Jokob-sk
d1b1f078aa webhook #271 work 2023-07-02 08:45:57 +10:00
Jokob-sk
8839ed5932 nginx docs + webhook debug 2023-07-01 10:52:02 +10:00
Jokob-sk
c5987778b6 #269 2023-06-25 08:53:31 +10:00
Jokob-sk
abe9ff5b2c Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2023-06-25 08:25:29 +10:00
Jokob-sk
e231600b88 #266, docs, contact details 2023-06-25 08:24:53 +10:00
jokob-sk
3ccad7a564 Merge pull request #268 from sbe-arg/fix/docker
fix: github packages 403
2023-06-25 07:59:15 +10:00
Santiago Bernhardt
8cf034ed29 package permissions 2023-06-24 21:57:58 +12:00
jokob-sk
9784092c7f Merge pull request #267 from sbe-arg/fix/github-login
fix: docker cache to github registry mistake
2023-06-24 19:43:08 +10:00
Santiago Bernhardt
ef3fe4dd52 fix docker cache to github packages 2023-06-24 21:36:19 +12:00
jokob-sk
7530fb0e23 Merge pull request #265 from Data-Monkey/pialert-folder-documentation
Update README.md
2023-06-24 19:12:58 +10:00
Roland Beck
49211719f0 Update README.md
complete the list of modules
2023-06-24 18:50:11 +10:00
Jokob-sk
430e53820a Docs 2023-06-24 09:45:45 +10:00
Jokob-sk
170772eb7c HA Docs 2023-06-24 09:36:52 +10:00
Jokob-sk
6f1d795c60 HA Docs + Debug ARP scan 2023-06-24 09:29:49 +10:00
Jokob-sk
3d1178bd16 Docs 2023-06-24 08:32:34 +10:00
Jokob-sk
17f2421836 Debug and attempt for #261 2023-06-24 07:55:03 +10:00
Jokob-sk
c61a5bedcf Hypervisor added for #260 2023-06-24 07:09:02 +10:00
Jokob-sk
a318a15cad SNMP walk attempt at #258 2023-06-24 06:55:24 +10:00
Jokob-sk
f430587965 Suggest Oxsq in Readme #256 2023-06-24 06:21:18 +10:00
Jokob-sk
de3b0c7ffc .VERSION file creation prod built attempt fix #209 2023-06-18 10:25:34 +10:00
Jokob-sk
689f54cdc3 GitHub API rate limit handling 2023-06-18 09:22:43 +10:00
Jokob-sk
cee24e0b6c Table rowcount in Maintenance #253 2023-06-18 08:31:05 +10:00
Jokob-sk
2e713bf1d0 Fix Log Limit #255 2023-06-18 07:40:02 +10:00
Jokob-sk
d774901b6d change to dockerhub 2023-06-17 08:22:06 +10:00
jokob-sk
7d7f3df226 Merge pull request #254 from sbe-arg/docker/caches
Speedup docker builds with a cache, thanks @sbe-arg !
2023-06-17 08:02:01 +10:00
Santiago Bernhardt
07c2cd1af4 speedup docker builds with a cache 2023-06-16 11:00:30 +12:00
69 changed files with 2443 additions and 990 deletions

View File

@@ -7,24 +7,19 @@ assignees: ''
---
**Describe the issue**
## 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)
**Paste last few lines from `pialert.log`**
[describe your issue]
> You can use `tail -20 /home/pi/pialert/front/log/pialert.log`
## Paste your `pialert.conf` (remove personal info)
```
paste_here
```
**Paste your `pialert.conf` (remove personal info)**
```
paste_here
```
**Paste your `docker-compose.yml` and `.env` (remove personal info)**
## Paste your `docker-compose.yml` and `.env` (remove personal info)
`docker-compose.yml`
@@ -38,5 +33,14 @@ paste_here
paste_here
```
**Screenshots**
If applicable, add screenshots to help explain your problem.
## 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

25
.github/workflows/docker_cache-cleaner.yml vendored Executable file
View File

@@ -0,0 +1,25 @@
name: ci-package-cleaner
on:
workflow_dispatch: # manual option
schedule:
- cron: '15 22 * * 1' # every Monday 10.15pm UTC (~11.15am Tuesday NZT)
jobs:
package-cleaner:
name: package-cleaner
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
packages: write
steps:
- uses: actions/delete-package-versions@v4
with:
package-name: pi.alert
package-type: container
min-versions-to-keep: 0
delete-only-untagged-versions: true

View File

@@ -14,7 +14,13 @@ on:
jobs:
docker_dev:
runs-on: ubuntu-latest
if: contains(github.event.head_commit.message, 'PUSHPROD') != 'True'
timeout-minutes: 30
permissions:
contents: read
packages: write
if: >
contains(github.event.head_commit.message, 'PUSHPROD') != 'True' &&
github.repository == 'jokob-sk/Pi.Alert'
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -47,6 +53,13 @@ jobs:
type=semver,pattern={{major}}
type=sha
- name: Log in to Github Container registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: jokob-sk
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v2
@@ -62,3 +75,5 @@ jobs:
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

View File

@@ -17,6 +17,10 @@ on:
jobs:
docker:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -31,6 +35,13 @@ jobs:
id: getargs
run: echo "version=$(cat ./stable/VERSION)" >> $GITHUB_OUTPUT
- name: Get release version
id: get_version
run: echo "::set-output name=version::${GITHUB_REF#refs/tags/}"
- name: Create .VERSION file
run: echo "${{ steps.get_version.outputs.version }}" >> .VERSION
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
@@ -47,12 +58,13 @@ jobs:
type=ref,event=pr
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') }}
- name: Log in to Github Container registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: jokob-sk
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v2
@@ -68,3 +80,5 @@ jobs:
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

7
.gitignore vendored
View File

@@ -4,10 +4,13 @@ config/pialert.conf
db/*
db/pialert.db
front/log/*
front/plugins/**/*.log
**/plugins/**/*.log
**/%40eaDir/
**/@eaDir/
__pycache__/
*.py[cod]
*$py.class
*$py.class
**/last_result.log
**/script.log

View File

@@ -14,7 +14,7 @@ Scans for devices connected to your WIFI / LAN and alerts you if new and unknown
[![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)
🐳 [Docker hub](https://registry.hub.docker.com/r/jokobsk/pi.alert) | 📄 [Dockerfile](https://github.com/jokob-sk/Pi.Alert/blob/main/Dockerfile) | 📚 [Docker instructions](https://github.com/jokob-sk/Pi.Alert/blob/main/dockerfiles/README.md) | 🆕 [Release notes](https://github.com/jokob-sk/Pi.Alert/releases)
🐳 [Docker hub](https://registry.hub.docker.com/r/jokobsk/pi.alert) | 📑 [Docker instructions](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)
## 🔍 Scan Methods
The system continuously scans the network for, **New devices**, **New connections** (re-connections), **Disconnections**, **"Always Connected" devices down**, Devices **IP changes** and **Internet IP address changes**. Scanning methods are:
@@ -31,8 +31,8 @@ The system continuously scans the network for, **New devices**, **New connection
## 🧩 Integrations
- [Apprise](https://hub.docker.com/r/caronc/apprise), [Pushsafer](https://www.pushsafer.com/), [NTFY](https://ntfy.sh/)
- [Webhooks](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/WEBHOOK_N8N.md) ([sample JSON](docs/webhook_json_sample.json))
- Home Assistant via [MQTT](https://www.home-assistant.io/integrations/mqtt/) - discovery ~10s per device, use [MQTT Explorer](https://mqtt-explorer.com/) to delete devices
- [Webhooks](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/WEBHOOK_N8N.md)
- [Home Assistant](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/HOME_ASSISTANT.md)
- [API endpoint](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/API.md)
- [Plugin system](https://github.com/jokob-sk/Pi.Alert/tree/main/front/plugins) for custom script monitoring

View File

@@ -4,7 +4,7 @@
#
# repot_template.html - Back module. Template to email reporting in HTML format
#-------------------------------------------------------------------------------
# Puche 2021 pi.alert.application@gmail.com GNU GPLv3
# Puche 2021 GNU GPLv3
#--------------------------------------------------------------------------- -->
<html>
<head></head>

View File

@@ -4,7 +4,7 @@
#
# repot_template.html - Back module. Template to email reporting in HTML format
#-------------------------------------------------------------------------------
# Puche 2021 pi.alert.application@gmail.com GNU GPLv3
# Puche 2021 GNU GPLv3
#--------------------------------------------------------------------------- -->
<html>

View File

@@ -4,7 +4,7 @@
#
# repot_template.html - Back module. Template to email reporting in HTML format
#-------------------------------------------------------------------------------
# Puche 2021 pi.alert.application@gmail.com GNU GPLv3
# Puche 2021 GNU GPLv3
#--------------------------------------------------------------------------- -->
<html>

View File

@@ -2,15 +2,19 @@ version: "3"
services:
pialert:
privileged: true
build: .
build:
dockerfile: Dockerfile
context: .
cache_from:
- type=registry,ref=docker.io/jokob-sk/pi.alert:buildcache
container_name: pialert
network_mode: "host"
network_mode: host
restart: unless-stopped
volumes:
- ${APP_DATA_LOCATION}/pialert_dev/config:/home/pi/pialert/config
# - ${APP_DATA_LOCATION}/pialert/config:/home/pi/pialert/config
- ${APP_DATA_LOCATION}/pialert_dev/db:/home/pi/pialert/db
# - ${APP_DATA_LOCATION}/pialert/db:/home/pi/pialert/db
# - ${APP_DATA_LOCATION}/pialert_dev/config:/home/pi/pialert/config
- ${APP_DATA_LOCATION}/pialert/config:/home/pi/pialert/config
# - ${APP_DATA_LOCATION}/pialert_dev/db:/home/pi/pialert/db
- ${APP_DATA_LOCATION}/pialert/db:/home/pi/pialert/db
# (optional) useful for debugging if you have issues setting up the container
- ${LOGS_LOCATION}:/home/pi/pialert/front/log
# ---------------------------------------------------------------------------
@@ -34,13 +38,14 @@ services:
- ${DEV_LOCATION}/front/devices.php:/home/pi/pialert/front/devices.php
- ${DEV_LOCATION}/front/events.php:/home/pi/pialert/front/events.php
- ${DEV_LOCATION}/front/plugins.php:/home/pi/pialert/front/plugins.php
- ${DEV_LOCATION}/front/pluginsCore.php:/home/pi/pialert/front/pluginsCore.php
- ${DEV_LOCATION}/front/help_faq.php:/home/pi/pialert/front/help_faq.php
- ${DEV_LOCATION}/front/index.php:/home/pi/pialert/front/index.php
- ${DEV_LOCATION}/front/maintenance.php:/home/pi/pialert/front/maintenance.php
- ${DEV_LOCATION}/front/network.php:/home/pi/pialert/front/network.php
- ${DEV_LOCATION}/front/presence.php:/home/pi/pialert/front/presence.php
- ${DEV_LOCATION}/front/settings.php:/home/pi/pialert/front/settings.php
- ${DEV_LOCATION}/front/plugins:/home/pi/pialert/front/plugins
- ${DEV_LOCATION}/front/settings.php:/home/pi/pialert/front/settings.php
- ${DEV_LOCATION}/front/plugins:/home/pi/pialert/front/plugins
# DELETE END anyone trying to use this file: comment out / delete ABOVE lines, they are only for development purposes
# ---------------------------------------------------------------------------
environment:

View File

@@ -6,7 +6,7 @@
# 🐳 A docker image for Pi.Alert
🐳 [Docker hub](https://registry.hub.docker.com/r/jokobsk/pi.alert) | 📄 [Dockerfile](https://github.com/jokob-sk/Pi.Alert/blob/main/Dockerfile) | 📚 [Docker instructions](https://github.com/jokob-sk/Pi.Alert/blob/main/dockerfiles/README.md) | 🆕 [Release notes](https://github.com/jokob-sk/Pi.Alert/releases)
🐳 [Docker hub](https://registry.hub.docker.com/r/jokobsk/pi.alert) | 📑 [Docker instructions](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)
<a href="https://raw.githubusercontent.com/jokob-sk/Pi.Alert/main/docs/img/devices_split.png" target="_blank">
<img src="https://raw.githubusercontent.com/jokob-sk/Pi.Alert/main/docs/img/devices_split.png" width="300px" />
@@ -63,13 +63,7 @@ These are the most important settings to get at least some output in your Device
##### For arp-scan: ENABLE_ARPSCAN, SCAN_SUBNETS
- ❗ To use the arp-scan method, you need to set the `SCAN_SUBNETS` variable.
* The adapter will probably be `eth0` or `eth1`. (Run `iwconfig` to find your interface name(s))
* Specify the network filter (which **significantly** speeds up the scan process). For example, the filter `192.168.1.0/24` covers IP ranges 192.168.1.0 to 192.168.1.255.
* 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']`
* More documentation on how to e.g. [setup vlans & limitations](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/SUBNETS.md)
- ❗ To use the arp-scan method, you need to set the `SCAN_SUBNETS` variable. See the documentation on how [to setup SUBNETS, VLANs & limitations](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/SUBNETS.md)
##### For pihole: PIHOLE_ACTIVE, DHCP_ACTIVE
@@ -80,23 +74,7 @@ These are the most important settings to get at least some output in your Device
💡 Before creating a new issue, please check if a similar issue was [already resolved](https://github.com/jokob-sk/Pi.Alert/issues?q=is%3Aissue+is%3Aclosed).
**Permissions**
* If facing issues (AJAX errors, can't write to DB, empty screen, etc,) make sure permissions are set correctly, and check the logs under `/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)
**Container restarts / crashes**
* Check the logs for details. Often a required setting for a notification method is missing.
**unable to resolve host**
* Check that your `SCAN_SUBNETS` variable is using the correct mask and `--interface` as outlined in the instructions above.
Docker-compose examples can be found below.
⚠ Check also common issues and [debugging tips](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/DEBUG_TIPS.md).
## 📄 Examples

View File

@@ -10,10 +10,10 @@
| Table name | Description | Sample data |
|----------------------|----------------------| ----------------------|
| CurrentScan | Result of the current scan | ![Screen1][screen1] |
| Devices | The main devices database that also contains the Network tree mappings. | ![Screen2][screen2] |
| Devices | The main devices database that also contains the Network tree mappings. If `ScanCycle` is set to `0` device is not scanned. | ![Screen2][screen2] |
| DHCP_Leases | Used for importing devices from DHCP_Leases files. Also leveraged by some plugins. | ![Screen3][screen3] |
| Events | Used to collect connection/disconnection events. | ![Screen4][screen4] |
| Nmap_Scan | Contains results of the scheduled Nmap scan, taht is also displayed in the Nmap tab on each device. | ![Screen5][screen5] |
| Nmap_Scan | Contains results of the scheduled Nmap scan, that is also displayed in the Nmap tab on each device. | ![Screen5][screen5] |
| Online_History | Used to display the `Device presence over time` 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] |

84
docs/DEBUG_TIPS.md Executable file
View File

@@ -0,0 +1,84 @@
# Debugging and troubleshooting
Please follow tips 1 - 4 to get a more detailed error.
## 1. More Logging 📃
When debugging an issue always set the highest log level:
`LOG_LEVEL='debug'`
## 2. Surfacing errors when container restarts 🔁
Start the container via the **terminal** with a command similar to this one:
```bash
docker run --rm --network=host \
-v local/path/pialert/config:/home/pi/pialert/config \
-v local/path/pialert/db:/home/pi/pialert/db \
-e TZ=Europe/Berlin \
-e PORT=20211 \
jokobsk/pi.alert:latest
```
> ⚠ Please note, don't use the `-d` parameter so you see the error when the container crashes. Use this error in your issue description.
## 3. Check the _dev image and open issues ❓
If possible, check if your issue got fixed in the `_dev` image before opening a new issue. The container is:
`jokobsk/pi.alert_dev:latest`
> ⚠ Please backup your DB and config beforehand!
Please also search [open issues](https://github.com/jokob-sk/Pi.Alert/issues).
## 4. Disable restart behavior 🛑
To prevent a Docker container from automatically restarting in a Docker Compose file, specify the restart policy as `no`:
```yaml
version: '3'
services:
your-service:
image: your-image:tag
restart: no
# Other service configurations...
```
## 📃Common issues
### Permissions
* If facing issues (AJAX errors, can't write to DB, empty screen, etc,) make sure permissions are set correctly, and check the logs under `/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)
### Container restarts / crashes
* Check the logs for details. Often a required setting for a notification method is missing.
### unable to resolve host
* Check that your `SCAN_SUBNETS` variable is using the correct mask and `--interface` as outlined in the instructions above.
### Invalid JSON
Check the [Invalid JSON errors debug help](/docs/DEBUG_INVALID_JSON.md) docs on how to proceed.
### sudo execution failing (e.g.: on arpscan) on a Raspberry Pi 4
> sudo: unexpected child termination condition: 0
Resolution based on [this issue](https://github.com/linuxserver/docker-papermerge/issues/4#issuecomment-1003657581)
```
wget ftp.us.debian.org/debian/pool/main/libs/libseccomp/libseccomp2_2.5.3-2_armhf.deb
sudo dpkg -i libseccomp2_2.5.3-2_armhf.deb
```
The link above will probably break in time too. Go to https://packages.debian.org/sid/armhf/libseccomp2/download to find the new version number and put that in the url.

View File

@@ -81,9 +81,17 @@ decides to change the MAC).
GPL 3.0
[Read more here](../LICENSE.txt)
### Contact
pi.alert.application@gmail.com
### Contact
Always use the Issue tracker for the correct fork, for example:
[jokob-sk/Pi.Alert](https://github.com/jokob-sk/Pi.Alert/issues). Please also follow the guidelines on:
- [Pull Request guidelines](https://github.com/jokob-sk/Pi.Alert/tree/main/docs#-pull-requests-prs)
- 🙏 [Feature request guidelines](https://github.com/jokob-sk/Pi.Alert/tree/main/docs#-feature-requests)
- 🐛 [Issue guidelines](https://github.com/jokob-sk/Pi.Alert/tree/main/docs#-submitting-an-issue-or-bug)
***Suggestions and comments are welcome***

42
docs/HOME_ASSISTANT.md Executable file
View File

@@ -0,0 +1,42 @@
# Overview
PiAlert comes with MQTT support, allowing you to show all detected devices as devices in Home Assistant. It also supplies a collection of stats, such as number of online devices.
## ⚠ Note
- Please note that discovery takes about ~10s per device.
- Deleting of devices is not handled automatically. Please use [MQTT Explorer](https://mqtt-explorer.com/) to delete devices in the broker (Home Assistant), if needed.
## 🧭 Guide
> 💡 This guide was tested only with the Mosquitto MQTT broker
1. Enable Mosquitto MQTT in Home Assistant by following the [documentation](https://www.home-assistant.io/integrations/mqtt/)
2. Configure a user name and password on your broker.
3. Note down the following details that you will need to configure PiAlert:
- MQTT host url (usually your Home Assistant IP)
- MQTT broker port
- User
- Password
4. Ope the `PiAlert` > `Settings` > `MQTT` settings group
- Enable MQTT
- Fill in the details from above
- Fill in remaining settings as per description
## 📷 Screenshots
| ![Screen 1][sensors] | ![Screen 2][history] |
|----------------------|----------------------|
| ![Screen 3][list] | ![Screen 4][overview] |
[sensors]: /docs/img/HOME_ASISSTANT/PiAlert-HomeAssistant-Device-as-Sensors.png "sensors"
[history]: /docs/img/HOME_ASISSTANT/PiAlert-HomeAssistant-Device-Presence-History.png "history"
[list]: /docs/img/HOME_ASISSTANT/PiAlert-HomeAssistant-Devices-List.png "list"
[overview]: /docs/img/HOME_ASISSTANT/PiAlert-HomeAssistant-Overview-Card.png "overview"

View File

@@ -24,7 +24,7 @@ In this example you will setup a device named `rapberrypi` as a `Switch` in our
- In the (2) `Details` tab navigate to the the `Type` (3) dropdown and select the type `Switch` (4).
> Note: Only the following device types will show up as selectable Network nodes ( = devices you can connect other devices to):
> AP, Firewall, Gateway, PLC, Powerline, Router, Switch, USB LAN Adapter, USB WIFI Adapter and WLAN.
> AP, Firewall, Gateway, Hypervisor, PLC, Powerline, Router, Switch, USB LAN Adapter, USB WIFI Adapter and WLAN.
- Assign a device to your root device from the `Node` (5) dropdown which has the MAC `Internet` (6) (Your name may differ, but the MAC needs to be set to `Internet` - this is done by default).

View File

@@ -33,7 +33,14 @@ decides to change the MAC).
[Read more here](../LICENSE.txt)
### Contact
pi.alert.application@gmail.com
Always use the Issue tracker for the correct fork, for example:
[jokob-sk/Pi.Alert](https://github.com/jokob-sk/Pi.Alert/issues). Please also follow the guidelines on:
- [Pull Request guidelines](https://github.com/jokob-sk/Pi.Alert/tree/main/docs#-pull-requests-prs)
- 🙏 [Feature request guidelines](https://github.com/jokob-sk/Pi.Alert/tree/main/docs#-feature-requests)
- 🐛 [Issue guidelines](https://github.com/jokob-sk/Pi.Alert/tree/main/docs#-submitting-an-issue-or-bug)
***Suggestions and comments are welcome***

View File

@@ -1,6 +1,6 @@
## 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.
In the app hover over settings or fields/labels or click blue in-app ❔ (question-mark) icons to get to relevant documentation pages.
![In-app help](/docs/img/GENERAL/in-app-help.png)
@@ -14,60 +14,66 @@ There is also an in-app Help / FAQ section that should be answering frequently a
### 📚 Table of contents
#### Popular/Suggested
#### 🐛 Debugging help & tips
- [Debugging tips](/docs/DEBUG_TIPS.md)
- [Invalid JSON errors debug help](/docs/DEBUG_INVALID_JSON.md)
#### 🔝 Popular/Suggested
- [API endpoints details](/docs/API.md)
- [Plugin system details and how to develop your own](/front/plugins/README.md)
- [Network tree map configuration](/docs/NETWORK_TREE.md)
- [Network treemap configuration](/docs/NETWORK_TREE.md)
- [Gmail as SMTP server for sending emails](/docs/SMTP_GMAIL.md)
- [Subnets and vlans configuration for arp-scan](/docs/SUBNETS.md)
- [Subnets and VLANs configuration for arp-scan](/docs/SUBNETS.md)
- [Home Assistant](/docs/HOME_ASSISTANT.md)
#### System Management
#### System Management
- [Manage devices (legacy docs)](/docs/DEVICE_MANAGEMENT.md)
- [Random MAC/MAC icon meaning (legacy docs)](/docs/RANDOM_MAC.md)
- [Custom Icons configuration and support](/docs/ICONS.md)
#### Examples
#### 🔎 Examples
- [N8N webhook example](/docs/WEBHOOK_N8N.md)
#### Misc
#### Misc
- [New Version notifications](/docs/VERSIONS.md)
- [Version history (legacy)](/docs/VERSIONS_HISTORY.md)
- [Invalid JSON errors debug help](/docs/DEBUG_INVALID_JSON.md)
- [Database structure](/docs/DATABASE.md)
- [Reverse proxy with SWAG](/docs/REVERSE_PROXY.md)
Feel free to suggest or submit new docs via a PR.
## 👨‍💻 Development priorities
Highest to lowest:
Priorities from highest to lowest:
* Fixing core functionality bugs not solvable with workarounds
* New core functionality unlocking other opportunities (e.g.: plugins)
* Refactoring enabling faster implementation of future functionality
* UI improvements
* 🔼 Fixing core functionality bugs not solvable with workarounds
* 🔵 New core functionality unlocking other opportunities (e.g.: plugins)
* 🔵 Refactoring enabling faster implementation of future functionality
* 🔽 (low) UI functionality & improvements (PRs welcome 😉)
Design philosophy: Focus on core functionality and leverage existing apps and tools to make PiAlert integratable into other workflows.
Design philosophy: Focus on core functionality and leverage existing apps and tools to make PiAlert integrate into other workflows.
Examples:
1. Supporting apprise makes more sense than implementing multiple individual notification gateways
2. Implementing regular expressions support across settings for validation makes more sense than validating one setting with a specific expression.
2. Implementing regular expression support across settings for validation makes more sense than validating one setting with a specific expression.
UI specific requests are low priority as the framework picked by the original developer is not very extensible (and afaik doesn't support components) and has limited mobile support. Also I argue the value proposition is smaller than working on something else.
UI-specific requests are a low priority as the framework picked by the original developer is not very extensible (and afaik doesn't support components) and has limited mobile support. Also, I argue the value proposition is smaller than working on something else.
Feel free to submit PRs if interested. try to **keep the PRs small/on topic** so they are easier to review and approve.
Feel free to submit PRs if interested. try to **keep the PRs small/on-topic** so they are easier to review and approve.
That being said, I'd reconsider if more people and or recurring sponsors file a request 😉.
## 🙏 Feature requests
Please be as detailed as possible with **workarounds** you considered and why a native feature is the better way. This gives me better context and will make it more likely to be implemented. Ideally a feature request should be in the format "I want to be able to do XYZ so that ZYX. I considered these approaches XYZ".
Please be as detailed as possible with **workarounds** you considered and why a native feature is the better way. This gives me better context and will make it more likely to be implemented. Ideally, a feature request should be in the format "I want to be able to do XYZ so that ZYX. I considered these approaches XYZ".
## Pull-requests (PRs)
## Pull requests (PRs)
If you submit a PR please:
@@ -90,7 +96,7 @@ Suggested test cases:
Some additional context:
* Permanent settings/config is stored in the `pialert.conf` file
* Currently temporary (session?) settings are stored in the `Parameters` DB table as key - value pairs. This table is wiped during a container rebuild/restart and it's values re-initialized from cookies / session data from the browser.
* Currently temporary (session?) settings are stored in the `Parameters` DB table as key-value pairs. This table is wiped during a container rebuild/restart and its values are re-initialized from cookies/session data from the browser.
## 🐛 Submitting an issue or bug
@@ -98,5 +104,6 @@ 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 [💡 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)❗
⚠ Please follow the pre-defined issue template to resolve your issue faster.

65
docs/REVERSE_PROXY.md Executable file
View File

@@ -0,0 +1,65 @@
# Reverse Proxy Configuration
Reverse proxy example by using LinuxServer's SWAG container.
> Submitted by [s33d1ing](https://github.com/s33d1ing). 🙏
## [linuxserver/swag](https://github.com/linuxserver/docker-swag)
In the SWAG container create `/config/nginx/proxy-confs/pialert.subfolder.conf` with the following contents:
``` nginx
## Version 2023/02/05
# make sure that your pialert container is named pialert
# pialert does not require a base url setting
# Since Pi.Alert uses a Host network, you may need to use the IP address of the system running Pi.Alert for $upstream_app.
location /pialert {
return 301 $scheme://$host/pialert/;
}
location ^~ /pialert/ {
# enable the next two lines for http auth
#auth_basic "Restricted";
#auth_basic_user_file /config/nginx/.htpasswd;
# enable for ldap auth (requires ldap-server.conf in the server block)
#include /config/nginx/ldap-location.conf;
# enable for Authelia (requires authelia-server.conf in the server block)
#include /config/nginx/authelia-location.conf;
# enable for Authentik (requires authentik-server.conf in the server block)
#include /config/nginx/authentik-location.conf;
include /config/nginx/proxy.conf;
include /config/nginx/resolver.conf;
set $upstream_app pialert;
set $upstream_port 20211;
set $upstream_proto http;
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
proxy_set_header Accept-Encoding "";
proxy_redirect ~^/(.*)$ /pialert/$1;
rewrite ^/pialert/?(.*)$ /$1 break;
sub_filter_once off;
sub_filter_types *;
sub_filter 'href="/' 'href="/pialert/';
sub_filter '(?>$host)/css' '/pialert/css';
sub_filter '(?>$host)/js' '/pialert/js';
sub_filter '/img' '/pialert/img';
sub_filter '/lib' '/pialert/lib';
sub_filter '/php' '/pialert/php';
}
```

View File

@@ -1,21 +1,48 @@
## Subnets configuration
# Subnets configuration for arp-scan
You need to specify the network interface and the network mask. You can also configure multiple subnets and specify VLANS (see exceptions below).
## Examples
* 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']`
## Explanation
### Network mask
**Example value: `192.168.1.0/24`**
The arp-scan time itself depends on the number of IP addresses to check.
The number of IPs to check depends on the [network mask](https://www.calculator.net/ip-subnet-calculator.html) you set on the `SCAN_SUBNETS` setting.
For example, a `/24` mask results in 256 IPs to check, where as a `/16` mask checks around 65,536. Every IP takes a couple seconds. This means that with an incorrect configuration the arp-scan will take hours to complete instead of seconds.
> The number of IPs to check depends on the [network mask](https://www.calculator.net/ip-subnet-calculator.html) you set on the `SCAN_SUBNETS` setting.
> For example, a `/24` mask results in 256 IPs to check, whereas a `/16` mask checks around 65,536. Every IP takes a couple of seconds. This means that with an incorrect configuration, the arp-scan will take hours to complete instead of seconds.
Specify the network filter (which **significantly** speeds up the scan process). For example, the filter `192.168.1.0/24` covers IP ranges 192.168.1.0 to 192.168.1.255.
### Network interface (adapter)
**Example value: `--interface=eth0`**
The adapter will probably be `eth0` or `eth1`. (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`).
### VLANs
**Example value: `-vlan=107`**
- Specify the network mask. For example, the filter `192.168.1.0/24` covers IP ranges 192.168.1.0 to 192.168.1.255
- Run `iwconfig` in your container to find your interface name(s) (e.g.: `eth0`, `eth1`).
- Append e.g.: ` -vlan=107` to the interface field (e.g.: `eth0 -vlan=107`) for multiple vlans. More details in this [comment in this issue](https://github.com/jokob-sk/Pi.Alert/issues/170#issuecomment-1419902988)
### 🔍Example:
#### VLAN 🔍Example:
![Vlan configuration example](/docs/img/SUBNETS/subnets_vlan.png)
### Support for VLANS
#### Support for VLANS (& exceptions)
Please note about the accessibility of the macvlans when they are configured on the same computer. My understanding this is a general networking behavior, but feel free to clarify via a PR/issue.
Please note the accessibility of the macvlans when they are configured on the same computer. My understanding this is a general networking behavior, but feel free to clarify via a PR/issue.
- Pi.Alert does not detect the macvlan container when it is running on the same computer.
- Pi.Alert recognizes the macvlan container when it is running on a different computer.

View File

@@ -77,4 +77,11 @@
[Read more here](../LICENSE.txt)
### Contact
pi.alert.application@gmail.com
Always use the Issue tracker for the correct fork, for example:
[jokob-sk/Pi.Alert](https://github.com/jokob-sk/Pi.Alert/issues). Please also follow the guidelines on:
- [Pull Request guidelines](https://github.com/jokob-sk/Pi.Alert/tree/main/docs#-pull-requests-prs)
- 🙏 [Feature request guidelines](https://github.com/jokob-sk/Pi.Alert/tree/main/docs#-feature-requests)
- 🐛 [Issue guidelines](https://github.com/jokob-sk/Pi.Alert/tree/main/docs#-submitting-an-issue-or-bug)

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@@ -773,6 +773,12 @@ height: 50px;
margin: 2px;
position: absolute;
}
#networkTree .netPort
{
width: 8px;;
float:left;
display:inline;
}
#networkTree
{
margin-left: 16px;
@@ -814,25 +820,35 @@ height: 50px;
text-overflow: ellipsis;
}
.plugin-content
{
padding-bottom: 7px;
}
.plugin-content #tabs-content-location
{
margin-top: 20px;
margin-left: 7px;
.plugin-filters
{
margin: 7px;
margin-right: 7px;
margin-bottom: 9px;
padding-bottom: 8px;
}
.plugin-content
{
padding-bottom: 0px;
}
.plugin-content .left-nav{
width: 100%;
padding-right: 0px;
}
.plugin-content #tabs-content-location
{
margin: 0px;
}
.plugins-description
{
padding-top: 20px;
}
.login-page .login-custom
{

View File

@@ -107,6 +107,7 @@
<li> <a id="tabPresence" href="#panPresence" data-toggle="tab"> <?= lang('DevDetail_Tab_Presence');?> </a></li>
<li> <a id="tabEvents" href="#panEvents" data-toggle="tab"> <?= lang('DevDetail_Tab_Events');?> </a></li>
<li> <a id="tabPholus" href="#panPholus" data-toggle="tab"> <?= lang('DevDetail_Tab_Pholus');?> </a></li>
<li> <a id="tabPlugins" href="#panPlugins" data-toggle="tab"> <?= lang('DevDetail_Tab_Plugins');?> </a></li>
<div class="btn-group pull-right">
<button type="button" class="btn btn-default" style="padding: 10px; min-width: 30px;"
@@ -347,6 +348,8 @@
<input class="form-control" id="txtNetworkPort" type="text" value="--">
</div>
</div>
</div>
</div>
@@ -463,11 +466,35 @@
</ul>
</div>
</div>
</div>
</div>
</div>
<h4 class="bottom-border-aqua"><?= lang('DevDetail_Copy_Device_Title');?></h4>
<div class="box-body form-horizontal">
<label class="col-sm-3 control-label">
<?= lang('Navigation_Devices');?>
</label>
<div class="col-sm-9">
<div class="input-group">
<input class="form-control" title="<?= lang('DevDetail_Copy_Device_Tooltip');?>" id="txtFromDevice" type="text" value="--">
<span class="input-group-addon" title='<?= lang('Gen_Copy');?>'><i class="fa fa-copy pointer" onclick="askCopyFromDevice();"></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>
</button>
<ul id="dropdownDevices" class="dropdown-menu dropdown-menu-right">
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- Buttons -->
<div class="col-xs-12">
<div class="pull-right">
@@ -675,6 +702,17 @@
</table>
</div>
<!-- tab page 7 ------------------------------------------------------------ -->
<div class="tab-pane fade table-responsive" id="panPlugins">
<?php
// Include the other page
include 'pluginsCore.php';
?>
</div>
</div>
<!-- /.tab-content -->
</div>
@@ -814,6 +852,7 @@ function main () {
var urlParams = new URLSearchParams(window.location.search);
if (urlParams.has ('mac') == true) {
mac = urlParams.get ('mac');
setCache("piaDeviceDetailsMac", mac); // set cookie
} else {
$('#pageTitle').html ('Device not found');
}
@@ -949,6 +988,7 @@ function initializeCombos () {
initializeCombo ( '#dropdownNetworkNodeMac', 'getNetworkNodes', 'txtNetworkNodeMac', false);
initializeCombo ( '#dropdownIcon', 'getIcons', 'txtIcon', false);
initializeCombo ( '#dropdownAction', 'getActions', 'txtAction', false);
initializeCombo ( '#dropdownDevices', 'getDevices', 'txtFromDevice', false);
// Initialize static combos
initializeComboSkipRepeated ();
@@ -1350,7 +1390,7 @@ function getDeviceData (readAllData=false) {
// Name
if (deviceData['dev_Owner'] == null || deviceData['dev_Owner'] == '' ||
(deviceData['dev_Name']).indexOf (deviceData['dev_Owner']) != -1 ) {
(deviceData['dev_Name'].toString()).indexOf (deviceData['dev_Owner']) != -1 ) {
$('#pageTitle').html (deviceData['dev_Name']);
} else {
$('#pageTitle').html (deviceData['dev_Name'] + ' ('+ deviceData['dev_Owner'] +')');
@@ -1505,7 +1545,10 @@ 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();
setCache("piaDeviceDetailsMac", mac);
getDeviceData (true);
@@ -1623,6 +1666,29 @@ function deleteDeviceEvents () {
$('#panDetails :input').attr('disabled', true);
}
// -----------------------------------------------------------------------------
function askCopyFromDevice() {
// Ask
showModalWarning('<?= lang('BackDevDetail_Copy_Title');?>', '<?= lang('BackDevDetail_Copy_Ask');?>',
'<?= lang('Gen_Cancel');?>', '<?= lang('Gen_Run');?>', 'copyFromDevice');
}
function copyFromDevice() {
// Execute
$.get('php/server/devices.php?action=copyFromDevice&'
+ '&macTo=' + $('#txtMAC').val()
+ '&macFrom=' + $('#txtFromDevice').val()
, function(msg) {
showMessage (msg);
setTimeout(function() {
window.location.reload();
}, 2000);
});
}
// -----------------------------------------------------------------------------
function askRunAction() {
// Ask

View File

@@ -192,8 +192,8 @@
var tableRows = 10;
var tableOrder = [[3,'desc'], [0,'asc']];
var columnsStr = '[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17]';
var tableColumnOrder = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17] ;
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;
// Read parameters & Initialize components

View File

@@ -89,6 +89,19 @@
<?= lang('HelpFAQ_Cat_General_103_text');?>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion_net" href="#collapse601">
<?= lang('HelpFAQ_Cat_Network_601_head');?></a>
</h4>
</div>
<div id="collapse601" class="panel-collapse collapse" style="font-size: 16px;">
<div class="panel-body">
<?= lang('HelpFAQ_Cat_Network_601_text');?>
</div>
</div>
</div>
</div>
</div>
@@ -164,6 +177,7 @@
<?= lang('HelpFAQ_Cat_Detail_303_text');?>
</div>
</div>
</div>
</div>
@@ -211,7 +225,7 @@
<?= lang('HelpFAQ_Cat_Network_600_text');?>
</div>
</div>
</div>
</div>
</section>

View File

@@ -35,18 +35,18 @@ function handleVersion(){
function getVersion()
{
release_timestamp = getCookie("release_timestamp")
release_timestamp = getCookie("release_timestamp")
// no cached value available
if(release_timestamp == "")
{
// get parameter value
$.get('https://api.github.com/repos/jokob-sk/Pi.Alert/releases', function(data) {
$.get('https://api.github.com/repos/jokob-sk/Pi.Alert/releases').done(function(response) {
// Handle successful response
var releases = response;
var releases = data;
if(releases.length > 0)
{
release_datetime = releases[0].published_at;
release_timestamp = new Date(release_datetime).getTime() / 1000;
@@ -55,6 +55,11 @@ function handleVersion(){
handleVersion();
}
}).fail(function(jqXHR, textStatus, errorThrown) {
$('.version').append(`<p>Github API: ${errorThrown} (${jqXHR.status}), ${jqXHR.responseJSON.message}</p>`)
});
} else
{

View File

@@ -118,6 +118,30 @@ if (isset($_POST['submit']) && submit && isset($_POST['skinselector_set'])) {
}
}
}
// Table sizes -----------------------------------------------------------------
$tableSizesHTML = "";
// Open a connection to the SQLite database
$db = new SQLite3($pia_db);
// Retrieve the table names from sqlite_master
$query = "SELECT name FROM sqlite_master WHERE type='table'";
$result = $db->query($query);
// Iterate over the tables and get the row counts
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
$tableName = $row['name'];
$query = "SELECT COUNT(*) FROM $tableName";
$countResult = $db->querySingle($query);
$tableSizesHTML = $tableSizesHTML . "$tableName (<b>$countResult</b>), ";
}
// Close the database connection
$db->close();
// Language selector -----------------------------------------------------------------
@@ -150,7 +174,13 @@ if (isset($_POST['submit']) && submit && isset($_POST['skinselector_set'])) {
<div class="db_info_table_cell"><?= lang('Maintenance_database_size');?></div>
<div class="db_info_table_cell">
<?php echo $pia_db_size;?>
</div>
</div>
</div>
<div class="db_info_table_row">
<div class="db_info_table_cell"><?= lang('Maintenance_database_rows');?></div>
<div class="db_info_table_cell">
<?php echo $tableSizesHTML;?>
</div>
</div>
<div class="db_info_table_row">
<div class="db_info_table_cell"><?= lang('Maintenance_database_lastmod');?></div>
@@ -246,6 +276,7 @@ if (isset($_POST['submit']) && submit && isset($_POST['skinselector_set'])) {
<option value="15"><?= lang('Device_TableHead_Connected_Devices');?></option>
<option value="16"><?= lang('Device_TableHead_Location');?></option>
<option value="17"><?= lang('Device_TableHead_Vendor');?></option>
<option value="18"><?= lang('Device_TableHead_Port');?></option>
</select>
<span class="input-group-addon"><i title="<?= lang('Gen_Save');?>" class="fa fa-save pointer" onclick="saveSelectedColumns();"></i></span>
</div>
@@ -338,7 +369,17 @@ if (isset($_POST['submit']) && submit && isset($_POST['skinselector_set'])) {
<div class="db_info_table">
<div class="log-area">
<div class="row logs-row">
<textarea id="pialert_log" class="logs" cols="70" rows="10" wrap='off' readonly ><?php echo file_get_contents( "./log/pialert.log", false, null, -200000); ?>
<textarea id="pialert_log" class="logs" cols="70" rows="10" wrap='off' readonly >
<?php
if(filesize("./log/pialert.log") > 2000000)
{
echo file_get_contents( "./log/pialert.log", false, null, -2000000);
}
else{
echo file_get_contents( "./log/pialert.log" );
}
?>
</textarea>
</div>
<div class="row logs-row" >

View File

@@ -267,7 +267,7 @@
// / \
// Smart TV (leaf) Switch 2 (node (for the PC) and leaf (for Switch 1))
// \
// PC (leaf)
// PC (leaf) <------- leafs are not included in this SQL query
$sql = "SELECT node_name, node_mac, online, node_type, node_ports_count, parent_mac, node_icon
FROM
@@ -279,7 +279,7 @@
a.dev_Network_Node_MAC_ADDR as parent_mac,
a.dev_Icon as node_icon
FROM Devices a
WHERE a.dev_DeviceType in ('AP', 'Gateway', 'Firewall', 'Powerline', 'Switch', 'WLAN', 'PLC', 'Router','USB LAN Adapter', 'USB WIFI Adapter', 'Internet')
WHERE a.dev_DeviceType in ('AP', 'Gateway', 'Firewall', 'Hypervisor', 'Powerline', 'Switch', 'WLAN', 'PLC', 'Router','USB LAN Adapter', 'USB WIFI Adapter', 'Internet')
) t1
LEFT JOIN
(
@@ -465,7 +465,8 @@
"parentMac":item[14],
"rowid":item[13],
"status":item[10],
"childrenQty":item[15]
"childrenQty":item[15],
"port":item[18]
}})
setCache('devicesListNew', JSON.stringify(devicesListnew))
@@ -541,6 +542,7 @@
name: node.name,
path: path,
mac: node.mac,
port: node.port,
id: node.mac,
parentMac: node.parentMac,
icon: node.icon,
@@ -628,7 +630,9 @@
renderNode: nodeData => {
var fontSize = "font-size:"+emSize+"em;";
// 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 = (!emptyArr.includes(nodeData.data.port )) ? "<div class='netPort ' >"+nodeData.data.port +"</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;
@@ -648,7 +652,7 @@
border-radius:5px;'\
>\
<div class='netNodeText '>\
<strong>" + deviceIcon +
<strong>" + devicePort + deviceIcon +
"<span class='spanNetworkTree anonymizeDev'>"+nodeData.data.name+"</span>\
</strong>"
+collapseExpandHtml+

View File

@@ -65,7 +65,7 @@ function OpenDB (...$DBPath) {
die ('<div style="padding-left:150px">Error connecting to the database</div>');
}
$db->exec('PRAGMA journal_mode = wal;');
$db->exec('PRAGMA journal_mode = wal;');
}

View File

@@ -58,6 +58,8 @@
case 'overwriteIconType': overwriteIconType(); break;
case 'getIcons': getIcons(); break;
case 'getActions': getActions(); break;
case 'getDevices': getDevices(); break;
case 'copyFromDevice': copyFromDevice(); break;
case 'wakeonlan': wakeonlan(); break;
default: logServerConsole ('Action: '. $action); break;
@@ -477,6 +479,7 @@ function ImportCSV() {
global $db;
$skipped = "";
$error = "";
// sql
@@ -492,12 +495,13 @@ function ImportCSV() {
// Parse data from CSV file line by line (max 10000 lines)
$index = 0;
foreach($data as $row)
{
// Check if not empty and skipping first line
$rowArray = explode(',',$row);
if(count($rowArray) > 20)
if(count($rowArray) > 23)
{
$cleanMac = str_replace("\"","",$rowArray[0]);
@@ -513,19 +517,21 @@ function ImportCSV() {
break;
}
}
} else{
$skipped = $skipped . ($index+1) . ",";
}
$index = $index + 1;
}
if($error == "")
{
// import succesful
echo lang('BackDevices_DBTools_ImportCSV');
echo lang('BackDevices_DBTools_ImportCSV') . " (Skipped lines: " .$skipped .") ";
}
else{
// an error occurred while writing to the DB, display the last error message
echo lang('BackDevices_DBTools_ImportCSVError')."\n\n$sql \n\n".$result;
echo lang('BackDevices_DBTools_ImportCSVError')."\n".$error."\n$sql \n\n".$result;
}
} else {
@@ -602,7 +608,8 @@ function getDevicesList() {
array("dev_Network_Node_MAC_ADDR", 14, 14),
array("connected_devices", 15, 15),
array("dev_Location", 16, 16),
array("dev_Vendor", 17, 17)
array("dev_Vendor", 17, 17),
array("dev_Network_Node_port", 18, 18)
);
if($forceDefaultOrder == FALSE)
@@ -668,7 +675,8 @@ function getDevicesList() {
handleNull($row['dev_Network_Node_MAC_ADDR']),
handleNull($row['connected_devices']),
handleNull($row['dev_Location']),
handleNull($row['dev_Vendor'])
handleNull($row['dev_Vendor']),
handleNull($row['dev_Network_Node_port'])
);
$newOrder = array();
@@ -759,7 +767,7 @@ function getNetworkNodes() {
global $db;
// Device Data
$sql = 'SELECT * FROM Devices WHERE dev_DeviceType in ( "AP", "Gateway", "Firewall", "Powerline", "Switch", "WLAN", "PLC", "Router","USB LAN Adapter", "USB WIFI Adapter")';
$sql = 'SELECT * FROM Devices WHERE dev_DeviceType in ( "AP", "Gateway", "Firewall", "Hypervisor", "Powerline", "Switch", "WLAN", "PLC", "Router","USB LAN Adapter", "USB WIFI Adapter")';
$result = $db->query($sql);
@@ -819,6 +827,36 @@ function getActions() {
echo (json_encode ($tableData));
}
//------------------------------------------------------------------------------
function getDevices() {
global $db;
// Device Data
$sql = 'select dev_MAC, dev_Name from Devices';
$result = $db->query($sql);
// arrays of rows
$tableData = array();
while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
$name = handleNull($row['dev_Name'], "(unknown)");
$mac = handleNull($row['dev_MAC'], "(unknown)");
// Push row data
$tableData[] = array('id' => $mac,
'name' => $name );
}
// Control no rows
if (empty($tableData)) {
$tableData = [];
}
// Return json
echo (json_encode ($tableData));
}
//------------------------------------------------------------------------------
// Query the List of types
@@ -836,7 +874,7 @@ function getDeviceTypes() {
"Laptop", "Mini PC", "PC", "Printer", "Server", "Singleboard Computer (SBC)", "NAS",
"Domotic", "IP Camera", "Game Console", "SmartTV", "TV Decoder", "Virtual Assistance",
"Clock", "House Appliance", "Phone", "Radio",
"AP", "Gateway", "Firewall", "Powerline", "Switch", "WLAN", "PLC", "Router","USB LAN Adapter", "USB WIFI Adapter" )
"AP", "Gateway", "Firewall", "Hypervisor", "Powerline", "Switch", "WLAN", "PLC", "Router","USB LAN Adapter", "USB WIFI Adapter" )
UNION SELECT 1 as dev_Order, "Smartphone"
UNION SELECT 1 as dev_Order, "Tablet"
@@ -865,6 +903,7 @@ function getDeviceTypes() {
UNION SELECT 5 as dev_Order, "AP"
UNION SELECT 5 as dev_Order, "Gateway"
UNION SELECT 5 as dev_Order, "Firewall"
UNION SELECT 5 as dev_Order, "Hypervisor"
UNION SELECT 5 as dev_Order, "Powerline"
UNION SELECT 5 as dev_Order, "Switch"
UNION SELECT 5 as dev_Order, "WLAN"
@@ -1169,6 +1208,56 @@ function wakeonlan() {
echo lang('BackDevDetail_Tools_WOL_okay');
}
//------------------------------------------------------------------------------
// Copy from device
//------------------------------------------------------------------------------
function copyFromDevice() {
$MAC_FROM = $_REQUEST['macFrom'];
$MAC_TO = $_REQUEST['macTo'];
if ((false === filter_var($MAC_FROM , FILTER_VALIDATE_MAC) && $MAC_FROM != "Internet" && $MAC_FROM != "") ) {
throw new Exception('Invalid mac address');
}
if ((false === filter_var($MAC_TO , FILTER_VALIDATE_MAC) && $MAC_TO != "Internet" && $MAC_TO != "") ) {
throw new Exception('Invalid mac address');
}
global $db;
// clean-up temporary table
$sql = "DROP TABLE temp_devices ";
$result = $db->query($sql);
// create temporary table with the source data
$sql = "CREATE TABLE temp_devices AS SELECT * FROM Devices WHERE dev_MAC = '". $MAC_FROM . "';";
$result = $db->query($sql);
// update temporary table with the correct target MAC
$sql = "UPDATE temp_devices SET dev_MAC = '". $MAC_TO . "';";
$result = $db->query($sql);
// delete previous entry
$sql = "DELETE FROM Devices WHERE dev_MAC = '". $MAC_TO . "';";
$result = $db->query($sql);
// insert new entry with the correct target MAC from the temporary table
$sql = "INSERT INTO Devices SELECT * FROM temp_devices WHERE dev_MAC = '".$MAC_TO."'";
$result = $db->query($sql);
// clean-up temporary table
$sql = "DROP TABLE temp_devices ";
$result = $db->query($sql);
// check result
if ($result == TRUE) {
echo 'OK';
} else {
echo lang('BackDevices_Device_UpdDevError');
}
}
//------------------------------------------------------------------------------
// Status Where conditions
//------------------------------------------------------------------------------

View File

@@ -274,11 +274,11 @@ function saveSettings()
{
if($group == $setting[0])
{
if($setting[2] == 'text' or $setting[2] == 'password' or $setting[2] == 'readonly' or $setting[2] == 'selecttext')
if($setting[2] == 'text' or $setting[2] == 'password' or $setting[2] == 'readonly' or $setting[2] == 'text.select')
{
$val = encode_single_quotes($setting[3]);
$txt = $txt.$setting[1]."='".$val."'\n" ;
} elseif($setting[2] == 'integer' or $setting[2] == 'selectinteger')
} elseif($setting[2] == 'integer' or $setting[2] == 'integer.select')
{
$txt = $txt.$setting[1]."=".$setting[3]."\n" ;
} elseif($setting[2] == 'boolean')
@@ -289,7 +289,7 @@ function saveSettings()
$val = "True";
}
$txt = $txt.$setting[1]."=".$val."\n" ;
}elseif($setting[2] == 'multiselect' or $setting[2] == 'subnets' or $setting[2] == 'list')
}elseif($setting[2] == 'text.multiselect' or $setting[2] == 'subnets' or $setting[2] == 'list')
{
$temp = '[';
@@ -381,11 +381,11 @@ function handleNull ($text, $default = "") {
}
// -------------------------------------------------------------------------------------------
// Currently unused - should be source of truth for network types (or define somewhere else?)
function getNetworkTypes(){
$array = array(
"AP", "Gateway", "Firewall", "Powerline", "Switch", "WLAN", "PLC", "Router","USB LAN Adapter", "USB WIFI Adapter"
"AP", "Gateway", "Firewall", "Hypervisor", "Powerline", "Switch", "WLAN", "PLC", "Router","USB LAN Adapter", "USB WIFI Adapter"
);
return $array;

View File

@@ -380,7 +380,7 @@ $lang['de_de'] = array(
lösche ggf. die betreffende DHCP-Lease. Anschließend schaue, ebenfalls in Pi-hole, unter Tools -> Network, ob sich dort die immer wiederkehrenden Hosts finden lassen.
Wenn ja, lösche diese dort ebenfalls. Nun kannst du Pi.Alert wieder starten. Jetzt sollte das Gerät/die Geräte nicht mehr auftauchen.',
'HelpFAQ_Cat_Detail_300_head' => 'Was bedeutet ',
'HelpFAQ_Cat_Detail_300_text_a' => 'meint ein Netzwerkgerät (welches den typ AP, Gateway, Firewall, Powerline, Switch, WLAN, PLC, Router,USB LAN Adapter, USB WIFI Adapter, or Internet eingestellt hat)',
'HelpFAQ_Cat_Detail_300_text_a' => 'meint ein Netzwerkgerät (welches den typ AP, Gateway, Firewall, Hypervisor, Powerline, Switch, WLAN, PLC, Router,USB LAN Adapter, USB WIFI Adapter, or Internet eingestellt hat)',
'HelpFAQ_Cat_Detail_300_text_b' => 'bezeichnet die Anschlussnummer/Portnummer, an der das gerade bearbeitete Gerät mit diesem Netzwerkgerät verbunden ist.',
'HelpFAQ_Cat_Detail_301_head_a' => 'Wann wird nun gescannt? Bei ',
'HelpFAQ_Cat_Detail_301_head_b' => ' steht 1min aber der Graph zeigt 5min - Abstände an.',

View File

@@ -19,6 +19,7 @@ $lang['en_us'] = array(
'Gen_Save' => 'Save',
'Gen_Saved' => 'Saved',
'Gen_Run' => 'Run',
'Gen_Copy' => 'Run',
'Gen_Action' => 'Action',
'Gen_Purge' => 'Purge',
'Gen_Backup' => 'Run Backup',
@@ -27,7 +28,7 @@ $lang['en_us'] = array(
'Gen_AreYouSure' => 'Are you sure?',
'Gen_Upd' => 'Updated successfully',
'Gen_Upd_Fail' => 'Update failed',
'Gen_Help' => 'Need help?',
'Gen_ReadDocs' => 'Read more in the docs.',
'Gen_DataUpdatedUITakesTime' => 'OK - It may take a while for the UI to update if a scan is runnig.',
'Gen_LockedDB' => 'ERROR - DB might be locked - Check F12 Dev tools -> Console or try later.',
@@ -80,6 +81,7 @@ $lang['en_us'] = array(
'Device_TableHead_Connected_Devices' => 'Connected Devices',
'Device_TableHead_Location' => 'Location',
'Device_TableHead_Vendor' => 'Vendor',
'Device_TableHead_Port' => 'Port',
'Device_TableHead_Favorite' => 'Favorite',
'Device_TableHead_Group' => 'Group',
'Device_TableHead_FirstSession' => 'First Session',
@@ -174,6 +176,7 @@ $lang['en_us'] = array(
'DevDetail_Tab_Events' => '<i class="fa fa-bolt"></i> Events',
'DevDetail_Tab_Pholus' => '<i class="fa fa-search"></i> Pholus',
'DevDetail_Tab_PholusEmpty' => 'Nothing sniffed out with Pholus for this device.',
'DevDetail_Tab_Plugins' => '<i class="fa fa-plug"></i> Plugins',
'DevDetail_Tab_NmapTableHeader' => 'Scheduled scan results',
'DevDetail_Tab_NmapTableText' => 'Set up a schedule in <a href="/settings.php#NMAP_ACTIVE">Settings</a>',
'DevDetail_Tab_NmapEmpty' => 'No ports detected with Nmap on this device.',
@@ -244,17 +247,22 @@ $lang['en_us'] = array(
'DevDetail_WOL_Title' => '<i class="fa fa-power-off"></i> Wake-on-LAN',
'DevDetail_Run_Actions_Title' => '<i class="fa fa-play"></i> Run action on device',
'DevDetail_Run_Actions_Tooltip' => 'Run an action on the current device from the dropdown list.',
'DevDetail_Copy_Device_Title' => '<i class="fa fa-copy"></i> Copy details from device',
'DevDetail_Copy_Device_Tooltip' => 'Copy details from device from the dropdown list. Everything on this page will be overwritten',
'BackDevDetail_Copy_Title' => 'Copy details',
'BackDevDetail_Copy_Ask' => 'Copy details from device from the dropdown list (Everything on this page will be overwritten)?',
//////////////////////////////////////////////////////////////////
// Maintenance Page
//////////////////////////////////////////////////////////////////
'Maintenance_Title' => 'Maintenance tools',
'Maintenance_version' => 'Application updates',
'Maintenance_version' => 'App updates',
'Maintenance_new_version' => '🆕 A new version is available. Check out the <a href="https://github.com/jokob-sk/Pi.Alert/releases" target="_blank">release notes</a>.',
'Maintenance_current_version' => 'You are up-to-date. Check out what <a href="https://github.com/jokob-sk/Pi.Alert/issues/138" target="_blank">I\'m working on</a>.',
'Maintenance_database_path' => 'Database-Path',
'Maintenance_database_size' => 'Database-Size',
'Maintenance_database_rows' => 'Table (Rows)',
'Maintenance_database_lastmod' => 'Last Modification',
'Maintenance_database_backup' => 'DB Backups',
'Maintenance_database_backup_found' => 'backups were found',
@@ -366,7 +374,7 @@ $lang['en_us'] = array(
'BackDevices_DBTools_ImportCSV' => 'The devices from the CSV file were imported successfully.',
'BackDevices_DBTools_ImportCSVError' => 'The CSV file couldn\'t be imported. Make sure the format is correct.',
'BackDevices_DBTools_ImportCSVMissing' => 'The CSV file couldn\'t be found under <b>/config/devices.csv.</b>',
'BackDevices_Device_UpdDevError' => 'Error updating devices, try later. The database is probable locked due to an ongoing task.',
'BackDevices_Device_UpdDevError' => 'Error updating devices, try later. The database is probably locked due to an ongoing task.',
//////////////////////////////////////////////////////////////////
@@ -451,7 +459,7 @@ $lang['en_us'] = array(
delete the DHCP lease if necessary. Then, also in Pi-hole, look under Tools -> Network to see if you can find the recurring hosts there.
If yes, delete them there as well. Now you can start Pi.Alert again. Now the device(s) should not show up anymore.',
'HelpFAQ_Cat_Detail_300_head' => 'What means ',
'HelpFAQ_Cat_Detail_300_text_a' => 'means a network device (a device of the type AP, Gateway, Firewall, Powerline, Switch, WLAN, PLC, Router,USB LAN Adapter, USB WIFI Adapter, or Internet).',
'HelpFAQ_Cat_Detail_300_text_a' => 'means a network device (a device of the type AP, Gateway, Firewall, Hypervisor, Powerline, Switch, WLAN, PLC, Router,USB LAN Adapter, USB WIFI Adapter, or Internet).',
'HelpFAQ_Cat_Detail_300_text_b' => 'designates the port number where the currently edited device is connected to this network device. Read <a target="_blank" href="https://github.com/jokob-sk/Pi.Alert/blob/main/docs/NETWORK_TREE.md">this guide</a> for more info.',
'HelpFAQ_Cat_Detail_301_head_a' => 'When is scanning now? At ',
'HelpFAQ_Cat_Detail_301_head_b' => ' says 1min but the graph shows 5min intervals.',
@@ -475,6 +483,8 @@ $lang['en_us'] = array(
'HelpFAQ_Cat_Network_600_head' => 'What is this page for?',
'HelpFAQ_Cat_Network_600_text' => 'This page should offer you the possibility to map the assignment of your network devices. For this purpose, you can create one or more switches, WLANs, routers, etc., provide them with a port number if necessary and assign already detected
devices to them. This assignment is done in the detailed view of the device to be assigned. So it is possible for you to quickly determine to which port a host is connected and if it is online. Read <a target="_blank" href="https://github.com/jokob-sk/Pi.Alert/blob/main/docs/NETWORK_TREE.md">this guide</a> for more info.',
'HelpFAQ_Cat_Network_601_head' => 'Are there other docs?',
'HelpFAQ_Cat_Network_601_text' => 'Yes, there are! Check <a target="_blank" href="https://github.com/jokob-sk/Pi.Alert/blob/main/docs/">all docs</a> for more info.',
//////////////////////////////////////////////////////////////////
// Front end events
@@ -485,7 +495,7 @@ $lang['en_us'] = array(
'run_event_tooltip' => 'Enable the setting and save your changes at first before you run it.',
'run_event_icon' => 'fa-play',
'general_event_title' => 'Executing an ad-hoc event',
'general_event_description' => 'The event you\'ve triggered might take a while until background processes finish. The execution ended once you see <code>finished</code> below. Check the <a onclick=\'setCache(\"activeMaintenanceTab\", \"tab_Logging_id\")\' href=\"/maintenance.php#tab_Logging\">error log</a> if you didn\'t get the expected result. <br/> <br/> Status: ',
'general_event_description' => ' The event you\'ve triggered might take a while until background processes finish. The execution ended once you see <code>finished</code> below. Check the <a href=\"/maintenance.php#tab_Logging\">error log</a> if you didn\'t get the expected result. <br/> <br/> Status: ',
@@ -495,7 +505,9 @@ $lang['en_us'] = array(
'Plugins_Unprocessed_Events' => 'Unprocessed Events',
'Plugins_Objects' => 'Plugin Objects',
'Plugins_DeleteAll' => 'Delete all (filters are ignored)',
'Plugins_History' => 'Events History',
'Plugins_Filters_Mac' => 'Mac Filter',
//////////////////////////////////////////////////////////////////
// Settings
@@ -524,6 +536,8 @@ The arp-scan time itself depends on the number of IP addresses to check so set t
'TIMEZONE_description' => 'Time zone to display stats correctly. Find your time zone <a target="_blank" href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones" rel="nofollow">here</a>.',
'ENABLE_PLUGINS_name' => 'Enable Plugins',
'ENABLE_PLUGINS_description' => 'Enables the <a target="_blank" href="https://github.com/jokob-sk/Pi.Alert/tree/main/front/plugins">plugins</a> functionality. Loading plugins requires more hardware resources so you might want to disable them on low-powered system.',
'PLUGINS_KEEP_HIST_name' => 'Plugins History',
'PLUGINS_KEEP_HIST_description' => 'How many entries of Plugins History scan results should be kept (globally, not device specific!).',
'PIALERT_WEB_PROTECTION_name' => 'Enable login',
'PIALERT_WEB_PROTECTION_description' => 'When enabled a login dialog is displayed. Read below carefully if you get locked out of your instance.',
'PIALERT_WEB_PASSWORD_name' => 'Login password',
@@ -534,6 +548,8 @@ The arp-scan time itself depends on the number of IP addresses to check so set t
'SCAN_CYCLE_MINUTES_description' => 'The delay between scans in minutes. If using arp-scan, the scan time itself depends on the number of IP addresses to check. This is influenced by the network mask set in the <a href="#SCAN_SUBNETS"><code>SCAN_SUBNETS</code> setting</a> at the top. Every IP takes a couple seconds to scan.',
'DAYS_TO_KEEP_EVENTS_name' => 'Delete events older than',
'DAYS_TO_KEEP_EVENTS_description' => 'This is a maintenance setting. This specifies the number of days worth of event entries that will be kept. All older events will be deleted periodically. Also applies on Plugin Events History.',
'HRS_TO_KEEP_NEWDEV_name' => 'Keep new devices for',
'HRS_TO_KEEP_NEWDEV_description' => 'This is a maintenance setting. If enabled (<code>0</code> is disabled), devices marked as <b>New Device</b> will be deleted if their <b>First Session</b> time was older than the specified hours in this setting. Use this setting if you want to auto-delete <b>New Devices</b> after <code>X</code> hours.',
'REPORT_DASHBOARD_URL_name' => 'Pi.Alert URL',
'REPORT_DASHBOARD_URL_description' => 'This URL is used as the base for generating links in the emails. Enter full URL starting with <code>http://</code> including the port number (no trailig slash <code>/</code>).',
'DIG_GET_IP_ARG_name' => 'Internet IP discovery',
@@ -543,11 +559,12 @@ The arp-scan time itself depends on the number of IP addresses to check so set t
'UI_PRESENCE_name' => 'Show in presence chart',
'UI_PRESENCE_description' => 'Select what statuses should be shown in the <b>Device presence over time</b> chart in the <a href="/devices.php" target="_blank">Devices</a> page. (<code>CTRL + Click</code> to select/deselect)',
//Email
'Email_display_name' => 'Email',
'Email_icon' => '<i class="fa fa-at"></i>',
'REPORT_MAIL_name' => 'Enable email',
'REPORT_MAIL_description' => 'If enabled an email is sent out with a list of changes you\'ve subscribed to. Please also fill out all remaining settings related to the SMTP setup below.',
'REPORT_MAIL_description' => 'If enabled an email is sent out with a list of changes you\'ve subscribed to. Please also fill out all remaining settings related to the SMTP setup below. If facing issues, set <code>LOG_LEVEL</code> to <code>debug</code> and check the <a href=\"/maintenance.php#tab_Logging\">error log</a>.',
'SMTP_SERVER_name' => 'SMTP server URL',
'SMTP_SERVER_description' => '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_GMAIL.md">follow this guide</a>',
'SMTP_PORT_name' => 'SMTP server PORT',
@@ -565,7 +582,7 @@ The arp-scan time itself depends on the number of IP addresses to check so set t
'REPORT_TO_name' => 'Send email to',
'REPORT_TO_description' => 'Email address to which the notification will be send to.',
'REPORT_FROM_name' => 'Email subject',
'REPORT_FROM_description' => 'Notification email subject line.',
'REPORT_FROM_description' => 'Notification email subject line. Some SMTP servers need this to be an email.',
//Webhooks
'Webhooks_display_name' => 'Webhooks',
@@ -575,9 +592,11 @@ The arp-scan time itself depends on the number of IP addresses to check so set t
'WEBHOOK_URL_name' => 'Target URL',
'WEBHOOK_URL_description' => 'Target URL starting with <code>http://</code> or <code>https://</code>.',
'WEBHOOK_PAYLOAD_name' => 'Payload type',
'WEBHOOK_PAYLOAD_description' => '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>html</code>)',
'WEBHOOK_PAYLOAD_description' => '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>)',
'WEBHOOK_REQUEST_METHOD_name' => 'Request method',
'WEBHOOK_REQUEST_METHOD_description' => 'The HTTP request method to be used for the webhook call.',
'WEBHOOK_SIZE_name' => 'Max payload size',
'WEBHOOK_SIZE_description' => '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.',
// Apprise
'Apprise_display_name' => 'Apprise',
@@ -673,6 +692,7 @@ The arp-scan time itself depends on the number of IP addresses to check so set t
'PHOLUS_DAYS_DATA_name' => 'Data retention',
'PHOLUS_DAYS_DATA_description' => 'How many days of Pholus scan entries should be kept (globally, not device specific!) Enter <code>0</code> to disable.',
// Nmap
'Nmap_display_name' => 'Nmap',
'Nmap_icon' => '<i class="fa fa-ethernet"></i>',

View File

@@ -29,7 +29,7 @@ $lang['es_es'] = array(
'Gen_AreYouSure' => '¿Estás seguro de',
'Gen_Upd' => 'Actualizado correctamente',
'Gen_Upd_Fail' => 'Fallo al actualizar',
'Gen_Help' => 'Ayuda',
'Gen_ReadDocs' => 'Ayuda',
'Gen_DataUpdatedUITakesTime' => 'Correcto - La interfaz puede tardar en actualizarse si se está ejecutando un escaneo.',
'Gen_LockedDB' => 'Fallo - La base de datos puede estar bloqueada - Pulsa F1 -> Ajustes de desarrolladores -> Consola o prueba más tarde.',
@@ -571,7 +571,7 @@ $lang['es_es'] = array(
'WEBHOOK_URL_name' => 'URL de destino',
'WEBHOOK_URL_description' => 'URL de destino comienza con <code>http://</code> o <code>https://</code>.',
'WEBHOOK_PAYLOAD_name' => 'Tipo de carga',
'WEBHOOK_PAYLOAD_description' => '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>html</code>)',
'WEBHOOK_PAYLOAD_description' => '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>)',
'WEBHOOK_REQUEST_METHOD_name' => 'Método de solicitud',
'WEBHOOK_REQUEST_METHOD_description' => 'El método de solicitud HTTP que se utilizará para la llamada de webhook.',
'Webhooks_settings_group' => '<i class="fa fa-circle-nodes"></i> Webhooks',

View File

@@ -1,6 +1,7 @@
<?php
require 'php/templates/header.php';
require 'php/templates/notification.php';
?>
<script src="js/pialert_common.js"></script>
@@ -8,461 +9,23 @@
<!-- Page ------------------------------------------------------------------ -->
<div class="content-wrapper">
<!-- Content header--------------------------------------------------------- -->
<section class="content-header">
<?php require 'php/templates/notification.php'; ?>
<!-- Content header--------------------------------------------------------- -->
<section class="content-header">
<h1 id="pageTitle">
<i class="fa fa-fw fa-plug"></i> <?= lang('Navigation_Plugins');?>
<span class="pageHelp"> <a target="_blank" href="https://github.com/jokob-sk/Pi.Alert/tree/main/front/plugins"><i class="fa fa-circle-question"></i></a><span>
</h1>
</section>
<!-- Main content ---------------------------------------------------------- -->
<section class="content">
<div class="nav-tabs-custom plugin-content" style="margin-bottom: 0px;">
<ul id="tabs-location" class="nav nav-tabs">
<!-- PLACEHOLDER -->
</ul>
<div id="tabs-content-location" class="tab-content">
<!-- PLACEHOLDER -->
</div>
<?php
require 'pluginsCore.php';
?>
</section>
</div>
<script defer>
// -----------------------------------------------------------------------------
// Get form control according to the column definition from config.json > database_column_definitions
function getFormControl(dbColumnDef, value, index) {
result = ''
switch(dbColumnDef.type)
{
case 'label':
result = `<span>${value}<span>`;
break;
case 'textboxsave':
value = value == 'null' ? '' : value; // hide 'null' values
id = `${dbColumnDef.column}_${index}`
result = `<span class="form-group">
<div class="input-group">
<input class="form-control" type="text" value="${value}" id="${id}" data-my-column="${dbColumnDef.column}" data-my-index="${index}" name="${dbColumnDef.column}">
<span class="input-group-addon"><i class="fa fa-save pointer" onclick="saveData('${id}');"></i></span>
</div>
<span>`;
break;
case 'url':
result = `<span><a href="${value}" target="_blank">${value}</a><span>`;
break;
case 'devicemac':
result = `<span class="anonymizeMac"><a href="/deviceDetails.php?mac=${value}" target="_blank">${value}</a><span>`;
break;
case 'deviceip':
result = `<span class="anonymizeIp"><a href="#" onclick="navigateToDeviceWithIp('${value}')" >${value}</a><span>`;
break;
case 'threshold':
$.each(dbColumnDef.options, function(index, obj) {
if(Number(value) < obj.maximum && result == '')
{
result = `<div style="background-color:${obj.hexColor}">${value}</div>`
// return;
}
});
break;
case 'replace':
$.each(dbColumnDef.options, function(index, obj) {
if(value == obj.equals)
{
result = `<span title="${value}">${obj.replacement}</span>`
}
});
break;
default:
result = value;
}
return result;
}
// -----------------------------------------------------------------------------
// Update the coresponding DB column and entry
function saveData (id) {
columnName = $(`#${id}`).attr('data-my-column')
index = $(`#${id}`).attr('data-my-index')
columnValue = $(`#${id}`).val()
$.get(`php/server/dbHelper.php?action=update&dbtable=Plugins_Objects&columnName=Index&id=${index}&columns=UserData&values=${columnValue}`, function(data) {
// var result = JSON.parse(data);
console.log(data)
if(sanitize(data) == 'OK')
{
showMessage('<?= lang('Gen_DataUpdatedUITakesTime');?>')
// Remove navigation prompt "Are you sure you want to leave..."
window.onbeforeunload = null;
} else
{
showMessage('<?= lang('Gen_LockedDB');?>')
}
});
}
// -----------------------------------------------------------------------------
// Get translated string
function localize (obj, key) {
currLangCode = getCookie("language")
result = ""
en_us = ""
if(obj.localized && obj.localized.includes(key))
{
for(i=0;i<obj[key].length;i++)
{
code = obj[key][i]["language_code"]
if( code == 'en_us')
{
en_us = obj[key][i]["string"]
}
if(code == currLangCode)
{
result = obj[key][i]["string"]
}
}
}
result == "" ? result = en_us : result ;
return result;
}
// -----------------------------------------------------------------------------
pluginDefinitions = []
pluginUnprocessedEvents = []
pluginObjects = []
pluginHistory = []
function getData(){
$.get('api/plugins.json', function(res) {
pluginDefinitions = res["data"];
$.get('api/table_plugins_events.json', function(res) {
pluginUnprocessedEvents = res["data"];
$.get('api/table_plugins_objects.json', function(res) {
pluginObjects = res["data"];
$.get('api/table_plugins_history.json', function(res) {
pluginHistory = res["data"];
generateTabs()
});
});
});
});
}
// -----------------------------------------------------------------------------
function generateTabs()
{
activetab = 'active'
$.each(pluginDefinitions, function(index, obj) {
// console.log(obj)
$('#tabs-location').append(
`<li class=" ${activetab}">
<a href="#${obj.unique_prefix}" data-plugin-prefix="${obj.unique_prefix}" id="${obj.unique_prefix}_id" data-toggle="tab" >
${localize(obj, 'icon')} ${localize(obj, 'display_name')}
</a>
</li>`
);
activetab = '' // only first tab is active
});
activetab = 'active'
$.each(pluginDefinitions, function(index, obj) {
headersHtml = ""
colDefinitions = []
evRows = ""
obRows = ""
hiRows = ""
// Generate the header
$.each(obj["database_column_definitions"], function(index, colDef){
if(colDef.show == true) // select only the ones to show
{
colDefinitions.push(colDef)
headersHtml += `<th class="${colDef.css_classes}" >${localize(colDef, "name" )}</th>`
}
});
// Generate the event rows
var eveCount = 0;
for(i=0;i<pluginUnprocessedEvents.length;i++)
{
if(pluginUnprocessedEvents[i].Plugin == obj.unique_prefix)
{
clm = ""
for(j=0;j<colDefinitions.length;j++)
{
clm += '<td>'+ pluginUnprocessedEvents[i][colDefinitions[j].column] +'</td>'
}
evRows += `<tr data-my-index="${pluginUnprocessedEvents[i]["Index"]}" >${clm}</tr>`
eveCount++;
}
}
// Generate the history rows
var histCount = 0
var histCountDisplayed = 0
for(i=pluginHistory.length-1;i >= 0;i--) // from latest to the oldest
{
if(pluginHistory[i].Plugin == obj.unique_prefix)
{
if(histCount < 50) // only display 50 entries to optimize performance
{
clm = ""
for(j=0;j<colDefinitions.length;j++)
{
clm += '<td>'+ pluginHistory[i][colDefinitions[j].column] +'</td>'
}
hiRows += `<tr data-my-index="${pluginHistory[i]["Index"]}" >${clm}</tr>`
histCountDisplayed++;
}
histCount++; // count and display the total
}
}
// Generate the object rows
var obCount = 0;
for(var i=0;i<pluginObjects.length;i++)
{
if(pluginObjects[i].Plugin == obj.unique_prefix)
{
clm = ""
for(var j=0;j<colDefinitions.length;j++)
{
clm += '<td>'+ getFormControl(colDefinitions[j], pluginObjects[i][colDefinitions[j].column], pluginObjects[i]["Index"]) +'</td>'
}
obRows += `<tr data-my-index="${pluginObjects[i]["Index"]}" >${clm}</tr>`
obCount++;
}
}
$('#tabs-content-location').append(
`
<div id="${obj.unique_prefix}" class="tab-pane ${activetab}">
<div class="nav-tabs-custom" style="margin-bottom: 0px">
<ul class="nav nav-tabs">
<li class="active" >
<a href="#objectsTarget_${obj.unique_prefix}" data-toggle="tab" >
<i class="fa fa-cube"></i> <?= lang('Plugins_Objects');?> (${obCount})
</a>
</li>
<li >
<a href="#eventsTarget_${obj.unique_prefix}" data-toggle="tab" >
<i class="fa fa-bolt"></i> <?= lang('Plugins_Unprocessed_Events');?> (${eveCount})
</a>
</li>
<li >
<a href="#historyTarget_${obj.unique_prefix}" data-toggle="tab" >
<i class="fa fa-clock"></i> <?= lang('Plugins_History');?> (${histCountDisplayed} out of ${histCount} )
</a>
</li>
<ul>
</div>
<div class="tab-content">
<div id="objectsTarget_${obj.unique_prefix}" class="tab-pane ${activetab}">
<table class="table table-striped" data-my-dbtable="Plugins_Objects">
<tbody>
<tr>
${headersHtml}
</tr>
${obRows}
</tbody>
</table>
<div class="plugin-obj-purge">
<button class="btn btn-primary" onclick="purgeAll('${obj.unique_prefix}', 'Plugins_Objects' )"><?= lang('Gen_DeleteAll');?></button>
</div>
</div>
<div id="eventsTarget_${obj.unique_prefix}" class="tab-pane">
<table class="table table-striped" data-my-dbtable="Plugins_Events">
<tbody>
<tr>
${headersHtml}
</tr>
${evRows}
</tbody>
</table>
<div class="plugin-obj-purge">
<button class="btn btn-primary" onclick="purgeAll('${obj.unique_prefix}', 'Plugins_Events' )"><?= lang('Gen_DeleteAll');?></button>
</div>
</div>
<div id="historyTarget_${obj.unique_prefix}" class="tab-pane">
<table class="table table-striped" data-my-dbtable="Plugins_History">
<tbody>
<tr>
${headersHtml}
</tr>
${hiRows}
</tbody>
</table>
<div class="plugin-obj-purge">
<button class="btn btn-primary" onclick="purgeAll('${obj.unique_prefix}', 'Plugins_History' )"><?= lang('Gen_DeleteAll');?></button>
</div>
</div>
</div>
<div class='plugins-description'>
${localize(obj, 'description')}
<span>
<a href="https://github.com/jokob-sk/Pi.Alert/tree/main/front/plugins/${obj.code_name}" target="_blank"><?= lang('Gen_Help');?></a>
</span>
</div>
</div>
`);
activetab = '' // only first tab is active
});
initTabs()
}
// --------------------------------------------------------
// handle first tab (objectsTarget_) display
function initTabs()
{
// events on tab change
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
var target = $(e.target).attr("href") // activated tab
// save the last prefix
if(target.includes('_') == false )
{
pref = target.split('#')[1]
} else
{
pref = target.split('_')[1]
}
everythingHidden = false;
if($('#objectsTarget_'+ pref) != undefined && $('#historyTarget_'+ pref) != undefined && $('#eventsTarget_'+ pref) != undefined)
{
everythingHidden = $('#objectsTarget_'+ pref).attr('class').includes('active') == false && $('#historyTarget_'+ pref).attr('class').includes('active') == false && $('#eventsTarget_'+ pref).attr('class').includes('active') == false;
}
// show the objectsTarget if no specific pane selected or if selected is hidden
if((target == '#'+pref ) && everythingHidden)
{
var classTmp = $('#objectsTarget_'+ pref).attr('class');
if($('#objectsTarget_'+ pref).attr('class').includes('active') == false)
{
classTmp += ' active';
$('#objectsTarget_'+ pref).attr('class', classTmp)
}
}
});
}
// --------------------------------------------------------
plugPrefix = ''
dbTable = ''
function purgeAll(callback) {
plugPrefix = arguments[0]; // plugin prefix
dbTable = arguments[1]; // DB table
// Ask
showModalWarning('<?= lang('Gen_Purge');?>' + ' ' + plugPrefix + ' ' + dbTable , '<?= lang('Gen_AreYouSure');?>',
'<?= lang('Gen_Cancel');?>', '<?= lang('Gen_Okay');?>', "purgeAllExecute");
}
// --------------------------------------------------------
function purgeAllExecute() {
$.ajax({
method: "POST",
url: "php/server/dbHelper.php",
data: { action: "delete", dbtable: dbTable, columnName: 'Plugin', id:plugPrefix },
success: function(data, textStatus) {
showModalOk ('Result', data );
}
})
}
// --------------------------------------------------------
function purgeVisible() {
idArr = $(`#${plugPrefix} table[data-my-dbtable="${dbTable}"] tr[data-my-index]`).map(function(){return $(this).attr("data-my-index");}).get();
$.ajax({
method: "POST",
url: "php/server/dbHelper.php",
data: { action: "delete", dbtable: dbTable, columnName: 'Index', id:idArr.toString() },
success: function(data, textStatus) {
showModalOk ('Result', data );
}
})
}
// -----------------------------------------------------------------------------
getData()
</script>
<?php
require 'php/templates/footer.php';
?>

View File

@@ -1,10 +1,35 @@
## Overview
## 📚 Docs for individual plugins
| ![Screen 1][screen1] | ![Screen 2][screen2] |
|----------------------|----------------------|
| ![Screen 3][screen3] | ![Screen 4][screen4] |
### Script based plugins
PiAlert comes with a plugin system to feed events from third-party scripts into the UI and then send notifications, if desired. The highlighted functionality this plugin system supports, is dynamic creation of a simple UI to interact with the discovered objects, a mechanism to surface settings of plugins in the UI, or to import objects into existing PiAlert database tables. (Currently update/overwriting of existing objects is not supported.)
- [website_monitor (WEBMON)](https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/website_monitor/)
- [dhcp_servers (DHCPSRVS)](https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/dhcp_servers/)
- [dhcp_leases (DHCPLSS)](https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/dhcp_leases/)
- [unifi_import (UNFIMP)](https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/unifi_import/)
- [snmp_discovery (SNMPDSC)](https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/snmp_discovery/)
- [undiscoverables (UNDIS)](https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/undiscoverables/)
### SQL query based plugins
- [nmap_services (NMAPSERV)](https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/nmap_services/)
### template based plugins
- [newdev_template (NEWDEV)](https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/newdev_template/)
## 🌟 Create a custom plugin: Overview
| ![Screen 1][screen1] | ![Screen 2][screen2] | ![Screen 3][screen3] |
|----------------------|----------------------| ----------------------|
| ![Screen 4][screen4] | ![Screen 5][screen5] |
PiAlert comes with a plugin system to feed events from third-party scripts into the UI and then send notifications, if desired. The highlighted core functionality this plugin system supports, is:
* dynamic creation of a simple UI to interact with the discovered objects,
* filtering of displayed values in the Devices UI
* surface settings of plugins in the UI,
* different column types for reported values to e.g. link back to a device
* import objects into existing PiAlert database tables
> (Currently, update/overwriting of existing objects is not supported.)
Example use cases for plugins could be:
@@ -13,15 +38,15 @@ Example use cases for plugins could be:
* Creating ad-hoc UI tables from existing data in the PiAlert database, e.g. to show all open ports on devices, to list devices that disconnected in the last hour, etc.
* Using other device discovery methods on the network and importing the data as new devices
* Creating a script to create FAKE devices based on user input via custom settings
* ...at this point the limitation is mostly the creativity than the capability (there might be edge cases and need to support more form controls for user input off custom settings, but you probably get the idea)
* ...at this point the limitation is mostly the creativity than the capability (there might be edge cases and a need to support more form controls for user input off custom settings, but you probably get the idea)
If you wish to develop a plugin, please check the existing plugin structure. Once the settings are saved by the user they need to be removed from the `pialert.conf` file manually if you want to re-initialize them from the `config.json` of the plugin.
Again, please read the below carefully if you'd like to contribute with a plugin yourself. This documentation file might be outdated, so double check the sample plugins as well.
Again, please read the below carefully if you'd like to contribute with a plugin yourself. This documentation file might be outdated, so double-check the sample plugins as well.
## ⚠ Disclaimer
Experimental feature used also to speed up development and to make the app more maintainable. Follow the below very carefully and check example plugin(s) if you'd like to write one yourself. Plugin UI is not my priority right now, happy to approve PRs if you are interested in extending/improvintg the UI experience. Example improvements for the taking:
Follow the below very carefully and check example plugin(s) if you'd like to write one yourself. Plugin UI is not my priority right now, happy to approve PRs if you are interested in extending/improving the UI experience. Example improvements for the taking:
* Making the tables sortable/filterable
* Using the same approach to display table data as in the Devices section (solves above)
@@ -34,14 +59,14 @@ These issues will be hopefully fixed with time, so please don't report them. Ins
* Existing plugin objects sometimes not interpreted correctly and a new object is created instead, resulting in duplicate entries.
* Occasional (experienced twice) hanging of processing plugin script file.
* UI displaying outdated values until the API endpoints get refreshed.
UI displays outdated values until the API endpoints get refreshed.
## Plugin file structure overview
> Folder name must be the same as the code name value in: `"code_name": "<value>"`
> Unique prefix needs to be unique compared to the other settings prefixes, e.g.: the prefix `APPRISE` is already in use.
| File | Required | Description |
| File | Required (plugin type) | Description |
|----------------------|----------------------|----------------------|
| `config.json` | yes | Contains the plugin configuration (manifest) including the settings available to the user. |
| `script.py` | yes (script) | The Python script itself |
@@ -70,21 +95,21 @@ More on specifics below.
## Supported data sources
Currently only two data sources are supported:
Currently, only 3 data sources are supported (valid `data_source` value).
- Script
- SQL query on the PiAlert database
- Script (`python-script`)
- SQL query on the PiAlert database (`pialert-db-query`)
- Template (`template`)
You need to set the `data_source` to either `pialert-db-query` or `python-script`:
```json
"data_source": "pialert-db-query"
```
Any of the above datasources have to return a "table" of the exact structure as outlined above.
> 🔎Example
>```json
>"data_source": "pialert-db-query"
>```
Any of the above data sources have to return a "table" of the exact structure as outlined above.
### "data_source": "python-script"
If the datasource is set to `python-script` the `CMD` setting (that you specify in the `settings` array section in the `config.json`) needs to contain a executable linux command, that generates a `last_result.log` file. This file needs to be stored in the same folder as the plugin.
If the `data_source` is set to `python-script` the `CMD` setting (that you specify in the `settings` array section in the `config.json`) needs to contain an executable Linux command, that generates a `last_result.log` file. This file needs to be stored in the same folder as the plugin.
The content of the `last_result.log` file needs to contain the columns as defined in the "Column order and values" section above. The order of columns can't be changed. After every scan it should contain only the results from the latest scan/execution.
@@ -99,7 +124,7 @@ Any of the above datasources have to return a "table" of the exact structure as
The [Undicoverables plugins `script.py` file](https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/undiscoverables/script.py) is a good and simple example to start with if you are considering creating a custom plugin. It uses the [`plugin_helper.py` library](https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/plugin_helper.py) that significantly simplifies the creation of your custom script.
#### last_result.log examples
#### 🔎 last_result.log examples
Valid CSV:
@@ -126,52 +151,107 @@ https://www.google.com|null|2023-01-02 15:56:30|200|0.7898|
### "data_source": "pialert-db-query"
If the datasource is set to `pialert-db-query` the `CMD` setting needs to contain a SQL query rendering the columns as defined in the "Column order and values" section above. The order of columns is important.
If the `data_source` is set to `pialert-db-query` the `CMD` setting needs to contain a SQL query rendering the columns as defined in the "Column order and values" section above. The order of columns is important.
This SQL query is executed on the `pialert.db` SQLite database file.
#### Examples
> 🔎Example
>
> SQL query example:
>
> ```SQL
> SELECT dv.dev_Name as Object_PrimaryID,
> cast(dv.dev_LastIP as VARCHAR(100)) || ':' || cast( SUBSTR(ns.Port ,0, INSTR(ns.Port , '/')) as VARCHAR(100)) as Object_SecondaryID,
> datetime() as DateTime,
> ns.Service as Watched_Value1,
> ns.State as Watched_Value2,
> 'null' as Watched_Value3,
> 'null' as Watched_Value4,
> ns.Extra as Extra,
> dv.dev_MAC as ForeignKey
> FROM
> (SELECT * FROM Nmap_Scan) ns
> LEFT JOIN
> (SELECT dev_Name, dev_MAC, dev_LastIP FROM Devices) dv
> ON ns.MAC = dv.dev_MAC
> ```
>
> Required `CMD` setting example with above query (you can set `"type": "label"` if you want it to make uneditable in the UI):
>
> ```json
> {
> "function": "CMD",
> "type": "text",
> "default_value":"SELECT dv.dev_Name as Object_PrimaryID, cast(dv.dev_LastIP as VARCHAR(100)) || ':' || cast( SUBSTR(ns.Port ,0, INSTR(ns.Port , '/')) as VARCHAR(100)) as Object_SecondaryID, datetime() as DateTime, ns.Service as Watched_Value1, ns.State as Watched_Value2, 'null' as Watched_Value3, 'null' as Watched_Value4, ns.Extra as Extra FROM (SELECT * FROM Nmap_Scan) ns LEFT JOIN (SELECT dev_Name, dev_MAC, dev_LastIP FROM Devices) dv ON ns.MAC = dv.dev_MAC",
> "options": [],
> "localized": ["name", "description"],
> "name" : [{
> "language_code":"en_us",
> "string" : "SQL to run"
> }],
> "description": [{
> "language_code":"en_us",
> "string" : "This SQL query is used to populate the coresponding UI tables under the Plugins section."
> }]
> }
> ```
SQL query example:
### "data_source": "template"
```SQL
SELECT dv.dev_Name as Object_PrimaryID,
cast(dv.dev_LastIP as VARCHAR(100)) || ':' || cast( SUBSTR(ns.Port ,0, INSTR(ns.Port , '/')) as VARCHAR(100)) as Object_SecondaryID,
datetime() as DateTime,
ns.Service as Watched_Value1,
ns.State as Watched_Value2,
'null' as Watched_Value3,
'null' as Watched_Value4,
ns.Extra as Extra,
dv.dev_MAC as ForeignKey
FROM
(SELECT * FROM Nmap_Scan) ns
LEFT JOIN
(SELECT dev_Name, dev_MAC, dev_LastIP FROM Devices) dv
ON ns.MAC = dv.dev_MAC
Used to initialize internal settings. Check the `newdev_template` plugin for details.
## 🕳 Filters
Plugin entries can be filtered based on values entered into filter fields. The `txtMacFilter` textbox/field contains the Mac address of the currently viewed device or simply a Mac address that's available in the `mac` query string.
| Property | Required | Description |
|----------------------|----------------------|----------------------|
| `compare_column` | yes | Plugin column name that's value is used for comparison (**Left** side of the equation) |
| `compare_operator` | yes | JavaScript comparison operator |
| `compare_field_id` | yes | The `id` of a input text field containing a value is used for comparison (**Right** side of the equation)|
| `compare_js_template` | yes | JavaScript code used to convert left and right side of the equation. `{value}` is replaced with input values. |
| `compare_use_quotes` | yes | If `true` then the end result of the `compare_js_template` i swrapped in `"` quotes. Use to compare strings. |
> 🔎Example:
>
> ```json
> "data_filters": [
> {
> "compare_column" : "Object_PrimaryID",
> "compare_operator" : "==",
> "compare_field_id": "txtMacFilter",
> "compare_js_template": "'{value}'.toString()",
> "compare_use_quotes": true
> }
> ],
> ```
1. On the `pluginsCore.php` page is an input field with the `txtMacFilter` id:
```html
<input class="form-control" id="txtMacFilter" type="text" value="--">
```
Required `CMD` setting example with above query (you can set `"type": "label"` if you want it to make uneditable in the UI):
2. This input field is initialized via the `&mac=` query string.
```json
{
"function": "CMD",
"type": "text",
"default_value":"SELECT dv.dev_Name as Object_PrimaryID, cast(dv.dev_LastIP as VARCHAR(100)) || ':' || cast( SUBSTR(ns.Port ,0, INSTR(ns.Port , '/')) as VARCHAR(100)) as Object_SecondaryID, datetime() as DateTime, ns.Service as Watched_Value1, ns.State as Watched_Value2, 'null' as Watched_Value3, 'null' as Watched_Value4, ns.Extra as Extra FROM (SELECT * FROM Nmap_Scan) ns LEFT JOIN (SELECT dev_Name, dev_MAC, dev_LastIP FROM Devices) dv ON ns.MAC = dv.dev_MAC",
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code":"en_us",
"string" : "SQL to run"
}],
"description": [{
"language_code":"en_us",
"string" : "This SQL query is used to populate the coresponding UI tables under the Plugins section."
}]
}
3. The app then proceeds to use this Mac value from this field and compares it to the value of the `Object_PrimaryID` database field. The `compare_operator` is `==`.
4. Both values, from the database field `Object_PrimaryID` and from the `txtMacFilter` are wrapped and evaluated with the `compare_js_template`, that is `'{value}.toString()'`.
5. `compare_use_quotes` is set to `true` so `'{value}'.toString()` is wrappe dinto `"` quotes.
6. This results in for example this code:
```javascript
// left part of teh expression coming from compare_column and right from the input field
// notice the added quotes ()") around the left and right part of teh expression
"eval('ac:82:ac:82:ac:82".toString()')" == "eval('ac:82:ac:82:ac:82".toString()')"
```
### Mapping the plugin results into a database table
7. Filters are only applied if a filter is specified and the `txtMacFilter` is not `undefined` or empty (`--`).
### 🗺 Mapping the plugin results into a database table
PiAlert will take the results of the plugin execution and insert these results into a database table, if a plugin contains the property `"mapped_to_table"` in the `config.json` root. The mapping of the columns is defined in the `database_column_definitions` array.
@@ -215,13 +295,12 @@ This approach is used to implement the `DHCPLSS` plugin. The script parses all s
The `params` array in the `config.json` is used to enable the user to change the parameters of the executed script. For example, the user wants to monitor a specific URL.
##### Example:
Passing user defined settings to a command. Let's say, you want to have a script, that is called with a user-defined parameter called `urls`:
```bash
root@server# python3 /home/pi/pialert/front/plugins/website_monitor/script.py urls=https://google.com,https://duck.com
```
> 🔎 Example:
> Passing user-defined settings to a command. Let's say, you want to have a script, that is called with a user-defined parameter called `urls`:
>
> ```bash
> root@server# python3 /home/pi/pialert/front/plugins/website_monitor/script.py urls=https://google.com,https://duck.com
> ```
* You can allow the user to add URLs to a setting with the `function` property set to a custom name, such as `urls_to_check` (this is not a reserved name from the section "Supported settings `function` values" below).
* You specify the parameter `urls` in the `params` section of the `config.json` the following way (`WEBMON_` is the plugin prefix automatically added to all the settings):
@@ -271,42 +350,42 @@ During script execution, the app will take the command `"python3 /home/pi/pialer
- to
- `python3 /home/pi/pialert/front/plugins/website_monitor/script.py urls=https://google.com,https://duck.com`
Below are some general additional notes, when definig `params`:
Below are some general additional notes, when defining `params`:
- `"name":"name_value"` - is used as a wildcard replacement in the `CMD` setting value by using curly brackets `{name_value}`. The wildcard is replaced by the result of the `"value" : "param_value"` and `"type":"type_value"` combo configuration below.
- `"type":"<sql|setting>"` - is used to specify the type of the params, currently only 2 supported (`sql`,`setting`).
- `"type":"sql"` - will execute the SQL query specified in the `value` property. The sql query needs to return only one column. The column is flattened and separated by commas (`,`), e.g: `SELECT dev_MAC from DEVICES` -> `Internet,74:ac:74:ac:74:ac,44:44:74:ac:74:ac`. This is then used to replace the wildcards in the `CMD` setting.
- `"type":"setting"` - The setting code name. A combination of the value from `unique_prefix` + `_` + `function` value, or otherwise the code name you can find in the Settings page under the Setting dispaly name, e.g. `SCAN_CYCLE_MINUTES`.
- `"value" : "param_value"` - Needs to contain a setting code name or sql query without wildcards.
- `"type":"setting"` - The setting code name. A combination of the value from `unique_prefix` + `_` + `function` value, or otherwise the code name you can find in the Settings page under the Setting display name, e.g. `SCAN_CYCLE_MINUTES`.
- `"value" : "param_value"` - Needs to contain a setting code name or SQL query without wildcards.
Example:
```json
{
"params" : [{
"name" : "macs",
"type" : "sql",
"value" : "SELECT dev_MAC from DEVICES"
},
{
"name" : "urls",
"type" : "setting",
"value" : "WEBMON_urls_to_check"
},
{
"name" : "internet_ip",
"type" : "setting",
"value" : "WEBMON_SQL_internet_ip"
}]
}
```
> 🔎Example:
>
> ```json
> {
> "params" : [{
> "name" : "macs",
> "type" : "sql",
> "value" : "SELECT dev_MAC from DEVICES"
> },
> {
> "name" : "urls",
> "type" : "setting",
> "value" : "WEBMON_urls_to_check"
> },
> {
> "name" : "internet_ip",
> "type" : "setting",
> "value" : "WEBMON_SQL_internet_ip"
> }]
> }
> ```
#### Setting object struncture
#### Setting object structure
- `"function": "<see Supported settings function values>"` - What function the setting drives or a simple unique code name
- `"type": "<text|integer|boolean|password|readonly|selectinteger|selecttext|multiselect|list>"` - The form control used for the setting displayed in the Settings page and what values are accepted.
- `"type": "<text|integer|boolean|password|readonly|integer.select|text.select|text.multiselect|list|integer.checkbox>"` - The form control used for the setting displayed in the Settings page and what values are accepted.
- `"localized"` - a list of properties on the current JSON level which need to be localized
- `"name"` and `"description"` - Displayed in the Settings page. An array of localized strings. (see Localized strings below).
@@ -327,43 +406,45 @@ You can have any `"function": "my_custom_name"` custom name, however, the ones l
- `watched-not-changed` - reports even on events where selected `Watched_ValueN` did not change
Example:
> 🔎 Example:
>
> ```json
> {
> "function": "RUN",
> "type": "text.select",
> "default_value":"disabled",
> "options": ["disabled", "once", "schedule", "always_after_scan", "on_new_device"],
> "localized": ["name", "description"],
> "name" :[{
> "language_code":"en_us",
> "string" : "When to run"
> }],
> "description": [{
> "language_code":"en_us",
> "string" : "Enable a regular scan of your services. 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=\"#WEBMON_RUN_TIMEOUT\"><code>WEBMON_RUN_TIMEOUT</code> setting</a>."
> }]
> }
> ```
```json
{
"function": "RUN",
"type": "selecttext",
"default_value":"disabled",
"options": ["disabled", "once", "schedule", "always_after_scan", "on_new_device"],
"localized": ["name", "description"],
"name" :[{
"language_code":"en_us",
"string" : "When to run"
}],
"description": [{
"language_code":"en_us",
"string" : "Enable a regular scan of your services. 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=\"#WEBMON_RUN_TIMEOUT\"><code>WEBMON_RUN_TIMEOUT</code> setting</a>."
}]
}
```
##### Localized strings
##### 🌍Localized strings
- `"language_code":"<en_us|es_es|de_de>"` - code name of the language string. Only these three currently supported. At least the `"language_code":"en_us"` variant has to be defined.
- `"language_code":"<en_us|es_es|de_de>"` - code name of the language string. Only these three are currently supported. At least the `"language_code":"en_us"` variant has to be defined.
- `"string"` - The string to be displayed in the given language.
Example:
> 🔎 Example:
>
> ```json
>
> {
> "language_code":"en_us",
> "string" : "When to run"
> }
>
> ```
```json
{
"language_code":"en_us",
"string" : "When to run"
}
```
##### UI settings in database_column_definitions
The UI will adjust how columns are displayed in the UI based on the definition of the `database_column_definitions` object. Thease are the supported form controls and related functionality:
The UI will adjust how columns are displayed in the UI based on the definition of the `database_column_definitions` object. These are the supported form controls and related functionality:
- Only columns with `"show": true` and also with at least an English translation will be shown in the UI.
- Supported types: `label`, `text`, `threshold`, `replace`, `deviceip`, `devicemac`, `url`. Check for details below, how columns behave based on the type.
@@ -373,9 +454,9 @@ The UI will adjust how columns are displayed in the UI based on the definition o
- The `options` property is used in conjunction with these types:
- `threshold` - The `options` array contains objects from lowest `maximum` to highest with corresponding `hexColor` used for the value background color if it's less than the specified `maximum`, but more than the previous one in the `options` array
- `replace` - The `options` array contains objects with an `equals` property, that is compared to the "value" and if the values are the same, the string in `replacement` is displayed in the UI instead of the actual "value"
- `devicemac` - The value is considered to be a mac address and a link pointing to the device with the given mac address is generated.
- `deviceip` - The value is considered to be an IP address and a link pointing to the device with the given IP is generated. The IP is cheked against the last detected IP addresses and translated into a mac address that is then used for the link itself.
- `url` - The value is considered to be a url so a link is generated.
- `devicemac` - The value is considered to be a Mac address and a link pointing to the device with the given Mac address is generated.
- `deviceip` - The value is considered to be an IP address and a link pointing to the device with the given IP is generated. The IP is checked against the last detected IP addresses and translated into a Mac address that is then used for the link itself.
- `url` - The value is considered to be a URL so a link is generated.
```json
@@ -440,20 +521,6 @@ The UI will adjust how columns are displayed in the UI based on the definition o
}
```
## Full Examples
### Script based plugins
- [website_monitor (WEBMON) config.json](https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/website_monitor/config.json)
- [dhcp_servers (DHCPSRVS) config.json](https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/dhcp_servers/config.json)
- [dhcp_leases (DHCPLSS) config.json](https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/dhcp_leases/config.json)
- [unifi_import (UNFIMP) config.json](https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/unifi_import/config.json)
- [snmp_discovery (SNMPDSC) config.json](https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/snmp_discovery/config.json)
- [undiscoverables (UNDIS) config.json](https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/undiscoverables/config.json)
### SQL query based plugins
- [nmap_services (NMAPSERV) config.json](https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/nmap_services/config.json)
[screen1]: https://raw.githubusercontent.com/jokob-sk/Pi.Alert/main/docs/img/plugins.png "Screen 1"

View File

@@ -3,6 +3,15 @@
"unique_prefix": "DHCPLSS",
"enabled": true,
"data_source": "python-script",
"data_filters": [
{
"compare_column" : "Object_PrimaryID",
"compare_operator" : "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
"compare_use_quotes": true
}
],
"localized": ["display_name", "description", "icon"],
"mapped_to_table": "DHCP_Leases",
"display_name" : [{
@@ -215,7 +224,7 @@
"settings":[
{
"function": "RUN",
"type": "selecttext",
"type": "text.select",
"default_value":"disabled",
"options": ["disabled", "once", "schedule", "always_after_scan", "on_new_device"],
"localized": ["name", "description"],
@@ -294,7 +303,7 @@
},
{
"function": "WATCH",
"type": "multiselect",
"type": "text.multiselect",
"default_value":["Watched_Value1", "Watched_Value4"],
"options": ["Watched_Value1","Watched_Value2","Watched_Value3","Watched_Value4"],
"localized": ["name", "description"],
@@ -309,7 +318,7 @@
},
{
"function": "REPORT_ON",
"type": "multiselect",
"type": "text.multiselect",
"default_value":["new","watched-changed"],
"options": ["new","watched-changed","watched-not-changed"],
"localized": ["name", "description"],

View File

@@ -205,7 +205,7 @@
"settings":[
{
"function": "RUN",
"type": "selecttext",
"type": "text.select",
"default_value":"disabled",
"options": ["disabled", "once", "schedule", "always_after_scan", "on_new_device"],
"localized": ["name", "description"],
@@ -269,7 +269,7 @@
},
{
"function": "WATCH",
"type": "multiselect",
"type": "text.multiselect",
"default_value":["Watched_Value1"],
"options": ["Watched_Value1","Watched_Value2","Watched_Value3","Watched_Value4"],
"localized": ["name", "description"],
@@ -284,7 +284,7 @@
},
{
"function": "REPORT_ON",
"type": "multiselect",
"type": "text.multiselect",
"default_value":["new","watched-changed"],
"options": ["new","watched-changed","watched-not-changed"],
"localized": ["name", "description"],

View File

@@ -0,0 +1,11 @@
## Overview
A simple template-based plugin for new devices. You can change the default values for newly detected devices.
### Usage
- Head to **Settings** > **New Devices** to adjust the default values.
### Notes
- This plugin generates editable settings that are then used in the `device.py` script to initialize new values.

View File

@@ -0,0 +1,535 @@
{
"code_name": "Devices.new",
"template_type": "database-entry",
"unique_prefix": "NEWDEV",
"enabled": true,
"data_source": "template",
"localized": ["display_name", "description", "icon"],
"display_name": [{
"language_code": "en_us",
"string": "New Devices"
}],
"description": [{
"language_code": "en_us",
"string": "The template used for new devices."
}],
"icon": [
{
"language_code": "en_us",
"string": "<i class=\"fa fa-plus\"></i>"
}
],
"settings":[
{
"function": "dev_MAC",
"type": "readonly",
"maxLength": 50,
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Device MAC"
}
],
"description": [
{
"language_code": "en_us",
"string": "The MAC address of the device. Uneditable - Autodetected."
}
]
},
{
"function": "dev_Name",
"type": "readonly",
"maxLength": 50,
"default_value": "(unknown)",
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Device Name"
}
],
"description": [
{
"language_code": "en_us",
"string": "The name of the device. Uneditable as internal functionality is dependent on specific new device names."
}
]
},
{
"function": "dev_Owner",
"type": "string",
"maxLength": 30,
"default_value": "House",
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Device Owner"
}
],
"description": [
{
"language_code": "en_us",
"string": "The owner of the device."
}
]
},
{
"function": "dev_DeviceType",
"type": "string",
"maxLength": 30,
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Device Type"
}
],
"description": [
{
"language_code": "en_us",
"string": "The type of the device."
}
]
},
{
"function": "dev_Vendor",
"type": "readonly",
"maxLength": 250,
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Device Vendor"
}
],
"description": [
{
"language_code": "en_us",
"string": "The vendor of the device. Uneditable - Autodetected."
}
]
},
{
"function": "dev_Favorite",
"type": "integer.checkbox",
"default_value": 0,
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Favorite Device"
}
],
"description": [
{
"language_code": "en_us",
"string": "Indicates whether the device is marked as a favorite."
}
]
},
{
"function": "dev_Group",
"type": "string",
"maxLength": 10,
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Device Group"
}
],
"description": [
{
"language_code": "en_us",
"string": "The group to which the device belongs."
}
]
},
{
"function": "dev_Comments",
"type": "string",
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Device Comments"
}
],
"description": [
{
"language_code": "en_us",
"string": "Additional comments or notes about the device."
}
]
},
{
"function": "dev_FirstConnection",
"type": "readonly",
"format": "date-time",
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "First Connection"
}
],
"description": [
{
"language_code": "en_us",
"string": "The date and time of the first connection with the device. Uneditable - Autodetected."
}
]
},
{
"function": "dev_LastConnection",
"type": "readonly",
"format": "date-time",
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Last Connection"
}
],
"description": [
{
"language_code": "en_us",
"string": "The date and time of the last connection with the device. Uneditable - Autodetected."
}
]
},
{
"function": "dev_LastIP",
"type": "readonly",
"maxLength": 50,
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Last IP"
}
],
"description": [
{
"language_code": "en_us",
"string": "The last known IP address of the device. Uneditable - Autodetected."
}
]
},
{
"function": "dev_StaticIP",
"type": "integer.checkbox",
"default_value": 0,
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Static IP"
}
],
"description": [
{
"language_code": "en_us",
"string": "Indicates whether the device has a static IP address."
}
]
},
{
"function": "dev_ScanCycle",
"type": "integer.checkbox",
"default_value": 1,
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Scan Cycle"
}
],
"description": [
{
"language_code": "en_us",
"string": "The default value of the <code>Scan device</code> dropdown. Enable if newly discovered devices should be scanned."
}
]
},
{
"function": "dev_LogEvents",
"type": "integer.checkbox",
"default_value": 1,
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Log Events"
}
],
"description": [
{
"language_code": "en_us",
"string": "Indicates whether events related to the device shouldbe logged."
}]
},
{
"function": "dev_AlertEvents",
"type": "integer.checkbox",
"default_value": 1,
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Alert Events"
}
],
"description": [
{
"language_code": "en_us",
"string": "Indicates whether events related to the device should trigger alerts. The default value of the <code>Alert All Events</code> checkbox."
}
]
},
{
"function": "dev_AlertDeviceDown",
"type": "integer.checkbox",
"default_value": 0,
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Alert Device Down"
}
],
"description": [
{
"language_code": "en_us",
"string": "Indicates whether an alert should be triggered when the device goes down. The default value of the <code>Alert Down</code> checkbox."
}
]
},
{
"function": "dev_SkipRepeated",
"type": "integer",
"default_value": 0,
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Skip Repeated"
}
],
"description": [
{
"language_code": "en_us",
"string": "The default value of the <code>Skip repeated notifications for</code> dropdown. Enter number of <b>hours</b> for which repeated notifications should be ignored for. If you enter <code>0</code> then you get notified on all events."
}
]
},
{
"function": "dev_LastNotification",
"type": "readonly",
"format": "date-time",
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Last Notification"
}
],
"description": [
{
"language_code": "en_us",
"string": "The date and time of the last notification sent for the device. Uneditable - Autodetected."
}
]
},
{
"function": "dev_PresentLastScan",
"type": "integer.checkbox",
"default_value": 1,
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Present Last Scan"
}
],
"description": [
{
"language_code": "en_us",
"string": "Indicates whether the device should be marked as present after detected in a scan."
}
]
},
{
"function": "dev_NewDevice",
"type": "integer.checkbox",
"default_value": true,
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "New Device"
}
],
"description": [
{
"language_code": "en_us",
"string": "Indicates whether the device is considered a new device. The default value of the <code>New Device</code> checkbox."
}
]
},
{
"function": "dev_Location",
"type": "string",
"maxLength": 250,
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Device Location"
}
],
"description": [
{
"language_code": "en_us",
"string": "The location of the device."
}
]
},
{
"function": "dev_Archived",
"type": "integer.checkbox",
"default_value": 0,
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Archived"
}
],
"description": [
{
"language_code": "en_us",
"string": "Indicates whether the device is archived. The default value of the <code>Archived</code> checkbox."
}
]
},
{
"function": "dev_Network_Node_MAC_ADDR",
"type": "string",
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Network Node MAC Address"
}
],
"description": [
{
"language_code": "en_us",
"string": "The MAC address of the network node."
}
]
},
{
"function": "dev_Network_Node_port",
"type": "readonly",
"default_value": 0,
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Network Node Port"
}
],
"description": [
{
"language_code": "en_us",
"string": "The port number of the network node. Uneditable."
}
]
},
{
"function": "dev_Icon",
"type": "string",
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Device Icon"
}
],
"description": [
{
"language_code": "en_us",
"string": "The icon associated with the device. Check the <a href=\"https://github.com/jokob-sk/Pi.Alert/blob/main/docs/ICONS.md\" target=\"_blank\">documentation on icons</a> for more details."
}
]
}
],
"required": [
"dev_MAC",
"dev_Name",
"dev_Owner",
"dev_FirstConnection",
"dev_LastConnection",
"dev_LastIP",
"dev_StaticIP",
"dev_ScanCycle",
"dev_LogEvents",
"dev_AlertEvents",
"dev_AlertDeviceDown",
"dev_SkipRepeated",
"dev_LastNotification",
"dev_PresentLastScan",
"dev_NewDevice",
"dev_Location",
"dev_Archived",
"dev_Network_Node_MAC_ADDR",
"dev_Network_Node_port",
"dev_Icon"
],
"additionalProperties": false
}

View File

@@ -3,6 +3,15 @@
"unique_prefix": "NMAPSRV",
"enabled": true,
"data_source": "pialert-db-query",
"data_filters": [
{
"compare_column" : "ForeignKey",
"compare_operator" : "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}.toString()'",
"compare_use_quotes": true
}
],
"localized": ["display_name", "description", "icon"],
"display_name" : [{
"language_code":"en_us",
@@ -49,13 +58,13 @@
"column": "Object_PrimaryID",
"css_classes": "col-sm-2",
"show": true,
"type": "devicemac",
"type": "label",
"default_value":"",
"options": [],
"localized": ["name"],
"name":[{
"language_code":"en_us",
"string" : "Device MAC"
"string" : "Device name"
}]
},
{
@@ -204,7 +213,7 @@
"settings":[
{
"function": "RUN",
"type": "selecttext",
"type": "text.select",
"default_value":"disabled",
"options": ["disabled", "once", "schedule", "always_after_scan", "on_new_device"],
"localized": ["name", "description"],
@@ -249,7 +258,7 @@
},
{
"function": "WATCH",
"type": "multiselect",
"type": "text.multiselect",
"default_value":["Watched_Value1"],
"options": ["Watched_Value1","Watched_Value2","Watched_Value3","Watched_Value4"],
"localized": ["name", "description"],
@@ -264,7 +273,7 @@
},
{
"function": "REPORT_ON",
"type": "multiselect",
"type": "text.multiselect",
"default_value":["new","watched-changed"],
"options": ["new","watched-changed","watched-not-changed"],
"localized": ["name", "description"],

View File

@@ -6,7 +6,10 @@ A plugin for importing devices from an SNMP enabled router or switch. Using SNM
Specify the following settings in the Settings section of PiAlert:
- `SNMPDSC_routers` - A list of `snmpwalk` commands to execute against IP addresses of roputers/switches with SNMP turned on. For example: `snmpwalk -v 2c -c public -OXsq 192.168.1.1 .1.3.6.1.2.1.3.1.1.2`
- `SNMPDSC_routers` - A list of `snmpwalk` commands to execute against IP addresses of roputers/switches with SNMP turned on. For example:
- `snmpwalk -v 2c -c public -OXsq 192.168.1.1 .1.3.6.1.2.1.3.1.1.2`
- `snmpwalk -v 2c -c public -Oxsq 192.168.1.1 .1.3.6.1.2.1.3.1.1.2` (note: lower case `x`)
### Setup Cisco IOS

View File

@@ -2,7 +2,16 @@
"code_name": "snmp_discovery",
"unique_prefix": "SNMPDSC",
"enabled": true,
"data_source": "python-script",
"data_source": "pyton-script",
"data_filters": [
{
"compare_column" : "Object_PrimaryID",
"compare_operator" : "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
"compare_use_quotes": true
}
],
"localized": ["display_name", "description", "icon"],
"mapped_to_table": "DHCP_Leases",
"display_name" : [{
@@ -216,7 +225,7 @@
"settings":[
{
"function": "RUN",
"type": "selecttext",
"type": "text.select",
"default_value":"disabled",
"options": ["disabled", "once", "schedule", "always_after_scan", "on_new_device"],
"localized": ["name", "description"],
@@ -295,7 +304,7 @@
},
{
"function": "WATCH",
"type": "multiselect",
"type": "text.multiselect",
"default_value":["Watched_Value1"],
"options": ["Watched_Value1","Watched_Value2","Watched_Value3","Watched_Value4"],
"localized": ["name", "description"],
@@ -310,7 +319,7 @@
},
{
"function": "REPORT_ON",
"type": "multiselect",
"type": "text.multiselect",
"default_value":["new","watched-changed"],
"options": ["new","watched-changed","watched-not-changed"],
"localized": ["name", "description"],

View File

@@ -90,26 +90,28 @@ def get_entries(newEntries):
with open(log_file, 'a') as run_logfile:
for line in newLines:
# debug
run_logfile.write(line)
tmpSplt = line.split('"')
if len(tmpSplt) == 3:
ipStr = tmpSplt[0].split('.')[-4:] # Get the last 4 elements to extract the IP
macStr = tmpSplt[1].strip().split(' ') # Remove leading/trailing spaces from MAC
ipStr = tmpSplt[0].split('.') # contains IP
if 'iso.' in line and len(ipStr) == 4:
macAddress = ':'.join(macStr)
ipAddress = '.'.join(ipStr)
macStr = tmpSplt[1].split(' ') # contains MAC
if 'iso.' in line and len(ipStr) == 16:
tmpEntry = plugin_object_class(
f'{macStr[0]}:{macStr[1]}:{macStr[2]}:{macStr[3]}:{macStr[4]}:{macStr[5]}',
f'{ipStr[12]}.{ipStr[13]}.{ipStr[14]}.{ipStr[15]}'.strip(),
macAddress,
ipAddress,
watched1='(unknown)',
watched2=snmpwalkArgs[6], # router IP
watched2=snmpwalkArgs[6], # router IP
extra=line
)
newEntries.append(tmpEntry)
newEntries.append(tmpEntry)
return newEntries

View File

@@ -35,7 +35,7 @@
"settings": [
{
"function": "RUN",
"type": "selecttext",
"type": "text.select",
"default_value":"disabled",
"options": ["disabled", "once", "always_after_scan"],
"localized": ["name", "description"],
@@ -50,7 +50,7 @@
},
{
"function": "CMD",
"type": "readonly",
"type": "text",
"default_value": "python3 /home/pi/pialert/front/plugins/undiscoverables/script.py devices={devices}",
"options": [],
"localized": ["name", "description"],

View File

@@ -3,6 +3,15 @@
"unique_prefix": "UNFIMP",
"enabled": true,
"data_source": "python-script",
"data_filters": [
{
"compare_column" : "Object_PrimaryID",
"compare_operator" : "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
"compare_use_quotes": true
}
],
"localized": ["display_name", "description", "icon"],
"mapped_to_table": "DHCP_Leases",
"display_name" : [{
@@ -187,7 +196,7 @@
"localized": ["name"],
"name":[{
"language_code":"en_us",
"string" : "Is online?"
"string" : "Online?"
}]
} ,
{
@@ -246,7 +255,7 @@
"settings":[
{
"function": "RUN",
"type": "selecttext",
"type": "text.select",
"default_value":"disabled",
"options": ["disabled", "once", "schedule", "always_after_scan", "on_new_device"],
"localized": ["name", "description"],
@@ -306,7 +315,7 @@
},
{
"function": "protocol",
"type": "selecttext",
"type": "text.select",
"default_value":"https://",
"options": ["https://", "http://"],
"localized": ["name", "description"],
@@ -415,7 +424,7 @@
},
{
"function": "WATCH",
"type": "multiselect",
"type": "text.multiselect",
"default_value":["Watched_Value1", "Watched_Value4"],
"options": ["Watched_Value1","Watched_Value2","Watched_Value3","Watched_Value4"],
"localized": ["name", "description"],
@@ -430,7 +439,7 @@
},
{
"function": "REPORT_ON",
"type": "multiselect",
"type": "text.multiselect",
"default_value":["new","watched-changed"],
"options": ["new","watched-changed","watched-not-changed"],
"localized": ["name", "description"],

View File

@@ -240,7 +240,7 @@
"settings":[
{
"function": "RUN",
"type": "selecttext",
"type": "text.select",
"default_value":"disabled",
"options": ["disabled", "once", "schedule", "always_after_scan", "on_new_device"],
"localized": ["name", "description"],
@@ -319,7 +319,7 @@
},
{
"function": "WATCH",
"type": "multiselect",
"type": "text.multiselect",
"default_value":["Watched_Value1"],
"options": ["Watched_Value1","Watched_Value2","Watched_Value3","Watched_Value4"],
"localized": ["name", "description"],
@@ -334,7 +334,7 @@
},
{
"function": "REPORT_ON",
"type": "multiselect",
"type": "text.multiselect",
"default_value":["new","watched-changed"],
"options": ["new","watched-changed","watched-not-changed"],
"localized": ["name", "description"],

541
front/pluginsCore.php Executable file
View File

@@ -0,0 +1,541 @@
<!-- Main content ---------------------------------------------------------- -->
<section class="content">
<div class="plugin-filters">
<div class="input-group col-sm-4">
<label class="control-label col-sm-3"><?= lang('Plugins_Filters_Mac');?></label>
<input class="form-control col-sm-3" id="txtMacFilter" type="text" value="--" readonly>
</div>
</div>
<div class="nav-tabs-custom plugin-content" style="margin-bottom: 0px;">
<ul id="tabs-location" class="nav nav-tabs col-sm-2 ">
<!-- PLACEHOLDER -->
</ul>
<div id="tabs-content-location" class="tab-content col-sm-10">
<!-- PLACEHOLDER -->
</div>
</section>
<script defer>
// -----------------------------------------------------------------------------
// Initializes fields based on current MAC
function initFields() {
var urlParams = new URLSearchParams(window.location.search);
mac = urlParams.get ('mac');
// if the current mac has changed, reinitialize the data
if(mac != undefined && $("#txtMacFilter").val() != mac)
{
$("#txtMacFilter").val(mac);
console.log("UPDATE");
getData();
}
}
// -----------------------------------------------------------------------------
// Checking if current MAC has changed and triggering an updated if needed
function updater() {
initFields()
// loop
setTimeout(function() {
updater();
}, 500);
}
// -----------------------------------------------------------------------------
// Get form control according to the column definition from config.json > database_column_definitions
function getFormControl(dbColumnDef, value, index) {
result = ''
switch(dbColumnDef.type)
{
case 'label':
result = `<span>${value}<span>`;
break;
case 'textboxsave':
value = value == 'null' ? '' : value; // hide 'null' values
id = `${dbColumnDef.column}_${index}`
result = `<span class="form-group">
<div class="input-group">
<input class="form-control" type="text" value="${value}" id="${id}" data-my-column="${dbColumnDef.column}" data-my-index="${index}" name="${dbColumnDef.column}">
<span class="input-group-addon"><i class="fa fa-save pointer" onclick="saveData('${id}');"></i></span>
</div>
<span>`;
break;
case 'url':
result = `<span><a href="${value}" target="_blank">${value}</a><span>`;
break;
case 'devicemac':
result = `<span class="anonymizeMac"><a href="/deviceDetails.php?mac=${value}" target="_blank">${value}</a><span>`;
break;
case 'deviceip':
result = `<span class="anonymizeIp"><a href="#" onclick="navigateToDeviceWithIp('${value}')" >${value}</a><span>`;
break;
case 'threshold':
$.each(dbColumnDef.options, function(index, obj) {
if(Number(value) < obj.maximum && result == '')
{
result = `<div style="background-color:${obj.hexColor}">${value}</div>`
// return;
}
});
break;
case 'replace':
$.each(dbColumnDef.options, function(index, obj) {
if(value == obj.equals)
{
result = `<span title="${value}">${obj.replacement}</span>`
}
});
break;
default:
result = value;
}
return result;
}
// -----------------------------------------------------------------------------
// Update the corresponding DB column and entry
function saveData (id) {
columnName = $(`#${id}`).attr('data-my-column')
index = $(`#${id}`).attr('data-my-index')
columnValue = $(`#${id}`).val()
$.get(`php/server/dbHelper.php?action=update&dbtable=Plugins_Objects&columnName=Index&id=${index}&columns=UserData&values=${columnValue}`, function(data) {
// var result = JSON.parse(data);
console.log(data)
if(sanitize(data) == 'OK')
{
showMessage('<?= lang('Gen_DataUpdatedUITakesTime');?>')
// Remove navigation prompt "Are you sure you want to leave..."
window.onbeforeunload = null;
} else
{
showMessage('<?= lang('Gen_LockedDB');?>')
}
});
}
// -----------------------------------------------------------------------------
// Get translated string
function localize (obj, key) {
currLangCode = getCookie("language")
result = ""
en_us = ""
if(obj.localized && obj.localized.includes(key))
{
for(i=0;i<obj[key].length;i++)
{
code = obj[key][i]["language_code"]
if( code == 'en_us')
{
en_us = obj[key][i]["string"]
}
if(code == currLangCode)
{
result = obj[key][i]["string"]
}
}
}
result == "" ? result = en_us : result ;
return result;
}
// -----------------------------------------------------------------------------
pluginDefinitions = []
pluginUnprocessedEvents = []
pluginObjects = []
pluginHistory = []
function getData(){
$.get('api/plugins.json', function(res) {
pluginDefinitions = res["data"];
$.get('api/table_plugins_events.json', function(res) {
pluginUnprocessedEvents = res["data"];
$.get('api/table_plugins_objects.json', function(res) {
pluginObjects = res["data"];
$.get('api/table_plugins_history.json', function(res) {
pluginHistory = res["data"];
generateTabs()
});
});
});
});
}
// -----------------------------------------------------------------------------
function generateTabs()
{
activetab = 'active'
// clear previous headers data
$('#tabs-location').html("");
// clear previous content data
$('#tabs-content-location').html("");
$.each(pluginDefinitions, function(index, pluginObj) {
// console.log(pluginObj)
if(pluginObj.data_source != "template") // hiding template-based plugins as they don't produce any output
{
$('#tabs-location').append(
`<li class=" left-nav ${activetab}">
<a class=" col-sm-12 " href="#${pluginObj.unique_prefix}" data-plugin-prefix="${pluginObj.unique_prefix}" id="${pluginObj.unique_prefix}_id" data-toggle="tab" >
${localize(pluginObj, 'icon')} ${localize(pluginObj, 'display_name')}
</a>
</li>`
);
activetab = '' // only first tab is active
}
});
activetab = 'active'
$.each(pluginDefinitions, function(index, pluginObj) {
headersHtml = ""
colDefinitions = []
evRows = ""
obRows = ""
hiRows = ""
// Generate the header
$.each(pluginObj["database_column_definitions"], function(index, colDef){
if(colDef.show == true) // select only the ones to show
{
colDefinitions.push(colDef)
headersHtml += `<th class="${colDef.css_classes}" >${localize(colDef, "name" )}</th>`
}
});
// Generate the event rows
var eveCount = 0;
for(i=0;i<pluginUnprocessedEvents.length;i++)
{
if(pluginUnprocessedEvents[i].Plugin == pluginObj.unique_prefix)
{
clm = ""
for(j=0;j<colDefinitions.length;j++)
{
clm += '<td>'+ pluginUnprocessedEvents[i][colDefinitions[j].column] +'</td>'
}
evRows += `<tr data-my-index="${pluginUnprocessedEvents[i]["Index"]}" >${clm}</tr>`
eveCount++;
}
}
// Generate the history rows
var histCount = 0
var histCountDisplayed = 0
for(i=pluginHistory.length-1;i >= 0;i--) // from latest to the oldest
{
if(pluginHistory[i].Plugin == pluginObj.unique_prefix)
{
if(histCount < 50) // only display 50 entries to optimize performance
{
clm = ""
for(j=0;j<colDefinitions.length;j++)
{
clm += '<td>'+ pluginHistory[i][colDefinitions[j].column] +'</td>'
}
hiRows += `<tr data-my-index="${pluginHistory[i]["Index"]}" >${clm}</tr>`
histCountDisplayed++;
}
histCount++; // count and display the total
}
}
// Generate the object rows
var obCount = 0;
for(var i=0;i<pluginObjects.length;i++)
{
if(pluginObjects[i].Plugin == pluginObj.unique_prefix)
{
if(shouldBeShown(pluginObjects[i], pluginObj)) // filter TODO
{
clm = ""
for(var j=0;j<colDefinitions.length;j++)
{
clm += '<td>'+ getFormControl(colDefinitions[j], pluginObjects[i][colDefinitions[j].column], pluginObjects[i]["Index"]) +'</td>'
}
obRows += `<tr data-my-index="${pluginObjects[i]["Index"]}" >${clm}</tr>`
obCount++;
}
}
}
// Generate the HTML
$('#tabs-content-location').append(
`
<div id="${pluginObj.unique_prefix}" class="tab-pane ${activetab}">
<div class="nav-tabs-custom" style="margin-bottom: 0px">
<ul class="nav nav-tabs">
<li class="active" >
<a href="#objectsTarget_${pluginObj.unique_prefix}" data-toggle="tab" >
<i class="fa fa-cube"></i> <?= lang('Plugins_Objects');?> (${obCount})
</a>
</li>
<li >
<a href="#eventsTarget_${pluginObj.unique_prefix}" data-toggle="tab" >
<i class="fa fa-bolt"></i> <?= lang('Plugins_Unprocessed_Events');?> (${eveCount})
</a>
</li>
<li >
<a href="#historyTarget_${pluginObj.unique_prefix}" data-toggle="tab" >
<i class="fa fa-clock"></i> <?= lang('Plugins_History');?> (${histCountDisplayed} out of ${histCount} )
</a>
</li>
<ul>
</div>
<div class="tab-content">
<div id="objectsTarget_${pluginObj.unique_prefix}" class="tab-pane ${activetab}">
<table class="table table-striped" data-my-dbtable="Plugins_Objects">
<tbody>
<tr>
${headersHtml}
</tr>
${obRows}
</tbody>
</table>
<div class="plugin-obj-purge">
<button class="btn btn-primary" onclick="purgeAll('${pluginObj.unique_prefix}', 'Plugins_Objects' )"><?= lang('Plugins_DeleteAll');?></button>
</div>
</div>
<div id="eventsTarget_${pluginObj.unique_prefix}" class="tab-pane">
<table class="table table-striped" data-my-dbtable="Plugins_Events">
<tbody>
<tr>
${headersHtml}
</tr>
${evRows}
</tbody>
</table>
<div class="plugin-obj-purge">
<button class="btn btn-primary" onclick="purgeAll('${pluginObj.unique_prefix}', 'Plugins_Events' )"><?= lang('Plugins_DeleteAll');?></button>
</div>
</div>
<div id="historyTarget_${pluginObj.unique_prefix}" class="tab-pane">
<table class="table table-striped" data-my-dbtable="Plugins_History">
<tbody>
<tr>
${headersHtml}
</tr>
${hiRows}
</tbody>
</table>
<div class="plugin-obj-purge">
<button class="btn btn-primary" onclick="purgeAll('${pluginObj.unique_prefix}', 'Plugins_History' )"><?= lang('Plugins_DeleteAll');?></button>
</div>
</div>
</div>
<div class='plugins-description'>
${localize(pluginObj, 'description')}
<span>
<a href="https://github.com/jokob-sk/Pi.Alert/tree/main/front/plugins/${pluginObj.code_name}" target="_blank"><?= lang('Gen_ReadDocs');?></a>
</span>
</div>
</div>
`);
activetab = '' // only first tab is active
});
initTabs()
}
// --------------------------------------------------------
// Handle active / selected tabs
// handle first tab (objectsTarget_) display
function initTabs()
{
// events on tab change
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
var target = $(e.target).attr("href") // activated tab
// save the last prefix
if(target.includes('_') == false )
{
pref = target.split('#')[1]
} else
{
pref = target.split('_')[1]
}
everythingHidden = false;
if($('#objectsTarget_'+ pref) != undefined && $('#historyTarget_'+ pref) != undefined && $('#eventsTarget_'+ pref) != undefined)
{
everythingHidden = $('#objectsTarget_'+ pref).attr('class').includes('active') == false && $('#historyTarget_'+ pref).attr('class').includes('active') == false && $('#eventsTarget_'+ pref).attr('class').includes('active') == false;
}
// show the objectsTarget if no specific pane selected or if selected is hidden
if((target == '#'+pref ) && everythingHidden)
{
var classTmp = $('#objectsTarget_'+ pref).attr('class');
if($('#objectsTarget_'+ pref).attr('class').includes('active') == false)
{
classTmp += ' active';
$('#objectsTarget_'+ pref).attr('class', classTmp)
}
}
});
}
// --------------------------------------------------------
// Filter method that determines if an entry should be shown
function shouldBeShown(entry, pluginObj)
{
if (pluginObj.hasOwnProperty('data_filters')) {
let dataFilters = pluginObj.data_filters;
// Loop through 'data_filters' array and appply filters on individual plugin entries
for (let i = 0; i < dataFilters.length; i++) {
compare_field_id = dataFilters[i].compare_field_id;
compare_column = dataFilters[i].compare_column;
compare_operator = dataFilters[i].compare_operator;
compare_js_template = dataFilters[i].compare_js_template;
compare_use_quotes = dataFilters[i].compare_use_quotes;
compare_field_id_value = $(`#${compare_field_id}`).val();
if(compare_field_id_value != undefined && compare_field_id_value != '--')
{
// valid value
// resolve the left and right part of the comparison
let left = compare_js_template.replace('{value}', `${compare_field_id_value}`)
let right = compare_js_template.replace('{value}', `${entry[compare_column]}`)
// include wrapper quotes if specified
compare_use_quotes ? quotes = '"' : quotes = ''
result = eval(
quotes + `${eval(left)}` + quotes +
` ${compare_operator} ` +
quotes + `${eval(right)}` + quotes
);
return result;
}
}
}
return true;
}
// --------------------------------------------------------
// Data cleanup/purge functionality
plugPrefix = ''
dbTable = ''
function purgeAll(callback) {
plugPrefix = arguments[0]; // plugin prefix
dbTable = arguments[1]; // DB table
// Ask
showModalWarning('<?= lang('Gen_Purge');?>' + ' ' + plugPrefix + ' ' + dbTable , '<?= lang('Gen_AreYouSure');?>',
'<?= lang('Gen_Cancel');?>', '<?= lang('Gen_Okay');?>', "purgeAllExecute");
}
// --------------------------------------------------------
function purgeAllExecute() {
$.ajax({
method: "POST",
url: "php/server/dbHelper.php",
data: { action: "delete", dbtable: dbTable, columnName: 'Plugin', id:plugPrefix },
success: function(data, textStatus) {
showModalOk ('Result', data );
}
})
}
// --------------------------------------------------------
function purgeVisible() {
idArr = $(`#${plugPrefix} table[data-my-dbtable="${dbTable}"] tr[data-my-index]`).map(function(){return $(this).attr("data-my-index");}).get();
$.ajax({
method: "POST",
url: "php/server/dbHelper.php",
data: { action: "delete", dbtable: dbTable, columnName: 'Index', id:idArr.toString() },
success: function(data, textStatus) {
showModalOk ('Result', data );
}
})
}
// -----------------------------------------------------------------------------
// Main sequence
getData()
updater()
</script>

View File

@@ -32,7 +32,7 @@ $result = $db->query("SELECT * FROM Settings");
// array
$settingKeyOfLists = array();
$settingCoreGroups = array('General', 'Email', 'Webhooks', 'Apprise', 'NTFY', 'PUSHSAFER', 'MQTT', 'DynDNS', 'PiHole', 'Pholus', 'Nmap', 'API');
$settingCoreGroups = array('General', 'NewDeviceDefaults', 'Email', 'Webhooks', 'Apprise', 'NTFY', 'PUSHSAFER', 'MQTT', 'DynDNS', 'PiHole', 'Pholus', 'Nmap', 'API');
$settings = array();
while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
// Push row data
@@ -82,13 +82,16 @@ while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
// create settings groups
$isIn = ' in ';
foreach ($groups as $group)
{
{
$isPlugin = FALSE;
if (in_array($group, $settingCoreGroups))
{
$settingGroupTypeHtml = "";
} else
{
$settingGroupTypeHtml = ' (<i class="fa-regular fa-plug fa-sm"></i>) ';
$isPlugin = TRUE;
}
$html = $html.'<div class=" box panel panel-default">
@@ -101,6 +104,15 @@ while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
<div class="panel-body">';
$isIn = ' '; // open the first panel only by default on page load
if($isPlugin)
{
$html = $html.
'<div class=" row table_row" >
<div class="table_cell bold">
<i class="fa-regular fa-book fa-sm"></i> <a href="https://github.com/jokob-sk/Pi.Alert/tree/main/front/plugins" target="_blank">' . lang('Gen_ReadDocs').'</a></div>
</div>';
}
// populate settings for each group
foreach ($settings as $set)
{
@@ -128,37 +140,37 @@ while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
$input = "";
// text - textbox
if($set['Type'] == 'text' )
if($set['Type'] == 'text' || $set['Type'] == 'string' || $set['Type'] == 'date-time' )
{
$input = '<input class="form-control" onChange="settingsChanged()" id="'.$set['Code_Name'].'" value="'.$set['Value'].'"/>';
$input = '<input class="form-control" onChange="settingsChanged()" my-data-type="'.$set['Type'].'" id="'.$set['Code_Name'].'" value="'.$set['Value'].'"/>';
}
// password - hidden text
elseif ($set['Type'] == 'password')
{
$input = '<input onChange="settingsChanged()" class="form-control input" id="'.$set['Code_Name'].'" type="password" value="'.$set['Value'].'"/>';
$input = '<input onChange="settingsChanged()" my-data-type="'.$set['Type'].'" class="form-control input" id="'.$set['Code_Name'].'" type="password" value="'.$set['Value'].'"/>';
}
// readonly
elseif ($set['Type'] == 'readonly')
{
$input = '<input class="form-control input" id="'.$set['Code_Name'].'" value="'.$set['Value'].'" readonly/>';
$input = '<input class="form-control input" my-data-type="'.$set['Type'].'" id="'.$set['Code_Name'].'" value="'.$set['Value'].'" readonly/>';
}
// boolean - checkbox
elseif ($set['Type'] == 'boolean')
elseif ($set['Type'] == 'boolean' || $set['Type'] == 'integer.checkbox')
{
$checked = "";
if ($set['Value'] == "True") { $checked = "checked";};
$input = '<input onChange="settingsChanged()" class="checkbox" id="'.$set['Code_Name'].'" type="checkbox" value="'.$set['Value'].'" '.$checked.' />';
if ($set['Value'] == "True" || $set['Value'] == "1") { $checked = "checked";};
$input = '<input onChange="settingsChanged()" my-data-type="'.$set['Type'].'" class="checkbox" id="'.$set['Code_Name'].'" type="checkbox" value="'.$set['Value'].'" '.$checked.' />';
}
// integer - number input
elseif ($set['Type'] == 'integer')
{
$input = '<input onChange="settingsChanged()" class="form-control" id="'.$set['Code_Name'].'" type="number" value="'.$set['Value'].'"/>';
$input = '<input onChange="settingsChanged()" my-data-type="'.$set['Type'].'" class="form-control" id="'.$set['Code_Name'].'" type="number" value="'.$set['Value'].'"/>';
}
// selecttext - dropdown
elseif ($set['Type'] == 'selecttext')
// text.select - dropdown
elseif ($set['Type'] == 'text.select')
{
$input = '<select onChange="settingsChanged()" class="form-control" name="'.$set['Code_Name'].'" id="'.$set['Code_Name'].'">';
$input = '<select onChange="settingsChanged()" my-data-type="'.$set['Type'].'" class="form-control" name="'.$set['Code_Name'].'" id="'.$set['Code_Name'].'">';
$values = createArray($set['Value']);
$options = createArray($set['Options']);
@@ -174,10 +186,10 @@ while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
}
$input = $input.'</select>';
}
// selectinteger - dropdown
elseif ($set['Type'] == 'selectinteger')
// integer.select - dropdown
elseif ($set['Type'] == 'integer.select')
{
$input = '<select onChange="settingsChanged()" class="form-control" name="'.$set['Code_Name'].'" id="'.$set['Code_Name'].'">';
$input = '<select onChange="settingsChanged()" my-data-type="'.$set['Type'].'" class="form-control" name="'.$set['Code_Name'].'" id="'.$set['Code_Name'].'">';
$values = createArray($set['Value']);
$options = createArray($set['Options']);
@@ -194,10 +206,10 @@ while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
}
$input = $input.'</select>';
}
// multiselect
elseif ($set['Type'] == 'multiselect')
// text.multiselect
elseif ($set['Type'] == 'text.multiselect')
{
$input = '<select onChange="settingsChanged()" class="form-control" name="'.$set['Code_Name'].'" id="'.$set['Code_Name'].'" multiple>';
$input = '<select onChange="settingsChanged()" my-data-type="'.$set['Type'].'" class="form-control" name="'.$set['Code_Name'].'" id="'.$set['Code_Name'].'" multiple>';
$values = createArray($set['Value']);
$options = createArray($set['Options']);
@@ -219,7 +231,7 @@ while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
$input = $input.
'<div class="row form-group">
<div class="col-xs-5">
<input class="form-control" id="ipMask" type="text" placeholder="192.168.1.0/24"/>
<input class="form-control" id="ipMask" type="text" placeholder="192.168.1.0/24"/>
</div>';
// Add interface button
$input = $input.
@@ -231,7 +243,7 @@ while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
// list all interfaces as options
$input = $input.'<div class="form-group">
<select class="form-control" name="'.$set['Code_Name'].'" id="'.$set['Code_Name'].'" multiple readonly>';
<select class="form-control" my-data-type="'.$set['Type'].'" name="'.$set['Code_Name'].'" id="'.$set['Code_Name'].'" multiple readonly>';
$options = createArray($set['Value']);
@@ -262,7 +274,7 @@ while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
// list all interfaces as options
$input = $input.'<div class="form-group">
<select class="form-control" name="'.$set['Code_Name'].'" id="'.$set['Code_Name'].'" multiple readonly>';
<select class="form-control" my-data-type="'.$set['Type'].'" name="'.$set['Code_Name'].'" id="'.$set['Code_Name'].'" multiple readonly>';
$options = createArray($set['Value']);
@@ -411,7 +423,7 @@ while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
// generate javascript to collect values
<?php
$noConversion = array('text', 'integer', 'password', 'readonly', 'selecttext', 'selectinteger', 'multiselect');
$noConversion = array('text', 'integer', 'password', 'readonly', 'text.select', 'integer.select', 'text.multiselect');
foreach ($settings as $set) {
if(in_array($set['Type'] , $noConversion))
@@ -424,6 +436,12 @@ while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
echo 'temp = $("#'.$set["Code_Name"].'").is(":checked") ;';
echo 'settingsArray.push(["'.$set["Group"].'", "'.$set["Code_Name"].'", "'.$set["Type"].'", temp ]);';
}
elseif ($set['Type'] == "integer.checkbox")
{
echo 'temp = $("#'.$set["Code_Name"].'").is(":checked") ;';
echo 'temp ? temp = 1 : temp = 0;';
echo 'settingsArray.push(["'.$set["Group"].'", "'.$set["Code_Name"].'", "'.$set["Type"].'", temp ]);';
}
elseif ($set["Code_Name"] == "SCAN_SUBNETS")
{
echo "var temps = [];

View File

@@ -4,7 +4,7 @@
#
# index.html - Redirect default page to pialert web admin portal
#-------------------------------------------------------------------------------
# Puche 2021 pi.alert.application@gmail.com GNU GPLv3
# Puche 2021 GNU GPLv3
#--------------------------------------------------------------------------- -->
<meta http-equiv="refresh" content="0; url=pialert"/>

View File

@@ -4,7 +4,7 @@
#
# pialert_front.conf - lighttpd domain redirection
# ------------------------------------------------------------------------------
# Puche 2021 pi.alert.application@gmail.com GNU GPLv3
# Puche 2021 GNU GPLv3
# ------------------------------------------------------------------------------
$HTTP["host"] == "pi.alert" {

View File

@@ -1,14 +1,50 @@
# Pi.Alert all split into modules
# Pi.Alert modules
I am trying to split this big original file into modules and gives me some nice challanges to solve.
Since the original code is all in one file, the original author has taken quite some shortcuts by defining lots of variables as global !!
These need to be changed now.
Here is the main structure
The original pilaert.py code is now moved to this new folder and split into different modules.
| Module | Description |
|--------|-----------|
|pialert.py | The MAIN program of Pi.Alert|
|const.py | A place to define the constants for Pi.Alert like log path or config path.|
|const.py| const.py holds the configuration variables and makes them availabe for all modules. It is also the <b>workaround</b> for the global variables until I can work them out|
|api.py| |
|```__main__.py```| The MAIN program of Pi.Alert|
|```__init__.py```| an empty init file|
|```README.md```| this readme file|
|**publishers**| a folder containing all modules used to publish the results|
|**scanners**| a folder containing all modules used to scan for devices |
|```api.py```| updating the API endpoints with the relevant data. (Should move to publishers)|
|```const.py```| A place to define the constants for Pi.Alert like log path or config path.|
|```conf.py```| conf.py holds the configuration variables and makes them available for all modules. It is also the <b>workaround</b> for global variables that need to be resolved at some point|
|```database.py```| This module connects to the DB, makes sure the DB is up to date and defines some standard queries and interfaces. |
|```device.py```| The device module looks after the devices and saves the scan results into the devices |
|```helper.py```| Helper as the name suggest contains multiple little functions and methods used in many of the other modules and helps keep things clean |
|```initialise.py```| Initiatlise sets up the environment and makes everything ready to go |
|```logger.py```| Logger is there the keep all the logs organised and looking identical. |
|```mac_vendor.py```| This module runs and manages the ``` update_vendors.sh ``` script from within Pi.Alert |
|```networscan.py```| Networkscan orchestrates the actual scanning of the network, calling the individual scanners and managing the results |
|```plugin.py```| This is where the plugins get integrated into the backend of Pi.Alert |
|```reporting.py```| Reporting generates the email, html and json reports to be sent by the publishers |
|```scheduler.py```| All things scheduling |
## publishers
publishers generally have a check_config method as well as a send method.
| Module | Description |
|--------|-----------|
|```__init__.py```| an empty init file|
|```apprise.py```| use apprise to integrate to "everywhere" https://github.com/caronc/apprise |
|```email.py```| Configure and send the reports and notifications via email |
|```mqtt.py```| integrate with a MQTT broker and even make the devices automatically discoverable in Home-Assistant |
|```ntfy.py```| integrate with ntfy |
|```pushsafer.py```| integrate with pushsafer |
|```webhook.py```| integrate via webhook |
## scanners
different methods to scan the network for devices or to find more details about the discovered devices
| Module | Description |
|--------|-----------|
|```__init__.py```| an empty init file (oops missing in the repo)|
|```arpscan.py```| run an arp-scan to discover devices |
|```internet.py```| discover the internet interface and check the external IP also manage Dynamic DNS |
|```nmapscan.py```| use Nmap to discover more about devices |
|```pholusscan.py```| use a 3rd party script Pholus to detect more details about the devices on the network |
|```pihole.py```| Use the PiHole network table in its db and also read the DHCP leases file to discover new devices |

View File

@@ -81,9 +81,6 @@ def main ():
conf.check_report = [1, "internet_IP", "update_vendors_silent"]
conf.plugins_once_run = False
pialert_start_time = timeNow()
# to be deleted if not used
conf.log_timestamp = conf.time_started
#cron_instance = Cron()
@@ -115,14 +112,12 @@ def main ():
# Upgrade DB if needed
db.upgradeDB()
#===============================================================================
# This is the main loop of Pi.Alert
#===============================================================================
while True:
# update time started
time_started = datetime.datetime.now() # not sure why we need this ...
loop_start_time = timeNow()
mylog('debug', '[MAIN] Starting loop')
@@ -131,7 +126,9 @@ def main ():
# check if new version is available / only check once an hour
# if newVersionAvailable is already true the function does nothing and returns true again
mylog('debug', [f"[Version check] Last version check timestamp: {last_version_check}"])
if last_version_check + datetime.timedelta(hours=1) < loop_start_time :
last_version_check = loop_start_time
conf.newVersionAvailable = isNewVersion(conf.newVersionAvailable)
# Handle plugins executed ONCE
@@ -149,13 +146,13 @@ def main ():
if last_scan_run + datetime.timedelta(minutes=1) < loop_start_time :
# last time any scan or maintenance/upkeep was run
last_scan_run = time_started
last_scan_run = loop_start_time
# Header
updateState(db,"Process: Start")
# Timestamp
startTime = time_started
startTime = loop_start_time
startTime = startTime.replace (microsecond=0)
# Check if any plugins need to run on schedule
@@ -166,14 +163,14 @@ def main ():
# --------------------------------------------
# check for changes in Internet IP
if last_internet_IP_scan + datetime.timedelta(minutes=3) < time_started:
if last_internet_IP_scan + datetime.timedelta(minutes=3) < loop_start_time:
conf.cycle = 'internet_IP'
last_internet_IP_scan = time_started
last_internet_IP_scan = loop_start_time
check_internet_IP(db)
# Update vendors once a week
if last_update_vendors + datetime.timedelta(days = 7) < time_started:
last_update_vendors = time_started
if last_update_vendors + datetime.timedelta(days = 7) < loop_start_time:
last_update_vendors = loop_start_time
conf.cycle = 'update_vendors'
mylog('verbose', ['[MAIN] cycle:',conf.cycle])
update_devices_MAC_vendors(db)
@@ -217,8 +214,8 @@ def main ():
performNmapScan(db, get_all_devices(db))
# Perform a network scan via arp-scan or pihole
if last_network_scan + datetime.timedelta(minutes=conf.SCAN_CYCLE_MINUTES) < time_started:
last_network_scan = time_started
if last_network_scan + datetime.timedelta(minutes=conf.SCAN_CYCLE_MINUTES) < loop_start_time:
last_network_scan = loop_start_time
conf.cycle = 1 # network scan
mylog('verbose', ['[MAIN] cycle:',conf.cycle])
updateState(db,"Scan: Network")
@@ -275,12 +272,12 @@ def main ():
# send all configured notifications
send_notifications(db)
# clean up the DB once a day
if last_cleanup + datetime.timedelta(hours = 24) < time_started:
last_cleanup = time_started
# clean up the DB once an hour
if last_cleanup + datetime.timedelta(hours = 1) < loop_start_time:
last_cleanup = loop_start_time
conf.cycle = 'cleanup'
mylog('verbose', ['[MAIN] cycle:',conf.cycle])
db.cleanup_database(startTime, conf.DAYS_TO_KEEP_EVENTS, conf.PHOLUS_DAYS_DATA)
db.cleanup_database(startTime, conf.DAYS_TO_KEEP_EVENTS, conf.PHOLUS_DAYS_DATA, conf.HRS_TO_KEEP_NEWDEV, conf.PLUGINS_KEEP_HIST)
# Commit SQL
db.commitDB()

View File

@@ -10,10 +10,6 @@ from helper import json_struc, initOrSetParam, row_to_json, timeNow #, updateSta
class DB():
"""
DB Class to provide the basic database interactions.
@@ -76,7 +72,7 @@ class DB():
#===============================================================================
# Cleanup / upkeep database
#===============================================================================
def cleanup_database (self, startTime, DAYS_TO_KEEP_EVENTS, PHOLUS_DAYS_DATA):
def cleanup_database (self, startTime, DAYS_TO_KEEP_EVENTS, PHOLUS_DAYS_DATA, HRS_TO_KEEP_NEWDEV, PLUGINS_KEEP_HIST):
"""
Cleaning out old records from the tables that don't need to keep all data.
"""
@@ -91,19 +87,32 @@ class DB():
order by Scan_Date desc limit 150)""")
mylog('verbose', ['[DB Cleanup] Optimize Database'])
# Cleanup Events
mylog('verbose', ['[DB Cleanup] Events: Delete all older than '+str(DAYS_TO_KEEP_EVENTS)+' days'])
self.sql.execute ("""DELETE FROM Events
WHERE eve_DateTime <= date('now', '-"+str(DAYS_TO_KEEP_EVENTS)+" day')""")
mylog('verbose', [f'[DB Cleanup] Events: Delete all older than {str(DAYS_TO_KEEP_EVENTS)} days (DAYS_TO_KEEP_EVENTS setting)'])
self.sql.execute (f"""DELETE FROM Events
WHERE eve_DateTime <= date('now', '-{str(DAYS_TO_KEEP_EVENTS)} day')""")
# Cleanup Plugin Events History
mylog('verbose', ['[DB Cleanup] Plugin Events History: Delete all older than '+str(DAYS_TO_KEEP_EVENTS)+' days'])
self.sql.execute ("""DELETE FROM Plugins_History
WHERE DateTimeChanged <= date('now', '-"+str(DAYS_TO_KEEP_EVENTS)+" day')""")
mylog('verbose', ['[DB Cleanup] Plugins_History: Delete all older than '+str(DAYS_TO_KEEP_EVENTS)+' days (DAYS_TO_KEEP_EVENTS setting)'])
self.sql.execute (f"""DELETE FROM Plugins_History
WHERE DateTimeChanged <= date('now', '{str(DAYS_TO_KEEP_EVENTS)} day')""")
# Trim Plugins_History entries to less than PLUGINS_KEEP_HIST setting
mylog('verbose', [f'[DB Cleanup] Plugins_History: Trim Plugins_History entries to less than {str(PLUGINS_KEEP_HIST)} (PLUGINS_KEEP_HIST setting)'])
self.sql.execute (f"""DELETE from Plugins_History where "Index" not in (
SELECT "Index" from Plugins_History
order by "Index" desc limit {str(PLUGINS_KEEP_HIST)})""")
# Cleanup Pholus_Scan
if PHOLUS_DAYS_DATA != 0:
mylog('verbose', ['[DB Cleanup] Pholus_Scan: Delete all older than ' + str(PHOLUS_DAYS_DATA) + ' days'])
# improvement possibility: keep at least N per mac
self.sql.execute ("""DELETE FROM Pholus_Scan
WHERE Time <= date('now', '-"+ str(PHOLUS_DAYS_DATA) +" day')""")
mylog('verbose', ['[DB Cleanup] Pholus_Scan: Delete all older than ' + str(PHOLUS_DAYS_DATA) + ' days (PHOLUS_DAYS_DATA setting)'])
# todo: improvement possibility: keep at least N per mac
self.sql.execute (f"""DELETE FROM Pholus_Scan
WHERE Time <= date('now', '-{str(PHOLUS_DAYS_DATA)} day')""")
# Cleanup New Devices
if HRS_TO_KEEP_NEWDEV != 0:
mylog('verbose', [f'[DB Cleanup] Devices: Delete all New Devices older than {str(HRS_TO_KEEP_NEWDEV)} hours (HRS_TO_KEEP_NEWDEV setting)'])
self.sql.execute (f"""DELETE FROM Devices
WHERE dev_NewDevice = 1 AND dev_FirstConnection < date('now', '+{str(HRS_TO_KEEP_NEWDEV)} hour')""")
# De-Dupe (de-duplicate - remove duplicate entries) from the Pholus_Scan table
mylog('verbose', ['[DB Cleanup] Pholus_Scan: Delete all duplicates'])
@@ -115,7 +124,7 @@ class DB():
AND Pholus_Scan.Record_Type = p2.Record_Type
);""")
# De-Dupe (de-duplicate - remove duplicate entries) from the Nmap_Scan table
mylog('verbose', [' Nmap_Scan: Delete all duplicates'])
mylog('verbose', ['[DB Cleanup] Nmap_Scan: Delete all duplicates'])
self.sql.execute ("""DELETE FROM Nmap_Scan
WHERE rowid > (
SELECT MIN(rowid) FROM Nmap_Scan p2
@@ -125,8 +134,9 @@ class DB():
AND Nmap_Scan.Service = p2.Service
);""")
# Shrink DB
mylog('verbose', [' Shrink Database'])
mylog('verbose', ['[DB Cleanup] Shrink Database'])
self.sql.execute ("VACUUM;")
self.commitDB()

View File

@@ -6,6 +6,7 @@ import subprocess
import conf
from helper import timeNow
from plugin import get_setting_value
from scanners.internet import check_IP_format, get_internet_IP
from logger import mylog, print_log
from mac_vendor import query_MAC_vendor
@@ -17,6 +18,8 @@ def save_scanned_devices (db, p_arpscan_devices, p_cycle_interval):
sql = db.sql #TO-DO
cycle = 1 # always 1, only one cycle supported
mylog('debug', ['[ARP Scan] Detected devices:', len(p_arpscan_devices)])
# Delete previous scan data
sql.execute ("DELETE FROM CurrentScan WHERE cur_ScanCycle = ?",
(cycle,))
@@ -190,17 +193,56 @@ def create_new_devices (db):
# arpscan - Create new devices
mylog('debug','[New Devices] 2 Create devices')
sql.execute ("""INSERT INTO Devices (dev_MAC, dev_name, dev_Vendor,
# default New Device values preparation
newDevColumns = """dev_AlertEvents,
dev_AlertDeviceDown,
dev_PresentLastScan,
dev_Archived,
dev_NewDevice,
dev_SkipRepeated,
dev_ScanCycle,
dev_Owner,
dev_DeviceType,
dev_Favorite,
dev_Group,
dev_Comments,
dev_LogEvents,
dev_Location,
dev_Network_Node_MAC_ADDR,
dev_Icon"""
newDevDefaults = f"""{get_setting_value('NEWDEV_dev_AlertEvents')},
{get_setting_value('NEWDEV_dev_AlertDeviceDown')},
{get_setting_value('NEWDEV_dev_PresentLastScan')},
{get_setting_value('NEWDEV_dev_Archived')},
{get_setting_value('NEWDEV_dev_NewDevice')},
{get_setting_value('NEWDEV_dev_SkipRepeated')},
{get_setting_value('NEWDEV_dev_ScanCycle')},
'{get_setting_value('NEWDEV_dev_Owner')}',
'{get_setting_value('NEWDEV_dev_DeviceType')}',
{get_setting_value('NEWDEV_dev_Favorite')},
'{get_setting_value('NEWDEV_dev_Group')}',
'{get_setting_value('NEWDEV_dev_Comments')}',
{get_setting_value('NEWDEV_dev_LogEvents')},
'{get_setting_value('NEWDEV_dev_Location')}',
'{get_setting_value('NEWDEV_dev_Network_Node_MAC_ADDR')}',
'{get_setting_value('NEWDEV_dev_Icon')}'
"""
sqlQuery = f"""INSERT INTO Devices (dev_MAC, dev_name, dev_Vendor,
dev_LastIP, dev_FirstConnection, dev_LastConnection,
dev_ScanCycle, dev_AlertEvents, dev_AlertDeviceDown,
dev_PresentLastScan)
{newDevColumns})
SELECT cur_MAC, '(unknown)', cur_Vendor, cur_IP, ?, ?,
1, 1, 0, 1
{newDevDefaults}
FROM CurrentScan
WHERE cur_ScanCycle = ?
AND NOT EXISTS (SELECT 1 FROM Devices
WHERE dev_MAC = cur_MAC) """,
(startTime, startTime, conf.cycle) )
WHERE dev_MAC = cur_MAC) """
mylog('debug',f'[New Devices] 2 Create devices SQL: {sqlQuery}')
sql.execute (sqlQuery, (startTime, startTime, conf.cycle) )
# Pi-hole - Insert events for new devices
# NOT STRICYLY NECESARY (Devices can be created through Current_Scan)
@@ -219,19 +261,24 @@ def create_new_devices (db):
# Pi-hole - Create New Devices
# Bugfix #2 - Pi-hole devices w/o IP
mylog('debug','[New Devices] 4 Pi-hole Create devices')
sql.execute ("""INSERT INTO Devices (dev_MAC, dev_name, dev_Vendor,
sqlQuery = f"""INSERT INTO Devices (dev_MAC, dev_name, dev_Vendor,
dev_LastIP, dev_FirstConnection, dev_LastConnection,
dev_ScanCycle, dev_AlertEvents, dev_AlertDeviceDown,
dev_PresentLastScan)
{newDevColumns})
SELECT PH_MAC, PH_Name, PH_Vendor, IFNULL (PH_IP,'-'),
?, ?, 1, 1, 0, 1
?, ?,
{newDevDefaults}
FROM PiHole_Network
WHERE NOT EXISTS (SELECT 1 FROM Devices
WHERE dev_MAC = PH_MAC) """,
(startTime, startTime) )
WHERE dev_MAC = PH_MAC) """
mylog('debug',f'[New Devices] 4 Create devices SQL: {sqlQuery}')
sql.execute (sqlQuery, (startTime, startTime) )
# DHCP Leases - Insert events for new devices
mylog('debug','[New Devices] 5 DHCP Leases Events')
sql.execute ("""INSERT INTO Events (eve_MAC, eve_IP, eve_DateTime,
eve_EventType, eve_AdditionalInfo,
eve_PendingAlertEmail)
@@ -243,16 +290,10 @@ def create_new_devices (db):
# DHCP Leases - Create New Devices
mylog('debug','[New Devices] 6 DHCP Leases Create devices')
# BUGFIX #23 - Duplicated MAC in DHCP.Leases
# TEST - Force Duplicated MAC
# sql.execute ("""INSERT INTO DHCP_Leases VALUES
# (1610700000, 'TEST1', '10.10.10.1', 'Test 1', '*')""")
# sql.execute ("""INSERT INTO DHCP_Leases VALUES
# (1610700000, 'TEST2', '10.10.10.2', 'Test 2', '*')""")
sql.execute ("""INSERT INTO Devices (dev_MAC, dev_name, dev_LastIP,
dev_Vendor, dev_FirstConnection, dev_LastConnection,
dev_ScanCycle, dev_AlertEvents, dev_AlertDeviceDown,
dev_PresentLastScan)
sqlQuery = f"""INSERT INTO Devices (dev_MAC, dev_name, dev_LastIP,
dev_Vendor, dev_FirstConnection, dev_LastConnection,
{newDevColumns})
SELECT DISTINCT DHCP_MAC,
(SELECT DHCP_Name FROM DHCP_Leases AS D2
WHERE D2.DHCP_MAC = D1.DHCP_MAC
@@ -260,22 +301,16 @@ def create_new_devices (db):
(SELECT DHCP_IP FROM DHCP_Leases AS D2
WHERE D2.DHCP_MAC = D1.DHCP_MAC
ORDER BY DHCP_DateTime DESC LIMIT 1),
'(unknown)', ?, ?, 1, 1, 0, 1
'(unknown)', ?, ?,
{newDevDefaults}
FROM DHCP_Leases AS D1
WHERE NOT EXISTS (SELECT 1 FROM Devices
WHERE dev_MAC = DHCP_MAC) """,
(startTime, startTime) )
WHERE dev_MAC = DHCP_MAC) """
mylog('debug',f'[New Devices] 6 Create devices SQL: {sqlQuery}')
sql.execute (sqlQuery, (startTime, startTime) )
# sql.execute ("""INSERT INTO Devices (dev_MAC, dev_name, dev_Vendor,
# dev_LastIP, dev_FirstConnection, dev_LastConnection,
# dev_ScanCycle, dev_AlertEvents, dev_AlertDeviceDown,
# dev_PresentLastScan)
# SELECT DHCP_MAC, DHCP_Name, '(unknown)', DHCP_IP, ?, ?,
# 1, 1, 0, 1
# FROM DHCP_Leases
# WHERE NOT EXISTS (SELECT 1 FROM Devices
# WHERE dev_MAC = DHCP_MAC) """,
# (startTime, startTime) )
mylog('debug','[New Devices] New Devices end')
db.commitDB()

View File

@@ -30,6 +30,8 @@ def timeNowTZ():
def updateState(db, newState):
# ?? Why is the state written to the DB?
# The state is written to the DB so the front-end can use the value to display the current state in the header of the app
# The Parameters DB table is used to communicate with the front end.
#sql = db.sql
@@ -63,6 +65,8 @@ def checkPermissionsOK():
dbR_access = (os.access(fullDbPath, os.R_OK))
dbW_access = (os.access(fullDbPath, os.W_OK))
mylog('none', ['\n'])
mylog('none', ['The container restarted (started). If this is unexpected check https://bit.ly/PiAlertDebug for troubleshooting tips.'])
mylog('none', ['\n'])
mylog('none', ['Permissions check (All should be True)'])
mylog('none', ['------------------------------------------------'])
@@ -194,6 +198,8 @@ def checkIPV4(ip):
#-------------------------------------------------------------------------------
def isNewVersion(newVersion: bool):
mylog('debug', [f"[Version check] New version available? {newVersion}"])
if newVersion == False:
f = open(pialertPath + '/front/buildtimestamp.txt', 'r')
@@ -218,7 +224,7 @@ def isNewVersion(newVersion: bool):
realeaseTimestamp = int(datetime.datetime.strptime(dateTimeStr, '%Y-%m-%dT%H:%M:%SZ').strftime('%s'))
if realeaseTimestamp > buildTimestamp + 600:
mylog('none', [" New version of the container available!"])
mylog('none', ["[Version check] New version of the container available!"])
newVersion = True
# updateState(db, 'Back_New_Version_Available', str(newVersionAvailable)) ## TO DO add this back in but avoid circular ref with database
@@ -302,18 +308,28 @@ def get_file_content(path):
return content
#-------------------------------------------------------------------------------
def write_file (pPath, pText):
# Write the text depending using the correct python version
if sys.version_info < (3, 0):
file = io.open (pPath , mode='w', encoding='utf-8')
file.write ( pText.decode('unicode_escape') )
file.close()
def write_file(pPath, pText):
# Convert pText to a string if it's a dictionary
if isinstance(pText, dict):
pText = json.dumps(pText)
# Convert pText to a string if it's a list
if isinstance(pText, list):
for item in pText:
write_file(pPath, item)
else:
file = open (pPath, 'w', encoding='utf-8')
if pText is None:
pText = ""
file.write (pText)
file.close()
# Write the text using the correct Python version
if sys.version_info < (3, 0):
file = io.open(pPath, mode='w', encoding='utf-8')
file.write(pText.decode('unicode_escape'))
file.close()
else:
file = open(pPath, 'w', encoding='utf-8')
if pText is None:
pText = ""
file.write(pText)
file.close()
#-------------------------------------------------------------------------------
class noti_struc:

View File

@@ -58,9 +58,6 @@ def importConfigs (db):
return
conf.lastImportedConfFile = os.path.getmtime(config_file)
mylog('debug', ['[Import Config] importing config file'])
conf.mySettings = [] # reset settings
@@ -72,18 +69,20 @@ def importConfigs (db):
# General
conf.ENABLE_ARPSCAN = ccd('ENABLE_ARPSCAN', True , c_d, 'Enable arpscan', 'boolean', '', 'General', ['run'])
conf.SCAN_SUBNETS = ccd('SCAN_SUBNETS', ['192.168.1.0/24 --interface=eth1', '192.168.1.0/24 --interface=eth0'] , c_d, 'Subnets to scan', 'subnets', '', 'General')
conf.LOG_LEVEL = ccd('LOG_LEVEL', 'verbose' , c_d, 'Log verboseness', 'selecttext', "['none', 'minimal', 'verbose', 'debug']", 'General')
conf.LOG_LEVEL = ccd('LOG_LEVEL', 'verbose' , c_d, 'Log verboseness', 'text.select', "['none', 'minimal', 'verbose', 'debug']", 'General')
conf.TIMEZONE = ccd('TIMEZONE', 'Europe/Berlin' , c_d, 'Time zone', 'text', '', 'General')
conf.ENABLE_PLUGINS = ccd('ENABLE_PLUGINS', True , c_d, 'Enable plugins', 'boolean', '', 'General')
conf.PLUGINS_KEEP_HIST = ccd('PLUGINS_KEEP_HIST', 10000 , c_d, 'Keep history entries', 'integer', '', 'General')
conf.PIALERT_WEB_PROTECTION = ccd('PIALERT_WEB_PROTECTION', False , c_d, 'Enable logon', 'boolean', '', 'General')
conf.PIALERT_WEB_PASSWORD = ccd('PIALERT_WEB_PASSWORD', '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92' , c_d, 'Logon password', 'readonly', '', 'General')
conf.INCLUDED_SECTIONS = ccd('INCLUDED_SECTIONS', ['internet', 'new_devices', 'down_devices', 'events', 'ports'] , c_d, 'Notify on', 'multiselect', "['internet', 'new_devices', 'down_devices', 'events', 'ports', 'plugins']", 'General')
conf.SCAN_CYCLE_MINUTES = ccd('SCAN_CYCLE_MINUTES', 5 , c_d, 'Scan cycle delay (m)', 'integer', '', 'General')
conf.DAYS_TO_KEEP_EVENTS = ccd('DAYS_TO_KEEP_EVENTS', 90 , c_d, 'Delete events days', 'integer', '', 'General')
conf.INCLUDED_SECTIONS = ccd('INCLUDED_SECTIONS', ['internet', 'new_devices', 'down_devices', 'events', 'ports'] , c_d, 'Notify on', 'text.multiselect', "['internet', 'new_devices', 'down_devices', 'events', 'ports', 'plugins']", 'General')
conf.SCAN_CYCLE_MINUTES = ccd('SCAN_CYCLE_MINUTES', 5 , c_d, 'Scan cycle delay (m)', 'integer', '', 'General')
conf.REPORT_DASHBOARD_URL = ccd('REPORT_DASHBOARD_URL', 'http://pi.alert/' , c_d, 'PiAlert URL', 'text', '', 'General')
conf.DIG_GET_IP_ARG = ccd('DIG_GET_IP_ARG', '-4 myip.opendns.com @resolver1.opendns.com' , c_d, 'DIG arguments', 'text', '', 'General')
conf.UI_LANG = ccd('UI_LANG', 'English' , c_d, 'Language Interface', 'selecttext', "['English', 'German', 'Spanish']", 'General')
conf.UI_PRESENCE = ccd('UI_PRESENCE', ['online', 'offline', 'archived'] , c_d, 'Include in presence', 'multiselect', "['online', 'offline', 'archived']", 'General')
conf.UI_LANG = ccd('UI_LANG', 'English' , c_d, 'Language Interface', 'text.select', "['English', 'German', 'Spanish']", 'General')
conf.UI_PRESENCE = ccd('UI_PRESENCE', ['online', 'offline', 'archived'] , c_d, 'Include in presence', 'text.multiselect', "['online', 'offline', 'archived']", 'General')
conf.DAYS_TO_KEEP_EVENTS = ccd('DAYS_TO_KEEP_EVENTS', 90 , c_d, 'Delete events days', 'integer', '', 'General')
conf.HRS_TO_KEEP_NEWDEV = ccd('HRS_TO_KEEP_NEWDEV', 0 , c_d, 'Keep new devices for', 'integer', "0", 'General')
# Email
conf.REPORT_MAIL = ccd('REPORT_MAIL', False , c_d, 'Enable email', 'boolean', '', 'Email', ['test'])
@@ -100,14 +99,15 @@ def importConfigs (db):
# Webhooks
conf.REPORT_WEBHOOK = ccd('REPORT_WEBHOOK', False , c_d, 'Enable Webhooks', 'boolean', '', 'Webhooks', ['test'])
conf.WEBHOOK_URL = ccd('WEBHOOK_URL', '' , c_d, 'Target URL', 'text', '', 'Webhooks')
conf.WEBHOOK_PAYLOAD = ccd('WEBHOOK_PAYLOAD', 'json' , c_d, 'Payload type', 'selecttext', "['json', 'html', 'text']", 'Webhooks')
conf.WEBHOOK_REQUEST_METHOD = ccd('WEBHOOK_REQUEST_METHOD', 'GET' , c_d, 'Req type', 'selecttext', "['GET', 'POST', 'PUT']", 'Webhooks')
conf.WEBHOOK_PAYLOAD = ccd('WEBHOOK_PAYLOAD', 'json' , c_d, 'Payload type', 'text.select', "['json', 'html', 'text']", 'Webhooks')
conf.WEBHOOK_REQUEST_METHOD = ccd('WEBHOOK_REQUEST_METHOD', 'GET' , c_d, 'Req type', 'text.select', "['GET', 'POST', 'PUT']", 'Webhooks')
conf.WEBHOOK_SIZE = ccd('WEBHOOK_SIZE', 1024 , c_d, 'Payload size', 'integer', '', 'Webhooks')
# Apprise
conf.REPORT_APPRISE = ccd('REPORT_APPRISE', False , c_d, 'Enable Apprise', 'boolean', '', 'Apprise', ['test'])
conf.APPRISE_HOST = ccd('APPRISE_HOST', '' , c_d, 'Apprise host URL', 'text', '', 'Apprise')
conf.APPRISE_URL = ccd('APPRISE_URL', '' , c_d, 'Apprise notification URL', 'text', '', 'Apprise')
conf.APPRISE_PAYLOAD = ccd('APPRISE_PAYLOAD', 'html' , c_d, 'Payload type', 'selecttext', "['html', 'text']", 'Apprise')
conf.APPRISE_PAYLOAD = ccd('APPRISE_PAYLOAD', 'html' , c_d, 'Payload type', 'text.select', "['html', 'text']", 'Apprise')
# NTFY
conf.REPORT_NTFY = ccd('REPORT_NTFY', False , c_d, 'Enable NTFY', 'boolean', '', 'NTFY', ['test'])
@@ -126,8 +126,8 @@ def importConfigs (db):
conf.MQTT_PORT = ccd('MQTT_PORT', 1883 , c_d, 'MQTT broker port', 'integer', '', 'MQTT')
conf.MQTT_USER = ccd('MQTT_USER', '' , c_d, 'MQTT user', 'text', '', 'MQTT')
conf.MQTT_PASSWORD = ccd('MQTT_PASSWORD', '' , c_d, 'MQTT password', 'password', '', 'MQTT')
conf.MQTT_QOS = ccd('MQTT_QOS', 0 , c_d, 'MQTT Quality of Service', 'selectinteger', "['0', '1', '2']", 'MQTT')
conf.MQTT_DELAY_SEC = ccd('MQTT_DELAY_SEC', 2 , c_d, 'MQTT delay', 'selectinteger', "['2', '3', '4', '5']", 'MQTT')
conf.MQTT_QOS = ccd('MQTT_QOS', 0 , c_d, 'MQTT Quality of Service', 'integer.select', "['0', '1', '2']", 'MQTT')
conf.MQTT_DELAY_SEC = ccd('MQTT_DELAY_SEC', 2 , c_d, 'MQTT delay', 'integer.select', "['2', '3', '4', '5']", 'MQTT')
# DynDNS
conf.DDNS_ACTIVE = ccd('DDNS_ACTIVE', False , c_d, 'Enable DynDNS', 'boolean', '', 'DynDNS')
@@ -144,7 +144,7 @@ def importConfigs (db):
conf.PHOLUS_ACTIVE = ccd('PHOLUS_ACTIVE', False , c_d, 'Enable Pholus scans', 'boolean', '', 'Pholus')
conf.PHOLUS_TIMEOUT = ccd('PHOLUS_TIMEOUT', 20 , c_d, 'Pholus timeout', 'integer', '', 'Pholus')
conf.PHOLUS_FORCE = ccd('PHOLUS_FORCE', False , c_d, 'Pholus force check', 'boolean', '', 'Pholus')
conf.PHOLUS_RUN = ccd('PHOLUS_RUN', 'once' , c_d, 'Pholus enable schedule', 'selecttext', "['none', 'once', 'schedule']", 'Pholus')
conf.PHOLUS_RUN = ccd('PHOLUS_RUN', 'once' , c_d, 'Pholus enable schedule', 'text.select', "['disabled', 'once', 'schedule']", 'Pholus')
conf.PHOLUS_RUN_TIMEOUT = ccd('PHOLUS_RUN_TIMEOUT', 600 , c_d, 'Pholus timeout schedule', 'integer', '', 'Pholus')
conf.PHOLUS_RUN_SCHD = ccd('PHOLUS_RUN_SCHD', '0 4 * * *' , c_d, 'Pholus schedule', 'text', '', 'Pholus')
conf.PHOLUS_DAYS_DATA = ccd('PHOLUS_DAYS_DATA', 0 , c_d, 'Pholus keep days', 'integer', '', 'Pholus')
@@ -152,7 +152,7 @@ def importConfigs (db):
# Nmap
conf.NMAP_ACTIVE = ccd('NMAP_ACTIVE', True , c_d, 'Enable Nmap scans', 'boolean', '', 'Nmap')
conf.NMAP_TIMEOUT = ccd('NMAP_TIMEOUT', 150 , c_d, 'Nmap timeout', 'integer', '', 'Nmap')
conf.NMAP_RUN = ccd('NMAP_RUN', 'none' , c_d, 'Nmap enable schedule', 'selecttext', "['none', 'once', 'schedule']", 'Nmap')
conf.NMAP_RUN = ccd('NMAP_RUN', 'disabled' , c_d, 'Nmap enable schedule', 'text.select', "['disabled', 'once', 'schedule']", 'Nmap')
conf.NMAP_RUN_SCHD = ccd('NMAP_RUN_SCHD', '0 2 * * *' , c_d, 'Nmap schedule', 'text', '', 'Nmap')
conf.NMAP_ARGS = ccd('NMAP_ARGS', '-p -10000' , c_d, 'Nmap custom arguments', 'text', '', 'Nmap')
@@ -180,44 +180,43 @@ def importConfigs (db):
# Plugins START
# -----------------
if conf.ENABLE_PLUGINS:
conf.plugins = get_plugins_configs()
conf.plugins = get_plugins_configs()
mylog('none', ['[Config] Plugins: Number of dynamically loaded plugins: ', len(conf.plugins)])
mylog('none', ['[Config] Plugins: Number of dynamically loaded plugins: ', len(conf.plugins)])
# handle plugins
for plugin in conf.plugins:
pref = plugin["unique_prefix"]
print_plugin_info(plugin, ['display_name','description'])
# handle plugins
for plugin in conf.plugins:
pref = plugin["unique_prefix"]
print_plugin_info(plugin, ['display_name','description'])
# if plugin["enabled"] == 'true':
# collect plugin level language strings
collect_lang_strings(db, plugin, pref)
for set in plugin["settings"]:
setFunction = set["function"]
# Setting code name / key
key = pref + "_" + setFunction
# if plugin["enabled"] == 'true':
# collect plugin level language strings
collect_lang_strings(db, plugin, pref)
for set in plugin["settings"]:
setFunction = set["function"]
# Setting code name / key
key = pref + "_" + setFunction
v = ccd(key, set["default_value"], c_d, set["name"][0]["string"], set["type"] , str(set["options"]), pref)
v = ccd(key, set["default_value"], c_d, set["name"][0]["string"], set["type"] , str(set["options"]), pref)
# Save the user defined value into the object
set["value"] = v
# Save the user defined value into the object
set["value"] = v
# Setup schedules
if setFunction == 'RUN_SCHD':
newSchedule = Cron(v).schedule(start_date=datetime.datetime.now(conf.tz))
conf.mySchedules.append(schedule_class(pref, newSchedule, newSchedule.next(), False))
# Setup schedules
if setFunction == 'RUN_SCHD':
newSchedule = Cron(v).schedule(start_date=datetime.datetime.now(conf.tz))
conf.mySchedules.append(schedule_class(pref, newSchedule, newSchedule.next(), False))
# Collect settings related language strings
collect_lang_strings(db, set, pref + "_" + set["function"])
# Collect settings related language strings
collect_lang_strings(db, set, pref + "_" + set["function"])
conf.plugins_once_run = False
conf.plugins_once_run = False
# -----------------
# Plugins END
# write_file(self.path, json.dumps(self.jsonData))

View File

@@ -71,6 +71,9 @@ def print_log (pText):
#-------------------------------------------------------------------------------
# textchars = bytearray({7,8,9,10,12,13,27} | set(range(0x20, 0x100)) - {0x7f})
# is_binary_string = lambda bytes: bool(bytes.translate(None, textchars))
def append_file_binary (pPath, input):
file = open (pPath, 'ab')
file.write (input)

View File

@@ -1,5 +1,6 @@
import subprocess
import conf
from const import pialertPath, vendorsDB
from helper import timeNow, updateState
@@ -22,13 +23,18 @@ def update_devices_MAC_vendors (db, pArg = ''):
mylog('verbose', [' Updating vendors DB (iab & oui)'])
update_args = ['sh', pialertPath + '/update_vendors.sh', pArg]
try:
# Execute command
if conf.LOG_LEVEL == 'debug':
# try runnning a subprocess
update_output = subprocess.check_output (update_args)
except subprocess.CalledProcessError as e:
# An error occured, handle it
mylog('none', [' FAILED: Updating vendors DB, set LOG_LEVEL=debug for more info'])
mylog('none', [e.output])
else:
try:
# try runnning a subprocess safely
update_output = subprocess.check_output (update_args)
except subprocess.CalledProcessError as e:
# An error occured, handle it
mylog('none', [' FAILED: Updating vendors DB, set LOG_LEVEL=debug for more info'])
mylog('none', [e.output])
# Initialize variables
recordsToUpdate = []
@@ -82,14 +88,19 @@ def query_MAC_vendor (pMAC):
# Search vendor in HW Vendors DB
mac = mac[0:6]
grep_args = ['grep', '-i', mac, vendorsDB]
# Execute command
try:
if conf.LOG_LEVEL == 'debug':
# try runnning a subprocess
grep_output = subprocess.check_output (grep_args)
except subprocess.CalledProcessError as e:
# An error occured, handle it
mylog('none', ["[Mac Vendor Check] Error: ", e.output])
grep_output = " There was an error, check logs for details"
else:
try:
# try runnning a subprocess
grep_output = subprocess.check_output (grep_args)
except subprocess.CalledProcessError as e:
# An error occured, handle it
mylog('none', ["[Mac Vendor Check] Error: ", e.output])
grep_output = " There was an error, check logs for details"
# Return Vendor
vendor = grep_output[7:]

View File

@@ -77,6 +77,8 @@ def process_scan (db, arpscan_devices):
# Load current scan data
mylog('verbose','[Process Scan] Processing scan results')
save_scanned_devices (db, arpscan_devices, cycle_interval)
db.commitDB()
# Print stats
mylog('none','[Process Scan] Print Stats')
@@ -275,7 +277,7 @@ def insert_events (db):
eve_EventType, eve_AdditionalInfo,
eve_PendingAlertEmail)
SELECT dev_MAC, dev_LastIP, ?, 'Device Down', '', 1
FROM Devices
FROM Devices
WHERE dev_AlertDeviceDown = 1
AND dev_PresentLastScan = 1
AND dev_ScanCycle = ?

View File

@@ -281,8 +281,8 @@ def plugin_param_from_glob_set(globalSetting):
setTyp = globalSetting[3] # setting type
noConversion = ['text', 'integer', 'boolean', 'password', 'readonly', 'selectinteger', 'selecttext' ]
arrayConversion = ['multiselect', 'list']
noConversion = ['text', 'integer', 'boolean', 'password', 'readonly', 'integer.select', 'text.select', 'integer.checkbox' ]
arrayConversion = ['text.multiselect', 'list']
if setTyp in noConversion:
return setVal

View File

@@ -18,13 +18,47 @@ def check_config():
def send (msg: noti_struc):
# limit = 1024 * 1024 # 1MB limit (1024 bytes * 1024 bytes = 1MB)
limit = conf.WEBHOOK_SIZE
# use data type based on specified payload type
if conf.WEBHOOK_PAYLOAD == 'json':
payloadData = msg.json
if conf.WEBHOOK_PAYLOAD == 'html':
payloadData = msg.html
if conf.WEBHOOK_PAYLOAD == 'text':
payloadData = to_text(msg.json) # TO DO can we just send msg.text?
# 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_data = msg.json
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 conf.WEBHOOK_PAYLOAD == 'html':
if len(msg.html) > limit:
payloadData = msg.html[:limit] + " <h1> (text was truncated)</h1>"
else:
payloadData = msg.html
if conf.WEBHOOK_PAYLOAD == 'text':
if len(msg.text) > limit:
payloadData = msg.text[:limit] + " (text was truncated)"
else:
payloadData = msg.text
# Define slack-compatible payload
_json_payload = { "text": payloadData } if conf.WEBHOOK_PAYLOAD == 'text' else {
@@ -41,6 +75,7 @@ def send (msg: noti_struc):
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(conf.WEBHOOK_URL.startswith('https://discord.com/api/webhooks/') and not conf.WEBHOOK_URL.endswith("/slack")):
_WEBHOOK_URL = f"{conf.WEBHOOK_URL}/slack"
curlParams = ["curl","-i","-H", "Content-Type:application/json" ,"-d", json.dumps(_json_payload), _WEBHOOK_URL]
@@ -48,51 +83,20 @@ def send (msg: noti_struc):
_WEBHOOK_URL = conf.WEBHOOK_URL
curlParams = ["curl","-i","-X", conf.WEBHOOK_REQUEST_METHOD ,"-H", "Content-Type:application/json" ,"-d", json.dumps(_json_payload), _WEBHOOK_URL]
# execute CURL call
try:
# try runnning a subprocess
# Execute CURL call
mylog('debug', ['[send_webhook] curlParams: ', curlParams])
p = subprocess.Popen(curlParams, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
result = subprocess.run(curlParams, capture_output=True, text=True)
stdout, stderr = p.communicate()
stdout = result.stdout
stderr = result.stderr
# Write stdout and stderr into .log files for debugging if needed
mylog('debug', ['[send_webhook] stdout: ', stdout])
mylog('debug', ['[send_webhook] stderr: ', stderr])
# logResult(stdout, stderr) # TO-DO should be changed to mylog
# write stdout and stderr into .log files for debugging if needed
logResult (stdout, stderr) # TO-DO should be changed to mylog
except subprocess.CalledProcessError as e:
# An error occured, handle it
mylog('none', ['[send_webhook]', e.output])
# An error occurred, handle it
mylog('none', ['[send_webhook] Error: ', e.output])
#-------------------------------------------------------------------------------
def to_text(_json):
payloadData = ""
if len(_json['internet']) > 0 and 'internet' in conf.INCLUDED_SECTIONS:
payloadData += "INTERNET\n"
for event in _json['internet']:
payloadData += event[3] + ' on ' + event[2] + '. ' + event[4] + '. New address:' + event[1] + '\n'
if len(_json['new_devices']) > 0 and 'new_devices' in conf.INCLUDED_SECTIONS:
payloadData += "NEW DEVICES:\n"
for event in _json['new_devices']:
if event[4] is None:
event[4] = event[11]
payloadData += event[1] + ' - ' + event[4] + '\n'
if len(_json['down_devices']) > 0 and 'down_devices' in conf.INCLUDED_SECTIONS:
write_file (logPath + '/down_devices_example.log', _json['down_devices'])
payloadData += 'DOWN DEVICES:\n'
for event in _json['down_devices']:
if event[4] is None:
event[4] = event[11]
payloadData += event[1] + ' - ' + event[4] + '\n'
if len(_json['events']) > 0 and 'events' in conf.INCLUDED_SECTIONS:
payloadData += "EVENTS:\n"
for event in _json['events']:
if event[8] != "Internet":
payloadData += event[8] + " on " + event[1] + " " + event[3] + " at " + event[2] + "\n"
return payloadData

View File

@@ -271,7 +271,7 @@ def send_notifications (db):
msg = noti_struc(json_final, mail_text, mail_html)
mylog('info', ['[Notification] Udateing API files'])
mylog('info', ['[Notification] Udating API files'])
send_api()
if conf.REPORT_MAIL and check_config('email'):

View File

@@ -1,7 +1,10 @@
import re
import subprocess
import conf
from logger import mylog
from helper import write_file
from const import logPath
#-------------------------------------------------------------------------------
def execute_arpscan (userSubnets):
@@ -10,8 +13,10 @@ def execute_arpscan (userSubnets):
arpscan_output = ""
# scan each interface
for interface in userSubnets :
index = 0
for interface in userSubnets :
arpscan_output += execute_arpscan_on_interface (interface)
index += 1
# Search IP + MAC + Vendor as regular expresion
re_ip = r'(?P<ip>((2[0-5]|1[0-9]|[0-9])?[0-9]\.){3}((2[0-5]|1[0-9]|[0-9])?[0-9]))'
@@ -22,6 +27,8 @@ def execute_arpscan (userSubnets):
# Create Userdict of devices
devices_list = [device.groupdict()
for device in re.finditer (re_pattern, arpscan_output)]
mylog('debug', ['[ARP Scan] Found: Devices including duplicates ', len(devices_list) ])
# Delete duplicate MAC
unique_mac = []
@@ -33,7 +40,8 @@ def execute_arpscan (userSubnets):
unique_devices.append(device)
# return list
mylog('debug', ['[ARP Scan] Completed found ', len(unique_devices) ,' devices ' ])
mylog('debug', ['[ARP Scan] Found: Devices without duplicates ', len(unique_devices) ])
return unique_devices
#-------------------------------------------------------------------------------
@@ -45,13 +53,22 @@ def execute_arpscan_on_interface (interface):
arpscan_args = ['sudo', 'arp-scan', '--ignoredups', '--retry=6'] + subnets
# Execute command
try:
if conf.LOG_LEVEL == 'debug':
# try runnning a subprocess
result = subprocess.check_output (arpscan_args, universal_newlines=True)
except subprocess.CalledProcessError as e:
# An error occured, handle it
mylog('none', ['[ARP Scan]', e.output])
result = ""
else:
try:
# try runnning a subprocess safely
result = subprocess.check_output (arpscan_args, universal_newlines=True)
except subprocess.CalledProcessError as e:
# An error occured, handle it
error_type = type(e).__name__ # Capture the error type
mylog('none', [f'[ARP Scan] Error type : {error_type}'])
mylog('none', [f'[ARP Scan] Set LOG_LEVEL=debug for more details'])
mylog('none', [f'[ARP Scan] Error output: {e.output}'])
result = ""
mylog('debug', ['[ARP Scan] on Interface Completed with results: ', result])
return result

View File

@@ -157,7 +157,8 @@ def resolve_device_name_pholus (pMAC, pIP, allRes):
# _esphomelib._tcp.local. PTR Class:IN "ceiling-light-1._esphomelib._tcp.local."
for i in pholusMatchesIndexes:
if checkIPV4(allRes[i]['IP_v4_or_v6']) and 'PTR Class:IN' in allRes[i]["Value"]:
return cleanResult(allRes[i]["Value"].split('"')[1])
if allRes[i]["Value"] and len(allRes[i]["Value"].split('"')) > 1:
return cleanResult(allRes[i]["Value"].split('"')[1])
return -1