Compare commits

..

120 Commits

Author SHA1 Message Date
Hosted Weblate
b4001f54bb Merge branch 'origin/main' into Weblate. 2024-03-18 16:01:48 +01:00
gallegonovato
47d1740fdd Translated using Weblate (Spanish)
Currently translated at 100.0% (648 of 648 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2024-03-18 16:01:46 +01:00
github-actions[bot]
03d1e0e097 [🤖Automation] Update README with sponsors information 2024-03-18 11:53:42 +00:00
Jokob-sk
08ee4adddd Better first-load handling 🔄 2024-03-18 22:44:16 +11:00
Jokob-sk
1a221fabc9 Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-03-18 22:02:41 +11:00
Jokob-sk
f412ca0636 Restart bug & docs 🩹 2024-03-18 22:02:29 +11:00
Anonymous
6beb2584e5 Translated using Weblate (Spanish)
Currently translated at 99.6% (646 of 648 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2024-03-17 15:18:08 +01:00
Anonymous
8419750bdd Translated using Weblate (German)
Currently translated at 89.5% (580 of 648 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/de/
2024-03-17 15:18:08 +01:00
Hosted Weblate
9364cea706 Merge branch 'origin/main' into Weblate. 2024-03-17 15:01:59 +01:00
gallegonovato
6ceff80ec5 Translated using Weblate (Spanish)
Currently translated at 100.0% (648 of 648 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2024-03-17 15:01:54 +01:00
github-actions[bot]
cc9e4c722a [🤖Automation] Update README with sponsors information 2024-03-17 11:53:52 +00:00
Jokob-sk
5e687e1bdb Netw root node fixes 🩹 2024-03-17 10:52:28 +11:00
Jokob-sk
d955e058e1 Multi edit ⚒ 2024-03-17 10:27:31 +11:00
Jokob-sk
0615611a49 UI tweaks 🩹 2024-03-17 09:41:16 +11:00
jokob-sk
59243813a8 Merge pull request #591 from Schlump/main
Add Pushover device support - thanks @Schlump 🙏
2024-03-17 08:23:32 +11:00
Schlump
195206c699 Update pushover.py 2024-03-16 15:34:22 +01:00
Schlump
2f170fb156 Update config.json 2024-03-16 15:34:10 +01:00
github-actions[bot]
e7764324dc [🤖Automation] Update README with sponsors information 2024-03-16 11:53:51 +00:00
Anonymous
958444984a Translated using Weblate (Spanish)
Currently translated at 98.9% (641 of 648 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2024-03-16 01:58:21 +01:00
Anonymous
594af4903e Translated using Weblate (German)
Currently translated at 89.5% (580 of 648 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/de/
2024-03-16 01:58:21 +01:00
Jokob-sk
dd4de7c5a3 UI tweaks 🩹 2024-03-16 11:54:37 +11:00
Jokob-sk
1fa49a7730 cache fixes 🩹 2024-03-16 11:24:24 +11:00
Jokob-sk
7c70d435e4 menu css fixes 🩹 2024-03-16 10:57:15 +11:00
Jokob-sk
2e159635c6 menuitem Integrations + docs 📚 2024-03-16 10:51:05 +11:00
Jokob-sk
23aa1e4e85 multi-edit floating, no-cache headers 🔘 2024-03-16 10:40:18 +11:00
Jokob-sk
a99dbaef78 sysinfo improvements 2024-03-16 09:42:21 +11:00
github-actions[bot]
06a1fa3512 [🤖Automation] Update README with sponsors information 2024-03-15 11:54:04 +00:00
github-actions[bot]
e2270b4439 [🤖Automation] Update README with sponsors information 2024-03-14 11:53:37 +00:00
github-actions[bot]
4c790c6ff2 [🤖Automation] Update README with sponsors information 2024-03-13 11:53:50 +00:00
github-actions[bot]
07432daa28 [🤖Automation] Update README with sponsors information 2024-03-12 11:53:38 +00:00
Jokob-sk
54901be437 Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-03-11 23:15:21 +11:00
Jokob-sk
fb1e73d7d2 cleanup + drpdown fixes 2024-03-11 23:15:15 +11:00
github-actions[bot]
1401a24533 [🤖Automation] Update README with sponsors information 2024-03-11 11:53:39 +00:00
Jokob-sk
27ae11c1bc cleanup + log fixes 2024-03-11 07:57:49 +11:00
Jokob-sk
8817f8d0e9 Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-03-10 23:07:04 +11:00
Jokob-sk
4f9e8c5ecd cleanup + fixes 2024-03-10 23:06:43 +11:00
github-actions[bot]
c201e98f5b [🤖Automation] Update README with sponsors information 2024-03-10 11:53:36 +00:00
Jokob-sk
7cd76178b1 cleanup + fixes 2024-03-10 22:48:49 +11:00
Jokob-sk
bcf4144364 cleanup + fixes 2024-03-10 22:41:33 +11:00
Jokob-sk
78352f77b7 cleanup + fixes 2024-03-10 22:21:43 +11:00
Jokob-sk
e38d2f9055 dynamic dropdown support in FE - core app feature 💠 2024-03-10 21:50:04 +11:00
Hosted Weblate
a66df76f74 Merge branch 'origin/main' into Weblate. 2024-03-10 00:01:57 +01:00
gallegonovato
a6d3c92d2a Translated using Weblate (Spanish)
Currently translated at 100.0% (643 of 643 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2024-03-10 00:01:55 +01:00
github-actions[bot]
9720a15e91 [🤖Automation] Update README with sponsors information 2024-03-09 11:53:39 +00:00
Jokob-sk
6a0033da75 lang updates 🌎 2024-03-09 08:27:39 +11:00
Jokob-sk
6fcdaf8843 Weblate 2024-03-09 08:22:35 +11:00
Anonymous
97fc553278 Translated using Weblate (Norwegian Bokmål)
Currently translated at 1.7% (11 of 646 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/nb_NO/
2024-03-08 22:19:18 +01:00
Anonymous
bb57080b06 Translated using Weblate (Russian)
Currently translated at 10.8% (70 of 646 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ru/
2024-03-08 22:17:41 +01:00
Anonymous
1310a4b751 Translated using Weblate (French)
Currently translated at 44.2% (286 of 646 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/
2024-03-08 22:17:41 +01:00
Anonymous
1c7e729036 Translated using Weblate (Spanish)
Currently translated at 99.5% (643 of 646 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2024-03-08 22:17:41 +01:00
Anonymous
d7af580488 Translated using Weblate (German)
Currently translated at 90.4% (584 of 646 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/de/
2024-03-08 22:17:40 +01:00
Jokob-sk
37e163e883 lang updates 🌎 2024-03-09 08:17:03 +11:00
Jokob-sk
785f7c03bf Menu updates🧭 2024-03-09 08:04:14 +11:00
github-actions[bot]
849c39d75d [🤖Automation] Update README with sponsors information 2024-03-08 11:54:03 +00:00
github-actions[bot]
c57e3e7557 [🤖Automation] Update README with sponsors information 2024-03-07 11:53:46 +00:00
github-actions[bot]
ddc747e192 [🤖Automation] Update README with sponsors information 2024-03-06 11:53:47 +00:00
Jokob-sk
92801d6ddc Menu updates🧭 2024-03-05 23:12:17 +11:00
github-actions[bot]
b6c464be6d [🤖Automation] Update README with sponsors information 2024-03-05 11:53:45 +00:00
Jokob-sk
be81668d6d Smooth scrolling 2024-03-05 16:17:52 +11:00
Jokob-sk
8e85abfda4 Paho MQTT Version slection support + JS/CSS fixes #580 🩹 2024-03-05 09:34:13 +11:00
github-actions[bot]
5559194617 [🤖Automation] Update README with sponsors information 2024-03-04 12:13:02 +00:00
github-actions[bot]
1cc5cd56f8 [🤖Automation] Update README with sponsors information 2024-03-03 11:53:52 +00:00
jokob-sk
9d4759898d Merge pull request #585 from vladaurosh/main
Adding lsblk package
2024-03-03 14:52:22 +11:00
root
178ff30938 Adding lsblk package 2024-03-03 03:46:27 +00:00
Jokob-sk
a44575926f caching fixes 🩹 2024-03-03 11:38:27 +11:00
Hosted Weblate
6a367c826e Merge branch 'origin/main' into Weblate. 2024-03-02 13:02:02 +01:00
gallegonovato
83336d3289 Translated using Weblate (Spanish)
Currently translated at 100.0% (644 of 644 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2024-03-02 13:02:00 +01:00
github-actions[bot]
12ba6fbdad [🤖Automation] Update README with sponsors information 2024-03-02 11:53:54 +00:00
Jokob-sk
17a4656c41 small fixes 🩹 #582 2024-03-02 14:20:12 +11:00
jokob-sk
7ef3fe5ac0 Merge pull request #583 from vladaurosh/main
Removing unneeded package, small tweaks
2024-03-02 13:59:15 +11:00
root
06c7ffa39e Fixing hardcoded path 2024-03-02 02:08:40 +00:00
root
9ac0163f20 removing unneeded package, small tweaks 2024-03-02 01:41:03 +00:00
Jokob-sk
0126e448cc alpine re-base cleanup 🧹 2024-03-02 10:29:45 +11:00
Jokob-sk
2362622cd0 alpine re-base cleanup 🧹 2024-03-02 10:06:32 +11:00
Jokob-sk
ca2df744df alpine re-base 2024-03-02 08:58:38 +11:00
jokob-sk
9420c41e7c Merge pull request #581 from vladaurosh/main
Adding support for alpine-based image - thanks to @vladaurosh 🙏
2024-03-02 08:30:14 +11:00
github-actions[bot]
a015466c7f [🤖Automation] Update README with sponsors information 2024-03-01 11:53:37 +00:00
root
89f2c28046 Adding support for alpine based image 2024-02-29 21:56:55 +00:00
github-actions[bot]
57d0680b6a [🤖Automation] Update README with sponsors information 2024-02-29 11:53:47 +00:00
Jokob-sk
ddd405f379 Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-02-29 21:42:10 +11:00
Jokob-sk
3c38909b57 Multi edit CSS 2024-02-29 21:41:45 +11:00
github-actions[bot]
e830d1718e [🤖Automation] Update README with sponsors information 2024-02-28 11:53:39 +00:00
Jokob-sk
0ab78ffab7 merge 2024-02-28 21:12:10 +11:00
Jokob-sk
9685784452 Docs, Mass-delete 📚 2024-02-28 21:03:27 +11:00
Hosted Weblate
2a9085151f Merge branch 'origin/main' into Weblate. 2024-02-27 16:02:48 +01:00
Anonymous
c6fb35838a Translated using Weblate (German)
Currently translated at 91.2% (585 of 641 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/de/
2024-02-27 16:02:42 +01:00
github-actions[bot]
588ddf3cb3 [🤖Automation] Update README with sponsors information 2024-02-27 11:53:36 +00:00
Jokob-sk
84f96d72c8 Docs 📚 2024-02-27 08:07:11 +11:00
Jokob-sk
ed2ba9a435 Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-02-27 08:04:04 +11:00
Jokob-sk
265d313719 mark TIMEOUT log entry as ERROR 2024-02-27 08:03:58 +11:00
Hosted Weblate
21ebc55335 Merge branch 'origin/main' into Weblate. 2024-02-26 16:02:00 +01:00
gallegonovato
a7bfc6f6f6 Translated using Weblate (Spanish)
Currently translated at 100.0% (641 of 641 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/es/
2024-02-26 16:01:58 +01:00
github-actions[bot]
369fc44183 [🤖Automation] Update README with sponsors information 2024-02-26 11:53:58 +00:00
github-actions[bot]
6a2a56e059 [🤖Automation] Update README with sponsors information 2024-02-25 11:53:44 +00:00
Jokob-sk
b0008ebd3f Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-02-25 09:45:34 +11:00
Jokob-sk
c624bfeae0 Docs 📚 2024-02-25 09:44:41 +11:00
github-actions[bot]
624d7499a5 [🤖Automation] Update README with sponsors information 2024-02-24 11:53:56 +00:00
Jokob-sk
4f5fbb1316 Multi-edit ✏ #571 2024-02-24 13:30:18 +11:00
github-actions[bot]
7d715493a6 [🤖Automation] Update README with sponsors information 2024-02-23 11:53:55 +00:00
github-actions[bot]
0c92bf8d0a [🤖Automation] Update README with sponsors information 2024-02-22 11:53:38 +00:00
github-actions[bot]
ea51b93263 [🤖Automation] Update README with sponsors information 2024-02-21 11:53:55 +00:00
github-actions[bot]
539556e01d [🤖Automation] Update README with sponsors information 2024-02-20 11:53:38 +00:00
Hosted Weblate
43e9fc324f Merge branch 'origin/main' into Weblate. 2024-02-20 10:19:54 +01:00
Safeguard
e6e4620e13 Translated using Weblate (Russian)
Currently translated at 11.1% (71 of 636 strings)

Translation: PiAlert/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ru/
2024-02-20 10:19:53 +01:00
github-actions[bot]
6963e0b507 [🤖Automation] Update README with sponsors information 2024-02-19 11:53:40 +00:00
Jokob-sk
460f8038f2 weblate 2024-02-19 21:37:45 +11:00
Jokob-sk
ee2e228e15 Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-02-19 07:36:19 +11:00
Jokob-sk
15ab54f5d5 Hide labels in Online Presence #569 2024-02-19 07:36:03 +11:00
github-actions[bot]
2226b9ff39 [🤖Automation] Update README with sponsors information 2024-02-18 11:53:34 +00:00
Jokob-sk
d4b701653e PAHO MQTT API v2 2024-02-18 11:38:56 +11:00
Jokob-sk
34f5658516 ALWAYS_FRESH_INSTALL fix 2024-02-18 09:48:22 +11:00
Jokob-sk
ea7dfa832d docs 📚 2024-02-18 09:22:14 +11:00
Jokob-sk
9bbd549d93 Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-02-18 08:28:13 +11:00
Jokob-sk
7424cf4645 docs 📚 + work on #569 2024-02-18 08:28:08 +11:00
github-actions[bot]
19f767a887 [🤖Automation] Update README with sponsors information 2024-02-17 11:53:36 +00:00
Jokob-sk
57d9024ed3 BACKUPS.md docs 📚 2024-02-17 10:08:00 +11:00
Jokob-sk
87dd1cdf2d ALWAYS_FRESH_INSTALL variable 2024-02-17 08:51:10 +11:00
Jokob-sk
fdf381d565 Merge branch 'main' of https://github.com/jokob-sk/Pi.Alert 2024-02-17 07:26:15 +11:00
Jokob-sk
20e29ecd15 Empty Devices error #568🩹 2024-02-17 07:25:42 +11:00
github-actions[bot]
a6ce702487 [🤖Automation] Update README with sponsors information 2024-02-16 11:53:42 +00:00
76 changed files with 3277 additions and 662 deletions

View File

@@ -5,9 +5,16 @@
.gitignore
docker-compose.yml
Dockerfile
Dockerfile.debian
dockerfiles/LICENSE
dockerfiles/README.md
dockerfiles/README_ES.md
docs
LICENSE.txt
README.md
CONTRIBUTING
FUNDING.yml
config/.gitignore
db/.gitignore
pialert/README.md
pialert/README_ES.md

View File

@@ -13,7 +13,7 @@ on:
release:
types: [published]
tags:
- '*.*.*'
- '*.[1-9]+[0-9]?.[1-9]+*'
jobs:
docker:
runs-on: ubuntu-latest

View File

@@ -1,50 +1,53 @@
FROM debian:bookworm-slim
FROM alpine:3.19 as builder
# default UID and GID
ENV USER=pi USER_ID=1000 USER_GID=1000 PORT=20211
#TZ=Europe/London
ARG INSTALL_DIR=/home/pi
ENV PYTHONUNBUFFERED 1
# Todo, figure out why using a workdir instead of full paths don't work
# Todo, do we still need all these packages? I can already see sudo which isn't needed
RUN apk add --no-cache bash python3 \
&& python -m venv /opt/venv
RUN apt-get update
RUN apt-get install sudo -y
# Enable venv
ENV PATH="/opt/venv/bin:$PATH"
COPY . ${INSTALL_DIR}/pialert/
# create pi user and group
# add root and www-data to pi group so they can r/w files and db
RUN groupadd --gid "${USER_GID}" "${USER}" && \
useradd \
--uid ${USER_ID} \
--gid ${USER_GID} \
--create-home \
--shell /bin/bash \
${USER} && \
usermod -a -G ${USER_GID} root && \
usermod -a -G ${USER_GID} www-data
RUN pip install requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet \
&& bash -c "find ${INSTALL_DIR} -type d -exec chmod 750 {} \;" \
&& bash -c "find ${INSTALL_DIR} -type f -exec chmod 640 {} \;" \
&& bash -c "find ${INSTALL_DIR} -type f \( -name '*.sh' -o -name '*.py' -o -name 'pialert-cli' -o -name 'speedtest-cli' \) -exec chmod 750 {} \;"
COPY --chmod=775 --chown=${USER_ID}:${USER_GID} . /home/pi/pialert/
# second stage
FROM alpine:3.19 as runner
ARG INSTALL_DIR=/home/pi
COPY --from=builder /opt/venv /opt/venv
# Enable venv
ENV PATH="/opt/venv/bin:$PATH"
# default port and listen address
ENV PORT=20211 LISTEN_ADDR=0.0.0.0
# needed for s6-overlay
ENV S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0
# ❗ IMPORTANT - if you modify this file modify the /install/install_dependecies.sh file as well ❗
RUN apt-get install -y \
tini snmp ca-certificates curl libwww-perl arp-scan perl apt-utils cron sudo \
nginx-light php php-cgi php-fpm php-sqlite3 php-curl sqlite3 dnsutils net-tools \
python3 iproute2 nmap python3-pip zip systemctl usbutils traceroute
RUN apk update --no-cache \
&& apk add --no-cache bash zip lsblk gettext-envsubst sudo mtr s6-overlay \
&& apk add --no-cache curl arp-scan iproute2 iproute2-ss nmap traceroute net-tools net-snmp-tools bind-tools awake ca-certificates \
&& apk add --no-cache sqlite php82 php82-fpm php82-cgi php82-curl php82-sqlite3 php82-session \
&& apk add --no-cache python3 nginx \
&& ln -s /usr/bin/awake /usr/bin/wakeonlan \
&& bash -c "install -d -m 750 -o nginx -g www-data ${INSTALL_DIR} ${INSTALL_DIR}/pialert" \
&& rm -f /etc/nginx/http.d/default.conf
# Alternate dependencies
RUN apt-get install nginx nginx-core mtr php-fpm php8.2-fpm php-cli php8.2 php8.2-sqlite3 -y
RUN phpenmod -v 8.2 sqlite3
COPY --from=builder --chown=nginx:www-data ${INSTALL_DIR}/pialert/ ${INSTALL_DIR}/pialert/
# Setup virtual python environment and use pip3 to install packages
RUN apt-get install -y python3-venv
RUN python3 -m venv myenv
RUN /bin/bash -c "source myenv/bin/activate && update-alternatives --install /usr/bin/python python /usr/bin/python3 10 && pip3 install requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet"
# Create a buildtimestamp.txt to later check if a new version was released
RUN date +%s > /home/pi/pialert/front/buildtimestamp.txt
CMD ["/home/pi/pialert/dockerfiles/start.sh"]
RUN ${INSTALL_DIR}/pialert/dockerfiles/pre-setup.sh
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=2 \
CMD curl -sf -o /dev/null ${LISTEN_ADDR}:${PORT}/api/app_state.json
ENTRYPOINT ["/init"]

50
Dockerfile.debian Executable file
View File

@@ -0,0 +1,50 @@
FROM debian:bookworm-slim
# default UID and GID
ENV USER=pi USER_ID=1000 USER_GID=1000 PORT=20211
#TZ=Europe/London
# Todo, figure out why using a workdir instead of full paths don't work
# Todo, do we still need all these packages? I can already see sudo which isn't needed
RUN apt-get update
RUN apt-get install sudo -y
# create pi user and group
# add root and www-data to pi group so they can r/w files and db
RUN groupadd --gid "${USER_GID}" "${USER}" && \
useradd \
--uid ${USER_ID} \
--gid ${USER_GID} \
--create-home \
--shell /bin/bash \
${USER} && \
usermod -a -G ${USER_GID} root && \
usermod -a -G ${USER_GID} www-data
COPY --chmod=775 --chown=${USER_ID}:${USER_GID} . /home/pi/pialert/
# ❗ IMPORTANT - if you modify this file modify the /install/install_dependecies.debian.sh file as well ❗
RUN apt-get install -y \
tini snmp ca-certificates curl libwww-perl arp-scan perl apt-utils cron sudo \
nginx-light php php-cgi php-fpm php-sqlite3 php-curl sqlite3 dnsutils net-tools \
python3 iproute2 nmap python3-pip zip systemctl usbutils traceroute
# Alternate dependencies
RUN apt-get install nginx nginx-core mtr php-fpm php8.2-fpm php-cli php8.2 php8.2-sqlite3 -y
RUN phpenmod -v 8.2 sqlite3
# Setup virtual python environment and use pip3 to install packages
RUN apt-get install -y python3-venv
RUN python3 -m venv myenv
RUN /bin/bash -c "source myenv/bin/activate && update-alternatives --install /usr/bin/python python /usr/bin/python3 10 && pip3 install requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet"
# Create a buildtimestamp.txt to later check if a new version was released
RUN date +%s > /home/pi/pialert/front/buildtimestamp.txt
CMD ["/home/pi/pialert/install/start.debian.sh"]

View File

@@ -1,11 +1,11 @@
# 💻🔍 Network security scanner & notification framework
Get visibility of what's going on on your WIFI/LAN network. Scan for devices, port changes and get alerts if unknown devices or changes are found. Write your own [Plugins](https://github.com/jokob-sk/Pi.Alert/tree/main/front/plugins#readme) with auto-generated UI and in-build notification system.
Get visibility of what's going on on your WIFI/LAN network. Schedule scans for devices, port changes and get alerts if unknown devices or changes are found. Write your own [Plugins](https://github.com/jokob-sk/Pi.Alert/tree/main/front/plugins#readme) with auto-generated UI and in-build notification system.
[![GitHub Committed](https://img.shields.io/github/last-commit/jokob-sk/Pi.Alert?color=40ba12&label=Committed&logo=GitHub&logoColor=fff)](https://github.com/jokob-sk/Pi.Alert)
[![Docker Size](https://img.shields.io/docker/image-size/jokobsk/pi.alert?label=Size&logo=Docker&color=0aa8d2&logoColor=fff)](https://hub.docker.com/r/jokobsk/pi.alert)
[![Docker Pulls](https://img.shields.io/docker/pulls/jokobsk/pi.alert?label=Pulls&logo=docker&color=0aa8d2&logoColor=fff)](https://hub.docker.com/r/jokobsk/pi.alert)
![GitHub Release](https://img.shields.io/github/v/release/jokob-sk/Pi.Alert?color=0aa8d2&logoColor=fff&logo=GitHub)
[![GitHub Release](https://img.shields.io/github/v/release/jokob-sk/Pi.Alert?color=0aa8d2&logoColor=fff&logo=GitHub)](https://github.com/jokob-sk/Pi.Alert/releases)
[![GitHub Sponsors](https://img.shields.io/github/sponsors/jokob-sk?style=social)](https://github.com/sponsors/jokob-sk)
| 🐳 [Docker hub](https://registry.hub.docker.com/r/jokobsk/pi.alert) | 📑 [Docker guide](https://github.com/jokob-sk/Pi.Alert/blob/main/dockerfiles/README.md) |🆕 [Release notes](https://github.com/jokob-sk/Pi.Alert/releases) | 📚 [All Docs](https://github.com/jokob-sk/Pi.Alert/tree/main/docs) |
@@ -35,7 +35,7 @@ Get visibility of what's going on on your WIFI/LAN network. Scan for devices, po
PiAlert combines several network and other scanning tools 🔍 with notifications 📧 into one user-friendly package 📦.
Setup a _kill switch ☠_ for your network via a smart plug with the available [Home Assistant](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/HOME_ASSISTANT.md) integration. Implement custom automations with the [CSV device Exports 📤](https://github.com/jokob-sk/Pi.Alert/tree/main/front/plugins/csv_backup), [Webhooks](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/WEBHOOK_N8N.md), or [API endpoints](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/API.md) features.
Set up a _kill switch ☠_ for your network via a smart plug with the available [Home Assistant](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/HOME_ASSISTANT.md) integration. Implement custom automations with the [CSV device Exports 📤](https://github.com/jokob-sk/Pi.Alert/tree/main/front/plugins/csv_backup), [Webhooks](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/WEBHOOK_N8N.md), or [API endpoints](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/API.md) features.
Extend the app if you want to create your own scanner [Plugin](https://github.com/jokob-sk/Pi.Alert/tree/main/front/plugins#readme) and handle the results and notifications in PiAlert.
@@ -75,7 +75,6 @@ Get visibility of what's going on on your WIFI/LAN network. Scan for devices, po
- Regular updates to keep your data and family safe 🔄
- Better and more functionality
- Quicker and better support with issues 🆘
- Less grumpy me 😄
| [![GitHub](https://i.imgur.com/emsRCPh.png)](https://github.com/sponsors/jokob-sk) | [![Buy Me A Coffee](https://i.imgur.com/pIM6YXL.png)](https://www.buymeacoffee.com/jokobsk) | [![Patreon](https://i.imgur.com/MuYsrq1.png)](https://www.patreon.com/user?u=84385063) |
| --- | --- | --- |
@@ -103,11 +102,21 @@ Thank you to all the wonderful people who are sponsoring this project (=preventi
<!-- SPONSORS-LIST DO NOT MODIFY BELOW -->
| All Sponsors |
|---|
| [dtech77pl](https://github.com/dtech77pl) |
| [Tony Hanratty](https://github.com/thanratty) |
<!-- SPONSORS-LIST DO NOT MODIFY ABOVE -->
### 🙏Contributors
This project would be nothing without the amazing work of the community, with special thanks to:
> [pucherot/Pi.Alert](https://github.com/pucherot/Pi.Alert) (the original creator of PiAlert), [leiweibau](https://github.com/leiweibau/Pi.Alert): Dark mode (and much more), [Macleykun](https://github.com/Macleykun) (Help with Dockerfile clean-up) [Final-Hawk](https://github.com/Final-Hawk) (Help with NTFY, styling and other fixes), [TeroRERO](https://github.com/terorero) (Spanish translations), [Data-Monkey](https://github.com/Data-Monkey), (Split-up of the python.py file and more), [cvc90](https://github.com/cvc90) (Spanish translation and various UI work) to name a few...
Here is everyone that helped and contributed to this project:
<a href="https://github.com/jokob-sk/pi.alert/graphs/contributors">
<img src="https://contri-graphy.yourselfhosted.com/graph?repo=jokob-sk/pi.alert&format=svg" />
</a>
## Everything else
<!--- --------------------------------------------------------------------- --->
@@ -124,13 +133,7 @@ Help out and suggest languages in the [online portal of Weblate](https://hosted.
### License
> GPL 3.0 | [Read more here](LICENSE.txt) | Source of the [animated GIF (Loading Animation)](https://commons.wikimedia.org/wiki/File:Loading_Animation.gif) | Source of the [selfhosted Fonts](https://github.com/adobe-fonts/source-sans)
### Special thanks
This code is a collaborative body of work, with special thanks to:
> [pucherot/Pi.Alert](https://github.com/pucherot/Pi.Alert) (the original creator of PiAlert), [leiweibau](https://github.com/leiweibau/Pi.Alert): Dark mode (and much more), [Macleykun](https://github.com/Macleykun) (Help with Dockerfile clean-up) [Final-Hawk](https://github.com/Final-Hawk) (Help with NTFY, styling and other fixes), [TeroRERO](https://github.com/terorero) (Spanish translations), [Data-Monkey](https://github.com/Data-Monkey), (Split-up of the python.py file and more), [cvc90](https://github.com/cvc90) (Spanish translation and various UI work) to name a few...
>
> Please see the [Git contributors](https://github.com/jokob-sk/Pi.Alert/graphs/contributors) for a full list of people and their contributions to the project
<!--- --------------------------------------------------------------------- --->
[main]: ./docs/img/devices_split.png "Main screen"

View File

@@ -11,12 +11,12 @@ services:
network_mode: host
# restart: unless-stopped
volumes:
- ${APP_DATA_LOCATION}/pialert_dev/config:/home/pi/pialert/config
# - ${APP_DATA_LOCATION}/pialert/config:/home/pi/pialert/config
- ${APP_DATA_LOCATION}/pialert_dev/db:/home/pi/pialert/db
# - ${APP_DATA_LOCATION}/pialert/db:/home/pi/pialert/db
# - ${APP_DATA_LOCATION}/pialert_dev/config:/home/pi/pialert/config
- ${APP_DATA_LOCATION}/pialert/config:/home/pi/pialert/config
# - ${APP_DATA_LOCATION}/pialert_dev/db:/home/pi/pialert/db
- ${APP_DATA_LOCATION}/pialert/db:/home/pi/pialert/db
# (optional) useful for debugging if you have issues setting up the container
- ${LOGS_LOCATION}:/home/pi/pialert/front/log
# - ${LOGS_LOCATION}:/home/pi/pialert/front/log
# ---------------------------------------------------------------------------
# DELETE START anyone trying to use this file: comment out / delete BELOW lines, they are only for development purposes
- ${APP_DATA_LOCATION}/pialert/dhcp_samples/dhcp1.leases:/mnt/dhcp1.leases
@@ -32,10 +32,10 @@ services:
- ${DEV_LOCATION}/back/update_vendors.sh:/home/pi/pialert/back/update_vendors.sh
- ${DEV_LOCATION}/front/lib/AdminLTE:/home/pi/pialert/front/lib/AdminLTE
- ${DEV_LOCATION}/front/js:/home/pi/pialert/front/js
- ${DEV_LOCATION}/dockerfiles/start.sh:/home/pi/pialert/dockerfiles/start.sh
- ${DEV_LOCATION}/dockerfiles/user-mapping.sh:/home/pi/pialert/dockerfiles/user-mapping.sh
- ${DEV_LOCATION}/install/install.sh:/home/pi/pialert/install/install.sh
- ${DEV_LOCATION}/install/install_dependencies.sh:/home/pi/pialert/install/install_dependencies.sh
- ${DEV_LOCATION}/install/start.debian.sh:/home/pi/pialert/install/start.debian.sh
- ${DEV_LOCATION}/install/user-mapping.debian.sh:/home/pi/pialert/install/user-mapping.debian.sh
- ${DEV_LOCATION}/install/install.debian.sh:/home/pi/pialert/install/install.debian.sh
- ${DEV_LOCATION}/install/install_dependencies.debian.sh:/home/pi/pialert/install/install_dependencies.debian.sh
- ${DEV_LOCATION}/front/api:/home/pi/pialert/front/api
- ${DEV_LOCATION}/front/php:/home/pi/pialert/front/php
- ${DEV_LOCATION}/front/deviceDetails.php:/home/pi/pialert/front/deviceDetails.php
@@ -54,6 +54,7 @@ services:
- ${DEV_LOCATION}/front/report.php:/home/pi/pialert/front/report.php
- ${DEV_LOCATION}/front/workflows.php:/home/pi/pialert/front/workflows.php
- ${DEV_LOCATION}/front/appEventsCore.php:/home/pi/pialert/front/appEventsCore.php
- ${DEV_LOCATION}/front/multiEditCore.php:/home/pi/pialert/front/multiEditCore.php
- ${DEV_LOCATION}/front/donations.php:/home/pi/pialert/front/donations.php
- ${DEV_LOCATION}/front/plugins:/home/pi/pialert/front/plugins
# DELETE END anyone trying to use this file: comment out / delete ABOVE lines, they are only for development purposes
@@ -63,3 +64,5 @@ services:
- PORT=${PORT}
- HOST_USER_ID=${HOST_USER_ID}
- HOST_USER_GID=${HOST_USER_GID}
# ❗ DANGER ZONE BELOW - Setting ALWAYS_FRESH_INSTALL=true will delete the content of the /db & /config folders
- ALWAYS_FRESH_INSTALL=${ALWAYS_FRESH_INSTALL}

View File

@@ -42,12 +42,16 @@ docker run -d --rm --network=host \
|`TZ` |Time zone to display stats correctly. Find your time zone [here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) | `Europe/Berlin` |
|`HOST_USER_GID` |User ID (UID) to map the user in the container to a server user with sufficient read&write permissions on the mapped files | `1000` |
|`HOST_USER_ID` |User Group ID (GID) to map the user group in the container to a server user group with sufficient read&write permissions on the mapped files | `1000` |
|`ALWAYS_FRESH_INSTALL` | Setting `ALWAYS_FRESH_INSTALL=true` will delete the content of the `/db` & `/config` folders. For testing purposes. Can be coupled with [watchtower](https://github.com/containrrr/watchtower) to have an always freshly installed `pi.alert`/`_dev` image. | `N/A` |
### Docker paths
> [!NOTE]
> See also [Backup strategies](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/BACKUPS.md).
| Required | Path | Description |
| :------------- | :------------- | :-------------|
| ✅ | `:/home/pi/pialert/config` | Folder which will contain the `pialert.conf` file (see below for details) |
| ✅ | `:/home/pi/pialert/config` | Folder which will contain the `pialert.conf` & `devices.csv` ([read about devices.csv](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/DEVICES_BULK_EDITING.md)) files (see below for details) |
| ✅ | `:/home/pi/pialert/db` | Folder which will contain the `pialert.db` file |
| | `:/home/pi/pialert/front/log` | Logs folder useful for debugging if you have issues setting up the container |
| | `:/etc/pihole/pihole-FTL.db` | PiHole's `pihole-FTL.db` database file. Required if you want to use PiHole DB mapping. |
@@ -236,7 +240,7 @@ Courtesy of [pbek](https://github.com/pbek). The volume `pialert_db` is used by
## 🏅 Recognitions
Big thanks to <a href="https://github.com/Macleykun">@Macleykun</a> for help and tips&tricks for Dockerfile(s).
Big thanks to <a href="https://github.com/Macleykun">@Macleykun</a> & for help and tips&tricks for Dockerfile(s) and <a href="https://github.com/vladaurosh">@vladaurosh</a> for Alpine re-base help.
## ❤ Support me

31
dockerfiles/pre-setup.sh Executable file
View File

@@ -0,0 +1,31 @@
#!/bin/bash
export INSTALL_DIR=/home/pi
# php-fpm setup
install -d -o nginx -g www-data /run/php/
sed -i "/^;pid/c\pid = /run/php/php8.2-fpm.pid" /etc/php82/php-fpm.conf
sed -i "/^listen/c\listen = /run/php/php8.2-fpm.sock" /etc/php82/php-fpm.d/www.conf
sed -i "/^;listen.owner/c\listen.owner = nginx" /etc/php82/php-fpm.d/www.conf
sed -i "/^;listen.group/c\listen.group = www-data" /etc/php82/php-fpm.d/www.conf
sed -i "/^user/c\user = nginx" /etc/php82/php-fpm.d/www.conf
sed -i "/^group/c\group = www-data" /etc/php82/php-fpm.d/www.conf
# s6 overlay setup
mkdir -p /etc/s6-overlay/s6-rc.d/{SetupOneshot,php-fpm/dependencies.d,nginx/dependencies.d}
mkdir -p /etc/s6-overlay/s6-rc.d/{SetupOneshot,php-fpm/dependencies.d,nginx/dependencies.d,pialert/dependencies.d}
echo "oneshot" > /etc/s6-overlay/s6-rc.d/SetupOneshot/type
echo "longrun" > /etc/s6-overlay/s6-rc.d/php-fpm/type
echo "longrun" > /etc/s6-overlay/s6-rc.d/nginx/type
echo "longrun" > /etc/s6-overlay/s6-rc.d/pialert/type
echo -e "${INSTALL_DIR}/pialert/dockerfiles/setup.sh" > /etc/s6-overlay/s6-rc.d/SetupOneshot/up
echo -e "#!/bin/execlineb -P\n/usr/sbin/php-fpm82 -F" > /etc/s6-overlay/s6-rc.d/php-fpm/run
echo -e '#!/bin/execlineb -P\nnginx -g "daemon off;"' > /etc/s6-overlay/s6-rc.d/nginx/run
echo -e '#!/bin/execlineb -P\n\nwith-contenv\nimportas -i PORT PORT\nif { echo "[INSTALL] 🚀 Starting app - navigate to your <server IP>:${PORT}" }' > /etc/s6-overlay/s6-rc.d/pialert/run
echo -e "python ${INSTALL_DIR}/pialert/pialert" >> /etc/s6-overlay/s6-rc.d/pialert/run
touch /etc/s6-overlay/s6-rc.d/user/contents.d/{SetupOneshot,php-fpm,nginx} /etc/s6-overlay/s6-rc.d/{php-fpm,nginx}/dependencies.d/SetupOneshot
touch /etc/s6-overlay/s6-rc.d/user/contents.d/{SetupOneshot,php-fpm,nginx,pialert} /etc/s6-overlay/s6-rc.d/{php-fpm,nginx,pialert}/dependencies.d/SetupOneshot
touch /etc/s6-overlay/s6-rc.d/nginx/dependencies.d/php-fpm
touch /etc/s6-overlay/s6-rc.d/pialert/dependencies.d/nginx
rm -f $0

79
dockerfiles/setup.sh Executable file
View File

@@ -0,0 +1,79 @@
#!/usr/bin/with-contenv bash
echo "---------------------------------------------------------"
echo "[INSTALL] Run setup.sh"
echo "---------------------------------------------------------"
export INSTALL_DIR=/home/pi # Specify the installation directory here
# DO NOT CHANGE ANYTHING BELOW THIS LINE!
NGINX_CONFIG_FILE=/etc/nginx/http.d/pialert.conf
OUI_FILE="/usr/share/arp-scan/ieee-oui.txt" # Define the path to ieee-oui.txt and ieee-iab.txt
FILEDB="${INSTALL_DIR}/pialert/db/pialert.db"
# DO NOT CHANGE ANYTHING ABOVE THIS LINE!
# Check if script is run as root
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root. Please use 'sudo'."
exit 1
fi
echo "[INSTALL] Copy starter pialert.db and pialert.conf if they don't exist"
# DANGER ZONE: ALWAYS_FRESH_INSTALL
if [ "$ALWAYS_FRESH_INSTALL" = true ]; then
echo "[INSTALL] ❗ ALERT /db and /config folders are cleared because the ALWAYS_FRESH_INSTALL is set to: $ALWAYS_FRESH_INSTALL"
# Delete content of "$INSTALL_DIR/pialert/config/"
rm -rf "$INSTALL_DIR/pialert/config/"*
# Delete content of "$INSTALL_DIR/pialert/db/"
rm -rf "$INSTALL_DIR/pialert/db/"*
fi
# Copy starter pialert.db and pialert.conf if they don't exist
cp -na "${INSTALL_DIR}/pialert/back/pialert.conf" "${INSTALL_DIR}/pialert/config/pialert.conf"
cp -na "${INSTALL_DIR}/pialert/back/pialert.db" "${FILEDB}"
# if custom variables not set we do not need to do anything
if [ -n "${TZ}" ]; then
FILECONF="${INSTALL_DIR}/pialert/config/pialert.conf"
echo "[INSTALL] Setup timezone"
sed -i "\#^TIMEZONE=#c\TIMEZONE='${TZ}'" "${FILECONF}"
fi
echo "[INSTALL] Setup NGINX"
echo "Setting webserver to address ($LISTEN_ADDR) and port ($PORT)"
envsubst '$INSTALL_DIR $LISTEN_ADDR $PORT' < "${INSTALL_DIR}/pialert/install/pialert.template.conf" > "${NGINX_CONFIG_FILE}"
# Run the hardware vendors update at least once
echo "[INSTALL] Run the hardware vendors update"
# Check if ieee-oui.txt or ieee-iab.txt exist
if [ -f "${OUI_FILE}" ]; then
echo "The file ieee-oui.txt exists. Skipping update_vendors.sh..."
else
echo "The file ieee-oui.txt does not exist. Running update_vendors..."
# Run the update_vendors.sh script
if [ -f "${INSTALL_DIR}/pialert/back/update_vendors.sh" ]; then
"${INSTALL_DIR}/pialert/back/update_vendors.sh"
else
echo "update_vendors.sh script not found in ${INSTALL_DIR}."
fi
fi
# Create an empty log files
# Create the execution_queue.log and pialert_front.log files if they don't exist
touch "${INSTALL_DIR}"/pialert/front/log/{execution_queue.log,pialert_front.log,pialert.php_errors.log,stderr.log,stdout.log}
echo "[INSTALL] Fixing permissions after copied starter config & DB"
chown -R nginx:www-data "${INSTALL_DIR}"/pialert/{config,front/log,db}
chmod 750 "${INSTALL_DIR}"/pialert/{config,front/log,db}
chmod 640 "${INSTALL_DIR}"/pialert/{config,front/log,db}/*
# Check if buildtimestamp.txt doesn't exist
if [ ! -f "${INSTALL_DIR}/pialert/front/buildtimestamp.txt" ]; then
# Create buildtimestamp.txt
date +%s > "${INSTALL_DIR}/pialert/front/buildtimestamp.txt"
chown nginx:www-data "${INSTALL_DIR}/pialert/front/buildtimestamp.txt"
fi

82
docs/BACKUPS.md Executable file
View File

@@ -0,0 +1,82 @@
# 💾 Backing things up
> [!NOTE]
> To backup 99% of your configuration backup at least the `/config` folder. Please read the whole page (or at least "Scenario 2: Corrupted database") for details.
There are 3 artifacts that can be used to backup the application:
| File | Description | Limitations |
|-----------------------|-------------------------------|-------------------------------|
| `/db/pialert.db` | Database file(s) | The database file might be in an uncommitted state or corrupted |
| `/config/pialert.conf` | Configuration file | Doesn't contain settings from the Maintenance section |
| `/config/devices.csv` | CSV file containing device information | Doesn't contain historical data |
## Data and cackup storage
To decide on a backup strategy, check where the data is stored:
### Core Configuration
The core application configuration is in the `pialert.conf` file (See [Settings System](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/SETTINGS_SYSTEM.md) for details), such as:
- Notification settings
- Scanner settings
- Scheduled maintenance settings
- UI configuration (80%)
### Core Device Data
The core device data is backed up to the `devices_<timestamp>.csv` file via the [CSV Backup `CSVBCKP` Plugin](https://github.com/jokob-sk/Pi.Alert/tree/main/front/plugins/csv_backup). This file contains data, such as:
- Device names
- Device Icons
- Device Network configuration
- Device categorization
### Historical data
Historical data is stored in the `pialert.db` database (See [Database overview](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/DATABASE.md) for details). This data includes:
- Plugin objects
- Plugin historical entries
- History of Events, Notifications, Workflow Events
- Presence History
## 🧭 Backup strategies
The safest approach to backups is to backup all of the above, by taking regular file system backups (I use [Kopia](https://github.com/kopia/kopia)).
Arguably, the most time is spent setting up the device list, so if only one file is kept I'd recommend to have a latest backup of the `devices_<timestamp>.csv` file, followed by the `pialert.conf` file.
### Scenario 1: Full backup
End-result: Full restore
#### Source artifacts:
- `/db/pialert.db` (uncorrupted)
- `/config/pialert.conf`
#### Recovery:
To restore the application map the above files as described in the [Setup documentation](https://github.com/jokob-sk/Pi.Alert/blob/main/dockerfiles/README.md#docker-paths).
### Scenario 2: Corrupted database
End-result: Partial restore (historical data & configurations from the Maintenance section will be missing)
#### Source artifacts:
- `/config/pialert.conf`
- `/config/devices_<timestamp>.csv` or `/config/devices.csv`
#### Recovery:
Even with a corrupted database you can recover what I would argue is 99% of the configuration (except of a couple of settings under Maintenance).
- map the `/config/pialert.conf` file as described in the [Setup documentation](https://github.com/jokob-sk/Pi.Alert/blob/main/dockerfiles/README.md#docker-paths).
- rename the `devices_<timestamp>.csv` to `devices.csv` and place it in the `/config` folder
- Restore the `devices.csv` backup via the [Maintenance section](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/DEVICES_BULK_EDITING.md)

View File

@@ -1,9 +1,19 @@
# 🖊 Multi-editing via the UI
> [!NOTE]
> Make sure you have your backups saved and restorable before doing any mass edits. Check [Backup strategies](/docs/BACKUPS.md).
You can select devices in the _Devices_ view by selecting devices to edit and then clicking the _Multi-edit_ button or via the _Maintenance_ > _Multi-Edit_ section.
![Maintenance > Multi-edit](/docs/img/DEVICES_BULK_EDITING/MULTI-EDIT.gif)
# 📝Bulk-edit devices via CSV Export/Import
> [!NOTE]
> As always, backup everything, just in case.
1. In `Maintenance` > `Backup / Restore` click the `CSV Export` button.
1. In _Maintenance_ > _Backup / Restore_ click the _CSV Export_ button.
2. A `devices.csv` is generated in the `/config` folder
3. Edit the `devices.csv` file however you like.

View File

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

View File

@@ -22,11 +22,12 @@ PiAlert comes with MQTT support, allowing you to show all detected devices as de
- User
- Password
4. Ope the `PiAlert` > `Settings` > `MQTT` settings group
4. Open the _PiAlert_ > _Settings_ > _MQTT_ settings group
- Enable MQTT
- Fill in the details from above
- Fill in remaining settings as per description
![Configuration Example][configuration]
## 📷 Screenshots
@@ -35,8 +36,9 @@ PiAlert comes with MQTT support, allowing you to show all detected devices as de
| ![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"
[configuration]: /docs/img/HOME_ASISSTANT/PiAlert-HomeAssistant-Configuration.png "configuration"
[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

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

View File

@@ -17,7 +17,7 @@ Make sure you have a root device with the MAC `Internet` (No other MAC addresses
> [!NOTE]
>
> [Bulk-edit devices](/docs/DEVICES_BULK_EDITING.md) by using the `CSV Export` functionality in the `Maintenance` section. You can use this to fix `Internet` node assignment issues.
> [Bulk-edit devices](/docs/DEVICES_BULK_EDITING.md) by using the _CSV Export_ functionality in the _Maintenance_ section. You can use this to fix `Internet` node assignment issues.
## 🔍Detailed example:

View File

@@ -32,6 +32,7 @@ There is also an in-app Help / FAQ section that should be answering frequently a
- [Custom Icon configuration and support](/docs/ICONS.md)
- [Better name resolution with Reverse DNS](/docs/REVERSE_DNS.md)
- [Network treemap configuration](/docs/NETWORK_TREE.md)
- [Backups](/docs/BACKUPS.md)
#### 🐛 Debugging help & tips
@@ -106,7 +107,7 @@ If you submit a PR please:
1. Check that your changes are backward compatible with existing installations and with a blank setup.
2. Existing features should always be preserved.
3. Keep the PR small, on-topic and don't change code that is not necessary for the PR to work
4. New features code should ideally be re-usable for different purposes, not be for a very narrow use-case.
4. New features code should ideally be re-usable for different purposes, not for a very narrow use case.
5. New functionality should ideally be implemented via the Plugins system, if possible.
Suggested test cases:

View File

@@ -2,6 +2,10 @@
> Submitted by amazing [cvc90](https://github.com/cvc90) 🙏
> [!NOTE]
> There are 2 NGINX files for PiAlert, one for the bare-metal Debian install (`pialert.debian.conf`), and one for the docker container (`pialert.template.conf`). Both can be found in the [install](https://github.com/jokob-sk/Pi.Alert/tree/main/install) folder. Map, or use, the one appropriate for your setup.
## NGINX HTTP Configuration (Direct Path)
1. On your NGINX server, create a new file called /etc/nginx/sites-available/pialert

View File

@@ -2,7 +2,7 @@
This is an explanation how settings are handled intended for anyone thinking about writing their own plugin or contributing to the project.
If you are a user of the app, settings should be described in the `Settings` section of the app. Open an issue if you'd like to clarify any of the settings.
If you are a user of the app, settings have a detailed description in the _Settings_ section of the app. Open an issue if you'd like to clarify any of the settings.
### 🛢 Data storage

View File

@@ -2,6 +2,9 @@
You need to specify the network interface and the network mask. You can also configure multiple subnets and specify VLANS (see exceptions below).
> [!TIP]
> You may need to increase the time between scans `ARPSCAN_RUN_SCHD` and the timeout `ARPSCAN_RUN_TIMEOUT` settings when adding more subnets. If the timeout setting is exceeded, the scan is cancelled to prevent application hanging from rogue plugins. Check [debugging plugins](/docs/DEBUG_PLUGINS.md) for more tips.
## Examples
> [!NOTE]
@@ -12,6 +15,7 @@ You need to specify the network interface and the network mask. You can also con
* 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
@@ -31,7 +35,10 @@ Specify the network filter (which **significantly** speeds up the scan process).
The adapter will probably be `eth0` or `eth1`. (Check `System info` > `Network Hardware` or run `iwconfig` in the container to find your interface name(s))
> Run `ip -o link show | awk -F': ' '!/lo|vir|docker/ {print $2}'` in your container to find your interface name(s) (e.g.: `eth0`, `eth1`).
![Network hardware](/docs/img/SUBNETS/system_info-network_hardware.png)
> [!TIP]
> Alterantive to `iwconfig` run `ip -o link show | awk -F': ' '!/lo|vir|docker/ {print $2}'` in your container to find your interface name(s) (e.g.: `eth0`, `eth1`).
### VLANs

View File

@@ -1,5 +1,7 @@
### Create a simple n8n workflow
N8N can be used for more advanced conditional notification use cases. For example, you want only to get notified if two out of a specified list of devices is down. Or you can use other plugins to process the notifiations further. The below is a simple example of sending an email on a webhook.
![n8n workflow](/docs/img/WEBHOOK_N8N/n8n_workflow.png)
### Specify your email template

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

View File

@@ -725,4 +725,6 @@ input[type="password"]::-webkit-caps-lock-indicator {
}
.pa_semitransparent-panel{
background-color: #000 !important;
}
}

View File

@@ -1030,6 +1030,53 @@ input[readonly] {
z-index: 100;
}
/* Multi-edit adjustements */
.box-header
{
min-height: 55px;
}
.red-hover-border:hover
{
border-color: red !important;
border-width: 1px;
border-style: solid;
}
.red-hover-background:hover
{
background-color: red !important;
}
#multi-edit-form .form-group
{
height: 45px;
}
.pia-top-left-logo
{
height:50px;
}
/* -----------------------------------------------------------------------------
Floating edit button
----------------------------------------------------------------------------- */
#multiEditPlc
{
position: fixed;
bottom: 50px;
right: 0px;
z-index: 10;
}
table.dataTable tbody > tr.selected
{
color:red;
}
/* -----------------------------------------------------------------------------
Donations
----------------------------------------------------------------------------- */

View File

@@ -843,7 +843,7 @@ function initializeCombo (dropdownId, queryAction, txtDataField, useCache) {
// get data from server
$.get('php/server/devices.php?action='+queryAction, function(data) {
console.log(data)
// console.log(data)
var listData = JSON.parse(data);
var order = 1;
@@ -1288,6 +1288,16 @@ function getDeviceData (readAllData=false) {
devicesList = getDevicesList();
// handle empty dev_Network_Node_MAC_ADDR
networkParentMac = deviceData['dev_Network_Node_MAC_ADDR']
if(networkParentMac)
{
networkParentMacName = getDeviceDataByMacAddress(deviceData['dev_Network_Node_MAC_ADDR'], "dev_Name")
} else
{
networkParentMacName = '--'
}
$('#txtMAC').val (deviceData['dev_MAC']);
$('#txtName').val (deviceData['dev_Name']);
$('#txtOwner').val (deviceData['dev_Owner']);
@@ -1300,7 +1310,7 @@ function getDeviceData (readAllData=false) {
$('#txtGroup').val (deviceData['dev_Group']);
$('#txtLocation').val (deviceData['dev_Location']);
$('#txtComments').val (deviceData['dev_Comments']);
$('#txtNetworkNodeMac').val ( getDeviceDataByMacAddress(deviceData['dev_Network_Node_MAC_ADDR'], "dev_Name")) ;
$('#txtNetworkNodeMac').val ( networkParentMacName) ;
$('#txtNetworkNodeMac').attr ('data-mynodemac', deviceData['dev_Network_Node_MAC_ADDR']);
$('#txtNetworkPort').val (deviceData['dev_Network_Node_port']);
// disabling network node configuration if root Internet node
@@ -1471,23 +1481,7 @@ function setDeviceData (direction='', refreshCallback='') {
});
}
// --------------------------------------------------------
// Calls a backend function to add a front-end event to an execution queue
function updateApi()
{
// value has to be in format event|param. e.g. run|ARPSCAN
action = `update_api|devices,appevents`
$.ajax({
method: "POST",
url: "php/server/util.php",
data: { function: "addToExecutionQueue", action: action },
success: function(data, textStatus) {
console.log(data)
}
})
}
// -----------------------------------------------------------------------------

View File

@@ -129,7 +129,7 @@
beforeSend: function() { $('#scanoutput').addClass("ajax_scripts_loading"); },
complete: function() { $('#scanoutput').removeClass("ajax_scripts_loading"); },
success: function(data, textStatus) {
console.log(data);
// console.log(data);
$("#scanoutput").html(data);
}
})

View File

@@ -120,7 +120,7 @@
<div class="col-md-12">
<div class="box" id="clients">
<div class="box-header with-border">
<h3 class="box-title"><?= lang('Device_Shortcut_OnlineChart');?> </h3>
<h3 class="box-title"><?= lang('Device_Shortcut_OnlineChart');?> </h3>
</div>
<div class="box-body">
<div class="chart">
@@ -148,7 +148,9 @@
<!-- box-header -->
<div class="box-header">
<h3 id="tableDevicesTitle" class="box-title text-gray">Devices</h3>
<div class=" col-md-10 ">
<h3 id="tableDevicesTitle" class="box-title text-gray "></h3>
</div>
</div>
<!-- table -->
@@ -173,6 +175,7 @@
<!-- ----------------------------------------------------------------------- -->
</section>
<!-- /.content -->
<div id="multiEditPlc" class="col-md-2"></div>
</div>
<!-- /.content-wrapper -->
@@ -186,9 +189,10 @@
<!-- ----------------------------------------------------------------------- -->
<!-- Datatable -->
<link rel="stylesheet" href="lib/AdminLTE/bower_components/datatables.net-bs/css/dataTables.bootstrap.min.css">
<link rel="stylesheet" href="lib/AdminLTE/bower_components/datatables.net/css/select.dataTables.min.css">
<script src="lib/AdminLTE/bower_components/datatables.net/js/jquery.dataTables.min.js"></script>
<script src="lib/AdminLTE/bower_components/datatables.net-bs/js/dataTables.bootstrap.min.js"></script>
<script src="lib/AdminLTE/bower_components/datatables.net/js/dataTables.select.min.js"></script>
<!-- page script ----------------------------------------------------------- -->
<script>
@@ -291,7 +295,8 @@ function main () {
}
// Initialize components with parameters
initializeDatatable('my');
initializeDatatable(getUrlAnchor('my'));
@@ -359,19 +364,19 @@ function filterDataByStatus(data, status) {
case 'my':
to_display = getSetting('UI_MY_DEVICES');
let result = false;
let result = true;
if (to_display.includes('online') && item.dev_PresentLastScan === 1) {
result = true;
} else if (to_display.includes('offline') && item.dev_PresentLastScan === 0) {
result = true;
} else if (to_display.includes('archived') && item.dev_Archived === 1) {
result = true;
} else if (to_display.includes('new') && item.dev_NewDevice === 1) {
result = true;
} else if (to_display.includes('down') && item.dev_PresentLastScan === 0 && item.dev_AlertDeviceDown !== 0) {
result = true;
}
if (!to_display.includes('down') && item.dev_PresentLastScan === 0 && item.dev_AlertDeviceDown !== 0) {
result = false;
} else if (!to_display.includes('new') && item.dev_NewDevice === 1) {
result = false;
} else if (!to_display.includes('archived') && item.dev_Archived === 1) {
result = false;
} else if (!to_display.includes('offline') && item.dev_PresentLastScan === 0) {
result = false;
} else if (!to_display.includes('online') && item.dev_PresentLastScan === 1) {
result = false;
}
return result; // Include all items for 'my' status
case 'connected':
@@ -381,7 +386,7 @@ function filterDataByStatus(data, status) {
case 'new':
return item.dev_NewDevice === 1;
case 'down':
return item.dev_PresentLastScan === 0 && item.dev_AlertDeviceDown !== 0;
return (item.dev_PresentLastScan === 0 && item.dev_AlertDeviceDown !== 0) || item.dev_PresentLastScan === 0;
case 'archived':
return item.dev_Archived === 1;
default:
@@ -421,6 +426,11 @@ function getDeviceStatus(item)
// -----------------------------------------------------------------------------
function initializeDatatable (status) {
if(!status)
{
status = 'my'
}
// Save status selected
deviceStatus = status;
@@ -520,7 +530,8 @@ function initializeDatatable (status) {
// Parameters
'pageLength' : tableRows,
'order' : tableOrder,
'order' : tableOrder,
'select' : true, // Enable selection
'columnDefs' : [
{visible: false, targets: tableColumnHide },
@@ -656,6 +667,28 @@ function initializeDatatable (status) {
setCache ('devicesList', getDevicesFromTable(table) );
} );
// add multi-edit button
$('#multiEditPlc').append(
`<button type="submit" id="multiEdit" class="btn btn-primary" style="display:none" onclick="multiEditDevices();">
<i class="fa fa-pencil pointer" ></i> ${getString("Device_MultiEdit")}
</button>`)
// Event listener for row selection in DataTable
$('#tableDevices').on('click', 'tr', function (e) {
setTimeout(function(){
// Check if any row is selected
var anyRowSelected = $('#tableDevices tr.selected').length > 0;
console.log(anyRowSelected);
// Toggle visibility of element with ID 'multiEdit'
$('#multiEdit').toggle(anyRowSelected);
}, 200);
});
});
};
@@ -697,7 +730,7 @@ function getNumberOfChildren(mac, devices)
$.each(devices, function(index, dev) {
if(dev.dev_Network_Node_MAC_ADDR.trim() == mac.trim())
if(dev.dev_Network_Node_MAC_ADDR != null && dev.dev_Network_Node_MAC_ADDR.trim() == mac.trim())
{
childrenCount++;
}
@@ -729,6 +762,51 @@ function handleLoadingDialog()
}
// -----------------------------------------------------------------------------
// Function collects selected devices in the DataTable and redirects the user to
// the Miantenance section with a 'macs' query string identifying selected devices
function multiEditDevices()
{
rows = $('#tableDevices')[0].rows
// Initialize an empty array to store selected rows
var selectedRows = [];
console.log($('#tableDevices')[0].rows);
// Loop through each row in the HTML collection
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
// Check if the row has the 'selected' class
if (row.classList.contains('selected')) {
// If selected, push the row's data to the selectedRows array
selectedRows.push(row);
}
}
// Now, selectedRows contains all selected rows
console.log(selectedRows);
var devicesDataTableData = $('#tableDevices').dataTable().fnGetData();
var selectedDevices = [];
for (var i = 0; i < selectedRows.length; i++) {
selectedDevices.push(devicesDataTableData[selectedRows[i]._DT_RowIndex]);
}
// Now, selectedDevices contains all selected devices
console.log(selectedDevices);
macs = ""
for (var i = 0; i < selectedDevices.length; i++) {
macs += selectedDevices[i][mapIndx(11)] + ","; // [11] == MAC
}
// redirect to the Maintenance section
window.location.href = window.location.origin + '/maintenance.php#tab_multiEdit?macs=' + macs.slice(0, -1);
}
</script>
<script src="js/pialert_common.js"></script>

View File

@@ -1,7 +1,7 @@
<?php
require 'php/templates/header.php';
?>
<script src="js/pialert_common.js"></script>
<div id="donationsPage" class="content-wrapper">
<!-- Content header--------------------------------------------------------- -->
<section class="content-header">

View File

@@ -99,8 +99,8 @@ if ($ENABLED_DARKMODE === True) {
$BACKGROUND_IMAGE_PATCH='style="background-image: url(\'img/boxed-bg-dark.png\');"';
} else { $BACKGROUND_IMAGE_PATCH='style="background-image: url(\'img/background.png\');"';}
?>
<link rel="stylesheet" href="/front/css/offline-font.css">
<!-- /var/www/html/pialert/css/offline-font.css -->
<link rel="stylesheet" href="/css/offline-font.css">
</head>
<body class="hold-transition login-page">
<div class="login-box login-custom">

18
front/js/db_methods.js Executable file
View File

@@ -0,0 +1,18 @@
// -----------------------------------------------------------------------------
// General utilities to interact with teh database
// -----------------------------------------------------------------------------
// Read data and place intotarget location, callback processies the results
function readData(sqlQuery, processDataCallback, valuesArray, targetLocation) {
var apiUrl = `php/server/dbHelper.php?action=read&rawSql=${encodeURIComponent(sqlQuery)}`;
$.get(apiUrl, function(data) {
// Process the JSON data using the provided callback function
data = JSON.parse(data)
var htmlResult = processDataCallback(data, valuesArray);
// Place the resulting HTML into the specified placeholder div
$("#" + targetLocation).replaceWith(htmlResult);
});
}

View File

@@ -1,32 +1,70 @@
function pia_draw_graph_online_history(pia_js_graph_online_history_time, pia_js_graph_online_history_ondev, pia_js_graph_online_history_dodev, pia_js_graph_online_history_ardev) {
var xValues = pia_js_graph_online_history_time;
// alert("dev presence")
// Data object for online status
onlineData = {
label: 'Online',
data: pia_js_graph_online_history_ondev,
borderColor: "rgba(0, 166, 89)",
fill: true,
backgroundColor: "rgba(0, 166, 89, .6)",
pointStyle: 'circle',
pointRadius: 3,
pointHoverRadius: 3
};
// Data object for offline status
offlineData = {
label: 'Offline/Down',
data: pia_js_graph_online_history_dodev,
borderColor: "rgba(222, 74, 56)",
fill: true,
backgroundColor: "rgba(222, 74, 56, .6)",
};
// Data object for archived status
archivedData = {
label: 'Archived',
data: pia_js_graph_online_history_ardev,
borderColor: "rgba(220,220,220)",
fill: true,
backgroundColor: "rgba(220,220,220, .6)",
};
// Array to store datasets
datasets = [];
// Get UI presence settings
showStats = getSetting("UI_PRESENCE");
// Check if 'online' status should be displayed
if(showStats.includes("online"))
{
datasets.push(onlineData); // Add onlineData to datasets array
}
// Check if 'offline' status should be displayed
if(showStats.includes("offline"))
{
datasets.push(offlineData); // Add offlineData to datasets array
}
// Check if 'archived' status should be displayed
if(showStats.includes("archived"))
{
datasets.push(archivedData); // Add archivedData to datasets array
}
new Chart("OnlineChart", {
type: "bar",
scaleIntegersOnly: true,
data: {
labels: xValues,
datasets: [{
label: 'Online',
data: pia_js_graph_online_history_ondev,
borderColor: "rgba(0, 166, 89)",
fill: true,
backgroundColor: "rgba(0, 166, 89, .6)",
pointStyle: 'circle',
pointRadius: 3,
pointHoverRadius: 3
}, {
label: 'Offline/Down',
data: pia_js_graph_online_history_dodev,
borderColor: "rgba(222, 74, 56)",
fill: true,
backgroundColor: "rgba(222, 74, 56, .6)",
}, {
label: 'Archived',
data: pia_js_graph_online_history_ardev,
borderColor: "rgba(220,220,220)",
fill: true,
backgroundColor: "rgba(220,220,220, .6)",
}]
datasets: datasets
},
options: {
legend: {

View File

@@ -35,8 +35,8 @@ function checkIfNewVersionAvailable()
{
$.get('api/app_state.json?nocache=' + Date.now(), function(appState) {
console.log(appState["isNewVersionChecked"])
console.log(appState["isNewVersion"])
// console.log(appState["isNewVersionChecked"])
// console.log(appState["isNewVersion"])
// cache value
setCookie("isNewVersion", appState["isNewVersion"], 30);

View File

@@ -26,13 +26,17 @@ var settingsJSON = {}
function getCache(key, noCookie = false)
{
// check cache
if(sessionStorage.getItem(key))
cachedValue = localStorage.getItem(key)
// console.log(cachedValue);
if(cachedValue)
{
// check if not expired
if(noCookie || getCookie(key + '_session_expiry') != "")
{
return sessionStorage.getItem(key);
}
// // check if not expired
// if(noCookie || getCookie(key + '_session_expiry') != "")
// {
return cachedValue;
// }
}
return "";
@@ -41,13 +45,13 @@ function getCache(key, noCookie = false)
// -----------------------------------------------------------------------------
function setCache(key, data, expirationMinutes='')
{
sessionStorage.setItem(key, data);
localStorage.setItem(key, data);
// create cookie if expiration set to handle refresh of data
if (expirationMinutes != '')
{
setCookie (key + '_session_expiry', 'OK', expirationMinutes='')
}
// // create cookie if expiration set to handle refresh of data
// if (expirationMinutes != '')
// {
// setCookie ('cache_session_expiry', 'OK', 1)
// }
}
@@ -107,24 +111,77 @@ function deleteAllCookies() {
// -----------------------------------------------------------------------------
// Get settings from the .json file generated by the python backend
// Get settings from the .json file generated by the python backend
// and cache them, if available, with options
// -----------------------------------------------------------------------------
function cacheSettings()
{
$.get('api/table_settings.json', function(res) {
settingsJSON = res;
data = settingsJSON["data"];
$.get('api/table_settings.json?nocache=' + Date.now(), function(resSet) {
data.forEach((set) => {
setCache(`pia_set_${set.Code_Name}`, set.Value)
});
$.get('api/plugins.json?nocache=' + Date.now(), function(resPlug) {
pluginsData = resPlug["data"];
settingsData = resSet["data"];
settingsData.forEach((set) => {
resolvedOptions = createArray(set.Options)
setPlugObj = {};
options_params = [];
// proceed only if first option item contains something to resolve
if( !set.Code_Name.includes("__metadata") &&
resolvedOptions.length != 0 &&
resolvedOptions[0].includes("{value}"))
{
// get setting definition from the plugin config if available
setPlugObj = getPluginSettingObject(pluginsData, set.Code_Name)
// check if options contains parameters and resolve
if(setPlugObj != {} && setPlugObj["options_params"])
{
// get option_params for {value} resolution
options_params = setPlugObj["options_params"]
if(options_params != [])
{
// handles only strings of length == 1
resolvedOptions = `["${resolveParams(options_params, resolvedOptions[0])}"]`
}
}
}
setCache(`pia_set_${set.Code_Name}`, set.Value)
setCache(`pia_set_opt_${set.Code_Name}`, resolvedOptions)
});
}).then(() => handleSuccess('cacheSettings')).catch(() => handleFailure('cacheSettings', cacheSettings)); // handle AJAX synchronization
})
}
// -----------------------------------------------------------------------------
// Get a setting value by key
function getSettingOptions (key) {
// handle initial load to make sure everything is set-up and cached
// handleFirstLoad()
result = getCache(`pia_set_opt_${key}`, true);
if (result == "")
{
console.log(`Setting options with key "${key}" not found`)
}
return result;
}
// -----------------------------------------------------------------------------
// Get a setting value by key
function getSetting (key) {
// handle initial load to make sure everything is set-up and cached
// handleFirstLoad()
result = getCache(`pia_set_${key}`, true);
@@ -143,10 +200,10 @@ function cacheStrings()
{
// handle core strings and translations
var allLanguages = ["en_us", "es_es", "de_de"]; // needs to be same as in lang.php
var allLanguages = ["en_us", "es_es", "de_de", "fr_fr", "ru_ru", "nb_no"]; // needs to be same as in lang.php
allLanguages.forEach(function (language_code) {
$.get(`php/templates/language/${language_code}.json`, function (res) {
$.get(`php/templates/language/${language_code}.json?nocache=${Date.now()}`, function (res) {
// Iterate over each language
Object.entries(res).forEach(([key, value]) => {
// Store translations for each key-value pair
@@ -157,22 +214,31 @@ function cacheStrings()
// handle strings and translations from plugins
$.get('api/table_plugins_language_strings.json', function(res) {
$.get(`api/table_plugins_language_strings.json?nocache=${Date.now()}`, function(res) {
data = res["data"];
data.forEach((langString) => {
setCache(`pia_lang_${langString.String_Key}_${langString.Language_Code}`, langString.String_Value)
});
})
}).then(() => handleSuccess('cacheStrings')).catch(() => handleFailure('cacheStrings', cacheStrings)); // handle AJAX synchronization
}
// Get translated language string
function getString (key) {
// handle initial laod to make sure everything is set-up and cached
handleFirstLoad()
UI_LANG = getSetting("UI_LANG");
// //
// if(UI_LANG == "")
// {
// }
lang_code = 'en_us';
switch(UI_LANG)
@@ -186,6 +252,15 @@ function getString (key) {
case 'German':
lang_code = 'de_de';
break;
case 'French':
lang_code = 'fr_fr';
break;
case 'Norwegian':
lang_code = 'nb_no';
break;
case 'Russian':
lang_code = 'ru_ru';
break;
}
result = getCache(`pia_lang_${key}_${lang_code}`, true);
@@ -357,7 +432,7 @@ function handle_locked_DB(data)
{
if(data.includes('database is locked'))
{
console.log(data)
// console.log(data)
showSpinner()
setTimeout(function() {
@@ -368,9 +443,7 @@ function handle_locked_DB(data)
// -----------------------------------------------------------------------------
function numberArrayFromString(data)
{
{
data = JSON.parse(sanitize(data));
return data.replace(/\[|\]/g, '').split(',').map(Number);
}
@@ -466,13 +539,71 @@ function settingsChanged()
}
// -----------------------------------------------------------------------------
// Get Anchor from URL
function getUrlAnchor(defaultValue){
target = defaultValue
var url = window.location.href;
if (url.includes("#")) {
// default selection
selectedTab = defaultValue
// the #target from the url
target = window.location.hash.substr(1)
// get only the part between #...?
if(target.includes('?'))
{
target = target.split('?')[0]
}
return target
}
}
// -----------------------------------------------------------------------------
// get query string from URL
function getQueryString(key){
params = new Proxy(new URLSearchParams(window.location.search), {
get: (searchParams, prop) => searchParams.get(prop),
});
tmp = params[key]
if(emptyArr.includes(tmp))
{
var queryParams = {};
fullUrl = window.location.toString();
// console.log(fullUrl);
if (fullUrl.includes('?')) {
var queryString = fullUrl.split('?')[1];
// Split the query string into individual parameters
var paramsArray = queryString.split('&');
// Loop through the parameters array
paramsArray.forEach(function(param) {
// Split each parameter into key and value
var keyValue = param.split('=');
var keyTmp = decodeURIComponent(keyValue[0]);
var value = decodeURIComponent(keyValue[1] || '');
// Store key-value pair in the queryParams object
queryParams[keyTmp] = value;
});
}
// console.log(queryParams);
tmp = queryParams[key]
}
result = emptyArr.includes(tmp) ? "" : tmp;
return result
@@ -515,10 +646,37 @@ function debugTimer () {
// -----------------------------------------------------------------------------
// Open url in new tab
function openInNewTab (url) {
window.open(url, "_blank");
}
// -----------------------------------------------------------------------------
// Navigate to URL if the current URL is not in the provided list of URLs
function openUrl(urls) {
var currentUrl = window.location.href;
var mainUrl = currentUrl.match(/^.*?(?=#|\?|$)/)[0]; // Extract main URL
var isMatch = false;
$.each(urls,function(index, obj){
// remove . for comaprison if in the string, e.g.: ./devices.php
arrayUrl = obj.replace('.','')
// check if we are on a url contained in the array
if(mainUrl.includes(arrayUrl))
{
isMatch = true;
}
});
// if we are not, redirect
if (isMatch == false) {
window.location.href = urls[0]; // Redirect to the first URL in the list if not found
}
}
// -----------------------------------------------------------------------------
function navigateToDeviceWithIp (ip) {
@@ -633,30 +791,51 @@ function isRandomMAC(mac)
if (input === '[]') {
return [];
}
// Regex patterns
// Regex pattern for brackets
const patternBrackets = /(^\s*\[)|(\]\s*$)/g;
const patternQuotes = /(^\s*')|('\s*$)/g;
const replacement = '';
// Remove brackets
const noBrackets = input.replace(patternBrackets, replacement);
const options = [];
// Create array
const optionsTmp = noBrackets.split(',');
// Handle only one item in array
if (optionsTmp.length === 0) {
return [noBrackets.replace(patternQuotes, replacement)];
// Detect the type of quote used after the opening bracket
const firstChar = noBrackets.trim()[0];
const isDoubleQuoted = firstChar === '"';
const isSingleQuoted = firstChar === "'";
// Create array while handling commas within quoted segments
let currentSegment = '';
let withinQuotes = false;
for (let i = 0; i < noBrackets.length; i++) {
const char = noBrackets[i];
if ((char === '"' && !isSingleQuoted) || (char === "'" && !isDoubleQuoted)) {
withinQuotes = !withinQuotes;
}
if (char === ',' && !withinQuotes) {
options.push(currentSegment.trim());
currentSegment = '';
} else {
currentSegment += char;
}
}
// Remove quotes
optionsTmp.forEach(item => {
options.push(item.replace(patternQuotes, replacement).trim());
// Push the last segment
options.push(currentSegment.trim());
// Remove quotes based on detected type
options.forEach((item, index) => {
let trimmedItem = item.trim();
// Check if the string starts and ends with the same type of quote
if ((isDoubleQuoted && trimmedItem.startsWith('"') && trimmedItem.endsWith('"')) ||
(isSingleQuoted && trimmedItem.startsWith("'") && trimmedItem.endsWith("'"))) {
// Remove the quotes
trimmedItem = trimmedItem.substring(1, trimmedItem.length - 1);
}
options[index] = trimmedItem;
});
return options;
}
@@ -666,14 +845,14 @@ function isRandomMAC(mac)
function getDeviceDataByMacAddress(macAddress, dbColumn) {
const sessionDataKey = 'devicesListAll_JSON';
const sessionData = sessionStorage.getItem(sessionDataKey);
const devicesCache = getCache(sessionDataKey);
if (!sessionData) {
console.log(`Session variable "${sessionDataKey}" not found.`);
if (!devicesCache || devicesCache == "") {
console.error(`Session variable "${sessionDataKey}" not found.`);
return "Unknown";
}
const devices = JSON.parse(sessionData);
const devices = JSON.parse(devicesCache);
for (const device of devices) {
if (device["dev_MAC"].toLowerCase() === macAddress.toLowerCase()) {
@@ -692,12 +871,27 @@ function initDeviceListAll_JSON()
$.get('api/table_devices.json', function(data) {
console.log(data)
// console.log(data)
devicesListAll_JSON = data["data"]
setCache('devicesListAll_JSON', JSON.stringify(devicesListAll_JSON))
});
devicesListAll_JSON_str = JSON.stringify(devicesListAll_JSON)
if(devicesListAll_JSON_str == "")
{
setTimeout(() => {
initDeviceListAll_JSON()
}, 1000);
}
// console.log("devicesListAll_JSON_str");
// console.log(devicesListAll_JSON_str);
setCache('devicesListAll_JSON', devicesListAll_JSON_str)
// console.log(getCache('devicesListAll_JSON'))
}).then(() => handleSuccess('initDeviceListAll_JSON')).catch(() => handleFailure('initDeviceListAll_JSON', initDeviceListAll_JSON)); // handle AJAX synchronization
}
@@ -722,18 +916,7 @@ function getGuid() {
// UI
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// Genrate work-in-progress icons
function workInProgress() {
console.log()
if($(".work-in-progress").html().trim() == "")
{
$(".work-in-progress").append(`
<a href="https://github.com/jokob-sk/Pi.Alert/issues" target="_blank">
<b class="pointer" title="${getString("Gen_Work_In_Progress")}">🦺</b>
</a>
`)
}
}
// -----------------------------------------------------------------------------
// Loading Spinner overlay
@@ -741,8 +924,7 @@ function workInProgress() {
function showSpinner(stringKey='Loading')
{
if($("#loadingSpinner").length)
{
{
$("#loadingSpinner").show();
}
else{
@@ -768,15 +950,261 @@ function hideSpinner()
$("#loadingSpinner").hide()
}
// --------------------------------------------------------
// Calls a backend function to add a front-end event to an execution queue
function updateApi()
{
// value has to be in format event|param. e.g. run|ARPSCAN
action = `update_api|devices,appevents`
$.ajax({
method: "POST",
url: "php/server/util.php",
data: { function: "addToExecutionQueue", action: action },
success: function(data, textStatus) {
console.log(data)
}
})
}
// -----------------------------------------------------------------------------
// handling smooth scrolling
// -----------------------------------------------------------------------------
function setupSmoothScrolling() {
// Function to scroll to the element
function scrollToElement(id) {
$('html, body').animate({
scrollTop: $("#" + id).offset().top - 50
}, 1000);
}
// Scroll to the element when clicking on anchor links
$('a[href*="#"]').on('click', function(event) {
var href = $(this).attr('href');
if (href !=='#' && href && href.includes('#') && !$(this).is('[data-toggle="collapse"]')) {
var id = href.substring(href.indexOf("#") + 1); // Get the ID from the href attribute
if ($("#" + id).length > 0) {
event.preventDefault(); // Prevent default anchor behavior
scrollToElement(id); // Scroll to the element
}
}
});
// Check if there's an ID in the URL and scroll to it
var url = window.location.href;
if (url.includes("#")) {
var idFromURL = url.substring(url.indexOf("#") + 1);
if ($("#" + idFromURL).length > 0) {
scrollToElement(idFromURL);
}
}
}
// -------------------------------------------------------------------
// Function to check if options_params contains a parameter with type "sql"
function hasSqlType(params) {
for (let param of params) {
if (param.type === "sql") {
return true; // Found a parameter with type "sql"
}
}
return false; // No parameter with type "sql" found
}
// -------------------------------------------------------------------
// Function to check if string is SQL query
function isSQLQuery(query) {
// Regular expression to match common SQL keywords and syntax with word boundaries
var sqlRegex = /\b(SELECT|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER|FROM|JOIN|WHERE|SET|VALUES|GROUP BY|ORDER BY|LIMIT)\b/i;
return sqlRegex.test(query);
}
// -------------------------------------------------------------------
// Get corresponding plugin setting object
function getPluginSettingObject(pluginsData, setting_key, unique_prefix ) {
result = {}
unique_prefix == undefined ? unique_prefix = setting_key.split("_")[0] : unique_prefix = unique_prefix;
$.each(pluginsData, function (i, plgnObj){
// go thru plugins
if(plgnObj.unique_prefix == unique_prefix)
{
// go thru plugin settings
$.each(plgnObj["settings"], function (j, setObj){
if(`${unique_prefix}_${setObj.function}` == setting_key)
{
result = setObj
}
});
}
});
return result
}
// -------------------------------------------------------------------
// Resolve all option parameters
function resolveParams(params, template) {
params.forEach(param => {
// Check if the template includes the parameter name
if (template.includes("{" + param.name + "}")) {
// If the parameter type is 'setting', retrieve setting value
if (param.type == "setting") {
var value = getSetting(param.value);
// Replace placeholder with setting value
template = template.replace("{" + param.name + "}", value);
} else {
// If the parameter type is not 'setting', use the provided value
template = template.replace("{" + param.name + "}", param.value);
}
}
});
// Log the resolved template
// console.log(template);
// Return the resolved template
return template;
}
// -----------------------------------------------------------------------------
// check if two arrays contain same values even if out of order
function arraysContainSameValues(arr1, arr2) {
// Sort and stringify arrays, then compare
return JSON.stringify(arr1.slice().sort()) === JSON.stringify(arr2.slice().sort());
}
// -----------------------------------------------------------------------------
// initialize
// -----------------------------------------------------------------------------
cacheSettings()
cacheStrings()
initDeviceListAll_JSON()
workInProgress()
// Define a unique key for storing the flag in sessionStorage
var sessionStorageKey = "myScriptExecuted_pialert_common";
// -----------------------------------------------------------------------------
// Clearing all the caches
function clearCache()
{
resetInitializedFlag()
window.location.reload()
}
// -----------------------------------------------------------------------------
function resetInitializedFlag()
{
// Clear local storage
localStorage.clear();
// Set the flag in sessionStorage to indicate that the code and cahce needs to be reloaded
sessionStorage.setItem(sessionStorageKey, "false");
}
// -----------------------------------------------------------------------------
// check if cache needs to be refreshed because of setting changes
$.get('api/app_state.json?nocache=' + Date.now(), function(appState) {
console.log(appState["settingsImported"]*1000)
importedMiliseconds = parseInt((appState["settingsImported"]*1000));
lastReloaded = parseInt(sessionStorage.getItem(sessionStorageKey + '_time'));
if(importedMiliseconds > lastReloaded)
{
console.log("Cache needs to be refreshed because of setting changes");
setTimeout(() => {
resetInitializedFlag()
location.reload();
}, 500);
}
});
// -----------------------------------------------------------------------------
// Display spinner and reload page if not yet initialized
function handleFirstLoad()
{
if(!pialert_common_init)
{
setTimeout(function() {
location.reload();
}, 1000);
}
}
// -----------------------------------------------------------------------------
// Check if the code has been executed before by checking sessionStorage
var pialert_common_init = sessionStorage.getItem(sessionStorageKey) === "true";
// Define a function that will execute the code only once
function executeOnce() {
if (!pialert_common_init) {
resetInitializedFlag()
showSpinner()
// to keep track of completed AJAX calls
completedCalls = []
completedCalls_final = ['cacheSettings', 'cacheStrings', 'initDeviceListAll_JSON'];
// Your initialization code here
cacheSettings();
cacheStrings();
initDeviceListAll_JSON();
}
}
// -----------------------------------------------------------------------------
// Function to handle successful completion of an AJAX call
const handleSuccess = (callName) => {
console.log(`AJAX call ${callName} successful`);
// store completed call
completedCalls.push(callName)
onAllCallsComplete();
};
// -----------------------------------------------------------------------------
// Function to handle failure of an AJAX call
const handleFailure = (callName, callback) => {
// Handle AJAX call failure here
console.error(`AJAX call ${callName} failed`);
// try until successful
callback()
};
// -----------------------------------------------------------------------------
// Function to execute when all AJAX calls have completed
const onAllCallsComplete = () => {
// Check if all three AJAX calls have completed
if (arraysContainSameValues(completedCalls,completedCalls_final)) {
// Set the flag in sessionStorage to indicate that the code has been executed
// and save time when last time the page was initialized
sessionStorage.setItem(sessionStorageKey, "true");
const millisecondsNow = Date.now();
sessionStorage.setItem(sessionStorageKey + '_time', millisecondsNow);
console.log("init pialert_common.js");
}
};
// Call the function to execute the code
executeOnce();
console.log("init pialert_common.js")

View File

@@ -163,4 +163,9 @@
}
return true; // Return true if no schedules are found
}
}

194
front/js/ui_components.js Executable file
View File

@@ -0,0 +1,194 @@
/* -----------------------------------------------------------------------------
* Pi.Alert
* Open Source Network Guard / WIFI & LAN intrusion detector
*
* ui_components.js - Front module. Common UI components
*-------------------------------------------------------------------------------
# jokob jokob@duck.com GNU GPLv3
----------------------------------------------------------------------------- */
// -----------------------------------------------------------------------------
// Initialize device selectors / pickers fields
// -----------------------------------------------------------------------------
function initDeviceSelectors() {
console.log(devicesList)
// Retrieve device list from session variable
var devicesListAll_JSON = getCache('devicesListAll_JSON');
var devicesList = JSON.parse(devicesListAll_JSON);
console.log(devicesList);
// Check if both device list exists
if (devicesListAll_JSON) {
// Parse the JSON string to get the device list array
var devicesList = JSON.parse(devicesListAll_JSON);
var selectorFieldsHTML = ''
// Loop through the devices list
devicesList.forEach(function(device) {
selectorFieldsHTML += `<option value="${device.dev_MAC}">${device.dev_Name}</option>`;
});
selector = `<div class="db_info_table_row col-sm-12" >
<div class="form-group" >
<div class="input-group col-sm-12 " >
<select class="form-control select2 select2-hidden-accessible" multiple="" style="width: 100%;" tabindex="-1" aria-hidden="true">
${selectorFieldsHTML}
</select>
</div>
</div>
</div>`
// Find HTML elements with class "deviceSelector" and append selector field
$('.deviceSelector').append(selector);
}
// Initialize selected items after a delay so selected macs are available in the context
setTimeout(function(){
// Retrieve MAC addresses from query string or cache
var macs = getQueryString('macs') || getCache('selectedDevices');
if(macs)
{
// Split MAC addresses if they are comma-separated
macs = macs.split(',');
console.log(macs)
// Loop through macs to be selected list
macs.forEach(function(mac) {
// Create the option and append to Select2
var option = new Option($('.deviceSelector select option[value="' + mac + '"]').html(), mac, true, true);
$('.deviceSelector select').append(option).trigger('change');
});
}
}, 10);
}
// --------------------------------------------------------
//Initialize Select2 Elements and make them sortable
$(function () {
// Iterate over each Select2 dropdown
$('.select2').each(function() {
var selectEl = $(this).select2();
// Apply sortable functionality to the dropdown's dropdown-container
selectEl.next().children().children().children().sortable({
containment: 'parent',
update: function () {
var sortedValues = $(this).children().map(function() {
return $(this).attr('title');
}).get();
var sortedOptions = selectEl.find('option').sort(function(a, b) {
return sortedValues.indexOf($(a).text()) - sortedValues.indexOf($(b).text());
});
// Replace all options in selectEl
selectEl.empty().append(sortedOptions);
// Trigger change event on Select2
selectEl.trigger('change');
}
});
});
});
// -----------------------------------------------------------------------------
// Initiate dropdown
function initSettingDropdown(settingKey, valuesArray, targetLocation)
{
var optionsHtml = ""
var targetLocation_options = settingKey + "_initSettingDropdown"
optionsArray = createArray(getSettingOptions(settingKey))
// check if the result is a SQL query
if(isSQLQuery(optionsArray[0]))
{
optionsHtml += `<option id="${targetLocation_options}"></option>`;
readData(optionsArray[0], generateDropdownOptions, valuesArray, targetLocation_options);
} else // this should be already an array, e.g. from a setting or pre-defined
{
optionsArray.forEach(option => {
let selected = valuesArray.includes(option) ? 'selected' : '';
optionsHtml += `<option value="${option}" ${selected}>${option}</option>`;
});
// Replace the specified placeholder div with the resulting HTML
setTimeout(() => {
$("#" + targetLocation).replaceWith(optionsHtml);
}, 50);
}
}
// -----------------------------------------------------------------------------
// Data processors
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// Processor to generate options for a dropdown menu
function generateDropdownOptions(data, valuesArray) {
var optionsHtml = "";
data.forEach(function(item) {
let selected = valuesArray.includes(item.id) ? 'selected' : '';
optionsHtml += `<option value="${item.id}" ${selected}>${item.name}</option>`;
});
return `${optionsHtml}`;
}
// -----------------------------------------------------------------------------
// Processor to generate a list
function generateList(data, valuesArray) {
var listHtml = "";
data.forEach(function(item) {
let selected = valuesArray.includes(item.id) ? 'selected' : '';
listHtml += `<li ${selected}>${item.name}</li>`;
});
listHtml += "";
return listHtml;
}
// -----------------------------------------------------------------------------
// initialize
// -----------------------------------------------------------------------------
initDeviceSelectors();
console.log("init ui_components.js")

View File

@@ -0,0 +1 @@
table.dataTable tbody>tr.selected,table.dataTable tbody>tr>.selected{background-color:#b0bed9}table.dataTable.stripe tbody>tr.odd.selected,table.dataTable.stripe tbody>tr.odd>.selected,table.dataTable.display tbody>tr.odd.selected,table.dataTable.display tbody>tr.odd>.selected{background-color:#acbad4}table.dataTable.hover tbody>tr.selected:hover,table.dataTable.hover tbody>tr>.selected:hover,table.dataTable.display tbody>tr.selected:hover,table.dataTable.display tbody>tr>.selected:hover{background-color:#aab7d1}table.dataTable.order-column tbody>tr.selected>.sorting_1,table.dataTable.order-column tbody>tr.selected>.sorting_2,table.dataTable.order-column tbody>tr.selected>.sorting_3,table.dataTable.order-column tbody>tr>.selected,table.dataTable.display tbody>tr.selected>.sorting_1,table.dataTable.display tbody>tr.selected>.sorting_2,table.dataTable.display tbody>tr.selected>.sorting_3,table.dataTable.display tbody>tr>.selected{background-color:#acbad5}table.dataTable.display tbody>tr.odd.selected>.sorting_1,table.dataTable.order-column.stripe tbody>tr.odd.selected>.sorting_1{background-color:#a6b4cd}table.dataTable.display tbody>tr.odd.selected>.sorting_2,table.dataTable.order-column.stripe tbody>tr.odd.selected>.sorting_2{background-color:#a8b5cf}table.dataTable.display tbody>tr.odd.selected>.sorting_3,table.dataTable.order-column.stripe tbody>tr.odd.selected>.sorting_3{background-color:#a9b7d1}table.dataTable.display tbody>tr.even.selected>.sorting_1,table.dataTable.order-column.stripe tbody>tr.even.selected>.sorting_1{background-color:#acbad5}table.dataTable.display tbody>tr.even.selected>.sorting_2,table.dataTable.order-column.stripe tbody>tr.even.selected>.sorting_2{background-color:#aebcd6}table.dataTable.display tbody>tr.even.selected>.sorting_3,table.dataTable.order-column.stripe tbody>tr.even.selected>.sorting_3{background-color:#afbdd8}table.dataTable.display tbody>tr.odd>.selected,table.dataTable.order-column.stripe tbody>tr.odd>.selected{background-color:#a6b4cd}table.dataTable.display tbody>tr.even>.selected,table.dataTable.order-column.stripe tbody>tr.even>.selected{background-color:#acbad5}table.dataTable.display tbody>tr.selected:hover>.sorting_1,table.dataTable.order-column.hover tbody>tr.selected:hover>.sorting_1{background-color:#a2aec7}table.dataTable.display tbody>tr.selected:hover>.sorting_2,table.dataTable.order-column.hover tbody>tr.selected:hover>.sorting_2{background-color:#a3b0c9}table.dataTable.display tbody>tr.selected:hover>.sorting_3,table.dataTable.order-column.hover tbody>tr.selected:hover>.sorting_3{background-color:#a5b2cb}table.dataTable.display tbody>tr:hover>.selected,table.dataTable.display tbody>tr>.selected:hover,table.dataTable.order-column.hover tbody>tr:hover>.selected,table.dataTable.order-column.hover tbody>tr>.selected:hover{background-color:#a2aec7}table.dataTable tbody td.select-checkbox,table.dataTable tbody th.select-checkbox{position:relative}table.dataTable tbody td.select-checkbox:before,table.dataTable tbody td.select-checkbox:after,table.dataTable tbody th.select-checkbox:before,table.dataTable tbody th.select-checkbox:after{display:block;position:absolute;top:1.2em;left:50%;width:12px;height:12px;box-sizing:border-box}table.dataTable tbody td.select-checkbox:before,table.dataTable tbody th.select-checkbox:before{content:" ";margin-top:-6px;margin-left:-6px;border:1px solid black;border-radius:3px}table.dataTable tr.selected td.select-checkbox:after,table.dataTable tr.selected th.select-checkbox:after{content:"✓";font-size:20px;margin-top:-19px;margin-left:-6px;text-align:center;text-shadow:1px 1px #b0bed9,-1px -1px #b0bed9,1px -1px #b0bed9,-1px 1px #b0bed9}table.dataTable.compact tbody td.select-checkbox:before,table.dataTable.compact tbody th.select-checkbox:before{margin-top:-12px}table.dataTable.compact tr.selected td.select-checkbox:after,table.dataTable.compact tr.selected th.select-checkbox:after{margin-top:-16px}div.dataTables_wrapper span.select-info,div.dataTables_wrapper span.select-item{margin-left:.5em}@media screen and (max-width: 640px){div.dataTables_wrapper span.select-info,div.dataTables_wrapper span.select-item{margin-left:0;display:block}}

View File

@@ -0,0 +1,38 @@
/*!
Copyright 2015-2021 SpryMedia Ltd.
This source file is free software, available under the following license:
MIT license - http://datatables.net/license/mit
This source file is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
For details please refer to: http://www.datatables.net/extensions/select
Select for DataTables 1.3.3
2015-2021 SpryMedia Ltd - datatables.net/license/mit
*/
(function(h){"function"===typeof define&&define.amd?define(["jquery","datatables.net"],function(q){return h(q,window,document)}):"object"===typeof exports?module.exports=function(q,t){q||(q=window);t&&t.fn.dataTable||(t=require("datatables.net")(q,t).$);return h(t,q,q.document)}:h(jQuery,window,document)})(function(h,q,t,n){function E(a,b,c){var d=function(g,f){if(g>f){var k=f;f=g;g=k}var l=!1;return a.columns(":visible").indexes().filter(function(p){p===g&&(l=!0);return p===f?(l=!1,!0):l})};var e=
function(g,f){var k=a.rows({search:"applied"}).indexes();if(k.indexOf(g)>k.indexOf(f)){var l=f;f=g;g=l}var p=!1;return k.filter(function(u){u===g&&(p=!0);return u===f?(p=!1,!0):p})};a.cells({selected:!0}).any()||c?(d=d(c.column,b.column),c=e(c.row,b.row)):(d=d(0,b.column),c=e(0,b.row));c=a.cells(c,d).flatten();a.cells(b,{selected:!0}).any()?a.cells(c).deselect():a.cells(c).select()}function A(a){var b=a.settings()[0]._select.selector;h(a.table().container()).off("mousedown.dtSelect",b).off("mouseup.dtSelect",
b).off("click.dtSelect",b);h("body").off("click.dtSelect"+a.table().node().id.replace(/[^a-zA-Z0-9\-_]/g,"-"))}function F(a){var b=h(a.table().container()),c=a.settings()[0],d=c._select.selector,e;b.on("mousedown.dtSelect",d,function(g){if(g.shiftKey||g.metaKey||g.ctrlKey)b.css("-moz-user-select","none").one("selectstart.dtSelect",d,function(){return!1});q.getSelection&&(e=q.getSelection())}).on("mouseup.dtSelect",d,function(){b.css("-moz-user-select","")}).on("click.dtSelect",d,function(g){var f=
a.select.items();if(e){var k=q.getSelection();if((!k.anchorNode||h(k.anchorNode).closest("table")[0]===a.table().node())&&k!==e)return}k=a.settings()[0];var l=a.settings()[0].oClasses.sWrapper.trim().replace(/ +/g,".");if(h(g.target).closest("div."+l)[0]==a.table().container()&&(l=a.cell(h(g.target).closest("td, th")),l.any())){var p=h.Event("user-select.dt");r(a,p,[f,l,g]);p.isDefaultPrevented()||(p=l.index(),"row"===f?(f=p.row,B(g,a,k,"row",f)):"column"===f?(f=l.index().column,B(g,a,k,"column",
f)):"cell"===f&&(f=l.index(),B(g,a,k,"cell",f)),k._select_lastCell=p)}});h("body").on("click.dtSelect"+a.table().node().id.replace(/[^a-zA-Z0-9\-_]/g,"-"),function(g){!c._select.blurable||h(g.target).parents().filter(a.table().container()).length||0===h(g.target).parents("html").length||h(g.target).parents("div.DTE").length||x(c,!0)})}function r(a,b,c,d){if(!d||a.flatten().length)"string"===typeof b&&(b+=".dt"),c.unshift(a),h(a.table().node()).trigger(b,c)}function I(a){var b=a.settings()[0];if(b._select.info&&
b.aanFeatures.i&&"api"!==a.select.style()){var c=a.rows({selected:!0}).flatten().length,d=a.columns({selected:!0}).flatten().length,e=a.cells({selected:!0}).flatten().length,g=function(f,k,l){f.append(h('<span class="select-item"/>').append(a.i18n("select."+k+"s",{_:"%d "+k+"s selected",0:"",1:"1 "+k+" selected"},l)))};h.each(b.aanFeatures.i,function(f,k){k=h(k);f=h('<span class="select-info"/>');g(f,"row",c);g(f,"column",d);g(f,"cell",e);var l=k.children("span.select-info");l.length&&l.remove();
""!==f.text()&&k.append(f)})}}function J(a){var b=new m.Api(a);a.aoRowCreatedCallback.push({fn:function(c,d,e){d=a.aoData[e];d._select_selected&&h(c).addClass(a._select.className);c=0;for(e=a.aoColumns.length;c<e;c++)(a.aoColumns[c]._select_selected||d._selected_cells&&d._selected_cells[c])&&h(d.anCells[c]).addClass(a._select.className)},sName:"select-deferRender"});b.on("preXhr.dt.dtSelect",function(c,d){if(d===b.settings()[0]){var e=b.rows({selected:!0}).ids(!0).filter(function(f){return f!==n}),
g=b.cells({selected:!0}).eq(0).map(function(f){var k=b.row(f.row).id(!0);return k?{row:k,column:f.column}:n}).filter(function(f){return f!==n});b.one("draw.dt.dtSelect",function(){b.rows(e).select();g.any()&&g.each(function(f){b.cells(f.row,f.column).select()})})}});b.on("draw.dtSelect.dt select.dtSelect.dt deselect.dtSelect.dt info.dt",function(){I(b)});b.on("destroy.dtSelect",function(){b.rows({selected:!0}).deselect();A(b);b.off(".dtSelect")})}function G(a,b,c,d){var e=a[b+"s"]({search:"applied"}).indexes();
d=h.inArray(d,e);var g=h.inArray(c,e);if(a[b+"s"]({selected:!0}).any()||-1!==d){if(d>g){var f=g;g=d;d=f}e.splice(g+1,e.length);e.splice(0,d)}else e.splice(h.inArray(c,e)+1,e.length);a[b](c,{selected:!0}).any()?(e.splice(h.inArray(c,e),1),a[b+"s"](e).deselect()):a[b+"s"](e).select()}function x(a,b){if(b||"single"===a._select.style)a=new m.Api(a),a.rows({selected:!0}).deselect(),a.columns({selected:!0}).deselect(),a.cells({selected:!0}).deselect()}function B(a,b,c,d,e){var g=b.select.style(),f=b.select.toggleable(),
k=b[d](e,{selected:!0}).any();if(!k||f)"os"===g?a.ctrlKey||a.metaKey?b[d](e).select(!k):a.shiftKey?"cell"===d?E(b,e,c._select_lastCell||null):G(b,d,e,c._select_lastCell?c._select_lastCell[d]:null):(a=b[d+"s"]({selected:!0}),k&&1===a.flatten().length?b[d](e).deselect():(a.deselect(),b[d](e).select())):"multi+shift"==g?a.shiftKey?"cell"===d?E(b,e,c._select_lastCell||null):G(b,d,e,c._select_lastCell?c._select_lastCell[d]:null):b[d](e).select(!k):b[d](e).select(!k)}function y(a,b){return function(c){return c.i18n("buttons."+
a,b)}}function C(a){a=a._eventNamespace;return"draw.dt.DT"+a+" select.dt.DT"+a+" deselect.dt.DT"+a}function K(a,b){return-1!==h.inArray("rows",b.limitTo)&&a.rows({selected:!0}).any()||-1!==h.inArray("columns",b.limitTo)&&a.columns({selected:!0}).any()||-1!==h.inArray("cells",b.limitTo)&&a.cells({selected:!0}).any()?!0:!1}var m=h.fn.dataTable;m.select={};m.select.version="1.3.3";m.select.init=function(a){var b=a.settings()[0],c=b.oInit.select,d=m.defaults.select;c=c===n?d:c;d="row";var e="api",g=!1,
f=!0,k=!0,l="td, th",p="selected",u=!1;b._select={};!0===c?(e="os",u=!0):"string"===typeof c?(e=c,u=!0):h.isPlainObject(c)&&(c.blurable!==n&&(g=c.blurable),c.toggleable!==n&&(f=c.toggleable),c.info!==n&&(k=c.info),c.items!==n&&(d=c.items),e=c.style!==n?c.style:"os",u=!0,c.selector!==n&&(l=c.selector),c.className!==n&&(p=c.className));a.select.selector(l);a.select.items(d);a.select.style(e);a.select.blurable(g);a.select.toggleable(f);a.select.info(k);b._select.className=p;h.fn.dataTable.ext.order["select-checkbox"]=
function(z,L){return this.api().column(L,{order:"index"}).nodes().map(function(H){return"row"===z._select.items?h(H).parent().hasClass(z._select.className):"cell"===z._select.items?h(H).hasClass(z._select.className):!1})};!u&&h(a.table().node()).hasClass("selectable")&&a.select.style("os")};h.each([{type:"row",prop:"aoData"},{type:"column",prop:"aoColumns"}],function(a,b){m.ext.selector[b.type].push(function(c,d,e){d=d.selected;var g=[];if(!0!==d&&!1!==d)return e;for(var f=0,k=e.length;f<k;f++){var l=
c[b.prop][e[f]];(!0===d&&!0===l._select_selected||!1===d&&!l._select_selected)&&g.push(e[f])}return g})});m.ext.selector.cell.push(function(a,b,c){b=b.selected;var d=[];if(b===n)return c;for(var e=0,g=c.length;e<g;e++){var f=a.aoData[c[e].row];(!0===b&&f._selected_cells&&!0===f._selected_cells[c[e].column]||!(!1!==b||f._selected_cells&&f._selected_cells[c[e].column]))&&d.push(c[e])}return d});var v=m.Api.register,w=m.Api.registerPlural;v("select()",function(){return this.iterator("table",function(a){m.select.init(new m.Api(a))})});
v("select.blurable()",function(a){return a===n?this.context[0]._select.blurable:this.iterator("table",function(b){b._select.blurable=a})});v("select.toggleable()",function(a){return a===n?this.context[0]._select.toggleable:this.iterator("table",function(b){b._select.toggleable=a})});v("select.info()",function(a){return a===n?this.context[0]._select.info:this.iterator("table",function(b){b._select.info=a})});v("select.items()",function(a){return a===n?this.context[0]._select.items:this.iterator("table",
function(b){b._select.items=a;r(new m.Api(b),"selectItems",[a])})});v("select.style()",function(a){return a===n?this.context[0]._select.style:this.iterator("table",function(b){b._select.style=a;b._select_init||J(b);var c=new m.Api(b);A(c);"api"!==a&&F(c);r(new m.Api(b),"selectStyle",[a])})});v("select.selector()",function(a){return a===n?this.context[0]._select.selector:this.iterator("table",function(b){A(new m.Api(b));b._select.selector=a;"api"!==b._select.style&&F(new m.Api(b))})});w("rows().select()",
"row().select()",function(a){var b=this;if(!1===a)return this.deselect();this.iterator("row",function(c,d){x(c);c.aoData[d]._select_selected=!0;h(c.aoData[d].nTr).addClass(c._select.className)});this.iterator("table",function(c,d){r(b,"select",["row",b[d]],!0)});return this});w("columns().select()","column().select()",function(a){var b=this;if(!1===a)return this.deselect();this.iterator("column",function(c,d){x(c);c.aoColumns[d]._select_selected=!0;d=(new m.Api(c)).column(d);h(d.header()).addClass(c._select.className);
h(d.footer()).addClass(c._select.className);d.nodes().to$().addClass(c._select.className)});this.iterator("table",function(c,d){r(b,"select",["column",b[d]],!0)});return this});w("cells().select()","cell().select()",function(a){var b=this;if(!1===a)return this.deselect();this.iterator("cell",function(c,d,e){x(c);d=c.aoData[d];d._selected_cells===n&&(d._selected_cells=[]);d._selected_cells[e]=!0;d.anCells&&h(d.anCells[e]).addClass(c._select.className)});this.iterator("table",function(c,d){r(b,"select",
["cell",b.cells(b[d]).indexes().toArray()],!0)});return this});w("rows().deselect()","row().deselect()",function(){var a=this;this.iterator("row",function(b,c){b.aoData[c]._select_selected=!1;b._select_lastCell=null;h(b.aoData[c].nTr).removeClass(b._select.className)});this.iterator("table",function(b,c){r(a,"deselect",["row",a[c]],!0)});return this});w("columns().deselect()","column().deselect()",function(){var a=this;this.iterator("column",function(b,c){b.aoColumns[c]._select_selected=!1;var d=
new m.Api(b),e=d.column(c);h(e.header()).removeClass(b._select.className);h(e.footer()).removeClass(b._select.className);d.cells(null,c).indexes().each(function(g){var f=b.aoData[g.row],k=f._selected_cells;!f.anCells||k&&k[g.column]||h(f.anCells[g.column]).removeClass(b._select.className)})});this.iterator("table",function(b,c){r(a,"deselect",["column",a[c]],!0)});return this});w("cells().deselect()","cell().deselect()",function(){var a=this;this.iterator("cell",function(b,c,d){c=b.aoData[c];c._selected_cells[d]=
!1;c.anCells&&!b.aoColumns[d]._select_selected&&h(c.anCells[d]).removeClass(b._select.className)});this.iterator("table",function(b,c){r(a,"deselect",["cell",a[c]],!0)});return this});var D=0;h.extend(m.ext.buttons,{selected:{text:y("selected","Selected"),className:"buttons-selected",limitTo:["rows","columns","cells"],init:function(a,b,c){var d=this;c._eventNamespace=".select"+D++;a.on(C(c),function(){d.enable(K(a,c))});this.disable()},destroy:function(a,b,c){a.off(c._eventNamespace)}},selectedSingle:{text:y("selectedSingle",
"Selected single"),className:"buttons-selected-single",init:function(a,b,c){var d=this;c._eventNamespace=".select"+D++;a.on(C(c),function(){var e=a.rows({selected:!0}).flatten().length+a.columns({selected:!0}).flatten().length+a.cells({selected:!0}).flatten().length;d.enable(1===e)});this.disable()},destroy:function(a,b,c){a.off(c._eventNamespace)}},selectAll:{text:y("selectAll","Select all"),className:"buttons-select-all",action:function(){this[this.select.items()+"s"]().select()}},selectNone:{text:y("selectNone",
"Deselect all"),className:"buttons-select-none",action:function(){x(this.settings()[0],!0)},init:function(a,b,c){var d=this;c._eventNamespace=".select"+D++;a.on(C(c),function(){var e=a.rows({selected:!0}).flatten().length+a.columns({selected:!0}).flatten().length+a.cells({selected:!0}).flatten().length;d.enable(0<e)});this.disable()},destroy:function(a,b,c){a.off(c._eventNamespace)}}});h.each(["Row","Column","Cell"],function(a,b){var c=b.toLowerCase();m.ext.buttons["select"+b+"s"]={text:y("select"+
b+"s","Select "+c+"s"),className:"buttons-select-"+c+"s",action:function(){this.select.items(c)},init:function(d){var e=this;d.on("selectItems.dt.DT",function(g,f,k){e.active(k===c)})}}});h(t).on("preInit.dt.dtSelect",function(a,b){"dt"===a.namespace&&m.select.init(new m.Api(b))});return m.select});

View File

@@ -243,6 +243,12 @@ $db->close();
<?= lang('Maintenance_Tools_Tab_Logging');?>
</a>
</li>
<li>
<a id="tab_multiEdit_id" href="#tab_multiEdit" data-toggle="tab">
<i class="fa fa-pencil pointer" ></i>
<?= lang('Device_MultiEdit');?>
</a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="tab_Settings">
@@ -501,8 +507,21 @@ $db->close();
</div>
</div>
<!-- ---------------------------Bulk edit -------------------------------------------- -->
<div class="tab-pane" id="tab_multiEdit">
<div class="db_info_table">
<div class="box box-solid">
<?php
require 'multiEditCore.php';
?>
</div>
</div>
</div>
</div>
<!-- ------------------------------------------------------------------------------ -->
</div>
<div class="box-body" style="text-align: center;">
@@ -763,20 +782,34 @@ function performLogManage() {
}
// --------------------------------------------------------
// scroll down the log areas
function scrollDown()
{
var areaIDs = ['pialert_log', 'pialert_front_log', 'IP_changes_log', 'stdout_log', 'stderr_log', 'pialert_pholus_log', 'pialert_pholus_lastrun_log', 'pialert_php_log'];
for (let i = 0; i < areaIDs.length; i++) {
setTimeout(() => {
var tempArea = $('#' + areaIDs[i]);
if (tempArea.length > 0)
var elementToCheck = $("#tab_Logging_id");
// Check if the parent <li> is active
if (elementToCheck.parent().hasClass("active")) {
{
$(tempArea[0]).scrollTop(tempArea[0].scrollHeight);
}
var areaIDs = ['pialert_log', 'pialert_front_log', 'IP_changes_log', 'stdout_log', 'stderr_log', 'pialert_pholus_log', 'pialert_pholus_lastrun_log', 'pialert_php_log'];
for (let i = 0; i < areaIDs.length; i++) {
}
var tempArea = $('#' + areaIDs[i]);
if (tempArea.length > 0)
{
$(tempArea[0]).scrollTop(tempArea[0].scrollHeight);
}
}
}
}
}, 200);
}
// --------------------------------------------------------
@@ -827,37 +860,12 @@ function initializeSelectedColumns () {
$("#columnsSelect").append(option).trigger('change');
$(option).attr('eee','eee')
}
});
}
// --------------------------------------------------------
//Initialize Select2 Elements and make them sortable
$(function () {
var selectEl = $('.select2').select2();
selectEl.next().children().children().children().sortable({
containment: 'parent',
update: function () {
var sortedValues = $(this).children().map(function() {
return $(this).attr('title');
}).get();
var sortedOptions = selectEl.find('option').sort(function(a, b) {
return sortedValues.indexOf($(a).text()) - sortedValues.indexOf($(b).text());
});
// Replace all options in selectEl
selectEl.empty().append(sortedOptions);
// Trigger change event on Select2
selectEl.trigger('change');
}
});
});
// --------------------------------------------------------
@@ -865,72 +873,79 @@ $(function () {
// --------------------------------------------------------
function initializeTabs () {
key = "activeMaintenanceTab"
setTimeout(function() {
// default selection
selectedTab = "tab_Settings"
key = "activeMaintenanceTab"
// the #target from the url
target = window.location.hash.substr(1)
// default selection
selectedTab = "tab_Settings"
// update cookie if target specified
if(target != "")
{
setCache(key, target+'_id') // _id is added so it doesn't conflict with AdminLTE tab behavior
}
// the #target from the url
target = window.location.hash.substr(1)
// get the tab id from the cookie (already overriden by the target)
if(!emptyArr.includes(getCache(key)))
{
selectedTab = getCache(key);
}
// Activate panel
$('.nav-tabs a[id='+ selectedTab +']').tab('show');
// When changed save new current tab
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
setCache(key, $(e.target).attr('id'))
});
// events on tab change
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
var target = $(e.target).attr("href") // activated tab
if(target == "#tab_Logging")
// get only the part between #...?
if(target.includes('?'))
{
scrollDown();
target = target.split('?')[0]
}
});
console.log(target);
// update cookie if target specified
if(target != "")
{
if (!selectedTab.endsWith("_id")) {
selectedTab = target + "_id";
}
setCache(key, selectedTab) // _id is added so it doesn't conflict with AdminLTE tab behavior
}
// get the tab id from the cookie (already overriden by the target)
if(!emptyArr.includes(getCache(key)))
{
selectedTab = getCache(key);
}
// Activate panel
$('.nav-tabs a[id='+ selectedTab +']').tab('show');
// When changed save new current tab
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
setCache(key, $(e.target).attr('id'))
});
// events on tab change
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
var target = $(e.target).attr("href") // activated tab
});
}, 50);
}
// --------------------------------------------------------
// save language in a cookie
$('#langselector').on('change', function (e) {
var optionSelected = $("option:selected", this);
var valueSelected = this.value;
setCookie("language",valueSelected )
location.reload();
});
// --------------------------------------------------------
// load footer asynchronously not to block the page load/other sections
window.onload = function asyncFooter()
{
initializeSelectedColumns();
scrollDown();
initializeTabs();
$("#lastCommit").append('<a href="https://github.com/jokob-sk/Pi.Alert/commits" target="_blank"><img alt="GitHub last commit" src="https://img.shields.io/github/last-commit/jokob-sk/pi.alert/main?logo=github"></a>');
$("#lastDockerUpdate").append(
'<a href="https://hub.docker.com/r/jokobsk/pi.alert/tags" target="_blank"><img alt="Docker last pushed" src="https://img.shields.io/badge/dynamic/json?color=blue&label=Last%20pushed&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"></a>');
'<a href="https://github.com/jokob-sk/Pi.Alert/releases" target="_blank"><img alt="Docker last pushed" src="https://img.shields.io/github/v/release/jokob-sk/Pi.Alert?color=0aa8d2&logoColor=fff&logo=GitHub&label=Latest"></a>');
}
// scroll to the latest log entrie sat teh bottom of the file
</script>
<link rel="stylesheet" href="lib/AdminLTE/bower_components/select2/dist/css/select2.min.css">
@@ -938,6 +953,3 @@ window.onload = function asyncFooter()
<script src="lib/AdminLTE/bower_components/jquery-ui/jquery-ui.min.js"></script>
<!-- ----------------------------------------------------------------------- -->
<script src="js/pialert_common.js"></script>

268
front/multiEditCore.php Executable file
View File

@@ -0,0 +1,268 @@
<div class="col-md-12">
<div class="box box-default">
<div class="box-header">
<h3 class="box-title"><?= lang('Gen_Selected_Devices');?></h3>
</div>
<div class="deviceSelector"></div>
<div class="callout callout-warning">
<h4><?= lang('Gen_Warning');?></h4>
<p><?= lang('Device_MultiEdit_Backup');?></p>
</div>
</div>
<div class="col-md-12">
<div class="box box-default">
<div class="box-header">
<h3 class="box-title"><?= lang('Device_MultiEdit_Fields');?></h3>
</div>
<div class="box-body">
<form id="multi-edit-form">
<!-- Form fields will be appended here -->
</form>
</div>
</div>
</div>
</div>
<div class="col-md-12">
<div class="box box-default">
<div class="box-header ">
<h3 class="box-title"><?= lang('Device_MultiEdit_MassActions');?></h3>
</div>
<div class="box-body">
<div class="col-md-2" style="">
<button type="button" class="btn btn-default pa-btn pa-btn-delete bg-red" id="btnDeleteMAC" onclick="askDeleteSelectedDevices()"><?= lang('Maintenance_Tool_del_selecteddev');?></button>
</div>
<div class="col-md-10"><?= lang('Maintenance_Tool_del_selecteddev_text');?></div>
</div>
</div>
</div>
</div>
<script defer>
// -------------------------------------------------------------------
// Get plugin and settings data from API endpoints
function getData(){
$.get('api/table_settings.json?nocache=' + Date.now(), function(res) {
settingsData = res["data"];
excludedColumns = ["NEWDEV_dev_MAC", "NEWDEV_dev_FirstConnection", "NEWDEV_dev_LastConnection", "NEWDEV_dev_LastNotification", "NEWDEV_dev_LastIP", "NEWDEV_dev_StaticIP", "NEWDEV_dev_ScanCycle", "NEWDEV_dev_PresentLastScan" ]
const relevantColumns = settingsData.filter(set =>
set.Group === "NEWDEV" &&
set.Code_Name.includes("_dev_") &&
!excludedColumns.includes(set.Code_Name) &&
!set.Code_Name.includes("__metadata")
);
const generateSimpleForm = columns => {
const form = $('#multi-edit-form');
const numColumns = 2; // Number of columns
// Calculate number of elements per column
const elementsPerColumn = Math.ceil(columns.length / numColumns);
// Divide columns equally
for (let i = 0; i < numColumns; i++) {
const column = $('<div>').addClass('col-md-6');
// Append form groups to the column
for (let j = i * elementsPerColumn; j < Math.min((i + 1) * elementsPerColumn, columns.length); j++) {
let inputType;
switch (columns[j].Type) {
case 'integer.checkbox':
case 'checkbox':
inputType = 'checkbox';
break;
case 'text.select':
inputType = 'text.select';
break;
default:
inputType = 'text';
break;
}
// Add classes specifically for checkboxes
if (inputType === 'text.select') {
targetLocation = columns[j].Code_Name + "_initSettingDropdown"
initSettingDropdown(columns[j].Code_Name, [], targetLocation)
input = `<select class="form-control"
id="${columns[j].Code_Name}"
data-my-column="${columns[j].Code_Name}"
data-my-targetColumns="${columns[j].Code_Name.replace('NEWDEV_','')}" >
<option id="${targetLocation}"></option>
</select>`
} else {
if (inputType === 'checkbox') {
inputClass = 'checkbox';
} else {
inputClass = 'form-control';
}
input = `<input class="${inputClass}"
id="${columns[j].Code_Name}"
data-my-column="${columns[j].Code_Name}"
data-my-targetColumns="${columns[j].Code_Name.replace('NEWDEV_','')}"
type="${inputType}">`
}
const inputEntry = `<div class="form-group col-sm-12" >
<label class="col-sm-3 control-label">${columns[j].Display_Name}</label>
<div class="col-sm-9">
<div class="input-group red-hover-border">
${input}
<span class="input-group-addon pointer red-hover-background" onclick="massUpdateField('${columns[j].Code_Name}');" title="${getString('Device_MultiEdit_Tooltip')}">
<i class="fa fa-save"></i>
</span>
</div>
</div>
</div>`
column.append(inputEntry);
}
form.append(column);
}
};
console.log(relevantColumns)
generateSimpleForm(relevantColumns);
})
}
// -----------------------------------------------------------------------------
// Get selected devices Macs
function selectorMacs () {
return $('.deviceSelector select').val().join(',');
}
// -----------------------------------------------------------------------------
// Update specified field over the specified DB column and selected entry(ies)
function massUpdateField(id) {
// Get the input element
var inputElement = $(`#${id}`);
console.log(inputElement);
console.log(id);
// Initialize columnValue variable
var columnValue;
// Check the type of the input element
if (inputElement.is(':checkbox')) {
// For checkboxes, set the value to 1 if checked, otherwise set it to 0
columnValue = inputElement.is(':checked') ? 1 : 0;
} else {
// For other input types (like textboxes), simply retrieve their values
columnValue = inputElement.val();
}
var targetColumns = inputElement.attr('data-my-targetColumns');
console.log(targetColumns);
console.log(columnValue);
// update selected
executeAction('update', 'dev_MAC', selectorMacs(), targetColumns, columnValue )
}
// -----------------------------------------------------------------------------
// action: Represents the action to be performed, a CRUD operation like "update", "delete", etc.
// whereColumnName: Specifies the name of the column used in the WHERE or SELECT statement for filtering.
// key: Represents the unique identifier of the row or record to be acted upon.
// targetColumns: Indicates the columns to be updated or affected by the action.
// newTargetColumnValue: Specifies the new value to be assigned to the specified column(s).
function executeAction(action, whereColumnName, key, targetColumns, newTargetColumnValue )
{
$.get(`php/server/dbHelper.php?action=${action}&dbtable=Devices&columnName=${whereColumnName}&id=${key}&columns=${targetColumns}&values=${newTargetColumnValue}`, function(data) {
// console.log(data);
if (sanitize(data) == 'OK') {
showMessage(getString('Gen_DataUpdatedUITakesTime'));
// Remove navigation prompt "Are you sure you want to leave..."
window.onbeforeunload = null;
// update API endpoints to refresh the UI
updateApi()
} else {
showMessage(getString('Gen_LockedDB'));
}
});
}
// -----------------------------------------------------------------------------
// Ask to delete selected devices
function askDeleteSelectedDevices () {
// Ask
showModalWarning(
getString('Maintenance_Tool_del_alldev_noti'),
getString('Gen_AreYouSure'),
getString('Gen_Cancel'),
getString('Gen_Delete'),
'deleteSelectedDevices');
}
// -----------------------------------------------------------------------------
// Delete selected devices
function deleteSelectedDevices()
{
executeAction('delete', 'dev_MAC', selectorMacs() )
}
getData();
</script>
<!-- ----------------------------------------------------------------------- -->
<script src="js/ui_components.js"></script>
<script src="js/db_methods.js"></script>
<!-- ----------------------------------------------------------------------- -->

View File

@@ -454,7 +454,7 @@
<script src="lib/treeviz/index.js"></script>
<script src="lib/treeviz/require.js"></script>
<script src="js/pialert_common.js"></script>
<script>
$.get('php/server/devices.php?action=getDevicesList&status=all&forceDefaultOrder', function(data) {
@@ -724,6 +724,9 @@
console.log(myHierarchy)
myTree.refresh(myHierarchy);
// hide spinning icon
hideSpinner()
}
// ---------------------------------------------------------------------------
@@ -840,6 +843,8 @@
}
}
// show spinning icon
showSpinner()
// init device names where macs are used
initDeviceNamesFromMACs();

View File

@@ -56,6 +56,10 @@
$columns = $_REQUEST['columns'];
}
if (isset ($_REQUEST['rawSql'])) {
$rawSql = $_REQUEST['rawSql'];
}
if (isset ($_REQUEST['dbtable'])) {
$dbtable = $_REQUEST['dbtable'];
}
@@ -64,92 +68,148 @@
if (isset ($_REQUEST['action']) && !empty ($_REQUEST['action'])) {
$action = $_REQUEST['action'];
switch ($action) {
case 'create': create($skipCache, $defaultValue, $expireMinutes, $dbtable, $columns, $values ); break;
// case 'read' : read($skipCache, $defaultValue, $expireMinutes, $dbtable, $columns, $values); break;
case 'update': update($columnName, $id, $skipCache, $defaultValue, $expireMinutes, $dbtable, $columns, $values); break;
case 'create': create($defaultValue, $expireMinutes, $dbtable, $columns, $values ); break;
case 'read' : read($rawSql); break;
case 'update': update($columnName, $id, $defaultValue, $expireMinutes, $dbtable, $columns, $values); break;
case 'delete': delete($columnName, $id, $dbtable); break;
default: logServerConsole ('Action: '. $action); break;
}
}
//------------------------------------------------------------------------------
// read
//------------------------------------------------------------------------------
function read($rawSql) {
global $db;
// Construct the SQL query to select values
$sql = $rawSql;
// Execute the SQL query
$result = $db->query($sql);
// Check if the query executed successfully
if (! $result == TRUE) {
// Output an error message if the query failed
echo "Error reading data\n\n " .$sql." \n\n". $db->lastErrorMsg();
return;
} else
{
// Output $result
// Fetching rows from the result object and storing them in an array
$rows = array();
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
$rows[] = $row;
}
// Converting the array to JSON
$json = json_encode($rows);
// Outputting the JSON
echo $json;
return;
}
}
//------------------------------------------------------------------------------
// update
//------------------------------------------------------------------------------
function update($columnName, $id, $skipCache, $defaultValue, $expireMinutes, $dbtable, $columns, $values) {
function update($columnName, $id, $defaultValue, $expireMinutes, $dbtable, $columns, $values) {
global $db;
// handle one or multiple columns
if(strpos($columns, ',') !== false)
{
$columnsArr = explode(",", $columns);
}else
{
$columnsArr = array($columns);
// Handle one or multiple columns
if(strpos($columns, ',') !== false) {
$columnsArr = explode(",", $columns);
} else {
$columnsArr = array($columns);
}
// handle one or multiple values
if(strpos($values, ',') !== false)
{
$valuesArr = explode(",", $values);
} else
{
$valuesArr = array($values);
// Handle one or multiple values
if(strpos($values, ',') !== false) {
$valuesArr = explode(",", $values);
} else {
$valuesArr = array($values);
}
// Handle one or multiple IDs
if(strpos($id, ',') !== false) {
$idsArr = explode(",", $id);
$idsPlaceholder = rtrim(str_repeat('?,', count($idsArr)), ',');
} else {
$idsArr = array($id);
$idsPlaceholder = '?';
}
// Build column-value pairs string
$columnValues = '';
$index = 0;
foreach($columnsArr as $column)
{
$columnValues = $columnValues .' "' .$column.'" = "'.$valuesArr[$index] . '",' ;
$index = $index + 1;
foreach($columnsArr as $column) {
$columnValues .= '"' . $column . '" = ?,';
}
// Remove trailing comma
$columnValues = rtrim($columnValues, ',');
// Construct the SQL query
$sql = 'UPDATE ' . $dbtable . ' SET ' . $columnValues . ' WHERE ' . $columnName . ' IN (' . $idsPlaceholder . ')';
$columnValues = substr($columnValues, 0, -1);
// Update value
$sql = 'UPDATE '.$dbtable.' SET '. $columnValues .'
WHERE "'. $columnName .'"="'. $id.'"';
$result = $db->query($sql);
// Prepare the statement
$stmt = $db->prepare($sql);
if (! $result == TRUE) {
echo "Error updating parameter\n\n$sql \n\n". $db->lastErrorMsg();
return;
// Check for errors
if(!$stmt) {
echo "Error preparing statement: " . $db->lastErrorMsg();
return;
}
// Bind the parameters
$paramTypes = str_repeat('s', count($columnsArr));
foreach($valuesArr as $i => $value) {
$stmt->bindValue($i + 1, $value);
}
foreach($idsArr as $i => $idValue) {
$stmt->bindValue(count($valuesArr) + $i + 1, $idValue);
}
// Execute the statement
$result = $stmt->execute();
$changes = $db->changes();
if ($changes == 0) {
// Insert new value
create($skipCache, $defaultValue, $expireMinutes, $dbtable, $columns, $values);
create( $defaultValue, $expireMinutes, $dbtable, $columns, $values);
}
// update cache
$uniqueHash = hash('ripemd160', $dbtable . $columns);
setCache($uniqueHash, $values, $expireMinutes);
echo 'OK';
echo 'OK' ;
}
//------------------------------------------------------------------------------
// create
//------------------------------------------------------------------------------
function create($skipCache, $defaultValue, $expireMinutes, $dbtable, $columns, $values)
function create( $defaultValue, $expireMinutes, $dbtable, $columns, $values)
{
global $db;
// Insert new value
$sql = 'INSERT INTO '.$dbtable.' ('.$columns.')
VALUES ("'. quotes($parameter) .'",
"'. $values .'")';
$result = $db->query($sql);
echo "NOT IMPLEMENTED!\n\n";
return;
if (! $result == TRUE) {
echo "Error creating entry\n\n$sql \n\n". $db->lastErrorMsg();
return;
}
// // Insert new value
// $sql = 'INSERT INTO '.$dbtable.' ('.$columns.')
// VALUES ("'. quotes($parameter) .'",
// "'. $values .'")';
// $result = $db->query($sql);
// if (! $result == TRUE) {
// echo "Error creating entry\n\n$sql \n\n". $db->lastErrorMsg();
// return;
// }
}
//------------------------------------------------------------------------------
@@ -159,35 +219,49 @@ function delete($columnName, $id, $dbtable)
{
global $db;
// handle one or multiple ids
// Handle one or multiple ids
if(strpos($id, ',') !== false)
{
$idsArr = explode(",", $id);
}else
} else
{
$idsArr = array($id);
}
// Initialize an empty string to store the comma-separated list of IDs
$idsStr = "";
foreach ($idsArr as $item)
// Iterate over each ID
foreach ($idsArr as $index => $item)
{
$idsStr = $idsStr . '"' .$item.'"';
// Append the current ID to the string
$idsStr .= '"' . $item . '"';
// Add a comma if the current ID is not the last one
if ($index < count($idsArr) - 1) {
$idsStr .= ', ';
}
}
// Insert new value
// Construct the SQL query to delete entries based on the given IDs
$sql = 'DELETE FROM '.$dbtable.' WHERE "'.$columnName.'" IN ('. $idsStr .')';
// Execute the SQL query
$result = $db->query($sql);
// Check if the query executed successfully
if (! $result == TRUE) {
echo "Error deleting entry\n\n$sql \n\n". $db->lastErrorMsg();
// Output an error message if the query failed
echo "Error deleting entry\n\n".$sql." \n\n". $db->lastErrorMsg();
return;
} else
{
echo lang('Gen_DataUpdatedUITakesTime');
// Output 'OK' if the deletion was successful
echo 'OK' ;
return;
}
}
?>

View File

@@ -185,7 +185,7 @@ function displayMessage($message, $logAlert = FALSE, $logConsole = TRUE, $logFil
echo '<script>alert(escape("'.$message.'"));</script>';
}
// F12 Browser console
// F12 Browser dev console
if($logConsole)
{
echo '<script>console.log(escape("'.str_replace('"',"'",$message).'"));</script>';
@@ -194,16 +194,26 @@ function displayMessage($message, $logAlert = FALSE, $logConsole = TRUE, $logFil
//File
if($logFile)
{
if(file_exists($logFolderPath.$log_file) != 1) // file doesn't exist, create one
{
$log = fopen($logFolderPath.$log_file, "w") or die("Unable to open file!");
}else // file exists, append
{
$log = fopen($logFolderPath.$log_file, "a") or die("Unable to open file!");
if (is_writable($logFolderPath.$log_file)) {
if(file_exists($logFolderPath.$log_file) != 1) // file doesn't exist, create one
{
$log = fopen($logFolderPath.$log_file, "w") or die("Unable to open file!");
}else // file exists, append
{
$log = fopen($logFolderPath.$log_file, "a") or die("Unable to open file - Permissions issue!");
}
fwrite($log, "[".$timestamp. "] " . str_replace('<br>',"\n ",str_replace('<br/>',"\n ",$message)).PHP_EOL."" );
fclose($log);
} else {
echo 'The file is not writable: '.$logFolderPath.$log_file;
}
fwrite($log, "[".$timestamp. "] " . str_replace('<br>',"\n ",str_replace('<br/>',"\n ",$message)).PHP_EOL."" );
fclose($log);
}
//echo

View File

@@ -55,7 +55,7 @@
<!-- <script src="lib/AdminLTE/bower_components/fastclick/lib/fastclick.js"></script> -->
<!-- Pi.Alert -------------------------------------------------------------- -->
<script src="js/pialert_common.js"></script>
<script src="js/handle_version.js"></script>
</body>

View File

@@ -28,16 +28,17 @@ require dirname(__FILE__).'/security.php';
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<!-- ----------------------------------------------------------------------- -->
<!-- REQUIRED JS SCRIPTS -->
<!-- jQuery 3 -->
<script src="lib/AdminLTE/bower_components/jquery/dist/jquery.min.js"></script>
<script src="js/pialert_common.js"></script>
<!-- Bootstrap 3.3.7 -->
<link rel="stylesheet" href="lib/AdminLTE/bower_components/bootstrap/dist/css/bootstrap.min.css">
<!-- Font Awesome -->
<link rel="stylesheet" href="lib/AdminLTE/bower_components/font-awesome/css/fontawesome.min.css">
<link rel="stylesheet" href="lib/AdminLTE/bower_components/font-awesome/css/font-awesome.min.css">
<link rel="stylesheet" href="lib/AdminLTE/bower_components/font-awesome/css/solid.css">
<link rel="stylesheet" href="lib/AdminLTE/bower_components/font-awesome/css/brands.css">
<link rel="stylesheet" href="lib/AdminLTE/bower_components/font-awesome/css/v5-font-face.css">
@@ -111,7 +112,7 @@ if ($ENABLED_DARKMODE === True) {
<!-- ----------------------------------------------------------------------- -->
<!-- Layout Boxed Yellow -->
<body class="hold-transition <?php echo $pia_skin_selected;?> sidebar-mini" <?php echo $BACKGROUND_IMAGE_PATCH;?> onLoad="show_pia_servertime();" >
<body class="hold-transition fixed <?php echo $pia_skin_selected;?> sidebar-mini" <?php echo $BACKGROUND_IMAGE_PATCH;?> onLoad="show_pia_servertime();" >
<!-- Site wrapper -->
<div class="wrapper">
@@ -122,9 +123,14 @@ if ($ENABLED_DARKMODE === True) {
<!-- Logo -->
<a href="devices.php" class="logo">
<!-- mini logo for sidebar mini 50x50 pixels -->
<span class="logo-mini">P<b>a</b></span>
<span class="logo-mini">
<img src="img/pialertLogoWhite.png" class="pia-top-left-logo" alt="Pi.Alert Logo"/>
</span>
<!-- logo for regular state and mobile devices -->
<span class="logo-lg">Pi<b>.Alert</b></span>
<span class="logo-lg">Pi<b>.Alert</b>
</span>
</a>
<!-- ----------------------------------------------------------------------- -->
@@ -145,14 +151,14 @@ if ($ENABLED_DARKMODE === True) {
<li>
<a id="next-button" href="javascript:history.go(1);" role="button" span class='of-bt-icon'><i class='fa fa-arrow-right'></i></a>
</li>
<!-- Reload -->
<!-- Clear cache & Reload -->
<li>
<a id="reload-button" href='#' role="button" span class='of-bt-icon' onclick='location.reload()'><i class='fa fa-repeat'></i></a>
<a id="reload-button" href='#' role="button" span class='of-bt-icon' onclick='clearCache()'><i class='fa fa-repeat'></i></a>
</li>
<!-- Full Screen -->
<li>
<a id="fullscreen-button" href='#' role="button" span class='of-bt-icon' onclick='toggleFullscreen()'><i class='fa fa-arrows-alt'></i></a>
</li>
</li>
<!-- Server Status -->
<li>
<a onclick="setCache('activeMaintenanceTab', 'tab_Logging_id')" href="maintenance.php#tab_Logging">
@@ -183,7 +189,7 @@ if ($ENABLED_DARKMODE === True) {
<img src="img/pialertLogoWhite.png" class="img-circle" alt="Pi.Alert Logo" style="border-color:transparent; height: 50px; width: 50px; margin-top:15px;">
<p style="float: right; width: 200px">
<?= lang('About_Title');?>
<small><?= lang('About_Design');?> Raspberry Pi</small>
<small><?= lang('About_Design');?> Docker</small>
</p>
</li>
@@ -208,62 +214,172 @@ if ($ENABLED_DARKMODE === True) {
<!-- sidebar: style can be found in sidebar.less -->
<section class="sidebar">
<!-- Sidebar user panel (optional) -->
<div class="user-panel"> <a href="." class="logo">
<img src="img/pialertLogoGray80.png" class="img-responsive" alt="Pi.Alert Logo"/>
</a>
</div>
<!-- search form (Optional) -->
<!-- DELETED -->
<!-- Sidebar Menu -->
<!-- Navigation Mneu -->
<ul class="sidebar-menu" data-widget="tree">
<li class=" <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('devices.php', 'deviceDetails.php') ) ){ echo 'active'; } ?>">
<a href="devices.php"><i class="fa fa-laptop"></i> <span><?= lang('Navigation_Devices');?></span></a>
<li class=" treeview <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('devices.php', 'deviceDetails.php') ) ){ echo 'active menu-open'; } ?>">
<a href="#" onclick="openUrl(['./devices.php', './deviceDetails.php'])">
<i class="fa fa-fw fa-laptop"></i> <span><?= lang('Navigation_Devices');?></span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu" style="display: <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('devices.php', 'deviceDetails.php') ) ){ echo 'block'; } else {echo 'none';} ?>;">
<li>
<a href="devices.php#my" onclick="initializeDatatable('my')" > <?= lang("Device_Shortcut_AllDevices");?> </a>
</li>
<li>
<a href="devices.php#connected" onclick="initializeDatatable('connected')" > <?= lang("Device_Shortcut_Connected");?> </a>
</li>
<li>
<a href="devices.php#favorites" onclick="initializeDatatable('favorites')" > <?= lang("Device_Shortcut_Favorites");?> </a>
</li>
<li>
<a href="devices.php#new" onclick="initializeDatatable('new')" > <?= lang("Device_Shortcut_NewDevices");?> </a>
</li>
<li>
<a href="devices.php#down" onclick="initializeDatatable('down')" > <?= lang("Device_Shortcut_DownAlerts");?> </a>
</li>
<li>
<a href="devices.php#archived" onclick="initializeDatatable('archived')" > <?= lang("Device_Shortcut_Archived");?> </a>
</li>
</ul>
</li>
<li class=" <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('presence.php') ) ){ echo 'active'; } ?>">
<a href="presence.php"><i class="fa fa-calendar"></i> <span><?= lang('Navigation_Presence');?></span></a>
<!-- Monitoring menu item -->
<li class=" treeview <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('presence.php', 'report.php', 'events.php' ) ) ){ echo 'active menu-open'; } ?>">
<a href="#">
<i class="fa fa-fw fa-chart-bar"></i> <span><?= lang('Navigation_Monitoring');?></span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu " style="display: <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('presence.php', 'report.php', 'events.php' ) ) ){ echo 'block'; } else {echo 'none';} ?>;">
<li>
<a href="presence.php"> <?= lang("Navigation_Presence");?> </a>
</li>
<li>
<a href="events.php"> <?= lang("Navigation_Events");?> </a>
</li>
<li>
<a href="report.php"> <?= lang("Navigation_Report");?> </a>
</li>
</ul>
</li>
<li class=" <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('events.php') ) ){ echo 'active'; } ?>">
<a href="events.php"><i class="fa fa-bolt"></i> <span><?= lang('Navigation_Events');?></span></a>
</li>
<li class=" <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('report.php') ) ){ echo 'active'; } ?>">
<a href="report.php"><i class="fa fa-flag"></i> <span><?= lang('Navigation_Report');?></span></a>
</li>
<!-- Network menu item -->
<li class=" <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('network.php') ) ){ echo 'active'; } ?>">
<a href="network.php"><i class="fa fa-fw fa-network-wired"></i> <span><?= lang('Navigation_Network');?></span></a>
</li>
<li class=" <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('plugins.php') ) ){ echo 'active'; } ?>">
<a href="plugins.php"><i class="fa fa-fw fa-plug"></i> <span><?= lang('Navigation_Plugins');?></span></a>
<!-- Maintenance menu item -->
<li class=" treeview <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('maintenance.php') ) ){ echo 'active menu-open'; } ?>">
<a href="#" onclick="openUrl(['./maintenance.php'])">
<div class="info-icon-nav myhidden" id="version" data-build-time="<?php echo file_get_contents( "buildtimestamp.txt");?>">🆕</div>
<i class="fa fa-fw fa-wrench"></i> <span><?= lang('Navigation_Maintenance');?></span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu" style="display: <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('maintenance.php') ) ){ echo 'block'; } else {echo 'none';} ?>;">
<li>
<a href="maintenance.php#tab_Settings" onclick="initializeTabs()"> <?= lang("Maintenance_Tools_Tab_UISettings");?> </a>
</li>
<li>
<a href="maintenance.php#tab_DBTools" onclick="initializeTabs()"> <?= lang("Maintenance_Tools_Tab_Tools");?> </a>
</li>
<li>
<a href="maintenance.php#tab_BackupRestore" onclick="initializeTabs()"> <?= lang("Maintenance_Tools_Tab_BackupRestore");?> </a>
</li>
<li>
<a href="maintenance.php#tab_Logging" onclick="initializeTabs()"> <?= lang("Maintenance_Tools_Tab_Logging");?> </a>
</li>
<li>
<a href="maintenance.php#tab_multiEdit" onclick="initializeTabs()"> <?= lang("Device_MultiEdit");?> </a>
</li>
</ul>
</li>
<li class=" <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('maintenance.php') ) ){ echo 'active'; } ?>">
<div class="info-icon-nav myhidden" id="version" data-build-time="<?php echo file_get_contents( "buildtimestamp.txt");?>">🆕</div>
<a href="maintenance.php"><i class="fa fa-wrench "></i> <span><?= lang('Navigation_Maintenance');?></span></a>
<!-- Settings menu item -->
<li class=" treeview <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('settings.php') ) ){ echo 'active menu-open'; } ?>">
<a href="#" onclick="openUrl(['./settings.php'])">
<i class="fa fa-fw fa-cog"></i> <span><?= lang('Navigation_Settings');?></span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu" style="display: <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('settings.php') ) ){ echo 'block'; } else {echo 'none';} ?>;">
<li>
<a href="settings.php#pageTitle"> <?= lang("settings_enabled");?> </a>
</li>
<li>
<a href="settings.php#core_content_header"> <?= lang("settings_core_label");?> </a>
</li>
<li>
<a href="settings.php#system_content_header"> <?= lang("settings_system_label");?> </a>
</li>
<li>
<a href="settings.php#device_scanner_content_header"> <?= lang("settings_device_scanners_label");?> </a>
</li>
<li>
<a href="settings.php#other_content_header"> <?= lang("settings_other_scanners_label");?> </a>
</li>
<li>
<a href="settings.php#publisher_content_header"> <?= lang("settings_publishers_label");?> </a>
</li>
</ul>
</li>
<li class=" <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('settings.php') ) ){ echo 'active'; } ?>">
<a href="settings.php"><i class="fa fa-cog"></i> <span><?= lang('Navigation_Settings');?></span></a>
<!-- Integrations menu item -->
<li class=" treeview <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('plugins.php', 'workflows.php' ) ) ){ echo 'active menu-open'; } ?>">
<a href="#">
<i class="fa fa-fw fa-plug"></i> <span><?= lang('Navigation_Integrations');?></span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu " style="display: <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('plugins.php', 'workflows.php' ) ) ){ echo 'block'; } else {echo 'none';} ?>;">
<li>
<div class="info-icon-nav work-in-progress"> </div>
<a href="workflows.php"><?= lang('Navigation_Workflows');?></a>
</li>
<li>
<a href="plugins.php"><?= lang("Navigation_Plugins");?> </a>
</li>
</ul>
</li>
<li class=" <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('workflows.php') ) ){ echo 'active'; } ?>">
<div class="info-icon-nav work-in-progress"> </div>
<a href="workflows.php"><i class="fa fa-shuffle"></i> <span><?= lang('Navigation_Workflows');?></span></a>
</li>
<li class=" <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('systeminfo.php') ) ){ echo 'active'; } ?>">
<a href="systeminfo.php"><i class="fa fa-microchip"></i> <span><?= lang('Navigation_SystemInfo');?></span></a>
</li>
<li class=" <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('help_faq.php') ) ){ echo 'active'; } ?>">
<a href="help_faq.php"><i class="fa fa-question"></i> <span><?= lang('Navigation_HelpFAQ');?></span></a>
</li>
<li class=" <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('donations.php') ) ){ echo 'active'; } ?>">
<a href="donations.php"><i class="fa fa-heart"></i> <span><?= lang('Navigation_Donations');?></span></a>
<!-- About menu item -->
<li class=" treeview <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('donations.php', 'help_faq.php', 'systeminfo.php' ) ) ){ echo 'active menu-open'; } ?>">
<a href="#">
<i class="fa fa-fw fa-info"></i> <span><?= lang('Navigation_About');?></span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu " style="display: <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('donations.php', 'help_faq.php', 'systeminfo.php' ) ) ){ echo 'block'; } else {echo 'none';} ?>;">
<li>
<a href="donations.php"> <?= lang("Navigation_Donations");?> </a>
</li>
<li>
<a href="help_faq.php"> <?= lang("Navigation_HelpFAQ");?> </a>
</li>
<li>
<a href="systeminfo.php"> <?= lang("Navigation_SystemInfo");?> </a>
</li>
</ul>
</li>
</ul>
<!-- /.sidebar-menu -->
@@ -271,11 +387,25 @@ if ($ENABLED_DARKMODE === True) {
<!-- /.sidebar -->
</aside>
<script src="js/pialert_common.js"></script>
<script defer>
// Generate work-in-progress icons
function workInProgress() {
if($(".work-in-progress").html().trim() == "")
{
$(".work-in-progress").append(`
<a href="https://github.com/jokob-sk/Pi.Alert/issues" target="_blank">
<b class="pointer" title="${getString("Gen_Work_In_Progress")}">🦺</b>
</a>
`)
}
}
//--------------------------------------------------------------
//--------------------------------------------------------------
function toggleFullscreen() {
@@ -295,5 +425,6 @@ if ($ENABLED_DARKMODE === True) {
// Update server state in the header
updateState()
workInProgress()
</script>

View File

@@ -187,6 +187,11 @@
"DevDetail_button_OverwriteIcons_Warning": "Are you sure you want to overwrite all icons of all devices with the same device type as the current device type?",
"DevDetail_button_Reset": "Verwerfen",
"DevDetail_button_Save": "Speichern",
"Device_MultiEdit": "",
"Device_MultiEdit_Backup": "",
"Device_MultiEdit_Fields": "",
"Device_MultiEdit_MassActions": "",
"Device_MultiEdit_Tooltip": "",
"Device_Searchbox": "Suche",
"Device_Shortcut_AllDevices": "Alle Geräte",
"Device_Shortcut_Archived": "Archiviert",
@@ -280,6 +285,7 @@
"Gen_Run": "Run",
"Gen_Save": "Speichern",
"Gen_Saved": "Gespeichert",
"Gen_Selected_Devices": "",
"Gen_Switch": "Umschalten",
"Gen_Upd": "Aktualisierung erfolgreich",
"Gen_Upd_Fail": "Aktualisierung fehlgeschlagen",
@@ -399,6 +405,8 @@
"Maintenance_Tool_del_empty_macs_noti": "Geräte löschen",
"Maintenance_Tool_del_empty_macs_noti_text": "Sind Sie sicher, dass Sie alle Geräte ohne MAC-Adresse löschen wollen?<br>(Vielleicht bevorzugen Sie eine Archivierung)",
"Maintenance_Tool_del_empty_macs_text": "Machen Sie ein Backup, bevor Sie diese Funk&shy;tion nutzen. Der Vor&shy;gang kann ohne Back&shy;up nicht rück&shy;gängig gemacht werden. Alle Geäte ohne MAC-Adresse werden aus der Datenbank ge&shy;löscht.",
"Maintenance_Tool_del_selecteddev": "",
"Maintenance_Tool_del_selecteddev_text": "",
"Maintenance_Tool_del_unknowndev": "Löschen der (unknown) Geräte",
"Maintenance_Tool_del_unknowndev_noti": "Lösche (unknown) Geräte",
"Maintenance_Tool_del_unknowndev_noti_text": "Sind Sie sicher, dass Sie alle (unknown) Geräte aus der Datenbank löschen wollen?",
@@ -434,9 +442,6 @@
"Maintenance_database_path": "Datenbank-Pfad",
"Maintenance_database_rows": "Tabelle (Reihen)",
"Maintenance_database_size": "Datenbank-Größe",
"Maintenance_lang_de_de": "Deutsch (DE)",
"Maintenance_lang_en_us": "Englisch (US)",
"Maintenance_lang_es_es": "Spanisch (ES)",
"Maintenance_lang_selector_apply": "Übernehmen",
"Maintenance_lang_selector_empty": "Sprache wählen",
"Maintenance_lang_selector_lable": "Sprachauswahl",
@@ -459,12 +464,15 @@
"NTFY_USER_name": "NTFY user",
"NTFY_display_name": "NTFY",
"NTFY_icon": "<i class=\"fa fa-terminal\"></i>",
"Navigation_About": "",
"Navigation_Devices": "Geräte",
"Navigation_Donations": "Donations",
"Navigation_Events": "Ereignisse",
"Navigation_Flows": "Flows",
"Navigation_HelpFAQ": "Hilfe / FAQ",
"Navigation_Integrations": "",
"Navigation_Maintenance": "Wartung",
"Navigation_Monitoring": "",
"Navigation_Network": "Netzwerk",
"Navigation_Plugins": "Plugins",
"Navigation_Presence": "Anwesenheit",
@@ -623,6 +631,10 @@
"Systeminfo_Network_HTTP_Referer": "HTTP-Referer:",
"Systeminfo_Network_HTTP_Referer_String": "Kein HTTP-Referer",
"Systeminfo_Network_Hardware": "Netzwerk Hardware",
"Systeminfo_Network_Hardware_Interface_Mask": "",
"Systeminfo_Network_Hardware_Interface_Name": "",
"Systeminfo_Network_Hardware_Interface_RX": "",
"Systeminfo_Network_Hardware_Interface_TX": "",
"Systeminfo_Network_IP": "IP Internet:",
"Systeminfo_Network_IP_Connection": "IP-Verbindung:",
"Systeminfo_Network_IP_Server": "Server-IP:",

View File

@@ -5,7 +5,7 @@
"API_icon": "<i class=\"fa fa-arrow-down-up-across-line\"></i>",
"About_Design": "Designed for:",
"About_Exit": "Sign out",
"About_Title": "Open Source Network Guard",
"About_Title": "Network security scanner & notification framework",
"AppEvents_DateTimeCreated": "Logged",
"AppEvents_Extra": "Extra",
"AppEvents_GUID": "Application Event GUID",
@@ -175,12 +175,17 @@
"DevDetail_button_OverwriteIcons_Warning": "Are you sure you want to overwrite all icons of all devices with the same device type as the current device type?",
"DevDetail_button_Reset": "Reset Changes",
"DevDetail_button_Save": "Save",
"Device_MultiEdit": "Multi-edit",
"Device_MultiEdit_Backup": "Careful, entering wrong values below will break your setup. Please backup your database or Devices configuration first (<a href=\"php/server/devices.php?action=ExportCSV\">click to download <i class=\"fa-solid fa-download fa-bounce\"></i></a>). Read how to recover Devices from this file in the <a href=\"https://github.com/jokob-sk/Pi.Alert/blob/main/docs/BACKUPS.md#scenario-2-corrupted-database\" target=\"_blank\">Backups documentation</a>.",
"Device_MultiEdit_Fields": "Edit fields:",
"Device_MultiEdit_MassActions": "Mass actions:",
"Device_MultiEdit_Tooltip": "Careful. Clicking this will apply the value on the left to all devices selected above.",
"Device_Searchbox": "Search",
"Device_Shortcut_AllDevices": "My Devices",
"Device_Shortcut_Archived": "Archived",
"Device_Shortcut_Connected": "Connected",
"Device_Shortcut_Devices": "Devices",
"Device_Shortcut_DownAlerts": "Down Alerts",
"Device_Shortcut_DownAlerts": "Down & Offline",
"Device_Shortcut_Favorites": "Favorites",
"Device_Shortcut_NewDevices": "New Devices",
"Device_Shortcut_OnlineChart": "Device presence",
@@ -213,7 +218,7 @@
"Device_Title": "Devices",
"Donations_Others": "Others",
"Donations_Platforms": "Sponsor platforms",
"Donations_Text": "Hey \ud83d\udc4b! </br> Thanks for clicking on this menu item \ud83d\ude05 </br> </br> I'm trying to collect some donations to make you better software. Also, it would help me not to get burned out. Me burning out might mean end of support for this app. Any small (recurring or not) sponsorship makes me want to put more effort into this app. I don't want to lock features (new plugins) behind paywalls \ud83d\udd10. </br> Currently, I'm waking up 2h before work so I contribute to the app a bit. If I had some recurring income I could shorten my workweek and in the remaining time fully focus on PiAlert. You'd get more functionality, a more polished app and less bugs. </br> </br> Thanks for reading - I'm super grateful for any support \u2764\ud83d\ude4f </br> </br> TL;DR: By supporting me you get: </br> </br> <ul><li>Regular updates to keep your data and family safe \ud83d\udd04</li><li>Less bugs \ud83d\udc1b\ud83d\udd2b</li><li>Better and more functionality\u2795</li><li>I don't get burned out \ud83d\udd25\ud83e\udd2f</li><li>Less rushed releases \ud83d\udca8</li><li>Better docs\ud83d\udcda</li><li>Quicker and better support with issues \ud83c\udd98</li><li>Less grumpy me \ud83d\ude04</li></ul> </br> \ud83d\udce7Email me to <a href='mailto:jokob@duck.com?subject=PiAlert'>jokob@duck.com</a> if you want to get in touch or if I should add other sponsorship platforms. </br>",
"Donations_Text": "Hey \ud83d\udc4b! </br> Thanks for clicking on this menu item \ud83d\ude05 </br> </br> I'm trying to collect some donations to make you better software. Also, it would help me not to get burned out, so I can support this app longer. Any small (recurring or not) sponsorship makes me want to put more effort into this app. </br> I'd love to shorten my work week and in the remaining time fully focus on PiAlert. You'd get more functionality, a more polished app and less bugs. </br> </br> Thanks for reading - I'm grateful for any support \u2764\ud83d\ude4f </br> </br> TL;DR: By supporting me you get: </br> </br> <ul><li>Regular updates to keep your data and family safe \ud83d\udd04</li><li>Less bugs \ud83d\udc1b\ud83d\udd2b</li><li>Better and more functionality\u2795</li><li>I don't get burned out \ud83d\udd25\ud83e\udd2f</li><li>Less rushed releases \ud83d\udca8</li><li>Better docs\ud83d\udcda</li><li>Quicker and better support with issues \ud83c\udd98</li></ul> </br> \ud83d\udce7Email me to <a href='mailto:jokob@duck.com?subject=PiAlert'>jokob@duck.com</a> if you want to get in touch or if I should add other sponsorship platforms. </br>",
"Donations_Title": "Donations",
"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.",
"ENABLE_PLUGINS_name": "Enable Plugins",
@@ -268,6 +273,7 @@
"Gen_Run": "Run",
"Gen_Save": "Save",
"Gen_Saved": "Saved",
"Gen_Selected_Devices": "Selected Devices:",
"Gen_Switch": "Switch",
"Gen_Upd": "Updated successfully",
"Gen_Upd_Fail": "Update failed",
@@ -373,6 +379,8 @@
"Maintenance_Tool_del_empty_macs_noti": "Delete Devices",
"Maintenance_Tool_del_empty_macs_noti_text": "Are you sure you want to delete all devices with empty MAC addresses?<br>(maybe you prefer to archive it)",
"Maintenance_Tool_del_empty_macs_text": "Before using this function, please make a backup. The deletion cannot be undone. All devices without MAC will be deleted from the database.",
"Maintenance_Tool_del_selecteddev": "Delete selected devices",
"Maintenance_Tool_del_selecteddev_text": "Before using this function, please make a backup. The deletion cannot be undone. Selected devices will be deleted from the database.",
"Maintenance_Tool_del_unknowndev": "Delete (unknown) Devices",
"Maintenance_Tool_del_unknowndev_noti": "Delete (unknown) Devices",
"Maintenance_Tool_del_unknowndev_noti_text": "Are you sure you want to delete all (unknown) and (name not found) devices?",
@@ -408,9 +416,6 @@
"Maintenance_database_path": "Database-Path",
"Maintenance_database_rows": "Table (Rows)",
"Maintenance_database_size": "Database-Size",
"Maintenance_lang_de_de": "German (DE)",
"Maintenance_lang_en_us": "English (US)",
"Maintenance_lang_es_es": "Spanish (ES)",
"Maintenance_lang_selector_apply": "Apply",
"Maintenance_lang_selector_empty": "Choose Language",
"Maintenance_lang_selector_lable": "Select Language",
@@ -423,11 +428,14 @@
"Maintenance_version": "App updates",
"NETWORK_DEVICE_TYPES_description": "Which device types are allowed to be used as network devices in the Network view. The device type has to match exactly the <code>Type</code> setting on a specific device in Device details. Do not remove existing types, only add new ones.",
"NETWORK_DEVICE_TYPES_name": "Network device types",
"Navigation_About": "About",
"Navigation_Devices": "Devices",
"Navigation_Donations": "Donations",
"Navigation_Events": "Events",
"Navigation_HelpFAQ": "Help / FAQ",
"Navigation_Integrations": "Integrations",
"Navigation_Maintenance": "Maintenance",
"Navigation_Monitoring": "Monitoring",
"Navigation_Network": "Network",
"Navigation_Plugins": "Plugins",
"Navigation_Presence": "Presence",
@@ -554,6 +562,10 @@
"Systeminfo_Network_HTTP_Referer": "HTTP referrer:",
"Systeminfo_Network_HTTP_Referer_String": "No HTTP referrer",
"Systeminfo_Network_Hardware": "Network Hardware",
"Systeminfo_Network_Hardware_Interface_Mask": "Network Mask",
"Systeminfo_Network_Hardware_Interface_Name": "Interface Name",
"Systeminfo_Network_Hardware_Interface_RX": "Received",
"Systeminfo_Network_Hardware_Interface_TX": "Transmitted",
"Systeminfo_Network_IP": "IP Internet:",
"Systeminfo_Network_IP_Connection": "IP connection:",
"Systeminfo_Network_IP_Server": "Server IP:",

View File

@@ -13,7 +13,7 @@
"APPRISE_URL_name": "URL de notificación de Apprise",
"About_Design": "Diseñado para:",
"About_Exit": "Salir",
"About_Title": "Open Source Network Guard",
"About_Title": "Escáner de seguridad de la red y marco de notificaciones",
"AppEvents_DateTimeCreated": "Registrado",
"AppEvents_Extra": "Extra",
"AppEvents_GUID": "GUID del evento de aplicación",
@@ -185,12 +185,17 @@
"DevDetail_button_OverwriteIcons_Warning": "¿Sobreescribir todos los iconos de todos los dispositivos con el mismo tipo que el dispositivo actual?",
"DevDetail_button_Reset": "Restablecer cambios",
"DevDetail_button_Save": "Guardar",
"Device_MultiEdit": "Edición múltiple",
"Device_MultiEdit_Backup": "Tenga cuidado, ingresar valores incorrectos o romperá su configuración. Por favor, haga una copia de seguridad de su base de datos o de la configuración de los dispositivos primero (<a href=\"php/server/devices.php?action=ExportCSV\">haga clic para descargar <i class=\"fa-solid fa-download fa-bounce\"></i></a>). Lea cómo recuperar dispositivos de este archivo en la documentación de <a href=\"https://github.com/jokob-sk/Pi.Alert/blob/main/docs/BACKUPS.md#scenario-2-corrupted-database\" target=\"_blank\">Copia de seguridad</a>.",
"Device_MultiEdit_Fields": "Editar campos:",
"Device_MultiEdit_MassActions": "Acciones masivas:",
"Device_MultiEdit_Tooltip": "Cuidado. Al hacer clic se aplicará el valor de la izquierda a todos los dispositivos seleccionados anteriormente.",
"Device_Searchbox": "Búsqueda",
"Device_Shortcut_AllDevices": "Mis dispositivos",
"Device_Shortcut_Archived": "Archivado(s)",
"Device_Shortcut_Connected": "Conectado(s)",
"Device_Shortcut_Devices": "Dispositivos",
"Device_Shortcut_DownAlerts": "Alerta(s) de caída(s)",
"Device_Shortcut_DownAlerts": "Caído y sin conexión",
"Device_Shortcut_Favorites": "Favorito(s)",
"Device_Shortcut_NewDevices": "Nuevo(s)",
"Device_Shortcut_OnlineChart": "Presencia del dispositivo a lo largo del tiempo",
@@ -223,7 +228,7 @@
"Device_Title": "Dispositivos",
"Donations_Others": "Otros",
"Donations_Platforms": "Plataforma de patrocinadores",
"Donations_Text": "¡Hola 👋! </br> Gracias por hacer clic en esta parte del menú 😅 </br> </br> Estoy intentando recaudar algunas donaciones para mejorar el software. Además, me ayudaría a no quemarme. Que me agote podría significar el fin del soporte para esta aplicación. Cualquier pequeño patrocinio (recurrente o no) me hace querer poner más esfuerzo en esta aplicación. No quiero bloquear funciones (nuevos complementos) detrás de muros de pago 🔐. </br> Actualmente, me levanto 2 horas antes del trabajo, así que contribuyo un poco a la aplicación. Si tuviera algún ingreso recurrente podría acortar mi semana laboral y en el tiempo restante concentrarme completamente en PiAlert. Obtendrías más funcionalidad, una aplicación más pulida y menos errores. </br> </br> Gracias por leerme. Estoy muy agradecido por cualquier apoyo ❤🙏 </br> </br> TL;DR: Al apoyarme obtienes: </br> </br> <ul><li>Actualizaciones periódicas para mantener tus datos y a tu familia seguros 🔄</li><li>Menos errores 🐛🔫</li><li>Mejor y más funcionalidad</li><li> No me agoto 🔥🤯</li><li>Publicaciones menos apresuradas 💨</li><li>Mejores documentos📚</li><li>Soporte mejor y más rápido para problemas 🆘</li><li >Me menos gruñón 😄</li></ul> </br> 📧Envíame un correo electrónico a <a href='mailto:jokob@duck.com?subject=PiAlert'>jokob@duck.com</a> si quiero ponerme en contacto o si debo agregar otras plataformas de patrocinio. </br>",
"Donations_Text": "¡Hola! 👋 </br> Gracias por hacer clic en este elemento 😅 del menú </br> </br>, estoy tratando de recolectar algunas donaciones para mejorar el software. Además, me ayudaría a no quemarse, por lo que puedo apoyar esta aplicación por más tiempo. Cualquier pequeño patrocinio (recurrente o no) me hace querer esforzarme más en esta aplicación. </br> Me encantaría acortar mi semana de trabajo y en el tiempo que me queda centrarme por completo en PiAlert. Obtendrías más funcionalidad, una aplicación más pulida y menos errores. </br> </br> Gracias por leer, agradezco cualquier apoyo ❤🙏 </br> </br> TL; DR: Al apoyarme, obtienes: </br> </br> <ul><li>Actualizaciones periódicas para mantener tus datos y tu familia seguros 🔄</li><li>Menos errores 🐛🔫</li><li>Mejor y más funcionalidad</li><li>No me quemo 🔥🤯</li><li>Lanzamientos 💨menos apresurados</li> <li>Mejores documentos📚</li><li>Soporte más rápido y mejor con problemas 🆘</li></ul> </br> 📧Envíame un correo electrónico a <a href='mailto:jokob@duck.com?subject=PiAlert'>jokob@duck.com</a> si quieres ponerte en contacto o si debo añadir otras plataformas de patrocinio. </br>",
"Donations_Title": "Donaciones",
"ENABLE_PLUGINS_description": "Habilita la funcionalidad de los <a target=\"_blank\" href=\"https://github.com/jokob-sk/Pi.Alert/tree/main/front/plugins\">complementos</a>. Cargar los complementos requiere más recursos de hardware, así que quizás quieras desactivarlo en hardware poco potente.",
"ENABLE_PLUGINS_name": "Habilitar complementos",
@@ -278,6 +283,7 @@
"Gen_Run": "Ejecutar",
"Gen_Save": "Guardar",
"Gen_Saved": "Guardado",
"Gen_Selected_Devices": "Dispositivos seleccionados:",
"Gen_Switch": "Cambiar",
"Gen_Upd": "Actualizado correctamente",
"Gen_Upd_Fail": "Fallo al actualizar",
@@ -397,6 +403,8 @@
"Maintenance_Tool_del_empty_macs_noti": "Eliminar dispositivos",
"Maintenance_Tool_del_empty_macs_noti_text": "¿Estás seguro de que quieres eliminar todos los dispositivos con direcciones MAC vacías? <br> (tal vez prefiera archivarlo)",
"Maintenance_Tool_del_empty_macs_text": "Antes de usar esta función, haga una copia de seguridad. La eliminación no se puede deshacer. Todos los dispositivos sin Mac se eliminarán de la base de datos.",
"Maintenance_Tool_del_selecteddev": "Borrar dispositivos seleccionados",
"Maintenance_Tool_del_selecteddev_text": "Antes de utilizar esta función, haga una copia de seguridad. La eliminación no se puede deshacer. Los dispositivos seleccionados se eliminarán de la base de datos.",
"Maintenance_Tool_del_unknowndev": "Eliminar dispositivos (desconocidos)",
"Maintenance_Tool_del_unknowndev_noti": "Eliminar dispositivos (desconocidos)",
"Maintenance_Tool_del_unknowndev_noti_text": "¿Estás seguro de que quieres eliminar todos los dispositivos (desconocidos)?",
@@ -432,9 +440,6 @@
"Maintenance_database_path": "Ruta de la base de datos",
"Maintenance_database_rows": "Tabla (Filas)",
"Maintenance_database_size": "Tamaño de base de datos",
"Maintenance_lang_de_de": "German (DE)",
"Maintenance_lang_en_us": "English (US)",
"Maintenance_lang_es_es": "Spanish (ES)",
"Maintenance_lang_selector_apply": "Aplicar",
"Maintenance_lang_selector_empty": "Elija un idioma",
"Maintenance_lang_selector_lable": "Seleccione su idioma",
@@ -457,12 +462,15 @@
"NTFY_USER_name": "Usuario de NTFY",
"NTFY_display_name": "NTFY",
"NTFY_icon": "<i class=\"fa fa-terminal\"></i>",
"Navigation_About": "Acerca de",
"Navigation_Devices": "Dispositivos",
"Navigation_Donations": "Donaciones",
"Navigation_Events": "Eventos",
"Navigation_Flows": "Flows",
"Navigation_HelpFAQ": "Ayuda / FAQ",
"Navigation_Integrations": "Integraciones",
"Navigation_Maintenance": "Mantenimiento",
"Navigation_Monitoring": "Supervisión",
"Navigation_Network": "Red",
"Navigation_Plugins": "Plugins",
"Navigation_Presence": "Historial",
@@ -623,6 +631,10 @@
"Systeminfo_Network_HTTP_Referer": "Referido HTTP:",
"Systeminfo_Network_HTTP_Referer_String": "Sin referencia HTTP",
"Systeminfo_Network_Hardware": "Hardware de red",
"Systeminfo_Network_Hardware_Interface_Mask": "Máscara de red",
"Systeminfo_Network_Hardware_Interface_Name": "Nombre de la interfaz",
"Systeminfo_Network_Hardware_Interface_RX": "Recibido",
"Systeminfo_Network_Hardware_Interface_TX": "Transmitido",
"Systeminfo_Network_IP": "IP Internet:",
"Systeminfo_Network_IP_Connection": "Conexión IP:",
"Systeminfo_Network_IP_Server": "IP del servidor:",

View File

@@ -175,6 +175,11 @@
"DevDetail_button_OverwriteIcons_Warning": "",
"DevDetail_button_Reset": "",
"DevDetail_button_Save": "Enregistrer",
"Device_MultiEdit": "",
"Device_MultiEdit_Backup": "",
"Device_MultiEdit_Fields": "",
"Device_MultiEdit_MassActions": "",
"Device_MultiEdit_Tooltip": "",
"Device_Searchbox": "Rechercher",
"Device_Shortcut_AllDevices": "Tous les appareils",
"Device_Shortcut_Archived": "Archiv\u00e9",
@@ -268,6 +273,7 @@
"Gen_Run": "Lancer",
"Gen_Save": "Enregistrer",
"Gen_Saved": "Enregistr\u00e9",
"Gen_Selected_Devices": "",
"Gen_Switch": "Basculer",
"Gen_Upd": "",
"Gen_Upd_Fail": "",
@@ -373,6 +379,8 @@
"Maintenance_Tool_del_empty_macs_noti": "",
"Maintenance_Tool_del_empty_macs_noti_text": "",
"Maintenance_Tool_del_empty_macs_text": "",
"Maintenance_Tool_del_selecteddev": "",
"Maintenance_Tool_del_selecteddev_text": "",
"Maintenance_Tool_del_unknowndev": "",
"Maintenance_Tool_del_unknowndev_noti": "",
"Maintenance_Tool_del_unknowndev_noti_text": "",
@@ -408,9 +416,6 @@
"Maintenance_database_path": "Chemin de la base de donn\u00e9es",
"Maintenance_database_rows": "",
"Maintenance_database_size": "",
"Maintenance_lang_de_de": "",
"Maintenance_lang_en_us": "",
"Maintenance_lang_es_es": "",
"Maintenance_lang_selector_apply": "Appliquer",
"Maintenance_lang_selector_empty": "",
"Maintenance_lang_selector_lable": "",
@@ -423,11 +428,14 @@
"Maintenance_version": "",
"NETWORK_DEVICE_TYPES_description": "",
"NETWORK_DEVICE_TYPES_name": "",
"Navigation_About": "",
"Navigation_Devices": "Appareils",
"Navigation_Donations": "Dons",
"Navigation_Events": "\u00c9v\u00e8nements",
"Navigation_HelpFAQ": "Aide / FAQ",
"Navigation_Integrations": "",
"Navigation_Maintenance": "",
"Navigation_Monitoring": "",
"Navigation_Network": "R\u00e9seau",
"Navigation_Plugins": "Greffons",
"Navigation_Presence": "Pr\u00e9sence",
@@ -554,6 +562,10 @@
"Systeminfo_Network_HTTP_Referer": "R\u00e9f\u00e9rent HTTP\u202f:",
"Systeminfo_Network_HTTP_Referer_String": "Pas de r\u00e9f\u00e9rent HTTP",
"Systeminfo_Network_Hardware": "Mat\u00e9riel r\u00e9seau",
"Systeminfo_Network_Hardware_Interface_Mask": "",
"Systeminfo_Network_Hardware_Interface_Name": "",
"Systeminfo_Network_Hardware_Interface_RX": "",
"Systeminfo_Network_Hardware_Interface_TX": "",
"Systeminfo_Network_IP": "IP Internet\u202f:",
"Systeminfo_Network_IP_Connection": "Connexion IP\u202f:",
"Systeminfo_Network_IP_Server": "IP du serveur\u202f:",

View File

@@ -5,7 +5,7 @@
// ###################################
$defaultLang = "en_us";
$allLanguages = ["en_us","es_es","de_de"];
$allLanguages = ["en_us","es_es","de_de", "nb_no", "ru_ru", "fr_fr"];
global $db;
@@ -13,6 +13,9 @@ $result = $db->querySingle("SELECT Value FROM Settings WHERE Code_Name = 'UI_LAN
switch($result){
case 'Spanish': $pia_lang_selected = 'es_es'; break;
case 'German': $pia_lang_selected = 'de_de'; break;
case 'Norwegian': $pia_lang_selected = 'nb_no'; break;
case 'Russian': $pia_lang_selected = 'ru_ru'; break;
case 'French': $pia_lang_selected = 'fr_fr'; break;
default: $pia_lang_selected = 'en_us'; break;
}

View File

@@ -32,6 +32,6 @@ def merge_translations(main_file, other_files):
if __name__ == "__main__":
current_path = os.path.dirname(os.path.abspath(__file__))
json_files = ["en_us.json", "de_de.json", "es_es.json", "fr_fr.json", "nb_no.json"]
json_files = ["en_us.json", "de_de.json", "es_es.json", "fr_fr.json", "nb_no.json", "ru_ru.json"]
file_paths = [os.path.join(current_path, file) for file in json_files]
merge_translations(file_paths[0], file_paths[1:])

View File

@@ -175,6 +175,11 @@
"DevDetail_button_OverwriteIcons_Warning": "",
"DevDetail_button_Reset": "",
"DevDetail_button_Save": "",
"Device_MultiEdit": "",
"Device_MultiEdit_Backup": "",
"Device_MultiEdit_Fields": "",
"Device_MultiEdit_MassActions": "",
"Device_MultiEdit_Tooltip": "",
"Device_Searchbox": "",
"Device_Shortcut_AllDevices": "",
"Device_Shortcut_Archived": "",
@@ -268,6 +273,7 @@
"Gen_Run": "",
"Gen_Save": "",
"Gen_Saved": "",
"Gen_Selected_Devices": "",
"Gen_Switch": "",
"Gen_Upd": "",
"Gen_Upd_Fail": "",
@@ -373,6 +379,8 @@
"Maintenance_Tool_del_empty_macs_noti": "",
"Maintenance_Tool_del_empty_macs_noti_text": "",
"Maintenance_Tool_del_empty_macs_text": "",
"Maintenance_Tool_del_selecteddev": "",
"Maintenance_Tool_del_selecteddev_text": "",
"Maintenance_Tool_del_unknowndev": "",
"Maintenance_Tool_del_unknowndev_noti": "",
"Maintenance_Tool_del_unknowndev_noti_text": "",
@@ -408,9 +416,6 @@
"Maintenance_database_path": "",
"Maintenance_database_rows": "",
"Maintenance_database_size": "",
"Maintenance_lang_de_de": "",
"Maintenance_lang_en_us": "",
"Maintenance_lang_es_es": "",
"Maintenance_lang_selector_apply": "",
"Maintenance_lang_selector_empty": "",
"Maintenance_lang_selector_lable": "",
@@ -423,11 +428,14 @@
"Maintenance_version": "",
"NETWORK_DEVICE_TYPES_description": "",
"NETWORK_DEVICE_TYPES_name": "",
"Navigation_About": "",
"Navigation_Devices": "",
"Navigation_Donations": "",
"Navigation_Events": "",
"Navigation_HelpFAQ": "",
"Navigation_Integrations": "",
"Navigation_Maintenance": "",
"Navigation_Monitoring": "",
"Navigation_Network": "",
"Navigation_Plugins": "",
"Navigation_Presence": "",
@@ -554,6 +562,10 @@
"Systeminfo_Network_HTTP_Referer": "",
"Systeminfo_Network_HTTP_Referer_String": "",
"Systeminfo_Network_Hardware": "",
"Systeminfo_Network_Hardware_Interface_Mask": "",
"Systeminfo_Network_Hardware_Interface_Name": "",
"Systeminfo_Network_Hardware_Interface_RX": "",
"Systeminfo_Network_Hardware_Interface_TX": "",
"Systeminfo_Network_IP": "",
"Systeminfo_Network_IP_Connection": "",
"Systeminfo_Network_IP_Server": "",

View File

@@ -0,0 +1,650 @@
{
"API_CUSTOM_SQL_description": "\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 SQL-\u0437\u0430\u043f\u0440\u043e\u0441, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0444\u0430\u0439\u043b JSON, \u0430 \u0437\u0430\u0442\u0435\u043c \u0432\u044b\u0434\u0430\u0442\u044c \u0435\u0433\u043e \u0447\u0435\u0440\u0435\u0437 <a href=\"/api/table_custom_endpoint.json\" target=\"_blank\"><code>table_custom_endpoint.json</code> file endpoint</a>.",
"API_CUSTOM_SQL_name": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0430\u044f \u043a\u043e\u043d\u0435\u0447\u043d\u0430\u044f \u0442\u043e\u0447\u043a\u0430",
"API_display_name": "API",
"API_icon": "<i class=\"fa fa-arrow-down-up-across-line\"></i>",
"About_Design": "\u0420\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u043d:",
"About_Exit": "\u0417\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f",
"About_Title": "\u0421\u0435\u0442\u0435\u0432\u0430\u044f \u0437\u0430\u0449\u0438\u0442\u0430 \u0441 \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u043c \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u043c \u043a\u043e\u0434\u043e\u043c",
"AppEvents_DateTimeCreated": "\u0416\u0443\u0440\u043d\u0430\u043b",
"AppEvents_Extra": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e",
"AppEvents_GUID": "GUID \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f",
"AppEvents_Helper1": "\u041f\u043e\u043c\u043e\u0449\u043d\u0438\u043a 1",
"AppEvents_Helper2": "\u041f\u043e\u043c\u043e\u0449\u043d\u0438\u043a 2",
"AppEvents_Helper3": "\u041f\u043e\u043c\u043e\u0449\u043d\u0438\u043a 3",
"AppEvents_ObjectForeignKey": "\u0412\u043d\u0435\u0448\u043d\u0438\u0439 \u043a\u043b\u044e\u0447",
"AppEvents_ObjectIndex": "\u0418\u043d\u0434\u0435\u043a\u0441",
"AppEvents_ObjectIsArchived": "\u0410\u0440\u0445\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u043d\u043e (\u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0432\u0445\u043e\u0434\u0430 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443)",
"AppEvents_ObjectIsNew": "\u041d\u043e\u0432\u044b\u0439 (\u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0432\u0445\u043e\u0434\u0430 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443)",
"AppEvents_ObjectPlugin": "\u0421\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043b\u0430\u0433\u0438\u043d",
"AppEvents_ObjectPrimaryID": "\u041f\u0435\u0440\u0432\u0438\u0447\u043d\u044b\u0439 ID",
"AppEvents_ObjectSecondaryID": "\u0412\u0442\u043e\u0440\u0438\u0447\u043d\u044b\u0439 ID",
"AppEvents_ObjectStatus": "\u0421\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 (\u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0432\u0445\u043e\u0434\u0430 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443)",
"AppEvents_ObjectStatusColumn": "\u041a\u043e\u043b\u043e\u043d\u043a\u0430 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f",
"AppEvents_ObjectType": "\u0422\u0438\u043f \u043e\u0431\u044a\u0435\u043a\u0442\u0430",
"AppEvents_Plugin": "\u041f\u043b\u0430\u0433\u0438\u043d",
"AppEvents_Type": "\u0422\u0438\u043f",
"BackDevDetail_Actions_Ask_Run": "\u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435?",
"BackDevDetail_Actions_Not_Registered": "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u043d\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043e:\u00b7 ",
"BackDevDetail_Actions_Title_Run": "\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435",
"BackDevDetail_Copy_Ask": "\u041a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0438\u0437 \u0432\u044b\u043f\u0430\u0434\u0430\u044e\u0449\u0435\u0433\u043e \u0441\u043f\u0438\u0441\u043a\u0430 (\u0432\u0441\u0435 \u043d\u0430 \u044d\u0442\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u0431\u0443\u0434\u0435\u0442 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u0430\u043d\u043e)?",
"BackDevDetail_Copy_Title": "\u041a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u0435\u0442\u0430\u043b\u0438",
"BackDevDetail_Tools_WOL_error": "\u041a\u043e\u043c\u0430\u043d\u0434\u0430 \u041d\u0415 \u0431\u044b\u043b\u0430 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.",
"BackDevDetail_Tools_WOL_okay": "\u041a\u043e\u043c\u0430\u043d\u0434\u0430 \u0431\u044b\u043b\u0430 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.",
"BackDevices_Arpscan_disabled": "Arp \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0437\u0430\u043f\u0440\u0435\u0449\u0435\u043d\u043e",
"BackDevices_Arpscan_enabled": "Arp \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u043e",
"BackDevices_Backup_CopError": "\u041e\u0440\u0438\u0433\u0438\u043d\u0430\u043b\u044c\u043d\u0443\u044e \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c.",
"BackDevices_Backup_Failed": "\u0420\u0435\u0437\u0435\u0440\u0432\u043d\u043e\u0435 \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043e \u0447\u0430\u0441\u0442\u0438\u0447\u043d\u043e. \u0410\u0440\u0445\u0438\u0432 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0441\u043e\u0437\u0434\u0430\u043d \u0438\u043b\u0438 \u043f\u0443\u0441\u0442.",
"BackDevices_Backup_okay": "\u0420\u0435\u0437\u0435\u0440\u0432\u043d\u043e\u0435 \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043e \u0441 \u043d\u043e\u0432\u044b\u043c \u0430\u0440\u0445\u0438\u0432\u043e\u043c",
"BackDevices_DBTools_DelDevError_a": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430",
"BackDevices_DBTools_DelDevError_b": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432",
"BackDevices_DBTools_DelDev_a": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0434\u0430\u043b\u0435\u043d\u043e",
"BackDevices_DBTools_DelDev_b": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0434\u0430\u043b\u0435\u043d\u044b",
"BackDevices_DBTools_DelEvents": "\u0421\u043e\u0431\u044b\u0442\u0438\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u044b",
"BackDevices_DBTools_DelEventsError": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u0441\u043e\u0431\u044b\u0442\u0438\u0439",
"BackDevices_DBTools_ImportCSV": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0438\u0437 \u0444\u0430\u0439\u043b\u0430 CSV \u0431\u044b\u043b\u0438 \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u044b.",
"BackDevices_DBTools_ImportCSVError": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0444\u0430\u0439\u043b CSV. \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0444\u043e\u0440\u043c\u0430\u0442 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439.",
"BackDevices_DBTools_ImportCSVMissing": "CSV-\u0444\u0430\u0439\u043b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d \u0432<b>/config/devices.csv.</b>",
"BackDevices_DBTools_Purge": "\u0421\u0430\u043c\u044b\u0435 \u0441\u0442\u0430\u0440\u044b\u0435 \u0440\u0435\u0437\u0435\u0440\u0432\u043d\u044b\u0435 \u043a\u043e\u043f\u0438\u0438 \u0431\u044b\u043b\u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u044b",
"BackDevices_DBTools_UpdDev": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u043e",
"BackDevices_DBTools_UpdDevError": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430",
"BackDevices_DBTools_Upgrade": "\u0411\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445 \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0430",
"BackDevices_DBTools_UpgradeError": "\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c",
"BackDevices_Device_UpdDevError": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432. \u041f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u043f\u043e\u0437\u0436\u0435. \u0412\u0435\u0440\u043e\u044f\u0442\u043d\u043e, \u0431\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445 \u0437\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u0430 \u0438\u0437-\u0437\u0430 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0438.",
"BackDevices_Restore_CopError": "\u0418\u0441\u0445\u043e\u0434\u043d\u0443\u044e \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c.",
"BackDevices_Restore_Failed": "\u0412\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0435 \u0440\u0435\u0437\u0435\u0440\u0432\u043d\u0443\u044e \u043a\u043e\u043f\u0438\u044e \u0432\u0440\u0443\u0447\u043d\u0443\u044e.",
"BackDevices_Restore_okay": "\u0412\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043e \u0443\u0441\u043f\u0435\u0448\u043d\u043e.",
"BackDevices_darkmode_disabled": "\u0422\u0435\u043c\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d",
"BackDevices_darkmode_enabled": "\u0422\u0435\u043c\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c \u0432\u043a\u043b\u044e\u0447\u0435\u043d",
"DAYS_TO_KEEP_EVENTS_description": "\u042d\u0442\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043e\u0431\u0441\u043b\u0443\u0436\u0438\u0432\u0430\u043d\u0438\u044f. \u0417\u0434\u0435\u0441\u044c \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0434\u043d\u0435\u0439, \u0432 \u0442\u0435\u0447\u0435\u043d\u0438\u0435 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0431\u0443\u0434\u0443\u0442 \u0445\u0440\u0430\u043d\u0438\u0442\u044c\u0441\u044f \u0437\u0430\u043f\u0438\u0441\u0438 \u043e \u0441\u043e\u0431\u044b\u0442\u0438\u044f\u0445. \u0412\u0441\u0435 \u0441\u0442\u0430\u0440\u044b\u0435 \u043c\u0435\u0440\u043e\u043f\u0440\u0438\u044f\u0442\u0438\u044f \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u0435\u0441\u043a\u0438 \u0443\u0434\u0430\u043b\u044f\u0442\u044c\u0441\u044f. \u0422\u0430\u043a\u0436\u0435 \u043f\u0440\u0438\u043c\u0435\u043d\u0438\u043c\u043e \u043a \u0438\u0441\u0442\u043e\u0440\u0438\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u043f\u043b\u0430\u0433\u0438\u043d\u0430.",
"DAYS_TO_KEEP_EVENTS_name": "\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0441\u0442\u0430\u0440\u0448\u0435",
"DevDetail_Copy_Device_Title": "<i class=\"fa fa-copy\"></i> \u0421\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430",
"DevDetail_Copy_Device_Tooltip": "\u0421\u043a\u043e\u043f\u0438\u0440\u0443\u0439\u0442\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0438\u0437 \u0440\u0430\u0441\u043a\u0440\u044b\u0432\u0430\u044e\u0449\u0435\u0433\u043e\u0441\u044f \u0441\u043f\u0438\u0441\u043a\u0430. \u0412\u0441\u0435 \u043d\u0430 \u044d\u0442\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u0431\u0443\u0434\u0435\u0442 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u0430\u043d\u043e",
"DevDetail_EveandAl_AlertAllEvents": "\u041e\u043f\u043e\u0432\u0435\u0449\u0435\u043d\u0438\u0435 \u043e\u0431\u043e \u0432\u0441\u0435\u0445 \u0441\u043e\u0431\u044b\u0442\u0438\u044f\u0445",
"DevDetail_EveandAl_AlertDown": "\u041e\u043f\u043e\u0432\u0435\u0449\u0435\u043d\u0438\u0435 \u043e \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0441\u0442\u0438",
"DevDetail_EveandAl_Archived": "\u0410\u0440\u0445\u0438\u0432",
"DevDetail_EveandAl_NewDevice": "\u041d\u043e\u0432\u043e\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e",
"DevDetail_EveandAl_NewDevice_Tooltip": "\u0411\u0443\u0434\u0435\u0442 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0441\u0442\u0430\u0442\u0443\u0441 \u00ab\u041d\u043e\u0432\u043e\u0435\u00bb \u0434\u043b\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0438 \u0432\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u0435\u0433\u043e \u0432 \u0441\u043f\u0438\u0441\u043a\u0438, \u043a\u043e\u0433\u0434\u0430 \u0444\u0438\u043b\u044c\u0442\u0440 \u00ab\u041d\u043e\u0432\u044b\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u00bb \u0430\u043a\u0442\u0438\u0432\u0435\u043d. \u041d\u0435 \u0432\u043b\u0438\u044f\u0435\u0442 \u043d\u0430 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f.",
"DevDetail_EveandAl_RandomMAC": "\u0421\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u0439 MAC-\u0430\u0434\u0440\u0435\u0441",
"DevDetail_EveandAl_ScanCycle": "\u0421\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e",
"DevDetail_EveandAl_ScanCycle_a": "\u0421\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e",
"DevDetail_EveandAl_ScanCycle_z": "\u041d\u0435 \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e",
"DevDetail_EveandAl_Skip": "",
"DevDetail_EveandAl_Title": "",
"DevDetail_Events_CheckBox": "",
"DevDetail_GoToNetworkNode": "",
"DevDetail_Icon": "",
"DevDetail_Icon_Descr": "",
"DevDetail_Loading": "",
"DevDetail_MainInfo_Comments": "",
"DevDetail_MainInfo_Favorite": "",
"DevDetail_MainInfo_Group": "",
"DevDetail_MainInfo_Location": "",
"DevDetail_MainInfo_Name": "",
"DevDetail_MainInfo_Network": "",
"DevDetail_MainInfo_Network_Port": "",
"DevDetail_MainInfo_Network_Title": "",
"DevDetail_MainInfo_Owner": "",
"DevDetail_MainInfo_Title": "",
"DevDetail_MainInfo_Type": "",
"DevDetail_MainInfo_Vendor": "",
"DevDetail_MainInfo_mac": "",
"DevDetail_Network_Node_hover": "",
"DevDetail_Network_Port_hover": "",
"DevDetail_Nmap_Scans": "",
"DevDetail_Nmap_Scans_desc": "",
"DevDetail_Nmap_buttonDefault": "",
"DevDetail_Nmap_buttonDefault_text": "",
"DevDetail_Nmap_buttonDetail": "",
"DevDetail_Nmap_buttonDetail_text": "",
"DevDetail_Nmap_buttonFast": "",
"DevDetail_Nmap_buttonFast_text": "",
"DevDetail_Nmap_buttonSkipDiscovery": "",
"DevDetail_Nmap_buttonSkipDiscovery_text": "",
"DevDetail_Nmap_resultsLink": "",
"DevDetail_Owner_hover": "",
"DevDetail_Periodselect_All": "",
"DevDetail_Periodselect_LastMonth": "",
"DevDetail_Periodselect_LastWeek": "",
"DevDetail_Periodselect_LastYear": "",
"DevDetail_Periodselect_today": "",
"DevDetail_Run_Actions_Title": "",
"DevDetail_Run_Actions_Tooltip": "",
"DevDetail_SessionInfo_FirstSession": "",
"DevDetail_SessionInfo_LastIP": "",
"DevDetail_SessionInfo_LastSession": "",
"DevDetail_SessionInfo_StaticIP": "",
"DevDetail_SessionInfo_Status": "",
"DevDetail_SessionInfo_Title": "",
"DevDetail_SessionTable_Additionalinfo": "",
"DevDetail_SessionTable_Connection": "",
"DevDetail_SessionTable_Disconnection": "",
"DevDetail_SessionTable_Duration": "",
"DevDetail_SessionTable_IP": "",
"DevDetail_SessionTable_Order": "",
"DevDetail_Shortcut_CurrentStatus": "",
"DevDetail_Shortcut_DownAlerts": "",
"DevDetail_Shortcut_Presence": "",
"DevDetail_Shortcut_Sessions": "",
"DevDetail_Tab_Details": "",
"DevDetail_Tab_Events": "",
"DevDetail_Tab_EventsTableDate": "",
"DevDetail_Tab_EventsTableEvent": "",
"DevDetail_Tab_EventsTableIP": "",
"DevDetail_Tab_EventsTableInfo": "",
"DevDetail_Tab_Nmap": "",
"DevDetail_Tab_NmapEmpty": "",
"DevDetail_Tab_NmapTableExtra": "",
"DevDetail_Tab_NmapTableHeader": "",
"DevDetail_Tab_NmapTableIndex": "",
"DevDetail_Tab_NmapTablePort": "",
"DevDetail_Tab_NmapTableService": "",
"DevDetail_Tab_NmapTableState": "",
"DevDetail_Tab_NmapTableText": "",
"DevDetail_Tab_NmapTableTime": "",
"DevDetail_Tab_Plugins": "",
"DevDetail_Tab_Presence": "",
"DevDetail_Tab_Sessions": "",
"DevDetail_Tab_Tools": "",
"DevDetail_Tab_Tools_Internet_Info_Description": "",
"DevDetail_Tab_Tools_Internet_Info_Error": "",
"DevDetail_Tab_Tools_Internet_Info_Start": "",
"DevDetail_Tab_Tools_Internet_Info_Title": "",
"DevDetail_Tab_Tools_Nslookup_Description": "",
"DevDetail_Tab_Tools_Nslookup_Error": "",
"DevDetail_Tab_Tools_Nslookup_Start": "",
"DevDetail_Tab_Tools_Nslookup_Title": "",
"DevDetail_Tab_Tools_Speedtest_Description": "",
"DevDetail_Tab_Tools_Speedtest_Start": "",
"DevDetail_Tab_Tools_Speedtest_Title": "",
"DevDetail_Tab_Tools_Traceroute_Description": "",
"DevDetail_Tab_Tools_Traceroute_Error": "",
"DevDetail_Tab_Tools_Traceroute_Start": "",
"DevDetail_Tab_Tools_Traceroute_Title": "",
"DevDetail_Tools_WOL": "",
"DevDetail_Tools_WOL_noti": "",
"DevDetail_Tools_WOL_noti_text": "",
"DevDetail_Type_hover": "",
"DevDetail_Vendor_hover": "",
"DevDetail_WOL_Title": "",
"DevDetail_button_Delete": "",
"DevDetail_button_DeleteEvents": "",
"DevDetail_button_DeleteEvents_Warning": "",
"DevDetail_button_OverwriteIcons": "",
"DevDetail_button_OverwriteIcons_Tooltip": "",
"DevDetail_button_OverwriteIcons_Warning": "",
"DevDetail_button_Reset": "",
"DevDetail_button_Save": "",
"Device_MultiEdit": "",
"Device_MultiEdit_Backup": "",
"Device_MultiEdit_Fields": "",
"Device_MultiEdit_MassActions": "",
"Device_MultiEdit_Tooltip": "",
"Device_Searchbox": "",
"Device_Shortcut_AllDevices": "",
"Device_Shortcut_Archived": "",
"Device_Shortcut_Connected": "",
"Device_Shortcut_Devices": "",
"Device_Shortcut_DownAlerts": "",
"Device_Shortcut_Favorites": "",
"Device_Shortcut_NewDevices": "",
"Device_Shortcut_OnlineChart": "",
"Device_TableHead_Connected_Devices": "",
"Device_TableHead_Favorite": "",
"Device_TableHead_FirstSession": "",
"Device_TableHead_Group": "",
"Device_TableHead_Icon": "",
"Device_TableHead_LastIP": "",
"Device_TableHead_LastIPOrder": "",
"Device_TableHead_LastSession": "",
"Device_TableHead_Location": "",
"Device_TableHead_MAC": "",
"Device_TableHead_MAC_full": "",
"Device_TableHead_Name": "",
"Device_TableHead_Owner": "",
"Device_TableHead_Parent_MAC": "",
"Device_TableHead_Port": "",
"Device_TableHead_RowID": "",
"Device_TableHead_Rowid": "",
"Device_TableHead_Status": "",
"Device_TableHead_Type": "",
"Device_TableHead_Vendor": "",
"Device_Table_Not_Network_Device": "",
"Device_Table_info": "",
"Device_Table_nav_next": "",
"Device_Table_nav_prev": "",
"Device_Tablelenght": "",
"Device_Tablelenght_all": "",
"Device_Title": "",
"Donations_Others": "",
"Donations_Platforms": "",
"Donations_Text": "",
"Donations_Title": "",
"ENABLE_PLUGINS_description": "",
"ENABLE_PLUGINS_name": "",
"Email_display_name": "",
"Email_icon": "",
"Events_Loading": "",
"Events_Periodselect_All": "",
"Events_Periodselect_LastMonth": "",
"Events_Periodselect_LastWeek": "",
"Events_Periodselect_LastYear": "",
"Events_Periodselect_today": "",
"Events_Searchbox": "",
"Events_Shortcut_AllEvents": "",
"Events_Shortcut_DownAlerts": "",
"Events_Shortcut_Events": "",
"Events_Shortcut_MissSessions": "",
"Events_Shortcut_NewDevices": "",
"Events_Shortcut_Sessions": "",
"Events_Shortcut_VoidSessions": "",
"Events_TableHead_AdditionalInfo": "",
"Events_TableHead_Connection": "",
"Events_TableHead_Date": "",
"Events_TableHead_Device": "",
"Events_TableHead_Disconnection": "",
"Events_TableHead_Duration": "",
"Events_TableHead_DurationOrder": "",
"Events_TableHead_EventType": "",
"Events_TableHead_IP": "",
"Events_TableHead_IPOrder": "",
"Events_TableHead_Order": "",
"Events_TableHead_Owner": "",
"Events_Table_info": "",
"Events_Table_nav_next": "",
"Events_Table_nav_prev": "",
"Events_Tablelenght": "",
"Events_Tablelenght_all": "",
"Events_Title": "",
"Gen_Action": "",
"Gen_AreYouSure": "",
"Gen_Backup": "",
"Gen_Cancel": "",
"Gen_Copy": "",
"Gen_DataUpdatedUITakesTime": "",
"Gen_Delete": "",
"Gen_DeleteAll": "",
"Gen_Error": "",
"Gen_LockedDB": "",
"Gen_Okay": "",
"Gen_Purge": "",
"Gen_ReadDocs": "",
"Gen_Restore": "",
"Gen_Run": "",
"Gen_Save": "",
"Gen_Saved": "",
"Gen_Selected_Devices": "",
"Gen_Switch": "",
"Gen_Upd": "",
"Gen_Upd_Fail": "",
"Gen_Warning": "",
"Gen_Work_In_Progress": "",
"General_display_name": "",
"General_icon": "",
"HRS_TO_KEEP_NEWDEV_description": "",
"HRS_TO_KEEP_NEWDEV_name": "",
"HelpFAQ_Cat_Detail": "",
"HelpFAQ_Cat_Detail_300_head": "",
"HelpFAQ_Cat_Detail_300_text_a": "",
"HelpFAQ_Cat_Detail_300_text_b": "",
"HelpFAQ_Cat_Detail_301_head_a": "",
"HelpFAQ_Cat_Detail_301_head_b": "",
"HelpFAQ_Cat_Detail_301_text": "",
"HelpFAQ_Cat_Detail_302_head_a": "",
"HelpFAQ_Cat_Detail_302_head_b": "",
"HelpFAQ_Cat_Detail_302_text": "",
"HelpFAQ_Cat_Detail_303_head": "",
"HelpFAQ_Cat_Detail_303_text": "",
"HelpFAQ_Cat_Device_200_head": "",
"HelpFAQ_Cat_Device_200_text": "",
"HelpFAQ_Cat_General": "",
"HelpFAQ_Cat_General_100_head": "",
"HelpFAQ_Cat_General_100_text_a": "",
"HelpFAQ_Cat_General_100_text_b": "",
"HelpFAQ_Cat_General_100_text_c": "",
"HelpFAQ_Cat_General_101_head": "",
"HelpFAQ_Cat_General_101_text": "",
"HelpFAQ_Cat_General_102_head": "",
"HelpFAQ_Cat_General_102_text": "",
"HelpFAQ_Cat_General_102docker_head": "",
"HelpFAQ_Cat_General_102docker_text": "",
"HelpFAQ_Cat_General_103_head": "",
"HelpFAQ_Cat_General_103_text": "",
"HelpFAQ_Cat_Network_600_head": "",
"HelpFAQ_Cat_Network_600_text": "",
"HelpFAQ_Cat_Network_601_head": "",
"HelpFAQ_Cat_Network_601_text": "",
"HelpFAQ_Cat_Presence_400_head": "",
"HelpFAQ_Cat_Presence_400_text": "",
"HelpFAQ_Cat_Presence_401_head": "",
"HelpFAQ_Cat_Presence_401_text": "",
"HelpFAQ_Title": "",
"LOG_LEVEL_description": "",
"LOG_LEVEL_name": "",
"Loading": "",
"Login_Box": "",
"Login_Default_PWD": "",
"Login_Psw-box": "",
"Login_Psw_alert": "",
"Login_Psw_folder": "",
"Login_Psw_new": "",
"Login_Psw_run": "",
"Login_Remember": "",
"Login_Remember_small": "",
"Login_Submit": "",
"Login_Toggle_Alert_headline": "",
"Login_Toggle_Info": "",
"Login_Toggle_Info_headline": "",
"Maintenance_Running_Version": "",
"Maintenance_Status": "",
"Maintenance_Title": "",
"Maintenance_Tool_ExportCSV": "",
"Maintenance_Tool_ExportCSV_noti": "",
"Maintenance_Tool_ExportCSV_noti_text": "",
"Maintenance_Tool_ExportCSV_text": "",
"Maintenance_Tool_ImportCSV": "",
"Maintenance_Tool_ImportCSV_noti": "",
"Maintenance_Tool_ImportCSV_noti_text": "",
"Maintenance_Tool_ImportCSV_text": "",
"Maintenance_Tool_arpscansw": "",
"Maintenance_Tool_arpscansw_noti": "",
"Maintenance_Tool_arpscansw_noti_text": "",
"Maintenance_Tool_arpscansw_text": "",
"Maintenance_Tool_backup": "",
"Maintenance_Tool_backup_noti": "",
"Maintenance_Tool_backup_noti_text": "",
"Maintenance_Tool_backup_text": "",
"Maintenance_Tool_check_visible": "",
"Maintenance_Tool_darkmode": "",
"Maintenance_Tool_darkmode_noti": "",
"Maintenance_Tool_darkmode_noti_text": "",
"Maintenance_Tool_darkmode_text": "",
"Maintenance_Tool_del_ActHistory": "",
"Maintenance_Tool_del_ActHistory_noti": "",
"Maintenance_Tool_del_ActHistory_noti_text": "",
"Maintenance_Tool_del_ActHistory_text": "",
"Maintenance_Tool_del_alldev": "",
"Maintenance_Tool_del_alldev_noti": "",
"Maintenance_Tool_del_alldev_noti_text": "",
"Maintenance_Tool_del_alldev_text": "",
"Maintenance_Tool_del_allevents": "",
"Maintenance_Tool_del_allevents30": "",
"Maintenance_Tool_del_allevents30_noti": "",
"Maintenance_Tool_del_allevents30_noti_text": "",
"Maintenance_Tool_del_allevents30_text": "",
"Maintenance_Tool_del_allevents_noti": "",
"Maintenance_Tool_del_allevents_noti_text": "",
"Maintenance_Tool_del_allevents_text": "",
"Maintenance_Tool_del_empty_macs": "",
"Maintenance_Tool_del_empty_macs_noti": "",
"Maintenance_Tool_del_empty_macs_noti_text": "",
"Maintenance_Tool_del_empty_macs_text": "",
"Maintenance_Tool_del_selecteddev": "",
"Maintenance_Tool_del_selecteddev_text": "",
"Maintenance_Tool_del_unknowndev": "",
"Maintenance_Tool_del_unknowndev_noti": "",
"Maintenance_Tool_del_unknowndev_noti_text": "",
"Maintenance_Tool_del_unknowndev_text": "",
"Maintenance_Tool_displayed_columns_text": "",
"Maintenance_Tool_drag_me": "",
"Maintenance_Tool_order_columns_text": "",
"Maintenance_Tool_purgebackup": "",
"Maintenance_Tool_purgebackup_noti": "",
"Maintenance_Tool_purgebackup_noti_text": "",
"Maintenance_Tool_purgebackup_text": "",
"Maintenance_Tool_restore": "",
"Maintenance_Tool_restore_noti": "",
"Maintenance_Tool_restore_noti_text": "",
"Maintenance_Tool_restore_text": "",
"Maintenance_Tool_upgrade_database_noti": "",
"Maintenance_Tool_upgrade_database_noti_text": "",
"Maintenance_Tool_upgrade_database_text": "",
"Maintenance_Tools_Tab_BackupRestore": "",
"Maintenance_Tools_Tab_Logging": "",
"Maintenance_Tools_Tab_Settings": "",
"Maintenance_Tools_Tab_Tools": "",
"Maintenance_Tools_Tab_UISettings": "",
"Maintenance_arp_status": "",
"Maintenance_arp_status_off": "",
"Maintenance_arp_status_on": "",
"Maintenance_built_on": "",
"Maintenance_current_version": "",
"Maintenance_database_backup": "",
"Maintenance_database_backup_found": "",
"Maintenance_database_backup_total": "",
"Maintenance_database_lastmod": "",
"Maintenance_database_path": "",
"Maintenance_database_rows": "",
"Maintenance_database_size": "",
"Maintenance_lang_selector_apply": "",
"Maintenance_lang_selector_empty": "",
"Maintenance_lang_selector_lable": "",
"Maintenance_lang_selector_text": "",
"Maintenance_new_version": "",
"Maintenance_themeselector_apply": "",
"Maintenance_themeselector_empty": "",
"Maintenance_themeselector_lable": "",
"Maintenance_themeselector_text": "",
"Maintenance_version": "",
"NETWORK_DEVICE_TYPES_description": "",
"NETWORK_DEVICE_TYPES_name": "",
"Navigation_About": "",
"Navigation_Devices": "",
"Navigation_Donations": "",
"Navigation_Events": "",
"Navigation_HelpFAQ": "",
"Navigation_Integrations": "",
"Navigation_Maintenance": "",
"Navigation_Monitoring": "",
"Navigation_Network": "",
"Navigation_Plugins": "",
"Navigation_Presence": "",
"Navigation_Report": "",
"Navigation_Settings": "",
"Navigation_SystemInfo": "",
"Navigation_Workflows": "",
"Network_Assign": "",
"Network_Cant_Assign": "",
"Network_Configuration_Error": "",
"Network_Connected": "",
"Network_ManageAdd": "",
"Network_ManageAdd_Name": "",
"Network_ManageAdd_Name_text": "",
"Network_ManageAdd_Port": "",
"Network_ManageAdd_Port_text": "",
"Network_ManageAdd_Submit": "",
"Network_ManageAdd_Type": "",
"Network_ManageAdd_Type_text": "",
"Network_ManageAssign": "",
"Network_ManageDel": "",
"Network_ManageDel_Name": "",
"Network_ManageDel_Name_text": "",
"Network_ManageDel_Submit": "",
"Network_ManageDevices": "",
"Network_ManageEdit": "",
"Network_ManageEdit_ID": "",
"Network_ManageEdit_ID_text": "",
"Network_ManageEdit_Name": "",
"Network_ManageEdit_Name_text": "",
"Network_ManageEdit_Port": "",
"Network_ManageEdit_Port_text": "",
"Network_ManageEdit_Submit": "",
"Network_ManageEdit_Type": "",
"Network_ManageEdit_Type_text": "",
"Network_ManageLeaf": "",
"Network_ManageUnassign": "",
"Network_NoAssignedDevices": "",
"Network_NoDevices": "",
"Network_Node": "",
"Network_Node_Name": "",
"Network_Parent": "",
"Network_Root": "",
"Network_Root_Not_Configured": "",
"Network_Root_Unconfigurable": "",
"Network_Table_Hostname": "",
"Network_Table_IP": "",
"Network_Table_State": "",
"Network_Title": "",
"Network_UnassignedDevices": "",
"PIALERT_WEB_PASSWORD_description": "",
"PIALERT_WEB_PASSWORD_name": "",
"PIALERT_WEB_PROTECTION_description": "",
"PIALERT_WEB_PROTECTION_name": "",
"PLUGINS_KEEP_HIST_description": "",
"PLUGINS_KEEP_HIST_name": "",
"Plugins_DeleteAll": "",
"Plugins_Filters_Mac": "",
"Plugins_History": "",
"Plugins_Objects": "",
"Plugins_Out_of": "",
"Plugins_Unprocessed_Events": "",
"Plugins_no_control": "",
"Presence_CalHead_day": "",
"Presence_CalHead_lang": "",
"Presence_CalHead_month": "",
"Presence_CalHead_quarter": "",
"Presence_CalHead_week": "",
"Presence_CalHead_year": "",
"Presence_CallHead_Devices": "",
"Presence_Loading": "",
"Presence_Shortcut_AllDevices": "",
"Presence_Shortcut_Archived": "",
"Presence_Shortcut_Connected": "",
"Presence_Shortcut_Devices": "",
"Presence_Shortcut_DownAlerts": "",
"Presence_Shortcut_Favorites": "",
"Presence_Shortcut_NewDevices": "",
"Presence_Title": "",
"REPORT_DASHBOARD_URL_description": "",
"REPORT_DASHBOARD_URL_name": "",
"REPORT_ERROR": "",
"REPORT_MAIL_description": "",
"REPORT_MAIL_name": "",
"REPORT_TITLE": "",
"RandomMAC_hover": "",
"SCAN_SUBNETS_description": "",
"SYSTEM_TITLE": "",
"Setting_Override": "",
"Setting_Override_Description": "",
"Settings_Metadata_Toggle": "",
"Settings_device_Scanners_desync": "",
"Settings_device_Scanners_desync_popup": "",
"Speedtest_Results": "",
"Systeminfo_CPU": "",
"Systeminfo_CPU_Cores": "",
"Systeminfo_CPU_Name": "",
"Systeminfo_CPU_Speed": "",
"Systeminfo_CPU_Temp": "",
"Systeminfo_CPU_Vendor": "",
"Systeminfo_Client_Resolution": "",
"Systeminfo_Client_User_Agent": "",
"Systeminfo_General": "",
"Systeminfo_General_Date": "",
"Systeminfo_General_Date2": "",
"Systeminfo_General_Full_Date": "",
"Systeminfo_General_TimeZone": "",
"Systeminfo_Memory": "",
"Systeminfo_Memory_Total_Memory": "",
"Systeminfo_Memory_Usage": "",
"Systeminfo_Memory_Usage_Percent": "",
"Systeminfo_Motherboard": "",
"Systeminfo_Motherboard_BIOS": "",
"Systeminfo_Motherboard_BIOS_Date": "",
"Systeminfo_Motherboard_BIOS_Vendor": "",
"Systeminfo_Motherboard_Manufactured": "",
"Systeminfo_Motherboard_Name": "",
"Systeminfo_Motherboard_Revision": "",
"Systeminfo_Network": "",
"Systeminfo_Network_Accept_Encoding": "",
"Systeminfo_Network_Accept_Language": "",
"Systeminfo_Network_Connection_Port": "",
"Systeminfo_Network_HTTP_Host": "",
"Systeminfo_Network_HTTP_Referer": "",
"Systeminfo_Network_HTTP_Referer_String": "",
"Systeminfo_Network_Hardware": "",
"Systeminfo_Network_Hardware_Interface_Mask": "",
"Systeminfo_Network_Hardware_Interface_Name": "",
"Systeminfo_Network_Hardware_Interface_RX": "",
"Systeminfo_Network_Hardware_Interface_TX": "",
"Systeminfo_Network_IP": "",
"Systeminfo_Network_IP_Connection": "",
"Systeminfo_Network_IP_Server": "",
"Systeminfo_Network_MIME": "",
"Systeminfo_Network_Request_Method": "",
"Systeminfo_Network_Request_Time": "",
"Systeminfo_Network_Request_URI": "",
"Systeminfo_Network_Secure_Connection": "",
"Systeminfo_Network_Secure_Connection_String": "",
"Systeminfo_Network_Server_Name": "",
"Systeminfo_Network_Server_Name_String": "",
"Systeminfo_Network_Server_Query": "",
"Systeminfo_Network_Server_Query_String": "",
"Systeminfo_Network_Server_Version": "",
"Systeminfo_Services": "",
"Systeminfo_Services_Description": "",
"Systeminfo_Services_Name": "",
"Systeminfo_Storage": "",
"Systeminfo_Storage_Device": "",
"Systeminfo_Storage_Mount": "",
"Systeminfo_Storage_Size": "",
"Systeminfo_Storage_Type": "",
"Systeminfo_Storage_Usage": "",
"Systeminfo_Storage_Usage_Free": "",
"Systeminfo_Storage_Usage_Mount": "",
"Systeminfo_Storage_Usage_Total": "",
"Systeminfo_Storage_Usage_Used": "",
"Systeminfo_System": "",
"Systeminfo_System_AVG": "",
"Systeminfo_System_Architecture": "",
"Systeminfo_System_Kernel": "",
"Systeminfo_System_OSVersion": "",
"Systeminfo_System_Running_Processes": "",
"Systeminfo_System_System": "",
"Systeminfo_System_Uname": "",
"Systeminfo_System_Uptime": "",
"Systeminfo_This_Client": "",
"Systeminfo_USB_Devices": "",
"TIMEZONE_description": "",
"TIMEZONE_name": "",
"UI_LANG_description": "",
"UI_LANG_name": "",
"UI_MY_DEVICES_description": "",
"UI_MY_DEVICES_name": "",
"UI_NOT_RANDOM_MAC_description": "",
"UI_NOT_RANDOM_MAC_name": "",
"UI_PRESENCE_description": "",
"UI_PRESENCE_name": "",
"devices_old": "",
"general_event_description": "",
"general_event_title": "",
"report_guid": "",
"report_guid_missing": "",
"report_select_format": "",
"report_time": "",
"run_event_icon": "",
"run_event_tooltip": "",
"settings_core_icon": "",
"settings_core_label": "",
"settings_device_scanners": "",
"settings_device_scanners_icon": "",
"settings_device_scanners_label": "",
"settings_enabled": "",
"settings_enabled_icon": "",
"settings_expand_all": "",
"settings_imported": "",
"settings_imported_label": "",
"settings_missing": "",
"settings_missing_block": "",
"settings_old": "",
"settings_other_scanners": "",
"settings_other_scanners_icon": "",
"settings_other_scanners_label": "",
"settings_publishers": "",
"settings_publishers_icon": "",
"settings_publishers_label": "",
"settings_saved": "",
"settings_system_icon": "",
"settings_system_label": "",
"test_event_icon": "",
"test_event_tooltip": ""
}

View File

@@ -4,8 +4,6 @@
require 'php/templates/notification.php';
?>
<script src="js/pialert_common.js"></script>
<!-- Page ------------------------------------------------------------------ -->
<div class="content-wrapper">

View File

@@ -28,6 +28,7 @@
| | | PHOLUS | Script | ♻ other | 📚[pholus_scan](/front/plugins/pholus_scan/) |
| | Yes | PIHOLE | External SQLite DB | 🔍dev scanner | 📚[pihole_scan](/front/plugins/pihole_scan/) |
| | | PUSHSAFER | Script | 💬 publisher | 📚[_publisher_pushsafer](/front/plugins/_publisher_pushsafer/) |
| | | PUSHOVER | Script | 💬 publisher | 📚[_pushover_pushsafer](/front/plugins/_publisher_pushover/) |
| | | SETPWD | Script | ⚙ system | 📚[set_password](/front/plugins/set_password/) |
| | | SMTP | Script | 💬 publisher | 📚[_publisher_email](/front/plugins/_publisher_email/) |
| | Yes | SNMPDSC | Script | 🔍dev scanner | 📚[snmp_discovery](/front/plugins/snmp_discovery/) |
@@ -647,6 +648,7 @@ The UI will adjust how columns are displayed in the UI based on the resolvers de
| See below for information on `threshold`, `replace`. | |
| | |
| `options` Property | Used in conjunction with types like `threshold`, `replace`, `regex`. |
| `options_params` Property | Used in conjunction with a `"options": "[{value}]"` template and `text.select`. Can specify SQL query (needs to return 2 columns `SELECT dev_Name as name, dev_Mac as id`) or Setting (not tested) to populate the dropdown. Check example below or have a look at the `NEWDEV` plugin `config.json` file. |
| `threshold` | The `options` array contains objects ordered from the lowest `maximum` to the highest. The corresponding `hexColor` is used for the value background color if it's less than the specified `maximum` but more than the previous one in the `options` array. |
| `replace` | The `options` array contains objects with an `equals` property, which is compared to the "value." If the values are the same, the string in `replacement` is displayed in the UI instead of the actual "value". |
| `regex` | Applies a regex to the value. The `options` array contains objects with an `type` (must be set to `regex`) and `param` (must contain the regex itself) property. |
@@ -665,6 +667,26 @@ The UI will adjust how columns are displayed in the UI based on the resolvers de
> Supports chaining. You can chain multiple resolvers with `.`. For example `regex.url_http_https`. This will apply the `regex` resolver and then the `url_http_https` resolver.
```json
"function": "dev_DeviceType",
"type": "text.select",
"maxLength": 30,
"default_value": "",
"options": ["{value}"],
"options_params" : [
{
"name" : "value",
"type" : "sql",
"value" : "SELECT '' as id, '' as name UNION SELECT dev_DeviceType as id, dev_DeviceType as name FROM (SELECT dev_DeviceType FROM Devices UNION SELECT 'Smartphone' UNION SELECT 'Tablet' UNION SELECT 'Laptop' UNION SELECT 'PC' UNION SELECT 'Printer' UNION SELECT 'Server' UNION SELECT 'NAS' UNION SELECT 'Domotic' UNION SELECT 'Game Console' UNION SELECT 'SmartTV' UNION SELECT 'Clock' UNION SELECT 'House Appliance' UNION SELECT 'Phone' UNION SELECT 'AP' UNION SELECT 'Gateway' UNION SELECT 'Firewall' UNION SELECT 'Switch' UNION SELECT 'WLAN' UNION SELECT 'Router' UNION SELECT 'Other') AS all_devices ORDER BY id;"
},
{
"name" : "uilang",
"type" : "setting",
"value" : "UI_LANG"
}
]
```
```json
{

View File

@@ -276,7 +276,7 @@
"description": [
{
"language_code": "en_us",
"string" : "Enable sending notifications via <a target=\"_blank\" href=\"https://hub.docker.com/r/caronc/apprise\">Apprise</a>."
"string" : "Enable sending notifications via a self-hosted <a target=\"_blank\" href=\"https://hub.docker.com/r/caronc/apprise\">Apprise</a> instance. Please specify the URL on which you are running your instance in the <code>APPRISE_HOST</code> setting."
},
{
"language_code": "es_es",

View File

@@ -464,13 +464,28 @@
}],
"description": [{
"language_code": "en_us",
"string" : "Quality of service setting for MQTT message sending. <code>0</code> - Low quality to <code>2</code> - High quality. The higher the quality the longer the delay."
"string" : "Quality of service setting for MQTT message sending. The higher the quality the longer the delay. <br/> <code>0</code> - Low quality to <code>2</code> - High quality."
},
{
"language_code": "es_es",
"string" : "Configuración de calidad de servicio para el envío de mensajes MQTT. <code>0</code>: baja calidad a <code>2</code>: alta calidad. Cuanto mayor sea la calidad, mayor será el retraso."
}]
},
{
"function": "VERSION",
"type": "integer.select",
"default_value": 1,
"options": [ 1, 2 ],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "Version"
}],
"description": [{
"language_code": "en_us",
"string" : "Paho MQTT API version. Depends on the MQTT <a href=\"https://eclipse.dev/paho/files/paho.mqtt.python/html/index.html#callbacks\" target=\"_blank\">version supported by the MQTT broker</a>. Usually set to <code>1</code>."
}]
},
{
"function": "DELAY_SEC",
"type": "integer",

View File

@@ -9,7 +9,9 @@ import sys
from datetime import datetime
import time
import re
from paho.mqtt import client as mqtt_client
import paho.mqtt.client as mqtt
# from paho.mqtt import client as mqtt_client
# from paho.mqtt import CallbackAPIVersion as mqtt_CallbackAPIVersion
import hashlib
@@ -37,13 +39,12 @@ plugin_objects = Plugin_Objects(RESULT_FILE)
md5_hash = hashlib.md5()
pluginName = 'MQTT'
module_name = pluginName
# globals
mqtt_sensors = []
mqtt_connected_to_broker = False
client = None # mqtt client
mqtt_client = None # mqtt client
def main():
@@ -51,7 +52,7 @@ def main():
# Check if basic config settings supplied
if check_config() == False:
mylog('none', [f'[{pluginName}] ⚠ ERROR: Publisher notification gateway not set up correctly. Check your pialert.conf {pluginName}_* variables.'])
mylog('verbose', [f'[{pluginName}] ⚠ ERROR: Publisher notification gateway not set up correctly. Check your pialert.conf {pluginName}_* variables.'])
return
# Create a database connection
@@ -70,7 +71,7 @@ def main():
#-------------------------------------------------------------------------------
def check_config():
if get_setting_value('MQTT_BROKER') == '' or get_setting_value('MQTT_PORT') == '' or get_setting_value('MQTT_USER') == '' or get_setting_value('MQTT_PASSWORD') == '':
mylog('none', ['[Check Config] ⚠ ERROR: MQTT service not set up correctly. Check your pialert.conf MQTT_* variables.'])
mylog('verbose', ['[Check Config] ⚠ ERROR: MQTT service not set up correctly. Check your pialert.conf MQTT_* variables.'])
return False
else:
return True
@@ -130,46 +131,61 @@ class sensor_config:
#-------------------------------------------------------------------------------
def publish_mqtt(client, topic, message):
def publish_mqtt(mqtt_client, topic, message):
status = 1
message = json.dumps(message).replace("'",'"')
qos = get_setting_value('MQTT_QOS')
mylog('verbose', [f"[{pluginName}] Sending MQTT topic: {topic}"])
mylog('verbose', [f"[{pluginName}] Sending MQTT message: {message}"])
# mylog('verbose', [f"[{pluginName}] get_setting_value('MQTT_QOS'): {qos}"])
if mqtt_connected_to_broker == False:
mylog('verbose', [f"[{pluginName}] ⚠ ERROR: Not connected to broker, aborting."])
return False
while status != 0:
result = client.publish(
# mylog('verbose', [f"[{pluginName}] mqtt_client.publish "])
# mylog('verbose', [f"[{pluginName}] mqtt_client.is_connected(): {mqtt_client.is_connected()} "])
result = mqtt_client.publish(
topic=topic,
payload=message,
qos=get_setting_value('MQTT_QOS'),
qos=qos,
retain=True,
)
status = result[0]
# mylog('verbose', [f"[{pluginName}] status: {status}"])
# mylog('verbose', [f"[{pluginName}] result: {result}"])
if status != 0:
mylog('minimal', [f"[{pluginName}] Waiting to reconnect to MQTT broker"])
mylog('verbose', [f"[{pluginName}] Waiting to reconnect to MQTT broker"])
time.sleep(0.1)
return True
#-------------------------------------------------------------------------------
def create_generic_device(client):
def create_generic_device(mqtt_client):
deviceName = 'PiAlert'
deviceId = 'pialert'
create_sensor(client, deviceId, deviceName, 'sensor', 'online', 'wifi-check')
create_sensor(client, deviceId, deviceName, 'sensor', 'down', 'wifi-cancel')
create_sensor(client, deviceId, deviceName, 'sensor', 'all', 'wifi')
create_sensor(client, deviceId, deviceName, 'sensor', 'archived', 'wifi-lock')
create_sensor(client, deviceId, deviceName, 'sensor', 'new', 'wifi-plus')
create_sensor(client, deviceId, deviceName, 'sensor', 'unknown', 'wifi-alert')
create_sensor(mqtt_client, deviceId, deviceName, 'sensor', 'online', 'wifi-check')
create_sensor(mqtt_client, deviceId, deviceName, 'sensor', 'down', 'wifi-cancel')
create_sensor(mqtt_client, deviceId, deviceName, 'sensor', 'all', 'wifi')
create_sensor(mqtt_client, deviceId, deviceName, 'sensor', 'archived', 'wifi-lock')
create_sensor(mqtt_client, deviceId, deviceName, 'sensor', 'new', 'wifi-plus')
create_sensor(mqtt_client, deviceId, deviceName, 'sensor', 'unknown', 'wifi-alert')
#-------------------------------------------------------------------------------
def create_sensor(client, deviceId, deviceName, sensorType, sensorName, icon, mac=""):
def create_sensor(mqtt_client, deviceId, deviceName, sensorType, sensorName, icon, mac=""):
global mqtt_sensors
@@ -177,35 +193,37 @@ def create_sensor(client, deviceId, deviceName, sensorType, sensorName, icon, ma
# save if new
if new_sensor_config.isNew:
mylog('minimal', [f"[{pluginName}] Publishing sensor number {len(mqtt_sensors)}"])
publish_sensor(client, new_sensor_config)
mylog('verbose', [f"[{pluginName}] Publishing sensor number {len(mqtt_sensors)}"])
publish_sensor(mqtt_client, new_sensor_config)
#-------------------------------------------------------------------------------
def publish_sensor(client, sensorConfig):
def publish_sensor(mqtt_client, sensorConfig):
global mqtt_sensors
message = '{ \
"name":"'+sensorConfig.sensorName+'", \
"state_topic":"system-sensors/'+sensorConfig.sensorType+'/'+sensorConfig.deviceId+'/state", \
"value_template":"{{value_json.'+sensorConfig.sensorName+'}}", \
"unique_id":"'+sensorConfig.deviceId+'_sensor_'+sensorConfig.sensorName+'", \
"device": \
{ \
"identifiers": ["'+sensorConfig.deviceId+'_sensor"], \
"manufacturer": "PiAlert", \
"name":"'+sensorConfig.deviceName+'" \
}, \
"icon":"mdi:'+sensorConfig.icon+'" \
}'
icon = "mdi:" + sensorConfig.icon
message = {
"name" : sensorConfig.sensorName,
"state_topic" : "system-sensors/"+sensorConfig.sensorType+'/'+sensorConfig.deviceId+"/state",
"value_template" : "{{value_json."+sensorConfig.sensorName+"}}",
"unique_id" : sensorConfig.deviceId+'_sensor_'+sensorConfig.sensorName,
"device":
{
"identifiers" : [sensorConfig.deviceId+"_sensor"],
"manufacturer" : "PiAlert",
"name" : sensorConfig.deviceName
},
"icon": icon
}
topic='homeassistant/'+sensorConfig.sensorType+'/'+sensorConfig.deviceId+'/'+sensorConfig.sensorName+'/config'
# add the sensor to the global list to keep track of succesfully added sensors
if publish_mqtt(client, topic, message):
if publish_mqtt(mqtt_client, topic, message):
# hack - delay adding to the queue in case the process is
time.sleep(get_setting_value('MQTT_DELAY_SEC')) # restarted and previous publish processes aborted
# (it takes ~2s to update a sensor config on the broker)
@@ -213,7 +231,7 @@ def publish_sensor(client, sensorConfig):
#-------------------------------------------------------------------------------
def mqtt_create_client():
def on_disconnect(client, userdata, rc):
def on_disconnect(mqtt_client, userdata, reason_code):
global mqtt_connected_to_broker
@@ -222,61 +240,63 @@ def mqtt_create_client():
# not sure is below line is correct / necessary
# client = mqtt_create_client()
def on_connect(client, userdata, flags, rc):
def on_connect(mqtt_client, userdata, flags, reason_code):
global mqtt_connected_to_broker
if rc == 0:
if reason_code == 0:
mylog('verbose', [f"[{pluginName}] Connected to broker"])
mqtt_connected_to_broker = True # Signal connection
else:
mylog('none', [f"[{pluginName}] Connection failed"])
mylog('verbose', [f"[{pluginName}] Connection failed, reason_code: {reason_code}"])
mqtt_connected_to_broker = False
global client
global mqtt_client
client = mqtt_client.Client('PiAlert') # Set Connecting Client ID
client.username_pw_set(get_setting_value('MQTT_USER'), get_setting_value('MQTT_PASSWORD'))
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.connect(get_setting_value('MQTT_BROKER'), get_setting_value('MQTT_PORT'))
client.loop_start()
if get_setting_value('MQTT_VERSION') == 1:
mqtt_client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION1)
else:
mqtt_client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
return client
mqtt_client.username_pw_set(get_setting_value('MQTT_USER'), get_setting_value('MQTT_PASSWORD'))
mqtt_client.on_connect = on_connect
mqtt_client.on_disconnect = on_disconnect
mqtt_client.connect(get_setting_value('MQTT_BROKER'), get_setting_value('MQTT_PORT'))
mqtt_client.loop_start()
return mqtt_client
#-------------------------------------------------------------------------------
def mqtt_start(db):
global client, mqtt_connected_to_broker
global mqtt_client, mqtt_connected_to_broker
if mqtt_connected_to_broker == False:
mqtt_connected_to_broker = True
client = mqtt_create_client()
mqtt_client = mqtt_create_client()
# General stats
# Create a generic device for overal stats
if get_setting_value('MQTT_SEND_STATS') == True:
# Create a new device representing overall PiAlert stats
create_generic_device(client)
create_generic_device(mqtt_client)
# Get the data
row = get_device_stats(db)
columns = ["online","down","all","archived","new","unknown"]
payload = ""
# Update the values
for column in columns:
payload += '"'+column+'": ' + str(row[column]) +','
# Publish (wrap into {} and remove last ',' from above)
publish_mqtt(client, "system-sensors/sensor/pialert/state",
'{ \
'+ payload[:-1] +'\
}'
publish_mqtt(mqtt_client, "system-sensors/sensor/pialert/state",
{
"online": row[0],
"down": row[1],
"all": row[2],
"archived": row[3],
"new": row[4],
"unknown": row[5]
}
)
# Generate device-specific MQTT messages if enabled
@@ -289,37 +309,37 @@ def mqtt_start(db):
sec_delay = len(devices) * int(get_setting_value('MQTT_DELAY_SEC'))*5
mylog('minimal', [f"[{pluginName}] Estimated delay: ", (sec_delay), 's ', '(', round(sec_delay/60,1) , 'min)' ])
mylog('verbose', [f"[{pluginName}] Estimated delay: ", (sec_delay), 's ', '(', round(sec_delay/60,1) , 'min)' ])
# debug_index = 0
for device in devices:
# Create devices in Home Assistant - send config messages
deviceId = 'mac_' + device["dev_MAC"].replace(" ", "").replace(":", "_").lower()
deviceNameDisplay = re.sub('[^a-zA-Z0-9-_\s]', '', device["dev_Name"])
create_sensor(client, deviceId, deviceNameDisplay, 'sensor', 'last_ip', 'ip-network', device["dev_MAC"])
create_sensor(client, deviceId, deviceNameDisplay, 'binary_sensor', 'is_present', 'wifi', device["dev_MAC"])
create_sensor(client, deviceId, deviceNameDisplay, 'sensor', 'mac_address', 'folder-key-network', device["dev_MAC"])
create_sensor(client, deviceId, deviceNameDisplay, 'sensor', 'is_new', 'bell-alert-outline', device["dev_MAC"])
create_sensor(client, deviceId, deviceNameDisplay, 'sensor', 'vendor', 'cog', device["dev_MAC"])
create_sensor(mqtt_client, deviceId, deviceNameDisplay, 'sensor', 'last_ip', 'ip-network', device["dev_MAC"])
create_sensor(mqtt_client, deviceId, deviceNameDisplay, 'binary_sensor', 'is_present', 'wifi', device["dev_MAC"])
create_sensor(mqtt_client, deviceId, deviceNameDisplay, 'sensor', 'mac_address', 'folder-key-network', device["dev_MAC"])
create_sensor(mqtt_client, deviceId, deviceNameDisplay, 'sensor', 'is_new', 'bell-alert-outline', device["dev_MAC"])
create_sensor(mqtt_client, deviceId, deviceNameDisplay, 'sensor', 'vendor', 'cog', device["dev_MAC"])
# update device sensors in home assistant
publish_mqtt(client, 'system-sensors/sensor/'+deviceId+'/state',
'{ \
"last_ip": "' + device["dev_LastIP"] +'", \
"is_new": "' + str(device["dev_NewDevice"]) +'", \
"vendor": "' + sanitize_string(device["dev_Vendor"]) +'", \
"mac_address": "' + str(device["dev_MAC"]) +'" \
}'
publish_mqtt(mqtt_client, 'system-sensors/sensor/'+deviceId+'/state',
{
"last_ip": device["dev_LastIP"],
"is_new": str(device["dev_NewDevice"]),
"vendor": sanitize_string(device["dev_Vendor"]),
"mac_address": str(device["dev_MAC"])
}
)
publish_mqtt(client, 'system-sensors/binary_sensor/'+deviceId+'/state',
'{ \
"is_present": "' + to_binary_sensor(str(device["dev_PresentLastScan"])) +'"\
}'
publish_mqtt(mqtt_client, 'system-sensors/binary_sensor/'+deviceId+'/state',
{
"is_present": to_binary_sensor(str(device["dev_PresentLastScan"]))
}
)
# delete device / topic

View File

@@ -404,6 +404,28 @@
"string": "Your Pushover APP Token."
}
]
},
{
"function": "DEVICE_NAME",
"type": "text",
"default_value": "DEVICE_NAME",
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Pushover Device name"
}
],
"description": [
{
"language_code": "en_us",
"string": "(Optional) When specifying a device name, notifications will be exclusively sent to the device."
}
]
}
]
}
}

View File

@@ -71,18 +71,28 @@ def send(text):
user_key = get_setting_value("PUSHOVER_USER_KEY")
app_token = get_setting_value("PUSHOVER_APP_TOKEN")
device_name = (
None
if get_setting_value("PUSHOVER_DEVICE_NAME") == "DEVICE_NAME"
else get_setting_value("PUSHOVER_DEVICE_NAME")
)
mylog("verbose", f'[{pluginName}] PUSHOVER_USER_KEY: "{hide_string(user_key)}"')
mylog("verbose", f'[{pluginName}] PUSHOVER_APP_TOKEN: "{hide_string(app_token)}"')
data = {"token": app_token, "user": user_key, "message": text}
# Add device_name to the data dictionary only if it is not None
if device_name:
data["device"] = device_name
try:
response = requests.post(
"https://api.pushover.net/1/messages.json",
data={"token": app_token, "user": user_key, "message": text},
)
response = requests.post("https://api.pushover.net/1/messages.json", data=data)
# Update response_status_code with the actual status code from the response
response_status_code = response.status_code
# Check if the request was successful (status code 200)
if response.status_code == 200:
if response_status_code == 200:
response_text = response.text # This captures the response body/message
else:
response_text = json.dumps(response.text)

View File

@@ -12,7 +12,7 @@
## Overview
Plugin generating CSV backups of your Devices database table, including the network mappings. Can be used for importing your setup via the Maintenance > Backup / Restore > CSV Import feature.
Plugin generating CSV backups of your Devices database table, including the network mappings. Can be used for importing your setup via the Maintenance > Backup / Restore > CSV Import feature (See also: [Devices Bulk Editing](https://github.com/jokob-sk/Pi.Alert/blob/main/docs/DEVICES_BULK_EDITING.md)).
### Usage

View File

@@ -58,7 +58,7 @@
"description": [
{
"language_code": "en_us",
"string": "List of IPs to ignore. Use <code>%</code> as a wildcard. Ignored devices will not be shown anywhere in the UI or notifications. <br/><br/>For example <code>192.168.3.%</code> to filter out a subnet."
"string": "List of IPs to ignore. Use <code>%</code> as a wildcard. Ignored devices will not be shown anywhere in the UI or notifications. <br/><br/>For example <code>192.168.3.%</code> to filter out an IP range."
}
]
},
@@ -104,10 +104,17 @@
},
{
"function": "dev_Owner",
"type": "string",
"type": "text.select",
"maxLength": 30,
"default_value": "House",
"options": [],
"default_value": "",
"options": ["{value}"],
"options_params" : [
{
"name" : "value",
"type" : "sql",
"value" : "SELECT DISTINCT '' as id, '' as name UNION SELECT dev_Owner as id, dev_Owner as name FROM (SELECT dev_Owner FROM Devices UNION SELECT 'House' ) AS all_devices ORDER BY id;"
}
],
"localized": ["name", "description"],
"name": [
{
@@ -124,10 +131,22 @@
},
{
"function": "dev_DeviceType",
"type": "string",
"type": "text.select",
"maxLength": 30,
"default_value": "",
"options": [],
"options": ["{value}"],
"options_params" : [
{
"name" : "value",
"type" : "sql",
"value" : "SELECT '' as id, '' as name UNION SELECT dev_DeviceType as id, dev_DeviceType as name FROM (SELECT dev_DeviceType FROM Devices UNION SELECT 'Smartphone' UNION SELECT 'Tablet' UNION SELECT 'Laptop' UNION SELECT 'PC' UNION SELECT 'Printer' UNION SELECT 'Server' UNION SELECT 'NAS' UNION SELECT 'Domotic' UNION SELECT 'Game Console' UNION SELECT 'SmartTV' UNION SELECT 'Clock' UNION SELECT 'House Appliance' UNION SELECT 'Phone' UNION SELECT 'AP' UNION SELECT 'Gateway' UNION SELECT 'Firewall' UNION SELECT 'Switch' UNION SELECT 'WLAN' UNION SELECT 'Router' UNION SELECT 'Other') AS all_devices ORDER BY id;"
},
{
"name" : "uilang",
"type" : "setting",
"value" : "UI_LANG"
}
],
"localized": ["name", "description"],
"name": [
{
@@ -183,10 +202,17 @@
},
{
"function": "dev_Group",
"type": "string",
"type": "text.select",
"maxLength": 10,
"default_value": "",
"options": [],
"options": ["{value}"],
"options_params" : [
{
"name" : "value",
"type" : "sql",
"value" : "SELECT DISTINCT '' as id, '' as name UNION SELECT dev_Group as id, dev_Group as name FROM (SELECT dev_Group FROM Devices UNION SELECT 'Personal' ) AS all_devices ORDER BY id;"
}
],
"localized": ["name", "description"],
"name": [
{
@@ -376,14 +402,21 @@
},
{
"function": "dev_SkipRepeated",
"type": "integer",
"type": "text.select",
"default_value": 0,
"options": [],
"options": ["{value}"],
"options_params" : [
{
"name" : "value",
"type" : "sql",
"value" : "SELECT '0' as id, '0 (notify all)' as name UNION SELECT '168' as id, '1 week' as name UNION SELECT '24' as id, '1 day' as name UNION SELECT '8' as id, '8 h' as name UNION SELECT '1' as id, '1 h' as name"
}
],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Skip Repeated"
"string": "Skip Repeated (h)"
}
],
"description": [
@@ -447,16 +480,23 @@
"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."
"string": "Indicates whether the device is considered a new device. The default value of the <code>New Device</code> checkbox. If checked this will show the New status for the device and include it in lists when the New Devices filter is active. Doesn't affect notifications."
}
]
},
{
"function": "dev_Location",
"type": "string",
"type": "text.select",
"maxLength": 250,
"default_value": "",
"options": [],
"options": ["{value}"],
"options_params" : [
{
"name" : "value",
"type" : "sql",
"value" : "SELECT DISTINCT '' as id, '' as name UNION SELECT dev_Location as id, dev_Location as name FROM (SELECT dev_Location FROM Devices where dev_Location not in (null, 'null', '') UNION SELECT 'Bathroom' UNION SELECT 'Bedroom' UNION SELECT 'Dining room' UNION SELECT 'Hall' UNION SELECT 'Kitchen' UNION SELECT 'Laundry' UNION SELECT 'Living room' UNION SELECT 'Study' UNION SELECT 'Attic' UNION SELECT 'Basement' UNION SELECT 'Garage' UNION SELECT 'Back yard' UNION SELECT 'Garden' UNION SELECT 'Terrace') AS all_devices ORDER BY id; "
}
],
"localized": ["name", "description"],
"name": [
{
@@ -492,9 +532,21 @@
},
{
"function": "dev_Network_Node_MAC_ADDR",
"type": "string",
"type": "text.select",
"default_value": "",
"options": [],
"options": ["{value}"],
"options_params" : [
{
"name" : "value",
"type" : "sql",
"value" : "SELECT '' as name, '' as id UNION SELECT Dev_Name as name, dev_MAC as id FROM Devices WHERE EXISTS (SELECT 1 FROM Settings WHERE Code_Name = 'NETWORK_DEVICE_TYPES' AND LOWER(value) LIKE '%' || LOWER(dev_DeviceType) || '%' AND dev_DeviceType <> '')"
},
{
"name" : "target_macs",
"type" : "setting",
"value" : "KNWN_target_macs"
}
],
"localized": ["name", "description"],
"name": [
{

View File

@@ -95,7 +95,7 @@ def execute_nslookup (ip, timeout):
domain_name = ''
dns_server = ''
mylog('verbose', [f'[{pluginName}] DEBUG OUTPUT : {output}'])
# mylog('verbose', [f'[{pluginName}] DEBUG OUTPUT : {output}'])
# Parse output using case-insensitive regular expressions
domain_pattern = re.compile(r'name\s*=\s*([^\s]+)', re.IGNORECASE)
@@ -116,14 +116,20 @@ def execute_nslookup (ip, timeout):
return domain_name, dns_server
except subprocess.CalledProcessError as e:
# An error occured, handle it
mylog('verbose', [f'[{pluginName}]', e.output])
mylog('verbose', [f'[{pluginName}] ⚠ ERROR - check logs'])
# An error occurred, handle it
if "NXDOMAIN" in e.output:
mylog('verbose', [f'[{pluginName}]', f"No PTR record found for IP: {ip}"])
else:
mylog('verbose', [f'[{pluginName}]', e.output])
# Handle other errors here
# mylog('verbose', [f'[{pluginName}] ⚠ ERROR - check logs'])
except subprocess.TimeoutExpired as timeErr:
mylog('verbose', [f'[{pluginName}] TIMEOUT - the process forcefully terminated as timeout reached'])
if output == "": # check if the subprocess failed
mylog('verbose', [f'[{pluginName}] Scan: FAIL - check logs'])
if output == "": # check if the subprocess failed
tmp = 1 # can't have empty
# mylog('verbose', [f'[{pluginName}] Scan: FAIL - check logs'])
else:
mylog('verbose', [f'[{pluginName}] Scan: SUCCESS'])

View File

@@ -204,7 +204,7 @@ function genericSaveData (id) {
$.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)
// console.log(data)
if(sanitize(data) == 'OK')
{
@@ -246,6 +246,9 @@ function getData(){
generateTabs()
// hide spinning icon
hideSpinner()
});
});
});
@@ -580,6 +583,8 @@ function purgeVisible() {
// -----------------------------------------------------------------------------
// Main sequence
// show spinning icon
showSpinner()
getData()
updater()

View File

@@ -53,8 +53,9 @@ while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
<!-- Page ------------------------------------------------------------------ -->
<!-- Page ------------------------------------------------------------------ -->
<script src="js/pialert_common.js"></script>
<script src="js/settings_utils.js"></script>
<script src="js/db_methods.js"></script>
<script src="js/ui_components.js"></script>
<div id="settingsPage" class="content-wrapper">
@@ -98,35 +99,35 @@ while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
<div class="content settingswrap " id="accordion_gen">
<div class ="bg-grey-dark color-palette box panel panel-default col-sm-12 box-default box-info" >
<div class ="bg-grey-dark color-palette box panel panel-default col-sm-12 box-default box-info" id="core_content_header" >
<div class ="settings-group col-sm-12">
<i class="<?= lang("settings_core_icon");?>"></i> <?= lang("settings_core_label");?>
</div>
<div class =" col-sm-12" id="core_content"></div>
</div>
<div class ="bg-grey-dark color-palette box panel panel-default col-sm-12 box-default box-info" >
<div class ="bg-grey-dark color-palette box panel panel-default col-sm-12 box-default box-info" id="system_content_header" >
<div class ="settings-group col-sm-12">
<i class="<?= lang("settings_system_icon");?>"></i> <?= lang("settings_system_label");?>
</div>
<div class =" col-sm-12" id="system_content"></div>
</div>
<div class ="bg-grey-dark color-palette box panel panel-default col-sm-12 box-default box-info" >
<div class ="bg-grey-dark color-palette box panel panel-default col-sm-12 box-default box-info" id="device_scanner_content_header" >
<div class ="settings-group col-sm-12">
<i class="<?= lang("settings_device_scanners_icon");?>"></i> <?= lang("settings_device_scanners_label");?>
</div>
<div class =" col-sm-12" id="device_scanner_content"></div>
</div>
<div class ="bg-grey-dark color-palette box panel panel-default col-sm-12 box-default box-info" >
<div class ="bg-grey-dark color-palette box panel panel-default col-sm-12 box-default box-info" id="other_content_header">
<div class ="settings-group col-sm-12">
<i class="<?= lang("settings_other_scanners_icon");?>"></i> <?= lang("settings_other_scanners_label");?>
</div>
<div class =" col-sm-12" id="other_content"></div>
</div>
<div class ="bg-grey-dark color-palette box panel panel-default col-sm-12 box-default box-info" >
<div class ="bg-grey-dark color-palette box panel panel-default col-sm-12 box-default box-info" id="publisher_content_header" >
<div class ="settings-group col-sm-12">
<i class="<?= lang("settings_publishers_icon");?>"></i> <?= lang("settings_publishers_label");?>
</div>
@@ -394,11 +395,11 @@ while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
if(setType.includes(".select"))
{
inputHtml = generateInputOptions(set, inputHtml, isMultiSelect = false)
inputHtml = generateInputOptions(pluginsData, set, inputHtml, isMultiSelect = false)
} else if(setType.includes(".multiselect"))
{
inputHtml = generateInputOptions(set, inputHtml, isMultiSelect = true)
inputHtml = generateInputOptions(pluginsData, set, inputHtml, isMultiSelect = true)
} else{
// if it's overridable set readonly accordingly
@@ -427,7 +428,7 @@ while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
inputHtml = `<input onChange="settingsChanged()" my-data-type="${setType}" class="checkbox" id="${codeName}" type="checkbox" value="${val}" ${checked} ${disabled}/>`;
} else if (setType === 'integer.select') {
inputHtml = generateInputOptions(set, inputHtml)
inputHtml = generateInputOptions(pluginsData, set, inputHtml)
} else if (setType === 'subnets') {
inputHtml = `
@@ -517,29 +518,58 @@ while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
}
setupSmoothScrolling()
}
// ---------------------------------------------------------
// generate a list of options for a input select
function generateInputOptions(set, input, isMultiSelect = false)
function generateInputOptions(pluginsData, set, input, isMultiSelect = false)
{
var optionsHtml = ""
multi = isMultiSelect ? "multiple" : "";
input = `<select onChange="settingsChanged()" my-data-type="${set['Type']}" class="form-control" name="${set['Code_Name']}" id="${set['Code_Name']}" ${multi}>`;
values = createArray(set['Value']);
options = createArray(set['Options']);
optionsArray = getSettingOptions(set['Code_Name'] )
valuesArray = createArray(set['Value']);
// // check if the result is a SQL query - if so, dropdown will be populated async with AJAX
// if(isSQLQuery(optionsArray))
// {
var targetLocation = set['Code_Name'] + "_initSettingDropdown";
// placeholder option which will be replaced on callback
optionsHtml += `<option id="${targetLocation}" temporary="temporary"></option>`;
options.forEach(option => {
let selected = values.includes(option) ? 'selected' : '';
input += `<option value="${option}" ${selected}>${option}</option>`;
});
// execute AJAX callabck + SQL query resolution
initSettingDropdown(set['Code_Name'] , valuesArray, targetLocation)
input += '</select>';
// }
// else // it's a string without a SQL resolution requirements
// {
// options = createArray(optionsArray);
// options.forEach(option => {
// let selected = valuesArray.includes(option) ? 'selected' : '';
// optionsHtml += `<option value="${option}" ${selected}>${option}</option>`;
// });
// }
// main selection dropdown wrapper
input += `
<select onChange="settingsChanged()"
my-data-type="${set['Type']}"
class="form-control"
name="${set['Code_Name']}"
id="${set['Code_Name']}" ${multi}>
${optionsHtml}
</select>`;
return input;
}
@@ -634,41 +664,6 @@ while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
$('#SCAN_SUBNETS').empty();
}
// ---------------------------------------------------------
function collectSettings()
{
var settingsArray = [];
// collect values for each of the different input form controls
const noConversion = ['text', 'integer', 'string', 'password', 'readonly', 'text.select', 'integer.select', 'text.multiselect'];
settingsJSON["data"].forEach(set => {
if (noConversion.includes(set['Type'])) {
settingsArray.push([set["Group"], set["Code_Name"], set["Type"], $('#'+set["Code_Name"]).val()]);
} else if (set['Type'] === 'boolean' || set['Type'] === 'integer.checkbox') {
const temp = $(`#${set["Code_Name"]}`).is(':checked') ? 1 : 0;
settingsArray.push([set["Group"], set["Code_Name"], set["Type"], temp]);
} else if (set['Type'] === 'list' || set['Type'] === 'subnets') {
const temps = [];
$(`#${set["Code_Name"]} option`).each(function (i, selected) {
const vl = $(selected).val();
if (vl !== '') {
temps.push(vl);
}
});
settingsArray.push([set["Group"], set["Code_Name"], set["Type"], JSON.stringify(temps)]);
} else if (set['Type'] === 'json') {
const temps = $('#'+set["Code_Name"]).val();
settingsArray.push([set["Group"], set["Code_Name"], set["Type"], temps]);
}
});
return settingsArray;
}
// ---------------------------------------------------------
function saveSettings() {
@@ -677,27 +672,65 @@ while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
showModalOk('WARNING', "<?= lang("settings_missing_block")?>");
} else
{
var settingsArray = [];
// trigger a save settings event in the backend
$.ajax({
method: "POST",
url: "php/server/util.php",
data: {
function: 'savesettings',
settings: JSON.stringify(collectSettings()) },
success: function(data, textStatus) {
showModalOk ('Result', data );
// Remove navigation prompt "Are you sure you want to leave..."
window.onbeforeunload = null;
// collect values for each of the different input form controls
const noConversion = ['text', 'integer', 'string', 'password', 'readonly', 'text.select', 'integer.select', 'text.multiselect'];
// Reloads the current page
setTimeout("window.location.reload()", 3000);
// get settings to determine setting type to store values appropriately
$.get('api/table_settings.json', function(res) {
settingsJSON = res;
data = settingsJSON["data"];
data.forEach(set => {
if (noConversion.includes(set['Type'])) {
settingsArray.push([set["Group"], set["Code_Name"], set["Type"], $('#'+set["Code_Name"]).val()]);
} else if (set['Type'] === 'boolean' || set['Type'] === 'integer.checkbox') {
const temp = $(`#${set["Code_Name"]}`).is(':checked') ? 1 : 0;
settingsArray.push([set["Group"], set["Code_Name"], set["Type"], temp]);
}
});
} else if (set['Type'] === 'list' || set['Type'] === 'subnets') {
const temps = [];
$(`#${set["Code_Name"]} option`).each(function (i, selected) {
const vl = $(selected).val();
if (vl !== '') {
temps.push(vl);
}
});
settingsArray.push([set["Group"], set["Code_Name"], set["Type"], JSON.stringify(temps)]);
} else if (set['Type'] === 'json') {
const temps = $('#'+set["Code_Name"]).val();
settingsArray.push([set["Group"], set["Code_Name"], set["Type"], temps]);
}
});
// trigger a save settings event in the backend
$.ajax({
method: "POST",
url: "php/server/util.php",
data: {
function: 'savesettings',
settings: JSON.stringify(settingsArray) },
success: function(data, textStatus) {
showModalOk ('Result', data );
// Remove navigation prompt "Are you sure you want to leave..."
window.onbeforeunload = null;
// Reloads the current page
setTimeout("window.location.reload()", 3000);
}
});
})
}
}
@@ -747,11 +780,12 @@ while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
} else
{
hideSpinner()
hideSpinner()
}
document.getElementById('lastImportedTime').innerHTML = humanReadable;
})
}
@@ -763,6 +797,8 @@ while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
<script defer>
// -----------------------------------------------------------------------------
// handling events on the backend initiated by the front end START
// -----------------------------------------------------------------------------

View File

@@ -96,17 +96,17 @@ $total_memorymb = number_format($total_memorymb, 0, '.', '.');
$mem_used = round(memory_get_usage() / 1048576 * 100, 2);
$memory_usage_percent = round(($mem_used / $total_memorymb), 2);
//HDD stats
$hdd_result = shell_exec("df | awk '{print $1}'");
$hdd_result = shell_exec(" df -P | awk '{print $1}'");
$hdd_devices = explode("\n", trim($hdd_result));
$hdd_result = shell_exec("df | awk '{print $2}'");
$hdd_result = shell_exec(" df -P | awk '{print $2}'");
$hdd_devices_total = explode("\n", trim($hdd_result));
$hdd_result = shell_exec("df | awk '{print $3}'");
$hdd_result = shell_exec(" df -P | awk '{print $3}'");
$hdd_devices_used = explode("\n", trim($hdd_result));
$hdd_result = shell_exec("df | awk '{print $4}'");
$hdd_result = shell_exec(" df -P | awk '{print $4}'");
$hdd_devices_free = explode("\n", trim($hdd_result));
$hdd_result = shell_exec("df | awk '{print $5}'");
$hdd_result = shell_exec(" df -P | awk '{print $5}'");
$hdd_devices_percent = explode("\n", trim($hdd_result));
$hdd_result = shell_exec("df | awk '{print $6}'");
$hdd_result = shell_exec(" df -P | awk '{print $6}'");
$hdd_devices_mount = explode("\n", trim($hdd_result));
//Network stats
// Check Server name
@@ -153,6 +153,55 @@ echo '<div class="box box-solid">
</div>
</div>';
// Network Hardware ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title sysinfo_headline"><i class="fas fa-network-wired"></i> ' . lang('Systeminfo_Network_Hardware') . '</h3>
</div>
<div class="box-body">
<table id="networkTable" class="table table-bordered table-hover">
<thead>
<tr>
<th>' . lang('Systeminfo_Network_Hardware_Interface_Name') . '</th>
<th>' . lang('Systeminfo_Network_Hardware_Interface_Mask') . '</th>
<th>' . lang('Systeminfo_Network_Hardware_Interface_RX') . '</th>
<th>' . lang('Systeminfo_Network_Hardware_Interface_TX') . '</th>
</tr>
</thead>
<tbody>';
for ($x = 0; $x < sizeof($net_interfaces); $x++) {
$interface_name = str_replace(':', '', $net_interfaces[$x]);
$interface_ip_temp = exec('ip addr show ' . $interface_name . ' | grep "inet "');
$interface_ip_arr = explode(' ', trim($interface_ip_temp));
if (!isset($interface_ip_arr[1])) {
$interface_ip_arr[1] = '--';
}
if ($net_interfaces_rx[$x] == 0) {
$temp_rx = 0;
} else {
$temp_rx = number_format(round(($net_interfaces_rx[$x] / 1024 / 1024), 2), 2, ',', '.');
}
if ($net_interfaces_tx[$x] == 0) {
$temp_tx = 0;
} else {
$temp_tx = number_format(round(($net_interfaces_tx[$x] / 1024 / 1024), 2), 2, ',', '.');
}
echo '<tr>';
echo '<td>' . $interface_name . '</td>';
echo '<td>' . $interface_ip_arr[1] . '</td>';
echo '<td>' . $temp_rx . ' MB</td>';
echo '<td>' . $temp_tx . ' MB</td>';
echo '</tr>';
}
echo ' </tbody>
</table>
</div>
</div>';
// Client ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
@@ -455,32 +504,8 @@ echo '<div class="box box-solid">
</div>
</div>';
// Network Hardware ----------------------------------------------------------
echo '<div class="box box-solid">
<div class="box-header">
<h3 class="box-title sysinfo_headline"><i class="fas fa-network-wired"></i> ' . lang('Systeminfo_Network_Hardware') . '</h3>
</div>
<div class="box-body">';
for ($x = 0; $x < sizeof($net_interfaces); $x++) {
$interface_name = str_replace(':', '', $net_interfaces[$x]);
$interface_ip_temp = exec('ip addr show ' . $interface_name . ' | grep "inet "');
$interface_ip_arr = explode(' ', trim($interface_ip_temp));
if (!isset($interface_ip_arr[1])) {$interface_ip_arr[1] = '--';}
if ($net_interfaces_rx[$x] == 0) {$temp_rx = 0;} else { $temp_rx = number_format(round(($net_interfaces_rx[$x] / 1024 / 1024), 2), 2, ',', '.');}
if ($net_interfaces_tx[$x] == 0) {$temp_tx = 0;} else { $temp_tx = number_format(round(($net_interfaces_tx[$x] / 1024 / 1024), 2), 2, ',', '.');}
echo '<div class="row">';
echo '<div class="col-sm-2 sysinfo_network_hardware_a">' . $interface_name . '</div>';
echo '<div class="col-sm-2 sysinfo_network_hardware_b">' . $interface_ip_arr[1] . '</div>';
echo '<div class="col-sm-3 sysinfo_network_hardware_b">RX: <div class="sysinfo_network_value">' . $temp_rx . ' MB</div></div>';
echo '<div class="col-sm-3 sysinfo_network_hardware_b">TX: <div class="sysinfo_network_value">' . $temp_tx . ' MB</div></div>';
echo '</div>';
}
echo ' </div>
</div>';
// Services ----------------------------------------------------------
echo '<div class="box box-solid">
@@ -553,3 +578,32 @@ echo '<br>';
<!-- /.content-wrapper -->
<!-- ----------------------------------------------------------------------- -->
<link rel="stylesheet" href="lib/AdminLTE/bower_components/datatables.net-bs/css/dataTables.bootstrap.min.css">
<link rel="stylesheet" href="lib/AdminLTE/bower_components/datatables.net/css/select.dataTables.min.css">
<script src="lib/AdminLTE/bower_components/datatables.net/js/jquery.dataTables.min.js"></script>
<script src="lib/AdminLTE/bower_components/datatables.net-bs/js/dataTables.bootstrap.min.js"></script>
<!-- DataTable initialization -->
<script>
// show spinning icon
showSpinner()
setTimeout(() => {
$('#networkTable').DataTable({
"searching": true,
"order": [[0, "desc"]]
});
// hide spinning icon
hideSpinner()
}, 500);
</script>

View File

@@ -4,7 +4,6 @@
require 'php/templates/notification.php';
?>
<script src="js/pialert_common.js"></script>
<!-- Page ------------------------------------------------------------------ -->
<div class="content-wrapper">

View File

@@ -1,10 +1,10 @@
#!/usr/bin/env bash
# 🛑 Important: This is only used for the bare-metal install 🛑
# Update /dockerfiles/start.sh in most cases is preferred
# Update /install/start.debian.sh in most cases is preferred
echo "---------------------------------------------------------"
echo "[INSTALL] Run install.sh"
echo "[INSTALL] Run install.debian.sh"
echo "---------------------------------------------------------"
# Set environment variables
@@ -35,4 +35,4 @@ if [ ! -f $INSTALL_DIR/pialert/front/buildtimestamp.txt ]; then
fi
# Start PiAlert
"$INSTALL_DIR/pialert/dockerfiles/start.sh"
"$INSTALL_DIR/pialert/install/start.debian.sh"

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash
echo "---------------------------------------------------------"
echo "[INSTALL] Run install_dependencies.sh"
echo "[INSTALL] Run install_dependencies.debian.sh"
echo "---------------------------------------------------------"
# ❗ IMPORTANT - if you modify this file modify the root Dockerfile as well ❗

View File

@@ -7,6 +7,8 @@ server {
proxy_set_header X-Forwarded-Prefix "/pialert";
location ~* \.php$ {
# Set Cache-Control header to prevent caching on the first load
add_header Cache-Control "no-store";
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

19
install/pialert.template.conf Executable file
View File

@@ -0,0 +1,19 @@
server {
listen ${LISTEN_ADDR}:${PORT} default_server;
root ${INSTALL_DIR}/pialert/front;
index index.php;
add_header X-Forwarded-Prefix "/pialert" always;
proxy_set_header X-Forwarded-Prefix "/pialert";
location ~* \.php$ {
# Set Cache-Control header to prevent caching on the first load
add_header Cache-Control "no-store";
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_connect_timeout 75;
fastcgi_send_timeout 600;
fastcgi_read_timeout 600;
}
}

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash
echo "---------------------------------------------------------"
echo "[INSTALL] Run start.sh"
echo "[INSTALL] Run start.debian.sh"
echo "---------------------------------------------------------"
@@ -33,8 +33,8 @@ fi
# Run setup scripts
echo "[INSTALL] Run setup scripts"
"$INSTALL_DIR/pialert/dockerfiles/user-mapping.sh"
"$INSTALL_DIR/pialert/install/install_dependencies.sh" # if modifying this file transfer the chanegs into the root Dockerfile as well!
"$INSTALL_DIR/pialert/install/user-mapping.debian.sh"
"$INSTALL_DIR/pialert/install/install_dependencies.debian.sh" # if modifying this file transfer the changes into the root Dockerfile.debian as well!
echo "[INSTALL] Setup NGINX"
@@ -61,7 +61,7 @@ fi
# create symbolic link to the pialert install directory
ln -s $INSTALL_DIR/pialert/front $WEB_UI_DIR
# create symbolic link to NGINX configuaration coming with PiAlert
sudo ln -s "$INSTALL_DIR/pialert/install/pialert.conf" /etc/nginx/conf.d/pialert.conf
sudo ln -s "$INSTALL_DIR/pialert/install/pialert.debian.conf" /etc/nginx/conf.d/pialert.conf
# Use user-supplied port if set
if [ -n "${PORT}" ]; then
@@ -114,6 +114,17 @@ chmod -R a+rwx $INSTALL_DIR
echo "[INSTALL] Copy starter pialert.db and pialert.conf if they don't exist"
# DANGER ZONE: ALWAYS_FRESH_INSTALL
if [ "$ALWAYS_FRESH_INSTALL" = true ]; then
echo "[INSTALL] ❗ ALERT /db and /config folders are cleared because the ALWAYS_FRESH_INSTALL is set to: $ALWAYS_FRESH_INSTALL"
# Delete content of "$INSTALL_DIR/pialert/config/"
rm -rf "$INSTALL_DIR/pialert/config/"*
# Delete content of "$INSTALL_DIR/pialert/db/"
rm -rf "$INSTALL_DIR/pialert/db/"*
fi
# Copy starter pialert.db and pialert.conf if they don't exist
cp -n "$INSTALL_DIR/pialert/back/pialert.conf" "$INSTALL_DIR/pialert/config/pialert.conf"
cp -n "$INSTALL_DIR/pialert/back/pialert.db" "$FILEDB"

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash
echo "---------------------------------------------------------"
echo "[INSTALL] Run user-mapping.sh"
echo "[INSTALL] Run user-mapping.debian.sh"
echo "---------------------------------------------------------"
if [ -z "${USER}" ]; then

View File

@@ -105,7 +105,7 @@ def importConfigs (db):
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.REPORT_DASHBOARD_URL = ccd('REPORT_DASHBOARD_URL', 'http://pi.alert/' , c_d, 'PiAlert URL', 'text', '', 'General')
conf.UI_LANG = ccd('UI_LANG', 'English' , c_d, 'Language Interface', 'text.select', "['English', 'German', 'Spanish']", 'General')
conf.UI_LANG = ccd('UI_LANG', 'English' , c_d, 'Language Interface', 'text.select', "['English', 'French', 'German', 'Norwegian', 'Russian', 'Spanish' ]", 'General')
conf.UI_PRESENCE = ccd('UI_PRESENCE', ['online', 'offline', 'archived'] , c_d, 'Include in presence', 'text.multiselect', "['online', 'offline', 'archived']", 'General')
conf.UI_MY_DEVICES = ccd('UI_MY_DEVICES', ['online', 'offline', 'archived', 'new', 'down'] , c_d, 'Include in My Devices', 'text.multiselect', "['online', 'offline', 'archived', 'new', 'down']", 'General')
conf.UI_NOT_RANDOM_MAC = ccd('UI_NOT_RANDOM_MAC', [] , c_d, 'Exlude from Random Prefix', 'list', "", 'General')

View File

@@ -215,7 +215,7 @@ def execute_plugin(db, plugin, pluginsState = plugins_state() ):
mylog('none', [e.output])
mylog('none', ['[Plugins] ⚠ ERROR - enable LOG_LEVEL=debug and check logs'])
except subprocess.TimeoutExpired as timeErr:
mylog('none', ['[Plugins] TIMEOUT - the process forcefully terminated as timeout reached'])
mylog('none', [f'[Plugins] ⚠ ERROR - TIMEOUT - the plugin {plugin["unique_prefix"]} forcefully terminated as timeout reached. Increase TIMEOUT setting and scan interval.'])
# check the last run output