Compare commits
162 Commits
bb39b0dc6c
...
v25.1.8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad32e76a55 | ||
|
|
190ffd3237 | ||
|
|
c8280184dc | ||
|
|
076d8bbcc2 | ||
|
|
b21d57c524 | ||
|
|
064e0cb0ff | ||
|
|
ad9d61aa16 | ||
|
|
64ff9710d1 | ||
|
|
0cc87e3cfc | ||
|
|
c40af37ca1 | ||
|
|
07a7ace5fc | ||
|
|
4a82ea87f7 | ||
|
|
db47571424 | ||
|
|
58eaa33f39 | ||
|
|
6e4d34b93a | ||
|
|
b2c445e39d | ||
|
|
90ff2efbfb | ||
|
|
b49f1ab335 | ||
|
|
3da5af1b7c | ||
|
|
90dd8e3198 | ||
|
|
c31966bdd0 | ||
|
|
f2a0018982 | ||
|
|
48ac108ca0 | ||
|
|
19441a4431 | ||
|
|
5541d6c9d2 | ||
|
|
93886cec8a | ||
|
|
8401ecda5e | ||
|
|
09aba51a33 | ||
|
|
06946e4fea | ||
|
|
f96ca3e08b | ||
|
|
4aa18691f4 | ||
|
|
bd198587cd | ||
|
|
e8a4cb1d51 | ||
|
|
75e25867e1 | ||
|
|
7dadb735c2 | ||
|
|
b444f98e2d | ||
|
|
8ca2bff456 | ||
|
|
308285f808 | ||
|
|
1a03b2ccc3 | ||
|
|
f6a6c3684c | ||
|
|
f2bf379597 | ||
|
|
802002a5f0 | ||
|
|
e6d2a1c138 | ||
|
|
729c24029f | ||
|
|
3260a67bf4 | ||
|
|
5621f13c6e | ||
|
|
aae08594bb | ||
|
|
ba83884b9f | ||
|
|
2799d3598b | ||
|
|
1ee746a625 | ||
|
|
e52601e062 | ||
|
|
2dad079979 | ||
|
|
47bdb60c85 | ||
|
|
6b9df66b02 | ||
|
|
b188afab44 | ||
|
|
5fe6ac9816 | ||
|
|
d354acbcb2 | ||
|
|
846e15d518 | ||
|
|
067467da53 | ||
|
|
8ac4112ab9 | ||
|
|
ae32b0dd42 | ||
|
|
4fef6e156b | ||
|
|
161a74dee0 | ||
|
|
fa570b9bc9 | ||
|
|
9cfbc7d140 | ||
|
|
dda440eb53 | ||
|
|
efe161494e | ||
|
|
35f1624804 | ||
|
|
74ec75f105 | ||
|
|
0f474fb884 | ||
|
|
f54ffcbbc3 | ||
|
|
21fd9866fe | ||
|
|
3732416616 | ||
|
|
29396ad6bd | ||
|
|
37ae75ed9a | ||
|
|
7cdcf95300 | ||
|
|
e26f7d42e9 | ||
|
|
8f8264c6fa | ||
|
|
a4c34140bf | ||
|
|
466aa62a02 | ||
|
|
febc26b187 | ||
|
|
7248e73e03 | ||
|
|
96e62468fc | ||
|
|
c8c95b22a1 | ||
|
|
b63b00f30c | ||
|
|
6e07032c15 | ||
|
|
3663e617e0 | ||
|
|
5a9ff3f07f | ||
|
|
5af2b283c9 | ||
|
|
b88cfeda4c | ||
|
|
ec9466c562 | ||
|
|
e3039b5f7c | ||
|
|
46bb490384 | ||
|
|
a174eb58d7 | ||
|
|
43df53f1c1 | ||
|
|
4ec803f4c4 | ||
|
|
cfbbe83b7a | ||
|
|
7913d42699 | ||
|
|
40e4502f29 | ||
|
|
b80718d409 | ||
|
|
147d8833f7 | ||
|
|
8877e7a528 | ||
|
|
f38d72a690 | ||
|
|
be673315d4 | ||
|
|
07f3eabea3 | ||
|
|
d51373dcab | ||
|
|
773b49a1b4 | ||
|
|
f4d215c843 | ||
|
|
30d9d4b8fc | ||
|
|
d82d76a1c7 | ||
|
|
743f97e194 | ||
|
|
31e4743caa | ||
|
|
0a7356f9be | ||
|
|
498ca227fd | ||
|
|
55fc068dda | ||
|
|
0d030ef355 | ||
|
|
0e381c6592 | ||
|
|
4976324c50 | ||
|
|
48f86e91f7 | ||
|
|
3cc4caa34c | ||
|
|
191afdf857 | ||
|
|
721a275fd8 | ||
|
|
caf5a5347c | ||
|
|
688944bda1 | ||
|
|
c132374421 | ||
|
|
953534724c | ||
|
|
6115c12a6c | ||
|
|
07d3a3fede | ||
|
|
2e47af7b63 | ||
|
|
e7843e6e73 | ||
|
|
f247e4aabc | ||
|
|
4f02232ee3 | ||
|
|
c1c8352274 | ||
|
|
c3157f6ef4 | ||
|
|
0a200afebc | ||
|
|
c4c1f9e345 | ||
|
|
7b15efa913 | ||
|
|
a0db36cb2d | ||
|
|
796b4596ce | ||
|
|
2625f2f96e | ||
|
|
90f4abc037 | ||
|
|
8345bda25d | ||
|
|
7656ef99e8 | ||
|
|
9c2841167c | ||
|
|
03056f7823 | ||
|
|
2275bf3114 | ||
|
|
ac0c13dced | ||
|
|
74df660145 | ||
|
|
a7e35c4697 | ||
|
|
cd9c4a2176 | ||
|
|
f7160f0843 | ||
|
|
2ad80f5ba5 | ||
|
|
d7858c6042 | ||
|
|
79c4574e21 | ||
|
|
daff0ee7f2 | ||
|
|
a22df52725 | ||
|
|
89b4b9b98e | ||
|
|
80e8cb3f74 | ||
|
|
caef64ddf4 | ||
|
|
0d231caecd | ||
|
|
fbc43e6116 | ||
|
|
db7a122f39 |
@@ -6,14 +6,10 @@
|
||||
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
|
||||
|
||||
db/.gitignore
|
||||
2
.github/ISSUE_TEMPLATE/i-have-an-issue.yml
vendored
@@ -74,7 +74,7 @@ body:
|
||||
***Generally speaking, all bug reports should have logs provided.***
|
||||
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||
Additionally, any additional info? Screenshots? References? Anything that will give us more context about the issue you are encountering!
|
||||
You can use `tail -100 /app/front/log/app.log` in the container if you have trouble getting to the log files.
|
||||
You can use `tail -100 /app/log/app.log` in the container if you have trouble getting to the log files.
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
|
||||
11
.gitignore
vendored
@@ -7,8 +7,11 @@ db/*
|
||||
db/pialert.db
|
||||
db/app.db
|
||||
front/log/*
|
||||
/log/*
|
||||
front/api/*
|
||||
/api/*
|
||||
**/plugins/**/*.log
|
||||
**/plugins/cloud_services/*
|
||||
**/%40eaDir/
|
||||
**/@eaDir/
|
||||
|
||||
@@ -22,10 +25,10 @@ __pycache__/
|
||||
**/pialert.db_bak
|
||||
.*.swp
|
||||
|
||||
front/img/account/*
|
||||
**/account.php
|
||||
**/account.js
|
||||
front/css/account.css
|
||||
front/img/cloud_services/*
|
||||
**/cloud_services.php
|
||||
**/cloud_services.js
|
||||
front/css/cloud_services.css
|
||||
|
||||
docker-compose.yml.ffsb42
|
||||
.env.omada.ffsb42
|
||||
|
||||
@@ -14,7 +14,7 @@ ENV PATH="/opt/venv/bin:$PATH"
|
||||
COPY . ${INSTALL_DIR}/
|
||||
|
||||
|
||||
RUN pip install graphene flask netifaces tplink-omada-client pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros git+https://github.com/foreign-sub/aiofreepybox.git \
|
||||
RUN pip install openwrt-luci-rpc graphene flask netifaces tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros git+https://github.com/foreign-sub/aiofreepybox.git \
|
||||
&& 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 'speedtest-cli' \) -exec chmod 750 {} \;"
|
||||
@@ -69,9 +69,9 @@ COPY --from=builder --chown=nginx:www-data ${INSTALL_DIR}/ ${INSTALL_DIR}/
|
||||
COPY install/crontab /etc/crontabs/root
|
||||
|
||||
# Start all required services
|
||||
RUN ${INSTALL_DIR}/dockerfiles/pre-setup.sh
|
||||
RUN ${INSTALL_DIR}/dockerfiles/start.sh
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=2 \
|
||||
CMD curl -sf -o /dev/null ${LISTEN_ADDR}:${PORT}/api/app_state.json
|
||||
CMD curl -sf -o /dev/null ${LISTEN_ADDR}:${PORT}/php/server/query_json.php?file=app_state.json
|
||||
|
||||
ENTRYPOINT ["/init"]
|
||||
|
||||
@@ -43,7 +43,7 @@ RUN phpenmod -v 8.2 sqlite3
|
||||
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 tplink-omada-client pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros "
|
||||
RUN /bin/bash -c "source myenv/bin/activate && update-alternatives --install /usr/bin/python python /usr/bin/python3 10 && pip3 install openwrt-luci-rpc graphene flask netifaces tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros "
|
||||
|
||||
# Create a buildtimestamp.txt to later check if a new version was released
|
||||
RUN date +%s > ${INSTALL_DIR}/front/buildtimestamp.txt
|
||||
|
||||
29
README.md
@@ -1,4 +1,3 @@
|
||||
[](https://github.com/jokob-sk/NetAlertX)
|
||||
[](https://hub.docker.com/r/jokobsk/netalertx)
|
||||
[](https://hub.docker.com/r/jokobsk/netalertx)
|
||||
[](https://github.com/jokob-sk/NetAlertX/releases)
|
||||
@@ -9,23 +8,19 @@
|
||||
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/NetAlertX/tree/main/front/plugins#readme) with auto-generated UI and in-build notification system. Build out and easily maintain your network source of truth (NSoT).
|
||||
|
||||
|
||||
| 🐳 [Docker hub](https://registry.hub.docker.com/r/jokobsk/netalertx) | 📑 [Docker guide](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md) |🆕 [Release notes](https://github.com/jokob-sk/NetAlertX/releases) | 📚 [All Docs](https://github.com/jokob-sk/NetAlertX/tree/main/docs) |
|
||||
|----------------------|----------------------| ----------------------| ----------------------|
|
||||
|
||||
|
||||
| ![Main screen][main] | ![device_details 1][device_details] | ![Screen network][network] |
|
||||
|----------------------|----------------------| ----------------------|
|
||||
|
||||
![network_setup][network_setup]
|
||||
| [📑 Docker guide](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md) | [🚀 Releases](https://github.com/jokob-sk/NetAlertX/releases) | [📚 Docs](https://github.com/jokob-sk/NetAlertX/tree/main/docs) | [🔌 Plugins](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/README.md) | [🤖 Ask AI](https://gurubase.io/g/netalertx)
|
||||
|----------------------| ----------------------| ----------------------| ----------------------| ----------------------|
|
||||
|
||||
![showcase][showcase]
|
||||
|
||||
Head to [https://netalertx.com/](https://netalertx.com/) for more gifs and screenshots 📷.
|
||||
|
||||
<details>
|
||||
<summary>📷 Click for more screenshots</summary>
|
||||
|
||||
| ![presence][presence] | ![maintenance][maintenance] | ![settings][settings] |
|
||||
| ![Main screen][main] | ![device_details 1][device_details] | ![Screen network][network] |
|
||||
|----------------------|----------------------|----------------------|
|
||||
| ![presence][presence] | ![maintenance][maintenance] | ![settings][settings] |
|
||||
| ![sync_hub][sync_hub] | ![report1][report1] | ![device_nmap][device_nmap] |
|
||||
|
||||
</details>
|
||||
@@ -35,9 +30,9 @@ Head to [https://netalertx.com/](https://netalertx.com/) for more gifs and scree
|
||||
|
||||
<hr>
|
||||
|
||||
Most of us don't know what's going on on our home network, but we want our family and data to be safe. _Command-line tools_ are great, but the output can be _hard to understand_ and action if you are not a network specialist.
|
||||
Most of us don't know what's going on on our home network, but we want our family and data to be safe. _Command-line tools_ are great, but the output can be _hard to understand_ and action if you are not a network specialist.
|
||||
|
||||
Net<b>Alert</b><sup>x</sup> gives you peace of mind. _Visualize and immediately report 📬_ what is going on in your network - this is the first step to enhance your _network security 🔐_.
|
||||
Net<b>Alert</b><sup>x</sup> gives you peace of mind. _Visualize and immediately report 📬_ what is going on in your network - this is the first step to enhance your _network security 🔐_.
|
||||
|
||||
Net<b>Alert</b><sup>x</sup> combines several network and other scanning tools 🔍 with notifications 📧 into one user-friendly package 📦.
|
||||
|
||||
@@ -88,9 +83,7 @@ Get notified about a new release, what new functionality you can use and about b
|
||||
|
||||
[](https://github.com/sponsors/jokob-sk)
|
||||
|
||||
Thank you to all the wonderful people who are sponsoring this project.
|
||||
|
||||
> preventing my burnout😅 are:
|
||||
Thank you to all the wonderful people who are sponsoring this project (private sponsors are hidden).
|
||||
|
||||
<!-- SPONSORS-LIST DO NOT MODIFY BELOW -->
|
||||
| All Sponsors |
|
||||
@@ -98,8 +91,6 @@ Thank you to all the wonderful people who are sponsoring this project.
|
||||
|
||||
<!-- SPONSORS-LIST DO NOT MODIFY ABOVE -->
|
||||
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Click for more ways to donate</summary>
|
||||
|
||||
@@ -119,7 +110,7 @@ Thank you to all the wonderful people who are sponsoring this project.
|
||||
|
||||
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...
|
||||
> [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), [vladaurosh](https://github.com/vladaurosh) for Alpine re-base help, [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. Check out all the [amazing contributors](https://github.com/jokob-sk/NetAlertX/graphs/contributors).
|
||||
|
||||
|
||||
## Everything else
|
||||
@@ -148,7 +139,7 @@ Help out and suggest languages in the [online portal of Weblate](https://hosted.
|
||||
[maintenance]: ./docs/img/maintenance.png "Screen 4"
|
||||
[network]: ./docs/img/network.png "Screen 5"
|
||||
[settings]: ./docs/img/settings.png "Screen 6"
|
||||
[network_setup]: ./docs/img/network_setup.gif "Screen 6"
|
||||
[showcase]: ./docs/img/showcase.gif "Screen 6"
|
||||
[help_faq]: ./docs/img/help_faq.png "Screen 7"
|
||||
[sync_hub]: ./docs/img/sync_hub.png "Screen 8"
|
||||
[notification_center]: ./docs/img/notification_center.png "Screen 8"
|
||||
|
||||
0
front/api/.gitignore → api/.gitignore
vendored
@@ -17,9 +17,10 @@
|
||||
# Scan multiple interfaces (eth1 and eth0):
|
||||
# SCAN_SUBNETS = [ '192.168.1.0/24 --interface=eth1', '192.168.1.0/24 --interface=eth0' ]
|
||||
|
||||
DISCOVER_PLUGINS=True
|
||||
SCAN_SUBNETS=['192.168.1.0/24 --interface=eth0']
|
||||
TIMEZONE='Europe/Berlin'
|
||||
LOADED_PLUGINS = ['ARPSCAN','CSVBCKP','DBCLNP', 'INTRNT','MAINT','NEWDEV','NSLOOKUP','NTFPRCS', 'AVAHISCAN', 'SETPWD','SMTP', 'SYNC', 'VNDRPDT', 'WORKFLOWS']
|
||||
LOADED_PLUGINS = ['ARPSCAN','CSVBCKP','DBCLNP', 'INTRNT','MAINT','NEWDEV','NSLOOKUP','NTFPRCS', 'AVAHISCAN', 'SETPWD','SMTP', 'SYNC', 'VNDRPDT', 'WORKFLOWS', 'UI']
|
||||
|
||||
DAYS_TO_KEEP_EVENTS=90
|
||||
# Used for generating links in emails. Make sure not to add a trailing slash!
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
export INSTALL_DIR=/app
|
||||
|
||||
LOG_FILE="${INSTALL_DIR}/front/log/execution_queue.log"
|
||||
LOG_FILE="${INSTALL_DIR}/log/execution_queue.log"
|
||||
|
||||
# Check if there are any entries with cron_restart_backend
|
||||
if grep -q "cron_restart_backend" "$LOG_FILE"; then
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
version: "3"
|
||||
services:
|
||||
netalertx:
|
||||
privileged: true
|
||||
@@ -16,7 +15,12 @@ services:
|
||||
# - ${APP_DATA_LOCATION}/netalertx_dev/db:/app/db
|
||||
- ${APP_DATA_LOCATION}/netalertx/db:/app/db
|
||||
# (optional) useful for debugging if you have issues setting up the container
|
||||
# - ${LOGS_LOCATION}:/app/front/log
|
||||
- ${APP_DATA_LOCATION}/netalertx/log:/app/log
|
||||
# (API: OPTION 1) use for performance
|
||||
- type: tmpfs
|
||||
target: /app/api
|
||||
# (API: OPTION 2) use when debugging issues
|
||||
# - ${DEV_LOCATION}/api:/app/api
|
||||
# ---------------------------------------------------------------------------
|
||||
# DELETE START anyone trying to use this file: comment out / delete BELOW lines, they are only for development purposes
|
||||
- ${APP_DATA_LOCATION}/netalertx/dhcp_samples/dhcp1.leases:/mnt/dhcp1.leases
|
||||
@@ -35,16 +39,14 @@ services:
|
||||
- ${DEV_LOCATION}/front/lib:/app/front/lib
|
||||
- ${DEV_LOCATION}/front/js:/app/front/js
|
||||
- ${DEV_LOCATION}/front/report_templates:/front/report_templates
|
||||
- ${DEV_LOCATION}/install/start.debian.sh:/app/install/start.debian.sh
|
||||
- ${DEV_LOCATION}/install/user-mapping.debian.sh:/app/install/user-mapping.debian.sh
|
||||
- ${DEV_LOCATION}/install/install.debian.sh:/app/install/install.debian.sh
|
||||
- ${DEV_LOCATION}/install/install_dependencies.debian.sh:/app/install/install_dependencies.debian.sh
|
||||
- ${DEV_LOCATION}/front/api:/app/front/api
|
||||
- ${DEV_LOCATION}/front/php:/app/front/php
|
||||
- ${DEV_LOCATION}/front/deviceDetails.php:/app/front/deviceDetails.php
|
||||
- ${DEV_LOCATION}/front/deviceDetailsEdit.php:/app/front/deviceDetailsEdit.php
|
||||
- ${DEV_LOCATION}/front/userNotifications.php:/app/front/userNotifications.php
|
||||
- ${DEV_LOCATION}/front/deviceDetailsTools.php:/app/front/deviceDetailsTools.php
|
||||
- ${DEV_LOCATION}/front/deviceDetailsPresence.php:/app/front/deviceDetailsPresence.php
|
||||
- ${DEV_LOCATION}/front/deviceDetailsSessions.php:/app/front/deviceDetailsSessions.php
|
||||
- ${DEV_LOCATION}/front/deviceDetailsEvents.php:/app/front/deviceDetailsEvents.php
|
||||
- ${DEV_LOCATION}/front/devices.php:/app/front/devices.php
|
||||
- ${DEV_LOCATION}/front/events.php:/app/front/events.php
|
||||
- ${DEV_LOCATION}/front/plugins.php:/app/front/plugins.php
|
||||
@@ -56,7 +58,7 @@ services:
|
||||
- ${DEV_LOCATION}/front/presence.php:/app/front/presence.php
|
||||
- ${DEV_LOCATION}/front/settings.php:/app/front/settings.php
|
||||
- ${DEV_LOCATION}/front/systeminfo.php:/app/front/systeminfo.php
|
||||
- ${DEV_LOCATION}/front/account.php:/app/front/account.php
|
||||
- ${DEV_LOCATION}/front/cloud_services.php:/app/front/cloud_services.php
|
||||
- ${DEV_LOCATION}/front/report.php:/app/front/report.php
|
||||
- ${DEV_LOCATION}/front/workflows.php:/app/front/workflows.php
|
||||
- ${DEV_LOCATION}/front/appEventsCore.php:/app/front/appEventsCore.php
|
||||
@@ -65,8 +67,8 @@ services:
|
||||
- ${DEV_LOCATION}/front/plugins:/app/front/plugins
|
||||
# DELETE END anyone trying to use this file: comment out / delete ABOVE lines, they are only for development purposes
|
||||
# ---------------------------------------------------------------------------
|
||||
environment:
|
||||
# - APP_CONF_OVERRIDE={"SCAN_SUBNETS":"['192.168.1.0/24 --interface=eth1']","UI_theme":"Dark"}
|
||||
environment:
|
||||
# - APP_CONF_OVERRIDE={"SCAN_SUBNETS":"['192.168.1.0/24 --interface=eth1']","GRAPHQL_PORT":"20223","UI_theme":"Light"}
|
||||
- TZ=${TZ}
|
||||
- PORT=${PORT}
|
||||
# ❗ DANGER ZONE BELOW - Setting ALWAYS_FRESH_INSTALL=true will delete the content of the /db & /config folders
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
[](https://github.com/jokob-sk/NetAlertX)
|
||||
[](https://hub.docker.com/r/jokobsk/netalertx)
|
||||
[](https://hub.docker.com/r/jokobsk/netalertx)
|
||||
[](https://github.com/jokob-sk/NetAlertX/releases)
|
||||
@@ -7,8 +6,8 @@
|
||||
|
||||
# NetAlertX 🖧🔍 Network scanner & notification framework
|
||||
|
||||
| 🐳 [Docker hub](https://registry.hub.docker.com/r/jokobsk/netalertx) | 📑 [Docker guide](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md) |🆕 [Release notes](https://github.com/jokob-sk/NetAlertX/releases) | 📚 [All Docs](https://github.com/jokob-sk/NetAlertX/tree/main/docs) |
|
||||
|----------------------|----------------------| ----------------------| ----------------------|
|
||||
| [📑 Docker guide](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md) | [🚀 Releases](https://github.com/jokob-sk/NetAlertX/releases) | [📚 Docs](https://github.com/jokob-sk/NetAlertX/tree/main/docs) | [🔌 Plugins](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/README.md) | [🤖 Ask AI](https://gurubase.io/g/netalertx)
|
||||
|----------------------| ----------------------| ----------------------| ----------------------| ----------------------|
|
||||
|
||||
<a href="https://raw.githubusercontent.com/jokob-sk/NetAlertX/main/docs/img/GENERAL/github_social_image.jpg" target="_blank">
|
||||
<img src="https://raw.githubusercontent.com/jokob-sk/NetAlertX/main/docs/img/GENERAL/github_social_image.jpg" width="1000px" />
|
||||
@@ -22,17 +21,19 @@ Head to [https://netalertx.com/](https://netalertx.com/) for more gifs and scree
|
||||
## 📕 Basic Usage
|
||||
|
||||
> [!WARNING]
|
||||
> You will have to run the container on the `host` network.
|
||||
> You will have to run the container on the `host` network and specify `SCAN_SUBNETS` unless you use other [plugin scanners](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/README.md). The initial scan can take a few minutes, so please wait 5-10 minutes for the initial discovery to finish.
|
||||
|
||||
```yaml
|
||||
docker run -d --rm --network=host \
|
||||
-v local/path/config:/app/config \
|
||||
-v local/path/db:/app/db \
|
||||
-v local_path/config:/app/config \
|
||||
-v local_path/db:/app/db \
|
||||
--mount type=tmpfs,target=/app/api \
|
||||
-e TZ=Europe/Berlin \
|
||||
-e PORT=20211 \
|
||||
jokobsk/netalertx:latest
|
||||
```
|
||||
- The initial scan can take up to 15min (with 50 devices and MQTT). Subsequent ones 3 and 5 minutes so wait that long for all of the scans to run.
|
||||
|
||||
See alternative [docked-compose examples](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DOCKER_COMPOSE.md).
|
||||
|
||||
### Docker environment variables
|
||||
|
||||
@@ -41,7 +42,7 @@ docker run -d --rm --network=host \
|
||||
| `PORT` |Port of the web interface | `20211` |
|
||||
| `LISTEN_ADDR` |Set the specific IP Address for the listener address for the nginx webserver (web interface). This could be useful when using multiple subnets to hide the web interface from all untrusted networks. | `0.0.0.0` |
|
||||
|`TZ` |Time zone to display stats correctly. Find your time zone [here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) | `Europe/Berlin` |
|
||||
|`APP_CONF_OVERRIDE` | JSON override for settings, e.g. `{"SCAN_SUBNETS":"['192.168.1.0/24 --interface=eth1']","UI_theme":"Dark"}` (Experimental 🧪) | `N/A` |
|
||||
|`APP_CONF_OVERRIDE` | JSON override for settings, e.g. `{"SCAN_SUBNETS":"['192.168.1.0/24 --interface=eth1']","GRAPHQL_PORT":"20212"}` | `N/A` |
|
||||
|`ALWAYS_FRESH_INSTALL` | If `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 `netalertx`/`netalertx-dev` image. | `N/A` |
|
||||
|
||||
> You can override the default GraphQL port setting `GRAPHQL_PORT` (set to `20212`) by using the `APP_CONF_OVERRIDE` env variable.
|
||||
@@ -53,54 +54,34 @@ docker run -d --rm --network=host \
|
||||
|
||||
| Required | Path | Description |
|
||||
| :------------- | :------------- | :-------------|
|
||||
| ✅ | `:/app/config` | Folder which will contain the `app.conf` & `devices.csv` ([read about devices.csv](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICES_BULK_EDITING.md)) files (see below for details). |
|
||||
| ✅ | `:/app/db` | Folder which will contain the `app.db` file |
|
||||
| | `:/app/front/log` | Logs folder useful for debugging if you have issues setting up the container |
|
||||
| | `:/etc/pihole/pihole-FTL.db` | PiHole's `pihole-FTL.db` database file. Required if you want to use PiHole DB mapping. |
|
||||
| | `:/etc/pihole/dhcp.leases` | PiHole's `dhcp.leases` file. Required if you want to use PiHole `dhcp.leases` file. This has to be matched with a corresponding `DHCPLSS_paths_to_check` setting entry (the path in the container must contain `pihole`)|
|
||||
| | `:/app/front/api` | A simple [API endpoint](https://github.com/jokob-sk/NetAlertX/blob/main/docs/API.md) containing static (but regularly updated) json and other files. |
|
||||
| ✅ | `:/app/config` | Folder which will contain the `app.conf` & `devices.csv` ([read about devices.csv](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICES_BULK_EDITING.md)) files |
|
||||
| ✅ | `:/app/db` | Folder which will contain the `app.db` database file |
|
||||
| | `:/app/log` | Logs folder useful for debugging if you have issues setting up the container |
|
||||
| | `:/app/api` | A simple [API endpoint](https://github.com/jokob-sk/NetAlertX/blob/main/docs/API.md) containing static (but regularly updated) json and other files. |
|
||||
| | `:/app/front/plugins/<plugin>/ignore_plugin` | Map a file `ignore_plugin` to ignore a plugin. Plugins can be soft-disabled via settings. More in the [Plugin docs](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/README.md). |
|
||||
| | `:/etc/resolv.conf` | Use a custom `resolv.conf` file for [better name resolution](https://github.com/jokob-sk/NetAlertX/blob/main/docs/REVERSE_DNS.md). |
|
||||
|
||||
> Use separate `db` and `config` directories, don't nest them.
|
||||
> Use separate `db` and `config` directories, do not nest them.
|
||||
|
||||
### (If UI is not available) Modify the config (`app.conf`)
|
||||
### Initial setup
|
||||
|
||||
- The preferred way is to manage the configuration via the Settings section in the UI.
|
||||
- You can modify [app.conf](https://github.com/jokob-sk/NetAlertX/tree/main/config) directly, if needed.
|
||||
- If unavailable, the app generates a default `app.conf` and `app.db` file on the first run.
|
||||
- The preferred way is to manage the configuration via the Settings section in the UI, if UI is inaccessible you can modify [app.conf](https://github.com/jokob-sk/NetAlertX/tree/main/back) in the `/app/config/` folder directly
|
||||
|
||||
### ⚙ Important settings
|
||||
### Setting up scanners
|
||||
|
||||
These are the most important settings to get at least some output in your Devices screen. Usually, only one approach is used, but you can combine these approaches.
|
||||
|
||||
| Scan method | Setting | Description |
|
||||
| :------------- | :------------- | :-------------|
|
||||
| arp-scan, nmap-scan | `SCAN_SUBNETS` | See the documentation on how [to setup SUBNETS, VLANs & limitations](https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md) |
|
||||
| PiHole | `PIHOLE_RUN` | There are 2 approaches how to get PiHole devices imported. Via the PiHole import (`PIHOLE`) plugin or DHCP leases (`DHCPLSS`) plugin. The `PIHOLE` plugin requires you to map the PiHole database, as mentioned above. |
|
||||
| dhcp.leases | `DHCPLSS_RUN` | You need to map `:/etc/myfiles/dhcp.leases` in the `docker-compose.yml` file if you enable this setting. This path has to be matched with a corresponding `DHCPLSS_paths_to_check` setting entry (check the [DHCPLSS plugin readme](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/dhcp_leases#overview) for details). |
|
||||
You have to specify which network(s) should be scanned. This is done by entering subnets that are accessible from the host. If you use the default `ARPSCAN` plugin, you have to specify at least one valid subnet and interface in the `SCAN_SUBNETS` setting. See the documentation on [How to set up multiple SUBNETS, VLANs and what are limitations](https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md) for troubleshooting and more advanced scenarios.
|
||||
|
||||
If you are running PiHole you can synchronize devices directly. Check the [PiHole configuration guide](https://github.com/jokob-sk/NetAlertX/blob/main/docs/PIHOLE_GUIDE.md) for details.
|
||||
|
||||
> [!NOTE]
|
||||
> It's recommended to use the same schedule interval for all plugins responsible for discovering new devices.
|
||||
|
||||
> You can bulk-import devices via the [CSV import method](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICES_BULK_EDITING.md).
|
||||
|
||||
#### 🧭 Community guides
|
||||
|
||||
Use the official installation guides at first and use community content as supplementary material. Open an issue if you'd like to add your link to the list 🙏
|
||||
You can read or watch several [community configuration guides](https://github.com/jokob-sk/NetAlertX/blob/main/docs/COMMUNITY_GUIDES.md) in Chinese, Korean, German, or French.
|
||||
|
||||
- ▶ [Home Lab Network Monitoring - Scotti-BYTE Enterprise Consulting Services](https://www.youtube.com/watch?v=0DryhzrQSJA) (July 2024)
|
||||
- 📄 [How to Install NetAlertX on Your Synology NAS - Marius hosting](https://mariushosting.com/how-to-install-pi-alert-on-your-synology-nas/) (Updated frequently)
|
||||
- 📄 [Using the PiAlert Network Security Scanner on a Raspberry Pi - PiMyLifeUp](https://pimylifeup.com/raspberry-pi-pialert/)
|
||||
- ▶ [How to Setup Pi.Alert on Your Synology NAS - Digital Aloha](https://www.youtube.com/watch?v=M4YhpuRFaUg)
|
||||
- 📄 [防蹭网神器,网络安全助手 | 极空间部署网络扫描和通知系统『NetAlertX』](https://blog.csdn.net/qq_63499861/article/details/141105273)
|
||||
- 📄 [시놀/헤놀에서 네트워크 스캐너 Pi.Alert Docker로 설치 및 사용하기](https://blog.dalso.org/article/%EC%8B%9C%EB%86%80-%ED%97%A4%EB%86%80%EC%97%90%EC%84%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%8A%A4%EC%BA%90%EB%84%88-pi-alert-docker%EB%A1%9C-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EC%82%AC%EC%9A%A9) (July 2023)
|
||||
- 📄 [网络入侵探测器Pi.Alert (Chinese)](https://codeantenna.com/a/VgUvIAjZ7J) (May 2023)
|
||||
- ▶ [Pi.Alert auf Synology & Docker by - Jürgen Barth](https://www.youtube.com/watch?v=-ouvA2UNu-A) (March 2023)
|
||||
- ▶ [Top Docker Container for Home Server Security - VirtualizationHowto](https://www.youtube.com/watch?v=tY-w-enLF6Q) (March 2023)
|
||||
- ▶ [Pi.Alert or WatchYourLAN can alert you to unknown devices appearing on your WiFi or LAN network - Danie van der Merwe](https://www.youtube.com/watch?v=v6an9QG2xF0) (November 2022)
|
||||
|
||||
> Ordered by last update time.
|
||||
> Please note these might be outdated. Rely on official documentation first.
|
||||
|
||||
### **Common issues**
|
||||
|
||||
@@ -108,129 +89,6 @@ Use the official installation guides at first and use community content as suppl
|
||||
|
||||
⚠ Check also common issues and [debugging tips](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEBUG_TIPS.md).
|
||||
|
||||
> [!NOTE]
|
||||
> You can bulk-update devices via the [CSV import method](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICES_BULK_EDITING.md).
|
||||
|
||||
## 📄 docker-compose.yml Examples
|
||||
|
||||
### Example 1
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
services:
|
||||
netalertx:
|
||||
container_name: netalertx
|
||||
# use the below line if you want to test the latest dev image
|
||||
# image: "jokobsk/netalertx-dev:latest"
|
||||
image: "jokobsk/netalertx:latest"
|
||||
network_mode: "host"
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- local/path/config:/app/config
|
||||
- local/path/db:/app/db
|
||||
# (optional) useful for debugging if you have issues setting up the container
|
||||
- local/path/logs:/app/front/log
|
||||
environment:
|
||||
- TZ=Europe/Berlin
|
||||
- PORT=20211
|
||||
```
|
||||
|
||||
To run the container execute: `sudo docker-compose up -d`
|
||||
|
||||
### Example 2
|
||||
|
||||
Example by [SeimuS](https://github.com/SeimusS).
|
||||
|
||||
```yaml
|
||||
netalertx:
|
||||
container_name: NetAlertX
|
||||
hostname: NetAlertX
|
||||
privileged: true
|
||||
# use the below line if you want to test the latest dev image
|
||||
# image: "jokobsk/netalertx-dev:latest"
|
||||
image: jokobsk/netalertx:latest
|
||||
environment:
|
||||
- TZ=Europe/Bratislava
|
||||
restart: always
|
||||
volumes:
|
||||
- ./netalertx/db:/app/db
|
||||
- ./netalertx/config:/app/config
|
||||
network_mode: host
|
||||
```
|
||||
|
||||
To run the container execute: `sudo docker-compose up -d`
|
||||
|
||||
### Example 3
|
||||
|
||||
`docker-compose.yml`
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
services:
|
||||
netalertx:
|
||||
container_name: netalertx
|
||||
# use the below line if you want to test the latest dev image
|
||||
# image: "jokobsk/netalertx-dev:latest"
|
||||
image: "jokobsk/netalertx:latest"
|
||||
network_mode: "host"
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ${APP_DATA_LOCATION}/netalertx/config:/app/config
|
||||
- ${APP_DATA_LOCATION}/netalertx/db/:/app/db/
|
||||
# (optional) useful for debugging if you have issues setting up the container
|
||||
- ${LOGS_LOCATION}:/app/front/log
|
||||
environment:
|
||||
- TZ=${TZ}
|
||||
- PORT=${PORT}
|
||||
```
|
||||
|
||||
`.env` file
|
||||
|
||||
```yaml
|
||||
#GLOBAL PATH VARIABLES
|
||||
|
||||
APP_DATA_LOCATION=/path/to/docker_appdata
|
||||
APP_CONFIG_LOCATION=/path/to/docker_config
|
||||
LOGS_LOCATION=/path/to/docker_logs
|
||||
|
||||
#ENVIRONMENT VARIABLES
|
||||
|
||||
TZ=Europe/Paris
|
||||
PORT=20211
|
||||
|
||||
#DEVELOPMENT VARIABLES
|
||||
|
||||
DEV_LOCATION=/path/to/local/source/code
|
||||
```
|
||||
|
||||
To run the container execute: `sudo docker-compose --env-file /path/to/.env up`
|
||||
|
||||
### Example 4
|
||||
|
||||
Courtesy of [pbek](https://github.com/pbek). The volume `netalertx_db` is used by the db directory. The two config files are mounted directly from a local folder to their places in the config folder. You can backup the `docker-compose.yaml` folder and the docker volumes folder.
|
||||
|
||||
```yaml
|
||||
netalertx:
|
||||
# use the below line if you want to test the latest dev image
|
||||
# image: "jokobsk/netalertx-dev:latest"
|
||||
image: jokobsk/netalertx
|
||||
ports:
|
||||
- "80:20211/tcp"
|
||||
environment:
|
||||
- TZ=Europe/Vienna
|
||||
networks:
|
||||
local:
|
||||
ipv4_address: 192.168.1.2
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- netalertx_db:/app/db
|
||||
- ./netalertx/:/app/config/
|
||||
```
|
||||
|
||||
## 🏅 Recognitions
|
||||
|
||||
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
|
||||
|
||||
| [](https://github.com/sponsors/jokob-sk) | [](https://www.buymeacoffee.com/jokobsk) | [](https://www.patreon.com/user?u=84385063) |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
|
||||
echo "---------------------------------------------------------"
|
||||
echo "[INSTALL] Run setup.sh"
|
||||
echo "[INSTALL] Run init.sh"
|
||||
echo "---------------------------------------------------------"
|
||||
|
||||
export INSTALL_DIR=/app # Specify the installation directory here
|
||||
@@ -106,15 +106,17 @@ fi
|
||||
|
||||
# Create an empty log files
|
||||
# Create the execution_queue.log and app_front.log files if they don't exist
|
||||
touch "${INSTALL_DIR}"/front/log/{app.log,execution_queue.log,app_front.log,app.php_errors.log,stderr.log,stdout.log,db_is_locked.log}
|
||||
touch "${INSTALL_DIR}"/front/api/user_notifications.json
|
||||
touch "${INSTALL_DIR}"/log/{app.log,execution_queue.log,app_front.log,app.php_errors.log,stderr.log,stdout.log,db_is_locked.log}
|
||||
touch "${INSTALL_DIR}"/api/user_notifications.json
|
||||
# Create plugins sub-directory if it doesn't exist in case a custom log folder is used
|
||||
mkdir -p "${INSTALL_DIR}"/log/plugins
|
||||
|
||||
echo "[INSTALL] Fixing permissions after copied starter config & DB"
|
||||
chown -R nginx:www-data "${INSTALL_DIR}"/{config,front/log,db,front/api}
|
||||
chown -R nginx:www-data "${INSTALL_DIR}"/front/api/user_notifications.json
|
||||
chown -R nginx:www-data "${INSTALL_DIR}"/{config,log,db,api}
|
||||
chown -R nginx:www-data "${INSTALL_DIR}"/api/user_notifications.json
|
||||
|
||||
chmod 750 "${INSTALL_DIR}"/{config,front/log,db}
|
||||
find "${INSTALL_DIR}"/{config,front/log,db} -type f -exec chmod 640 {} \;
|
||||
chmod 750 "${INSTALL_DIR}"/{config,log,db}
|
||||
find "${INSTALL_DIR}"/{config,log,db} -type f -exec chmod 640 {} \;
|
||||
|
||||
# Check if buildtimestamp.txt doesn't exist
|
||||
if [ ! -f "${INSTALL_DIR}/front/buildtimestamp.txt" ]; then
|
||||
@@ -19,7 +19,7 @@ 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/$APP_NAME/type
|
||||
echo -e "${INSTALL_DIR}/dockerfiles/setup.sh" > /etc/s6-overlay/s6-rc.d/SetupOneshot/up
|
||||
echo -e "${INSTALL_DIR}/dockerfiles/init.sh" > /etc/s6-overlay/s6-rc.d/SetupOneshot/up
|
||||
echo -e "#!/bin/execlineb -P\n/usr/sbin/php-fpm83 -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
|
||||
@@ -38,6 +38,5 @@ touch /etc/s6-overlay/s6-rc.d/user/contents.d/{SetupOneshot,php-fpm,nginx,$APP_N
|
||||
touch /etc/s6-overlay/s6-rc.d/nginx/dependencies.d/php-fpm
|
||||
touch /etc/s6-overlay/s6-rc.d/$APP_NAME/dependencies.d/nginx
|
||||
|
||||
|
||||
|
||||
rm -f $0
|
||||
# this removes the current file
|
||||
# rm -f $0
|
||||
171
docs/API.md
@@ -1,7 +1,134 @@
|
||||
## API endpoints
|
||||
# API endpoints
|
||||
|
||||
NetAlertX comes with a simple API. These API endpoints are static files, that are periodically updated based on your settings.
|
||||
NetAlertX comes with a couple of API endpoints. All requests need to be authorized (executed in a logged in browser session) or you have to pass the value of the `API_TOKEN` settings as authorization bearer, for example:
|
||||
|
||||
```graphql
|
||||
curl 'http://host:GRAPHQL_PORT/graphql' \
|
||||
-X POST \
|
||||
-H 'Authorization: Bearer API_TOKEN' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"query": "query GetDevices($options: PageQueryOptionsInput) { devices(options: $options) { devices { rowid devMac devName devOwner devType devVendor devLastConnection devStatus } count } }",
|
||||
"variables": {
|
||||
"options": {
|
||||
"page": 1,
|
||||
"limit": 10,
|
||||
"sort": [{ "field": "devName", "order": "asc" }],
|
||||
"search": "",
|
||||
"status": "connected"
|
||||
}
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
## API Endpoint: GraphQL
|
||||
|
||||
Endpoint URL: `php/server/query_graphql.php`
|
||||
Host: `same as front end (web ui)`
|
||||
Port: `20212` or as defined by the `GRAPHQL_PORT` setting
|
||||
|
||||
### Example Query to Fetch Devices
|
||||
|
||||
First, let's define the GraphQL query to fetch devices with pagination and sorting options.
|
||||
|
||||
```graphql
|
||||
query GetDevices($options: PageQueryOptionsInput) {
|
||||
devices(options: $options) {
|
||||
devices {
|
||||
rowid
|
||||
devMac
|
||||
devName
|
||||
devOwner
|
||||
devType
|
||||
devVendor
|
||||
devLastConnection
|
||||
devStatus
|
||||
}
|
||||
count
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `curl` Command
|
||||
|
||||
You can use the following `curl` command to execute the query.
|
||||
|
||||
```sh
|
||||
curl 'http://host:GRAPHQL_PORT/graphql' -X POST -H 'Authorization: Bearer API_TOKEN' -H 'Content-Type: application/json' --data '{
|
||||
"query": "query GetDevices($options: PageQueryOptionsInput) { devices(options: $options) { devices { rowid devMac devName devOwner devType devVendor devLastConnection devStatus } count } }",
|
||||
"variables": {
|
||||
"options": {
|
||||
"page": 1,
|
||||
"limit": 10,
|
||||
"sort": [{ "field": "devName", "order": "asc" }],
|
||||
"search": "",
|
||||
"status": "connected"
|
||||
}
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
### Explanation:
|
||||
|
||||
1. **GraphQL Query**:
|
||||
- The `query` parameter contains the GraphQL query as a string.
|
||||
- The `variables` parameter contains the input variables for the query.
|
||||
|
||||
2. **Query Variables**:
|
||||
- `page`: Specifies the page number of results to fetch.
|
||||
- `limit`: Specifies the number of results per page.
|
||||
- `sort`: Specifies the sorting options, with `field` being the field to sort by and `order` being the sort order (`asc` for ascending or `desc` for descending).
|
||||
- `search`: A search term to filter the devices.
|
||||
- `status`: The status filter to apply (valid values are `my_devices` (determined by the `UI_MY_DEVICES` setting), `connected`, `favorites`, `new`, `down`, `archived`, `offline`).
|
||||
|
||||
3. **`curl` Command**:
|
||||
- The `-X POST` option specifies that we are making a POST request.
|
||||
- The `-H "Content-Type: application/json"` option sets the content type of the request to JSON.
|
||||
- The `-d` option provides the request payload, which includes the GraphQL query and variables.
|
||||
|
||||
### Sample Response
|
||||
|
||||
The response will be in JSON format, similar to the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"devices": {
|
||||
"devices": [
|
||||
{
|
||||
"rowid": 1,
|
||||
"devMac": "00:11:22:33:44:55",
|
||||
"devName": "Device 1",
|
||||
"devOwner": "Owner 1",
|
||||
"devType": "Type 1",
|
||||
"devVendor": "Vendor 1",
|
||||
"devLastConnection": "2025-01-01T00:00:00Z",
|
||||
"devStatus": "connected"
|
||||
},
|
||||
{
|
||||
"rowid": 2,
|
||||
"devMac": "66:77:88:99:AA:BB",
|
||||
"devName": "Device 2",
|
||||
"devOwner": "Owner 2",
|
||||
"devType": "Type 2",
|
||||
"devVendor": "Vendor 2",
|
||||
"devLastConnection": "2025-01-02T00:00:00Z",
|
||||
"devStatus": "connected"
|
||||
}
|
||||
],
|
||||
"count": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Endpoint: JSON files
|
||||
|
||||
These API endpoint are static files, that are periodically updated.
|
||||
|
||||
Endpoint URL: `php/server/query_json.php?file=<file name>`
|
||||
Host: `same as front end (web ui)`
|
||||
Port: `20211` or as defined by the $PORT docker environment variable (same as the port for the web ui)
|
||||
|
||||
### When are the endpoints updated
|
||||
|
||||
@@ -9,7 +136,7 @@ The endpoints are updated when objects in the API endpoints are changed.
|
||||
|
||||
### Location of the endpoints
|
||||
|
||||
In the container, these files are located under the `/app/front/api/` folder and thus on the `<netalertx_url>/api/<File name>` url.
|
||||
In the container, these files are located under the `/app/api/` folder. You can access them via the `/php/server/query_json.php?file=user_notifications.json` endpoint.
|
||||
|
||||
### Available endpoints
|
||||
|
||||
@@ -95,3 +222,41 @@ Example JSON of the `table_devices.json` endpoint with two Devices (database row
|
||||
|
||||
```
|
||||
|
||||
## API Endpoint: /log files
|
||||
|
||||
To retrieve files from the `/app/log` folder.
|
||||
|
||||
Endpoint URL: `php/server/query_logs.php?file=<file name>`
|
||||
Host: `same as front end (web ui)`
|
||||
Port: `20211` or as defined by the $PORT docker environment variable (same as the port for the web ui)
|
||||
|
||||
| File | Description |
|
||||
|--------------------------|--------------------------------------------------|
|
||||
| `IP_changes.log` | Logs of IP address changes |
|
||||
| `app.log` | Main application log |
|
||||
| `app.php_errors`.log | PHP error log |
|
||||
| `app_front.log` | Frontend application log |
|
||||
| `app_nmap.log` | Logs of Nmap scan results |
|
||||
| `db_is_locked.log` | Logs when the database is locked |
|
||||
| `execution_queue.log` | Logs of execution queue activities |
|
||||
| `plugins/` | Directory for plugin-related files (not accessible) |
|
||||
| `report_output.html` | HTML report output |
|
||||
| `report_output.json` | JSON format report output |
|
||||
| `report_output.txt` | Text format report output |
|
||||
| `stderr.log` | Logs of standard error output |
|
||||
| `stdout.log` | Logs of standard output |
|
||||
|
||||
|
||||
## API Endpoint: /config files
|
||||
|
||||
To retrieve files from the `/app/config` folder.
|
||||
|
||||
Endpoint URL: `php/server/query_config.php?file=<file name>`
|
||||
Host: `same as front end (web ui)`
|
||||
Port: `20211` or as defined by the $PORT docker environment variable (same as the port for the web ui)
|
||||
|
||||
| File | Description |
|
||||
|--------------------------|--------------------------------------------------|
|
||||
| `devices.csv` | Devices csv file |
|
||||
| `app.conf` | Application config file |
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# 💾 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.
|
||||
> To backup 99% of your configuration backup at least the `/app/config` folder. Please read the whole page (or at least "Scenario 2: Corrupted database") for details.
|
||||
> Please also note that database definitions might change over versions. The safest way is to restore your older backups into the **same version** of the app and then gradually upgarde between releases to the latest version.
|
||||
|
||||
There are 3 artifacts that can be used to backup the application:
|
||||
|
||||
@@ -22,16 +23,17 @@ The core application configuration is in the `app.conf` file (See [Settings Syst
|
||||
- Notification settings
|
||||
- Scanner settings
|
||||
- Scheduled maintenance settings
|
||||
- UI configuration (80%)
|
||||
- UI configuration
|
||||
|
||||
### 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/NetAlertX/tree/main/front/plugins/csv_backup). This file contains data, such as:
|
||||
The core device data is backed up to the `devices_<timestamp>.csv` or `devices.csv` file via the [CSV Backup `CSVBCKP` Plugin](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/csv_backup). This file contains data, such as:
|
||||
|
||||
- Device names
|
||||
- Device Icons
|
||||
- Device Network configuration
|
||||
- Device icons
|
||||
- Device network configuration
|
||||
- Device categorization
|
||||
- Device custom properties data
|
||||
|
||||
### Historical data
|
||||
|
||||
@@ -40,13 +42,13 @@ Historical data is stored in the `app.db` database (See [Database overview](http
|
||||
- Plugin objects
|
||||
- Plugin historical entries
|
||||
- History of Events, Notifications, Workflow Events
|
||||
- Presence History
|
||||
- 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 `app.conf` file.
|
||||
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` or `devices.csv` file, followed by the `app.conf` file.
|
||||
|
||||
### Scenario 1: Full backup
|
||||
|
||||
@@ -54,8 +56,8 @@ End-result: Full restore
|
||||
|
||||
#### Source artifacts:
|
||||
|
||||
- `/db/app.db` (uncorrupted)
|
||||
- `/config/app.conf`
|
||||
- `/app/db/app.db` (uncorrupted)
|
||||
- `/app/config/app.conf`
|
||||
|
||||
#### Recovery:
|
||||
|
||||
@@ -68,15 +70,15 @@ End-result: Partial restore (historical data & configurations from the Maintenan
|
||||
|
||||
#### Source artifacts:
|
||||
|
||||
- `/config/app.conf`
|
||||
- `/config/devices_<timestamp>.csv` or `/config/devices.csv`
|
||||
- `/app/config/app.conf`
|
||||
- `/app/config/devices_<timestamp>.csv` or `/app/config/devices.csv`
|
||||
|
||||
#### Recovery:
|
||||
|
||||
Even with a corrupted database you can recover what I would argue is 99% of the configuration.
|
||||
|
||||
- map the `/config/app.conf` file as described in the [Setup documentation](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md#docker-paths).
|
||||
- rename the `devices_<timestamp>.csv` to `devices.csv` and place it in the `/config` folder
|
||||
- upload the `app.conf` file into the mounted `/app/config/` folder as described in the [Setup documentation](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md#docker-paths).
|
||||
- rename the `devices_<timestamp>.csv` to `devices.csv` and place it in the `/app/config` folder
|
||||
- Restore the `devices.csv` backup via the [Maintenance section](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICES_BULK_EDITING.md)
|
||||
|
||||
|
||||
|
||||
14
docs/COMMUNITY_GUIDES.md
Executable file
@@ -0,0 +1,14 @@
|
||||
# Community Guides
|
||||
|
||||
Use the official installation guides at first and use community content as supplementary material. Open an issue or PR if you'd like to add your link to the list 🙏 (Ordered by last update time)
|
||||
|
||||
- ▶ [Home Lab Network Monitoring - Scotti-BYTE Enterprise Consulting Services](https://www.youtube.com/watch?v=0DryhzrQSJA) (July 2024)
|
||||
- 📄 [How to Install NetAlertX on Your Synology NAS - Marius hosting](https://mariushosting.com/how-to-install-pi-alert-on-your-synology-nas/) (Updated frequently)
|
||||
- 📄 [Using the PiAlert Network Security Scanner on a Raspberry Pi - PiMyLifeUp](https://pimylifeup.com/raspberry-pi-pialert/)
|
||||
- ▶ [How to Setup Pi.Alert on Your Synology NAS - Digital Aloha](https://www.youtube.com/watch?v=M4YhpuRFaUg)
|
||||
- 📄 [防蹭网神器,网络安全助手 | 极空间部署网络扫描和通知系统『NetAlertX』](https://blog.csdn.net/qq_63499861/article/details/141105273)
|
||||
- 📄 [시놀/헤놀에서 네트워크 스캐너 Pi.Alert Docker로 설치 및 사용하기](https://blog.dalso.org/article/%EC%8B%9C%EB%86%80-%ED%97%A4%EB%86%80%EC%97%90%EC%84%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%8A%A4%EC%BA%90%EB%84%88-pi-alert-docker%EB%A1%9C-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EC%82%AC%EC%9A%A9) (July 2023)
|
||||
- 📄 [网络入侵探测器Pi.Alert (Chinese)](https://codeantenna.com/a/VgUvIAjZ7J) (May 2023)
|
||||
- ▶ [Pi.Alert auf Synology & Docker by - Jürgen Barth](https://www.youtube.com/watch?v=-ouvA2UNu-A) (March 2023)
|
||||
- ▶ [Top Docker Container for Home Server Security - VirtualizationHowto](https://www.youtube.com/watch?v=tY-w-enLF6Q) (March 2023)
|
||||
- ▶ [Pi.Alert or WatchYourLAN can alert you to unknown devices appearing on your WiFi or LAN network - Danie van der Merwe](https://www.youtube.com/watch?v=v6an9QG2xF0) (November 2022)
|
||||
85
docs/CUSTOM_PROPERTIES.md
Executable file
@@ -0,0 +1,85 @@
|
||||
# Custom Properties for Devices
|
||||
|
||||

|
||||
|
||||
## Overview
|
||||
|
||||
This functionality allows you to define **custom properties** for devices, which can store and display additional information on the device listing page. By marking properties as visible, you can enhance the user interface with quick actions, notes, or external links.
|
||||
|
||||
### Key Features:
|
||||
- **Customizable Properties**: Define specific properties for each device.
|
||||
- **Visibility Control**: Choose which properties are displayed on the device listing page.
|
||||
- **Interactive Elements**: Include actions like links, modals, and device management directly in the interface.
|
||||
|
||||
---
|
||||
|
||||
## Defining Custom Properties
|
||||
|
||||
Custom properties are structured as a list of objects, where each property includes the following fields:
|
||||
|
||||
| Field | Description |
|
||||
|--------------------|-----------------------------------------------------------------------------|
|
||||
| `CUSTPROP_icon` | The icon (Base64-encoded HTML) displayed for the property. |
|
||||
| `CUSTPROP_type` | The action type (e.g., `show_notes`, `link`, `delete_dev`). |
|
||||
| `CUSTPROP_name` | A short name or title for the property. |
|
||||
| `CUSTPROP_args` | Arguments for the action (e.g., URL or modal text). |
|
||||
| `CUSTPROP_notes` | Additional notes or details displayed when applicable. |
|
||||
| `CUSTPROP_show` | A boolean to control visibility (`true` to show on the listing page). |
|
||||
|
||||
---
|
||||
|
||||
## Available Action Types
|
||||
|
||||
- **Show Notes**: Displays a modal with a title and additional notes.
|
||||
- **Example**: Show firmware details or custom messages.
|
||||
- **Link**: Redirects to a specified URL in the current browser tab. (**Arguments** Needs to contain the full URL.)
|
||||
- **Link (New Tab)**: Opens a specified URL in a new browser tab. (**Arguments** Needs to contain the full URL.)
|
||||
- **Delete Device**: Deletes the device using its MAC address.
|
||||
- **Run Plugin**: Placeholder for executing custom plugins (not implemented yet).
|
||||
|
||||
---
|
||||
|
||||
## Usage on the Device Listing Page
|
||||
|
||||

|
||||
|
||||
Visible properties (`CUSTPROP_show: true`) are displayed as interactive icons in the device listing. Each icon can perform one of the following actions based on the `CUSTPROP_type`:
|
||||
|
||||
1. **Modals (e.g., Show Notes)**:
|
||||
- Displays detailed information in a popup modal.
|
||||
- Example: Firmware version details.
|
||||
|
||||
2. **Links**:
|
||||
- Redirect to an external or internal URL.
|
||||
- Example: Open a device's documentation or external site.
|
||||
|
||||
3. **Device Actions**:
|
||||
- Manage devices with actions like delete.
|
||||
- Example: Quickly remove a device from the network.
|
||||
|
||||
4. **Plugins**:
|
||||
- Future placeholder for running custom plugin scripts.
|
||||
- **Note**: Not implemented yet.
|
||||
|
||||
---
|
||||
|
||||
## Example Scenarios
|
||||
|
||||
1. **Device Documentation Link**:
|
||||
- Add a custom property with `CUSTPROP_type` set to `link` or `link_new_tab` to allow quick navigation to the documentation.
|
||||
|
||||
2. **Firmware Details**:
|
||||
- Use `CUSTPROP_type: show_notes` to display firmware versions or upgrade instructions in a modal.
|
||||
|
||||
3. **Device Removal**:
|
||||
- Enable device removal functionality using `CUSTPROP_type: delete_dev`.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- **Plugin Functionality**: The `run_plugin` action type is currently not implemented and will show an alert if used.
|
||||
- **Custom Icons (Experimental 🧪)**: Use Base64-encoded HTML to provide custom icons for each property. You can add your icons in Setttings via the `CUSTPROP_icon` settings
|
||||
- **Visibility Control**: Only properties with `CUSTPROP_show: true` will appear on the listing page.
|
||||
|
||||
This feature provides a flexible way to enhance device management and display with interactive elements tailored to your needs.
|
||||
@@ -55,7 +55,7 @@ Input data from the plugin might cause mapping issues in specific edge cases. Lo
|
||||
17:31:05 [Plugins] SQL sqlParams for mapping: [('01:01:01:01:01:01', '172.30.0.1', 0, 'aaaa', 'vvvvvvvvv', 'PIHOLE'), ('02:42:ac:1e:00:02', '172.30.0.2', 0, 'dddd', 'vvvvv2222', 'PIHOLE')]
|
||||
🔺
|
||||
17:31:05 [API] Update API starting
|
||||
17:31:06 [API] Updating table_plugins_history.json file in /front/api
|
||||
17:31:06 [API] Updating table_plugins_history.json file in /api
|
||||
```
|
||||
|
||||
> The debug output between the 🔻red arrows🔺 is important for debugging (arrows added only to highlight the section on this page, they are not available in the actual debug log)
|
||||
|
||||
@@ -64,7 +64,7 @@ Sometimes specific log sections are needed to debug issues. The Devices and Curr
|
||||
|
||||
### Permissions
|
||||
|
||||
* If facing issues (AJAX errors, can't write to DB, empty screen, etc,) make sure permissions are set correctly, and check the logs under `/app/front/log`.
|
||||
* If facing issues (AJAX errors, can't write to DB, empty screen, etc,) make sure permissions are set correctly, and check the logs under `/app/log`.
|
||||
* To solve permission issues you can try setting the owner and group of the `app.db` by executing the following on the host system: `docker exec netalertx chown -R www-data:www-data /app/db/app.db`.
|
||||
* If still facing issues, try to map the app.db file (⚠ not folder) to `:/app/db/app.db` (see [docker-compose Examples](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md#-docker-composeyml-examples) for details)
|
||||
|
||||
|
||||
6
docs/DEVICE_DISPLAY_SETTINGS.md
Executable file
@@ -0,0 +1,6 @@
|
||||
# Device Display Settings
|
||||
|
||||
This set of settings allows you to group Devices under different views. The Archived toggle allows you to exclude a Device from most listings and notifications.
|
||||
|
||||
|
||||

|
||||
@@ -1,107 +1,52 @@
|
||||
# NetAlertX - Device Management
|
||||
<!--- --------------------------------------------------------------------- --->
|
||||
To edit device information:
|
||||
- Select "Devices" in the menu on the left of the screen
|
||||
- Find the device you want to edit in the central table
|
||||
- Go to the device page by clicking on the device name or status
|
||||
- Press "Details" tab of the device
|
||||
- Edit the device data
|
||||
- Press the "Save" button
|
||||
|
||||
The Main Info section is where most of the device identifiable information is stored and edited. Some of the information is autodetected via various plugins. Initial values for most of the fields can be specified in the `NEWDEV` plugin.
|
||||
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> [Bulk-edit devices](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICES_BULK_EDITING.md) by using the _CSV Export_ functionality in the _Maintenance_ section.
|
||||
> You can multi-edit devices by selecting them in the main Devices view, from the Mainetence section, or via the CSV Export functionality under Maintenance. More info can be found in the [Devices Bulk-editing docs](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICES_BULK_EDITING.md).
|
||||
|
||||
|
||||
![Device Details][screen1]
|
||||

|
||||
|
||||
|
||||
## Main Info
|
||||
- **MAC**: MAC addres of the device. Not editable.
|
||||
- **Name**: Friendly device name
|
||||
- **Owner**: Device owner (The list is self-populated with existing owners)
|
||||
- **Type**: Select a device type from the dropdown list (Smartphone, Table,
|
||||
Laptop, TV, router, ....) or type a new device type
|
||||
- **Vendor**: Automatically updated by NetAlertX when empty or unknown
|
||||
- **Favorite**: Mark the device as favorite and then it will appears at the
|
||||
begining of the device list
|
||||
- **Group**: Select a grouper ('Always on', 'Personal', Friends') or type
|
||||
your own Group name
|
||||
- **Comments**: Type any comments for the device
|
||||
|
||||
## Session Info
|
||||
- **Status**: Show device status : On-line / Off-Line
|
||||
- **First Session**: Date and time of the first connection
|
||||
- **Last Offline**: Date and time of the last last time the device was offline
|
||||
- **Last IP**: Last known IP used during the last connection
|
||||
- **Static IP**: Check this box to identify devices that always use the
|
||||
same IP
|
||||
- **MAC**: MAC addres of the device. Not editable, unless creating a new dummy device.
|
||||
- **Last IP**: IP addres of the device. Not editable, unless creating a new dummy device.
|
||||
- **Name**: Friendly device name. Autodetected via various 🆎 Name discovery [plugins](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/README.md).
|
||||
- **Icon**: Partially autodetected. Select an existing or [add a custom icon](https://github.com/jokob-sk/NetAlertX/blob/main/docs/ICONS.md). You can also auto-apply the same icon on all devices of the same type.
|
||||
- **Owner**: Device owner (The list is self-populated with existing owners and you can add custom values).
|
||||
- **Type**: Select a device type from the dropdown list (`Smartphone`, `Tablet`,
|
||||
`Laptop`, `TV`, `router`, etc.) or add a new device type. If you want the device to act as a **Network device** (and be able to be a network node in the Network view), select a type under Network Devices or add a new Network Device type in Settings. More information can be found in the [Network Setup docs](https://github.com/jokob-sk/NetAlertX/blob/main/docs/NETWORK_TREE.md).
|
||||
- **Vendor**: The manufacturing vendor. Automatically updated by NetAlertX when empty or unknown, can be edited.
|
||||
- **Group**: Select a group (`Always on`, `Personal`, `Friends`, etc.) or type
|
||||
your own Group name.
|
||||
- **Location**: Select the location, usually a room, where the device is located (`Kitchen`, `Attic`, `Living room`, etc.) or add a custom Location.
|
||||
- **Comments**: Add any comments for the device, such as a serial number, or maintenance information.
|
||||
|
||||
## Events & Alerts config
|
||||
- **Scan Cycle**: Select the scan cycle: 0, 1', 15'
|
||||
- Some devices do not respond to all ARP packets, for this cases is better
|
||||
to use a 15' cycle.
|
||||
- **For Apple devices I recommend using 15' cycle**
|
||||
- **Alert All Events**: Send a notification in each event (connection,
|
||||
disconnection, IP Changed, ...)
|
||||
- **Alert Down**: Send a notification when the device is down
|
||||
- *(Userful with "always connected" devices: Camera, Alexa,...)*
|
||||
- **Skip repeated notifications during**: Do not send more than one
|
||||
notification to this device for X hours
|
||||
- *(Useful to avoid notification saturation on devices that frequently
|
||||
connects and disconnects)*
|
||||
> [!NOTE]
|
||||
>
|
||||
> Please note the above usage of the fields are only suggestions. You can use most of these fields for other purposes, such as storing the network interface, company owning a device, or similar.
|
||||
|
||||
# Privacy & Random MAC's
|
||||
<!--- --------------------------------------------------------------------- --->
|
||||
## Dummy devices
|
||||
|
||||
The latest versions of some operating systems (IOS and Android) incorporate a
|
||||
new & interesting functionality to improve privacy: **Random MACs**.
|
||||
You can create dummy devices from the Devices listing screen.
|
||||
|
||||
This functionality allows you to **hide the true MAC** of the device and
|
||||
**assign a random MAC** when we connect to WIFI networks.
|
||||

|
||||
|
||||
This behavior is especially useful when connecting to WIFI's that we do not
|
||||
know, but it **is totally useless when connecting to our own WIFI's** or known
|
||||
networks.
|
||||
The **MAC** field and the **Last IP** field will then become editable.
|
||||
|
||||
**I recommend disabling this operation when connecting our devices to our own
|
||||
WIFI's**, in this way, NetAlertX will be able to identify the device, and it
|
||||
will not identify it as a new device every so often (every time IOS or Android
|
||||
decides to change the MAC).
|
||||
|
||||
### IOS
|
||||
![ios][ios]
|
||||
|
||||
- [Use private Wi-Fi addresses in iOS 14](https://support.apple.com/en-us/HT211227)
|
||||
|
||||
### Android
|
||||
![Android][Android]
|
||||
|
||||
- [How to Disable MAC Randomization in Android 10](https://support.boingo.com/s/article/How-to-Disable-MAC-Randomization-in-Android-10-Android-Q)
|
||||
- [How do I disable random Wi-Fi MAC address on Android 10](https://support.plume.com/hc/en-gb/articles/360052070714-How-do-I-disable-random-Wi-Fi-MAC-address-on-Android-10-)
|
||||
|
||||
### License
|
||||
GPL 3.0
|
||||
[Read more here](../LICENSE.txt)
|
||||
|
||||
### Contact
|
||||
|
||||
Always use the Issue tracker for the correct fork, for example:
|
||||
|
||||
[jokob-sk/NetAlertX](https://github.com/jokob-sk/NetAlertX/issues). Please also follow the guidelines on:
|
||||
|
||||
- ➕ [Pull Request guidelines](https://github.com/jokob-sk/NetAlertX/tree/main/docs#-pull-requests-prs)
|
||||
- 🙏 [Feature request guidelines](https://github.com/jokob-sk/NetAlertX/tree/main/docs#-feature-requests)
|
||||
- 🐛 [Issue guidelines](https://github.com/jokob-sk/NetAlertX/tree/main/docs#-submitting-an-issue-or-bug)
|
||||
|
||||
|
||||
***Suggestions and comments are welcome***
|
||||

|
||||
|
||||
|
||||
<!--- --------------------------------------------------------------------- --->
|
||||
[main]: ./img/1_devices.jpg "Main screen"
|
||||
[screen1]: ./img/2_1_device_details.jpg "Screen 1"
|
||||
[ios]: https://9to5mac.com/wp-content/uploads/sites/6/2020/08/how-to-use-private-wifi-mac-address-iphone-ipad.png?resize=2048,1009 "ios"
|
||||
[Android]: ./img/android_random_mac.jpg "Android"
|
||||
> [!NOTE]
|
||||
>
|
||||
> You can couple this with the `ICMP` plugin which can be used to monitor the status of these devices, if they are actual devices reachable with the `ping` command. If not, you can use a loopback IP address so they appear online, such as `0.0.0.0` or `127.0.0.1`.
|
||||
|
||||
## Copying data from an existing device.
|
||||
|
||||
To speed up device population you can also copy data from an existing device. This can be done from the **Tools** tab on the Device details.
|
||||
|
||||
|
||||
|
||||
128
docs/DOCKER_COMPOSE.md
Executable file
@@ -0,0 +1,128 @@
|
||||
# `docker-compose.yaml` Examples
|
||||
|
||||
### Example 1
|
||||
|
||||
```yaml
|
||||
services:
|
||||
netalertx:
|
||||
container_name: netalertx
|
||||
# use the below line if you want to test the latest dev image
|
||||
# image: "jokobsk/netalertx-dev:latest"
|
||||
image: "jokobsk/netalertx:latest"
|
||||
network_mode: "host"
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- local_path/config:/app/config
|
||||
- local_path/db:/app/db
|
||||
# (optional) useful for debugging if you have issues setting up the container
|
||||
- local_path/logs:/app/log
|
||||
# (API: OPTION 1) use for performance
|
||||
- type: tmpfs
|
||||
target: /app/api
|
||||
# (API: OPTION 2) use when debugging issues
|
||||
# - local_path/api:/app/api
|
||||
environment:
|
||||
- TZ=Europe/Berlin
|
||||
- PORT=20211
|
||||
```
|
||||
|
||||
To run the container execute: `sudo docker-compose up -d`
|
||||
|
||||
### Example 2
|
||||
|
||||
Example by [SeimuS](https://github.com/SeimusS).
|
||||
|
||||
```yaml
|
||||
netalertx:
|
||||
container_name: NetAlertX
|
||||
hostname: NetAlertX
|
||||
privileged: true
|
||||
# use the below line if you want to test the latest dev image
|
||||
# image: "jokobsk/netalertx-dev:latest"
|
||||
image: jokobsk/netalertx:latest
|
||||
environment:
|
||||
- TZ=Europe/Bratislava
|
||||
restart: always
|
||||
volumes:
|
||||
- ./netalertx/db:/app/db
|
||||
- ./netalertx/config:/app/config
|
||||
network_mode: host
|
||||
```
|
||||
|
||||
To run the container execute: `sudo docker-compose up -d`
|
||||
|
||||
### Example 3
|
||||
|
||||
`docker-compose.yml`
|
||||
|
||||
```yaml
|
||||
services:
|
||||
netalertx:
|
||||
container_name: netalertx
|
||||
# use the below line if you want to test the latest dev image
|
||||
# image: "jokobsk/netalertx-dev:latest"
|
||||
image: "jokobsk/netalertx:latest"
|
||||
network_mode: "host"
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ${APP_DATA_LOCATION}/netalertx/config:/app/config
|
||||
- ${APP_DATA_LOCATION}/netalertx/db/:/app/db/
|
||||
# (optional) useful for debugging if you have issues setting up the container
|
||||
- ${LOGS_LOCATION}:/app/log
|
||||
# (API: OPTION 1) use for performance
|
||||
- type: tmpfs
|
||||
target: /app/api
|
||||
# (API: OPTION 2) use when debugging issues
|
||||
# - local/path/api:/app/api
|
||||
environment:
|
||||
- TZ=${TZ}
|
||||
- PORT=${PORT}
|
||||
```
|
||||
|
||||
`.env` file
|
||||
|
||||
```yaml
|
||||
#GLOBAL PATH VARIABLES
|
||||
|
||||
APP_DATA_LOCATION=/path/to/docker_appdata
|
||||
APP_CONFIG_LOCATION=/path/to/docker_config
|
||||
LOGS_LOCATION=/path/to/docker_logs
|
||||
|
||||
#ENVIRONMENT VARIABLES
|
||||
|
||||
TZ=Europe/Paris
|
||||
PORT=20211
|
||||
|
||||
#DEVELOPMENT VARIABLES
|
||||
|
||||
DEV_LOCATION=/path/to/local/source/code
|
||||
```
|
||||
|
||||
To run the container execute: `sudo docker-compose --env-file /path/to/.env up`
|
||||
|
||||
### Example 4
|
||||
|
||||
Courtesy of [pbek](https://github.com/pbek). The volume `netalertx_db` is used by the db directory. The two config files are mounted directly from a local folder to their places in the config folder. You can backup the `docker-compose.yaml` folder and the docker volumes folder.
|
||||
|
||||
```yaml
|
||||
netalertx:
|
||||
# use the below line if you want to test the latest dev image
|
||||
# image: "jokobsk/netalertx-dev:latest"
|
||||
image: jokobsk/netalertx
|
||||
ports:
|
||||
- "80:20211/tcp"
|
||||
environment:
|
||||
- TZ=Europe/Vienna
|
||||
networks:
|
||||
local:
|
||||
ipv4_address: 192.168.1.2
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- netalertx_db:/app/db
|
||||
- ./netalertx/:/app/config/
|
||||
# (API: OPTION 1) use for performance
|
||||
- type: tmpfs
|
||||
target: /app/api
|
||||
# (API: OPTION 2) use when debugging issues
|
||||
# - local/path/api:/app/api
|
||||
```
|
||||
@@ -5,7 +5,7 @@ To download and install NetAlertX on the hardware/server directly use the `curl`
|
||||
> [!NOTE]
|
||||
> This is an Experimental feature 🧪 and it relies on community support.
|
||||
>
|
||||
> Looking for maintainers for this installation method 🙂
|
||||
> 🙏 Looking for maintainers for this installation method 🙂
|
||||
>
|
||||
> There is no guarantee that the install script or any other script will gracefully handle other installed software.
|
||||
> Data loss is a possibility, **it is recommended to install NetAlertX using the supplied Docker image**.
|
||||
@@ -36,6 +36,9 @@ Some facts about what and where something will be changed/installed by the HW in
|
||||
|
||||
## 📥 Installation via CURL
|
||||
|
||||
> [!TIP]
|
||||
> If the below fails try grabbing and installing one of the [previous releases](https://github.com/jokob-sk/NetAlertX/releases) and run the installation from the zip package.
|
||||
|
||||
```bash
|
||||
curl -o install.debian.sh https://raw.githubusercontent.com/jokob-sk/NetAlertX/main/install/install.debian.sh && sudo chmod +x install.debian.sh && sudo ./install.debian.sh
|
||||
```
|
||||
|
||||
@@ -85,7 +85,7 @@ services:
|
||||
- local/path/config:/app/config # ⚠ This has changed (🔺required)
|
||||
- local/path/db:/app/db # ⚠ This has changed (🔺required)
|
||||
# (optional) useful for debugging if you have issues setting up the container
|
||||
- local/path/logs:/app/front/log # ⚠ This has changed (🟡optional)
|
||||
- local/path/logs:/app/log # ⚠ This has changed (🟡optional)
|
||||
environment:
|
||||
- TZ=Europe/Berlin
|
||||
- PORT=20211
|
||||
@@ -135,7 +135,7 @@ services:
|
||||
- local/path/config/app.conf:/app/config/app.conf # ⚠ This has changed (🔺required)
|
||||
- local/path/db/app.db:/app/db/app.db # ⚠ This has changed (🔺required)
|
||||
# (optional) useful for debugging if you have issues setting up the container
|
||||
- local/path/logs:/app/front/log # ⚠ This has changed (🟡optional)
|
||||
- local/path/logs:/app/log # ⚠ This has changed (🟡optional)
|
||||
environment:
|
||||
- TZ=Europe/Berlin
|
||||
- PORT=20211
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Make sure you have a root device with the MAC `Internet` (No other MAC addresses are currently supported as the root node) set to a network device type (e.g.: **Type**:`Router`).
|
||||
|
||||
> 💡 Tip: You can add dummy devices via the [Undiscoverables plugin](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/undiscoverables/README.md)
|
||||
> 💡 Tip: You can add dummy devices via the [Create dummy device](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICE_MANAGEMENT.md#dummy-devices) button in the Devices listing page.
|
||||
|
||||
> 💡 Tip: Export your configuration of the Network and Devices once in a while via the Export CSV feature under **Maintenance** -> **Backup/Restore** -> **CSV Export**.
|
||||
|
||||
|
||||
@@ -29,3 +29,31 @@ The more often you scan the networks the more resources, traffic and DB read/wri
|
||||
|
||||
Also consider decreasing the scanned subnet, e.g. from `/16` to `/24` if need be.
|
||||
|
||||
# Store temporary files in memory
|
||||
|
||||
You can also store temporary files in application memory (`/app/api` and `/app/log` folders). See highlighted lines `◀` below.
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
services:
|
||||
netalertx:
|
||||
container_name: netalertx
|
||||
# use the below line if you want to test the latest dev image
|
||||
# image: "jokobsk/netalertx-dev:latest"
|
||||
image: "jokobsk/netalertx:latest"
|
||||
network_mode: "host"
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- local/path/config:/app/config
|
||||
- local/path/db:/app/db
|
||||
# (optional) useful for debugging if you have issues setting up the container
|
||||
- local/path/logs:/app/log
|
||||
# (API: OPTION 1) use for performance
|
||||
- type: tmpfs # ◀
|
||||
target: /app/api # ◀
|
||||
# (API: OPTION 2) use when debugging issues
|
||||
# - local/path/api:/app/api
|
||||
environment:
|
||||
- TZ=Europe/Berlin
|
||||
- PORT=20211
|
||||
```
|
||||
|
||||
45
docs/PIHOLE_GUIDE.md
Executable file
@@ -0,0 +1,45 @@
|
||||
# Integration with PiHole
|
||||
|
||||
NetAlertX comes with 2 plugins suitable for integarting with your existing PiHole instace. One plugin is using a direct SQLite DB connection, the other leverages the DHCP.leases file generated by PiHole. You can combine both approaches and also supplement it with other [plugins](/front/plugins/README.md).
|
||||
|
||||
## Approach 1: `DHCPLSS` Plugin - Import devices from the PiHole DHCP leases file
|
||||
|
||||

|
||||
|
||||
### Settings
|
||||
|
||||
| Setting | Description | Recommended value |
|
||||
| :------------- | :------------- | :-------------|
|
||||
| `DHCPLSS_RUN` | When the plugin should run. | `schedule` |
|
||||
| `DHCPLSS_RUN_SCHD` | If you run multiple device scanner plugins, align the schedules of all plugins to the same value. | `*/5 * * * *` |
|
||||
| `DHCPLSS_paths_to_check` | You need to map the value in this setting in the `docker-compose.yml` file. The in-container path must contain `pihole` so it's parsed correctly. | `['/etc/pihole/dhcp.leases']` |
|
||||
|
||||
Check the [DHCPLSS plugin readme](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/dhcp_leases#overview) for details
|
||||
|
||||
### docker-compose changes
|
||||
|
||||
| Path | Description |
|
||||
| :------------- | :------------- |
|
||||
| `:/etc/pihole/dhcp.leases` | PiHole's `dhcp.leases` file. Required if you want to use PiHole `dhcp.leases` file. This has to be matched with a corresponding `DHCPLSS_paths_to_check` setting entry (the path in the container must contain `pihole`) |
|
||||
|
||||
|
||||
## Approach 2: `PIHOLE` Plugin - Import devices directly from the PiHole database
|
||||
|
||||

|
||||
|
||||
| Setting | Description | Recommended value |
|
||||
| :------------- | :------------- | :-------------|
|
||||
| `PIHOLE_RUN` | When the plugin should run. | `schedule` |
|
||||
| `PIHOLE_RUN_SCHD` | If you run multiple device scanner plugins, align the schedules of all plugins to the same value. | `*/5 * * * *` |
|
||||
| `PIHOLE_DB_PATH` | You need to map the value in this setting in the `docker-compose.yml` file. | `/etc/pihole/pihole-FTL.db` |
|
||||
|
||||
Check the [PiHole plugin readme](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/pihole_scan) for details
|
||||
|
||||
### docker-compose changes
|
||||
|
||||
| Path | Description |
|
||||
| :------------- | :------------- |
|
||||
| `:/etc/pihole/pihole-FTL.db` | PiHole's `pihole-FTL.db` database file. |
|
||||
|
||||
|
||||
Check out other [plugins](/front/plugins/README.md) that can help you discover more about your network or check how to scan [Remote networks](/docs/REMOTE_NETWORKS.md).
|
||||
@@ -12,6 +12,9 @@ NetAlertX comes with a plugin system to feed events from third-party scripts int
|
||||
|
||||
### 🎥 Watch the video:
|
||||
|
||||
> [!TIP]
|
||||
> Read this guide [Development environment setup guide](/docs/DEV_ENV_SETUP.md) to set up your local environment for development. 👩💻
|
||||
|
||||
[](https://youtu.be/cdbxlwiWhv8)
|
||||
|
||||
### 📸 Screenshots
|
||||
@@ -46,7 +49,7 @@ Please read the below carefully if you'd like to contribute with a plugin yourse
|
||||
|----------------------|----------------------|----------------------|
|
||||
| `config.json` | yes | Contains the plugin configuration (manifest) including the settings available to the user. |
|
||||
| `script.py` | no | The Python script itself. You may call any valid linux command. |
|
||||
| `last_result.log` | no | The file used to interface between NetAlertX and the plugin. Required for a script plugin if you want to feed data into the app. |
|
||||
| `last_result.<prefix>.log` | no | The file used to interface between NetAlertX and the plugin. Required for a script plugin if you want to feed data into the app. Stored in the `/api/log/plugins/` |
|
||||
| `script.log` | no | Logging output (recommended) |
|
||||
| `README.md` | yes | Any setup considerations or overview |
|
||||
|
||||
@@ -103,7 +106,7 @@ Currently, these data sources are supported (valid `data_source` value).
|
||||
| External SQLite DB query | `sqlite-db-query` | yes | Executes a SQL query from the `CMD` setting on an external SQLite database mapped in the `DB_PATH` setting. |
|
||||
| Plugin type | `plugin_type` | no | Specifies the type of the plugin and in which section the Plugin settings are displayed ( one of `general/system/scanner/other/publisher` ). |
|
||||
|
||||
> * "Needs to return a "table" means that the application expects a `last_result.log` file with some results. It's not a blocker, however warnings in the `app.log` might be logged.
|
||||
> * "Needs to return a "table" means that the application expects a `last_result.<prefix>.log` file with some results. It's not a blocker, however warnings in the `app.log` might be logged.
|
||||
|
||||
> 🔎Example
|
||||
>```json
|
||||
@@ -120,21 +123,21 @@ You can show or hide the UI on the "Plugins" page and "Plugins" tab for a plugin
|
||||
|
||||
### "data_source": "script"
|
||||
|
||||
If the `data_source` is set to `script` the `CMD` setting (that you specify in the `settings` array section in the `config.json`) contains an executable Linux command, that usually generates a `last_result.log` file (not required if you don't import any data into the app). The `last_result.log` file needs to be saved in the same folder as the plugin.
|
||||
If the `data_source` is set to `script` the `CMD` setting (that you specify in the `settings` array section in the `config.json`) contains an executable Linux command, that usually generates a `last_result.<prefix>.log` file (not required if you don't import any data into the app). The `last_result.<prefix>.log` file needs to be saved in `/api/log/plugins`.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> A lot of the work is taken care of by the [`plugin_helper.py` library](/front/plugins/plugin_helper.py). You don't need to manage the `last_result.log` file if using the helper objects. Check other `script.py` of other plugins for details (The [Undicoverables plugins `script.py` file](/front/plugins/undiscoverables/script.py) is a good example).
|
||||
> A lot of the work is taken care of by the [`plugin_helper.py` library](/front/plugins/plugin_helper.py). You don't need to manage the `last_result.<prefix>.log` file if using the helper objects. Check other `script.py` of other plugins for details (The [Undicoverables plugins `script.py` file](/front/plugins/undiscoverables/script.py) is a good example).
|
||||
|
||||
The content of the `last_result.log` file needs to contain the columns as defined in the "Column order and values" section above. The order of columns can't be changed. After every scan it should contain only the results from the latest scan/execution.
|
||||
The content of the `last_result.<prefix>.log` file needs to contain the columns as defined in the "Column order and values" section above. The order of columns can't be changed. After every scan it should contain only the results from the latest scan/execution.
|
||||
|
||||
- The format of the `last_result.log` is a `csv`-like file with the pipe `|` as a separator.
|
||||
- The format of the `last_result.<prefix>.log` is a `csv`-like file with the pipe `|` as a separator.
|
||||
- 9 (nine) values need to be supplied, so every line needs to contain 8 pipe separators. Empty values are represented by `null`.
|
||||
- Don't render "headers" for these "columns".
|
||||
Every scan result/event entry needs to be on a new line.
|
||||
- You can find which "columns" need to be present, and if the value is required or optional, in the "Column order and values" section.
|
||||
- The order of these "columns" can't be changed.
|
||||
|
||||
#### 🔎 last_result.log examples
|
||||
#### 🔎 last_result.prefix.log examples
|
||||
|
||||
Valid CSV:
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ There is also an in-app Help / FAQ section that should be answering frequently a
|
||||
|
||||
- [Synology Guide](/docs/SYNOLOGY_GUIDE.md)
|
||||
- [Subnets and VLANs configuration for arp-scan](/docs/SUBNETS.md)
|
||||
- [Scanning Remote Networks](/docs/REMOTE_NETWORKS.md)
|
||||
- [SMTP server config](/docs/SMTP.md)
|
||||
- [Custom Icon configuration and support](/docs/ICONS.md)
|
||||
- [Notifications](/docs/NOTIFICATIONS.md)
|
||||
@@ -56,7 +57,6 @@ There is also an in-app Help / FAQ section that should be answering frequently a
|
||||
- [Manage devices (legacy docs)](/docs/DEVICE_MANAGEMENT.md)
|
||||
- [Random MAC/MAC icon meaning (legacy docs)](/docs/RANDOM_MAC.md)
|
||||
|
||||
|
||||
#### 🔎 Examples
|
||||
|
||||
- [N8N webhook example](/docs/WEBHOOK_N8N.md)
|
||||
|
||||
49
docs/REMOTE_NETWORKS.md
Executable file
@@ -0,0 +1,49 @@
|
||||
# Scanning Remote or Inaccessible Networks
|
||||
|
||||
By design, local network scanners such as `arp-scan` use ARP (Address Resolution Protocol) to map IP addresses to MAC addresses on the local network. Since ARP operates at Layer 2 (Data Link Layer), it typically works only within a single broadcast domain, usually limited to a single router or network segment.
|
||||
|
||||
To scan multiple locally accessible network segments, add them as subnets according to the [subnets](https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md) documentation.
|
||||
|
||||
## Complex Use Cases
|
||||
|
||||
The following network setups might make some devices undetectable. Check the specific setup to understand the cause and find potential workarounds to still report on these devices.
|
||||
|
||||
### Wi-Fi Extenders
|
||||
|
||||
Wi-Fi extenders typically create a separate network or subnet, which can prevent network scanning tools like `arp-scan` from detecting devices behind the extender.
|
||||
|
||||
> **Possible workaround**: Scan the specific subnet that the extender uses, if it is separate from the main network.
|
||||
|
||||
### VPNs
|
||||
|
||||
ARP operates at Layer 2 (Data Link Layer) and works only within a local area network (LAN). VPNs, which operate at Layer 3 (Network Layer), route traffic between networks, preventing ARP requests from discovering devices outside the local network.
|
||||
|
||||
VPNs use virtual interfaces (e.g., `tun0`, `tap0`) to encapsulate traffic, bypassing ARP-based discovery. Additionally, many VPNs use NAT, which masks individual devices behind a shared IP address.
|
||||
|
||||
> **Possible workaround**: Configure the VPN to bridge networks instead of routing to enable ARP, though this depends on the VPN setup and security requirements.
|
||||
|
||||
# Other Workarounds
|
||||
|
||||
The following workarounds should work for most complex network setups.
|
||||
|
||||
## Supplementing Plugins
|
||||
|
||||
You can use supplementary plugins that employ alternate methods. Protocols used by the `SNMPDSC` or `DHCPLSS` plugins are widely supported on different routers and can be effective as workarounds. Check the [plugins list](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/README.md) to find a plugin that works with your router and network setup.
|
||||
|
||||
## Multiple NetAlertX Instances
|
||||
|
||||
If you have servers in different networks, you can set up separate NetAlertX instances on those subnets and synchronize the results into one instance using the [`SYNC` plugin](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/sync).
|
||||
|
||||
## Manual Entry
|
||||
|
||||
If you don't need to discover new devices and only need to report on their status (`online`, `offline`, `down`), you can manually enter devices and check their status using the [`ICMP` plugin](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/icmp_scan/), which uses the `ping` command internally.
|
||||
|
||||
For more information on how to add devices manually (or dummy devices), refer to the [Device Management](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICE_MANAGEMENT.md) documentation.
|
||||
|
||||
To create truly dummy devices, you can use a loopback IP address (e.g., `0.0.0.0` or `127.0.0.1`) so they appear online.
|
||||
|
||||
## NMAP and Fake MAC Addresses
|
||||
|
||||
Scanning remote networks with NMAP is possible (via the `NMAPDEV` plugin), but since it cannot retrieve the MAC address, you need to enable the `NMAPDEV_FAKE_MAC` setting. This will generate a fake MAC address based on the IP address, allowing you to track devices. However, this can lead to inconsistencies, especially if the IP address changes or a previously logged device is rediscovered. If this setting is disabled, only the IP address will be discovered, and devices with missing MAC addresses will be skipped.
|
||||
|
||||
Check the [NMAPDEV plugin](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/nmap_dev_scan) for details
|
||||
@@ -40,7 +40,7 @@ services:
|
||||
volumes:
|
||||
- /home/netalertx/config:/app/config
|
||||
- /home/netalertx/db:/app/db
|
||||
- /home/netalertx/log:/app/front/log
|
||||
- /home/netalertx/log:/app/log
|
||||
environment:
|
||||
- TZ=Europe/Berlin
|
||||
- PORT=20211
|
||||
@@ -66,7 +66,7 @@ services:
|
||||
volumes:
|
||||
- ./config/app.conf:/app/config/app.conf
|
||||
- ./db:/app/db
|
||||
- ./log:/app/front/log
|
||||
- ./log:/app/log
|
||||
- ./config/resolv.conf:/etc/resolv.conf # Mapping the /resolv.conf file for better name resolution
|
||||
environment:
|
||||
- TZ=Europe/Berlin
|
||||
|
||||
62
docs/SESSION_INFO.md
Executable file
@@ -0,0 +1,62 @@
|
||||
# Sessions Section in Device View
|
||||
|
||||
The **Sessions Section** provides details about a device's connection history. This data is automatically detected and cannot be edited by the user.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Key Fields
|
||||
|
||||
1. **Date and Time of First Connection**
|
||||
- **Description:** Displays the first detected connection time for the device.
|
||||
- **Editability:** Uneditable (auto-detected).
|
||||
- **Source:** Automatically captured when the device is first added to the system.
|
||||
|
||||
2. **Date and Time of Last Connection**
|
||||
- **Description:** Shows the most recent time the device was online.
|
||||
- **Editability:** Uneditable (auto-detected).
|
||||
- **Source:** Updated with every new connection event.
|
||||
|
||||
3. **Offline Devices with Missing or Conflicting Data**
|
||||
- **Description:** Handles cases where a device is offline but has incomplete or conflicting session data (e.g., missing start times).
|
||||
- **Handling:** The system flags these cases for review and attempts to infer missing details.
|
||||
|
||||
---
|
||||
|
||||
## How Sessions are Discovered and Calculated
|
||||
|
||||
### 1. Detecting New Devices
|
||||
When a device is first detected in the network, the system logs it in the events table:
|
||||
|
||||
`INSERT INTO Events (eve_MAC, eve_IP, eve_DateTime, eve_EventType, eve_AdditionalInfo, eve_PendingAlertEmail) SELECT cur_MAC, cur_IP, '{startTime}', 'New Device', cur_Vendor, 1 FROM CurrentScan WHERE NOT EXISTS (SELECT 1 FROM Devices WHERE devMac = cur_MAC)`
|
||||
|
||||
- Devices scanned in the current cycle (**CurrentScan**) are checked against the **Devices** table.
|
||||
- If a device is new:
|
||||
- A **New Device** event is logged.
|
||||
- The device’s MAC, IP, vendor, and detection time are recorded.
|
||||
|
||||
### 2. Logging Connection Sessions
|
||||
When a new connection is detected, the system creates a session record:
|
||||
|
||||
`INSERT INTO Sessions (ses_MAC, ses_IP, ses_EventTypeConnection, ses_DateTimeConnection, ses_EventTypeDisconnection, ses_DateTimeDisconnection, ses_StillConnected, ses_AdditionalInfo) SELECT cur_MAC, cur_IP, 'Connected', '{startTime}', NULL, NULL, 1, cur_Vendor FROM CurrentScan WHERE NOT EXISTS (SELECT 1 FROM Sessions WHERE ses_MAC = cur_MAC)`
|
||||
|
||||
- A new session is logged in the **Sessions** table if no prior session exists.
|
||||
- Fields like `MAC`, `IP`, `Connection Type`, and `Connection Time` are populated.
|
||||
- The `Still Connected` flag is set to `1` (active connection).
|
||||
|
||||
### 3. Handling Missing or Conflicting Data
|
||||
- Devices with incomplete or conflicting session data (e.g., missing start times) are detected.
|
||||
- The system flags these records and attempts corrections by inferring details from available data.
|
||||
|
||||
### 4. Updating Sessions
|
||||
- When a device reconnects, its session is updated with a new connection timestamp.
|
||||
- When a device disconnects:
|
||||
- The **Disconnection Time** is recorded.
|
||||
- The `Still Connected` flag is set to `0`.
|
||||
|
||||
The session information is then used to display the device presence under **Monitoring** -> **Presence**.
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -2,15 +2,13 @@
|
||||
|
||||
You need to specify the network interface and the network mask. You can also configure multiple subnets and specify VLANs (see VLAN exceptions below).
|
||||
|
||||
`ARPSCAN` can scan multiple networks if the network allows it. To scan networks directly, the subnets must be accessible from the network where NetAlertX is running. This means NetAlertX needs to have access to the interface attached to that subnet. You can verify this by running the following command in the container:
|
||||
`ARPSCAN` can scan multiple networks if the network allows it. To scan networks directly, the subnets must be accessible from the network where NetAlertX is running. This means NetAlertX needs to have access to the interface attached to that subnet. You can verify this by running the following command in the container (replace the interface and ip mask):
|
||||
|
||||
`sudo arp-scan --interface=eth0 192.168.1.0/24`
|
||||
|
||||
In this example, `--interface=eth0 192.168.1.0/24` represents a neighboring subnet. If this command returns no results, the network is not accessible due to your network or firewall restrictions.
|
||||
|
||||
If direct scans are not possible, you can use [supplementing plugins](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/README.md) that use alternate methods. Protocols used by the `SNMPDSC` or `DHCPLSS` plugins have good support and usually can be used as a workaround.
|
||||
|
||||
Alternatively, you can set up separate NetAlertX instances running on the subnets and synchronize the results into one instance with the [`SYNC` plugin](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/sync).
|
||||
If direct scans are not possible (Wi-Fi Extenders, VPNs and inaccessible networks), check the [remote networks documentation](https://github.com/jokob-sk/NetAlertX/blob/main/docs/REMOTE_NETWORKS.md).
|
||||
|
||||
> [!TIP]
|
||||
> You may need to increase the time between scans `ARPSCAN_RUN_SCHD` and the timeout `ARPSCAN_RUN_TIMEOUT` (and similar settings for related plugins) when adding more subnets. If the timeout setting is exceeded, the scan is canceled to prevent the application from hanging due to rogue plugins.
|
||||
@@ -47,18 +45,24 @@ Specify the network filter, which **significantly** speeds up the scan process.
|
||||
|
||||
**Example value:** `--interface=eth0`
|
||||
|
||||
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)).
|
||||
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)).
|
||||
|
||||

|
||||
|
||||
> [!TIP]
|
||||
> As an alternative 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`).
|
||||
> As an alternative 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`):
|
||||
> ```bash
|
||||
> Synology-NAS:/# ip -o link show | awk -F': ' '!/lo|vir|docker/ {print $2}'
|
||||
> sit0@NONE
|
||||
> eth1
|
||||
> eth0
|
||||
> ```
|
||||
|
||||
### VLANs
|
||||
|
||||
**Example value:** `-vlan=107`
|
||||
**Example value:** `--vlan=107`
|
||||
|
||||
- Append `-vlan=107` to the interface field (e.g.: `eth0 -vlan=107`) for multiple VLANs. More details are available in this [comment](https://github.com/jokob-sk/NetAlertX/issues/170#issuecomment-1419902988).
|
||||
- Append `--vlan=107` to the `SCAN_SUBNETS` field (e.g.: `192.168.1.0/24 --interface=vmbr0 --vlan=107`) for multiple VLANs.
|
||||
|
||||
#### VLANs on a Hyper-V Setup
|
||||
|
||||
@@ -111,20 +115,3 @@ Please note the accessibility of macvlans when configured on the same computer.
|
||||
- NetAlertX does not detect the macvlan container when it is running on the same computer.
|
||||
- NetAlertX recognizes the macvlan container when it is running on a different computer.
|
||||
|
||||
|
||||
### Wi-Fi Extenders
|
||||
|
||||
A Wi-Fi extender typically works by creating a separate network or subnet, which can cause certain network scanning tools, like `arp-scan`, to be unable to detect devices behind the extender.
|
||||
|
||||
This happens because `arp-scan` uses ARP (Address Resolution Protocol) to map IP addresses to MAC addresses on the local network. Since ARP is a Layer 2 (data link layer) protocol, it usually only works within a single broadcast domain, which is typically limited to a single router or network segment.
|
||||
|
||||
When you introduce a Wi-Fi extender, it may isolate devices on different segments of the network, meaning ARP packets cannot easily traverse from one segment (your main network) to another (the network behind the extender).
|
||||
|
||||
To scan devices behind the extender, you can try:
|
||||
|
||||
- Scanning the specific subnet that the extender uses, if it is separate from the main network.
|
||||
- Using [supplementing plugins](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/README.md) that use alternate methods. Protocols used by the `SNMPDSC` or `DHCPLSS` plugins have good support and usually can be used as a workaround.
|
||||
|
||||
Check the [plugins list](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/README.md) to find a plugin supported by your router and your network setup.
|
||||
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ services:
|
||||
- local/path/config:/app/config
|
||||
- local/path/db:/app/db
|
||||
# (optional) useful for debugging if you have issues setting up the container
|
||||
- local/path/logs:/app/front/log
|
||||
- local/path/logs:/app/log
|
||||
environment:
|
||||
- TZ=Europe/Berlin
|
||||
- PORT=20211
|
||||
@@ -60,7 +60,7 @@ services:
|
||||
- /volume1/app_storage/netalertx/config:/app/config
|
||||
- /volume1/app_storage/netalertx/db:/app/db
|
||||
# (optional) useful for debugging if you have issues setting up the container
|
||||
# - local/path/logs:/app/front/log <- commented out with # ⚠
|
||||
# - local/path/logs:/app/log <- commented out with # ⚠
|
||||
```
|
||||
|
||||

|
||||
|
||||
@@ -41,7 +41,7 @@ In the container execute:
|
||||
|
||||
`cat /var/log/nginx/error.log`
|
||||
|
||||
`cat /app/front/log/app.php_errors.log`
|
||||
`cat /app/log/app.php_errors.log`
|
||||
|
||||
## 8. Make sure permissions are correct
|
||||
|
||||
|
||||
BIN
docs/img/CUSTOM_PROPERTIES/Device_Custom_Properties.png
Executable file
|
After Width: | Height: | Size: 39 KiB |
BIN
docs/img/CUSTOM_PROPERTIES/Device_Custom_Properties_vid.gif
Executable file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
docs/img/DEVICE_MANAGEMENT/DeviceDetails_DisplaySettings.png
Executable file
|
After Width: | Height: | Size: 82 KiB |
BIN
docs/img/DEVICE_MANAGEMENT/DeviceEdit_SaveDummyDevice.png
Executable file
|
After Width: | Height: | Size: 78 KiB |
BIN
docs/img/DEVICE_MANAGEMENT/DeviceManagement_MainInfo.png
Executable file
|
After Width: | Height: | Size: 82 KiB |
BIN
docs/img/DEVICE_MANAGEMENT/Devices_CreateDummyDevice.png
Executable file
|
After Width: | Height: | Size: 36 KiB |
BIN
docs/img/PIHOLE_GUIDE/DHCPLSS_pihole_settings.png
Executable file
|
After Width: | Height: | Size: 99 KiB |
BIN
docs/img/PIHOLE_GUIDE/PIHOLE_settings.png
Executable file
|
After Width: | Height: | Size: 100 KiB |
BIN
docs/img/SESSION_INFO/DeviceDetails_SessionInfo.png
Executable file
|
After Width: | Height: | Size: 53 KiB |
BIN
docs/img/SESSION_INFO/Monitoring_Presence.png
Executable file
|
After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 18 KiB |
BIN
docs/img/showcase.gif
Executable file
|
After Width: | Height: | Size: 5.1 MiB |
@@ -9,6 +9,8 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
// show loading dialog
|
||||
@@ -17,7 +19,7 @@ showSpinner()
|
||||
$(document).ready(function() {
|
||||
|
||||
// Load JSON data from the provided URL
|
||||
$.getJSON('api/table_appevents.json', function(data) {
|
||||
$.getJSON('/php/server/query_json.php?file=table_appevents.json', function(data) {
|
||||
// Process the JSON data and generate UI dynamically
|
||||
processData(data)
|
||||
|
||||
@@ -89,7 +91,3 @@ function processData(data) {
|
||||
|
||||
</script>
|
||||
|
||||
<!-- Datatable -->
|
||||
<link rel="stylesheet" href="lib/datatables.net-bs/css/dataTables.bootstrap.min.css"/>
|
||||
<script src="lib/datatables.net/js/jquery.dataTables.min.js"></script>
|
||||
<script src="lib/datatables.net-bs/js/dataTables.bootstrap.min.js"></script>
|
||||
|
||||
@@ -42,6 +42,12 @@ h5
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.alignRight
|
||||
{
|
||||
text-align: end;
|
||||
float: inline-end;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Text Classes
|
||||
----------------------------------------------------------------------------- */
|
||||
@@ -281,11 +287,6 @@ body
|
||||
margin-left: 150px;
|
||||
}
|
||||
|
||||
#settingsPage
|
||||
{
|
||||
display: grid;
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.main-header .logo {
|
||||
@@ -614,7 +615,7 @@ body
|
||||
|
||||
.modal_red
|
||||
{
|
||||
color: rgb(245, 245, 245);
|
||||
color: #d41001;
|
||||
background-color: #e2acaa;
|
||||
border-color: #d41001;
|
||||
}
|
||||
@@ -887,6 +888,34 @@ height: 50px;
|
||||
} */
|
||||
}
|
||||
|
||||
/* Hide unusable buttons on the settings page for the NEWDEV plugin*/
|
||||
#settingsPage #add_option_NEWDEV_devGroup,
|
||||
#settingsPage #add_option_NEWDEV_devLocation,
|
||||
#settingsPage #add_option_NEWDEV_devOwner,
|
||||
#settingsPage #copy_icons_NEWDEV_devIcon,
|
||||
#settingsPage #add_icon_NEWDEV_devIcon,
|
||||
#settingsPage #add_option_NEWDEV_devSite,
|
||||
#settingsPage #add_option_NEWDEV_devType
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#settingsPage
|
||||
{
|
||||
display: grid;
|
||||
}
|
||||
|
||||
|
||||
#settingsPage .small-box .inner .card-title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
.settingswrap
|
||||
{
|
||||
margin-bottom: 100px;
|
||||
@@ -1260,6 +1289,11 @@ input[readonly] {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.iconColumn
|
||||
{
|
||||
max-height: 25px;
|
||||
}
|
||||
|
||||
.iconPreviewSelector svg
|
||||
{
|
||||
width:40px;
|
||||
@@ -1277,6 +1311,26 @@ input[readonly] {
|
||||
}
|
||||
|
||||
|
||||
.dummyDevice
|
||||
{
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
#tableDevices .fab
|
||||
{
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
#tableDevices .fa
|
||||
{
|
||||
font-size: 1.0em;
|
||||
}
|
||||
|
||||
#tableDevices tbody tr
|
||||
{
|
||||
height: 1.5em;
|
||||
}
|
||||
|
||||
.info-icon-nav
|
||||
{
|
||||
top: -6px;
|
||||
@@ -1344,10 +1398,87 @@ input[readonly] {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#devicePageInfoPlc
|
||||
{
|
||||
display: none;
|
||||
padding-top: 2em;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------- */
|
||||
/* Device details */
|
||||
/* ----------------------------------------------------------------- */
|
||||
|
||||
.dataTables_length label .form-control, .dataTables_filter label .form-control
|
||||
{
|
||||
float: none;
|
||||
}
|
||||
|
||||
|
||||
.form-inline .input-group
|
||||
{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input-group .actionIcon
|
||||
{
|
||||
height: 2.4em;
|
||||
}
|
||||
|
||||
.input-group .actionIcon:hover
|
||||
{
|
||||
backdrop-filter: brightness(50%);
|
||||
background-color: rgba(0, 0, 0, 0.2); /* darkens the background */
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
transition: box-shadow 0.1s ease-in-out, background-color 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
.devicePropAction
|
||||
{
|
||||
width: 1.2em;
|
||||
height: 1.2em;
|
||||
display: inline-block;
|
||||
padding: 0.1em;
|
||||
margin: 0.1em;
|
||||
/* transition: font-size 0.3s;*/
|
||||
}
|
||||
|
||||
.devicePropAction:hover
|
||||
{
|
||||
font-size: larger;
|
||||
padding: 0em;
|
||||
margin: 0em;
|
||||
|
||||
}
|
||||
|
||||
|
||||
#panDetails .dataTables_wrapper .bottom div
|
||||
{
|
||||
max-width: 34%;
|
||||
display: block;
|
||||
float:inline-end;
|
||||
height: 2em;
|
||||
}
|
||||
|
||||
#panDetails .dataTables_wrapper .bottom .dataTables_info
|
||||
{
|
||||
float:inline-start;
|
||||
}
|
||||
|
||||
#panDetails .dataTables_wrapper .bottom .dataTables_length
|
||||
{
|
||||
padding: 0.3em;
|
||||
}
|
||||
/* #panDetails .dataTables_wrapper .bottom .paging_simple_numbers */
|
||||
|
||||
|
||||
#panDetails #NEWDEV_devCustomProps_label
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------- */
|
||||
/* MODAL popups */
|
||||
/* ----------------------------------------------------------------- */
|
||||
|
||||
#modal-input-textarea
|
||||
{
|
||||
width: 100%;
|
||||
@@ -1518,6 +1649,7 @@ input[readonly] {
|
||||
.integrations-plugins .content
|
||||
{
|
||||
display: table;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.plugin-content .tab-content
|
||||
@@ -1569,13 +1701,6 @@ input[readonly] {
|
||||
}
|
||||
}
|
||||
|
||||
#settingsPage .small-box .inner .card-title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
.textOverflow
|
||||
{
|
||||
@@ -1682,7 +1807,8 @@ input[readonly] {
|
||||
|
||||
table.dataTable tbody > tr.selected
|
||||
{
|
||||
color:red;
|
||||
/* color:red; */
|
||||
color: #353c42;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
|
||||
@@ -17,9 +17,9 @@ html {
|
||||
background-color: #353c42;
|
||||
}
|
||||
|
||||
body {
|
||||
/* body {
|
||||
background-image: url('../img/boxed-bg-dark.png') !important;
|
||||
}
|
||||
} */
|
||||
|
||||
body, .bg-yellow, .callout.callout-warning, .alert-warning, .label-warning, .modal-warning .modal-body {
|
||||
|
||||
@@ -732,4 +732,8 @@ input[type="password"]::-webkit-caps-lock-indicator {
|
||||
background-color: #000 !important;
|
||||
}
|
||||
|
||||
.callout code {
|
||||
background-color: #fff !important;
|
||||
color:#000 !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
background-color: #353c42;
|
||||
}
|
||||
|
||||
body {
|
||||
/* body {
|
||||
background-image: url('../img/boxed-bg-dark.png') !important;
|
||||
}
|
||||
} */
|
||||
|
||||
body, .bg-yellow, .callout.callout-warning, .alert-warning, .label-warning, .modal-warning .modal-body {
|
||||
|
||||
@@ -735,3 +735,8 @@
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.callout code {
|
||||
background-color: #fff !important;
|
||||
color:#000 !important;
|
||||
}
|
||||
@@ -17,6 +17,8 @@
|
||||
?>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
<div class="content-wrapper">
|
||||
|
||||
@@ -28,6 +30,14 @@
|
||||
 <small>Quering device info...</small>
|
||||
</h1>
|
||||
|
||||
<div id="devicePageInfoPlc" class="card-body bg-light">
|
||||
<div class="small-box panel rounded">
|
||||
<div class="inner text-center">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- period selector -->
|
||||
<span class="breadcrumb" style="top: 0px;">
|
||||
<select class="form-control" id="period" onchange="javascript: periodChanged();">
|
||||
@@ -86,20 +96,10 @@
|
||||
|
||||
<!-- tab page 2 ------------------------------------------------------------ -->
|
||||
<div class="tab-pane fade table-responsive" id="panSessions">
|
||||
|
||||
<!-- Datatable Session -->
|
||||
<table id="tableSessions" class="table table-bordered table-hover table-striped ">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= lang('DevDetail_SessionTable_Order');?></th>
|
||||
<th><?= lang('DevDetail_SessionTable_Connection');?></th>
|
||||
<th><?= lang('DevDetail_SessionTable_Disconnection');?></th>
|
||||
<th><?= lang('DevDetail_SessionTable_Duration');?></th>
|
||||
<th><?= lang('DevDetail_SessionTable_IP');?></th>
|
||||
<th><?= lang('DevDetail_SessionTable_Additionalinfo');?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
<?php
|
||||
require 'deviceDetailsSessions.php';
|
||||
?>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -115,35 +115,24 @@
|
||||
</div>
|
||||
|
||||
<!-- tab page 3 ------------------------------------------------------------ -->
|
||||
<div class="tab-pane fade table-responsive" id="panPresence">
|
||||
<div class="tab-pane fade table-responsive" id="panPresence">
|
||||
|
||||
<?php
|
||||
// Include the other page
|
||||
include 'deviceDetailsPresence.php';
|
||||
?>
|
||||
|
||||
<!-- Calendar -->
|
||||
<div id="calendar">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- tab page 4 ------------------------------------------------------------ -->
|
||||
<div class="tab-pane fade table-responsive" id="panEvents">
|
||||
|
||||
<!-- Hide Connections -->
|
||||
<div class="text-center">
|
||||
<label>
|
||||
<input class="checkbox blue hidden" id="chkHideConnectionEvents" type="checkbox" checked>
|
||||
<?= lang('DevDetail_Events_CheckBox');?>
|
||||
</label>
|
||||
</div>
|
||||
<?php
|
||||
// Include the other page
|
||||
include 'deviceDetailsEvents.php';
|
||||
?>
|
||||
|
||||
|
||||
<!-- Datatable Events -->
|
||||
<table id="tableEvents" class="table table-bordered table-hover table-striped ">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= lang("DevDetail_Tab_EventsTableDate");?></th>
|
||||
<th><?= lang("DevDetail_Tab_EventsTableEvent");?></th>
|
||||
<th><?= lang("DevDetail_Tab_EventsTableIP");?></th>
|
||||
<th><?= lang("DevDetail_Tab_EventsTableInfo");?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- tab page 7 ------------------------------------------------------------ -->
|
||||
@@ -179,25 +168,6 @@
|
||||
require 'php/templates/footer.php';
|
||||
?>
|
||||
|
||||
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
<!-- iCkeck -->
|
||||
<link rel="stylesheet" href="lib/iCheck/all.css">
|
||||
<script src="lib/iCheck/icheck.min.js"></script>
|
||||
|
||||
<!-- Datatable -->
|
||||
<link rel="stylesheet" href="lib/datatables.net-bs/css/dataTables.bootstrap.min.css">
|
||||
<script src="lib/datatables.net/js/jquery.dataTables.min.js"></script>
|
||||
<script src="lib/datatables.net-bs/js/dataTables.bootstrap.min.js"></script>
|
||||
|
||||
<!-- fullCalendar -->
|
||||
<link rel="stylesheet" href="lib/fullcalendar/fullcalendar.min.css">
|
||||
<link rel="stylesheet" href="lib/fullcalendar/fullcalendar.print.min.css" media="print">
|
||||
<script src="lib/moment/moment.js"></script>
|
||||
<script src="lib/fullcalendar/fullcalendar.min.js"></script>
|
||||
<script src="lib/fullcalendar/locale-all.js"></script>
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
|
||||
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
|
||||
@@ -222,18 +192,10 @@ switch ($UI_THEME) {
|
||||
mac = getMac() // can also be rowID!! not only mac
|
||||
var devicesList = []; // this will contain a list the database row IDs of the devices ordered by the position displayed in the UI
|
||||
|
||||
|
||||
var pos = -1;
|
||||
var parPeriod = 'Front_Details_Period';
|
||||
var parSessionsRows = 'Front_Details_Sessions_Rows';
|
||||
var parEventsRows = 'Front_Details_Events_Rows';
|
||||
var parEventsHide = 'Front_Details_Events_Hide';
|
||||
var period = '1 month';
|
||||
|
||||
var tab = 'tabDetails'
|
||||
var sessionsRows = 10;
|
||||
var eventsRows = 10;
|
||||
var eventsHide = true;
|
||||
var skipRepeatedItems = ['0 h (notify all events)', '1 h', '8 h', '24 h', '168 h (one week)'];
|
||||
var selectedTab = 'tabDetails';
|
||||
var emptyArr = ['undefined', "", undefined, null];
|
||||
|
||||
@@ -272,22 +234,15 @@ function main () {
|
||||
$('#chkHideConnectionEvents')[0].checked = eval(eventsHide == 'true');
|
||||
|
||||
// Initialize components with parameters
|
||||
initializeTabs();
|
||||
initializeDatatables();
|
||||
initializeCalendar();
|
||||
|
||||
|
||||
// query data
|
||||
|
||||
getSessionsPresenceEvents();
|
||||
|
||||
// Force re-render calendar on tab change
|
||||
// (bugfix for render error at left panel)
|
||||
$(document).on('shown.bs.tab', 'a[data-toggle="tab"]', function (nav) {
|
||||
if ($(nav.target).attr('href') == '#panPresence') {
|
||||
$('#calendar').fullCalendar('rerenderEvents');
|
||||
}
|
||||
// Init tabs once DOM ready
|
||||
$( document ).ready(function() {
|
||||
initializeTabs();
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
// Events tab toggle conenction events
|
||||
$('input').on('ifToggled', function(event){
|
||||
@@ -308,205 +263,11 @@ function main () {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
function initializeDatatables () {
|
||||
// Sessions datatable
|
||||
$('#tableSessions').DataTable({
|
||||
'paging' : true,
|
||||
'lengthChange': true,
|
||||
'lengthMenu' : [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, 'All']],
|
||||
'searching' : true,
|
||||
'ordering' : true,
|
||||
'info' : true,
|
||||
'autoWidth' : false,
|
||||
'order' : [[0,'desc'], [1,'desc']],
|
||||
|
||||
// Parameters
|
||||
'pageLength' : sessionsRows,
|
||||
|
||||
'columnDefs' : [
|
||||
{visible: false, targets: [0]},
|
||||
|
||||
// Replace HTML codes
|
||||
{targets: [1,2,3,5],
|
||||
'createdCell': function (td, cellData, rowData, row, col) {
|
||||
$(td).html (translateHTMLcodes (cellData));
|
||||
} }
|
||||
],
|
||||
|
||||
// Processing
|
||||
'processing' : true,
|
||||
'language' : {
|
||||
processing: '<table><td width="130px" align="middle"><?= lang("DevDetail_Loading");?></td>'+
|
||||
'<td><i class="ion ion-ios-loop-strong fa-spin fa-2x fa-fw">'+
|
||||
'</td></table>',
|
||||
emptyTable: 'No data',
|
||||
"lengthMenu": "<?= lang('Events_Tablelenght');?>",
|
||||
"search": "<?= lang('Events_Searchbox');?>: ",
|
||||
"paginate": {
|
||||
"next": "<?= lang('Events_Table_nav_next');?>",
|
||||
"previous": "<?= lang('Events_Table_nav_prev');?>"
|
||||
},
|
||||
"info": "<?= lang('Events_Table_info');?>",
|
||||
}
|
||||
});
|
||||
|
||||
// Events datatable
|
||||
$('#tableEvents').DataTable({
|
||||
'paging' : true,
|
||||
'lengthChange': true,
|
||||
'lengthMenu' : [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, 'All']],
|
||||
'searching' : true,
|
||||
'ordering' : true,
|
||||
'info' : true,
|
||||
'autoWidth' : false,
|
||||
'order' : [[0,'desc']],
|
||||
|
||||
// Parameters
|
||||
'pageLength' : eventsRows,
|
||||
|
||||
'columnDefs' : [
|
||||
// Replace HTML codes
|
||||
{targets: [0],
|
||||
'createdCell': function (td, cellData, rowData, row, col) {
|
||||
$(td).html (translateHTMLcodes (cellData));
|
||||
} }
|
||||
],
|
||||
|
||||
// Processing
|
||||
'processing' : true,
|
||||
'language' : {
|
||||
processing: '<table><td width="130px" align="middle"><?= lang("DevDetail_Loading");?></td>'+
|
||||
'<td><i class="ion ion-ios-loop-strong fa-spin fa-2x fa-fw">'+
|
||||
'</td></table>',
|
||||
emptyTable: 'No data',
|
||||
"lengthMenu": "<?= lang('Events_Tablelenght');?>",
|
||||
"search": "<?= lang('Events_Searchbox');?>: ",
|
||||
"paginate": {
|
||||
"next": "<?= lang('Events_Table_nav_next');?>",
|
||||
"previous": "<?= lang('Events_Table_nav_prev');?>"
|
||||
},
|
||||
"info": "<?= lang('Events_Table_info');?>",
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
function initializeCalendar () {
|
||||
$('#calendar').fullCalendar({
|
||||
editable : false,
|
||||
droppable : false,
|
||||
defaultView : 'agendaMonth',
|
||||
|
||||
height : 'auto',
|
||||
firstDay : 1,
|
||||
allDaySlot : false,
|
||||
slotDuration : '02:00:00',
|
||||
slotLabelInterval : '04:00:00',
|
||||
slotLabelFormat : 'H:mm',
|
||||
timeFormat : 'H:mm',
|
||||
locale : '<?= lang('Presence_CalHead_lang');?>',
|
||||
header: {
|
||||
left : 'prev,next today',
|
||||
center : 'title',
|
||||
right : 'agendaYear,agendaMonth,agendaWeek'
|
||||
},
|
||||
|
||||
views: {
|
||||
agendaYear: {
|
||||
type : 'agenda',
|
||||
duration : { year: 1 },
|
||||
buttonText : '<?= lang('Presence_CalHead_year');?>',
|
||||
columnHeaderFormat : ''
|
||||
},
|
||||
|
||||
agendaMonth: {
|
||||
type : 'agenda',
|
||||
duration : { month: 1 },
|
||||
buttonText : '<?= lang('Presence_CalHead_month');?>',
|
||||
columnHeaderFormat : 'D'
|
||||
},
|
||||
agendaWeek: {
|
||||
buttonText : '<?= lang('Presence_CalHead_week');?>',
|
||||
},
|
||||
agendaDay: {
|
||||
type : 'agenda',
|
||||
duration : { day: 1 },
|
||||
buttonText : '<?= lang('Presence_CalHead_day');?>',
|
||||
slotLabelFormat : 'H',
|
||||
slotDuration : '01:00:00'
|
||||
}
|
||||
},
|
||||
|
||||
viewRender: function(view) {
|
||||
if (view.name === 'agendaYear') {
|
||||
var listHeader = $('.fc-day-header')[0];
|
||||
var listContent = $('.fc-widget-content')[0];
|
||||
|
||||
for (i=0; i < listHeader.length-2 ; i++) {
|
||||
listHeader[i].style.borderColor = 'transparent';
|
||||
listContent[i+2].style.borderColor = 'transparent';
|
||||
|
||||
if (listHeader[i].innerHTML != '<span></span>') {
|
||||
if (i==0) {
|
||||
listHeader[i].style.borderLeftColor = '#808080';
|
||||
} else {
|
||||
listHeader[i-1].style.borderRightColor = '#808080';
|
||||
listContent[i+1].style.borderRightColor = '#808080';
|
||||
}
|
||||
listHeader[i].style.paddingLeft = '10px';
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
columnHeaderText: function(mom) {
|
||||
switch ($('#calendar').fullCalendar('getView').name) {
|
||||
case 'agendaYear':
|
||||
if (mom.date() == 1) {
|
||||
return mom.format('MMM');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
break;
|
||||
case 'agendaMonth':
|
||||
return mom.date();
|
||||
break;
|
||||
case 'agendaWeek':
|
||||
return mom.format ('ddd D');
|
||||
break;
|
||||
default:
|
||||
return mom.date();
|
||||
}
|
||||
},
|
||||
|
||||
eventRender: function (event, element) {
|
||||
$(element).tooltip({container: 'body', placement: 'bottom',
|
||||
title: event.tooltip});
|
||||
// element.attr ('title', event.tooltip); // Alternative tooltip
|
||||
},
|
||||
|
||||
loading: function( isLoading, view ) {
|
||||
if (isLoading) {
|
||||
showSpinner()
|
||||
} else {
|
||||
// setTimeout(() => {
|
||||
// updateIconPreview($('#txtIcon'))
|
||||
// }, 500);
|
||||
|
||||
hideSpinner()
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
function periodChanged () {
|
||||
getSessionsPresenceEvents();
|
||||
loadSessionsData();
|
||||
loadPresenceData();
|
||||
loadEventsData();
|
||||
}
|
||||
|
||||
|
||||
@@ -531,6 +292,8 @@ function performSwitch(direction)
|
||||
{
|
||||
somethingChanged = false;
|
||||
|
||||
devicesList = getDevicesList()
|
||||
|
||||
// Update the global position in the devices list variable 'pos'
|
||||
if (direction === "next") {
|
||||
if (pos < devicesList.length - 1) {
|
||||
@@ -545,8 +308,6 @@ function performSwitch(direction)
|
||||
// Get the new MAC address from devicesList
|
||||
mac = devicesList[pos].devMac.toString();
|
||||
|
||||
console.log(mac);
|
||||
|
||||
setCache("naxDeviceDetailsMac", mac);
|
||||
|
||||
// Update the query string with the new MAC and refresh the page
|
||||
@@ -556,33 +317,6 @@ function performSwitch(direction)
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
function getSessionsPresenceEvents () {
|
||||
// Check MAC in url
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
mac = urlParams.get ('mac');
|
||||
// Define Sessions datasource and query dada
|
||||
$('#tableSessions').DataTable().ajax.url('php/server/events.php?action=getDeviceSessions&mac=' + mac +'&period='+ period).load();
|
||||
|
||||
// Define Presence datasource and query data
|
||||
$('#calendar').fullCalendar('removeEventSources');
|
||||
$('#calendar').fullCalendar('addEventSource',
|
||||
{ url: 'php/server/events.php?action=getDevicePresence&mac=' + mac});
|
||||
|
||||
// Query events
|
||||
getDeviceEvents();
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
function getDeviceEvents () {
|
||||
// Define Events datasource and query dada
|
||||
hideConnections = $('#chkHideConnectionEvents')[0].checked;
|
||||
$('#tableEvents').DataTable().ajax.url(
|
||||
'php/server/events.php?action=getDeviceEvents&mac=' + mac +'&period='+ period +'&hideConnections='+ hideConnections).load();
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Activate save & restore on any value change
|
||||
$(document).on('input', 'input:text', function() {
|
||||
@@ -602,7 +336,10 @@ function initializeTabs () {
|
||||
{
|
||||
selectedTab = getCache(key);
|
||||
}
|
||||
|
||||
$('.nav-tabs a[id='+ selectedTab +']').tab('show');
|
||||
// $('.nav-tabs a[id='+ selectedTab +']').parent().click();
|
||||
// $('.nav-tabs a[id="tabPlugins"]').tab('show');
|
||||
|
||||
// When changed save new current tab
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
@@ -621,62 +358,10 @@ function initializeTabs () {
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
function initTable(tableId, mac){
|
||||
|
||||
// clear table
|
||||
$("#"+tableId+" tbody").remove();
|
||||
|
||||
// Events datatable
|
||||
$('#'+tableId).DataTable({
|
||||
'paging' : true,
|
||||
'lengthChange': true,
|
||||
'lengthMenu' : [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, 'All']],
|
||||
'searching' : true,
|
||||
'ordering' : true,
|
||||
'info' : true,
|
||||
'autoWidth' : false,
|
||||
'order' : [[0,'desc']],
|
||||
|
||||
// Parameters
|
||||
'pageLength' : 50,
|
||||
|
||||
'columnDefs' : [
|
||||
// Replace HTML codes
|
||||
{targets: [0],
|
||||
'createdCell': function (td, cellData, rowData, row, col) {
|
||||
$(td).html (translateHTMLcodes (cellData));
|
||||
} }
|
||||
],
|
||||
|
||||
// Processing
|
||||
'processing' : true,
|
||||
'language' : {
|
||||
processing: '<table><td width="130px" align="middle"><?= lang("DevDetail_Loading");?></td>'+
|
||||
'<td><i class="ion ion-ios-loop-strong fa-spin fa-2x fa-fw">'+
|
||||
'</td></table>',
|
||||
emptyTable: 'No data',
|
||||
"lengthMenu": "<?= lang('Events_Tablelenght');?>",
|
||||
"search": "<?= lang('Events_Searchbox');?>: ",
|
||||
"paginate": {
|
||||
"next": "<?= lang('Events_Table_nav_next');?>",
|
||||
"previous": "<?= lang('Events_Table_nav_prev');?>"
|
||||
},
|
||||
"info": "<?= lang('Events_Table_info');?>",
|
||||
}
|
||||
});
|
||||
|
||||
$("#"+tableId).attr("data-mac", mac)
|
||||
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Render the small boxes on top
|
||||
async function renderSmallBoxes() {
|
||||
|
||||
|
||||
|
||||
try {
|
||||
// Show loading dialog
|
||||
showSpinner();
|
||||
|
||||
@@ -14,26 +14,21 @@
|
||||
<!-- Buttons -->
|
||||
<div class="col-xs-12">
|
||||
<div class="pull-right">
|
||||
<button type="button"
|
||||
class="btn btn-default pa-btn pa-btn-delete"
|
||||
style="margin-left:0px;"
|
||||
id="btnDeleteEvents"
|
||||
onclick="askDeleteDeviceEvents()">
|
||||
<?= lang('DevDetail_button_DeleteEvents');?>
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn btn-default pa-btn pa-btn-delete"
|
||||
style="margin-left:0px;"
|
||||
id="btnDelete"
|
||||
onclick="askDeleteDevice()">
|
||||
<?= lang('DevDetail_button_Delete');?>
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
<?= lang('DevDetail_button_Delete');?>
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn btn-primary pa-btn"
|
||||
style="margin-left:6px; "
|
||||
id="btnSave"
|
||||
onclick="setDeviceData()" >
|
||||
<?= lang('DevDetail_button_Save');?>
|
||||
<i class="fas fa-save"></i>
|
||||
<?= lang('DevDetail_button_Save');?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -68,47 +63,70 @@
|
||||
|
||||
// some race condition, need to implement delay
|
||||
setTimeout(() => {
|
||||
$.get('api/table_settings.json?nocache=' + Date.now(), function(res) {
|
||||
$.get('/php/server/query_json.php', { file: 'table_settings.json', nocache: Date.now() }, function(res) {
|
||||
|
||||
settingsData = res["data"];
|
||||
|
||||
// columns to hide
|
||||
hiddenFields = ["NEWDEV_devScan", "NEWDEV_devPresentLastScan" ]
|
||||
// columns to disable - conditional depending if a new dummy device is created
|
||||
disabledFields = mac == "new" ? ["NEWDEV_devLastNotification", "NEWDEV_devFirstConnection", "NEWDEV_devLastConnection"] : ["NEWDEV_devLastNotification", "NEWDEV_devFirstConnection", "NEWDEV_devLastConnection", "NEWDEV_devMac", "NEWDEV_devLastIP" ];
|
||||
disabledFields = mac == "new" ? ["NEWDEV_devLastNotification", "NEWDEV_devFirstConnection", "NEWDEV_devLastConnection"] : ["NEWDEV_devLastNotification", "NEWDEV_devFirstConnection", "NEWDEV_devLastConnection", "NEWDEV_devMac", "NEWDEV_devLastIP", "NEWDEV_devSyncHubNode" ];
|
||||
|
||||
// Grouping of fields into categories with associated documentation links
|
||||
const fieldGroups = {
|
||||
// Group for device main information
|
||||
DevDetail_MainInfo_Title: {
|
||||
data: ["devMac", "devLastIP", "devName", "devOwner", "devType", "devVendor", "devGroup", "devIcon", "devLocation", "devComments"],
|
||||
docs: "https://github.com/jokob-sk/NetAlertX/blob/main/docs/NOTIFICATIONS.md",
|
||||
iconClass: "fa fa-pencil"
|
||||
docs: "https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICE_MANAGEMENT.md",
|
||||
iconClass: "fa fa-pencil",
|
||||
inputGroupClasses: "field-group col-lg-4 col-sm-6 col-xs-12",
|
||||
labelClasses: "col-sm-4 col-xs-12 control-label",
|
||||
inputClasses: "col-sm-8 col-xs-12 input-group"
|
||||
},
|
||||
// Group for session information
|
||||
DevDetail_SessionInfo_Title: {
|
||||
data: ["devStatus", "devLastConnection", "devFirstConnection"],
|
||||
docs: "https://github.com/jokob-sk/NetAlertX/blob/main/docs/NOTIFICATIONS.md",
|
||||
iconClass: "fa fa-calendar"
|
||||
docs: "https://github.com/jokob-sk/NetAlertX/blob/main/docs/SESSION_INFO.md",
|
||||
iconClass: "fa fa-calendar",
|
||||
inputGroupClasses: "field-group col-lg-4 col-sm-6 col-xs-12",
|
||||
labelClasses: "col-sm-4 col-xs-12 control-label",
|
||||
inputClasses: "col-sm-8 col-xs-12 input-group"
|
||||
},
|
||||
// Group for event and alert settings
|
||||
DevDetail_EveandAl_Title: {
|
||||
data: ["devAlertEvents", "devAlertDown", "devSkipRepeated"],
|
||||
docs: "https://github.com/jokob-sk/NetAlertX/blob/main/docs/NOTIFICATIONS.md",
|
||||
iconClass: "fa fa-bell"
|
||||
iconClass: "fa fa-bell",
|
||||
inputGroupClasses: "field-group col-lg-4 col-sm-6 col-xs-12",
|
||||
labelClasses: "col-sm-4 col-xs-12 control-label",
|
||||
inputClasses: "col-sm-8 col-xs-12 input-group"
|
||||
},
|
||||
// Group for network details
|
||||
DevDetail_MainInfo_Network_Title: {
|
||||
data: ["devParentMAC", "devParentPort", "devSSID", "devSite"],
|
||||
data: ["devParentMAC", "devParentPort", "devSSID", "devSite", "devSyncHubNode"],
|
||||
docs: "https://github.com/jokob-sk/NetAlertX/blob/main/docs/NETWORK_TREE.md",
|
||||
iconClass: "fa fa-network-wired"
|
||||
iconClass: "fa fa-network-wired",
|
||||
inputGroupClasses: "field-group col-lg-4 col-sm-6 col-xs-12",
|
||||
labelClasses: "col-sm-4 col-xs-12 control-label",
|
||||
inputClasses: "col-sm-8 col-xs-12 input-group"
|
||||
},
|
||||
// Group for other fields like static IP, archived status, etc.
|
||||
DevDetail_DisplayFields_Title: {
|
||||
data: ["devStaticIP", "devIsNew", "devFavorite", "devIsArchived"],
|
||||
docs: "https://github.com/jokob-sk/NetAlertX/blob/main/docs/NOTIFICATIONS.md",
|
||||
iconClass: "fa fa-list-check"
|
||||
|
||||
docs: "https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICE_DISPLAY_SETTINGS.md",
|
||||
iconClass: "fa fa-list-check",
|
||||
inputGroupClasses: "field-group col-lg-4 col-sm-6 col-xs-12",
|
||||
labelClasses: "col-sm-4 col-xs-12 control-label",
|
||||
inputClasses: "col-sm-8 col-xs-12 input-group"
|
||||
},
|
||||
// Group for Custom properties.
|
||||
DevDetail_CustomProperties_Title: {
|
||||
data: ["devCustomProps"],
|
||||
docs: "https://github.com/jokob-sk/NetAlertX/blob/main/docs/CUSTOM_PROPERTIES.md",
|
||||
iconClass: "fa fa-list",
|
||||
inputGroupClasses: "field-group col-lg-12 col-sm-12 col-xs-12",
|
||||
labelClasses: "col-sm-12 col-xs-12 control-label",
|
||||
inputClasses: "col-sm-12 col-xs-12 input-group"
|
||||
}
|
||||
};
|
||||
|
||||
@@ -126,7 +144,7 @@
|
||||
|
||||
// Loop over each field group to generate sections for each category
|
||||
Object.entries(fieldGroups).forEach(([groupName, obj]) => {
|
||||
const groupDiv = $('<div>').addClass('field-group col-lg-4 col-sm-6 col-xs-12'); // Create a div for each group with responsive Bootstrap classes
|
||||
const groupDiv = $('<div>').addClass(obj.inputGroupClasses); // Create a div for each group with responsive Bootstrap classes
|
||||
|
||||
// Add group title and documentation link
|
||||
groupDiv.append(`<h5><i class="${obj.iconClass}"></i> ${getString(groupName)}
|
||||
@@ -137,7 +155,7 @@
|
||||
</span>
|
||||
</h5>
|
||||
<hr>
|
||||
`);
|
||||
`);
|
||||
|
||||
// Filter relevant settings for the current group
|
||||
const groupSettings = settings.filter(set => obj.data.includes(set.setKey.replace('NEWDEV_', '')));
|
||||
@@ -156,7 +174,7 @@
|
||||
// Additional form elements like the random MAC address button for devMac
|
||||
let inlineControl = "";
|
||||
// handle rendom mac
|
||||
if (setting.setKey == "NEWDEV_devMac" && deviceData["devRandomMAC"] == true) {
|
||||
if (setting.setKey == "NEWDEV_devMac" && deviceData["devIsRandomMAC"] == true) {
|
||||
inlineControl += `<span class="input-group-addon pointer"
|
||||
title="${getString("RandomMAC_hover")}">
|
||||
<a href="https://github.com/jokob-sk/NetAlertX/blob/main/docs/RANDOM_MAC.md" target="_blank">
|
||||
@@ -193,15 +211,15 @@
|
||||
|
||||
// Generate the input field HTML
|
||||
const inputFormHtml = `<div class="form-group col-xs-12">
|
||||
<label class="col-sm-4 col-xs-12 control-label"> ${setting.setName}
|
||||
<label id="${setting.setKey}_label" class="${obj.labelClasses}" > ${setting.setName}
|
||||
<i my-set-key="${setting.setKey}"
|
||||
title="${getString("Settings_Show_Description")}"
|
||||
class="fa fa-circle-info pointer helpIconSmallTopRight"
|
||||
onclick="showDescriptionPopup(this)">
|
||||
</i>
|
||||
</label>
|
||||
<div class="col-sm-8 col-xs-12 input-group">
|
||||
${generateFormHtml(setting, fieldData.toString())}
|
||||
<div class="${obj.inputClasses}">
|
||||
${generateFormHtml(settingsData, setting, fieldData.toString(), null, null)}
|
||||
${inlineControl}
|
||||
</div>
|
||||
</div>`;
|
||||
@@ -214,20 +232,24 @@
|
||||
});
|
||||
|
||||
|
||||
// wait until everything is initialized to update icon
|
||||
updateIconPreview();
|
||||
// wait until everything is initialized to update icons
|
||||
updateAllIconPreviews();
|
||||
|
||||
// update readonly fields
|
||||
handleReadOnly(settingsData, disabledFields);
|
||||
|
||||
// Page title - Name
|
||||
if (mac == "new") {
|
||||
$('#pageTitle').html(getString("Gen_AddDevice"));
|
||||
$('#pageTitle').html(`<i title="${getString("Gen_create_new_device")}" class="fa fa-square-plus"></i> ` + getString("Gen_create_new_device"));
|
||||
$('#devicePageInfoPlc .inner').html(`<i class="fa fa-circle-info"></i> ` + getString("Gen_create_new_device_info"));
|
||||
$('#devicePageInfoPlc').show();
|
||||
} else if (deviceData['devOwner'] == null || deviceData['devOwner'] == '' ||
|
||||
(deviceData['devName'].toString()).indexOf(deviceData['devOwner']) != -1) {
|
||||
$('#pageTitle').html(deviceData['devName']);
|
||||
$('#devicePageInfoPlc').hide();
|
||||
} else {
|
||||
$('#pageTitle').html(deviceData['devName'] + ' (' + deviceData['devOwner'] + ')');
|
||||
$('#devicePageInfoPlc').hide();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -313,64 +335,7 @@
|
||||
showModalOK("Info", getString($(e).attr("my-set-key") + '_description'))
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
function askDeleteDeviceEvents () {
|
||||
// Check MAC
|
||||
if (mac == '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ask delete device Events
|
||||
showModalWarning ('<?= lang('DevDetail_button_DeleteEvents');?>', '<?= lang('DevDetail_button_DeleteEvents_Warning');?>',
|
||||
'<?= lang('Gen_Cancel');?>', '<?= lang('Gen_Delete');?>', 'deleteDeviceEvents');
|
||||
}
|
||||
|
||||
function deleteDeviceEvents () {
|
||||
// Check MAC
|
||||
if (mac == '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete device events
|
||||
$.get('php/server/devices.php?action=deleteDeviceEvents&mac='+ mac, function(msg) {
|
||||
showMessage (msg);
|
||||
});
|
||||
|
||||
// Deactivate controls
|
||||
$('#panDetails :input').attr('disabled', true);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
function askDeleteDevice () {
|
||||
// Check MAC
|
||||
if (mac == '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ask delete device
|
||||
showModalWarning ('Delete Device', 'Are you sure you want to delete this device?<br>(maybe you prefer to archive it)',
|
||||
'<?= lang('Gen_Cancel');?>', '<?= lang('Gen_Delete');?>', 'deleteDevice');
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
function deleteDevice () {
|
||||
// Check MAC
|
||||
if (mac == '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete device
|
||||
$.get('php/server/devices.php?action=deleteDevice&mac='+ mac, function(msg) {
|
||||
showMessage (msg);
|
||||
});
|
||||
|
||||
// Deactivate controls
|
||||
$('#panDetails :input').attr('disabled', true);
|
||||
|
||||
// refresh API
|
||||
updateApi("devices,appevents")
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
function setDeviceData(direction = '', refreshCallback = '') {
|
||||
@@ -392,51 +357,53 @@
|
||||
|
||||
showSpinner();
|
||||
|
||||
// update data to server
|
||||
$.get('php/server/devices.php?action=setDeviceData&mac='+ $('#NEWDEV_devMac').val()
|
||||
+ '&name=' + encodeURIComponent($('#NEWDEV_devName').val().replace(/'/g, ""))
|
||||
+ '&owner=' + encodeURIComponent($('#NEWDEV_devOwner').val().replace(/'/g, ""))
|
||||
+ '&type=' + $('#NEWDEV_devType').val()
|
||||
+ '&vendor=' + encodeURIComponent($('#NEWDEV_devVendor').val().replace(/'/g, ""))
|
||||
+ '&icon=' + encodeURIComponent($('#NEWDEV_devIcon').val())
|
||||
+ '&favorite=' + ($('#NEWDEV_devFavorite')[0].checked * 1)
|
||||
+ '&group=' + encodeURIComponent($('#NEWDEV_devGroup').val())
|
||||
+ '&location=' + encodeURIComponent($('#NEWDEV_devLocation').val())
|
||||
+ '&comments=' + encodeURIComponent(encodeSpecialChars($('#NEWDEV_devComments').val()))
|
||||
+ '&networknode=' + $('#NEWDEV_devParentMAC').val()
|
||||
+ '&networknodeport=' + $('#NEWDEV_devParentPort').val()
|
||||
+ '&ssid=' + $('#NEWDEV_devSSID').val()
|
||||
+ '&networksite=' + $('#NEWDEV_devSite').val()
|
||||
+ '&staticIP=' + ($('#NEWDEV_devStaticIP')[0].checked * 1)
|
||||
+ '&scancycle=' + "1"
|
||||
+ '&alertevents=' + ($('#NEWDEV_devAlertEvents')[0].checked * 1)
|
||||
+ '&alertdown=' + ($('#NEWDEV_devAlertDown')[0].checked * 1)
|
||||
+ '&skiprepeated=' + $('#NEWDEV_devSkipRepeated').val().split(' ')[0]
|
||||
+ '&newdevice=' + ($('#NEWDEV_devIsNew')[0].checked * 1)
|
||||
+ '&archived=' + ($('#NEWDEV_devIsArchived')[0].checked * 1)
|
||||
+ '&devFirstConnection=' + ($('#NEWDEV_devFirstConnection').val())
|
||||
+ '&devLastConnection=' + ($('#NEWDEV_devLastConnection').val())
|
||||
+ '&ip=' + ($('#NEWDEV_devLastIP').val())
|
||||
+ '&createNew=' + createNew
|
||||
, function(msg) {
|
||||
|
||||
showMessage (msg);
|
||||
// Update data to server using POST
|
||||
$.post('php/server/devices.php?action=setDeviceData', {
|
||||
mac: $('#NEWDEV_devMac').val(),
|
||||
name: encodeURIComponent($('#NEWDEV_devName').val().replace(/'/g, "")),
|
||||
owner: encodeURIComponent($('#NEWDEV_devOwner').val().replace(/'/g, "")),
|
||||
type: $('#NEWDEV_devType').val().replace(/'/g, ""),
|
||||
vendor: encodeURIComponent($('#NEWDEV_devVendor').val().replace(/'/g, "")),
|
||||
icon: encodeURIComponent($('#NEWDEV_devIcon').val()),
|
||||
favorite: ($('#NEWDEV_devFavorite')[0].checked * 1),
|
||||
group: encodeURIComponent($('#NEWDEV_devGroup').val().replace(/'/g, "")),
|
||||
location: encodeURIComponent($('#NEWDEV_devLocation').val().replace(/'/g, "")),
|
||||
comments: encodeURIComponent(encodeSpecialChars($('#NEWDEV_devComments').val())),
|
||||
networknode: $('#NEWDEV_devParentMAC').val(),
|
||||
networknodeport: $('#NEWDEV_devParentPort').val(),
|
||||
ssid: $('#NEWDEV_devSSID').val(),
|
||||
networksite: $('#NEWDEV_devSite').val(),
|
||||
staticIP: ($('#NEWDEV_devStaticIP')[0].checked * 1),
|
||||
scancycle: "1",
|
||||
alertevents: ($('#NEWDEV_devAlertEvents')[0].checked * 1),
|
||||
alertdown: ($('#NEWDEV_devAlertDown')[0].checked * 1),
|
||||
skiprepeated: $('#NEWDEV_devSkipRepeated').val().split(' ')[0],
|
||||
newdevice: ($('#NEWDEV_devIsNew')[0].checked * 1),
|
||||
archived: ($('#NEWDEV_devIsArchived')[0].checked * 1),
|
||||
devFirstConnection: ($('#NEWDEV_devFirstConnection').val()),
|
||||
devLastConnection: ($('#NEWDEV_devLastConnection').val()),
|
||||
devCustomProps: btoa(JSON.stringify(collectTableData("#NEWDEV_devCustomProps_table"))),
|
||||
ip: ($('#NEWDEV_devLastIP').val()),
|
||||
createNew: createNew
|
||||
}, function(msg) {
|
||||
showMessage(msg);
|
||||
|
||||
// Remove navigation prompt "Are you sure you want to leave..."
|
||||
window.onbeforeunload = null;
|
||||
somethingChanged = false;
|
||||
somethingChanged = false;
|
||||
|
||||
// refresh API
|
||||
updateApi("devices,appevents")
|
||||
updateApi("devices,appevents");
|
||||
|
||||
// Callback fuction
|
||||
// Callback function
|
||||
if (typeof refreshCallback == 'function') {
|
||||
refreshCallback(direction);
|
||||
refreshCallback(direction);
|
||||
}
|
||||
|
||||
// everything loaded
|
||||
hideSpinner();
|
||||
// Everything loaded
|
||||
hideSpinner();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
94
front/deviceDetailsEvents.php
Executable file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
//------------------------------------------------------------------------------
|
||||
// check if authenticated
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
|
||||
?>
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
|
||||
|
||||
<!-- Hide Connections -->
|
||||
<div class="text-center">
|
||||
<label>
|
||||
<input class="checkbox blue hidden" id="chkHideConnectionEvents" type="checkbox" checked>
|
||||
<?= lang('DevDetail_Events_CheckBox');?>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Datatable Events -->
|
||||
<table id="tableEvents" class="table table-bordered table-hover table-striped ">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= lang("DevDetail_Tab_EventsTableDate");?></th>
|
||||
<th><?= lang("DevDetail_Tab_EventsTableEvent");?></th>
|
||||
<th><?= lang("DevDetail_Tab_EventsTableIP");?></th>
|
||||
<th><?= lang("DevDetail_Tab_EventsTableInfo");?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
var eventsRows = 10;
|
||||
var eventsHide = true;
|
||||
var parEventsRows = 'Front_Details_Events_Rows';
|
||||
var parEventsHide = 'Front_Details_Events_Hide';
|
||||
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
function loadEventsData() {
|
||||
// Define Events datasource and query dada
|
||||
hideConnections = $('#chkHideConnectionEvents')[0].checked;
|
||||
$('#tableEvents').DataTable().ajax.url('php/server/events.php?action=getDeviceEvents&mac=' + mac +'&period='+ period +'&hideConnections='+ hideConnections).load();
|
||||
}
|
||||
|
||||
function initializeSessionsDatatable () {
|
||||
|
||||
// Events datatable
|
||||
$('#tableEvents').DataTable({
|
||||
'paging' : true,
|
||||
'lengthChange': true,
|
||||
'lengthMenu' : [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, 'All']],
|
||||
'searching' : true,
|
||||
'ordering' : true,
|
||||
'info' : true,
|
||||
'autoWidth' : false,
|
||||
'order' : [[0,'desc']],
|
||||
|
||||
// Parameters
|
||||
'pageLength' : eventsRows,
|
||||
|
||||
'columnDefs' : [
|
||||
// Replace HTML codes
|
||||
{targets: [0],
|
||||
'createdCell': function (td, cellData, rowData, row, col) {
|
||||
$(td).html (translateHTMLcodes (cellData));
|
||||
} }
|
||||
],
|
||||
|
||||
// Processing
|
||||
'processing' : true,
|
||||
'language' : {
|
||||
processing: '<table><td width="130px" align="middle"><?= lang("DevDetail_Loading");?></td>'+
|
||||
'<td><i class="ion ion-ios-loop-strong fa-spin fa-2x fa-fw">'+
|
||||
'</td></table>',
|
||||
emptyTable: 'No data',
|
||||
"lengthMenu": "<?= lang('Events_Tablelenght');?>",
|
||||
"search": "<?= lang('Events_Searchbox');?>: ",
|
||||
"paginate": {
|
||||
"next": "<?= lang('Events_Table_nav_next');?>",
|
||||
"previous": "<?= lang('Events_Table_nav_prev');?>"
|
||||
},
|
||||
"info": "<?= lang('Events_Table_info');?>",
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
initializeSessionsDatatable();
|
||||
loadEventsData();
|
||||
|
||||
</script>
|
||||
240
front/deviceDetailsPresence.php
Executable file
@@ -0,0 +1,240 @@
|
||||
<?php
|
||||
//------------------------------------------------------------------------------
|
||||
// check if authenticated
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
|
||||
?>
|
||||
|
||||
<!-- fullCalendar -->
|
||||
|
||||
<link rel="stylesheet" href="lib/fullcalendar/fullcalendar.min.css">
|
||||
<link rel="stylesheet" href="lib/fullcalendar/fullcalendar.print.min.css" media="print">
|
||||
<script src="lib/moment/moment.js"></script>
|
||||
<script src="lib/fullcalendar/fullcalendar.min.js"></script>
|
||||
<script src="lib/fullcalendar/locale-all.js"></script>
|
||||
|
||||
<!-- fullCalendar Scheduler -->
|
||||
<link href="lib/fullcalendar-scheduler/scheduler.min.css" rel="stylesheet">
|
||||
<script src="lib/fullcalendar-scheduler/scheduler.min.js"></script>
|
||||
|
||||
|
||||
<!-- Calendar -->
|
||||
<div id="calendar">
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
initializeCalendar();
|
||||
loadPresenceData();
|
||||
|
||||
// Force re-render calendar on tab change
|
||||
// (bugfix for render error at left panel)
|
||||
$(document).on('shown.bs.tab', 'a[data-toggle="tab"]', function (nav) {
|
||||
if ($(nav.target).attr('href') == '#panPresence') {
|
||||
$('#calendar').fullCalendar('rerenderEvents');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// ---------------------------------------
|
||||
// query data
|
||||
function loadPresenceData()
|
||||
{
|
||||
// Define Presence datasource and query data
|
||||
$('#calendar').fullCalendar('removeEventSources');
|
||||
$('#calendar').fullCalendar('addEventSource',
|
||||
{ url: 'php/server/events.php?action=getDevicePresence&mac=' + mac});
|
||||
}
|
||||
|
||||
// ---------------------------------------
|
||||
function initializeCalendar_() {
|
||||
|
||||
|
||||
var calendarEl = document.getElementById('calendar');
|
||||
|
||||
var calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
headerToolbar: {
|
||||
left: 'prev,next today',
|
||||
center: 'title',
|
||||
right: 'dayGridYear,dayGridMonth,timeGridWeek'
|
||||
},
|
||||
initialView: 'dayGridYear',
|
||||
initialDate: '2023-01-12',
|
||||
editable: true,
|
||||
selectable: true,
|
||||
dayMaxEvents: true, // allow "more" link when too many events
|
||||
// businessHours: true,
|
||||
// weekends: false,
|
||||
events: [
|
||||
{
|
||||
title: 'All Day Event',
|
||||
start: '2023-01-01'
|
||||
},
|
||||
{
|
||||
title: 'Long Event',
|
||||
start: '2023-01-07',
|
||||
end: '2023-01-10'
|
||||
},
|
||||
{
|
||||
groupId: 999,
|
||||
title: 'Repeating Event',
|
||||
start: '2023-01-09T16:00:00'
|
||||
},
|
||||
{
|
||||
groupId: 999,
|
||||
title: 'Repeating Event',
|
||||
start: '2023-01-16T16:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Conference',
|
||||
start: '2023-01-11',
|
||||
end: '2023-01-13'
|
||||
},
|
||||
{
|
||||
title: 'Meeting',
|
||||
start: '2023-01-12T10:30:00',
|
||||
end: '2023-01-12T12:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Lunch',
|
||||
start: '2023-01-12T12:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Meeting',
|
||||
start: '2023-01-12T14:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Happy Hour',
|
||||
start: '2023-01-12T17:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Dinner',
|
||||
start: '2023-01-12T20:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Birthday Party',
|
||||
start: '2023-01-13T07:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Click for Google',
|
||||
url: 'http://google.com/',
|
||||
start: '2023-01-28'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
calendar.render();
|
||||
|
||||
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
function initializeCalendar() {
|
||||
$('#calendar').fullCalendar({
|
||||
editable : false,
|
||||
droppable : false,
|
||||
defaultView : 'agendaMonth',
|
||||
schedulerLicenseKey: 'GPL-My-Project-Is-Open-Source',
|
||||
height : 'auto',
|
||||
firstDay : 1,
|
||||
allDaySlot : false,
|
||||
slotDuration : '02:00:00',
|
||||
slotLabelInterval : '04:00:00',
|
||||
slotLabelFormat : 'H:mm',
|
||||
timeFormat : 'H:mm',
|
||||
locale : '<?= lang('Presence_CalHead_lang');?>',
|
||||
header: {
|
||||
left : 'prev,next today',
|
||||
center : 'title',
|
||||
right : 'agendaYear,agendaMonth,agendaWeek'
|
||||
},
|
||||
|
||||
views: {
|
||||
agendaYear: {
|
||||
type : 'agenda',
|
||||
duration : { year: 1 },
|
||||
buttonText : '<?= lang('Presence_CalHead_year');?>',
|
||||
columnHeaderFormat : ''
|
||||
},
|
||||
|
||||
agendaMonth: {
|
||||
type : 'agenda',
|
||||
duration : { month: 1 },
|
||||
buttonText : '<?= lang('Presence_CalHead_month');?>',
|
||||
columnHeaderFormat : 'D'
|
||||
},
|
||||
agendaWeek: {
|
||||
buttonText : '<?= lang('Presence_CalHead_week');?>',
|
||||
},
|
||||
agendaDay: {
|
||||
type : 'agenda',
|
||||
duration : { day: 1 },
|
||||
buttonText : '<?= lang('Presence_CalHead_day');?>',
|
||||
slotLabelFormat : 'H',
|
||||
slotDuration : '01:00:00'
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
|
||||
viewRender: function(view) {
|
||||
if (view.name === 'agendaYear') {
|
||||
var listHeader = $('.fc-day-header')[0];
|
||||
var listContent = $('.fc-widget-content')[0];
|
||||
|
||||
for (i=0; i < listHeader.length-2 ; i++) {
|
||||
listHeader[i].style.borderColor = 'transparent';
|
||||
listContent[i+2].style.borderColor = 'transparent';
|
||||
|
||||
if (listHeader[i].innerHTML != '<span></span>') {
|
||||
if (i==0) {
|
||||
listHeader[i].style.borderLeftColor = '#808080';
|
||||
} else {
|
||||
listHeader[i-1].style.borderRightColor = '#808080';
|
||||
listContent[i+1].style.borderRightColor = '#808080';
|
||||
}
|
||||
listHeader[i].style.paddingLeft = '10px';
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
columnHeaderText: function(mom) {
|
||||
switch ($('#calendar').fullCalendar('getView').name) {
|
||||
case 'agendaYear':
|
||||
if (mom.date() == 1) {
|
||||
return mom.format('MMM');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
break;
|
||||
case 'agendaMonth':
|
||||
return mom.date();
|
||||
break;
|
||||
case 'agendaWeek':
|
||||
return mom.format ('ddd D');
|
||||
break;
|
||||
default:
|
||||
return mom.date();
|
||||
}
|
||||
},
|
||||
|
||||
eventRender: function (event, element) {
|
||||
$(element).tooltip({container: 'body', placement: 'bottom',
|
||||
title: event.tooltip});
|
||||
// element.attr ('title', event.tooltip); // Alternative tooltip
|
||||
},
|
||||
|
||||
loading: function( isLoading, view ) {
|
||||
if (isLoading) {
|
||||
showSpinner()
|
||||
} else {
|
||||
hideSpinner()
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
81
front/deviceDetailsSessions.php
Executable file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
//------------------------------------------------------------------------------
|
||||
// check if authenticated
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
|
||||
?>
|
||||
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
|
||||
|
||||
<!-- Datatable Session -->
|
||||
<table id="tableSessions" class="table table-bordered table-hover table-striped ">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= lang('DevDetail_SessionTable_Order');?></th>
|
||||
<th><?= lang('DevDetail_SessionTable_Connection');?></th>
|
||||
<th><?= lang('DevDetail_SessionTable_Disconnection');?></th>
|
||||
<th><?= lang('DevDetail_SessionTable_Duration');?></th>
|
||||
<th><?= lang('DevDetail_SessionTable_IP');?></th>
|
||||
<th><?= lang('DevDetail_SessionTable_Additionalinfo');?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
var parSessionsRows = 'Front_Details_Sessions_Rows';
|
||||
var sessionsRows = 10;
|
||||
var period = '1 month';
|
||||
|
||||
function initializeSessionsDatatable () {
|
||||
// Sessions datatable
|
||||
$('#tableSessions').DataTable({
|
||||
'paging' : true,
|
||||
'lengthChange': true,
|
||||
'lengthMenu' : [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, 'All']],
|
||||
'searching' : true,
|
||||
'ordering' : true,
|
||||
'info' : true,
|
||||
'autoWidth' : false,
|
||||
'order' : [[0,'desc'], [1,'desc']],
|
||||
|
||||
// Parameters
|
||||
'pageLength' : sessionsRows,
|
||||
|
||||
'columnDefs' : [
|
||||
{visible: false, targets: [0]},
|
||||
|
||||
// Replace HTML codes
|
||||
{targets: [1,2,3,5],
|
||||
'createdCell': function (td, cellData, rowData, row, col) {
|
||||
$(td).html (translateHTMLcodes (cellData));
|
||||
} }
|
||||
],
|
||||
|
||||
// Processing
|
||||
'processing' : true,
|
||||
'language' : {
|
||||
processing: '<table><td width="130px" align="middle"><?= lang("DevDetail_Loading");?></td>'+
|
||||
'<td><i class="ion ion-ios-loop-strong fa-spin fa-2x fa-fw">'+
|
||||
'</td></table>',
|
||||
emptyTable: 'No data',
|
||||
"lengthMenu": "<?= lang('Events_Tablelenght');?>",
|
||||
"search": "<?= lang('Events_Searchbox');?>: ",
|
||||
"paginate": {
|
||||
"next": "<?= lang('Events_Table_nav_next');?>",
|
||||
"previous": "<?= lang('Events_Table_nav_prev');?>"
|
||||
},
|
||||
"info": "<?= lang('Events_Table_info');?>",
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadSessionsData(){
|
||||
$('#tableSessions').DataTable().ajax.url('php/server/events.php?action=getDeviceSessions&mac=' + getMac() +'&period='+ period).load();
|
||||
}
|
||||
|
||||
initializeSessionsDatatable();
|
||||
loadSessionsData();
|
||||
|
||||
</script>
|
||||
@@ -65,6 +65,48 @@
|
||||
|
||||
<?php } ?>
|
||||
|
||||
|
||||
<!-- Delete Events -->
|
||||
<h4 class=""><i class="fa-solid fa-bell"></i>
|
||||
<?= lang("DevDetail_button_DeleteEvents") ?>
|
||||
</h4>
|
||||
<h5 class="">
|
||||
<?= lang("DevDetail_button_DeleteEvents_Warning") ?>
|
||||
</h5>
|
||||
<br>
|
||||
<div style="width:100%; text-align: center; margin-bottom: 50px;">
|
||||
<button type="button"
|
||||
class="btn btn-default pa-btn pa-btn-delete"
|
||||
style="margin-left:0px;"
|
||||
id="btnDeleteEvents"
|
||||
onclick="askDeleteDeviceEvents()">
|
||||
<?= lang('DevDetail_button_DeleteEvents');?>
|
||||
</button>
|
||||
<br>
|
||||
<div id="wol_output" style="margin-top: 10px;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Reset Custom Proprties -->
|
||||
<h4 class=""><i class="fa-solid fa-list"></i>
|
||||
<?= lang("DevDetail_CustomProperties_Title") ?>
|
||||
</h4>
|
||||
<h5 class="">
|
||||
<?= lang("DevDetail_CustomProps_reset_info") ?>
|
||||
</h5>
|
||||
<br>
|
||||
<div style="width:100%; text-align: center; margin-bottom: 50px;">
|
||||
<button type="button"
|
||||
class="btn btn-default pa-btn pa-btn-delete"
|
||||
style="margin-left:0px;"
|
||||
id="btnDeleteEvents"
|
||||
onclick="askResetDeviceProps()">
|
||||
<?= lang("Gen_Reset") ?>
|
||||
</button>
|
||||
<br>
|
||||
<div id="wol_output" style="margin-top: 10px;"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- SPEEDTEST -->
|
||||
<?php if ($_REQUEST["mac"] == "Internet") { ?>
|
||||
<h4 class=""><i class="fa-solid fa-gauge-high"></i>
|
||||
@@ -336,6 +378,56 @@
|
||||
return devicesList;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
function askDeleteDeviceEvents () {
|
||||
// Check MAC
|
||||
if (mac == '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ask delete device Events
|
||||
showModalWarning ('<?= lang('DevDetail_button_DeleteEvents');?>', '<?= lang('DevDetail_button_DeleteEvents_Warning');?>',
|
||||
'<?= lang('Gen_Cancel');?>', '<?= lang('Gen_Delete');?>', 'deleteDeviceEvents');
|
||||
}
|
||||
|
||||
function deleteDeviceEvents () {
|
||||
// Check MAC
|
||||
if (mac == '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete device events
|
||||
$.get('php/server/devices.php?action=deleteDeviceEvents&mac='+ mac, function(msg) {
|
||||
showMessage (msg);
|
||||
});
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
function askResetDeviceProps () {
|
||||
// Check MAC
|
||||
if (mac == '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ask Resert Custom properties
|
||||
showModalWarning ('<?= lang('Gen_Reset');?>', '<?= lang('DevDetail_CustomProps_reset_info');?>',
|
||||
'<?= lang('Gen_Cancel');?>', '<?= lang('Gen_Delete');?>', 'resetDeviceProps');
|
||||
}
|
||||
|
||||
function resetDeviceProps () {
|
||||
// Check MAC
|
||||
if (mac == '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute
|
||||
$.get('php/server/devices.php?action=resetDeviceProps&mac='+ mac, function(msg) {
|
||||
showMessage (msg);
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
function internetinfo() {
|
||||
$( "#internetinfooutput" ).empty();
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
checkPermissions([$dbPath, $confPath]);
|
||||
?>
|
||||
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
|
||||
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
<div class="content-wrapper">
|
||||
@@ -72,10 +74,14 @@
|
||||
|
||||
<!-- box-header -->
|
||||
<div class="box-header">
|
||||
<div class=" col-md-10 ">
|
||||
<div class=" col-md-9 ">
|
||||
<h3 id="tableDevicesTitle" class="box-title text-gray "></h3>
|
||||
</div>
|
||||
<div class=" col-md-2 "><a href="deviceDetails.php?mac=new"><i title="Add new dummy device" class="fa fa-square-plus"></i> <?= lang('Gen_create_new_device');?></a></div>
|
||||
<div class="dummyDevice col-md-3 ">
|
||||
<span>
|
||||
<a href="deviceDetails.php?mac=new"><i title="<?= lang('Gen_create_new_device');?>" class="fa fa-square-plus"></i> <?= lang('Gen_create_new_device');?></a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- table -->
|
||||
@@ -111,14 +117,6 @@
|
||||
?>
|
||||
|
||||
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
<!-- Datatable -->
|
||||
<link rel="stylesheet" href="lib/datatables.net-bs/css/dataTables.bootstrap.min.css">
|
||||
<link rel="stylesheet" href="lib/datatables.net/css/select.dataTables.min.css">
|
||||
<script src="lib/datatables.net/js/jquery.dataTables.min.js"></script>
|
||||
<script src="lib/datatables.net-bs/js/dataTables.bootstrap.min.js"></script>
|
||||
<script src="lib/datatables.net/js/dataTables.select.min.js"></script>
|
||||
|
||||
<!-- page script ----------------------------------------------------------- -->
|
||||
<script>
|
||||
var deviceStatus = 'all';
|
||||
@@ -146,6 +144,10 @@ function main () {
|
||||
//initialize the table headers in the correct order
|
||||
var availableColumns = getSettingOptions("UI_device_columns").split(",");
|
||||
headersDefaultOrder = availableColumns.map(val => getString(val));
|
||||
|
||||
console.log(headersDefaultOrder);
|
||||
|
||||
|
||||
var selectedColumns = JSON.parse(getSetting("UI_device_columns").replace(/'/g, '"'));
|
||||
|
||||
// generate default order lists of given length
|
||||
@@ -183,6 +185,9 @@ function main () {
|
||||
// mapping the default order to the user specified one
|
||||
function mapIndx(oldIndex)
|
||||
{
|
||||
// console.log(oldIndex);
|
||||
// console.log(tableColumnOrder);
|
||||
|
||||
for(i=0;i<tableColumnOrder.length;i++)
|
||||
{
|
||||
if(tableColumnOrder[i] == oldIndex)
|
||||
@@ -199,26 +204,31 @@ function getDevicesTotals() {
|
||||
|
||||
// Fetch data via AJAX
|
||||
$.ajax({
|
||||
url: '/api/table_devices_tiles.json?nocache=' + Date.now(),
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: function(response) {
|
||||
if (response && response.data) {
|
||||
resultJSON = response.data[0]; // Assuming the structure {"data": [ ... ]}
|
||||
|
||||
// Save the result to cache
|
||||
setCache("getDevicesTotals", JSON.stringify(resultJSON));
|
||||
url: '/php/server/query_json.php',
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
data: {
|
||||
file: 'table_devices_tiles.json', // Pass the file parameter
|
||||
nocache: Date.now() // Prevent caching with a timestamp
|
||||
},
|
||||
success: function(response) {
|
||||
if (response && response.data) {
|
||||
resultJSON = response.data[0]; // Assuming the structure {"data": [ ... ]}
|
||||
|
||||
// Save the result to cache
|
||||
setCache("getDevicesTotals", JSON.stringify(resultJSON));
|
||||
|
||||
// Process the fetched data
|
||||
processDeviceTotals(resultJSON);
|
||||
} else {
|
||||
console.error("Invalid response format from API");
|
||||
// Process the fetched data
|
||||
processDeviceTotals(resultJSON);
|
||||
} else {
|
||||
console.error("Invalid response format from API");
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error("Failed to fetch devices data:", error);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error("Failed to fetch devices data:", error);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -286,47 +296,6 @@ function renderInfoboxes(customData) {
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Define a function to filter data based on deviceStatus
|
||||
function filterDataByStatus(data, status) {
|
||||
return data.filter(function(item) {
|
||||
switch (status) {
|
||||
case 'my_devices':
|
||||
to_display = getSetting('UI_MY_DEVICES');
|
||||
|
||||
let result = true;
|
||||
|
||||
if (!to_display.includes('down') && item.devPresentLastScan === 0 && item.devAlertDown !== 0) {
|
||||
result = false;
|
||||
} else if (!to_display.includes('new') && item.devIsNew === 1) {
|
||||
result = false;
|
||||
} else if (!to_display.includes('archived') && item.devIsArchived === 1) {
|
||||
result = false;
|
||||
} else if (!to_display.includes('offline') && item.devPresentLastScan === 0) {
|
||||
result = false;
|
||||
} else if (!to_display.includes('online') && item.devPresentLastScan === 1) {
|
||||
result = false;
|
||||
}
|
||||
|
||||
return result; // Include all items for 'my_devices' status
|
||||
case 'connected':
|
||||
return item.devPresentLastScan === 1;
|
||||
case 'favorites':
|
||||
return item.devFavorite === 1;
|
||||
case 'new':
|
||||
return item.devIsNew === 1;
|
||||
case 'offline':
|
||||
return item.devPresentLastScan === 0;
|
||||
case 'down':
|
||||
return (item.devPresentLastScan === 0 && item.devAlertDown !== 0);
|
||||
case 'archived':
|
||||
return item.devIsArchived === 1;
|
||||
default:
|
||||
return true; // Include all items for unknown statuses
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Map column index to column name for GraphQL query
|
||||
function mapColumnIndexToFieldName(index, tableColumnVisible) {
|
||||
// the order is important, don't change it!
|
||||
@@ -356,7 +325,8 @@ function mapColumnIndexToFieldName(index, tableColumnVisible) {
|
||||
"devSSID",
|
||||
"devSourcePlugin",
|
||||
"devPresentLastScan",
|
||||
"devAlertDown"
|
||||
"devAlertDown",
|
||||
"devCustomProps"
|
||||
];
|
||||
|
||||
// console.log("OrderBy: " + columnNames[tableColumnOrder[index]]);
|
||||
@@ -366,6 +336,7 @@ function mapColumnIndexToFieldName(index, tableColumnVisible) {
|
||||
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Initializes the main devices list datatable
|
||||
function initializeDatatable (status) {
|
||||
|
||||
if(!status)
|
||||
@@ -466,6 +437,7 @@ function initializeDatatable (status) {
|
||||
devStatus
|
||||
devParentChildrenCount
|
||||
devIpLong
|
||||
devCustomProps
|
||||
}
|
||||
count
|
||||
}
|
||||
@@ -532,7 +504,8 @@ function initializeDatatable (status) {
|
||||
device.devSSID || "",
|
||||
device.devSourcePlugin || "",
|
||||
device.devPresentLastScan || "",
|
||||
device.devAlertDown || ""
|
||||
device.devAlertDown || "",
|
||||
device.devCustomProps || ""
|
||||
];
|
||||
|
||||
const newRow = [];
|
||||
@@ -559,11 +532,19 @@ function initializeDatatable (status) {
|
||||
'order' : tableOrder,
|
||||
'select' : true, // Enable selection
|
||||
|
||||
'fixedHeader': true,
|
||||
'fixedHeader': {
|
||||
'header': true,
|
||||
'footer': true
|
||||
},
|
||||
|
||||
'columnDefs' : [
|
||||
{visible: false, targets: tableColumnHide },
|
||||
{className: 'text-center', targets: [mapIndx(3), mapIndx(4), mapIndx(9), mapIndx(10), mapIndx(15), mapIndx(18)] },
|
||||
{className: 'text-center', targets: [mapIndx(4), mapIndx(9), mapIndx(10), mapIndx(15), mapIndx(18)] },
|
||||
{className: 'iconColumn text-center', targets: [mapIndx(3)]},
|
||||
{width: '80px', targets: [mapIndx(6), mapIndx(7), mapIndx(15)] },
|
||||
{width: '30px', targets: [mapIndx(10), mapIndx(13), mapIndx(18)] },
|
||||
{width: '85px', targets: [mapIndx(9)] },
|
||||
{width: '30px', targets: [mapIndx(3), mapIndx(10), mapIndx(13), mapIndx(18)] },
|
||||
{orderData: [mapIndx(12)], targets: mapIndx(8) },
|
||||
|
||||
// Device Name
|
||||
@@ -620,9 +601,11 @@ function initializeDatatable (status) {
|
||||
<a href="http://${cellData}" class="pointer" target="_blank">
|
||||
${cellData}
|
||||
</a>
|
||||
<a href="https://${cellData}" class="pointer" target="_blank">
|
||||
<span class="alignRight">
|
||||
<a href="https://${cellData}" class="pointer" target="_blank">
|
||||
<i class="fa fa-lock "></i>
|
||||
</a>
|
||||
</a>
|
||||
<span>
|
||||
<span>`);
|
||||
} else {
|
||||
$(td).html ('');
|
||||
@@ -639,6 +622,17 @@ function initializeDatatable (status) {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Custom Properties
|
||||
{targets: [mapIndx(26)],
|
||||
'createdCell': function (td, cellData, rowData, row, col) {
|
||||
if (!emptyArr.includes(cellData)){
|
||||
$(td).html (`<span>${renderCustomProps(cellData, rowData[mapIndx(11)])}<span>`);
|
||||
} else {
|
||||
$(td).html ('');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Favorite
|
||||
{targets: [mapIndx(4)],
|
||||
@@ -742,9 +736,27 @@ function initializeDatatable (status) {
|
||||
|
||||
});
|
||||
|
||||
// search only after idle
|
||||
var typingTimer; // Timer identifier
|
||||
var debounceTime = 500; // Delay in milliseconds
|
||||
|
||||
$('input[aria-controls="tableDevices"]').off().on('keyup', function () {
|
||||
clearTimeout(typingTimer); // Clear the previous timer
|
||||
var searchValue = this.value;
|
||||
|
||||
typingTimer = setTimeout(function () {
|
||||
$('#tableDevices').DataTable().search(searchValue).draw(); // Trigger the search after delay
|
||||
}, debounceTime);
|
||||
});
|
||||
|
||||
|
||||
hideSpinner();
|
||||
|
||||
},
|
||||
createdRow: function(row, data, dataIndex) {
|
||||
// add devMac to the table row
|
||||
$(row).attr('my-devMac', data[mapIndx(11)]);
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -756,11 +768,9 @@ function initializeDatatable (status) {
|
||||
// -----------------------------------------------------------------------------
|
||||
function handleLoadingDialog(needsReload = false)
|
||||
{
|
||||
// console.log(`needsReload: ${needsReload}`);
|
||||
|
||||
// console.log('needsReload:');
|
||||
// console.log(needsReload);
|
||||
|
||||
$.get('log/execution_queue.log?nocache=' + Date.now(), function(data) {
|
||||
$.get('/php/server/query_logs.php?file=execution_queue.log&nocache=' + Date.now(), function(data) {
|
||||
|
||||
if(data.includes("update_api|devices"))
|
||||
{
|
||||
@@ -785,41 +795,15 @@ function handleLoadingDialog(needsReload = false)
|
||||
// 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);
|
||||
// get selected devices
|
||||
var selectedDevicesDataTableData = $('#tableDevices').DataTable().rows({ selected: true, page: 'current' }).data().toArray();
|
||||
|
||||
console.log(selectedDevicesDataTableData);
|
||||
|
||||
macs = ""
|
||||
|
||||
for (var i = 0; i < selectedDevices.length; i++) {
|
||||
macs += selectedDevices[i][mapIndx(11)] + ","; // [11] == MAC
|
||||
for (var j = 0; j < selectedDevicesDataTableData.length; j++) {
|
||||
macs += selectedDevicesDataTableData[j][mapIndx(11)] + ","; // [11] == MAC
|
||||
}
|
||||
|
||||
// redirect to the Maintenance section
|
||||
@@ -830,27 +814,93 @@ function multiEditDevices()
|
||||
// -----------------------------------------------------------------------------
|
||||
// Function collects shown devices from the DataTable
|
||||
function getMacsOfShownDevices() {
|
||||
rows = $('#tableDevices')[0].rows;
|
||||
macs = [];
|
||||
|
||||
rows = $('#tableDevices')[0].rows
|
||||
macs = []
|
||||
// var devicesDataTableData = $('#tableDevices').dataTable().fnGetData();
|
||||
var devicesDataTableData = $('#tableDevices').DataTable().rows({ selected: false, page: 'current' }).data().toArray();
|
||||
|
||||
var devicesDataTableData = $('#tableDevices').dataTable().fnGetData();
|
||||
console.log(devicesDataTableData);
|
||||
|
||||
var selectedDevices = [];
|
||||
|
||||
// first row is the heading, skip
|
||||
for (var i = 1; i < rows.length; i++) {
|
||||
selectedDevices.push(devicesDataTableData[rows[i]._DT_RowIndex]);
|
||||
}
|
||||
var rowIndex = rows[i]._DT_RowIndex;
|
||||
|
||||
// Ensure the rowIndex is valid and within bounds of devicesDataTableData
|
||||
if (rowIndex >= 0 && rowIndex < devicesDataTableData.length) {
|
||||
selectedDevices.push(devicesDataTableData[rowIndex]);
|
||||
} else {
|
||||
console.log(`Invalid rowIndex: ${rowIndex} at row ${i}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (var j = 0; j < selectedDevices.length; j++) {
|
||||
macs.push(selectedDevices[j][mapIndx(11)]); // mapIndx(11) == MAC
|
||||
// Ensure that selectedDevices[j] is not undefined
|
||||
if (selectedDevices[j]) {
|
||||
macs.push(selectedDevices[j][mapIndx(11)]); // mapIndx(11) == MAC
|
||||
} else {
|
||||
console.log(`selectedDevices[${j}] is undefined`);
|
||||
}
|
||||
}
|
||||
|
||||
return macs;
|
||||
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Handle custom actions/properties on a device
|
||||
function renderCustomProps(custProps, mac) {
|
||||
// Decode and parse the custom properties
|
||||
|
||||
if (!isBase64(custProps)) {
|
||||
|
||||
console.error(`Unable to decode CustomProps for ${mac}`);
|
||||
console.error(custProps);
|
||||
|
||||
} else{
|
||||
const props = JSON.parse(atob(custProps));
|
||||
let html = "";
|
||||
|
||||
props.forEach((propGroup, index) => {
|
||||
const propMap = Object.fromEntries(
|
||||
propGroup.map(prop => Object.entries(prop)[0]) // Convert array of objects to key-value pairs
|
||||
);
|
||||
|
||||
if (propMap["CUSTPROP_show"] === true) { // Render if visible
|
||||
let onClickEvent = "";
|
||||
|
||||
switch (propMap["CUSTPROP_type"]) {
|
||||
case "show_notes":
|
||||
onClickEvent = `showModalOK('${propMap["CUSTPROP_name"]}','${propMap["CUSTPROP_notes"]}')`;
|
||||
break;
|
||||
case "link":
|
||||
onClickEvent = `window.location.href='${propMap["CUSTPROP_args"]}';`;
|
||||
break;
|
||||
case "link_new_tab":
|
||||
onClickEvent = `openInNewTab('${propMap["CUSTPROP_args"]}')`;
|
||||
break;
|
||||
case "run_plugin":
|
||||
onClickEvent = `alert('Not implemented')`;
|
||||
break;
|
||||
case "delete_dev":
|
||||
onClickEvent = `askDelDevDTInline('${mac}')`;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
html += `<div class="pointer devicePropAction" onclick="${onClickEvent}" title="${propMap["CUSTPROP_name"]} ${propMap["CUSTPROP_args"]}"> ${atob(propMap["CUSTPROP_icon"])} </div>`;
|
||||
}
|
||||
});
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
return "Error, check browser Console log"
|
||||
}
|
||||
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Update cache with shown devices before navigating away
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
require 'php/templates/header.php';
|
||||
?>
|
||||
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
<div class="content-wrapper">
|
||||
|
||||
@@ -173,14 +175,6 @@
|
||||
require 'php/templates/footer.php';
|
||||
?>
|
||||
|
||||
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
<!-- Datatable -->
|
||||
<link rel="stylesheet" href="lib/datatables.net-bs/css/dataTables.bootstrap.min.css">
|
||||
<script src="lib/datatables.net/js/jquery.dataTables.min.js"></script>
|
||||
<script src="lib/datatables.net-bs/js/dataTables.bootstrap.min.js"></script>
|
||||
|
||||
|
||||
<!-- page script ----------------------------------------------------------- -->
|
||||
<script>
|
||||
var parPeriod = 'nax_parPeriod';
|
||||
|
||||
@@ -166,8 +166,7 @@ switch ($UI_THEME) {
|
||||
|
||||
<!-- jQuery 3 -->
|
||||
<script src="lib/jquery/jquery.min.js"></script>
|
||||
<!-- Bootstrap 3.3.7 -->
|
||||
<script src="lib/bootstrap/bootstrap.min.js"></script>
|
||||
|
||||
<!-- iCheck -->
|
||||
<script src="lib/iCheck/icheck.min.js"></script>
|
||||
<script>
|
||||
|
||||
@@ -12,7 +12,7 @@ var timerRefreshData = ''
|
||||
|
||||
var emptyArr = ['undefined', "", undefined, null, 'null'];
|
||||
var UI_LANG = "English";
|
||||
const allLanguages = ["en_us", "es_es", "de_de", "fr_fr", "it_it", "ru_ru", "nb_no", "pl_pl", "pt_br", "tr_tr", "zh_cn", "cs_cz", "ar_ar", "ca_ca"]; // needs to be same as in lang.php
|
||||
const allLanguages = ["en_us", "es_es", "de_de", "fr_fr", "it_it", "ru_ru", "nb_no", "pl_pl", "pt_br", "tr_tr", "zh_cn", "cs_cz", "ar_ar", "ca_ca", "uk_ua"]; // needs to be same as in lang.php
|
||||
var settingsJSON = {}
|
||||
|
||||
|
||||
@@ -115,10 +115,10 @@ function cacheSettings()
|
||||
return new Promise((resolve, reject) => {
|
||||
if(!getCache('completedCalls').includes('cacheSettings'))
|
||||
{
|
||||
$.get('api/table_settings.json?nocache=' + Date.now(), function(resSet) {
|
||||
|
||||
$.get('api/plugins.json?nocache=' + Date.now(), function(resPlug) {
|
||||
$.get('/php/server/query_json.php', { file: 'table_settings.json', nocache: Date.now() }, function(resSet) {
|
||||
|
||||
$.get('/php/server/query_json.php', { file: 'plugins.json', nocache: Date.now() }, function(resPlug) {
|
||||
|
||||
pluginsData = resPlug["data"];
|
||||
settingsData = resSet["data"];
|
||||
|
||||
@@ -171,7 +171,7 @@ function cacheSettings()
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Get a setting value by key
|
||||
// Get a setting options value by key
|
||||
function getSettingOptions (key) {
|
||||
|
||||
// handle initial load to make sure everything is set-up and cached
|
||||
@@ -225,7 +225,7 @@ function cacheStrings() {
|
||||
});
|
||||
|
||||
// Fetch strings and translations from plugins
|
||||
$.get(`api/table_plugins_language_strings.json?nocache=${Date.now()}`)
|
||||
$.get('/php/server/query_json.php', { file: 'table_plugins_language_strings.json', nocache: Date.now() })
|
||||
.done((pluginRes) => {
|
||||
const data = pluginRes["data"];
|
||||
|
||||
@@ -339,6 +339,9 @@ function getLangCode() {
|
||||
case 'Catalan (ca_ca)':
|
||||
lang_code = 'ca_ca';
|
||||
break;
|
||||
case 'Ukrainian (uk_uk)':
|
||||
lang_code = 'uk_ua';
|
||||
break;
|
||||
}
|
||||
|
||||
return lang_code;
|
||||
@@ -350,6 +353,30 @@ function getLangCode() {
|
||||
// String utilities
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// ----------------------------------------------------
|
||||
/**
|
||||
* Replaces double quotes within single-quoted strings, then converts all single quotes to double quotes,
|
||||
* while preserving the intended structure.
|
||||
*
|
||||
* @param {string} inputString - The input string to process.
|
||||
* @returns {string} - The processed string with transformations applied.
|
||||
*/
|
||||
function processQuotes(inputString) {
|
||||
// Step 1: Replace double quotes within single-quoted strings
|
||||
let tempString = inputString.replace(/'([^']*?)'/g, (match, p1) => {
|
||||
const escapedContent = p1.replace(/"/g, '_escaped_double_quote_'); // Temporarily replace double quotes
|
||||
return `'${escapedContent}'`;
|
||||
});
|
||||
|
||||
// Step 2: Replace all single quotes with double quotes
|
||||
tempString = tempString.replace(/'/g, '"');
|
||||
|
||||
// Step 3: Restore escaped double quotes
|
||||
const processedString = tempString.replace(/_escaped_double_quote_/g, "'");
|
||||
|
||||
return processedString;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
function jsonSyntaxHighlight(json) {
|
||||
if (typeof json != 'string') {
|
||||
@@ -383,6 +410,14 @@ function isValidBase64(str) {
|
||||
return invalidCharacters === '';
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Utility function to check if the value is already Base64
|
||||
function isBase64(value) {
|
||||
const base64Regex =
|
||||
/^(?:[A-Za-z0-9+\/]{4})*?(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/;
|
||||
return base64Regex.test(value);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
function isValidJSON(jsonString) {
|
||||
try {
|
||||
@@ -428,6 +463,8 @@ function utf8ToBase64(str) {
|
||||
// General utilities
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
// check if JSON object
|
||||
function isJsonObject(value) {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
@@ -702,7 +739,7 @@ function forceLoadUrl(relativeUrl) {
|
||||
// -----------------------------------------------------------------------------
|
||||
function navigateToDeviceWithIp (ip) {
|
||||
|
||||
$.get('api/table_devices.json?nocache=' + Date.now(), function(res) {
|
||||
$.get('/php/server/query_json.php', { file: 'table_devices.json', nocache: Date.now() }, function(res) {
|
||||
|
||||
devices = res["data"];
|
||||
|
||||
@@ -924,7 +961,7 @@ function cacheDevices()
|
||||
|
||||
// if(!getCache('completedCalls').includes('cacheDevices'))
|
||||
// {
|
||||
$.get('api/table_devices.json?nocache=' + Date.now(), function(data) {
|
||||
$.get('/php/server/query_json.php', { file: 'table_devices.json', nocache: Date.now() }, function(data) {
|
||||
|
||||
// console.log(data)
|
||||
|
||||
@@ -1290,7 +1327,7 @@ function clearCache() {
|
||||
// -----------------------------------------------------------------------------
|
||||
// Function to check if cache needs to be refreshed because of setting changes
|
||||
function checkSettingChanges() {
|
||||
$.get('api/app_state.json?nocache=' + Date.now(), function(appState) {
|
||||
$.get('/php/server/query_json.php', { file: 'app_state.json', nocache: Date.now() }, function(appState) {
|
||||
const importedMilliseconds = parseInt(appState["settingsImported"] * 1000);
|
||||
const lastReloaded = parseInt(sessionStorage.getItem(sessionStorageKey + '_time'));
|
||||
|
||||
@@ -1345,7 +1382,7 @@ async function waitForGraphQLServer() {
|
||||
// Returns 1 if running, 0 otherwise
|
||||
async function isGraphQLServerRunning() {
|
||||
try {
|
||||
const response = await $.get('api/app_state.json?nocache=' + Date.now());
|
||||
const response = await $.get('/php/server/query_json.php', { file: 'app_state.json', nocache: Date.now()});
|
||||
console.log("graphQLServerStarted: " + response["graphQLServerStarted"]);
|
||||
setCache("graphQLServerStarted", response["graphQLServerStarted"]);
|
||||
return response["graphQLServerStarted"];
|
||||
|
||||
@@ -85,7 +85,7 @@ function renderList(
|
||||
// Check if database is locked
|
||||
function checkDbLock() {
|
||||
$.ajax({
|
||||
url: "log/db_is_locked.log", // Replace with the actual path to your PHP file
|
||||
url: "/php/server/query_logs.php?file=db_is_locked.log",
|
||||
type: "GET",
|
||||
|
||||
success: function (response) {
|
||||
|
||||
70
front/js/device.js
Executable file
@@ -0,0 +1,70 @@
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
function askDeleteDevice() {
|
||||
// Check MAC
|
||||
if (mac == '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ask delete device
|
||||
showModalWarning(
|
||||
getString("DevDetail_button_Delete"),
|
||||
getString("DevDetail_button_Delete_ask"),
|
||||
getString('Gen_Cancel'),
|
||||
getString('Gen_Delete'),
|
||||
'deleteDevice');
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
function askDelDevDTInline(mac) {
|
||||
// Check MAC
|
||||
if (mac == '') {
|
||||
return;
|
||||
}
|
||||
|
||||
showModalWarning(
|
||||
getString("DevDetail_button_Delete"),
|
||||
getString("DevDetail_button_Delete_ask"),
|
||||
getString('Gen_Cancel'),
|
||||
getString('Gen_Delete'),
|
||||
() => deleteDeviceByMac(mac))
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
function deleteDevice() {
|
||||
// Check MAC
|
||||
if (mac == '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete device
|
||||
$.get('php/server/devices.php?action=deleteDevice&mac=' + mac, function (msg) {
|
||||
showMessage(msg);
|
||||
});
|
||||
|
||||
// refresh API
|
||||
updateApi("devices,appevents")
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
function deleteDeviceByMac(mac) {
|
||||
// Check MAC
|
||||
if (mac == '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// alert(mac)
|
||||
// return;
|
||||
|
||||
// Delete device
|
||||
$.get('php/server/devices.php?action=deleteDevice&mac=' + mac, function (msg) {
|
||||
showMessage(msg);
|
||||
});
|
||||
|
||||
// refresh API
|
||||
updateApi("devices,appevents")
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ function versionUpdateUI(){
|
||||
// Checks if a new version is available via the global app_state.json
|
||||
function checkIfNewVersionAvailable()
|
||||
{
|
||||
$.get('api/app_state.json?nocache=' + Date.now(), function(appState) {
|
||||
$.get('/php/server/query_json.php', { file: 'app_state.json', nocache: Date.now() }, function(appState) {
|
||||
|
||||
// console.log(appState["isNewVersionChecked"])
|
||||
// console.log(appState["isNewVersion"])
|
||||
|
||||
@@ -65,7 +65,8 @@ function showModalWarning(
|
||||
message,
|
||||
btnCancel = getString("Gen_Cancel"),
|
||||
btnOK = getString("Gen_Okay"),
|
||||
callbackFunction = null
|
||||
callbackFunction = null,
|
||||
triggeredBy = null
|
||||
) {
|
||||
// set captions
|
||||
$("#modal-warning-title").html(title);
|
||||
@@ -77,6 +78,10 @@ function showModalWarning(
|
||||
modalCallbackFunction = callbackFunction;
|
||||
}
|
||||
|
||||
if (triggeredBy != null) {
|
||||
$('#'+prefix).attr("data-myparam-triggered-by", triggeredBy)
|
||||
}
|
||||
|
||||
// Show modal
|
||||
$("#modal-warning").modal("show");
|
||||
}
|
||||
|
||||
@@ -191,14 +191,6 @@ function isSHA256(value) {
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Utility function to check if the value is already Base64
|
||||
function isBase64(value) {
|
||||
const base64Regex =
|
||||
/^(?:[A-Za-z0-9+\/]{4})*?(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/;
|
||||
return base64Regex.test(value);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Validation
|
||||
// -------------------------------------------------------------------
|
||||
@@ -245,6 +237,68 @@ function settingsCollectedCorrectly(settingsArray, settingsJSON_DB) {
|
||||
// Manipulating Editable List options
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Add row to datatable
|
||||
function addDataTableRow(el)
|
||||
{
|
||||
alert("a")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Clone datatable row
|
||||
function cloneDataTableRow(el){
|
||||
|
||||
console.log(el);
|
||||
|
||||
const id = "NEWDEV_devCustomProps_table"; // Your table ID
|
||||
const table = $('#'+id).DataTable();
|
||||
|
||||
|
||||
// Get the 'my-index' attribute from the closest tr element
|
||||
const myIndex = parseInt($(el).closest("tr").attr("my-index"));
|
||||
|
||||
// Find the row in the table with the matching 'my-index'
|
||||
const row = table.rows().nodes().to$().filter(`[my-index="${myIndex}"]`).first().get(0);
|
||||
|
||||
// Clone the row (including its data and controls)
|
||||
let clonedRow = $(row).clone(true, true); // The true arguments copy the data and event handlers
|
||||
|
||||
|
||||
$(clonedRow).attr("my-index",table.rows().count())
|
||||
|
||||
|
||||
console.log(clonedRow);
|
||||
|
||||
|
||||
// Add the cloned row to the DataTable
|
||||
table.row.add(clonedRow[0]).draw();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Remove current datatable row
|
||||
function removeDataTableRow(el) {
|
||||
console.log(el);
|
||||
|
||||
const id = "NEWDEV_devCustomProps_table"; // Your table ID
|
||||
const table = $('#'+id).DataTable();
|
||||
|
||||
if(table.rows().count() > 1)
|
||||
{
|
||||
// Get the 'my-index' attribute from the closest tr element
|
||||
const myIndex = parseInt($(el).closest("tr").attr("my-index"));
|
||||
|
||||
// Find the row in the table with the matching 'my-index'
|
||||
const row = table.rows().nodes().to$().filter(`[my-index="${myIndex}"]`).first().get(0);
|
||||
|
||||
// Remove the row from the DataTable
|
||||
table.row(row).remove().draw();
|
||||
}
|
||||
else
|
||||
{
|
||||
showMessage (getString("CustProps_cant_remove"), 3000, "modal_red");
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Add item to list
|
||||
function addList(element, clearInput = true) {
|
||||
@@ -551,7 +605,7 @@ function overrideToggle(element) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Generate options or set options based on the provided parameters
|
||||
function generateOptionsOrSetOptions(
|
||||
setKey,
|
||||
@@ -559,14 +613,21 @@ function generateOptionsOrSetOptions(
|
||||
placeholder, // ID of the HTML element where dropdown should be rendered (will be replaced)
|
||||
processDataCallback, // Callback function to generate entries based on options
|
||||
targetField, // Target field or element where selected value should be applied or updated
|
||||
transformers = [] // Transformers to be applied to the values
|
||||
transformers = [], // Transformers to be applied to the values
|
||||
overrideOptions = null // override options if available
|
||||
) {
|
||||
|
||||
// console.log(setKey);
|
||||
|
||||
// NOTE {value} options to replace with a setting or SQL value are handled in the cacheSettings() function
|
||||
options = arrayToObject(createArray(getSettingOptions(setKey)))
|
||||
// console.log(overrideOptions);
|
||||
// console.log( getSettingOptions(setKey));
|
||||
// console.log( setKey);
|
||||
|
||||
// NOTE {value} options to replace with a setting or SQL value are handled in the cacheSettings() function
|
||||
options = arrayToObject(createArray(overrideOptions ? overrideOptions : getSettingOptions(setKey)))
|
||||
|
||||
|
||||
|
||||
// Call to render lists
|
||||
renderList(
|
||||
options,
|
||||
@@ -655,6 +716,7 @@ const handleElementOptions = (setKey, elementOptions, transformers, val) => {
|
||||
let onChange = "console.log('onChange - Not implemented');";
|
||||
let customParams = "";
|
||||
let customId = "";
|
||||
let columns = [];
|
||||
|
||||
|
||||
elementOptions.forEach((option) => {
|
||||
@@ -708,6 +770,9 @@ const handleElementOptions = (setKey, elementOptions, transformers, val) => {
|
||||
if (option.customId) {
|
||||
customId = option.customId;
|
||||
}
|
||||
if (option.columns) {
|
||||
columns = option.columns;
|
||||
}
|
||||
});
|
||||
|
||||
if (transformers.includes("sha256")) {
|
||||
@@ -730,7 +795,8 @@ const handleElementOptions = (setKey, elementOptions, transformers, val) => {
|
||||
onClick,
|
||||
onChange,
|
||||
customParams,
|
||||
customId
|
||||
customId,
|
||||
columns
|
||||
};
|
||||
};
|
||||
|
||||
@@ -863,7 +929,7 @@ function genListWithInputSet(options, valuesArray, targetField, transformers, pl
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
// Generate the form control for setting
|
||||
function generateFormHtml(set, overrideValue) {
|
||||
function generateFormHtml(settingsData, set, overrideValue, overrideOptions, originalSetKey) {
|
||||
let inputHtml = '';
|
||||
|
||||
|
||||
@@ -871,14 +937,17 @@ function generateFormHtml(set, overrideValue) {
|
||||
const setKey = set['setKey'];
|
||||
const setType = set['setType'];
|
||||
|
||||
// if (setKey == '') {
|
||||
|
||||
// console.log(setType);
|
||||
// console.log(setKey);
|
||||
// console.log(overrideValue);
|
||||
// console.log(inVal);
|
||||
|
||||
// console.log(inVal);
|
||||
|
||||
// }
|
||||
|
||||
// Parse the setType JSON string
|
||||
const setTypeObject = JSON.parse(setType.replace(/'/g, '"'));
|
||||
const setTypeObject = JSON.parse(processQuotes(setType))
|
||||
const dataType = setTypeObject.dataType;
|
||||
const elements = setTypeObject.elements || [];
|
||||
|
||||
@@ -903,14 +972,22 @@ function generateFormHtml(set, overrideValue) {
|
||||
onClick,
|
||||
onChange,
|
||||
customParams,
|
||||
customId
|
||||
customId,
|
||||
columns
|
||||
} = handleElementOptions(setKey, elementOptions, transformers, inVal);
|
||||
|
||||
// Override value
|
||||
const val = valRes;
|
||||
let val = valRes;
|
||||
|
||||
// console.log(val);
|
||||
// if (setKey == '') {
|
||||
|
||||
// console.log(setType);
|
||||
// console.log(setKey);
|
||||
// console.log(overrideValue);
|
||||
// console.log(inVal);
|
||||
// console.log(val);
|
||||
|
||||
// }
|
||||
|
||||
// Generate HTML based on elementType
|
||||
switch (elementType) {
|
||||
@@ -921,20 +998,21 @@ function generateFormHtml(set, overrideValue) {
|
||||
inputHtml += `<select onChange="settingsChanged();${onChange}"
|
||||
my-data-type="${dataType}"
|
||||
my-editable="${editable}"
|
||||
class="form-control ${addCss}"
|
||||
class="form-control ${addCss} ${cssClasses}"
|
||||
name="${setKey}"
|
||||
id="${setKey}"
|
||||
my-customparams="${customParams}"
|
||||
my-customid="${customId}"
|
||||
my-originalSetKey="${originalSetKey}"
|
||||
${multi}>
|
||||
<option value="" id="${setKey + "_temp_"}"></option>
|
||||
</select>`;
|
||||
|
||||
generateOptionsOrSetOptions(setKey, createArray(val), `${setKey}_temp_`, generateOptions, null, transformers);
|
||||
generateOptionsOrSetOptions(setKey, createArray(val), `${setKey}_temp_`, generateOptions, null, transformers, overrideOptions);
|
||||
break;
|
||||
|
||||
case 'input':
|
||||
const checked = val === 'True' || val === '1' ? 'checked' : '';
|
||||
const checked = val === 'True' || val === 'true' || val === '1' ? 'checked' : '';
|
||||
const inputClass = inputType === 'checkbox' ? 'checkbox' : 'form-control';
|
||||
|
||||
inputHtml += `<input
|
||||
@@ -943,6 +1021,7 @@ function generateFormHtml(set, overrideValue) {
|
||||
my-data-type="${dataType}"
|
||||
my-customparams="${customParams}"
|
||||
my-customid="${customId}"
|
||||
my-originalSetKey="${originalSetKey}"
|
||||
id="${setKey}${suffix}"
|
||||
type="${inputType}"
|
||||
value="${val}"
|
||||
@@ -957,6 +1036,7 @@ function generateFormHtml(set, overrideValue) {
|
||||
class="btn btn-primary ${cssClasses}"
|
||||
my-customparams="${customParams}"
|
||||
my-customid="${customId}"
|
||||
my-originalSetKey="${originalSetKey}"
|
||||
my-input-from="${sourceIds}"
|
||||
my-input-to="${setKey}"
|
||||
onclick="${onClick}">
|
||||
@@ -969,6 +1049,7 @@ function generateFormHtml(set, overrideValue) {
|
||||
class="form-control input"
|
||||
my-customparams="${customParams}"
|
||||
my-customid="${customId}"
|
||||
my-originalSetKey="${originalSetKey}"
|
||||
my-data-type="${dataType}"
|
||||
id="${setKey}"
|
||||
${readOnly}>${val}</textarea>`;
|
||||
@@ -979,10 +1060,116 @@ function generateFormHtml(set, overrideValue) {
|
||||
class="${cssClasses}"
|
||||
my-data-type="${dataType}"
|
||||
my-customparams="${customParams}"
|
||||
my-customid="${customId}">
|
||||
${getString(getStringKey)}
|
||||
my-customid="${customId}"
|
||||
my-originalSetKey="${originalSetKey}"
|
||||
onclick="${onClick}">
|
||||
${getString(getStringKey)}${placeholder}
|
||||
</span>`;
|
||||
break;
|
||||
case 'datatable':
|
||||
|
||||
const tableId = `${setKey}_table`;
|
||||
let datatableHtml = `<table id="${tableId}" class="table table-striped">`;
|
||||
|
||||
// Dynamic array creation
|
||||
let emptyVal = [];
|
||||
|
||||
let columnSettings = [];
|
||||
|
||||
// Generate table headers
|
||||
datatableHtml += '<thead><tr>';
|
||||
|
||||
columns.forEach(column => {
|
||||
let columnSetting = getSetObject(settingsData, column.settingKey) || {};
|
||||
|
||||
datatableHtml += `<th>${columnSetting.setName}</th>`;
|
||||
|
||||
if(column.typeOverride)
|
||||
{
|
||||
columnSetting["setType"] = JSON.stringify(column.typeOverride);
|
||||
}
|
||||
|
||||
if(column.optionsOverride)
|
||||
{
|
||||
if (column.optionsOverride.startsWith("setting.")) {
|
||||
columnSetting["setOptions"] = getSetting(column.optionsOverride.replace("setting.",""));
|
||||
} else {
|
||||
columnSetting["setOptions"] = column.optionsOverride;
|
||||
}
|
||||
}
|
||||
|
||||
columnSettings.push(columnSetting)
|
||||
|
||||
// helper for if val is empty
|
||||
emptyVal.push('');
|
||||
});
|
||||
datatableHtml += '</tr></thead>';
|
||||
|
||||
// Generate table body
|
||||
datatableHtml += '<tbody>';
|
||||
|
||||
if(val.length > 0 && isBase64(val))
|
||||
{
|
||||
val = atob(val)
|
||||
// console.log(val);
|
||||
val = JSON.parse(val)
|
||||
}else{
|
||||
// init empty
|
||||
val = [emptyVal]
|
||||
}
|
||||
|
||||
let index = 0;
|
||||
val.forEach(rowData => {
|
||||
datatableHtml += `<tr my-index="${index}">`;
|
||||
|
||||
let j = 0;
|
||||
columnSettings.forEach(set => {
|
||||
// Extract the value for the current column based on the new structure
|
||||
let columnOverrideValue = rowData[j] && Object.values(rowData[j])[0];
|
||||
|
||||
if(columnOverrideValue == undefined)
|
||||
{
|
||||
columnOverrideValue = ""
|
||||
}
|
||||
|
||||
// Create unique key to prevent dropdown data duplication
|
||||
const oldKey = set["setKey"];
|
||||
set["setKey"] = oldKey + "_" + index;
|
||||
|
||||
// Generate the cell HTML using the extracted value
|
||||
const cellHtml = generateFormHtml(
|
||||
settingsData,
|
||||
set,
|
||||
columnOverrideValue.toString(),
|
||||
set["setOptions"],
|
||||
oldKey
|
||||
);
|
||||
datatableHtml += `<td> <div class="input-group"> ${cellHtml} </div></td>`;
|
||||
|
||||
// Restore the original key
|
||||
set["setKey"] = oldKey;
|
||||
|
||||
j++;
|
||||
});
|
||||
datatableHtml += '</tr>';
|
||||
index++;
|
||||
});
|
||||
|
||||
|
||||
datatableHtml += '</tbody></table>';
|
||||
|
||||
inputHtml += datatableHtml;
|
||||
|
||||
// Initialize DataTable with jQuery
|
||||
$(document).ready(() => {
|
||||
$(`#${tableId}`).DataTable({
|
||||
ordering: false, // Disables sorting on all columns
|
||||
searching: false, // Disables the search box
|
||||
dom: "<'top'rt><'bottom'ipl>", // Move length dropdown to the bottom
|
||||
});
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn(`🟥 Unknown element type: ${elementType}`);
|
||||
@@ -991,10 +1178,6 @@ function generateFormHtml(set, overrideValue) {
|
||||
|
||||
// Generate event HTML if applicable
|
||||
let eventsHtml = '';
|
||||
|
||||
// console.log(setTypeObject);
|
||||
|
||||
// console.log(set);
|
||||
|
||||
const eventsList = createArray(set['setEvents']);
|
||||
// inline buttons events
|
||||
@@ -1007,7 +1190,7 @@ function generateFormHtml(set, overrideValue) {
|
||||
id="${`${event}_${setKey}`}"
|
||||
data-myparam-setkey="${setKey}"
|
||||
data-myparam="${setKey}"
|
||||
data-myparam-plugin="${setTypeObject.prefix || ''}"
|
||||
data-myparam-plugin="${setKey.split('_')[0] || ''}"
|
||||
data-myevent="${event}"
|
||||
onclick="execute_settingEvent(this)">
|
||||
<i title="${getString(event + "_event_tooltip")}" class="fa ${getString(event + "_event_icon")}"></i>
|
||||
@@ -1019,4 +1202,68 @@ function generateFormHtml(set, overrideValue) {
|
||||
return inputHtml + eventsHtml;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------
|
||||
// Return the setting object by setKey
|
||||
function getSetObject(settingsData, setKey) {
|
||||
|
||||
found = false;
|
||||
result = ""
|
||||
|
||||
settingsData.forEach(function(set) {
|
||||
|
||||
if (set.setKey == setKey) {
|
||||
// console.log(set);
|
||||
|
||||
result = set;
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if(result == "")
|
||||
{
|
||||
console.error(settingsData);
|
||||
console.error(`Setting not found: ${setKey}`);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ---------------------------------------
|
||||
// Collect DataTable data
|
||||
function collectTableData(tableSelector) {
|
||||
const table = $(tableSelector).DataTable();
|
||||
|
||||
let tableData = [];
|
||||
|
||||
table.rows().every(function () {
|
||||
const rowData = [];
|
||||
const cells = $(this.node()).find('td');
|
||||
|
||||
cells.each((index, cell) => {
|
||||
const input = $(cell).find('input, select, textarea');
|
||||
|
||||
if (input.length) {
|
||||
if (input.attr('type') === 'checkbox') {
|
||||
// For checkboxes, check if they are checked
|
||||
rowData[index] = { [input.attr("my-originalsetkey")] : input.prop('checked') };
|
||||
} else {
|
||||
// Generic sync for other inputs (text, select, textarea)
|
||||
rowData[index] = { [input.attr("my-originalsetkey")] : input.val().replace(/'/g, "").replace(/"/g, "") };
|
||||
}
|
||||
} else {
|
||||
// Handle plain text
|
||||
// rowData[index] = { [input.attr("my-originalsetkey")] : $(cell).text()};
|
||||
console.log(`Nothig to collect: ${$(cell).html()}`)
|
||||
}
|
||||
});
|
||||
|
||||
tableData.push(rowData);
|
||||
});
|
||||
|
||||
return tableData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -97,12 +97,54 @@ function generateApiToken(elem, length) {
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------
|
||||
// Generate a random N-byte hexadecimal key
|
||||
function getRandomBytes(elem, length) {
|
||||
|
||||
// Retrieve and parse custom parameters from the element
|
||||
let params = $(elem).attr("my-customparams")?.split(',').map(param => param.trim());
|
||||
if (params && params.length >= 1) {
|
||||
var targetElementID = params[0]; // Get the target element's ID
|
||||
}
|
||||
|
||||
let targetElement = $('#' + targetElementID);
|
||||
|
||||
// Generate random bytes
|
||||
const array = new Uint8Array(length);
|
||||
window.crypto.getRandomValues(array);
|
||||
|
||||
// Convert bytes to hexadecimal string
|
||||
let hexString = Array.from(array, byte =>
|
||||
byte.toString(16).padStart(2, '0')
|
||||
).join('');
|
||||
|
||||
// Format hexadecimal string with hyphens
|
||||
let formattedHex = hexString.match(/.{1,2}/g).join('-');
|
||||
|
||||
console.log(formattedHex);
|
||||
// console.log($(`#${targetInput}`).val());
|
||||
|
||||
// Set the formatted key value to the input field
|
||||
targetElement.val(formattedHex);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// ----------------------------------------------
|
||||
// Updates the icon preview
|
||||
function updateAllIconPreviews() {
|
||||
$(".iconInputVal").each((index, el)=>{
|
||||
updateIconPreview(el)
|
||||
})
|
||||
}
|
||||
|
||||
// ----------------------------------------------
|
||||
// Updates the icon preview
|
||||
function updateIconPreview(elem) {
|
||||
const targetElement = $('[my-customid="NEWDEV_devIcon_preview"]');
|
||||
const iconInput = $("#NEWDEV_devIcon");
|
||||
|
||||
const previewSpan = $(elem).parent().find(".iconPreview");
|
||||
const iconInput = $(elem);
|
||||
|
||||
let attempts = 0;
|
||||
|
||||
@@ -110,10 +152,10 @@ function updateIconPreview(elem) {
|
||||
let value = iconInput.val();
|
||||
|
||||
if (value) {
|
||||
targetElement.html(atob(value));
|
||||
previewSpan.html(atob(value));
|
||||
iconInput.off('change input').on('change input', function () {
|
||||
let newValue = $(this).val();
|
||||
targetElement.html(atob(newValue));
|
||||
let newValue = $(elem).val();
|
||||
previewSpan.html(atob(newValue));
|
||||
});
|
||||
return; // Stop retrying if successful
|
||||
}
|
||||
@@ -314,7 +356,7 @@ function updateModalState() {
|
||||
setTimeout(function() {
|
||||
// Fetch the content from the log file using an AJAX request
|
||||
$.ajax({
|
||||
url: '/log/execution_queue.log',
|
||||
url: '/php/server/query_logs.php?file=execution_queue.log',
|
||||
type: 'GET',
|
||||
success: function(data) {
|
||||
// Update the content of the HTML element (e.g., a div with id 'logContent')
|
||||
@@ -449,7 +491,7 @@ function showIconSelection() {
|
||||
iconDiv.addEventListener('click', () => {
|
||||
selectElement.value = value; // Update the select element value
|
||||
$(`#${modalId}`).modal('hide'); // Hide the modal
|
||||
updateIconPreview();
|
||||
updateAllIconPreviews();
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -528,7 +570,7 @@ window.addEventListener("load", function() {
|
||||
// try to initialize
|
||||
setTimeout(() => {
|
||||
initSelect2()
|
||||
initializeiCheck();
|
||||
// initializeiCheck();
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
|
||||
20
front/lib/AdminLTE/dist/css/LICENSE
vendored
Executable file
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014-2023 ColorlibHQ
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
1
front/lib/AdminLTE/dist/js/adminlte.js.map
vendored
Executable file
1
front/lib/AdminLTE/dist/js/adminlte.min.js.map
vendored
Executable file
147
front/lib/AdminLTE/dist/js/pages/dashboard3.js
vendored
Executable file
@@ -0,0 +1,147 @@
|
||||
/* global Chart:false */
|
||||
|
||||
$(function () {
|
||||
'use strict'
|
||||
|
||||
var ticksStyle = {
|
||||
fontColor: '#495057',
|
||||
fontStyle: 'bold'
|
||||
}
|
||||
|
||||
var mode = 'index'
|
||||
var intersect = true
|
||||
|
||||
var $salesChart = $('#sales-chart')
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
var salesChart = new Chart($salesChart, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: ['JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'],
|
||||
datasets: [
|
||||
{
|
||||
backgroundColor: '#007bff',
|
||||
borderColor: '#007bff',
|
||||
data: [1000, 2000, 3000, 2500, 2700, 2500, 3000]
|
||||
},
|
||||
{
|
||||
backgroundColor: '#ced4da',
|
||||
borderColor: '#ced4da',
|
||||
data: [700, 1700, 2700, 2000, 1800, 1500, 2000]
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
maintainAspectRatio: false,
|
||||
tooltips: {
|
||||
mode: mode,
|
||||
intersect: intersect
|
||||
},
|
||||
hover: {
|
||||
mode: mode,
|
||||
intersect: intersect
|
||||
},
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
scales: {
|
||||
yAxes: [{
|
||||
// display: false,
|
||||
gridLines: {
|
||||
display: true,
|
||||
lineWidth: '4px',
|
||||
color: 'rgba(0, 0, 0, .2)',
|
||||
zeroLineColor: 'transparent'
|
||||
},
|
||||
ticks: $.extend({
|
||||
beginAtZero: true,
|
||||
|
||||
// Include a dollar sign in the ticks
|
||||
callback: function (value) {
|
||||
if (value >= 1000) {
|
||||
value /= 1000
|
||||
value += 'k'
|
||||
}
|
||||
|
||||
return '$' + value
|
||||
}
|
||||
}, ticksStyle)
|
||||
}],
|
||||
xAxes: [{
|
||||
display: true,
|
||||
gridLines: {
|
||||
display: false
|
||||
},
|
||||
ticks: ticksStyle
|
||||
}]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
var $visitorsChart = $('#visitors-chart')
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
var visitorsChart = new Chart($visitorsChart, {
|
||||
data: {
|
||||
labels: ['18th', '20th', '22nd', '24th', '26th', '28th', '30th'],
|
||||
datasets: [{
|
||||
type: 'line',
|
||||
data: [100, 120, 170, 167, 180, 177, 160],
|
||||
backgroundColor: 'transparent',
|
||||
borderColor: '#007bff',
|
||||
pointBorderColor: '#007bff',
|
||||
pointBackgroundColor: '#007bff',
|
||||
fill: false
|
||||
// pointHoverBackgroundColor: '#007bff',
|
||||
// pointHoverBorderColor : '#007bff'
|
||||
},
|
||||
{
|
||||
type: 'line',
|
||||
data: [60, 80, 70, 67, 80, 77, 100],
|
||||
backgroundColor: 'tansparent',
|
||||
borderColor: '#ced4da',
|
||||
pointBorderColor: '#ced4da',
|
||||
pointBackgroundColor: '#ced4da',
|
||||
fill: false
|
||||
// pointHoverBackgroundColor: '#ced4da',
|
||||
// pointHoverBorderColor : '#ced4da'
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
maintainAspectRatio: false,
|
||||
tooltips: {
|
||||
mode: mode,
|
||||
intersect: intersect
|
||||
},
|
||||
hover: {
|
||||
mode: mode,
|
||||
intersect: intersect
|
||||
},
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
scales: {
|
||||
yAxes: [{
|
||||
// display: false,
|
||||
gridLines: {
|
||||
display: true,
|
||||
lineWidth: '4px',
|
||||
color: 'rgba(0, 0, 0, .2)',
|
||||
zeroLineColor: 'transparent'
|
||||
},
|
||||
ticks: $.extend({
|
||||
beginAtZero: true,
|
||||
suggestedMax: 200
|
||||
}, ticksStyle)
|
||||
}],
|
||||
xAxes: [{
|
||||
display: true,
|
||||
gridLines: {
|
||||
display: false
|
||||
},
|
||||
ticks: ticksStyle
|
||||
}]
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// lgtm [js/unused-local-variable]
|
||||
48
front/lib/bcrypt/bcrypt.min.js
vendored
Executable file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
bcrypt.js (c) 2013 Daniel Wirtz <dcode@dcode.io>
|
||||
Released under the Apache License, Version 2.0
|
||||
see: https://github.com/dcodeIO/bcrypt.js for details
|
||||
*/
|
||||
(function(u,r){"function"===typeof define&&define.amd?define([],r):"function"===typeof require&&"object"===typeof module&&module&&module.exports?module.exports=r():(u.dcodeIO=u.dcodeIO||{}).bcrypt=r()})(this,function(){function u(e){if("undefined"!==typeof module&&module&&module.exports)try{return require("crypto").randomBytes(e)}catch(d){}try{var c;(self.crypto||self.msCrypto).getRandomValues(c=new Uint32Array(e));return Array.prototype.slice.call(c)}catch(b){}if(!w)throw Error("Neither WebCryptoAPI nor a crypto module is available. Use bcrypt.setRandomFallback to set an alternative");
|
||||
return w(e)}function r(e,d){for(var c=0,b=0,a=0,f=e.length;a<f;++a)e.charCodeAt(a)===d.charCodeAt(a)?++c:++b;return 0>c?!1:0===b}function H(e){var d=[],c=0;I.encodeUTF16toUTF8(function(){return c>=e.length?null:e.charCodeAt(c++)},function(b){d.push(b)});return d}function x(e,d){var c=0,b=[],a,f;if(0>=d||d>e.length)throw Error("Illegal len: "+d);for(;c<d;){a=e[c++]&255;b.push(s[a>>2&63]);a=(a&3)<<4;if(c>=d){b.push(s[a&63]);break}f=e[c++]&255;a|=f>>4&15;b.push(s[a&63]);a=(f&15)<<2;if(c>=d){b.push(s[a&
|
||||
63]);break}f=e[c++]&255;a|=f>>6&3;b.push(s[a&63]);b.push(s[f&63])}return b.join("")}function B(e,d){var c=0,b=e.length,a=0,f=[],g,m,h;if(0>=d)throw Error("Illegal len: "+d);for(;c<b-1&&a<d;){h=e.charCodeAt(c++);g=h<q.length?q[h]:-1;h=e.charCodeAt(c++);m=h<q.length?q[h]:-1;if(-1==g||-1==m)break;h=g<<2>>>0;h|=(m&48)>>4;f.push(z(h));if(++a>=d||c>=b)break;h=e.charCodeAt(c++);g=h<q.length?q[h]:-1;if(-1==g)break;h=(m&15)<<4>>>0;h|=(g&60)>>2;f.push(z(h));if(++a>=d||c>=b)break;h=e.charCodeAt(c++);m=h<q.length?
|
||||
q[h]:-1;h=(g&3)<<6>>>0;h|=m;f.push(z(h));++a}b=[];for(c=0;c<a;c++)b.push(f[c].charCodeAt(0));return b}function v(e,d,c,b){var a,f=e[d],g=e[d+1],f=f^c[0];a=b[f>>>24];a+=b[256|f>>16&255];a^=b[512|f>>8&255];a+=b[768|f&255];g=g^a^c[1];a=b[g>>>24];a+=b[256|g>>16&255];a^=b[512|g>>8&255];a+=b[768|g&255];f=f^a^c[2];a=b[f>>>24];a+=b[256|f>>16&255];a^=b[512|f>>8&255];a+=b[768|f&255];g=g^a^c[3];a=b[g>>>24];a+=b[256|g>>16&255];a^=b[512|g>>8&255];a+=b[768|g&255];f=f^a^c[4];a=b[f>>>24];a+=b[256|f>>16&255];a^=b[512|
|
||||
f>>8&255];a+=b[768|f&255];g=g^a^c[5];a=b[g>>>24];a+=b[256|g>>16&255];a^=b[512|g>>8&255];a+=b[768|g&255];f=f^a^c[6];a=b[f>>>24];a+=b[256|f>>16&255];a^=b[512|f>>8&255];a+=b[768|f&255];g=g^a^c[7];a=b[g>>>24];a+=b[256|g>>16&255];a^=b[512|g>>8&255];a+=b[768|g&255];f=f^a^c[8];a=b[f>>>24];a+=b[256|f>>16&255];a^=b[512|f>>8&255];a+=b[768|f&255];g=g^a^c[9];a=b[g>>>24];a+=b[256|g>>16&255];a^=b[512|g>>8&255];a+=b[768|g&255];f=f^a^c[10];a=b[f>>>24];a+=b[256|f>>16&255];a^=b[512|f>>8&255];a+=b[768|f&255];g=g^a^
|
||||
c[11];a=b[g>>>24];a+=b[256|g>>16&255];a^=b[512|g>>8&255];a+=b[768|g&255];f=f^a^c[12];a=b[f>>>24];a+=b[256|f>>16&255];a^=b[512|f>>8&255];a+=b[768|f&255];g=g^a^c[13];a=b[g>>>24];a+=b[256|g>>16&255];a^=b[512|g>>8&255];a+=b[768|g&255];f=f^a^c[14];a=b[f>>>24];a+=b[256|f>>16&255];a^=b[512|f>>8&255];a+=b[768|f&255];g=g^a^c[15];a=b[g>>>24];a+=b[256|g>>16&255];a^=b[512|g>>8&255];a+=b[768|g&255];f=f^a^c[16];e[d]=g^c[17];e[d+1]=f;return e}function t(e,d){for(var c=0,b=0;4>c;++c)b=b<<8|e[d]&255,d=(d+1)%e.length;
|
||||
return{key:b,offp:d}}function C(e,d,c){for(var b=0,a=[0,0],f=d.length,g=c.length,m,h=0;h<f;h++)m=t(e,b),b=m.offp,d[h]^=m.key;for(h=0;h<f;h+=2)a=v(a,0,d,c),d[h]=a[0],d[h+1]=a[1];for(h=0;h<g;h+=2)a=v(a,0,d,c),c[h]=a[0],c[h+1]=a[1]}function J(e,d,c,b){for(var a=0,f=[0,0],g=c.length,m=b.length,h,l=0;l<g;l++)h=t(d,a),a=h.offp,c[l]^=h.key;for(l=a=0;l<g;l+=2)h=t(e,a),a=h.offp,f[0]^=h.key,h=t(e,a),a=h.offp,f[1]^=h.key,f=v(f,0,c,b),c[l]=f[0],c[l+1]=f[1];for(l=0;l<m;l+=2)h=t(e,a),a=h.offp,f[0]^=h.key,h=t(e,
|
||||
a),a=h.offp,f[1]^=h.key,f=v(f,0,c,b),b[l]=f[0],b[l+1]=f[1]}function D(e,d,c,b,a){function f(){a&&a(n/c);if(n<c)for(var h=Date.now();n<c&&!(n+=1,C(e,l,k),C(d,l,k),100<Date.now()-h););else{for(n=0;64>n;n++)for(y=0;y<m>>1;y++)v(g,y<<1,l,k);h=[];for(n=0;n<m;n++)h.push((g[n]>>24&255)>>>0),h.push((g[n]>>16&255)>>>0),h.push((g[n]>>8&255)>>>0),h.push((g[n]&255)>>>0);if(b){b(null,h);return}return h}b&&p(f)}var g=E.slice(),m=g.length,h;if(4>c||31<c){h=Error("Illegal number of rounds (4-31): "+c);if(b){p(b.bind(this,
|
||||
h));return}throw h;}if(16!==d.length){h=Error("Illegal salt length: "+d.length+" != 16");if(b){p(b.bind(this,h));return}throw h;}c=1<<c>>>0;var l,k,n=0,y;Int32Array?(l=new Int32Array(F),k=new Int32Array(G)):(l=F.slice(),k=G.slice());J(d,e,l,k);if("undefined"!==typeof b)f();else for(;;)if("undefined"!==typeof(h=f()))return h||[]}function A(e,d,c,b){function a(a){var b=[];b.push("$2");"a"<=f&&b.push(f);b.push("$");10>l&&b.push("0");b.push(l.toString());b.push("$");b.push(x(k,k.length));b.push(x(a,4*
|
||||
E.length-1));return b.join("")}if("string"!==typeof e||"string"!==typeof d){b=Error("Invalid string / salt: Not a string");if(c){p(c.bind(this,b));return}throw b;}var f,g;if("$"!==d.charAt(0)||"2"!==d.charAt(1)){b=Error("Invalid salt version: "+d.substring(0,2));if(c){p(c.bind(this,b));return}throw b;}if("$"===d.charAt(2))f=String.fromCharCode(0),g=3;else{f=d.charAt(2);if("a"!==f&&"b"!==f&&"y"!==f||"$"!==d.charAt(3)){b=Error("Invalid salt revision: "+d.substring(2,4));if(c){p(c.bind(this,b));return}throw b;
|
||||
}g=4}if("$"<d.charAt(g+2)){b=Error("Missing salt rounds");if(c){p(c.bind(this,b));return}throw b;}var m=10*parseInt(d.substring(g,g+1),10),h=parseInt(d.substring(g+1,g+2),10),l=m+h;d=d.substring(g+3,g+25);e=H(e+("a"<=f?"\x00":""));var k=B(d,16);if("undefined"==typeof c)return a(D(e,k,l));D(e,k,l,function(b,d){b?c(b,null):c(null,a(d))},b)}var k={},w=null;try{u(1)}catch(K){}w=null;k.setRandomFallback=function(e){w=e};k.genSaltSync=function(e,d){e=e||10;if("number"!==typeof e)throw Error("Illegal arguments: "+
|
||||
typeof e+", "+typeof d);4>e?e=4:31<e&&(e=31);var c=[];c.push("$2a$");10>e&&c.push("0");c.push(e.toString());c.push("$");c.push(x(u(16),16));return c.join("")};k.genSalt=function(e,d,c){function b(a){p(function(){try{a(null,k.genSaltSync(e))}catch(b){a(b)}})}"function"===typeof d&&(c=d,d=void 0);"function"===typeof e&&(c=e,e=void 0);if("undefined"===typeof e)e=10;else if("number"!==typeof e)throw Error("illegal arguments: "+typeof e);if(c){if("function"!==typeof c)throw Error("Illegal callback: "+
|
||||
typeof c);b(c)}else return new Promise(function(a,c){b(function(b,d){b?c(b):a(d)})})};k.hashSync=function(e,d){"undefined"===typeof d&&(d=10);"number"===typeof d&&(d=k.genSaltSync(d));if("string"!==typeof e||"string"!==typeof d)throw Error("Illegal arguments: "+typeof e+", "+typeof d);return A(e,d)};k.hash=function(e,d,c,b){function a(a){"string"===typeof e&&"number"===typeof d?k.genSalt(d,function(c,d){A(e,d,a,b)}):"string"===typeof e&&"string"===typeof d?A(e,d,a,b):p(a.bind(this,Error("Illegal arguments: "+
|
||||
typeof e+", "+typeof d)))}if(c){if("function"!==typeof c)throw Error("Illegal callback: "+typeof c);a(c)}else return new Promise(function(b,c){a(function(a,d){a?c(a):b(d)})})};k.compareSync=function(e,d){if("string"!==typeof e||"string"!==typeof d)throw Error("Illegal arguments: "+typeof e+", "+typeof d);return 60!==d.length?!1:r(k.hashSync(e,d.substr(0,d.length-31)),d)};k.compare=function(e,d,c,b){function a(a){"string"!==typeof e||"string"!==typeof d?p(a.bind(this,Error("Illegal arguments: "+typeof e+
|
||||
", "+typeof d))):60!==d.length?p(a.bind(this,null,!1)):k.hash(e,d.substr(0,29),function(b,c){b?a(b):a(null,r(c,d))},b)}if(c){if("function"!==typeof c)throw Error("Illegal callback: "+typeof c);a(c)}else return new Promise(function(b,c){a(function(a,d){a?c(a):b(d)})})};k.getRounds=function(e){if("string"!==typeof e)throw Error("Illegal arguments: "+typeof e);return parseInt(e.split("$")[2],10)};k.getSalt=function(e){if("string"!==typeof e)throw Error("Illegal arguments: "+typeof e);if(60!==e.length)throw Error("Illegal hash length: "+
|
||||
e.length+" != 60");return e.substring(0,29)};var p="undefined"!==typeof process&&process&&"function"===typeof process.nextTick?"function"===typeof setImmediate?setImmediate:process.nextTick:setTimeout,s="./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".split(""),q=[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,1,54,55,56,57,58,59,60,61,62,63,-1,-1,-1,-1,-1,-1,-1,2,3,4,5,6,7,8,9,10,11,12,
|
||||
13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,-1,-1,-1,-1,-1,-1,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,-1,-1,-1,-1,-1],z=String.fromCharCode,I=function(){var e={MAX_CODEPOINT:1114111,encodeUTF8:function(d,c){var b=null;"number"===typeof d&&(b=d,d=function(){return null});for(;null!==b||null!==(b=d());)128>b?c(b&127):(2048>b?c(b>>6&31|192):(65536>b?c(b>>12&15|224):(c(b>>18&7|240),c(b>>12&63|128)),c(b>>6&63|128)),c(b&63|128)),b=null},decodeUTF8:function(d,c){for(var b,
|
||||
a,f,e,k=function(a){a=a.slice(0,a.indexOf(null));var b=Error(a.toString());b.name="TruncatedError";b.bytes=a;throw b;};null!==(b=d());)if(0===(b&128))c(b);else if(192===(b&224))null===(a=d())&&k([b,a]),c((b&31)<<6|a&63);else if(224===(b&240))null!==(a=d())&&null!==(f=d())||k([b,a,f]),c((b&15)<<12|(a&63)<<6|f&63);else if(240===(b&248))null!==(a=d())&&null!==(f=d())&&null!==(e=d())||k([b,a,f,e]),c((b&7)<<18|(a&63)<<12|(f&63)<<6|e&63);else throw RangeError("Illegal starting byte: "+b);},UTF16toUTF8:function(d,
|
||||
c){for(var b,a=null;null!==(b=null!==a?a:d());)55296<=b&&57343>=b&&null!==(a=d())&&56320<=a&&57343>=a?(c(1024*(b-55296)+a-56320+65536),a=null):c(b);null!==a&&c(a)},UTF8toUTF16:function(d,c){var b=null;"number"===typeof d&&(b=d,d=function(){return null});for(;null!==b||null!==(b=d());)65535>=b?c(b):(b-=65536,c((b>>10)+55296),c(b%1024+56320)),b=null},encodeUTF16toUTF8:function(d,c){e.UTF16toUTF8(d,function(b){e.encodeUTF8(b,c)})},decodeUTF8toUTF16:function(d,c){e.decodeUTF8(d,function(b){e.UTF8toUTF16(b,
|
||||
c)})},calculateCodePoint:function(d){return 128>d?1:2048>d?2:65536>d?3:4},calculateUTF8:function(d){for(var c,b=0;null!==(c=d());)b+=e.calculateCodePoint(c);return b},calculateUTF16asUTF8:function(d){var c=0,b=0;e.UTF16toUTF8(d,function(a){++c;b+=e.calculateCodePoint(a)});return[c,b]}};return e}();Date.now=Date.now||function(){return+new Date};var F=[608135816,2242054355,320440878,57701188,2752067618,698298832,137296536,3964562569,1160258022,953160567,3193202383,887688300,3232508343,3380367581,1065670069,
|
||||
3041331479,2450970073,2306472731],G=[3509652390,2564797868,805139163,3491422135,3101798381,1780907670,3128725573,4046225305,614570311,3012652279,134345442,2240740374,1667834072,1901547113,2757295779,4103290238,227898511,1921955416,1904987480,2182433518,2069144605,3260701109,2620446009,720527379,3318853667,677414384,3393288472,3101374703,2390351024,1614419982,1822297739,2954791486,3608508353,3174124327,2024746970,1432378464,3864339955,2857741204,1464375394,1676153920,1439316330,715854006,3033291828,
|
||||
289532110,2706671279,2087905683,3018724369,1668267050,732546397,1947742710,3462151702,2609353502,2950085171,1814351708,2050118529,680887927,999245976,1800124847,3300911131,1713906067,1641548236,4213287313,1216130144,1575780402,4018429277,3917837745,3693486850,3949271944,596196993,3549867205,258830323,2213823033,772490370,2760122372,1774776394,2652871518,566650946,4142492826,1728879713,2882767088,1783734482,3629395816,2517608232,2874225571,1861159788,326777828,3124490320,2130389656,2716951837,967770486,
|
||||
1724537150,2185432712,2364442137,1164943284,2105845187,998989502,3765401048,2244026483,1075463327,1455516326,1322494562,910128902,469688178,1117454909,936433444,3490320968,3675253459,1240580251,122909385,2157517691,634681816,4142456567,3825094682,3061402683,2540495037,79693498,3249098678,1084186820,1583128258,426386531,1761308591,1047286709,322548459,995290223,1845252383,2603652396,3431023940,2942221577,3202600964,3727903485,1712269319,422464435,3234572375,1170764815,3523960633,3117677531,1434042557,
|
||||
442511882,3600875718,1076654713,1738483198,4213154764,2393238008,3677496056,1014306527,4251020053,793779912,2902807211,842905082,4246964064,1395751752,1040244610,2656851899,3396308128,445077038,3742853595,3577915638,679411651,2892444358,2354009459,1767581616,3150600392,3791627101,3102740896,284835224,4246832056,1258075500,768725851,2589189241,3069724005,3532540348,1274779536,3789419226,2764799539,1660621633,3471099624,4011903706,913787905,3497959166,737222580,2514213453,2928710040,3937242737,1804850592,
|
||||
3499020752,2949064160,2386320175,2390070455,2415321851,4061277028,2290661394,2416832540,1336762016,1754252060,3520065937,3014181293,791618072,3188594551,3933548030,2332172193,3852520463,3043980520,413987798,3465142937,3030929376,4245938359,2093235073,3534596313,375366246,2157278981,2479649556,555357303,3870105701,2008414854,3344188149,4221384143,3956125452,2067696032,3594591187,2921233993,2428461,544322398,577241275,1471733935,610547355,4027169054,1432588573,1507829418,2025931657,3646575487,545086370,
|
||||
48609733,2200306550,1653985193,298326376,1316178497,3007786442,2064951626,458293330,2589141269,3591329599,3164325604,727753846,2179363840,146436021,1461446943,4069977195,705550613,3059967265,3887724982,4281599278,3313849956,1404054877,2845806497,146425753,1854211946,1266315497,3048417604,3681880366,3289982499,290971E4,1235738493,2632868024,2414719590,3970600049,1771706367,1449415276,3266420449,422970021,1963543593,2690192192,3826793022,1062508698,1531092325,1804592342,2583117782,2714934279,4024971509,
|
||||
1294809318,4028980673,1289560198,2221992742,1669523910,35572830,157838143,1052438473,1016535060,1802137761,1753167236,1386275462,3080475397,2857371447,1040679964,2145300060,2390574316,1461121720,2956646967,4031777805,4028374788,33600511,2920084762,1018524850,629373528,3691585981,3515945977,2091462646,2486323059,586499841,988145025,935516892,3367335476,2599673255,2839830854,265290510,3972581182,2759138881,3795373465,1005194799,847297441,406762289,1314163512,1332590856,1866599683,4127851711,750260880,
|
||||
613907577,1450815602,3165620655,3734664991,3650291728,3012275730,3704569646,1427272223,778793252,1343938022,2676280711,2052605720,1946737175,3164576444,3914038668,3967478842,3682934266,1661551462,3294938066,4011595847,840292616,3712170807,616741398,312560963,711312465,1351876610,322626781,1910503582,271666773,2175563734,1594956187,70604529,3617834859,1007753275,1495573769,4069517037,2549218298,2663038764,504708206,2263041392,3941167025,2249088522,1514023603,1998579484,1312622330,694541497,2582060303,
|
||||
2151582166,1382467621,776784248,2618340202,3323268794,2497899128,2784771155,503983604,4076293799,907881277,423175695,432175456,1378068232,4145222326,3954048622,3938656102,3820766613,2793130115,2977904593,26017576,3274890735,3194772133,1700274565,1756076034,4006520079,3677328699,720338349,1533947780,354530856,688349552,3973924725,1637815568,332179504,3949051286,53804574,2852348879,3044236432,1282449977,3583942155,3416972820,4006381244,1617046695,2628476075,3002303598,1686838959,431878346,2686675385,
|
||||
1700445008,1080580658,1009431731,832498133,3223435511,2605976345,2271191193,2516031870,1648197032,4164389018,2548247927,300782431,375919233,238389289,3353747414,2531188641,2019080857,1475708069,455242339,2609103871,448939670,3451063019,1395535956,2413381860,1841049896,1491858159,885456874,4264095073,4001119347,1565136089,3898914787,1108368660,540939232,1173283510,2745871338,3681308437,4207628240,3343053890,4016749493,1699691293,1103962373,3625875870,2256883143,3830138730,1031889488,3479347698,1535977030,
|
||||
4236805024,3251091107,2132092099,1774941330,1199868427,1452454533,157007616,2904115357,342012276,595725824,1480756522,206960106,497939518,591360097,863170706,2375253569,3596610801,1814182875,2094937945,3421402208,1082520231,3463918190,2785509508,435703966,3908032597,1641649973,2842273706,3305899714,1510255612,2148256476,2655287854,3276092548,4258621189,236887753,3681803219,274041037,1734335097,3815195456,3317970021,1899903192,1026095262,4050517792,356393447,2410691914,3873677099,3682840055,3913112168,
|
||||
2491498743,4132185628,2489919796,1091903735,1979897079,3170134830,3567386728,3557303409,857797738,1136121015,1342202287,507115054,2535736646,337727348,3213592640,1301675037,2528481711,1895095763,1721773893,3216771564,62756741,2142006736,835421444,2531993523,1442658625,3659876326,2882144922,676362277,1392781812,170690266,3921047035,1759253602,3611846912,1745797284,664899054,1329594018,3901205900,3045908486,2062866102,2865634940,3543621612,3464012697,1080764994,553557557,3656615353,3996768171,991055499,
|
||||
499776247,1265440854,648242737,3940784050,980351604,3713745714,1749149687,3396870395,4211799374,3640570775,1161844396,3125318951,1431517754,545492359,4268468663,3499529547,1437099964,2702547544,3433638243,2581715763,2787789398,1060185593,1593081372,2418618748,4260947970,69676912,2159744348,86519011,2512459080,3838209314,1220612927,3339683548,133810670,1090789135,1078426020,1569222167,845107691,3583754449,4072456591,1091646820,628848692,1613405280,3757631651,526609435,236106946,48312990,2942717905,
|
||||
3402727701,1797494240,859738849,992217954,4005476642,2243076622,3870952857,3732016268,765654824,3490871365,2511836413,1685915746,3888969200,1414112111,2273134842,3281911079,4080962846,172450625,2569994100,980381355,4109958455,2819808352,2716589560,2568741196,3681446669,3329971472,1835478071,660984891,3704678404,4045999559,3422617507,3040415634,1762651403,1719377915,3470491036,2693910283,3642056355,3138596744,1364962596,2073328063,1983633131,926494387,3423689081,2150032023,4096667949,1749200295,3328846651,
|
||||
309677260,2016342300,1779581495,3079819751,111262694,1274766160,443224088,298511866,1025883608,3806446537,1145181785,168956806,3641502830,3584813610,1689216846,3666258015,3200248200,1692713982,2646376535,4042768518,1618508792,1610833997,3523052358,4130873264,2001055236,3610705100,2202168115,4028541809,2961195399,1006657119,2006996926,3186142756,1430667929,3210227297,1314452623,4074634658,4101304120,2273951170,1399257539,3367210612,3027628629,1190975929,2062231137,2333990788,2221543033,2438960610,
|
||||
1181637006,548689776,2362791313,3372408396,3104550113,3145860560,296247880,1970579870,3078560182,3769228297,1714227617,3291629107,3898220290,166772364,1251581989,493813264,448347421,195405023,2709975567,677966185,3703036547,1463355134,2715995803,1338867538,1343315457,2802222074,2684532164,233230375,2599980071,2000651841,3277868038,1638401717,4028070440,3237316320,6314154,819756386,300326615,590932579,1405279636,3267499572,3150704214,2428286686,3959192993,3461946742,1862657033,1266418056,963775037,
|
||||
2089974820,2263052895,1917689273,448879540,3550394620,3981727096,150775221,3627908307,1303187396,508620638,2975983352,2726630617,1817252668,1876281319,1457606340,908771278,3720792119,3617206836,2455994898,1729034894,1080033504,976866871,3556439503,2881648439,1522871579,1555064734,1336096578,3548522304,2579274686,3574697629,3205460757,3593280638,3338716283,3079412587,564236357,2993598910,1781952180,1464380207,3163844217,3332601554,1699332808,1393555694,1183702653,3581086237,1288719814,691649499,2847557200,
|
||||
2895455976,3193889540,2717570544,1781354906,1676643554,2592534050,3230253752,1126444790,2770207658,2633158820,2210423226,2615765581,2414155088,3127139286,673620729,2805611233,1269405062,4015350505,3341807571,4149409754,1057255273,2012875353,2162469141,2276492801,2601117357,993977747,3918593370,2654263191,753973209,36408145,2530585658,25011837,3520020182,2088578344,530523599,2918365339,1524020338,1518925132,3760827505,3759777254,1202760957,3985898139,3906192525,674977740,4174734889,2031300136,2019492241,
|
||||
3983892565,4153806404,3822280332,352677332,2297720250,60907813,90501309,3286998549,1016092578,2535922412,2839152426,457141659,509813237,4120667899,652014361,1966332200,2975202805,55981186,2327461051,676427537,3255491064,2882294119,3433927263,1307055953,942726286,933058658,2468411793,3933900994,4215176142,1361170020,2001714738,2830558078,3274259782,1222529897,1679025792,2729314320,3714953764,1770335741,151462246,3013232138,1682292957,1483529935,471910574,1539241949,458788160,3436315007,1807016891,
|
||||
3718408830,978976581,1043663428,3165965781,1927990952,4200891579,2372276910,3208408903,3533431907,1412390302,2931980059,4132332400,1947078029,3881505623,4168226417,2941484381,1077988104,1320477388,886195818,18198404,3786409E3,2509781533,112762804,3463356488,1866414978,891333506,18488651,661792760,1628790961,3885187036,3141171499,876946877,2693282273,1372485963,791857591,2686433993,3759982718,3167212022,3472953795,2716379847,445679433,3561995674,3504004811,3574258232,54117162,3331405415,2381918588,
|
||||
3769707343,4154350007,1140177722,4074052095,668550556,3214352940,367459370,261225585,2610173221,4209349473,3468074219,3265815641,314222801,3066103646,3808782860,282218597,3406013506,3773591054,379116347,1285071038,846784868,2669647154,3771962079,3550491691,2305946142,453669953,1268987020,3317592352,3279303384,3744833421,2610507566,3859509063,266596637,3847019092,517658769,3462560207,3443424879,370717030,4247526661,2224018117,4143653529,4112773975,2788324899,2477274417,1456262402,2901442914,1517677493,
|
||||
1846949527,2295493580,3734397586,2176403920,1280348187,1908823572,3871786941,846861322,1172426758,3287448474,3383383037,1655181056,3139813346,901632758,1897031941,2986607138,3066810236,3447102507,1393639104,373351379,950779232,625454576,3124240540,4148612726,2007998917,544563296,2244738638,2330496472,2058025392,1291430526,424198748,50039436,29584100,3605783033,2429876329,2791104160,1057563949,3255363231,3075367218,3463963227,1469046755,985887462],E=[1332899944,1700884034,1701343084,1684370003,1668446532,
|
||||
1869963892];k.encodeBase64=x;k.decodeBase64=B;return k});
|
||||
1
front/lib/datatables/README_test.txt
Executable file
@@ -0,0 +1 @@
|
||||
- Presence on device details breaks
|
||||
7799
front/lib/datatables/datatables.css
Executable file
33679
front/lib/datatables/datatables.js
Executable file
37
front/lib/datatables/datatables.min.css
vendored
Executable file
180
front/lib/datatables/datatables.min.js
vendored
Executable file
@@ -16,9 +16,15 @@
|
||||
//------------------------------------------------------------------------------
|
||||
?>
|
||||
|
||||
|
||||
|
||||
<?php
|
||||
require 'php/templates/header.php';
|
||||
?>
|
||||
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
|
||||
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
<div class="content-wrapper" id="maintenancePage">
|
||||
|
||||
@@ -476,8 +482,13 @@ function ImportPastedCSV()
|
||||
{
|
||||
var csv = $('#modal-input-textarea').val();
|
||||
|
||||
console.log(csv);
|
||||
|
||||
csvBase64 = utf8ToBase64(csv);
|
||||
|
||||
console.log(csvBase64);
|
||||
|
||||
|
||||
$.post('php/server/devices.php?action=ImportCSV', { content: csvBase64 }, function(msg) {
|
||||
showMessage(msg);
|
||||
write_notification(`[Maintenance] Devices imported from pasted content`, 'info');
|
||||
|
||||
@@ -17,14 +17,14 @@
|
||||
<h3 class="box-title"><?= lang('Gen_Selected_Devices');?></h3>
|
||||
|
||||
</div>
|
||||
<div class="deviceSelector col-md-9 col-sm-12" style="z-index:5"></div>
|
||||
<div class="deviceSelector col-md-11 col-sm-11" style="z-index:5"></div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<button type="button" class="btn btn-default" onclick="markAllSelected()">
|
||||
<i class="fa-solid fa-circle-check"></i> <?= lang('Gen_Add_All');?>
|
||||
<div class="col-md-1">
|
||||
<button type="button" class="btn btn-default col-md-12" onclick="markAllSelected()" title="<?= lang('Gen_Add_All');?>">
|
||||
<i class="fa-solid fa-circle-check"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default" onclick="markAllNotSelected()">
|
||||
<i class="fa-solid fa-circle-xmark"></i> <?= lang('Gen_Remove_All');?>
|
||||
<button type="button" class="btn btn-default col-md-12" onclick="markAllNotSelected()" title="<?= lang('Gen_Remove_All');?>">
|
||||
<i class="fa-solid fa-circle-xmark"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -77,11 +77,11 @@
|
||||
|
||||
// some race condition, need to implement delay
|
||||
setTimeout(() => {
|
||||
$.get('api/table_settings.json?nocache=' + Date.now(), function(res) {
|
||||
$.get('/php/server/query_json.php', { file: 'table_settings.json', nocache: Date.now() }, function(res) {
|
||||
|
||||
settingsData = res["data"];
|
||||
|
||||
excludedColumns = ["NEWDEV_devMac", "NEWDEV_devFirstConnection", "NEWDEV_devLastConnection", "NEWDEV_devLastNotification", "NEWDEV_devLastIP", "NEWDEV_devStaticIP", "NEWDEV_devScan", "NEWDEV_devPresentLastScan" ]
|
||||
excludedColumns = ["NEWDEV_devMac", "NEWDEV_devFirstConnection", "NEWDEV_devLastConnection", "NEWDEV_devLastNotification", "NEWDEV_devStaticIP", "NEWDEV_devScan", "NEWDEV_devPresentLastScan", "NEWDEV_devCustomProps" ]
|
||||
|
||||
const relevantColumns = settingsData.filter(set =>
|
||||
set.setGroup === "NEWDEV" &&
|
||||
@@ -90,21 +90,21 @@
|
||||
!set.setKey.includes("__metadata")
|
||||
);
|
||||
|
||||
const generateSimpleForm = columns => {
|
||||
const generateSimpleForm = multiEditColumns => {
|
||||
const form = $('#multi-edit-form');
|
||||
const numColumns = 2; // Number of columns
|
||||
|
||||
// Calculate number of elements per column
|
||||
const elementsPerColumn = Math.ceil(columns.length / numColumns);
|
||||
const elementsPerColumn = Math.ceil(multiEditColumns.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++) {
|
||||
for (let j = i * elementsPerColumn; j < Math.min((i + 1) * elementsPerColumn, multiEditColumns.length); j++) {
|
||||
|
||||
const setTypeObject = JSON.parse(columns[j].setType.replace(/'/g, '"'));
|
||||
const setTypeObject = JSON.parse(multiEditColumns[j].setType.replace(/'/g, '"'));
|
||||
|
||||
// get the element with the input value(s)
|
||||
let elements = setTypeObject.elements.filter(element => element.elementHasInputValue === 1);
|
||||
@@ -135,37 +135,38 @@
|
||||
onClick,
|
||||
onChange,
|
||||
customParams,
|
||||
customId
|
||||
customId,
|
||||
columns
|
||||
} = handleElementOptions('none', elementOptions, transformers, val = "");
|
||||
|
||||
// render based on element type
|
||||
if (elementType === 'select') {
|
||||
|
||||
targetLocation = columns[j].setKey + "_generateSetOptions"
|
||||
targetLocation = multiEditColumns[j].setKey + "_generateSetOptions"
|
||||
|
||||
generateOptionsOrSetOptions(columns[j].setKey, [], targetLocation, generateOptions)
|
||||
generateOptionsOrSetOptions(multiEditColumns[j].setKey, [], targetLocation, generateOptions, null)
|
||||
|
||||
console.log(columns[j].setKey)
|
||||
console.log(multiEditColumns[j].setKey)
|
||||
// Handle Icons as they need a preview
|
||||
if(columns[j].setKey == 'NEWDEV_devIcon')
|
||||
if(multiEditColumns[j].setKey == 'NEWDEV_devIcon')
|
||||
{
|
||||
input = `
|
||||
<span class="input-group-addon iconPreview" my-customid="NEWDEV_devIcon_preview"></span>
|
||||
<select class="form-control"
|
||||
onChange="updateIconPreview(this)"
|
||||
my-customparams="NEWDEV_devIcon,NEWDEV_devIcon_preview"
|
||||
id="${columns[j].setKey}"
|
||||
data-my-column="${columns[j].setKey}"
|
||||
data-my-targetColumns="${columns[j].setKey.replace('NEWDEV_','')}" >
|
||||
id="${multiEditColumns[j].setKey}"
|
||||
data-my-column="${multiEditColumns[j].setKey}"
|
||||
data-my-targetColumns="${multiEditColumns[j].setKey.replace('NEWDEV_','')}" >
|
||||
<option id="${targetLocation}"></option>
|
||||
</select>`
|
||||
|
||||
} else{
|
||||
|
||||
input = `<select class="form-control"
|
||||
id="${columns[j].setKey}"
|
||||
data-my-column="${columns[j].setKey}"
|
||||
data-my-targetColumns="${columns[j].setKey.replace('NEWDEV_','')}" >
|
||||
id="${multiEditColumns[j].setKey}"
|
||||
data-my-column="${multiEditColumns[j].setKey}"
|
||||
data-my-targetColumns="${multiEditColumns[j].setKey.replace('NEWDEV_','')}" >
|
||||
<option id="${targetLocation}"></option>
|
||||
</select>`
|
||||
}
|
||||
@@ -178,20 +179,20 @@
|
||||
|
||||
|
||||
input = `<input class="${inputClass}"
|
||||
id="${columns[j].setKey}"
|
||||
my-customid="${columns[j].setKey}"
|
||||
data-my-column="${columns[j].setKey}"
|
||||
data-my-targetColumns="${columns[j].setKey.replace('NEWDEV_','')}"
|
||||
id="${multiEditColumns[j].setKey}"
|
||||
my-customid="${multiEditColumns[j].setKey}"
|
||||
data-my-column="${multiEditColumns[j].setKey}"
|
||||
data-my-targetColumns="${multiEditColumns[j].setKey.replace('NEWDEV_','')}"
|
||||
type="${inputType}">`
|
||||
}
|
||||
|
||||
|
||||
const inputEntry = `<div class="form-group col-sm-12" >
|
||||
<label class="col-sm-3 control-label">${columns[j].setName}</label>
|
||||
<label class="col-sm-3 control-label">${multiEditColumns[j].setName}</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].setKey}');" title="${getString('Device_MultiEdit_Tooltip')}">
|
||||
<span class="input-group-addon pointer red-hover-background" onclick="massUpdateField('${multiEditColumns[j].setKey}');" title="${getString('Device_MultiEdit_Tooltip')}">
|
||||
<i class="fa fa-save"></i>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
|
||||
?>
|
||||
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
|
||||
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
<div class="content-wrapper">
|
||||
|
||||
|
||||
@@ -7,8 +7,18 @@
|
||||
?>
|
||||
|
||||
<script src="js/graph_online_history.js"></script>
|
||||
<script>
|
||||
$.get('api/table_online_history.json?nocache=' + Date.now(), function(res) {
|
||||
<script >
|
||||
|
||||
|
||||
if (isAppInitialized()) {
|
||||
initOnlineHistoryGraph();
|
||||
} else {
|
||||
callAfterAppInitialized(() => initOnlineHistoryGraph());
|
||||
}
|
||||
|
||||
|
||||
function initOnlineHistoryGraph() {
|
||||
$.get('/php/server/query_json.php', { file: 'table_online_history.json', nocache: Date.now() }, function(res) {
|
||||
// Extracting data from the JSON response
|
||||
var timeStamps = [];
|
||||
var onlineCounts = [];
|
||||
@@ -39,6 +49,8 @@ $.get('api/table_online_history.json?nocache=' + Date.now(), function(res) {
|
||||
// Handle any errors in fetching the data
|
||||
console.error('Error fetching online history data.');
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
<!-- <canvas id="clientsChart" width="800" height="140" class="extratooltipcanvas no-user-select"></canvas> -->
|
||||
<canvas id="OnlineChart" style="width:100%; height: 150px; margin-bottom: 15px;"></canvas>
|
||||
@@ -20,12 +20,12 @@ function renderLogArea($params) {
|
||||
$content = file_get_contents($filePath);
|
||||
}
|
||||
|
||||
// Prepare the download button HTML if filePath starts with /app/front
|
||||
// Prepare the download button HTML if filePath starts with /app
|
||||
$downloadButtonHtml = '';
|
||||
if (strpos($filePath, '/app/front') === 0) {
|
||||
if (strpos($filePath, '/app') === 0) {
|
||||
$downloadButtonHtml = '
|
||||
<span class="span-padding">
|
||||
<a href="' . htmlspecialchars(str_replace('/app/front', '', $filePath)) . '" target="_blank">
|
||||
<a href="' . htmlspecialchars(str_replace('/app/log/', '/php/server/query_logs.php?file=', $filePath)) . '" target="_blank">
|
||||
<i class="fa fa-download"></i>
|
||||
</a>
|
||||
</span>';
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
}
|
||||
],
|
||||
"fileName": "app.log",
|
||||
"filePath": "/app/front/log/app.log",
|
||||
"filePath": "/app/log/app.log",
|
||||
"textAreaCssClass": "logs"
|
||||
|
||||
},
|
||||
@@ -23,7 +23,7 @@
|
||||
}
|
||||
],
|
||||
"fileName": "app_front.log",
|
||||
"filePath": "/app/front/log/app_front.log",
|
||||
"filePath": "/app/log/app_front.log",
|
||||
"textAreaCssClass": "logs logs-small"
|
||||
},
|
||||
{
|
||||
@@ -34,7 +34,7 @@
|
||||
}
|
||||
],
|
||||
"fileName": "app.php_errors.log",
|
||||
"filePath": "/app/front/log/app.php_errors.log",
|
||||
"filePath": "/app/log/app.php_errors.log",
|
||||
"textAreaCssClass": "logs logs-small"
|
||||
},
|
||||
{
|
||||
@@ -45,7 +45,7 @@
|
||||
}
|
||||
],
|
||||
"fileName": "execution_queue.log",
|
||||
"filePath": "/app/front/log/execution_queue.log",
|
||||
"filePath": "/app/log/execution_queue.log",
|
||||
"textAreaCssClass": "logs logs-small"
|
||||
},
|
||||
{
|
||||
@@ -55,17 +55,6 @@
|
||||
"filePath": "/var/log/nginx/error.log",
|
||||
"textAreaCssClass": "logs logs-small"
|
||||
},
|
||||
{
|
||||
"buttons": [
|
||||
{
|
||||
"labelStringCode": "Maint_PurgeLog",
|
||||
"event": "logManage('app_front.log', 'cleanLog')"
|
||||
}
|
||||
],
|
||||
"fileName": "app_front.log",
|
||||
"filePath": "/app/front/log/app_front.log",
|
||||
"textAreaCssClass": "logs logs-small"
|
||||
},
|
||||
{
|
||||
"buttons": [
|
||||
{
|
||||
@@ -74,7 +63,7 @@
|
||||
}
|
||||
],
|
||||
"fileName": "db_is_locked.log",
|
||||
"filePath": "/app/front/log/db_is_locked.log",
|
||||
"filePath": "/app/log/db_is_locked.log",
|
||||
"textAreaCssClass": "logs logs-small"
|
||||
},
|
||||
{
|
||||
@@ -85,7 +74,7 @@
|
||||
}
|
||||
],
|
||||
"fileName": "stdout.log",
|
||||
"filePath": "/app/front/log/stdout.log",
|
||||
"filePath": "/app/log/stdout.log",
|
||||
"textAreaCssClass": "logs logs-small"
|
||||
},
|
||||
{
|
||||
@@ -96,7 +85,7 @@
|
||||
}
|
||||
],
|
||||
"fileName": "stderr.log",
|
||||
"filePath": "/app/front/log/stderr.log",
|
||||
"filePath": "/app/log/stderr.log",
|
||||
"textAreaCssClass": "logs logs-small"
|
||||
}
|
||||
]
|
||||
@@ -11,7 +11,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// DB File Path
|
||||
$DBFILE = dirname(__FILE__).'/../../../db/app.db';
|
||||
$DBFILE_LOCKED_FILE = dirname(__FILE__).'/../../../front/log/db_is_locked.log';
|
||||
$DBFILE_LOCKED_FILE = dirname(__FILE__).'/../../../log/db_is_locked.log';
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// check if authenticated
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
case 'deleteEvents30': deleteEvents30(); break;
|
||||
case 'deleteActHistory': deleteActHistory(); break;
|
||||
case 'deleteDeviceEvents': deleteDeviceEvents(); break;
|
||||
case 'resetDeviceProps': resetDeviceProps(); break;
|
||||
case 'PiaBackupDBtoArchive': PiaBackupDBtoArchive(); break;
|
||||
case 'PiaRestoreDBfromArchive': PiaRestoreDBfromArchive(); break;
|
||||
case 'PiaPurgeDBBackups': PiaPurgeDBBackups(); break;
|
||||
@@ -105,8 +106,9 @@ function getServerDeviceData() {
|
||||
"devSSID" => "",
|
||||
"devSyncHubNode" => "",
|
||||
"devSourcePlugin" => "",
|
||||
"devCustomProps" => "",
|
||||
"devStatus" => "Unknown",
|
||||
"devRandomMAC" => false,
|
||||
"devIsRandomMAC" => false,
|
||||
"devSessions" => 0,
|
||||
"devEvents" => 0,
|
||||
"devDownAlerts" => 0,
|
||||
@@ -134,7 +136,7 @@ function getServerDeviceData() {
|
||||
$deviceData['devFirstConnection'] = formatDate ($row['devFirstConnection']); // Date formated
|
||||
$deviceData['devLastConnection'] = formatDate ($row['devLastConnection']); // Date formated
|
||||
|
||||
$deviceData['devRandomMAC'] = isRandomMAC($mac);
|
||||
$deviceData['devIsRandomMAC'] = isRandomMAC($mac);
|
||||
|
||||
// Count Totals
|
||||
$condition = ' WHERE eve_MAC="'. $mac .'" AND eve_DateTime >= '. $periodDate;
|
||||
@@ -190,129 +192,131 @@ function setDeviceData() {
|
||||
global $db;
|
||||
|
||||
// Sanitize input
|
||||
$mac = quotes($_REQUEST['mac']);
|
||||
$name = quotes($_REQUEST['name']);
|
||||
$owner = quotes($_REQUEST['owner']);
|
||||
$type = quotes($_REQUEST['type']);
|
||||
$vendor = quotes($_REQUEST['vendor']);
|
||||
$icon = quotes($_REQUEST['icon']);
|
||||
$favorite = quotes($_REQUEST['favorite']);
|
||||
$group = quotes($_REQUEST['group']);
|
||||
$location = quotes($_REQUEST['location']);
|
||||
$comments = quotes($_REQUEST['comments']);
|
||||
$parentMac = quotes($_REQUEST['networknode']);
|
||||
$parentPort = quotes($_REQUEST['networknodeport']);
|
||||
$ssid = quotes($_REQUEST['ssid']);
|
||||
$site = quotes($_REQUEST['networksite']);
|
||||
$staticIP = quotes($_REQUEST['staticIP']);
|
||||
$scancycle = quotes($_REQUEST['scancycle']);
|
||||
$alertevents = quotes($_REQUEST['alertevents']);
|
||||
$alertdown = quotes($_REQUEST['alertdown']);
|
||||
$skiprepeated = quotes($_REQUEST['skiprepeated']);
|
||||
$newdevice = quotes($_REQUEST['newdevice']);
|
||||
$archived = quotes($_REQUEST['archived']);
|
||||
$devFirstConnection = quotes($_REQUEST['devFirstConnection']);
|
||||
$devLastConnection = quotes($_REQUEST['devLastConnection']);
|
||||
$ip = quotes($_REQUEST['ip']);
|
||||
$createNew = quotes($_REQUEST['createNew']);
|
||||
$mac = quotes($_POST['mac']);
|
||||
$name = urldecode(quotes($_POST['name']));
|
||||
$owner = urldecode(quotes($_POST['owner']));
|
||||
$type = urldecode(quotes($_POST['type']));
|
||||
$vendor = urldecode(quotes($_POST['vendor']));
|
||||
$icon = urldecode(quotes($_POST['icon']));
|
||||
$favorite = quotes($_POST['favorite']);
|
||||
$group = urldecode(quotes($_POST['group']));
|
||||
$location = urldecode(quotes($_POST['location']));
|
||||
$comments = urldecode(quotes($_POST['comments']));
|
||||
$parentMac = quotes($_POST['networknode']);
|
||||
$parentPort = quotes($_POST['networknodeport']);
|
||||
$ssid = urldecode(quotes($_POST['ssid']));
|
||||
$site = quotes($_POST['networksite']);
|
||||
$staticIP = quotes($_POST['staticIP']);
|
||||
$scancycle = quotes($_POST['scancycle']);
|
||||
$alertevents = quotes($_POST['alertevents']);
|
||||
$alertdown = quotes($_POST['alertdown']);
|
||||
$skiprepeated = quotes($_POST['skiprepeated']);
|
||||
$newdevice = quotes($_POST['newdevice']);
|
||||
$archived = quotes($_POST['archived']);
|
||||
$devFirstConnection = quotes($_POST['devFirstConnection']);
|
||||
$devLastConnection = quotes($_POST['devLastConnection']);
|
||||
$ip = quotes($_POST['ip']);
|
||||
$devCustomProps = quotes($_POST['devCustomProps']);
|
||||
$createNew = quotes($_POST['createNew']);
|
||||
$devNewGuid = generateGUID();
|
||||
|
||||
// an update
|
||||
if ($_REQUEST['createNew'] == 0)
|
||||
{
|
||||
// UPDATE SQL query
|
||||
$sql = "UPDATE Devices SET
|
||||
devName = '$name',
|
||||
devOwner = '$owner',
|
||||
devType = '$type',
|
||||
devVendor = '$vendor',
|
||||
devIcon = '$icon',
|
||||
devFavorite = '$favorite',
|
||||
devGroup = '$group',
|
||||
devLocation = '$location',
|
||||
devComments = '$comments',
|
||||
devParentMAC = '$parentMac',
|
||||
devParentPort = '$parentPort',
|
||||
devSSID = '$ssid',
|
||||
devSite = '$site',
|
||||
devStaticIP = '$staticIP',
|
||||
devScan = '$scancycle',
|
||||
devAlertEvents = '$alertevents',
|
||||
devAlertDown = '$alertdown',
|
||||
devSkipRepeated = '$skiprepeated',
|
||||
devIsNew = '$newdevice',
|
||||
devIsArchived = '$archived'
|
||||
WHERE devMac = '$mac'";
|
||||
} else // an INSERT
|
||||
{
|
||||
$sql = "INSERT INTO Devices (
|
||||
devMac,
|
||||
devName,
|
||||
devOwner,
|
||||
devType,
|
||||
devVendor,
|
||||
devIcon,
|
||||
devFavorite,
|
||||
devGroup,
|
||||
devLocation,
|
||||
devComments,
|
||||
devParentMAC,
|
||||
devParentPort,
|
||||
devSSID,
|
||||
devSite,
|
||||
devStaticIP,
|
||||
devScan,
|
||||
devAlertEvents,
|
||||
devAlertDown,
|
||||
devSkipRepeated,
|
||||
devIsNew,
|
||||
devIsArchived,
|
||||
devLastConnection,
|
||||
devFirstConnection,
|
||||
devLastIP,
|
||||
devGUID
|
||||
) VALUES (
|
||||
'$mac',
|
||||
'$name',
|
||||
'$owner',
|
||||
'$type',
|
||||
'$vendor',
|
||||
'$icon',
|
||||
'$favorite',
|
||||
'$group',
|
||||
'$location',
|
||||
'$comments',
|
||||
'$parentMac',
|
||||
'$parentPort',
|
||||
'$ssid',
|
||||
'$site',
|
||||
'$staticIP',
|
||||
'$scancycle',
|
||||
'$alertevents',
|
||||
'$alertdown',
|
||||
'$skiprepeated',
|
||||
'$newdevice',
|
||||
'$archived',
|
||||
'$devLastConnection',
|
||||
'$devFirstConnection',
|
||||
'$ip',
|
||||
'$devNewGuid'
|
||||
)
|
||||
";
|
||||
// An update
|
||||
if ($_POST['createNew'] == 0) {
|
||||
// UPDATE SQL query
|
||||
$sql = "UPDATE Devices SET
|
||||
devName = '$name',
|
||||
devOwner = '$owner',
|
||||
devType = '$type',
|
||||
devVendor = '$vendor',
|
||||
devIcon = '$icon',
|
||||
devFavorite = '$favorite',
|
||||
devGroup = '$group',
|
||||
devLocation = '$location',
|
||||
devComments = '$comments',
|
||||
devParentMAC = '$parentMac',
|
||||
devParentPort = '$parentPort',
|
||||
devSSID = '$ssid',
|
||||
devSite = '$site',
|
||||
devStaticIP = '$staticIP',
|
||||
devScan = '$scancycle',
|
||||
devAlertEvents = '$alertevents',
|
||||
devAlertDown = '$alertdown',
|
||||
devSkipRepeated = '$skiprepeated',
|
||||
devIsNew = '$newdevice',
|
||||
devIsArchived = '$archived',
|
||||
devCustomProps = '$devCustomProps'
|
||||
WHERE devMac = '$mac'";
|
||||
} else { // An INSERT
|
||||
$sql = "INSERT INTO Devices (
|
||||
devMac,
|
||||
devName,
|
||||
devOwner,
|
||||
devType,
|
||||
devVendor,
|
||||
devIcon,
|
||||
devFavorite,
|
||||
devGroup,
|
||||
devLocation,
|
||||
devComments,
|
||||
devParentMAC,
|
||||
devParentPort,
|
||||
devSSID,
|
||||
devSite,
|
||||
devStaticIP,
|
||||
devScan,
|
||||
devAlertEvents,
|
||||
devAlertDown,
|
||||
devSkipRepeated,
|
||||
devIsNew,
|
||||
devIsArchived,
|
||||
devLastConnection,
|
||||
devFirstConnection,
|
||||
devLastIP,
|
||||
devGUID,
|
||||
devCustomProps
|
||||
) VALUES (
|
||||
'$mac',
|
||||
'$name',
|
||||
'$owner',
|
||||
'$type',
|
||||
'$vendor',
|
||||
'$icon',
|
||||
'$favorite',
|
||||
'$group',
|
||||
'$location',
|
||||
'$comments',
|
||||
'$parentMac',
|
||||
'$parentPort',
|
||||
'$ssid',
|
||||
'$site',
|
||||
'$staticIP',
|
||||
'$scancycle',
|
||||
'$alertevents',
|
||||
'$alertdown',
|
||||
'$skiprepeated',
|
||||
'$newdevice',
|
||||
'$archived',
|
||||
'$devLastConnection',
|
||||
'$devFirstConnection',
|
||||
'$ip',
|
||||
'$devNewGuid',
|
||||
'$devCustomProps'
|
||||
)";
|
||||
}
|
||||
|
||||
// update Data
|
||||
// Execute the query
|
||||
$result = $db->query($sql);
|
||||
|
||||
// check result
|
||||
// Check the result
|
||||
if ($result == TRUE) {
|
||||
echo lang('BackDevices_DBTools_UpdDev');
|
||||
echo lang('BackDevices_DBTools_UpdDev');
|
||||
} else {
|
||||
echo lang('BackDevices_DBTools_UpdDevError')."\n\n$sql \n\n". $db->lastErrorMsg();
|
||||
echo lang('BackDevices_DBTools_UpdDevError')."\n\n$sql \n\n". $db->lastErrorMsg();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Delete Device
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -389,6 +393,26 @@ function deleteDeviceEvents() {
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Delete Device Properties
|
||||
//------------------------------------------------------------------------------
|
||||
function resetDeviceProps() {
|
||||
global $db;
|
||||
|
||||
// sql
|
||||
$sql = 'UPDATE Devices set devCustomProps = "'.getSettingValue("NEWDEV_devCustomProps").'" WHERE devMac="' . $_REQUEST['mac'] .'"';
|
||||
|
||||
// execute sql
|
||||
$result = $db->query($sql);
|
||||
|
||||
// check result
|
||||
if ($result == TRUE) {
|
||||
echo lang('Gen_Okay');
|
||||
} else {
|
||||
echo lang('Gen_Error')."\n\n$sql \n\n". $db->lastErrorMsg();
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Delete all devices
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
38
front/php/server/query_config.php
Executable file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
// ---- IMPORTS ----
|
||||
//------------------------------------------------------------------------------
|
||||
// Check if authenticated
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
|
||||
// Get init.php
|
||||
require dirname(__FILE__).'/../server/init.php';
|
||||
// ---- IMPORTS ----
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Handle incoming requests
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
// Get query string parameter ?file=settings_table.json
|
||||
$file = isset($_GET['file']) ? $_GET['file'] : null;
|
||||
|
||||
// Check if file parameter is provided
|
||||
if ($file) {
|
||||
// Define the folder where files are located
|
||||
$filePath = "/app/config/" . basename($file);
|
||||
|
||||
// Check if the file exists
|
||||
if (file_exists($filePath)) {
|
||||
// Send the response back to the client
|
||||
header('Content-Type: text/plain');
|
||||
echo file_get_contents($filePath);
|
||||
} else {
|
||||
// File not found response
|
||||
http_response_code(404);
|
||||
echo json_encode(["error" => "File not found"]);
|
||||
}
|
||||
} else {
|
||||
// Missing file parameter response
|
||||
http_response_code(400);
|
||||
echo json_encode(["error" => "Missing 'file' parameter"]);
|
||||
}
|
||||
}
|
||||
?>
|
||||
38
front/php/server/query_json.php
Executable file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
// ---- IMPORTS ----
|
||||
//------------------------------------------------------------------------------
|
||||
// Check if authenticated
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
|
||||
// Get init.php
|
||||
require dirname(__FILE__).'/../server/init.php';
|
||||
// ---- IMPORTS ----
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Handle incoming requests
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
// Get query string parameter ?file=settings_table.json
|
||||
$file = isset($_GET['file']) ? $_GET['file'] : null;
|
||||
|
||||
// Check if file parameter is provided
|
||||
if ($file) {
|
||||
// Define the folder where files are located
|
||||
$filePath = "/app/api/" . basename($file);
|
||||
|
||||
// Check if the file exists
|
||||
if (file_exists($filePath)) {
|
||||
// Send the response back to the client
|
||||
header('Content-Type: application/json');
|
||||
echo file_get_contents($filePath);
|
||||
} else {
|
||||
// File not found response
|
||||
http_response_code(404);
|
||||
echo json_encode(["error" => "File not found"]);
|
||||
}
|
||||
} else {
|
||||
// Missing file parameter response
|
||||
http_response_code(400);
|
||||
echo json_encode(["error" => "Missing 'file' parameter"]);
|
||||
}
|
||||
}
|
||||
?>
|
||||
38
front/php/server/query_logs.php
Executable file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
// ---- IMPORTS ----
|
||||
//------------------------------------------------------------------------------
|
||||
// Check if authenticated
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
|
||||
// Get init.php
|
||||
require dirname(__FILE__).'/../server/init.php';
|
||||
// ---- IMPORTS ----
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Handle incoming requests
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
// Get query string parameter ?file=settings_table.json
|
||||
$file = isset($_GET['file']) ? $_GET['file'] : null;
|
||||
|
||||
// Check if file parameter is provided
|
||||
if ($file) {
|
||||
// Define the folder where files are located
|
||||
$filePath = "/app/log/" . basename($file);
|
||||
|
||||
// Check if the file exists
|
||||
if (file_exists($filePath)) {
|
||||
// Send the response back to the client
|
||||
header('Content-Type: text/plain');
|
||||
echo file_get_contents($filePath);
|
||||
} else {
|
||||
// File not found response
|
||||
http_response_code(404);
|
||||
echo json_encode(["error" => "File not found"]);
|
||||
}
|
||||
} else {
|
||||
// Missing file parameter response
|
||||
http_response_code(400);
|
||||
echo json_encode(["error" => "Missing 'file' parameter"]);
|
||||
}
|
||||
}
|
||||
?>
|
||||
55
front/php/server/query_replace_config.php
Executable file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
// ---- IMPORTS ----
|
||||
//------------------------------------------------------------------------------
|
||||
// Check if authenticated
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
|
||||
// Get init.php
|
||||
require dirname(__FILE__).'/../server/init.php';
|
||||
// ---- IMPORTS ----
|
||||
|
||||
global $fullConfPath;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Handle incoming requests
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// Access the 'config' parameter from the POST request
|
||||
$base64Data = $_POST['config'] ?? null;
|
||||
|
||||
if (!$base64Data) {
|
||||
$msg = "Missing 'config' parameter.";
|
||||
echo $msg;
|
||||
http_response_code(400); // Bad request
|
||||
die($msg);
|
||||
}
|
||||
|
||||
// Decode incoming base64 data
|
||||
$input = base64_decode($base64Data, true);
|
||||
|
||||
if ($input === false) {
|
||||
$msg = "Invalid base64 data.";
|
||||
echo $msg;
|
||||
http_response_code(400); // Bad request
|
||||
die($msg);
|
||||
}
|
||||
|
||||
// Backup the original file
|
||||
if (file_exists($fullConfPath)) {
|
||||
copy($fullConfPath, $fullConfPath . ".bak");
|
||||
}
|
||||
|
||||
// Write the new configuration
|
||||
$file = fopen($fullConfPath, "w");
|
||||
if (!$file) {
|
||||
$msg = "Unable to open file!";
|
||||
echo $msg;
|
||||
http_response_code(500); // Server error
|
||||
die($msg);
|
||||
}
|
||||
|
||||
fwrite($file, $input);
|
||||
fclose($file);
|
||||
|
||||
echo "Configuration saved successfully.";
|
||||
}
|
||||
?>
|
||||
@@ -263,7 +263,7 @@ function cleanLog($logFile)
|
||||
|
||||
$path = "";
|
||||
|
||||
$allowedFiles = ['app.log', 'app_front.log', 'IP_changes.log', 'stdout.log', 'stderr.log', 'app.php_errors.log', 'execution_queue.log'];
|
||||
$allowedFiles = ['app.log', 'app_front.log', 'IP_changes.log', 'stdout.log', 'stderr.log', 'app.php_errors.log', 'execution_queue.log', 'db_is_locked.log'];
|
||||
|
||||
if(in_array($logFile, $allowedFiles))
|
||||
{
|
||||
@@ -274,7 +274,7 @@ function cleanLog($logFile)
|
||||
{
|
||||
// purge content
|
||||
$file = fopen($path, "w") or die("Unable to open file!");
|
||||
fwrite($file, "[".$timestamp. "] Log file manually purged" .PHP_EOL."");
|
||||
fwrite($file, "");
|
||||
fclose($file);
|
||||
displayMessage('File <code>'.$logFile.'</code> purged.', FALSE, TRUE, TRUE, TRUE);
|
||||
} else
|
||||
@@ -356,6 +356,8 @@ function saveSettings()
|
||||
$txt .= $setKey . "='" . $val . "'\n";
|
||||
} elseif ($dataType == 'integer') {
|
||||
$txt .= $setKey . "=" . $settingValue . "\n";
|
||||
} elseif ($dataType == 'none') {
|
||||
$txt .= $setKey . "=''\n";
|
||||
} elseif ($dataType == 'json') {
|
||||
$txt .= $setKey . "=" . $settingValue . "\n";
|
||||
} elseif ($dataType == 'boolean') {
|
||||
@@ -430,7 +432,7 @@ function getString ($setKey, $default) {
|
||||
// -------------------------------------------------------------------------------------------
|
||||
function getSettingValue($setKey) {
|
||||
// Define the JSON endpoint URL
|
||||
$url = dirname(__FILE__).'/../../../front/api/table_settings.json';
|
||||
$url = dirname(__FILE__).'/../../../api/table_settings.json';
|
||||
|
||||
// Fetch the JSON data
|
||||
$json = file_get_contents($url);
|
||||
@@ -551,6 +553,7 @@ function decodeSpecialChars($str) {
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------------------------
|
||||
// used in Export CSV
|
||||
function getDevicesColumns(){
|
||||
|
||||
$columns = ["devMac",
|
||||
@@ -582,7 +585,8 @@ function getDevicesColumns(){
|
||||
"devSyncHubNode",
|
||||
"devSite",
|
||||
"devSSID",
|
||||
"devSourcePlugin"
|
||||
"devSourcePlugin",
|
||||
"devCustomProps"
|
||||
];
|
||||
|
||||
return $columns;
|
||||
|
||||
@@ -64,7 +64,7 @@ function generate_guid() {
|
||||
// ----------------------------------------------------------------------------------------
|
||||
// Logs a notification in in-app notification system
|
||||
function write_notification($content, $level = "interrupt") {
|
||||
$NOTIFICATION_API_FILE = '/app/front/api/user_notifications.json';
|
||||
$NOTIFICATION_API_FILE = '/app/api/user_notifications.json';
|
||||
|
||||
// Generate GUID
|
||||
$guid = generate_guid();
|
||||
@@ -97,7 +97,7 @@ function write_notification($content, $level = "interrupt") {
|
||||
// ----------------------------------------------------------------------------------------
|
||||
// Removes a notification based on GUID
|
||||
function remove_notification($guid) {
|
||||
$NOTIFICATION_API_FILE = '/app/front/api/user_notifications.json';
|
||||
$NOTIFICATION_API_FILE = '/app/api/user_notifications.json';
|
||||
|
||||
// Read existing notifications
|
||||
$notifications = json_decode(file_get_contents($NOTIFICATION_API_FILE), true);
|
||||
@@ -114,7 +114,7 @@ function remove_notification($guid) {
|
||||
// ----------------------------------------------------------------------------------------
|
||||
// Deletes all notifications
|
||||
function notifications_clear() {
|
||||
$NOTIFICATION_API_FILE = '/app/front/api/user_notifications.json';
|
||||
$NOTIFICATION_API_FILE = '/app/api/user_notifications.json';
|
||||
|
||||
// Clear notifications by writing an empty array to the file
|
||||
file_put_contents($NOTIFICATION_API_FILE, json_encode(array()));
|
||||
@@ -123,7 +123,7 @@ function notifications_clear() {
|
||||
// ----------------------------------------------------------------------------------------
|
||||
// Mark a notification read based on GUID
|
||||
function mark_notification_as_read($guid) {
|
||||
$NOTIFICATION_API_FILE = '/app/front/api/user_notifications.json';
|
||||
$NOTIFICATION_API_FILE = '/app/api/user_notifications.json';
|
||||
$max_attempts = 3;
|
||||
$attempts = 0;
|
||||
|
||||
@@ -172,7 +172,7 @@ function notifications_mark_all_read() {
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
function get_unread_notifications() {
|
||||
$NOTIFICATION_API_FILE = '/app/front/api/user_notifications.json';
|
||||
$NOTIFICATION_API_FILE = '/app/api/user_notifications.json';
|
||||
|
||||
// Read existing notifications
|
||||
if (file_exists($NOTIFICATION_API_FILE) && is_readable($NOTIFICATION_API_FILE)) {
|
||||
|
||||
@@ -28,12 +28,10 @@
|
||||
|
||||
<!-- To the right -->
|
||||
<div class="pull-right no-hidden-xs">
|
||||
| <a href="https://github.com/jokob-sk/NetAlertX/tree/main/docs#documentation-overview" target="_blank">Docs <i class="fa fa-circle-question"></i></a>
|
||||
| <a href="https://github.com/jokob-sk/NetAlertX/issues"><i class="fa-solid fa-bug"></i></a>
|
||||
| <a href="https://github.com/jokob-sk/NetAlertX/"><i class="fa-brands fa-github"></i></a>
|
||||
| <a href="https://discord.gg/UQnnHNYV"><i class="fa-brands fa-discord"></i></a>
|
||||
| <a href="mailto:jokob@duck.com?subject=NetAlertX"><i class="fa-solid fa-envelope"></i></a>
|
||||
| <a href="https://github.com/pucherot/Pi.Alert">©</a>
|
||||
| <a href="https://gurubase.io/g/netalertx" target="_blank" title="Ask AI"><i class="fa-regular fa-comment-dots fa-flip-horizontal"></i></a>
|
||||
| <a href="https://github.com/jokob-sk/NetAlertX/tree/main/docs#documentation-overview" target="_blank" title="Documentation"><i class="fa fa-book"></i></a>
|
||||
| <a href="https://github.com/jokob-sk/NetAlertX/issues" target="_blank"><i class="fa-solid fa-bug" title="Report a bug"></i></a>
|
||||
| <a href="https://discord.com/invite/NczTUTWyRr" target="_blank"><i class="fa-brands fa-discord" title="Join Discord"></i></a>
|
||||
| <?= lang('Maintenance_built_on');?>: <?php include 'php/templates/build.php'; ?>
|
||||
| Version: <?php include 'php/templates/version.php'; ?>
|
||||
|
|
||||
@@ -46,9 +44,6 @@
|
||||
|
||||
</div>
|
||||
<!-- ./wrapper -->
|
||||
|
||||
<!-- Bootstrap 3.3.7 -->
|
||||
<script src="lib/bootstrap/bootstrap.min.js"></script>
|
||||
|
||||
<!-- jQuery UI -->
|
||||
<script src="lib/jquery-ui/jquery-ui.min.js"></script>
|
||||
|
||||
@@ -32,18 +32,32 @@
|
||||
<!-- jQuery 3 -->
|
||||
<script src="lib/jquery/jquery.min.js"></script>
|
||||
|
||||
<!-- Bootstrap 3.3.7 -->
|
||||
<link rel="stylesheet" href="lib/bootstrap/bootstrap.min.css">
|
||||
<script src="lib/bootstrap/bootstrap.min.js"></script>
|
||||
|
||||
<!-- Datatable -->
|
||||
<link rel="stylesheet" href="lib/datatables.net-bs/css/dataTables.bootstrap.min.css">
|
||||
<link rel="stylesheet" href="lib/datatables.net/css/select.dataTables.min.css">
|
||||
<script src="lib/datatables.net/js/jquery.dataTables.min.js"></script>
|
||||
<script src="lib/datatables.net-bs/js/dataTables.bootstrap.min.js"></script>
|
||||
<script src="lib/datatables.net/js/dataTables.select.min.js"></script>
|
||||
|
||||
<script src="js/common.js?v=<?php include 'php/templates/version.php'; ?>"></script>
|
||||
<script src="js/modal.js?v=<?php include 'php/templates/version.php'; ?>"></script>
|
||||
<script src="js/tests.js?v=<?php include 'php/templates/version.php'; ?>"></script>
|
||||
<script src="js/db_methods.js?v=<?php include 'php/templates/version.php'; ?>"></script>
|
||||
<script src="js/settings_utils.js?v=<?php include 'php/templates/version.php'; ?>"></script>
|
||||
<script src="js/device.js?v=<?php include 'php/templates/version.php'; ?>"></script>
|
||||
|
||||
|
||||
<!-- Bootstrap 3.3.7 -->
|
||||
<link rel="stylesheet" href="lib/bootstrap/bootstrap.min.css">
|
||||
|
||||
<!-- iCheck -->
|
||||
|
||||
<link rel="stylesheet" href="lib/iCheck/all.css">
|
||||
<script src="lib/iCheck/icheck.min.js"></script>
|
||||
|
||||
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="lib/font-awesome/fontawesome.min.css">
|
||||
<link rel="stylesheet" href="lib/font-awesome/solid.css">
|
||||
@@ -56,6 +70,9 @@
|
||||
<!-- Theme style -->
|
||||
<link rel="stylesheet" href="lib/AdminLTE/dist/css/AdminLTE.min.css">
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- AdminLTE Skins. We have chosen the skin-blue for this starter
|
||||
page. However, you can choose any other skin. Make sure you
|
||||
apply the skin class to the body tag so the changes take effect. -->
|
||||
@@ -91,7 +108,7 @@
|
||||
<script>
|
||||
|
||||
function updateState(){
|
||||
$.get('api/app_state.json?nocache=' + Date.now(), function(appState) {
|
||||
$.get('/php/server/query_json.php', { file: 'app_state.json', nocache: Date.now() }, function(appState) {
|
||||
|
||||
document.getElementById('state').innerHTML = appState["currentState"].replaceAll('"', '');
|
||||
|
||||
|
||||
@@ -60,10 +60,15 @@
|
||||
"BackDevices_darkmode_enabled": "",
|
||||
"CLEAR_NEW_FLAG_description": "",
|
||||
"CLEAR_NEW_FLAG_name": "",
|
||||
"CustProps_cant_remove": "",
|
||||
"DAYS_TO_KEEP_EVENTS_description": "",
|
||||
"DAYS_TO_KEEP_EVENTS_name": "",
|
||||
"DISCOVER_PLUGINS_description": "",
|
||||
"DISCOVER_PLUGINS_name": "",
|
||||
"DevDetail_Copy_Device_Title": "",
|
||||
"DevDetail_Copy_Device_Tooltip": "",
|
||||
"DevDetail_CustomProperties_Title": "",
|
||||
"DevDetail_CustomProps_reset_info": "",
|
||||
"DevDetail_DisplayFields_Title": "",
|
||||
"DevDetail_EveandAl_AlertAllEvents": "",
|
||||
"DevDetail_EveandAl_AlertDown": "",
|
||||
@@ -180,6 +185,7 @@
|
||||
"DevDetail_button_Delete": "",
|
||||
"DevDetail_button_DeleteEvents": "",
|
||||
"DevDetail_button_DeleteEvents_Warning": "",
|
||||
"DevDetail_button_Delete_ask": "",
|
||||
"DevDetail_button_OverwriteIcons": "",
|
||||
"DevDetail_button_OverwriteIcons_Tooltip": "",
|
||||
"DevDetail_button_OverwriteIcons_Warning": "",
|
||||
@@ -203,6 +209,7 @@
|
||||
"Device_Shortcut_OnlineChart": "",
|
||||
"Device_TableHead_AlertDown": "",
|
||||
"Device_TableHead_Connected_Devices": "",
|
||||
"Device_TableHead_CustomProps": "",
|
||||
"Device_TableHead_Favorite": "",
|
||||
"Device_TableHead_FirstSession": "",
|
||||
"Device_TableHead_GUID": "",
|
||||
@@ -241,6 +248,8 @@
|
||||
"Donations_Title": "",
|
||||
"ENABLE_PLUGINS_description": "",
|
||||
"ENABLE_PLUGINS_name": "",
|
||||
"ENCRYPTION_KEY_description": "",
|
||||
"ENCRYPTION_KEY_name": "",
|
||||
"Email_display_name": "",
|
||||
"Email_icon": "",
|
||||
"Events_Loading": "",
|
||||
@@ -303,12 +312,14 @@
|
||||
"Gen_ReadDocs": "",
|
||||
"Gen_Remove_All": "",
|
||||
"Gen_Remove_Last": "",
|
||||
"Gen_Reset": "",
|
||||
"Gen_Restore": "",
|
||||
"Gen_Run": "",
|
||||
"Gen_Save": "",
|
||||
"Gen_Saved": "",
|
||||
"Gen_Search": "",
|
||||
"Gen_Select": "",
|
||||
"Gen_SelectIcon": "",
|
||||
"Gen_SelectToPreview": "",
|
||||
"Gen_Selected_Devices": "",
|
||||
"Gen_Switch": "",
|
||||
@@ -319,6 +330,7 @@
|
||||
"Gen_Warning": "",
|
||||
"Gen_Work_In_Progress": "",
|
||||
"Gen_create_new_device": "",
|
||||
"Gen_create_new_device_info": "",
|
||||
"General_display_name": "",
|
||||
"General_icon": "",
|
||||
"HRS_TO_KEEP_NEWDEV_description": "",
|
||||
|
||||
@@ -60,10 +60,15 @@
|
||||
"BackDevices_darkmode_enabled": "Mode fosc Activat",
|
||||
"CLEAR_NEW_FLAG_description": "Si està habilitat (<code>0</code> està desactivat), els dispositius marcats com <b>Nou dispositiu</b> es desmarcaran si el temps límit (especificat en hores) supera <b>Primera sessió</b>.",
|
||||
"CLEAR_NEW_FLAG_name": "Netejar indicador de nou",
|
||||
"CustProps_cant_remove": "No es pot eliminar, almenys es necessita una propietat.",
|
||||
"DAYS_TO_KEEP_EVENTS_description": "Això és una configuració de manteniment. Especifica el nombre de dies que es conservaran els esdeveniments. Els esdeveniments antics s'esborraran periòdicament. També aplica als esdeveniments dels Connectors (Plugins).",
|
||||
"DAYS_TO_KEEP_EVENTS_name": "Esborrar esdeveniments més vells de",
|
||||
"DISCOVER_PLUGINS_description": "Desactiva aquesta opció per accelerar la inicialització i l'estalvi de configuració. Quan està desactivat, els connectors no es descobreixen, i no podeu afegir nous connectors a la configuració <code>LOADED_PLUGINS</code>.",
|
||||
"DISCOVER_PLUGINS_name": "Descobreix els plugins",
|
||||
"DevDetail_Copy_Device_Title": "<i class=\"fa fa-copy\"></i> Copiar detalls des del dispositiu",
|
||||
"DevDetail_Copy_Device_Tooltip": "Copiar detalls del dispositius des de la llista desplegable. Tot el d'aquesta pàgina es sobre-escriurà",
|
||||
"DevDetail_CustomProperties_Title": "Propietats personalitzades",
|
||||
"DevDetail_CustomProps_reset_info": "Això eliminarà les seves propietats personalitzades en aquest dispositiu i restablir-les al valor predeterminat.",
|
||||
"DevDetail_DisplayFields_Title": "Pantalla",
|
||||
"DevDetail_EveandAl_AlertAllEvents": "Alertes",
|
||||
"DevDetail_EveandAl_AlertDown": "Cancel·lar alerta",
|
||||
@@ -180,6 +185,7 @@
|
||||
"DevDetail_button_Delete": "Eliminar dispositiu",
|
||||
"DevDetail_button_DeleteEvents": "Eliminar Esdeveniments",
|
||||
"DevDetail_button_DeleteEvents_Warning": "Estàs segur que vols eliminar tots els esdeveniments d'aquest dispositiu?<br><br>(això esborrarà la <b>Historial d'esdeveniments</b> i la <b>Sessions</b> i pot ajudar amb les notificacions constants (persistents)",
|
||||
"DevDetail_button_Delete_ask": "T'és segur vols eliminar aquest dispositiu? També el pots arxivar en comptes d'això.",
|
||||
"DevDetail_button_OverwriteIcons": "Sobreescriure icones",
|
||||
"DevDetail_button_OverwriteIcons_Tooltip": "Sobreescriure icones de tots els dispositius amb el mateix tipus de dispositiu",
|
||||
"DevDetail_button_OverwriteIcons_Warning": "Estàs segur que vols sobreescriure totes les icones de tots els dispositius amb el mateix tipus de dispositiu que el tipus de dispositiu actual?",
|
||||
@@ -203,6 +209,7 @@
|
||||
"Device_Shortcut_OnlineChart": "Presència de dispositius",
|
||||
"Device_TableHead_AlertDown": "Cancel·lar alerta",
|
||||
"Device_TableHead_Connected_Devices": "Connexions",
|
||||
"Device_TableHead_CustomProps": "Props / Accions",
|
||||
"Device_TableHead_Favorite": "Favorit",
|
||||
"Device_TableHead_FirstSession": "Primera Sessió",
|
||||
"Device_TableHead_GUID": "GUID",
|
||||
@@ -241,6 +248,8 @@
|
||||
"Donations_Title": "Donacions",
|
||||
"ENABLE_PLUGINS_description": "Habilita la <a target=\"_blank\" href=\"https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins\">connectors</a> funcionalitat. Carregar els connectors requereix més recursos de maquinari així podries voler desactivar-los en un sistema de baixos recursos.",
|
||||
"ENABLE_PLUGINS_name": "Activa els connectors(Plugins)",
|
||||
"ENCRYPTION_KEY_description": "Clau de xifrat de dades.",
|
||||
"ENCRYPTION_KEY_name": "Clau d'encriptació",
|
||||
"Email_display_name": "Correu electrònic",
|
||||
"Email_icon": "<i class=\"fa fa-at\"></i>",
|
||||
"Events_Loading": "Carregant ...",
|
||||
@@ -276,7 +285,7 @@
|
||||
"Events_Tablelenght": "Veure_entrades_MENU",
|
||||
"Events_Tablelenght_all": "Tot",
|
||||
"Events_Title": "Esdeveniments",
|
||||
"GRAPHQL_PORT_description": "El número de port del servidor GraphQL.",
|
||||
"GRAPHQL_PORT_description": "El número de port del servidor GraphQL. Comprova que el port és únic en totes les aplicacions d'aquest servidor i en totes les instàncies de NetAlertX.",
|
||||
"GRAPHQL_PORT_name": "Port GraphQL",
|
||||
"Gen_Action": "Acció",
|
||||
"Gen_Add": "Afegir",
|
||||
@@ -303,12 +312,14 @@
|
||||
"Gen_ReadDocs": "Llegit més dins el docs.",
|
||||
"Gen_Remove_All": "Esborra tot",
|
||||
"Gen_Remove_Last": "Esborra el darrer",
|
||||
"Gen_Reset": "Reinicialització",
|
||||
"Gen_Restore": "Executar Restaurar",
|
||||
"Gen_Run": "Executar",
|
||||
"Gen_Save": "Guardar",
|
||||
"Gen_Saved": "Guardat",
|
||||
"Gen_Search": "Cerca",
|
||||
"Gen_Select": "",
|
||||
"Gen_Select": "Seleccionar",
|
||||
"Gen_SelectIcon": "<i class=\"fa-solid fa-chevron-down fa-fade\"></i>",
|
||||
"Gen_SelectToPreview": "Seleccioneu la vista prèvia",
|
||||
"Gen_Selected_Devices": "Dispositius seleccionats:",
|
||||
"Gen_Switch": "Switch",
|
||||
@@ -318,7 +329,8 @@
|
||||
"Gen_Update_Value": "Actualitzar Valor",
|
||||
"Gen_Warning": "Advertència",
|
||||
"Gen_Work_In_Progress": "Work in progress, un bon moment per retroalimentació a https://github.com/jokob-sk/NetAlertX/issues",
|
||||
"Gen_create_new_device": "Crear un dispositiu simulat",
|
||||
"Gen_create_new_device": "Nou dispositiu",
|
||||
"Gen_create_new_device_info": "Els dispositius són típicament descobert utilitzant <a target=\"_blank\" href=\"https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins\">plugins</a>. Tanmateix, en certs casos, pots necessitar afegir dispositius a mà. Per explorar els temes concrets comproveu la <a target=\"_blank\" href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/REMOTE_NETWORKS.md\">documentació de Xarxes Remotes</a>.",
|
||||
"General_display_name": "General",
|
||||
"General_icon": "<i class=\"fa fa-gears\"></i>",
|
||||
"HRS_TO_KEEP_NEWDEV_description": "Això és un paràmetre de manteniment <b>ELIMINANT dispositius</b>. Si s'activa (<code>0</code> està desactivat), els dispositius marcats com <b>Dispositiu Nou</b> seran eliminats si el temps de <b>Primera Sessió</b> es més vell que les hores especificades en aquest paràmetre. Faci servir aquest paràmetre si vol auto-eliminar <b>Nous Dispositius</b> després de <code>X</code> hores.",
|
||||
@@ -477,7 +489,7 @@
|
||||
"Maintenance_themeselector_lable": "Selecciona una Skin",
|
||||
"Maintenance_themeselector_text": "El canvi té lloc a la part del servidor, per la qual cosa afecta tots els dispositius en ús.",
|
||||
"Maintenance_version": "Actualitzacions d'aplicació",
|
||||
"NETWORK_DEVICE_TYPES_description": "Quins tipus de dispositius es poden utilitzar com a dispositius de xarxa a la vista de xarxa. El tipus de dispositiu ha de coincidir exactament amb la <code>Type</code> configuració d'un dispositiu específic als detalls del dispositiu. No elimini els tipus existents, només afegir-ne nous.",
|
||||
"NETWORK_DEVICE_TYPES_description": "Quins tipus de dispositius es poden utilitzar com a dispositius de xarxa a la vista \"xarxa\". El tipus de dispositiu ha de coincidir exactament amb la configuració <code>Tipus</code> dels detalls de dispositiu. Afegir-ho al dispositiu fent servir el botó <code>+</code>. No elimini els tipus existents, només afegir-ne nous.",
|
||||
"NETWORK_DEVICE_TYPES_name": "Tipus de dispositiu de xarxa",
|
||||
"Navigation_About": "Sobre",
|
||||
"Navigation_Devices": "Dispositius",
|
||||
@@ -732,4 +744,4 @@
|
||||
"settings_update_item_warning": "Actualitza el valor sota. Sigues curós de seguir el format anterior. <b>No hi ha validació.</b>",
|
||||
"test_event_icon": "fa-vial-circle-check",
|
||||
"test_event_tooltip": "Deseu els canvis primer abans de comprovar la configuració."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,10 +60,15 @@
|
||||
"BackDevices_darkmode_enabled": "",
|
||||
"CLEAR_NEW_FLAG_description": "",
|
||||
"CLEAR_NEW_FLAG_name": "",
|
||||
"CustProps_cant_remove": "",
|
||||
"DAYS_TO_KEEP_EVENTS_description": "",
|
||||
"DAYS_TO_KEEP_EVENTS_name": "",
|
||||
"DISCOVER_PLUGINS_description": "",
|
||||
"DISCOVER_PLUGINS_name": "",
|
||||
"DevDetail_Copy_Device_Title": "",
|
||||
"DevDetail_Copy_Device_Tooltip": "",
|
||||
"DevDetail_CustomProperties_Title": "",
|
||||
"DevDetail_CustomProps_reset_info": "",
|
||||
"DevDetail_DisplayFields_Title": "",
|
||||
"DevDetail_EveandAl_AlertAllEvents": "",
|
||||
"DevDetail_EveandAl_AlertDown": "",
|
||||
@@ -180,6 +185,7 @@
|
||||
"DevDetail_button_Delete": "",
|
||||
"DevDetail_button_DeleteEvents": "",
|
||||
"DevDetail_button_DeleteEvents_Warning": "",
|
||||
"DevDetail_button_Delete_ask": "",
|
||||
"DevDetail_button_OverwriteIcons": "",
|
||||
"DevDetail_button_OverwriteIcons_Tooltip": "",
|
||||
"DevDetail_button_OverwriteIcons_Warning": "",
|
||||
@@ -203,6 +209,7 @@
|
||||
"Device_Shortcut_OnlineChart": "",
|
||||
"Device_TableHead_AlertDown": "",
|
||||
"Device_TableHead_Connected_Devices": "",
|
||||
"Device_TableHead_CustomProps": "",
|
||||
"Device_TableHead_Favorite": "",
|
||||
"Device_TableHead_FirstSession": "",
|
||||
"Device_TableHead_GUID": "",
|
||||
@@ -241,6 +248,8 @@
|
||||
"Donations_Title": "",
|
||||
"ENABLE_PLUGINS_description": "",
|
||||
"ENABLE_PLUGINS_name": "",
|
||||
"ENCRYPTION_KEY_description": "",
|
||||
"ENCRYPTION_KEY_name": "",
|
||||
"Email_display_name": "",
|
||||
"Email_icon": "",
|
||||
"Events_Loading": "",
|
||||
@@ -303,12 +312,14 @@
|
||||
"Gen_ReadDocs": "",
|
||||
"Gen_Remove_All": "",
|
||||
"Gen_Remove_Last": "",
|
||||
"Gen_Reset": "",
|
||||
"Gen_Restore": "",
|
||||
"Gen_Run": "",
|
||||
"Gen_Save": "",
|
||||
"Gen_Saved": "",
|
||||
"Gen_Search": "",
|
||||
"Gen_Select": "",
|
||||
"Gen_SelectIcon": "",
|
||||
"Gen_SelectToPreview": "",
|
||||
"Gen_Selected_Devices": "",
|
||||
"Gen_Switch": "",
|
||||
@@ -319,6 +330,7 @@
|
||||
"Gen_Warning": "",
|
||||
"Gen_Work_In_Progress": "",
|
||||
"Gen_create_new_device": "",
|
||||
"Gen_create_new_device_info": "",
|
||||
"General_display_name": "",
|
||||
"General_icon": "",
|
||||
"HRS_TO_KEEP_NEWDEV_description": "",
|
||||
|
||||
@@ -72,10 +72,15 @@
|
||||
"BackDevices_darkmode_enabled": "Dunkler Modus aktiviert",
|
||||
"CLEAR_NEW_FLAG_description": "Wenn aktiviert (<code>0</code> bedeutet deaktiviert), werden Geräte, die als <b>Neues Gerät</b> gekennzeichnet sind, wieder freigegeben, wenn das Zeitlimit (in Stunden) die Zeit ihrer <b>Ersten Sitzung</b> überschreitet.",
|
||||
"CLEAR_NEW_FLAG_name": "Neues Flag löschen",
|
||||
"CustProps_cant_remove": "Kann nicht entfernt werden, es wird mindestens eine Eigenschaft benötigt.",
|
||||
"DAYS_TO_KEEP_EVENTS_description": "Dies ist eine Wartungseinstellung. Spezifiziert wie viele Tage Events gespeichert bleiben. Alle älteren Events werden periodisch gelöscht. Wird auch auf die Plugins History angewendet.",
|
||||
"DAYS_TO_KEEP_EVENTS_name": "Ereignisse löschen, die älter sind als",
|
||||
"DISCOVER_PLUGINS_description": "",
|
||||
"DISCOVER_PLUGINS_name": "",
|
||||
"DevDetail_Copy_Device_Title": "Details von Gerät kopieren",
|
||||
"DevDetail_Copy_Device_Tooltip": "Details vom Gerät aus der Dropdown-Liste kopieren. Alles auf dieser Seite wird überschrieben",
|
||||
"DevDetail_CustomProperties_Title": "Benutzerdefinierte Eigenschaften",
|
||||
"DevDetail_CustomProps_reset_info": "Dadurch werden Ihre benutzerdefinierten Eigenschaften auf diesem Gerät entfernt und auf den Standardwert zurückgesetzt.",
|
||||
"DevDetail_DisplayFields_Title": "Anzeige",
|
||||
"DevDetail_EveandAl_AlertAllEvents": "Alarmereignisse",
|
||||
"DevDetail_EveandAl_AlertDown": "Melde Down",
|
||||
@@ -192,6 +197,7 @@
|
||||
"DevDetail_button_Delete": "Gerät löschen",
|
||||
"DevDetail_button_DeleteEvents": "Ereignisse löschen",
|
||||
"DevDetail_button_DeleteEvents_Warning": "Sind Sie sicher, dass Sie alle Ereignisse dieses Geräts löschen möchten? (dies löscht den Ereignisverlauf und die Sitzungen und könnte bei ständigen (anhaltenden) Benachrichtigungen helfen)",
|
||||
"DevDetail_button_Delete_ask": "Sind Sie sicher, dass Sie dieses Gerät löschen möchten? Sie können es stattdessen auch archivieren.",
|
||||
"DevDetail_button_OverwriteIcons": "Symbole überschreiben",
|
||||
"DevDetail_button_OverwriteIcons_Tooltip": "Symbole aller Geräte mit demselben Gerätetyp überschreiben",
|
||||
"DevDetail_button_OverwriteIcons_Warning": "Bist du sicher, dass du alle Symbole aller Geräte mit dem gleichen Gerätetyp wie dem aktuellen Gerätetyp überschreiben willst?",
|
||||
@@ -215,6 +221,7 @@
|
||||
"Device_Shortcut_OnlineChart": "Gerätepräsenz im Laufe der Zeit",
|
||||
"Device_TableHead_AlertDown": "",
|
||||
"Device_TableHead_Connected_Devices": "Verbindungen",
|
||||
"Device_TableHead_CustomProps": "",
|
||||
"Device_TableHead_Favorite": "Favorit",
|
||||
"Device_TableHead_FirstSession": "Erste Sitzung",
|
||||
"Device_TableHead_GUID": "GUID",
|
||||
@@ -253,6 +260,8 @@
|
||||
"Donations_Title": "Spenden",
|
||||
"ENABLE_PLUGINS_description": "NOTUSED Enables the <a target=\"_blank\" href=\"https://github.com/jokob-sk/NetAlertX/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": "NOTUSED Enable Plugins",
|
||||
"ENCRYPTION_KEY_description": "Schlüssel zur Datenverschlüsselung.",
|
||||
"ENCRYPTION_KEY_name": "Verschlüsselungsschlüssel",
|
||||
"Email_display_name": "Email",
|
||||
"Email_icon": "<i class=\"fa fa-at\"></i>",
|
||||
"Events_Loading": "Laden...",
|
||||
@@ -315,12 +324,14 @@
|
||||
"Gen_ReadDocs": "Mehr in der Dokumentation.",
|
||||
"Gen_Remove_All": "Alle entfernen",
|
||||
"Gen_Remove_Last": "Letzte entfernen",
|
||||
"Gen_Reset": "Zurücksetzen",
|
||||
"Gen_Restore": "Wiederherstellen",
|
||||
"Gen_Run": "Ausführen",
|
||||
"Gen_Save": "Speichern",
|
||||
"Gen_Saved": "Gespeichert",
|
||||
"Gen_Search": "Suchen",
|
||||
"Gen_Select": "",
|
||||
"Gen_Select": "Auswählen",
|
||||
"Gen_SelectIcon": "",
|
||||
"Gen_SelectToPreview": "Zur Vorschau auswählen",
|
||||
"Gen_Selected_Devices": "Ausgewählte Geräte:",
|
||||
"Gen_Switch": "Umschalten",
|
||||
@@ -330,7 +341,8 @@
|
||||
"Gen_Update_Value": "Wert aktualisieren",
|
||||
"Gen_Warning": "Warnung",
|
||||
"Gen_Work_In_Progress": "Keine Finalversion, feedback bitte unter: https://github.com/jokob-sk/NetAlertX/issues",
|
||||
"Gen_create_new_device": "",
|
||||
"Gen_create_new_device": "Neues Gerät",
|
||||
"Gen_create_new_device_info": "",
|
||||
"General_display_name": "Allgemein",
|
||||
"General_icon": "<i class=\"fa fa-gears\"></i>",
|
||||
"HRS_TO_KEEP_NEWDEV_description": "Dies ist eine Wartungseinstellung <b>DELETING devices</b>. Wenn aktiviert (<code>0</code> bedeutet deaktiviert), werden als <b>\"Neues Gerät\"</b> markierte Geräte gelöscht, wenn ihre <b>erste Sitzung</b> länger her ist als in dieser Einstellung angegeben. Verwenden Sie diese Einstellung, wenn Sie <b>Neue Geräte</b> nach <code>X</code> Stunden automatisch löschen wollen.",
|
||||
@@ -813,4 +825,4 @@
|
||||
"settings_update_item_warning": "",
|
||||
"test_event_icon": "",
|
||||
"test_event_tooltip": "Speichere die Änderungen, bevor Sie die Einstellungen testen."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,10 +60,15 @@
|
||||
"BackDevices_darkmode_enabled": "Darkmode Enabled",
|
||||
"CLEAR_NEW_FLAG_description": "If enabled (<code>0</code> is disabled), devices flagged as <b>New Device</b> will be unflagged if the time limit (specified in hours) exceeds their <b>First Session</b> time.",
|
||||
"CLEAR_NEW_FLAG_name": "Clear new flag",
|
||||
"CustProps_cant_remove": "Can't remove, at least one property is needed.",
|
||||
"DAYS_TO_KEEP_EVENTS_description": "This is a maintenance setting. This specifies the number of days worth of event entries that will be kept. All older events will be deleted periodically. Also applies on Plugin Events History.",
|
||||
"DAYS_TO_KEEP_EVENTS_name": "Delete events older than",
|
||||
"DISCOVER_PLUGINS_description": "Disable this option to speed up initialization and settings saving. When disabled, plugins are not discovered, and you cannot add new plugins to the <code>LOADED_PLUGINS</code> setting.",
|
||||
"DISCOVER_PLUGINS_name": "Discover plugins",
|
||||
"DevDetail_Copy_Device_Title": "Copy details from device",
|
||||
"DevDetail_Copy_Device_Tooltip": "Copy details from device from the dropdown list. Everything on this page will be overwritten",
|
||||
"DevDetail_CustomProperties_Title": "Custom Properties",
|
||||
"DevDetail_CustomProps_reset_info": "This will remove your custom properties on this device and reset them to the default value.",
|
||||
"DevDetail_DisplayFields_Title": "Display",
|
||||
"DevDetail_EveandAl_AlertAllEvents": "Alert Events",
|
||||
"DevDetail_EveandAl_AlertDown": "Alert Down",
|
||||
@@ -180,6 +185,7 @@
|
||||
"DevDetail_button_Delete": "Delete Device",
|
||||
"DevDetail_button_DeleteEvents": "Delete Events",
|
||||
"DevDetail_button_DeleteEvents_Warning": "Are you sure you want to delete all Events of this device?<br><br>(this will clear the <b>Events history</b> and the <b>Sessions</b> and might help with constant (persistent) notifications)",
|
||||
"DevDetail_button_Delete_ask": "Are you sure you want to delete this device? You may also archive it instead.",
|
||||
"DevDetail_button_OverwriteIcons": "Overwrite Icons",
|
||||
"DevDetail_button_OverwriteIcons_Tooltip": "Overwrite icons of all devices with the same device type",
|
||||
"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?",
|
||||
@@ -203,6 +209,7 @@
|
||||
"Device_Shortcut_OnlineChart": "Device presence",
|
||||
"Device_TableHead_AlertDown": "Alert Down",
|
||||
"Device_TableHead_Connected_Devices": "Connections",
|
||||
"Device_TableHead_CustomProps": "Props / Actions",
|
||||
"Device_TableHead_Favorite": "Favorite",
|
||||
"Device_TableHead_FirstSession": "First Session",
|
||||
"Device_TableHead_GUID": "GUID",
|
||||
@@ -241,6 +248,8 @@
|
||||
"Donations_Title": "Donations",
|
||||
"ENABLE_PLUGINS_description": "Enables the <a target=\"_blank\" href=\"https://github.com/jokob-sk/NetAlertX/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",
|
||||
"ENCRYPTION_KEY_description": "Data encryption key.",
|
||||
"ENCRYPTION_KEY_name": "Encryption key",
|
||||
"Email_display_name": "Email",
|
||||
"Email_icon": "<i class=\"fa fa-at\"></i>",
|
||||
"Events_Loading": "Loading...",
|
||||
@@ -276,7 +285,7 @@
|
||||
"Events_Tablelenght": "Show _MENU_ entries",
|
||||
"Events_Tablelenght_all": "All",
|
||||
"Events_Title": "Events",
|
||||
"GRAPHQL_PORT_description": "The port number of the GraphQL server.",
|
||||
"GRAPHQL_PORT_description": "The port number of the GraphQL server. Make sure the port is unique across all your applications on this host and NetAlertX instances.",
|
||||
"GRAPHQL_PORT_name": "GraphQL port",
|
||||
"Gen_Action": "Action",
|
||||
"Gen_Add": "Add",
|
||||
@@ -303,12 +312,14 @@
|
||||
"Gen_ReadDocs": "Read more in the docs.",
|
||||
"Gen_Remove_All": "Remove all",
|
||||
"Gen_Remove_Last": "Remove last",
|
||||
"Gen_Reset": "Reset",
|
||||
"Gen_Restore": "Run Restore",
|
||||
"Gen_Run": "Run",
|
||||
"Gen_Save": "Save",
|
||||
"Gen_Saved": "Saved",
|
||||
"Gen_Search": "Search",
|
||||
"Gen_Select": "Select",
|
||||
"Gen_SelectIcon": "<i class=\"fa-solid fa-chevron-down fa-fade\"></i>",
|
||||
"Gen_SelectToPreview": "Select to preview",
|
||||
"Gen_Selected_Devices": "Selected Devices:",
|
||||
"Gen_Switch": "Switch",
|
||||
@@ -318,7 +329,8 @@
|
||||
"Gen_Update_Value": "Update Value",
|
||||
"Gen_Warning": "Warning",
|
||||
"Gen_Work_In_Progress": "Work in progress, good time to feedback on https://github.com/jokob-sk/NetAlertX/issues",
|
||||
"Gen_create_new_device": "Create dummy device",
|
||||
"Gen_create_new_device": "New device",
|
||||
"Gen_create_new_device_info": "Devices are typically discovered using <a target=\"_blank\" href=\"https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins\">plugins</a>. However, in certain cases, you may need to add devices manually. To explore specific scenarios check the <a target=\"_blank\" href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/REMOTE_NETWORKS.md\">Remote Networks documentation</a>.",
|
||||
"General_display_name": "General",
|
||||
"General_icon": "<i class=\"fa fa-gears\"></i>",
|
||||
"HRS_TO_KEEP_NEWDEV_description": "This is a maintenance setting <b>DELETING devices</b>. If enabled (<code>0</code> is disabled), devices marked as <b>New Device</b> will be deleted if their <b>First Session</b> time was older than the specified hours in this setting. Use this setting if you want to auto-delete <b>New Devices</b> after <code>X</code> hours.",
|
||||
@@ -477,7 +489,7 @@
|
||||
"Maintenance_themeselector_lable": "Select Skin",
|
||||
"Maintenance_themeselector_text": "The change takes place on the server side, so it affects all devices in use.",
|
||||
"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_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. Add it on teh Device via the <code>+</code> button. Do not remove existing types, only add new ones.",
|
||||
"NETWORK_DEVICE_TYPES_name": "Network device types",
|
||||
"Navigation_About": "About",
|
||||
"Navigation_Devices": "Devices",
|
||||
|
||||
@@ -70,10 +70,15 @@
|
||||
"BackDevices_darkmode_enabled": "Darkmode Activado",
|
||||
"CLEAR_NEW_FLAG_description": "Si está habilitado (<code>0</code> está desactivado), los dispositivos marcados como <b>Nuevo dispositivo</b> se desmarcarán si el límite de tiempo (especificado en horas) excede su tiempo de <b>primera sesión</b>.",
|
||||
"CLEAR_NEW_FLAG_name": "Eliminar la nueva bandera",
|
||||
"CustProps_cant_remove": "",
|
||||
"DAYS_TO_KEEP_EVENTS_description": "Esta es una configuración de mantenimiento. Esto especifica el número de días de entradas de eventos que se guardarán. Todos los eventos anteriores se eliminarán periódicamente.",
|
||||
"DAYS_TO_KEEP_EVENTS_name": "Eliminar eventos anteriores a",
|
||||
"DISCOVER_PLUGINS_description": "Desactive esta opción para acelerar la inicialización y el ahorro de ajustes. Cuando está desactivado, los plugins no se descubren y no puede añadir nuevos plugins a la configuración <code>LOADED_PLUGINS</code>.",
|
||||
"DISCOVER_PLUGINS_name": "Descubrir plugins",
|
||||
"DevDetail_Copy_Device_Title": "Copiar detalles del dispositivo",
|
||||
"DevDetail_Copy_Device_Tooltip": "Copiar detalles del dispositivo de la lista desplegable. Todo en esta página se sobrescribirá",
|
||||
"DevDetail_CustomProperties_Title": "",
|
||||
"DevDetail_CustomProps_reset_info": "",
|
||||
"DevDetail_DisplayFields_Title": "Mostrar",
|
||||
"DevDetail_EveandAl_AlertAllEvents": "Notificaciones de eventos",
|
||||
"DevDetail_EveandAl_AlertDown": "Alerta de caída",
|
||||
@@ -131,7 +136,7 @@
|
||||
"DevDetail_SessionInfo_LastIP": "Última IP",
|
||||
"DevDetail_SessionInfo_LastSession": "Última conexión",
|
||||
"DevDetail_SessionInfo_StaticIP": "IP estática",
|
||||
"DevDetail_SessionInfo_Status": "Estado",
|
||||
"DevDetail_SessionInfo_Status": "Situación",
|
||||
"DevDetail_SessionInfo_Title": "Información de sesión",
|
||||
"DevDetail_SessionTable_Additionalinfo": "Información adicional",
|
||||
"DevDetail_SessionTable_Connection": "Conexión",
|
||||
@@ -190,6 +195,7 @@
|
||||
"DevDetail_button_Delete": "Eliminar dispositivo",
|
||||
"DevDetail_button_DeleteEvents": "Eliminar eventos",
|
||||
"DevDetail_button_DeleteEvents_Warning": "¿Desea eliminar todos los eventos de este dispositivo?<br><br>(se eliminarán el <b>Historial de eventos</b> y las <b>Sesiones</b>, y puede ayudar en el caso de notificaciones constantes)",
|
||||
"DevDetail_button_Delete_ask": "",
|
||||
"DevDetail_button_OverwriteIcons": "Sobreescribir iconos",
|
||||
"DevDetail_button_OverwriteIcons_Tooltip": "Sobreescribir los iconos de todos los dispositivos con el mismo tipo",
|
||||
"DevDetail_button_OverwriteIcons_Warning": "¿Sobreescribir todos los iconos de todos los dispositivos con el mismo tipo que el dispositivo actual?",
|
||||
@@ -213,6 +219,7 @@
|
||||
"Device_Shortcut_OnlineChart": "Presencia del dispositivo a lo largo del tiempo",
|
||||
"Device_TableHead_AlertDown": "Alerta desactivada",
|
||||
"Device_TableHead_Connected_Devices": "Conexiones",
|
||||
"Device_TableHead_CustomProps": "",
|
||||
"Device_TableHead_Favorite": "Favorito",
|
||||
"Device_TableHead_FirstSession": "1ra. sesión",
|
||||
"Device_TableHead_GUID": "GUID",
|
||||
@@ -230,8 +237,8 @@
|
||||
"Device_TableHead_Parent_MAC": "Nodo principal de la MAC",
|
||||
"Device_TableHead_Port": "Puerto",
|
||||
"Device_TableHead_PresentLastScan": "Historial",
|
||||
"Device_TableHead_RowID": "Row ID",
|
||||
"Device_TableHead_Rowid": "Row ID",
|
||||
"Device_TableHead_RowID": "ID de fila",
|
||||
"Device_TableHead_Rowid": "ID de fila",
|
||||
"Device_TableHead_SSID": "SSID",
|
||||
"Device_TableHead_SourcePlugin": "Fuente del plugin",
|
||||
"Device_TableHead_Status": "Situación",
|
||||
@@ -251,6 +258,8 @@
|
||||
"Donations_Title": "Donaciones",
|
||||
"ENABLE_PLUGINS_description": "Habilita la funcionalidad de los <a target=\"_blank\" href=\"https://github.com/jokob-sk/NetAlertX/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",
|
||||
"ENCRYPTION_KEY_description": "",
|
||||
"ENCRYPTION_KEY_name": "",
|
||||
"Email_display_name": "Email",
|
||||
"Email_icon": "<i class=\"fa fa-at\"></i>",
|
||||
"Events_Loading": "Cargando...",
|
||||
@@ -313,12 +322,14 @@
|
||||
"Gen_ReadDocs": "Lee más en los documentos.",
|
||||
"Gen_Remove_All": "Quitar todo",
|
||||
"Gen_Remove_Last": "Quitar el último",
|
||||
"Gen_Reset": "",
|
||||
"Gen_Restore": "Ejecutar restauración",
|
||||
"Gen_Run": "Ejecutar",
|
||||
"Gen_Save": "Guardar",
|
||||
"Gen_Saved": "Guardado",
|
||||
"Gen_Search": "Buscar",
|
||||
"Gen_Select": "",
|
||||
"Gen_Select": "Seleccionar",
|
||||
"Gen_SelectIcon": "<i class=\"fa-solid fa-chevron-down fa-fade\"></i>",
|
||||
"Gen_SelectToPreview": "Seleccionar para previsualizar",
|
||||
"Gen_Selected_Devices": "Dispositivos seleccionados:",
|
||||
"Gen_Switch": "Cambiar",
|
||||
@@ -328,7 +339,8 @@
|
||||
"Gen_Update_Value": "Actualizar valor",
|
||||
"Gen_Warning": "Advertencia",
|
||||
"Gen_Work_In_Progress": "Trabajo en curso, un buen momento para hacer comentarios en https://github.com/jokob-sk/NetAlertX/issues",
|
||||
"Gen_create_new_device": "Crear un dispositivo ficticio",
|
||||
"Gen_create_new_device": "Nuevo dispositivo",
|
||||
"Gen_create_new_device_info": "Los dispositivos se suelen descubrir utilizando <a target=\"_blank\" href=\"https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins\">plugins</a>. Sin embargo, en algunos casos, es posible que necesite agregar dispositivos manualmente. Para explorar escenarios específicos, consulte la documentación <a target=\"_blank\" href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/REMOTE_NETWORKS.md\">Redes remotas</a>.",
|
||||
"General_display_name": "General",
|
||||
"General_icon": "<i class=\"fa fa-gears\"></i>",
|
||||
"HRS_TO_KEEP_NEWDEV_description": "Se trata de una configuración de mantenimiento <b>BORRAR dispositivos</b>. Si está activado (<code>0</code> está desactivado), los dispositivos marcados como <b>Nuevo dispositivo</b> se eliminarán si su fecha de <b>primera sesión</b> es anterior a las horas especificadas en este ajuste. Use este ajuste si desea eliminar automáticamente <b>Nuevos dispositivos</b> después de <code>X</code> horas.",
|
||||
@@ -359,7 +371,7 @@
|
||||
"HelpFAQ_Cat_General_102_head": "Me aparece el mensaje de que la base de datos es de sólo de lectura.",
|
||||
"HelpFAQ_Cat_General_102_text": "Compruebe en el directorio NetAlertX si la carpeta de la base de datos (db) tiene asignados los permisos correctos:<br> <span class=\"text-danger help_faq_code\">drwxrwx--- 2 (nombre de usuario) www-data</span><br> Si el permiso no es correcto, puede establecerlo de nuevo con los siguientes comandos en la terminal o la consola:<br> <span class=\"text-danger help_faq_code\"> sudo chgrp -R www-data /app/db<br> chmod -R 770 /app/db </span><br> Si la base de datos sigue siendo de sólo lectura, intente reinstalar o restaurar una copia de seguridad de la base de datos desde la página de mantenimiento.",
|
||||
"HelpFAQ_Cat_General_102docker_head": "(🐳 Solo Docker) Problemas con la base de datos (errores de AJAX, solo lectura, no encontrado)",
|
||||
"HelpFAQ_Cat_General_102docker_text": "Comprueba que has seguido las instrucciones del <a href=\"https://github.com/jokob-sk/NetAlertX/tree/main/dockerfiles\">dockerfile (la información más actualizada)</a>. <br/> <br/> <ul data-sourcepos=\"49:4-52:146\" dir=\"auto\"> <li data-sourcepos=\"49:4-49:106\">Descarga la <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/db/app.db\">base de datos original desde GitHub</a>.</li> <li data-sourcepos=\"50:4-50:195\">Mapea el archivo <code>app.db</code> (<g-emoji class=\"g-emoji\" alias=\"warning\" fallback-src=\"https://github.githubassets.com/images/icons/emoji/unicode/26a0.png\">⚠</g-emoji> no carpeta) de arriba a <code>/app/db/app.db</code> (puedes comprobar los <a href=\"https://github.com/jokob-sk/NetAlertX/tree/main/dockerfiles#-examples\">ejemplos</a> para más detalles).</li> <li data-sourcepos=\"51:4-51:161\">Si aparecen problemas (errores de AJAX, no se puede escribir a la base de datos, etc,) asegúrate que los permisos están establecidos correctamente. También puedes comprobar los registros en <code>/app/front/log</code>.</li> <li data-sourcepos=\"52:4-52:146\">Para arreglar los problemas de los permisos, puedes probar a crear una copia de seguridad de la base de datos y después restaurarla desde la sección <strong>Mantenimiento > Copia de seguridad/Restaurar</strong>.</li> <li data-sourcepos=\"53:4-53:228\">Si la base de datos está en modo solo lectura, lo puedes arreglar ejecutando el siguiente comando para establecer el propietario y grupo en el sistema host: <code>docker exec netalertx chown -R www-data:www-data /app/db/app.db</code>.</li> </ul>",
|
||||
"HelpFAQ_Cat_General_102docker_text": "Compruebe dos veces que ha seguido el <a target=\"_blank\" href=\"https://github.com/jokob-sk/NetAlertX/tree/main/dockerfiles\">dockerfile readme (información más actualizada)</a>. <br/> <br/> <ul data-sourcepos=\"49:4-52:146\" dir=\"auto\"><li data-sourcepos=\"49:4-49:106\">Descargue el <a target=\"_blank\" href=\"https://github.com/jokob-sk/NetAlertX/blob/main/db/app.db\">DB original de GitHub</a>. </li><li data-sourcepos=\"50:4-50:195\">Mapear el archivo <code>app.db</code> (<g-emoji class=\"g-emoji\" alias=\"warning\" fallback-src=\"https://github.githubassets.com/images/icons/emoji/unicode/26a0.png\">⚠</g-emoji> no carpeta) desde arriba hasta <code>/db/app.db/app.code> (ver <a target=\"_blank\"\thref=\"https://github.com/jokob-sk/NetAlertX/tree/main/dockerfiles#-examples\">Ejemplos</a> para más detalles). </li><li data-sourcepos=\"51:4-51:161\">Si hay problemas (errores AJAX, no se puede escribir en la base de datos, etc), asegúrese de que los permisos estén configurados correctamente; alternativamente, compruebe los registros en <code>/app/front/log</code>. </li><li data-sourcepos=\"52:4-52:146\">Para resolver problemas de permisos, también puede intentar crear una copia de seguridad de la base de datos y luego ejecutar una restauración de la misma a través de la sección <strong>Mantenimiento > Copia de seguridad/restauración</strong>. </li><li data-sourcepos=\"53:4-53:228\">Si la base de datos está en modo de solo lectura, puede resolver esto configurando el propietario y el grupo ejecutando el siguiente comando en el sistema host: <code>docker exec netalertx chown -R www-data:www-data /app/db/app.db</code>. </li></ul>",
|
||||
"HelpFAQ_Cat_General_103_head": "La página de inicio de sesión no aparece, incluso después de cambiar la contraseña.",
|
||||
"HelpFAQ_Cat_General_103_text": "Además de la contraseña, el archivo de configuración debe contener <span class=\"text-danger help_faq_code\">/app/config/app.conf</span> además el parámetro <span class=\"text-danger help_faq_code\">PIALERT_WEB_PROTECTION</span> debe ajustarse a <span class=\"text-danger help_faq_code\">True</span>.",
|
||||
"HelpFAQ_Cat_Network_600_head": "¿Para qué sirve esta sección?",
|
||||
@@ -811,4 +823,4 @@
|
||||
"settings_update_item_warning": "Actualice el valor a continuación. Tenga cuidado de seguir el formato anterior. <b>O la validación no se realiza.</b>",
|
||||
"test_event_icon": "fa-vial-circle-check",
|
||||
"test_event_tooltip": "Guarda tus cambios antes de probar nuevos ajustes."
|
||||
}
|
||||
}
|
||||
|
||||