Compare commits
235 Commits
v24.12.23
...
bdf6e62ea6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdf6e62ea6 | ||
|
|
eb693bfdb2 | ||
|
|
cc5c4a6f06 | ||
|
|
ba27769fbd | ||
|
|
b6c6579cb5 | ||
|
|
c6adaf99f4 | ||
|
|
f793dec6c5 | ||
|
|
e840320e19 | ||
|
|
0fe903e076 | ||
|
|
cd8124a912 | ||
|
|
7867700856 | ||
|
|
3be39f6508 | ||
|
|
b69b76aa9f | ||
|
|
8b7431eae9 | ||
|
|
079a1b3954 | ||
|
|
ef8bfbb59e | ||
|
|
c30d98dd77 | ||
|
|
57ccdf0b0c | ||
|
|
703ba5c75b | ||
|
|
aad74451ef | ||
|
|
a787510963 | ||
|
|
dd2b872712 | ||
|
|
2a5e419034 | ||
|
|
b921144dbb | ||
|
|
4f2ddccdde | ||
|
|
780b818815 | ||
|
|
5779fd34c5 | ||
|
|
b7a6fe9112 | ||
|
|
906bfd24a4 | ||
|
|
8f48172940 | ||
|
|
736304eb8a | ||
|
|
5fce3c79b0 | ||
|
|
81c1f65816 | ||
|
|
edfaadf682 | ||
|
|
0a51d5fe79 | ||
|
|
893063c695 | ||
|
|
da5cf4a8f1 | ||
|
|
1e5a4e96e4 | ||
|
|
74d7a7853a | ||
|
|
63469007ef | ||
|
|
f5b875e2df | ||
|
|
d18efb2103 | ||
|
|
e2cdce2f39 | ||
|
|
c855d50999 | ||
|
|
9d8b147e40 | ||
|
|
3aa9be7019 | ||
|
|
72b3d5eb6d | ||
|
|
83bc406ed6 | ||
|
|
e6e1c79d6a | ||
|
|
e0616f72fe | ||
|
|
696403ac20 | ||
|
|
c946a5335a | ||
|
|
9610810891 | ||
|
|
2ad7f02255 | ||
|
|
431543ba80 | ||
|
|
09d2e68479 | ||
|
|
896b8b7641 | ||
|
|
8d607aac96 | ||
|
|
c95a371ad9 | ||
|
|
4b3ff048dc | ||
|
|
3267762280 | ||
|
|
885a470585 | ||
|
|
d0f4faca51 | ||
|
|
4443c69d31 | ||
|
|
890e533969 | ||
|
|
d7b9bb447f | ||
|
|
c63f424c7d | ||
|
|
dd1580e536 | ||
|
|
630e4f6327 | ||
|
|
cb8af32553 | ||
|
|
d469a9ded4 | ||
|
|
c8a40920b4 | ||
|
|
b0cd9acb79 | ||
|
|
6129f31a24 | ||
|
|
3eb8f39b5c | ||
|
|
5b1002620b | ||
|
|
e50d757f57 | ||
|
|
5110a3c2f3 | ||
|
|
abf7be5958 | ||
|
|
82708bd5df | ||
|
|
b5dce3f6aa | ||
|
|
5562ae7add | ||
|
|
4363e083d5 | ||
|
|
e766b19d8c | ||
|
|
b18ee70b8a | ||
|
|
f70bb40ef4 | ||
|
|
844a408ff4 | ||
|
|
a21cc0db85 | ||
|
|
a5427f795b | ||
|
|
6b390b66de | ||
|
|
af1e9b921b | ||
|
|
9503cc6397 | ||
|
|
a16c2dfed6 | ||
|
|
b4fc05d1e8 | ||
|
|
adb99e5f1a | ||
|
|
fbce3e18c2 | ||
|
|
0fcda5ff0a | ||
|
|
a5c6510654 | ||
|
|
6d44ed1bba | ||
|
|
314372a0f2 | ||
|
|
5a84cb5cc2 | ||
|
|
12cc71552c | ||
|
|
6752b7fc40 | ||
|
|
f371515258 | ||
|
|
f1d73f6ad4 | ||
|
|
9fee846436 | ||
|
|
b4ebd640e5 | ||
|
|
e94953b9af | ||
|
|
6718d054dc | ||
|
|
88e4dbf12e | ||
|
|
9f1db5ca1a | ||
|
|
5e7bb207c8 | ||
|
|
6ad90610ea | ||
|
|
c8ff0d79d1 | ||
|
|
61cf87f0d6 | ||
|
|
75bbc8bcf3 | ||
|
|
5582c0bf5e | ||
|
|
8278a92b11 | ||
|
|
d5ac00a307 | ||
|
|
2b02596b17 | ||
|
|
2a736f3c19 | ||
|
|
0b5c0b198a | ||
|
|
2d0a461724 | ||
|
|
74c6faccc7 | ||
|
|
89a154e224 | ||
|
|
8b84e4b325 | ||
|
|
e55002a9b0 | ||
|
|
b4e741568b | ||
|
|
711e0012cb | ||
|
|
ea2e8459b5 | ||
|
|
16d06e8a74 | ||
|
|
9625c36d12 | ||
|
|
f47858d773 | ||
|
|
ac2ce85f33 | ||
|
|
e887a11755 | ||
|
|
73f432c786 | ||
|
|
9c95a79e07 | ||
|
|
f0bbd37812 | ||
|
|
7b0777b805 | ||
|
|
b8dce59138 | ||
|
|
f1d79074ec | ||
|
|
4156bc1669 | ||
|
|
00c4dd86c6 | ||
|
|
979ce8fd75 | ||
|
|
ede926beee | ||
|
|
e1f91ddf17 | ||
|
|
e9539962c9 | ||
|
|
87d18a9067 | ||
|
|
2302cd9a31 | ||
|
|
babe0eab53 | ||
|
|
7c878690ef | ||
|
|
b29dcbfa98 | ||
|
|
8d9c48166b | ||
|
|
540f8e850b | ||
|
|
8316d8e741 | ||
|
|
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 |
@@ -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/feature_request.yml
vendored
@@ -46,7 +46,7 @@ body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Can I help implement this? 👩💻👨💻
|
||||
description: The maintainer will provide guidance and help. The implementer will read the PR guidelines https://github.com/jokob-sk/NetAlertX/tree/main/docs#-pull-requests-prs
|
||||
description: The maintainer will provide guidance and help. The implementer will read the PR guidelines https://jokob-sk.github.io/NetAlertX/DEV_ENV_SETUP/
|
||||
options:
|
||||
- label: "Yes"
|
||||
- label: "No"
|
||||
|
||||
9
.github/ISSUE_TEMPLATE/i-have-an-issue.yml
vendored
@@ -7,7 +7,7 @@ body:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an open or closed issue already exists for the bug you encountered.
|
||||
options:
|
||||
- label: I have searched the existing open and closed issues and I checked the docs https://github.com/jokob-sk/NetAlertX/tree/main/docs
|
||||
- label: I have searched the existing open and closed issues and I checked the docs https://jokob-sk.github.io/NetAlertX/
|
||||
required: true
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
@@ -60,10 +60,11 @@ body:
|
||||
required: false
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: What branch are you running?
|
||||
label: What installation are you running?
|
||||
options:
|
||||
- Production
|
||||
- Dev
|
||||
- Production (netalertx)
|
||||
- Dev (netalertx-dev)
|
||||
- Home Assistant (addon)
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
72
.github/ISSUE_TEMPLATE/setup-help.yml
vendored
Executable file
@@ -0,0 +1,72 @@
|
||||
name: Setup help
|
||||
description: 'When submitting an issue enable LOG_LEVEL="trace" and re-search first.'
|
||||
labels: ['Setup 📥']
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Did I research?
|
||||
description: Please confirm you checked the usual places before opening a setup support request.
|
||||
options:
|
||||
- label: I have searched the docs https://jokob-sk.github.io/NetAlertX/
|
||||
required: true
|
||||
- label: I have searched the existing open and closed issues
|
||||
required: true
|
||||
- label: I confirm my SCAN_SUBNETS is configured and tested as per https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md
|
||||
required: true
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: The issue occurs in the following browsers. Select at least 2.
|
||||
description: This step helps me understand if this is a cache or browser-specific issue.
|
||||
options:
|
||||
- label: "Firefox"
|
||||
- label: "Chrome"
|
||||
- label: "Other (unsupported) - PRs welcome"
|
||||
- label: "N/A - This is an issue with the backend"
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: What I want to do
|
||||
description: Describe what you want to achieve.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Relevant settings you changed
|
||||
description: |
|
||||
Paste a screenshot or setting values of the settings you changed.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: docker-compose.yml
|
||||
description: |
|
||||
Paste your `docker-compose.yml`
|
||||
render: python
|
||||
validations:
|
||||
required: false
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: What installation are you running?
|
||||
options:
|
||||
- Production (netalertx)
|
||||
- Dev (netalertx-dev)
|
||||
- Home Assistant (addon)
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: app.log
|
||||
description: |
|
||||
Logs with debug enabled (https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEBUG_TIPS.md) ⚠
|
||||
***Generally speaking, all bug reports should have logs provided.***
|
||||
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||
Additionally, any additional info? Screenshots? References? Anything that will give us more context about the issue you are encountering!
|
||||
You can use `tail -100 /app/log/app.log` in the container if you have trouble getting to the log files.
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Debug enabled
|
||||
description: I confirm I enabled `debug`
|
||||
options:
|
||||
- label: I have read and followed the steps in the wiki link above and provided the required debug logs and the log section covers the time when the issue occurs.
|
||||
required: true
|
||||
25
.github/workflows/mkdocs.yml
vendored
Executable file
@@ -0,0 +1,25 @@
|
||||
name: Deploy MkDocs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main # Change if your default branch is different
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.9'
|
||||
|
||||
- name: Install MkDocs
|
||||
run: |
|
||||
pip install mkdocs mkdocs-material && pip install mkdocs-github-admonitions-plugin
|
||||
|
||||
- name: Deploy MkDocs
|
||||
run: mkdocs gh-deploy --force
|
||||
43
.github/workflows/social_post_on_release.yml
vendored
Executable file
@@ -0,0 +1,43 @@
|
||||
name: 📧 Twitter and Discord Posts
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
post-discord:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Wait for 15 minutes
|
||||
run: sleep 900 # 15 minutes delay
|
||||
|
||||
- name: Post to Discord
|
||||
run: |
|
||||
curl -X POST -H "Content-Type: application/json" \
|
||||
-d '{"content": "🎉 New release: **${{ github.event.release.name }}** is live! 🚀\nCheck it out here: ${{ github.event.release.html_url }}"}' \
|
||||
${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
|
||||
post-twitter:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Wait for 15 minutes
|
||||
run: sleep 900 # 15 minutes delay
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Create tweet file
|
||||
run: |
|
||||
echo "🎉 New release: **${{ github.event.release.name }}** is live! 🚀" > .github/tweet.md
|
||||
echo "Check it out here: ${{ github.event.release.html_url }}" >> .github/tweet.md
|
||||
git add .github/tweet.md
|
||||
git commit -m "Add release tweet for ${{ github.event.release.name }}"
|
||||
git push
|
||||
|
||||
- name: Tweet
|
||||
uses: twitter-together/action@v3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }}
|
||||
TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
|
||||
TWITTER_API_KEY: ${{ secrets.TWITTER_API_KEY }}
|
||||
TWITTER_API_SECRET_KEY: ${{ secrets.TWITTER_API_SECRET_KEY }}
|
||||
29
.github/workflows/update_sponsors_table.yml
vendored
@@ -1,29 +0,0 @@
|
||||
name: 🤖Automation - Update Sponsors Table
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '50 11 * * *' # Set your preferred schedule (UTC)
|
||||
|
||||
jobs:
|
||||
update-table:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.8
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r update_sponsors_requirements.txt # If you have any Python dependencies
|
||||
|
||||
- name: Update Sponsors Table
|
||||
run: |
|
||||
python update_sponsors.py
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
9
.gitignore
vendored
@@ -11,6 +11,7 @@ front/log/*
|
||||
front/api/*
|
||||
/api/*
|
||||
**/plugins/**/*.log
|
||||
**/plugins/cloud_services/*
|
||||
**/%40eaDir/
|
||||
**/@eaDir/
|
||||
|
||||
@@ -24,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
|
||||
|
||||
34
Dockerfile
@@ -1,11 +1,11 @@
|
||||
FROM alpine:3.20 AS builder
|
||||
FROM alpine:3.21 AS builder
|
||||
|
||||
ARG INSTALL_DIR=/app
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
# Install build dependencies
|
||||
RUN apk add --no-cache bash python3 python3-dev gcc musl-dev libffi-dev openssl-dev git\
|
||||
RUN apk add --no-cache bash shadow python3 python3-dev gcc musl-dev libffi-dev openssl-dev git \
|
||||
&& python -m venv /opt/venv
|
||||
|
||||
# Enable venv
|
||||
@@ -13,34 +13,21 @@ 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 asusrouter asyncio aiohttp graphene flask 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 {} \;"
|
||||
|
||||
# Append Iliadbox certificate to aiofreepybox
|
||||
RUN printf "\n-----BEGIN CERTIFICATE-----\n\
|
||||
MIICOjCCAcCgAwIBAgIUI0Tu7zsrBJACQIZgLMJobtbdNn4wCgYIKoZIzj0EAwIw\n\
|
||||
TDELMAkGA1UEBhMCSVQxDjAMBgNVBAgMBUl0YWx5MQ4wDAYDVQQKDAVJbGlhZDEd\n\
|
||||
MBsGA1UEAwwUSWxpYWRib3ggRUNDIFJvb3QgQ0EwHhcNMjAxMTI3MDkzODEzWhcN\n\
|
||||
NDAxMTIyMDkzODEzWjBMMQswCQYDVQQGEwJJVDEOMAwGA1UECAwFSXRhbHkxDjAM\n\
|
||||
BgNVBAoMBUlsaWFkMR0wGwYDVQQDDBRJbGlhZGJveCBFQ0MgUm9vdCBDQTB2MBAG\n\
|
||||
ByqGSM49AgEGBSuBBAAiA2IABMryJyb2loHNAioY8IztN5MI3UgbVHVP/vZwcnre\n\
|
||||
ZvJOyDvE4HJgIti5qmfswlnMzpNbwf/MkT+7HAU8jJoTorRm1wtAnQ9cWD3Ebv79\n\
|
||||
RPwtjjy3Bza3SgdVxmd6fWPUKaNjMGEwHQYDVR0OBBYEFDUij/4lpoJ+kOXRyrcM\n\
|
||||
jf2RPzOqMB8GA1UdIwQYMBaAFDUij/4lpoJ+kOXRyrcMjf2RPzOqMA8GA1UdEwEB\n\
|
||||
/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQC6eUV1\n\
|
||||
pFh4UpJOTc1JToztN4ttnQR6rIzxMZ6mNCe+nhjkohWp24pr7BpUYSbEizYCMAQ6\n\
|
||||
LCiBKV2j7QQGy7N1aBmdur17ZepYzR1YV0eI+Kd978aZggsmhjXENQYVTmm/XA==\n\
|
||||
-----END CERTIFICATE-----\n" >> /opt/venv/lib/python3.12/site-packages/aiofreepybox/freebox_certificates.pem
|
||||
RUN cat ${INSTALL_DIR}/install/freebox_certificate.pem >> /opt/venv/lib/python3.12/site-packages/aiofreepybox/freebox_certificates.pem
|
||||
|
||||
# second stage
|
||||
FROM alpine:3.20 AS runner
|
||||
FROM alpine:3.21 AS runner
|
||||
|
||||
ARG INSTALL_DIR=/app
|
||||
|
||||
COPY --from=builder /opt/venv /opt/venv
|
||||
COPY --from=builder /usr/sbin/usermod /usr/sbin/groupmod /usr/sbin/
|
||||
|
||||
# Enable venv
|
||||
ENV PATH="/opt/venv/bin:$PATH"
|
||||
@@ -54,11 +41,10 @@ ENV S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0
|
||||
# ❗ IMPORTANT - if you modify this file modify the /install/install_dependecies.sh file as well ❗
|
||||
|
||||
RUN apk update --no-cache \
|
||||
&& apk add --no-cache bash zip lsblk gettext-envsubst sudo mtr tzdata s6-overlay \
|
||||
&& apk add --no-cache curl arp-scan iproute2 iproute2-ss nmap nmap-scripts traceroute nbtscan avahi avahi-tools openrc dbus net-tools net-snmp-tools bind-tools awake ca-certificates \
|
||||
&& apk add --no-cache bash libbsd zip lsblk gettext-envsubst sudo mtr tzdata s6-overlay \
|
||||
&& apk add --no-cache curl arp-scan iproute2 iproute2-ss nmap nmap-scripts traceroute nbtscan avahi avahi-tools openrc dbus net-tools net-snmp-tools bind-tools awake ca-certificates \
|
||||
&& apk add --no-cache sqlite php83 php83-fpm php83-cgi php83-curl php83-sqlite3 php83-session \
|
||||
&& apk add --no-cache python3 nginx \
|
||||
&& apk add --no-cache dcron \
|
||||
&& ln -s /usr/bin/awake /usr/bin/wakeonlan \
|
||||
&& bash -c "install -d -m 750 -o nginx -g www-data ${INSTALL_DIR} ${INSTALL_DIR}" \
|
||||
&& rm -f /etc/nginx/http.d/default.conf
|
||||
@@ -66,10 +52,10 @@ RUN apk update --no-cache \
|
||||
COPY --from=builder --chown=nginx:www-data ${INSTALL_DIR}/ ${INSTALL_DIR}/
|
||||
|
||||
# Add crontab file
|
||||
COPY install/crontab /etc/crontabs/root
|
||||
COPY --chmod=600 --chown=root:root 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}/php/server/query_json.php?file=app_state.json
|
||||
|
||||
@@ -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 asusrouter asyncio aiohttp graphene flask 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
|
||||
|
||||
122
README.md
@@ -1,104 +1,80 @@
|
||||
[](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)
|
||||
[](https://discord.gg/NczTUTWyRr)
|
||||
[](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Falexbelgium%2Fhassio-addons)
|
||||
|
||||
# 🖧🔍 Network scanner & notification framework
|
||||
# NetAlertX - Network, presence scanner and alert framework
|
||||
|
||||
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).
|
||||
Get visibility of what's going on on your WIFI/LAN network and enable presence detection of important devices. Schedule scans for devices, port changes and get alerts if unknown devices or changes are found. Write your own [Plugin](https://github.com/jokob-sk/NetAlertX/tree/main/docs/PLUGINS.md#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) |
|
||||
|----------------------|----------------------| ----------------------| ----------------------|
|
||||
| [📑 Docker guide](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md) | [🚀 Releases](https://github.com/jokob-sk/NetAlertX/releases) | [📚 Docs](https://jokob-sk.github.io/NetAlertX/) | [🔌 Plugins](https://github.com/jokob-sk/NetAlertX/blob/main/docs/PLUGINS.md) | [🤖 Ask AI](https://gurubase.io/g/netalertx)
|
||||
|----------------------| ----------------------| ----------------------| ----------------------| ----------------------|
|
||||
|
||||
|
||||
| ![Main screen][main] | ![device_details 1][device_details] | ![Screen network][network] |
|
||||
|----------------------|----------------------| ----------------------|
|
||||
|
||||
![network_setup][network_setup]
|
||||
|
||||
|
||||
Head to [https://netalertx.com/](https://netalertx.com/) for more gifs and screenshots 📷.
|
||||
![showcase][showcase]
|
||||
|
||||
<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>
|
||||
|
||||
<details>
|
||||
<summary>❓ Why use Net<b>Alert</b><sup>x</sup>?</summary>
|
||||
|
||||
<hr>
|
||||
|
||||
Most of us don't know what's going on on our home network, but we want our family and data to be safe. _Command-line tools_ are great, but the output can be _hard to understand_ and action if you are not a network specialist.
|
||||
|
||||
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 📦.
|
||||
|
||||
Set up a _kill switch ☠_ for your network via a smart plug with the available [Home Assistant](https://github.com/jokob-sk/NetAlertX/blob/main/docs/HOME_ASSISTANT.md) integration. Implement custom automations with the [CSV device Exports 📤](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/csv_backup), [Webhooks](https://github.com/jokob-sk/NetAlertX/blob/main/docs/WEBHOOK_N8N.md), or [API endpoints](https://github.com/jokob-sk/NetAlertX/blob/main/docs/API.md) features.
|
||||
|
||||
Extend the app if you want to create your own scanner [Plugin](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins#readme) and handle the results and notifications in Net<b>Alert</b><sup>x</sup>.
|
||||
|
||||
Looking forward to your contributions if you decide to share your work with the community ❤.
|
||||
Head to [https://netalertx.com/](https://netalertx.com/) for even more gifs and screenshots 📷.
|
||||
|
||||
</details>
|
||||
|
||||
## Scan Methods, Notifications, Integration, Extension system
|
||||
## 📦 Features
|
||||
|
||||
| Features | Details |
|
||||
|-------------|-------------|
|
||||
| 🔍 | The app scans your network for, **New devices**, **New connections** (re-connections), **Disconnections**, **"Always Connected" devices down**, Devices **IP changes** and **Internet IP address changes**. Discovery & scan methods include: **arp-scan**, **Pi-hole - DB import**, **Pi-hole - DHCP leases import**, **Generic DHCP leases import**. **UNIFI controller import**, **SNMP-enabled router import**. Check the [Plugins](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins#readme) docs for more info on individual scans. |
|
||||
|📧 | Send notifications to more than 80+ services, including Telegram via [Apprise](https://hub.docker.com/r/caronc/apprise), or use [Pushsafer](https://www.pushsafer.com/), [Pushover](https://www.pushover.net/), or [NTFY](https://ntfy.sh/). |
|
||||
|🧩 | Feed your data and device changes into [Home Assistant](https://github.com/jokob-sk/NetAlertX/blob/main/docs/HOME_ASSISTANT.md), read [API endpoints](https://github.com/jokob-sk/NetAlertX/blob/main/docs/API.md), or use [Webhooks](https://github.com/jokob-sk/NetAlertX/blob/main/docs/WEBHOOK_N8N.md) to setup custom automation flows. |
|
||||
|➕ | Build your own scanners with the [Plugin system](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins#readme) |
|
||||
### Scanners
|
||||
|
||||
The app scans your network for **New devices**, **New connections** (re-connections), **Disconnections**, **"Always Connected" devices down**, Devices **IP changes** and **Internet IP address changes**. Discovery & scan methods include: **arp-scan**, **Pi-hole - DB import**, **Pi-hole - DHCP leases import**, **Generic DHCP leases import**, **UNIFI controller import**, **SNMP-enabled router import**. Check the [Plugins](https://github.com/jokob-sk/NetAlertX/tree/main/docs/PLUGINS.md#readme) docs for a full lits of avaliable plugins.
|
||||
|
||||
### Notification gateways
|
||||
|
||||
Send notifications to more than 80+ services, including Telegram via [Apprise](https://hub.docker.com/r/caronc/apprise), or use native [Pushsafer](https://www.pushsafer.com/), [Pushover](https://www.pushover.net/), or [NTFY](https://ntfy.sh/) publishers.
|
||||
|
||||
### Integrations and Plugins
|
||||
|
||||
Feed your data and device changes into [Home Assistant](https://github.com/jokob-sk/NetAlertX/blob/main/docs/HOME_ASSISTANT.md), read [API endpoints](https://github.com/jokob-sk/NetAlertX/blob/main/docs/API.md), or use [Webhooks](https://github.com/jokob-sk/NetAlertX/blob/main/docs/WEBHOOK_N8N.md) to setup custom automation flows. You can also
|
||||
build your own scanners with the [Plugin system](https://github.com/jokob-sk/NetAlertX/tree/main/docs/PLUGINS.md#readme) in as little as [15 minutes](https://www.youtube.com/watch?v=cdbxlwiWhv8).
|
||||
|
||||
|
||||
## Installation & Documentation
|
||||
## 📚 Documentation
|
||||
<!--- --------------------------------------------------------------------- --->
|
||||
|
||||
Supported browsers: Chrome, Firefox
|
||||
|
||||
| Docs | Link |
|
||||
|-------------|-------------|
|
||||
| 📥🐳 | [Docker instructions](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md)
|
||||
| 📥🗄️ | [HW install (experimental 🧪)](https://github.com/jokob-sk/NetAlertX/blob/main/docs/HW_INSTALL.md) |
|
||||
| 📥🟧 | [Unraid App](https://unraid.net/community/apps) |
|
||||
| 📚 | [All Documentation](https://github.com/jokob-sk/NetAlertX/blob/main/docs/README.md) (App Usage and Configuration) |
|
||||
|
||||
> Other Alternatives
|
||||
>
|
||||
> - Check out [leiweibau's on HW installed fork](https://github.com/leiweibau/Pi.Alert/) (maintained)
|
||||
> - [WatchYourLAN](https://github.com/aceberg/WatchYourLAN) - Lightweight network IP scanner with web GUI (Open source)
|
||||
> - [Fing](https://www.fing.com/) - Network scanner app for your Internet security (Commercial, Phone App, Proprietary hardware)
|
||||
> - [NetBox](https://netboxlabs.com/) - Network management software (Commercial)
|
||||
- [[Installation] Docker](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md)
|
||||
- [[Installation] Home Assistant](https://github.com/alexbelgium/hassio-addons/tree/master/netalertx)
|
||||
- [[Installation] Bare metal](https://github.com/jokob-sk/NetAlertX/blob/main/docs/HW_INSTALL.md)
|
||||
- [[Installation] Unraid App](https://unraid.net/community/apps)
|
||||
- [[Setup] Usage and Configuration](https://github.com/jokob-sk/NetAlertX/blob/main/docs/README.md)
|
||||
- [[Development] API docs](https://github.com/jokob-sk/NetAlertX/blob/main/docs/API.md)
|
||||
- [[Development] Custom Plugins](https://github.com/jokob-sk/NetAlertX/blob/main/docs/PLUGINS_DEV.md)
|
||||
|
||||
## 🔔 Get notified what's new
|
||||
|
||||
## 📃 Everything else
|
||||
<!--- --------------------------------------------------------------------- --->
|
||||
|
||||
### 📧 Get notified what's new
|
||||
|
||||
Get notified about a new release, what new functionality you can use and about breaking changes.
|
||||
|
||||
![Follow and star][follow_star]
|
||||
|
||||
### ⭐ Sponsors
|
||||
### 🔀 Other Alternative Apps
|
||||
|
||||
[](https://github.com/sponsors/jokob-sk)
|
||||
|
||||
Thank you to all the wonderful people who are sponsoring this project.
|
||||
|
||||
> preventing my burnout😅 are:
|
||||
|
||||
<!-- SPONSORS-LIST DO NOT MODIFY BELOW -->
|
||||
| All Sponsors |
|
||||
|---|
|
||||
|
||||
<!-- SPONSORS-LIST DO NOT MODIFY ABOVE -->
|
||||
- [PiAlert by leiweibau](https://github.com/leiweibau/Pi.Alert/) (maintained, bare-metal install)
|
||||
- [WatchYourLAN](https://github.com/aceberg/WatchYourLAN) - Lightweight network IP scanner with web GUI (Open source)
|
||||
- [Fing](https://www.fing.com/) - Network scanner app for your Internet security (Commercial, Phone App, Proprietary hardware)
|
||||
- [NetBox](https://netboxlabs.com/) - Network management software (Commercial)
|
||||
|
||||
### 💙 Donations
|
||||
|
||||
Thank you to everyone who appreciates this tool and donates.
|
||||
|
||||
<details>
|
||||
<summary>Click for more ways to donate</summary>
|
||||
@@ -115,26 +91,20 @@ Thank you to all the wonderful people who are sponsoring this project.
|
||||
|
||||
</details>
|
||||
|
||||
### 🙏Contributors
|
||||
### 🏗 Contributors
|
||||
|
||||
This project would be nothing without the amazing work of the community, with special thanks to:
|
||||
|
||||
> [pucherot/Pi.Alert](https://github.com/pucherot/Pi.Alert) (the original creator of PiAlert), [leiweibau](https://github.com/leiweibau/Pi.Alert): Dark mode (and much more), [Macleykun](https://github.com/Macleykun) (Help with Dockerfile clean-up) [Final-Hawk](https://github.com/Final-Hawk) (Help with NTFY, styling and other fixes), [TeroRERO](https://github.com/terorero) (Spanish translations), [Data-Monkey](https://github.com/Data-Monkey), (Split-up of the python.py file and more), [cvc90](https://github.com/cvc90) (Spanish translation and various UI work) to name a few...
|
||||
|
||||
|
||||
## Everything else
|
||||
<!--- --------------------------------------------------------------------- --->
|
||||
> [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).
|
||||
|
||||
### 🌍 Translations
|
||||
|
||||
Proudly using [Weblate](https://hosted.weblate.org/projects/pialert/).
|
||||
Proudly using [Weblate](https://hosted.weblate.org/projects/pialert/). Help out and suggest languages in the [online portal of Weblate](https://hosted.weblate.org/projects/pialert/core/).
|
||||
|
||||
<a href="https://hosted.weblate.org/engage/pialert/">
|
||||
<img src="https://hosted.weblate.org/widget/pialert/core/multi-auto.svg" alt="Translation status" />
|
||||
</a>
|
||||
|
||||
Help out and suggest languages in the [online portal of Weblate](https://hosted.weblate.org/projects/pialert/core/).
|
||||
|
||||
### License
|
||||
> GPL 3.0 | [Read more here](LICENSE.txt) | Source of the [animated GIF (Loading Animation)](https://commons.wikimedia.org/wiki/File:Loading_Animation.gif) | Source of the [selfhosted Fonts](https://github.com/adobe-fonts/source-sans)
|
||||
|
||||
@@ -148,7 +118,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"
|
||||
|
||||
@@ -5,8 +5,9 @@ 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
|
||||
# Kill all python processes and restart the server
|
||||
pkill -f "python " && (python ${INSTALL_DIR}/server > /dev/null 2>&1 &) && echo 'done'
|
||||
# Restart python application using s6
|
||||
s6-svc -r /var/run/s6-rc/servicedirs/netalertx
|
||||
echo 'done'
|
||||
|
||||
# Remove all lines containing cron_restart_backend from the log file
|
||||
sed -i '/cron_restart_backend/d' "$LOG_FILE"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
version: "3"
|
||||
services:
|
||||
netalertx:
|
||||
privileged: true
|
||||
@@ -16,7 +15,7 @@ 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
|
||||
- ${DEV_LOCATION}/log:/app/log
|
||||
- ${APP_DATA_LOCATION}/netalertx/log:/app/log
|
||||
# (API: OPTION 1) use for performance
|
||||
- type: tmpfs
|
||||
target: /app/api
|
||||
@@ -29,6 +28,8 @@ services:
|
||||
- ${APP_DATA_LOCATION}/netalertx/dhcp_samples/pihole_dhcp_full.leases:/etc/pihole/dhcp.leases
|
||||
- ${APP_DATA_LOCATION}/netalertx/dhcp_samples/pihole_dhcp_2.leases:/etc/pihole/dhcp2.leases
|
||||
- ${APP_DATA_LOCATION}/pihole/etc-pihole/pihole-FTL.db:/etc/pihole/pihole-FTL.db
|
||||
- ${DEV_LOCATION}/mkdocs.yml:/app/mkdocs.yml
|
||||
- ${DEV_LOCATION}/docs:/app/docs
|
||||
- ${DEV_LOCATION}/server:/app/server
|
||||
- ${DEV_LOCATION}/test:/app/test
|
||||
- ${DEV_LOCATION}/dockerfiles:/app/dockerfiles
|
||||
@@ -39,12 +40,6 @@ services:
|
||||
- ${DEV_LOCATION}/back/update_vendors.sh:/app/back/update_vendors.sh
|
||||
- ${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/php:/app/front/php
|
||||
- ${DEV_LOCATION}/front/deviceDetails.php:/app/front/deviceDetails.php
|
||||
- ${DEV_LOCATION}/front/deviceDetailsEdit.php:/app/front/deviceDetailsEdit.php
|
||||
@@ -64,19 +59,19 @@ 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
|
||||
- ${DEV_LOCATION}/front/multiEditCore.php:/app/front/multiEditCore.php
|
||||
- ${DEV_LOCATION}/front/donations.php:/app/front/donations.php
|
||||
- ${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
|
||||
- ALWAYS_FRESH_INSTALL=${ALWAYS_FRESH_INSTALL}
|
||||
# - LOADED_PLUGINS=["DHCPLSS","PIHOLE","ASUSWRT","FREEBOX"]
|
||||
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
[](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)
|
||||
[](https://discord.gg/NczTUTWyRr)
|
||||
[](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Falexbelgium%2Fhassio-addons)
|
||||
|
||||
# NetAlertX - Network scanner & notification framework
|
||||
|
||||
# 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://jokob-sk.github.io/NetAlertX/) | [🔌 Plugins](https://github.com/jokob-sk/NetAlertX/blob/main/docs/PLUGINS.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,29 +21,35 @@ 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/docs/PLUGINS.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 PUID=200 -e PGID=300 \
|
||||
-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
|
||||
|
||||
| Variable | Description | Default |
|
||||
| :------------- |:-------------| -----:|
|
||||
| Variable | Description | Example Value |
|
||||
| :------------- |:------------------------| -----:|
|
||||
| `PORT` |Port of the web interface | `20211` |
|
||||
| `PUID` |Application User UID | `102` |
|
||||
| `PGID` |Application User GID | `82` |
|
||||
| `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` |
|
||||
|`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` |
|
||||
|`LOADED_PLUGINS` | Default [plugins](https://github.com/jokob-sk/NetAlertX/blob/main/docs/PLUGINS.md) to load. Plugins cannot be loaded with `APP_CONF_OVERRIDE`, you need to use this variable instead and then specify the plugins settings with `APP_CONF_OVERRIDE`. | `["PIHOLE","ASUSWRT"]` |
|
||||
|`APP_CONF_OVERRIDE` | JSON override for settings (except `LOADED_PLUGINS`). | `{"SCAN_SUBNETS":"['192.168.1.0/24 --interface=eth1']","GRAPHQL_PORT":"20212"}` |
|
||||
|`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. | `true` |
|
||||
|
||||
> You can override the default GraphQL port setting `GRAPHQL_PORT` (set to `20212`) by using the `APP_CONF_OVERRIDE` env variable.
|
||||
> You can override the default GraphQL port setting `GRAPHQL_PORT` (set to `20212`) by using the `APP_CONF_OVERRIDE` env variable. `LOADED_PLUGINS` and settings in `APP_CONF_OVERRIDE` can be specified via the UI as well.
|
||||
|
||||
### Docker paths
|
||||
|
||||
@@ -53,190 +58,41 @@ 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/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 |
|
||||
| | `:/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/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). |
|
||||
| | `:/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/docs/PLUGINS.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
|
||||
|
||||
#### 🧭 Community guides
|
||||
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.
|
||||
|
||||
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 🙏
|
||||
|
||||
- ▶ [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**
|
||||
#### Common issues
|
||||
|
||||
💡 Before creating a new issue, please check if a similar issue was [already resolved](https://github.com/jokob-sk/NetAlertX/issues?q=is%3Aissue+is%3Aclosed).
|
||||
- Before creating a new issue, please check if a similar issue was [already resolved](https://github.com/jokob-sk/NetAlertX/issues?q=is%3Aissue+is%3Aclosed).
|
||||
- Check also common issues and [debugging tips](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEBUG_TIPS.md).
|
||||
|
||||
⚠ 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/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
|
||||
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/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
|
||||
## 💙 Support me
|
||||
|
||||
| [](https://github.com/sponsors/jokob-sk) | [](https://www.buymeacoffee.com/jokobsk) | [](https://www.patreon.com/user?u=84385063) |
|
||||
| --- | --- | --- |
|
||||
@@ -244,4 +100,4 @@ Big thanks to <a href="https://github.com/Macleykun">@Macleykun</a> & for help a
|
||||
- Bitcoin: `1N8tupjeCK12qRVU2XrV17WvKK7LCawyZM`
|
||||
- Ethereum: `0x6e2749Cb42F4411bc98501406BdcD82244e3f9C7`
|
||||
|
||||
> 📧 Email me at [jokob@duck.com](mailto:jokob@duck.com?subject=NetAlertX) if you want to get in touch or if I should add other sponsorship platforms.
|
||||
> 📧 Email me at [netalertx@gmail.com](mailto:netalertx@gmail.com?subject=NetAlertX Donations) if you want to get in touch or if I should add other sponsorship platforms.
|
||||
|
||||
@@ -1,8 +1,36 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
|
||||
echo "---------------------------------------------------------"
|
||||
echo "[INSTALL] Run setup.sh"
|
||||
echo "---------------------------------------------------------"
|
||||
echo "---------------------------------------------------------
|
||||
[INSTALL] Run init.sh
|
||||
---------------------------------------------------------"
|
||||
|
||||
DEFAULT_PUID=102
|
||||
DEFAULT_GID=82
|
||||
|
||||
PUID=${PUID:-${DEFAULT_PUID}}
|
||||
PGID=${PGID:-${DEFAULT_GID}}
|
||||
|
||||
echo "[INSTALL] Setting up user UID and GID"
|
||||
|
||||
if ! groupmod -o -g "$PGID" www-data && [ "$PGID" != "$DEFAULT_GID" ] ; then
|
||||
echo "Failed to set user GID to ${PGID}, trying with default GID ${DEFAULT_GID}"
|
||||
groupmod -o -g "$DEFAULT_GID" www-data
|
||||
fi
|
||||
if ! usermod -o -u "$PUID" nginx && [ "$PUID" != "$DEFAULT_PUID" ] ; then
|
||||
echo "Failed to set user UID to ${PUID}, trying with default PUID ${DEFAULT_PUID}"
|
||||
usermod -o -u "$DEFAULT_PUID" nginx
|
||||
fi
|
||||
|
||||
echo "
|
||||
---------------------------------------------------------
|
||||
GID/UID
|
||||
---------------------------------------------------------
|
||||
User UID: $(id -u nginx)
|
||||
User GID: $(getent group www-data | cut -d: -f3)
|
||||
---------------------------------------------------------"
|
||||
|
||||
chown nginx:nginx /run/nginx/ /var/log/nginx/ /var/lib/nginx/ /var/lib/nginx/tmp/
|
||||
chgrp www-data /var/www/localhost/htdocs/
|
||||
|
||||
export INSTALL_DIR=/app # Specify the installation directory here
|
||||
|
||||
@@ -55,7 +83,7 @@ else
|
||||
echo "Config file saved to ${INSTALL_DIR}/config/app_conf_override.json"
|
||||
fi
|
||||
|
||||
# 🔻 FOR BACKWARD COMPATIBILITY - REMOVE AFTER 12/12/2024
|
||||
# 🔻 FOR BACKWARD COMPATIBILITY - REMOVE AFTER 12/12/2025
|
||||
|
||||
# Check if pialert.db exists, then create a symbolic link to app.db
|
||||
if [ -f "${INSTALL_DIR_OLD}/db/${OLD_APP_NAME}.db" ]; then
|
||||
@@ -66,7 +94,7 @@ fi
|
||||
if [ -f "${INSTALL_DIR_OLD}/config/${OLD_APP_NAME}.conf" ]; then
|
||||
ln -s "${INSTALL_DIR_OLD}/config/${OLD_APP_NAME}.conf" "${INSTALL_DIR}/config/${CONF_FILE}"
|
||||
fi
|
||||
# 🔺 FOR BACKWARD COMPATIBILITY - REMOVE AFTER 12/12/2024
|
||||
# 🔺 FOR BACKWARD COMPATIBILITY - REMOVE AFTER 12/12/2025
|
||||
|
||||
# Copy starter .db and .conf if they don't exist
|
||||
cp -na "${INSTALL_DIR}/back/${CONF_FILE}" "${INSTALL_DIR}/config/${CONF_FILE}"
|
||||
@@ -83,6 +111,13 @@ if [ -n "${TZ}" ]; then
|
||||
echo $TZ > /etc/timezone
|
||||
fi
|
||||
|
||||
# if custom variables not set we do not need to do anything
|
||||
if [ -n "${LOADED_PLUGINS}" ]; then
|
||||
FILECONF="${INSTALL_DIR}/config/${CONF_FILE}"
|
||||
echo "[INSTALL] Setup custom LOADED_PLUGINS variable"
|
||||
sed -i "\#^LOADED_PLUGINS=#c\LOADED_PLUGINS=${LOADED_PLUGINS}" "${FILECONF}"
|
||||
fi
|
||||
|
||||
echo "[INSTALL] Setup NGINX"
|
||||
echo "Setting webserver to address ($LISTEN_ADDR) and port ($PORT)"
|
||||
envsubst '$INSTALL_DIR $LISTEN_ADDR $PORT' < "${INSTALL_DIR}/install/netalertx.template.conf" > "${NGINX_CONFIG_FILE}"
|
||||
@@ -108,10 +143,11 @@ fi
|
||||
# Create the execution_queue.log and app_front.log files if they don't exist
|
||||
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,log,db,api}
|
||||
chown -R nginx:www-data "${INSTALL_DIR}"/api/user_notifications.json
|
||||
chown -R nginx:www-data "${INSTALL_DIR}"
|
||||
|
||||
chmod 750 "${INSTALL_DIR}"/{config,log,db}
|
||||
find "${INSTALL_DIR}"/{config,log,db} -type f -exec chmod 640 {} \;
|
||||
@@ -123,10 +159,6 @@ if [ ! -f "${INSTALL_DIR}/front/buildtimestamp.txt" ]; then
|
||||
chown nginx:www-data "${INSTALL_DIR}/front/buildtimestamp.txt"
|
||||
fi
|
||||
|
||||
# Start crond service in the background
|
||||
echo "[INSTALL] Starting crond service..."
|
||||
crond -f -d 8 > /dev/null 2>&1 &
|
||||
|
||||
echo -e "
|
||||
[ENV] PATH is ${PATH}
|
||||
[ENV] PORT is ${PORT}
|
||||
@@ -13,13 +13,21 @@ sed -i "/^user/c\user = nginx" /etc/php83/php-fpm.d/www.conf
|
||||
sed -i "/^group/c\group = www-data" /etc/php83/php-fpm.d/www.conf
|
||||
|
||||
# s6 overlay setup
|
||||
mkdir -p /etc/s6-overlay/s6-rc.d/{SetupOneshot,php-fpm/dependencies.d,nginx/dependencies.d}
|
||||
mkdir -p /etc/s6-overlay/s6-rc.d/{SetupOneshot,php-fpm/dependencies.d,nginx/dependencies.d,$APP_NAME/dependencies.d}
|
||||
mkdir -p /etc/s6-overlay/s6-rc.d/{SetupOneshot,crond/dependencies.d,php-fpm/dependencies.d,nginx/dependencies.d,$APP_NAME/dependencies.d}
|
||||
echo "oneshot" > /etc/s6-overlay/s6-rc.d/SetupOneshot/type
|
||||
echo "longrun" > /etc/s6-overlay/s6-rc.d/crond/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
|
||||
|
||||
if { echo
|
||||
"
|
||||
[INSTALL] Starting crond service...
|
||||
|
||||
" }' > /etc/s6-overlay/s6-rc.d/crond/run
|
||||
echo -e "/usr/sbin/crond -f" >> /etc/s6-overlay/s6-rc.d/crond/run
|
||||
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
|
||||
@@ -33,11 +41,9 @@ echo -e '#!/bin/execlineb -P
|
||||
|
||||
" }' > /etc/s6-overlay/s6-rc.d/$APP_NAME/run
|
||||
echo -e "python ${INSTALL_DIR}/server" >> /etc/s6-overlay/s6-rc.d/$APP_NAME/run
|
||||
touch /etc/s6-overlay/s6-rc.d/user/contents.d/{SetupOneshot,php-fpm,nginx} /etc/s6-overlay/s6-rc.d/{php-fpm,nginx}/dependencies.d/SetupOneshot
|
||||
touch /etc/s6-overlay/s6-rc.d/user/contents.d/{SetupOneshot,php-fpm,nginx,$APP_NAME} /etc/s6-overlay/s6-rc.d/{php-fpm,nginx,$APP_NAME}/dependencies.d/SetupOneshot
|
||||
touch /etc/s6-overlay/s6-rc.d/user/contents.d/{SetupOneshot,crond,php-fpm,nginx,$APP_NAME} /etc/s6-overlay/s6-rc.d/{crond,php-fpm,nginx,$APP_NAME}/dependencies.d/SetupOneshot
|
||||
touch /etc/s6-overlay/s6-rc.d/nginx/dependencies.d/php-fpm
|
||||
touch /etc/s6-overlay/s6-rc.d/$APP_NAME/dependencies.d/nginx
|
||||
|
||||
|
||||
|
||||
# this removes the current file
|
||||
rm -f $0
|
||||
178
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
|
||||
|
||||
This API endpoint retrieves 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/api/` folder. You can acces sthem via the `/php/server/query_json.php?file=user_notifications.json` endpoint.
|
||||
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
|
||||
|
||||
@@ -17,19 +144,16 @@ You can access the following files:
|
||||
|
||||
| File name | Description |
|
||||
|----------------------|----------------------|
|
||||
| `notification_text.txt` | The plain text version of the last notification. |
|
||||
| `notification_text.html` | The full HTML of the last email notification. |
|
||||
| `notification_json_final.json` | The json version of the last notification (e.g. used for webhooks - [sample JSON](https://github.com/jokob-sk/NetAlertX/blob/main/front/report_templates/webhook_json_sample.json)). |
|
||||
| `table_devices.json` | The current (at the time of the last update as mentioned above on this page) state of all of the available Devices detected by the app. |
|
||||
| `table_devices.json` | All of the available Devices detected by the app. |
|
||||
| `table_plugins_events.json` | The list of the unprocessed (pending) notification events (plugins_events DB table). |
|
||||
| `table_plugins_history.json` | The list of notification events history. |
|
||||
| `table_plugins_objects.json` | The content of the plugins_objects table. Find more info on the [Plugin system here](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins)|
|
||||
| `table_plugins_objects.json` | The content of the plugins_objects table. Find more info on the [Plugin system here](https://github.com/jokob-sk/NetAlertX/tree/main/docs/PLUGINS.md)|
|
||||
| `language_strings.json` | The content of the language_strings table, which in turn is loaded from the plugins `config.json` definitions. |
|
||||
| `table_custom_endpoint.json` | A custom endpoint generated by the SQL query specified by the `API_CUSTOM_SQL` setting. |
|
||||
| `table_settings.json` | The content of the settings table. |
|
||||
| `app_state.json` | Contains the current application state. |
|
||||
|
||||
Current/latest state of the aforementioned files depends on your settings.
|
||||
|
||||
### JSON Data format
|
||||
|
||||
@@ -95,3 +219,41 @@ Example JSON of the `table_devices.json` endpoint with two Devices (database row
|
||||
|
||||
```
|
||||
|
||||
## API Endpoint: /log files
|
||||
|
||||
This API endpoint retrieves 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 temporary 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,6 +1,8 @@
|
||||
(DRAFT) Authelia support
|
||||
|
||||
## Authelia support
|
||||
|
||||
> [!WARNING]
|
||||
>
|
||||
> This is community contributed content and work in progress. Contributions are welcome.
|
||||
|
||||
```yaml
|
||||
theme: dark
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -17,36 +18,39 @@ To decide on a backup strategy, check where the data is stored:
|
||||
|
||||
### Core Configuration
|
||||
|
||||
The core application configuration is in the `app.conf` file (See [Settings System](https://github.com/jokob-sk/NetAlertX/blob/main/docs/SETTINGS_SYSTEM.md) for details), such as:
|
||||
The core application configuration is in the `app.conf` file (See [Settings System](./SETTINGS_SYSTEM.md) for details), such as:
|
||||
|
||||
- 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
|
||||
|
||||
Historical data is stored in the `app.db` database (See [Database overview](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DATABASE.md) for details). This data includes:
|
||||
Historical data is stored in the `app.db` database (See [Database overview](./DATABASE.md) for details). This data includes:
|
||||
|
||||
- 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. You can also download `app.conf` and `devices.csv` file in the Maintenance section:
|
||||
|
||||

|
||||
|
||||
### Scenario 1: Full backup
|
||||
|
||||
@@ -54,8 +58,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 +72,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
|
||||
- Restore the `devices.csv` backup via the [Maintenance section](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICES_BULK_EDITING.md)
|
||||
- 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](./DEVICES_BULK_EDITING.md)
|
||||
|
||||
|
||||
|
||||
48
docs/COMMON_ISSUES.md
Executable file
@@ -0,0 +1,48 @@
|
||||
### Loading...
|
||||
|
||||
Often if the application is misconfigured the `Loading...` dialog is continuously displayed. This is most likely caused by the backed failing to start. The **Maintenance -> Logs** section should give you more details on what's happenning. If there is no exception, check the Portainer log, or start the container in the foreground (without the `-d` parameter) to observe any exceptions. It's advisable to enable `trace` or `debug`. Check the [Debug tips](./DEBUG_TIPS.md) on detailed instructions.
|
||||
|
||||
### Incorrect SCAN_SUBNETS
|
||||
|
||||
One of the most common issues is not configuring `SCAN_SUBNETS` correctly. If this setting is misconfigured you will only see one or two devices in your devices list after a scan. Please read the [subnets docs](./SUBNETS.md) carefully to resolve this.
|
||||
|
||||
### Duplicate devices and notifications
|
||||
|
||||
The app uses the MAC address as an unique identifier for devices. If a new MAC is detected a new device is added to the application and corresponding notifications are triggered. This means that if the MAC of an existing device changes, the device will be logged as a new device. You can usually prevent this from happenning by changing the device configuration (in Android, iOS, or Windows) for your network. See the [Random Macs](./RANDOM_MAC.md) guide for details.
|
||||
|
||||
### Permissions
|
||||
|
||||
Make sure you [File permissions](./FILE_PERMISSIONS.md) are set correctly.
|
||||
|
||||
* 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)
|
||||
|
||||
### Container restarts / crashes
|
||||
|
||||
* Check the logs for details. Often a required setting for a notification method is missing.
|
||||
|
||||
### unable to resolve host
|
||||
|
||||
* Check that your `SCAN_SUBNETS` variable is using the correct mask and `--interface`. See the [subnets docs for details](./SUBNETS.md).
|
||||
|
||||
### Invalid JSON
|
||||
|
||||
Check the [Invalid JSON errors debug help](./DEBUG_INVALID_JSON.md) docs on how to proceed.
|
||||
|
||||
### sudo execution failing (e.g.: on arpscan) on a Raspberry Pi 4
|
||||
|
||||
> sudo: unexpected child termination condition: 0
|
||||
|
||||
Resolution based on [this issue](https://github.com/linuxserver/docker-papermerge/issues/4#issuecomment-1003657581)
|
||||
|
||||
```
|
||||
wget ftp.us.debian.org/debian/pool/main/libs/libseccomp/libseccomp2_2.5.3-2_armhf.deb
|
||||
sudo dpkg -i libseccomp2_2.5.3-2_armhf.deb
|
||||
```
|
||||
|
||||
The link above will probably break in time too. Go to https://packages.debian.org/sid/armhf/libseccomp2/download to find the new version number and put that in the url.
|
||||
|
||||
### Only Router and own device show up
|
||||
|
||||
Make sure that the subnet and interface in `SCAN_SUBNETS` are correct. If your device/NAS has multiple ethernet ports, you probably need to change `eth0` to something else.
|
||||
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.
|
||||
@@ -1,11 +1,11 @@
|
||||
|
||||
# A high-level description of the database structure
|
||||
# A high-level description of the database structure
|
||||
|
||||
⚠ Disclaimer: As I'm not the original author, some of the information might be inaccurate. Feel free to submit a PR to correct anything within this page or documentation in general.
|
||||
|
||||
The MAC address is used as a foreign key in most cases.
|
||||
|
||||
## 🔍Tables overview
|
||||
## 🔍Tables overview
|
||||
|
||||
| Table name | Description | Sample data |
|
||||
|----------------------|----------------------| ----------------------|
|
||||
@@ -23,15 +23,15 @@
|
||||
|
||||
|
||||
|
||||
[screen1]: /docs/img/DATABASE/CurrentScan.png
|
||||
[screen2]: /docs/img/DATABASE/Devices.png
|
||||
[screen4]: /docs/img/DATABASE/Events.png
|
||||
[screen6]: /docs/img/DATABASE/Online_History.png
|
||||
[screen7]: /docs/img/DATABASE/Parameters.png
|
||||
[screen10]: /docs/img/DATABASE/Plugins_Events.png
|
||||
[screen11]: /docs/img/DATABASE/Plugins_History.png
|
||||
[screen12]: /docs/img/DATABASE/Plugins_Language_Strings.png
|
||||
[screen13]: /docs/img/DATABASE/Plugins_Objects.png
|
||||
[screen15]: /docs/img/DATABASE/Sessions.png
|
||||
[screen16]: /docs/img/DATABASE/Settings.png
|
||||
[screen1]: ./img/DATABASE/CurrentScan.png
|
||||
[screen2]: ./img/DATABASE/Devices.png
|
||||
[screen4]: ./img/DATABASE/Events.png
|
||||
[screen6]: ./img/DATABASE/Online_History.png
|
||||
[screen7]: ./img/DATABASE/Parameters.png
|
||||
[screen10]: ./img/DATABASE/Plugins_Events.png
|
||||
[screen11]: ./img/DATABASE/Plugins_History.png
|
||||
[screen12]: ./img/DATABASE/Plugins_Language_Strings.png
|
||||
[screen13]: ./img/DATABASE/Plugins_Objects.png
|
||||
[screen15]: ./img/DATABASE/Sessions.png
|
||||
[screen16]: ./img/DATABASE/Settings.png
|
||||
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
|
||||
If a Plugin supplies data to the main app it's doine either vie a SQL query or via a script that updates the `last_result.log` file in the plugin folder (`front/plugins/<plugin>`).
|
||||
|
||||
For a more in-depth overview on how plugins work check the [Plugins development docs](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/README.md).
|
||||
For a more in-depth overview on how plugins work check the [Plugins development docs](https://github.com/jokob-sk/NetAlertX/blob/main/docs/PLUGINS.md).
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Make sure you read and followed the specific plugin setup instructions.
|
||||
- Ensure you have [debug enabled (see More Logging)](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEBUG_TIPS.md#1-more-logging-)
|
||||
- Ensure you have [debug enabled (see More Logging)](./DEBUG_TIPS.md)
|
||||
|
||||
### Potential issues
|
||||
|
||||
@@ -75,7 +75,7 @@ In the above output notice the section logging how many events are produced by t
|
||||
|
||||
These values, if formatted correctly, will also show up in the UI:
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
### Sharing application state
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
|
||||
Please follow tips 1 - 4 to get a more detailed error.
|
||||
|
||||
## 1. More Logging 📃
|
||||
## 1. More Logging
|
||||
|
||||
When debugging an issue always set the highest log level:
|
||||
|
||||
`LOG_LEVEL='trace'`
|
||||
|
||||
|
||||
## 2. Surfacing errors when container restarts 🔁
|
||||
## 2. Surfacing errors when container restarts
|
||||
|
||||
Start the container via the **terminal** with a command similar to this one:
|
||||
|
||||
@@ -25,7 +24,7 @@ docker run --rm --network=host \
|
||||
|
||||
> ⚠ Please note, don't use the `-d` parameter so you see the error when the container crashes. Use this error in your issue description.
|
||||
|
||||
## 3. Check the _dev image and open issues ❓
|
||||
## 3. Check the _dev image and open issues
|
||||
|
||||
If possible, check if your issue got fixed in the `_dev` image before opening a new issue. The container is:
|
||||
|
||||
@@ -35,7 +34,7 @@ If possible, check if your issue got fixed in the `_dev` image before opening a
|
||||
|
||||
Please also search [open issues](https://github.com/jokob-sk/NetAlertX/issues).
|
||||
|
||||
## 4. Disable restart behavior 🛑
|
||||
## 4. Disable restart behavior
|
||||
|
||||
To prevent a Docker container from automatically restarting in a Docker Compose file, specify the restart policy as `no`:
|
||||
|
||||
@@ -60,39 +59,6 @@ Sometimes specific log sections are needed to debug issues. The Devices and Curr
|
||||
5. Open a new issue and post (redacted) output into the issue description (or send to the netalertx@gmail.com email if sensitive data present).
|
||||
6. Please set `LOG_LEVEL` to `debug` or lower.
|
||||
|
||||
## 📃Common issues
|
||||
## Common issues
|
||||
|
||||
### Permissions
|
||||
|
||||
* If facing issues (AJAX errors, can't write to DB, empty screen, etc,) make sure permissions are set correctly, and check the logs under `/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)
|
||||
|
||||
### Container restarts / crashes
|
||||
|
||||
* Check the logs for details. Often a required setting for a notification method is missing.
|
||||
|
||||
### unable to resolve host
|
||||
|
||||
* Check that your `SCAN_SUBNETS` variable is using the correct mask and `--interface` as outlined in the instructions above.
|
||||
|
||||
### Invalid JSON
|
||||
|
||||
Check the [Invalid JSON errors debug help](/docs/DEBUG_INVALID_JSON.md) docs on how to proceed.
|
||||
|
||||
### sudo execution failing (e.g.: on arpscan) on a Raspberry Pi 4
|
||||
|
||||
> sudo: unexpected child termination condition: 0
|
||||
|
||||
Resolution based on [this issue](https://github.com/linuxserver/docker-papermerge/issues/4#issuecomment-1003657581)
|
||||
|
||||
```
|
||||
wget ftp.us.debian.org/debian/pool/main/libs/libseccomp/libseccomp2_2.5.3-2_armhf.deb
|
||||
sudo dpkg -i libseccomp2_2.5.3-2_armhf.deb
|
||||
```
|
||||
|
||||
The link above will probably break in time too. Go to https://packages.debian.org/sid/armhf/libseccomp2/download to find the new version number and put that in the url.
|
||||
|
||||
### Only Router and own device show up
|
||||
|
||||
Make sure that the subnet and interface in SCAN_SUBNETS are the correct ones. If your device/NAS has multiple ethernet ports, you probably need to change eth0 to something else!
|
||||
See [Common issues](./COMMON_ISSUES.md) for details.
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
# 🖊 Multi-editing via the UI
|
||||
# Editing multiple devices at once
|
||||
|
||||
NetAlertX allows you to mass-edit devices via a CSV export and import feature, or directly in the UI.
|
||||
|
||||
## UI multi edit
|
||||
|
||||
> [!NOTE]
|
||||
> Make sure you have your backups saved and restorable before doing any mass edits. Check [Backup strategies](/docs/BACKUPS.md).
|
||||
> Make sure you have your backups saved and restorable before doing any mass edits. Check [Backup strategies](./BACKUPS.md).
|
||||
|
||||
You can select devices in the _Devices_ view by selecting devices to edit and then clicking the _Multi-edit_ button or via the _Maintenance_ > _Multi-Edit_ section.
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
# 📝Bulk-edit devices via CSV Export/Import
|
||||
## CSV bulk edit
|
||||
|
||||
> [!NOTE]
|
||||
> As always, backup everything, just in case.
|
||||
@@ -17,17 +21,19 @@ You can select devices in the _Devices_ view by selecting devices to edit and th
|
||||
2. A `devices.csv` is generated in the `/config` folder
|
||||
3. Edit the `devices.csv` file however you like.
|
||||
|
||||

|
||||

|
||||
|
||||
> [!NOTE]
|
||||
> The file containing a list of Devices including the Network relationships between Network Nodes and connected devices. You can also trigger this by acessing this URL: `<your netalertx url>/php/server/devices.php?action=ExportCSV` or via the `CSV Backup` plugin. (💡 You can schedule this)
|
||||
|
||||

|
||||

|
||||
|
||||
### File encoding format
|
||||
|
||||
> [!NOTE]
|
||||
> Keep Linux line endings (suggested editors: Nano, Notepad++)
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
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.
|
||||
|
||||
|
||||

|
||||

|
||||
@@ -2,24 +2,22 @@
|
||||
|
||||
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]
|
||||
>
|
||||
> 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).
|
||||
> 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](./DEVICES_BULK_EDITING.md).
|
||||
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Main Info
|
||||
|
||||
- **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.
|
||||
- **Name**: Friendly device name. Autodetected via various 🆎 Name discovery [plugins](https://github.com/jokob-sk/NetAlertX/blob/main/docs/PLUGINS.md). The app attaches `(IP match)` if the name is discovered via an IP match and not MAC match which could mean the name could be incorrect as IPs might change.
|
||||
- **Icon**: Partially autodetected. Select an existing or [add a custom icon](./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).
|
||||
`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](./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.
|
||||
@@ -34,11 +32,11 @@ The Main Info section is where most of the device identifiable information is st
|
||||
|
||||
You can create dummy devices from the Devices listing screen.
|
||||
|
||||

|
||||

|
||||
|
||||
The **MAC** field and the **Last IP** field will then become editable.
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
> [!NOTE]
|
||||
|
||||
@@ -3,6 +3,27 @@
|
||||
>[!NOTE]
|
||||
> Replace `/development` with the path where your code files will be stored. The default container name is `netalertx` so there might be a conflict with your running containers.
|
||||
|
||||
### Development Guidelines
|
||||
|
||||
**Priority Order (Highest to Lowest):**
|
||||
|
||||
1. 🔼 Fixing core bugs that lack workarounds.
|
||||
2. 🔵 Adding core functionality that unlocks other features (e.g., plugins).
|
||||
3. 🔵 Refactoring to enable faster development.
|
||||
4. 🔽 UI improvements (PRs welcome).
|
||||
|
||||
💡 **Design Philosophy:**
|
||||
Focus on core functionality and integrate with existing tools rather than reinventing the wheel.
|
||||
Examples:
|
||||
|
||||
- Using **Apprise** for notifications instead of implementing multiple separate gateways.
|
||||
- Implementing **regex-based validation** instead of one-off validation for each setting.
|
||||
|
||||
📌 **Note on UI requests:**
|
||||
|
||||
- UI changes have lower priority due to framework limitations and mobile support constraints.
|
||||
- PRs are welcome, but **keep them small & focused**.
|
||||
|
||||
## 1. Download the code:
|
||||
|
||||
- `mkdir /development`
|
||||
@@ -58,7 +79,7 @@ Most code changes can be tetsed without rebuilding the container. When working o
|
||||
|
||||
1. You can usually restart the backend via Maintenance > Logs > Restart server
|
||||
|
||||

|
||||

|
||||
|
||||
2. If above doesn't work, SSH into the container and kill & restart the main script loop
|
||||
|
||||
@@ -67,4 +88,27 @@ Most code changes can be tetsed without rebuilding the container. When working o
|
||||
|
||||
3. If none of the above work, restart the docker image. This is usually the last resort as sometimes the Docker engine becomes unresponsive and the whole engine needs to be restarted.
|
||||
|
||||
## ➕ Contributing & Pull Requests
|
||||
|
||||
**Before submitting a PR, please ensure:**
|
||||
|
||||
✔ Changes are **backward-compatible** with existing installs.
|
||||
✔ No unnecessary changes are made.
|
||||
✔ New features are **reusable**, not narrowly scoped.
|
||||
✔ Features are implemented via **plugins** if possible.
|
||||
|
||||
### Mandatory Test Cases
|
||||
|
||||
- Fresh install (no DB/config).
|
||||
- Existing DB/config compatibility.
|
||||
- Notification testing:
|
||||
|
||||
- Email
|
||||
- Apprise (e.g., Telegram)
|
||||
- Webhook (e.g., Discord)
|
||||
- MQTT (e.g., Home Assistant)
|
||||
|
||||
- Updating Settings and their persistence.
|
||||
- Updating a Device
|
||||
- Plugin functionality.
|
||||
- Error log inspection.
|
||||
|
||||
101
docs/DOCKER_COMPOSE.md
Executable file
@@ -0,0 +1,101 @@
|
||||
# `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`
|
||||
@@ -5,37 +5,19 @@
|
||||
> ```
|
||||
> docker run -d --rm --network=host \
|
||||
> -e TZ=Europe/Berlin \
|
||||
> -e PUID=200 -e PGID=200 \
|
||||
> -e PORT=20211 \
|
||||
> jokobsk/netalertx:latest
|
||||
> ```
|
||||
NetAlertX runs on an Nginx web server. On Alpine Linux, Nginx operates as the `nginx` user (user ID 101, group ID 82 - `www-data`). Consequently, files accessed or written by the NetAlertX application are owned by `nginx:www-data`.
|
||||
NetAlertX runs on an Nginx web server. On Alpine Linux, Nginx operates as the `nginx` user (if PUID and GID environment variables are not specified, nginx user UID will be set to 102, and its supplementary group `www-data` ID to 82). Consequently, files accessed or written by the NetAlertX application are owned by `nginx:www-data`.
|
||||
|
||||
Upon starting, NetAlertX changes the ownership of files on the host system mapped to `/app/config` and `/app/db` in the container to `nginx:www-data`. This ensures that Nginx can access and write to these files. Since the user in the Docker container is mapped to a user on the host system by ID:GID, the files in `/app/config` and `/app/db` on the host system are owned by a user with the same ID and GID (ID 101 and GID 82). On different systems, this ID:GID may belong to different users (on Debian, the user with ID 82 is `uuidd`), or there may not be a user with ID 82 at all.
|
||||
Upon starting, NetAlertX changes nginx user UID and www-data GID to specified values (or defaults), and the ownership of files on the host system mapped to `/app/config` and `/app/db` in the container to `nginx:www-data`. This ensures that Nginx can access and write to these files. Since the user in the Docker container is mapped to a user on the host system by ID:GID, the files in `/app/config` and `/app/db` on the host system are owned by a user with the same ID and GID (defaults are ID 102 and GID 82). On different systems, this ID:GID may belong to different users, or there may not be a group with ID 82 at all.
|
||||
|
||||
While this generally isn't problematic, it can cause issues for host system users needing to access these files (e.g., backup scripts). If users other than root need access to these files, it is recommended to add those users to the group with GID 82. If that group doesn't exist, it should be created.
|
||||
Option to set specific user UID and GID can be useful for host system users needing to access these files (e.g., backup scripts).
|
||||
|
||||
### Permissions Table for Individual Folders
|
||||
|
||||
| Folder | User | User ID | Group | Group ID | Permissions | Notes |
|
||||
|----------------|--------|---------|-----------|----------|-------------|---------------------------------------------------------------------|
|
||||
| `/app/config` | nginx | 101 | www-data | 82 | rwxr-xr-x | Ensure `nginx` can read/write; other users can read if in `www-data` |
|
||||
| `/app/db` | nginx | 101 | www-data | 82 | rwxr-xr-x | Same as above |
|
||||
|
||||
### Steps to Add Users to Group
|
||||
|
||||
1. **Check if group exists:**
|
||||
```sh
|
||||
getent group www-data
|
||||
```
|
||||
|
||||
2. **Create group if it does not exist:**
|
||||
```sh
|
||||
sudo groupadd -g 82 www-data
|
||||
```
|
||||
|
||||
3. **Add user to group:**
|
||||
```sh
|
||||
sudo usermod -aG www-data <username>
|
||||
```
|
||||
|
||||
Replace `<username>` with the actual username that requires access.
|
||||
| `/app/config` | nginx | PUID (default 102) | www-data | PGID (default 82) | rwxr-xr-x | Ensure `nginx` can read/write; other users can read if in `www-data` |
|
||||
| `/app/db` | nginx | PUID (default 102) | www-data | PGID (default 82) | rwxr-xr-x | Same as above |
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 🖼 Frontend development
|
||||
# Frontend development
|
||||
|
||||
This page contains tips for frontend development when extending NetAlertX. Guiding principles are:
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
NetAlertX comes with MQTT support, allowing you to show all detected devices as devices in Home Assistant. It also supplies a collection of stats, such as number of online devices.
|
||||
|
||||
> [!TIP]
|
||||
> You can install NetAlertX also as a Home Assistant addon [](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Falexbelgium%2Fhassio-addons) via the [alexbelgium/hassio-addons](https://github.com/alexbelgium/hassio-addons/). This is only possible if you run a supervised instance of Home Assistant. If not, you can still run NetAlertX in a separate Docker container and follow this guide to configure MQTT.
|
||||
|
||||
## ⚠ Note
|
||||
|
||||
- Please note that discovery takes about ~10s per device.
|
||||
@@ -18,15 +21,17 @@ NetAlertX comes with MQTT support, allowing you to show all detected devices as
|
||||
2. Configure a user name and password on your broker.
|
||||
|
||||
3. Note down the following details that you will need to configure NetAlertX:
|
||||
- MQTT host url (usually your Home Assistant IP)
|
||||
- MQTT broker port
|
||||
- User
|
||||
- Password
|
||||
|
||||
- MQTT host url (usually your Home Assistant IP)
|
||||
- MQTT broker port
|
||||
- User
|
||||
- Password
|
||||
|
||||
4. Open the _NetAlertX_ > _Settings_ > _MQTT_ settings group
|
||||
- Enable MQTT
|
||||
- Fill in the details from above
|
||||
- Fill in remaining settings as per description
|
||||
|
||||
- Enable MQTT
|
||||
- Fill in the details from above
|
||||
- Fill in remaining settings as per description
|
||||
|
||||
![Configuration Example][configuration]
|
||||
|
||||
@@ -37,9 +42,9 @@ NetAlertX comes with MQTT support, allowing you to show all detected devices as
|
||||
| ![Screen 3][list] | ![Screen 4][overview] |
|
||||
|
||||
|
||||
[configuration]: /docs/img/HOME_ASISSTANT/HomeAssistant-Configuration.png "configuration"
|
||||
[sensors]: /docs/img/HOME_ASISSTANT/HomeAssistant-Device-as-Sensors.png "sensors"
|
||||
[history]: /docs/img/HOME_ASISSTANT/HomeAssistant-Device-Presence-History.png "history"
|
||||
[list]: /docs/img/HOME_ASISSTANT/HomeAssistant-Devices-List.png "list"
|
||||
[overview]: /docs/img/HOME_ASISSTANT/HomeAssistant-Overview-Card.png "overview"
|
||||
[configuration]: ./img/HOME_ASISSTANT/HomeAssistant-Configuration.png "configuration"
|
||||
[sensors]: ./img/HOME_ASISSTANT/HomeAssistant-Device-as-Sensors.png "sensors"
|
||||
[history]: ./img/HOME_ASISSTANT/HomeAssistant-Device-Presence-History.png "history"
|
||||
[list]: ./img/HOME_ASISSTANT/HomeAssistant-Devices-List.png "list"
|
||||
[overview]: ./img/HOME_ASISSTANT/HomeAssistant-Overview-Card.png "overview"
|
||||
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
## Icons overview
|
||||
|
||||
Icons are used to visually distinguish devices in the app in most of the device listing tables and the [network tree](/docs/NETWORK_TREE.md).
|
||||
Icons are used to visually distinguish devices in the app in most of the device listing tables and the [network tree](./NETWORK_TREE.md).
|
||||
|
||||

|
||||

|
||||
|
||||
### Icons Support
|
||||
|
||||
@@ -19,26 +19,26 @@ You can assign icons individually on each device in the Details tab.
|
||||
|
||||
Copying the SVG (for example from [iconify.design](https://icon-sets.iconify.design/)):
|
||||
|
||||

|
||||

|
||||
|
||||
Copying the HTML code from [Font Awesome](https://fontawesome.com/search?o=r&m=free).
|
||||
|
||||

|
||||

|
||||
|
||||
2. Navigate to the device you want to use the icon on and click the "+" icon:
|
||||
|
||||

|
||||

|
||||
|
||||
3. Paste in the copied HTML or SVG code and click "OK":
|
||||
|
||||

|
||||

|
||||
|
||||
6. "Save" the device
|
||||
|
||||
> [!NOTE]
|
||||
> If you want to mass-apply an icon to all devices of the same device type (Field: Type), you can click the mass-copy button (next to the "+" button). A confirmation prompt is displayed. If you proceed, icons of all devices set to the same device type as the current device, will be overwritten with the current device's icon.
|
||||
|
||||
- The blue dropdown contains all icons already used in the app for device icons. You need to navigate away or refresh the page once you add a new icon.
|
||||
- The dropdown contains all icons already used in the app for device icons. You might need to navigate away or refresh the page once you add a new icon.
|
||||
|
||||
## 🌟 Pro Font Awesome icons
|
||||
|
||||
|
||||
42
docs/INITIAL_SETUP.md
Executable file
@@ -0,0 +1,42 @@
|
||||
# ⚙ Initial Setup
|
||||
|
||||
## 📁 Configuration Files
|
||||
|
||||
- On first run, the app generates a default `app.conf` and `app.db` if unavailable.
|
||||
- Preferred method: Use the **Settings UI**.
|
||||
- If the UI is inaccessible, manually edit [`app.conf`](https://github.com/jokob-sk/NetAlertX/tree/main/back) in `/app/config/`.
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ Setting Up Scanners
|
||||
|
||||
- Define networks to scan by entering accessible subnets.
|
||||
- Default plugin: **ARPSCAN** → Requires at least one valid subnet + interface in `SCAN_SUBNETS`.
|
||||
- 📖 [Subnet & VLAN setup guide](./SUBNETS.md) (for troubleshooting and advanced scenarios).
|
||||
|
||||
### 🔄 PiHole Sync
|
||||
- If using **PiHole**, devices can be synced automatically.
|
||||
- 📖 [PiHole configuration guide](./PIHOLE_GUIDE.md).
|
||||
|
||||
### 📦 Bulk Import
|
||||
> [!NOTE]
|
||||
> You can bulk-import devices via the [CSV import method](./DEVICES_BULK_EDITING.md).
|
||||
|
||||
---
|
||||
|
||||
## 🌍 Community Guides
|
||||
|
||||
- Various community-written configuration guides in **Chinese, Korean, German, French**.
|
||||
- 📖 [Community Guides](./COMMUNITY_GUIDES.md)
|
||||
|
||||
> ⚠️ **Note:** These guides may be outdated. Always refer to the official documentation first.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Common Issues
|
||||
|
||||
Before creating a new issue:
|
||||
|
||||
- Check if a similar issue was [already resolved](https://github.com/jokob-sk/NetAlertX/issues?q=is%3Aissue+is%3Aclosed).
|
||||
- Review [common debugging tips](./DEBUG_TIPS.md).
|
||||
- Check [Common Issues](./COMMON_ISSUES.md)
|
||||
25
docs/INSTALLATION.md
Executable file
@@ -0,0 +1,25 @@
|
||||
# Installation
|
||||
|
||||
## Installation options
|
||||
|
||||
NetAlertX can be installed several ways. The best supported option is Docker, followed by a supervised the Home Assistant instance, as an Unraid app and lastly on bare metal.
|
||||
|
||||
- [[Installation] Docker (recommended)](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md)
|
||||
- [[Installation] Home Assistant](https://github.com/alexbelgium/hassio-addons/tree/master/netalertx)
|
||||
- [[Installation] Unraid App](https://unraid.net/community/apps)
|
||||
- [[Installation] Bare metal (experimental - looking for maintainers)](https://github.com/jokob-sk/NetAlertX/blob/main/docs/HW_INSTALL.md)
|
||||
|
||||
|
||||
## Help
|
||||
|
||||
If facing issues, please spend a few minutes seraching.
|
||||
|
||||
- Check [common issues](./COMMON_ISSUES.md)
|
||||
- Have a look at [Community guides](./COMMUNITY_GUIDES.md)
|
||||
- [Search closed or open issues or discussions](https://github.com/jokob-sk/NetAlertX/issues?q=is%3Aissue)
|
||||
- Check [Discord](https://discord.gg/NczTUTWyRr)
|
||||
|
||||
> [!NOTE]
|
||||
> If you can't find a solution anywhere, ask in Discord if you think it's a quick question, otherwise open a new [issue](https://github.com/jokob-sk/NetAlertX/issues/new?template=setup-help.yml). Please fill in as much as possible to speed up the help process.
|
||||
>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
## STEPS:
|
||||
|
||||
> [!TIP]
|
||||
> In short: The application will auto-migrate the database, config, and all device information. A ticker message on top will be displayed until you update your docker mount points. It's always good to have a [backup strategy](https://github.com/jokob-sk/NetAlertX/blob/main/docs/BACKUPS.md) in place.
|
||||
> In short: The application will auto-migrate the database, config, and all device information. A ticker message on top will be displayed until you update your docker mount points. It's always good to have a [backup strategy](./BACKUPS.md) in place.
|
||||
|
||||
1. Backup your current config and database (optional `devices.csv` to have a backup) (See bellow tip if facing issues)
|
||||
2. Stop the container
|
||||
@@ -38,7 +38,7 @@ The application installation folder in the docker container has changed from `/h
|
||||
|
||||
|
||||
> [!NOTE]
|
||||
> The application uses symlinks linking the old db and config locations to the new ones, so data loss should not occur. [Backup strategies](https://github.com/jokob-sk/NetAlertX/blob/main/docs/BACKUPS.md) are still recommended to backup your setup.
|
||||
> The application uses symlinks linking the old db and config locations to the new ones, so data loss should not occur. [Backup strategies](./BACKUPS.md) are still recommended to backup your setup.
|
||||
|
||||
|
||||
# Examples
|
||||
|
||||
@@ -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 [Create dummy device](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICE_MANAGEMENT.md#dummy-devices) button in the Devices listing page.
|
||||
> 💡 Tip: You can add dummy devices via the [Create dummy device](./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**.
|
||||
|
||||
@@ -17,7 +17,7 @@ Make sure you have a root device with the MAC `Internet` (No other MAC addresses
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> [Bulk-edit devices](/docs/DEVICES_BULK_EDITING.md) by using the _CSV Export_ functionality in the _Maintenance_ section. You can use this to fix `Internet` node assignment issues.
|
||||
> [Bulk-edit devices](./DEVICES_BULK_EDITING.md) by using the _CSV Export_ functionality in the _Maintenance_ section. You can use this to fix `Internet` node assignment issues.
|
||||
|
||||
## 🔍Detailed example:
|
||||
|
||||
@@ -27,7 +27,7 @@ In this example you will setup a device named `rapberrypi` as a `Switch` in our
|
||||
|
||||
- Go to the `Devices` (1) page:
|
||||
|
||||

|
||||

|
||||
|
||||
- In the (2) `Details` tab navigate to the the `Type` (3) dropdown and select the type `Switch` (4).
|
||||
|
||||
@@ -42,7 +42,7 @@ In this example you will setup a device named `rapberrypi` as a `Switch` in our
|
||||
|
||||
- Navigate to your `Network` (1) page:
|
||||
|
||||

|
||||

|
||||
|
||||
- Notice the newly added `raspberrypi` (2) tab which now represents a network node, also showing up in the tree (3).
|
||||
- As we asssigned the `raspberrypi` in the previous (1) Device details page section to the `Internet` parent network node in step (6), the link is also showing up in the tree diagram (4)
|
||||
@@ -52,7 +52,7 @@ In this example you will setup a device named `rapberrypi` as a `Switch` in our
|
||||
|
||||
- After clicking the `Assign` button in the previous section, the `(AppleTV)` (1) device is now connected to our `raspberrypi` (2).
|
||||
|
||||

|
||||

|
||||
|
||||
- You can see the `raspberrypi` represents the Network node type `Switch` (3)
|
||||
- The `(AppleTV)` to `raspberrypi` connection is also displayed in the table of `Connected devices` (4).
|
||||
|
||||
@@ -9,11 +9,11 @@ There are 4 ways how to influence notifications:
|
||||
|
||||
> [!NOTE]
|
||||
> It's recommended to use the same schedule interval for all plugins responsible for scanning devices, otherwise false positives might be reported if different devices are discovered by different plugins. Check the **Settings** > **Enabled settings** section for a warning:
|
||||
> 
|
||||
> 
|
||||
|
||||
## Device settings 💻
|
||||
|
||||

|
||||

|
||||
|
||||
There are 4 settings on the device for influencing notifications. You can:
|
||||
|
||||
@@ -23,7 +23,7 @@ There are 4 settings on the device for influencing notifications. You can:
|
||||
|
||||
## Plugin settings 🔌
|
||||
|
||||

|
||||

|
||||
|
||||
On almost all plugins there are 2 core settings, `<plugin>_WATCH` and `<plugin>_REPORT_ON`.
|
||||
|
||||
@@ -34,7 +34,7 @@ Click the **Read more in the docs.** Link at the top of each plugin to get more
|
||||
|
||||
## Global settings ⚙
|
||||
|
||||

|
||||

|
||||
|
||||
In Notification Processing settings, you can specify blanket rules. These allow you to specify exceptions to the Plugin and Device settings and will override those.
|
||||
|
||||
@@ -45,7 +45,7 @@ In Notification Processing settings, you can specify blanket rules. These allow
|
||||
|
||||
## Ignoring devices 🔕
|
||||
|
||||

|
||||

|
||||
|
||||
You can completely ignore detected devices globally. This could be because your instance detects docker containers, you want to ignore devices from a specific manufacturer via MAC rules or you want to ignore devices on a specific IP range.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ Most performance issues are caused by a big database or large log files. Enablin
|
||||
|
||||
You can always check the size of your database and database tables under the Maintenance page.
|
||||
|
||||

|
||||

|
||||
|
||||
> [!NOTE]
|
||||
> For around 100 devices the database should be approximately `50MB` and none of the entries (rows) should exceed the value of `10 000` on a healthy system. These numbers will depend on your network activity and settings.
|
||||
|
||||
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](/docs/PLUGINS.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](/docs/PLUGINS.md) that can help you discover more about your network or check how to scan [Remote networks](./REMOTE_NETWORKS.md).
|
||||
112
docs/PLUGINS.md
Executable file
@@ -0,0 +1,112 @@
|
||||
# 🔌 Plugins
|
||||
|
||||
NetAlertX supports additional plugins to extend its functionality, each with its own settings and options. Plugins can be loaded via the General -> `LOADED_PLUGINS` setting. For custom plugin development, refer to the [Plugin development guide](/docs/PLUGINS_DEV.md).
|
||||
|
||||
>[!NOTE]
|
||||
> Please check this [Plugins debugging guide](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEBUG_PLUGINS.md) and the corresponding Plugin documentation in the below table if you are facing issues.
|
||||
|
||||
## ⚡ Quick start
|
||||
|
||||
> [!TIP]
|
||||
> You can load additional Plugins via the General -> `LOADED_PLUGINS` setting.
|
||||
|
||||
1. Pick your `🔍 dev scanner` plugin (e.g. `ARPSCAN` or `NMAPDEV`), or import devices into the application with an `📥 importer` plugin. (See **✅Enabling plugins** below)
|
||||
2. Pick a `▶️ publisher` plugin, if you want to send notifications. If you don't see a publisher you'd like to use, look at the [📚_publisher_apprise](/front/plugins/_publisher_apprise/) plugin which is a proxy for over 80 notification services.
|
||||
3. Setup your [Network topology diagram](/docs/NETWORK_TREE.md)
|
||||
4. Fine-tune [Notifications](/docs/NOTIFICATIONS.md)
|
||||
5. [Backup your setup](/docs/BACKUPS.md)
|
||||
6. Contribute and [Create custom plugins](/docs/PLUGINS_DEV.md)
|
||||
|
||||
|
||||
## 📑 Available Plugins
|
||||
|
||||
Device-detecting plugins insert values into the `CurrentScan` database table. The plugins that are not required are safe to ignore, however, it makes sense to have at least some device-detecting plugins enabled, such as `ARPSCAN` or `NMAPDEV`.
|
||||
|
||||
|
||||
| ID | Type | Description | Features | Required | Data source | Detailed docs |
|
||||
|---------------|---------|--------------------------------------------|----------|----------|--------------|---------------------------------------------------------------------|
|
||||
| `APPRISE` | ▶️ | Apprise notification proxy | | | Script | [_publisher_apprise](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/_publisher_apprise/) |
|
||||
| `ARPSCAN` | 🔍 | ARP-scan on current network | | | Script | [arp_scan](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/arp_scan/) |
|
||||
| `AVAHISCAN` | 🆎 | Avahi (mDNS-based) name resolution | | | Script | [avahi_scan](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/avahi_scan/) |
|
||||
| `ASUSWRT` | 🔍 | Import connected devices from AsusWRT | | | Script | [asuswrt_import](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/asuswrt_import/) |
|
||||
| `CSVBCKP` | ⚙ | CSV devices backup | | | Script | [csv_backup](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/csv_backup/) |
|
||||
| `CUSTPROP` | ⚙ | Managing custom device properties values | | Yes | Template | [custom_props](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/custom_props/) |
|
||||
| `DBCLNP` | ⚙ | Database cleanup | | Yes* | Script | [db_cleanup](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/db_cleanup/) |
|
||||
| `DDNS` | ⚙ | DDNS update | | | Script | [ddns_update](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/ddns_update/) |
|
||||
| `DHCPLSS` | 🔍/📥/🆎| Import devices from DHCP leases | | | Script | [dhcp_leases](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/dhcp_leases/) |
|
||||
| `DHCPSRVS` | ♻ | DHCP servers | | | Script | [dhcp_servers](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/dhcp_servers/) |
|
||||
| `FREEBOX` | 🔍/♻/🆎| Pull data and names from Freebox/Iliadbox | | | Script | [freebox](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/freebox/) |
|
||||
| `ICMP` | 🔍 | ICMP (ping) status checker | | | Script | [icmp_scan](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/icmp_scan/) |
|
||||
| `INTRNT` | 🔍 | Internet IP scanner | | | Script | [internet_ip](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/internet_ip/) |
|
||||
| `INTRSPD` | ♻ | Internet speed test | | | Script | [internet_speedtest](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/internet_speedtest/) |
|
||||
| `IPNEIGH` | 🔍 | Scan ARP (IPv4) and NDP (IPv6) tables | | | Script | [ipneigh](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/ipneigh/) |
|
||||
| `LUCIRPC` | 🔍 | Import connected devices from OpenWRT | | | Script | [luci_import](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/luci_import/) |
|
||||
| `MAINT` | ⚙ | Maintenance of logs, etc. | | | Script | [maintenance](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/maintenance/) |
|
||||
| `MQTT` | ▶️ | MQTT for synching to Home Assistant | | | Script | [_publisher_mqtt](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/_publisher_mqtt/) |
|
||||
| `NBTSCAN` | 🆎 | Nbtscan (NetBIOS-based) name resolution | | | Script | [nbtscan_scan](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/nbtscan_scan/) |
|
||||
| `NEWDEV` | ⚙ | New device template | | Yes | Template | [newdev_template](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/newdev_template/) |
|
||||
| `NMAP` | ♻ | Nmap port scanning & discovery | | | Script | [nmap_scan](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/nmap_scan/) |
|
||||
| `NMAPDEV` | 🔍 | Nmap dev scan on current network | | | Script | [nmap_dev_scan](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/nmap_dev_scan/) |
|
||||
| `NSLOOKUP` | 🆎 | NSLookup (DNS-based) name resolution | | | Script | [nslookup_scan](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/nslookup_scan/) |
|
||||
| `NTFPRCS` | ⚙ | Notification processing | | Yes | Template | [notification_processing](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/notification_processing/)|
|
||||
| `NTFY` | ▶️ | NTFY notifications | | | Script | [_publisher_ntfy](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/_publisher_ntfy/) |
|
||||
| `OMDSDN` | 📥/🆎 | OMADA TP-Link import | 🖧 🔄 | | Script | [omada_sdn_imp](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/omada_sdn_imp/) |
|
||||
| `PIHOLE` | 🔍/🆎/📥| Pi-hole device import & sync | | | SQLite DB | [pihole_scan](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/pihole_scan/) |
|
||||
| `PUSHSAFER` | ▶️ | Pushsafer notifications | | | Script | [_publisher_pushsafer](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/_publisher_pushsafer/) |
|
||||
| `PUSHOVER` | ▶️ | Pushover notifications | | | Script | [_publisher_pushover](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/_publisher_pushover/) |
|
||||
| `SETPWD` | ⚙ | Set password | | Yes | Template | [set_password](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/set_password/) |
|
||||
| `SMTP` | ▶️ | Email notifications | | | Script | [_publisher_email](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/_publisher_email/) |
|
||||
| `SNMPDSC` | 🔍/📥 | SNMP device import & sync | | | Script | [snmp_discovery](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/snmp_discovery/) |
|
||||
| `SYNC` | 🔍/⚙/📥| Sync & import from NetAlertX instances | 🖧 🔄 | Yes | Script | [sync](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/sync/) |
|
||||
| `TELEGRAM` | ▶️ | Telegram notifications | | | Script | [_publisher_telegram](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/_publisher_telegram/) |
|
||||
| `UI` | ♻ | UI specific settings | | Yes | Template | [ui_settings](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/ui_settings/) |
|
||||
| `UNFIMP` | 🔍/📥/🆎| UniFi device import & sync | 🖧 | | Script | [unifi_import](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/unifi_import/) |
|
||||
| `VNDRPDT` | ⚙ | Vendor database update | | | Script | [vendor_update](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/vendor_update/) |
|
||||
| `WEBHOOK` | ▶️ | Webhook notifications | | | Script | [_publisher_webhook](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/_publisher_webhook/) |
|
||||
| `WEBMON` | ♻ | Website down monitoring | | | Script | [website_monitor](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/website_monitor/) |
|
||||
| `WOL` | ♻ | Automatic wake-on-lan | | | Script | [wake_on_lan](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/wake_on_lan/) |
|
||||
|
||||
|
||||
> \* The database cleanup plugin (`DBCLNP`) is not _required_ but the app will become unusable after a while if not executed.
|
||||
> ❌ marked for removal
|
||||
> ⌚It's recommended to use the same schedule interval for all plugins responsible for discovering new devices.
|
||||
|
||||
## Plugin types
|
||||
|
||||
|
||||
| Plugin type | Icon | Description | When to run | Required | Data source [?](/docs/PLUGINS_DEV.md) |
|
||||
| -------------- | ---- | ---------------------------------------------------------------- | ----------------------------------- | -------- | ------------------------------------- |
|
||||
| publisher | ▶️ | Sending notifications to services. | `on_notification` | ✖ | Script |
|
||||
| dev scanner | 🔍 | Create devices in the app, manages online/offline device status. | `schedule` | ✖ | Script / SQLite DB |
|
||||
| name discovery | 🆎 | Discovers names of devices via various protocols. | `before_name_updates`, `schedule` | ✖ | Script |
|
||||
| importer | 📥 | Importing devices from another service. | `schedule` | ✖ | Script / SQLite DB |
|
||||
| system | ⚙ | Providing core system functionality. | `schedule` / always on | ✖/✔ | Script / Template |
|
||||
| other | ♻ | Other plugins | misc | ✖ | Script / Template |
|
||||
|
||||
## Features
|
||||
|
||||
| Icon | Description |
|
||||
| ---- | ------------------------------------------------------------ |
|
||||
| 🖧 | Auto-imports the network topology diagram |
|
||||
| 🔄 | Has the option to sync some data back into the plugin source |
|
||||
|
||||
|
||||
## ✅Enabling plugins
|
||||
|
||||
Plugins can be enabled via Settings, and can be disabled as needed.
|
||||
|
||||
1. Research which plugin you'd like to use, enable `DISCOVER_PLUGINS` and load the required plugins in Settings via the `LOADED_PLUGINS` setting.
|
||||
1. Save the changes and review the Settings of the newly loaded plugins.
|
||||
1. Change the `<prefix>_RUN` Setting to the recommended or custom value as per the documentation of the given setting
|
||||
- If using `schedule` on a `🔍 dev scanner` plugin, make sure the schedules are the same across all `🔍 dev scanner` plugins
|
||||
|
||||
### Disabling, Unloading and Ignoring plugins
|
||||
|
||||
1. Change the `<prefix>_RUN` Setting to `disabled` if you want to disable the plugin, but keep the settings
|
||||
1. If you want to speed up the application, you can unload the plugin by unselecting it in the `LOADED_PLUGINS` setting.
|
||||
- Careful, once you save the Settings Unloaded plugin settings will be lost (old `app.conf` files are kept in the `/config` folder)
|
||||
1. You can completely ignore plugins by placing a `ignore_plugin` file into the plugin directory. Ignored plugins won't show up in the `LOADED_PLUGINS` setting.
|
||||
|
||||
## 🆕 Developing new custom plugins
|
||||
|
||||
If you want to develop a custom plugin, please read this [Plugin development guide](/docs/PLUGINS_DEV.md).
|
||||
@@ -12,7 +12,10 @@ NetAlertX comes with a plugin system to feed events from third-party scripts int
|
||||
|
||||
### 🎥 Watch the video:
|
||||
|
||||
[](https://youtu.be/cdbxlwiWhv8)
|
||||
> [!TIP]
|
||||
> Read this guide [Development environment setup guide](./DEV_ENV_SETUP.md) to set up your local environment for development. 👩💻
|
||||
|
||||
[](https://youtu.be/cdbxlwiWhv8)
|
||||
|
||||
### 📸 Screenshots
|
||||
|
||||
@@ -123,7 +126,7 @@ You can show or hide the UI on the "Plugins" page and "Plugins" tab for a 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.<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).
|
||||
> 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 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.
|
||||
|
||||
@@ -500,7 +503,7 @@ Below are some general additional notes, when defining `params`:
|
||||
#### ⚙ Setting object structure
|
||||
|
||||
> [!NOTE]
|
||||
> The settings flow and when Plugin specific settings are applied is described under the [Settings system](/docs/SETTINGS_SYSTEM.md).
|
||||
> The settings flow and when Plugin specific settings are applied is described under the [Settings system](./SETTINGS_SYSTEM.md).
|
||||
|
||||
Required attributes are:
|
||||
|
||||
@@ -575,7 +578,7 @@ Each element may also have associated events (e.g., running a scan or triggering
|
||||
|
||||
##### Supported settings `function` values
|
||||
|
||||
You can have any `"function": "my_custom_name"` custom name, however, the ones listed below have a specific functionality attached to them. If you use a custom name, then the setting is mostly used as an input parameter for the `params` section.
|
||||
You can have any `"function": "my_custom_name"` custom name, however, the ones listed below have a specific functionality attached to them.
|
||||
|
||||
| Setting | Description |
|
||||
| ------- | ----------- |
|
||||
@@ -590,7 +593,7 @@ You can have any `"function": "my_custom_name"` custom name, however, the ones l
|
||||
| | - "before_config_save" - run before the config is marked as saved. Useful if your plugin needs to modify the `app.conf` file. |
|
||||
| `RUN_SCHD` | (required if you include "schedule" in the above `RUN` function) Cron-like scheduling is used if the `RUN` setting is set to `schedule`. |
|
||||
| `CMD` | (required) Specifies the command that should be executed. |
|
||||
| `API_SQL` | (not implemented) Generates a `table_` + `code_name` + `.json` file as per [API docs](https://github.com/jokob-sk/NetAlertX/blob/main/docs/API.md). |
|
||||
| `API_SQL` | (not implemented) Generates a `table_` + `code_name` + `.json` file as per [API docs](./API.md). |
|
||||
| `RUN_TIMEOUT` | (optional) Specifies the maximum execution time of the script. If not specified, a default value of 10 seconds is used to prevent hanging. |
|
||||
| `WATCH` | (optional) Specifies which database columns are watched for changes for this particular plugin. If not specified, no notifications are sent. |
|
||||
| `REPORT_ON` | (optional) Specifies when to send a notification. Supported options are: |
|
||||
@@ -657,7 +660,7 @@ The UI will adjust how columns are displayed in the UI based on the resolvers de
|
||||
| Type Definitions | |
|
||||
| `device_mac` | The value is considered to be a MAC address, and a link pointing to the device with the given MAC address is generated. |
|
||||
| `device_ip` | The value is considered to be an IP address. A link pointing to the device with the given IP is generated. The IP is checked against the last detected IP address and translated into a MAC address, which is then used for the link itself. |
|
||||
| `device_name_mac` | The value is considered to be a MAC address, and a link pointing to the device with the given IP is generated. The link label is resolved as the target device name. |
|
||||
| `device_name_mac` | The value is considered to be a MAC address, and a link pointing to the device with the given MAC is generated. The link label is resolved as the target device name. |
|
||||
| `url` | The value is considered to be a URL, so a link is generated. |
|
||||
| `textbox_save` | Generates an editable and saveable text box that saves values in the database. Primarily intended for the `UserData` database column in the `Plugins_Objects` table. |
|
||||
| `url_http_https` | Generates two links with the `https` and `http` prefix as lock icons. |
|
||||
|
||||
@@ -1,53 +1,32 @@
|
||||
# Privacy & Random MAC's
|
||||
<!--- --------------------------------------------------------------------- --->
|
||||
|
||||
The latest versions of some operating systems (IOS and Android) incorporate a
|
||||
new & interesting functionality to improve privacy: **Random MACs**.
|
||||
Some operating systems incorporate randomize MAC addresses to improve privacy.
|
||||
|
||||
This functionality allows you to **hide the real MAC** of the device and
|
||||
**assign a random MAC** when we connect to WIFI networks.
|
||||
This functionality allows you to **hide the real 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.
|
||||
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.
|
||||
|
||||
**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).
|
||||
**I recommend disabling this on-device functionality when connecting our devices to our own WIFI's**, 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 randomizes the MAC).
|
||||
|
||||
**Random MACs** are recognized by the characters "2", "6", "A", or "E" as the 2nd character in the Mac address. You can disable specific prefixes to be detected as random MAC addresses by specifying the `UI_NOT_RANDOM_MAC` setting.
|
||||
|
||||
## Windows
|
||||
|
||||

|
||||
|
||||
- [How to Disable MAC Randomization on Windows](https://www.androidphonesoft.com/blog/how-to-turn-off-random-mac-address-windows-10/)
|
||||
|
||||
## 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***
|
||||
|
||||
|
||||
<!--- --------------------------------------------------------------------- --->
|
||||
[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"
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<details>
|
||||
<summary>:information_source: In the app hover over settings or fields/labels or click blue in-app ❔ (question-mark) icons to get to relevant documentation pages.</summary>
|
||||
|
||||

|
||||

|
||||
|
||||
</details>
|
||||
|
||||
@@ -17,7 +17,7 @@ There is also an in-app Help / FAQ section that should be answering frequently a
|
||||
|
||||
#### 💻 Bare-metal / On-server (Experimental/community supported 🧪)
|
||||
|
||||
- [(Experimental 🧪) On-hardware instructions](https://github.com/jokob-sk/NetAlertX/blob/main/docs/HW_INSTALL.md)
|
||||
- [(Experimental 🧪) On-hardware instructions](./HW_INSTALL.md)
|
||||
|
||||
- Alternative bare-metal install forks:
|
||||
- [leiweibau's fork](https://github.com/leiweibau/Pi.Alert/) (maintained)
|
||||
@@ -27,58 +27,58 @@ There is also an in-app Help / FAQ section that should be answering frequently a
|
||||
|
||||
#### 📥 Initial Setup
|
||||
|
||||
- [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)
|
||||
- [Better name resolution with Reverse DNS](/docs/REVERSE_DNS.md)
|
||||
- [Network treemap configuration](/docs/NETWORK_TREE.md)
|
||||
- [Backups](/docs/BACKUPS.md)
|
||||
- [Plugins overview](/front/plugins/README.md)
|
||||
- [Synology Guide](./SYNOLOGY_GUIDE.md)
|
||||
- [Subnets and VLANs configuration for arp-scan](./SUBNETS.md)
|
||||
- [Scanning Remote Networks](./REMOTE_NETWORKS.md)
|
||||
- [SMTP server config](./SMTP.md)
|
||||
- [Custom Icon configuration and support](./ICONS.md)
|
||||
- [Notifications](./NOTIFICATIONS.md)
|
||||
- [Better name resolution with Reverse DNS](./REVERSE_DNS.md)
|
||||
- [Network treemap configuration](./NETWORK_TREE.md)
|
||||
- [Backups](./BACKUPS.md)
|
||||
- [Plugins overview](/docs/PLUGINS.md)
|
||||
|
||||
#### 🐛 Debugging help & tips
|
||||
|
||||
- [Debugging tips](/docs/DEBUG_TIPS.md)
|
||||
- [Debugging UI not showing](/docs/WEB_UI_PORT_DEBUG.md)
|
||||
- [Invalid JSON errors debug help](/docs/DEBUG_INVALID_JSON.md)
|
||||
- [Troubleshooting Plugins](/docs/DEBUG_PLUGINS.md)
|
||||
- [File Permissions](/docs/FILE_PERMISSIONS.md)
|
||||
- [Performance tips](/docs/PERFORMANCE.md)
|
||||
- [Debugging tips](./DEBUG_TIPS.md)
|
||||
- [Debugging UI not showing](./WEB_UI_PORT_DEBUG.md)
|
||||
- [Invalid JSON errors debug help](./DEBUG_INVALID_JSON.md)
|
||||
- [Troubleshooting Plugins](./DEBUG_PLUGINS.md)
|
||||
- [File Permissions](./FILE_PERMISSIONS.md)
|
||||
- [Performance tips](./PERFORMANCE.md)
|
||||
|
||||
#### 🔝 Popular/Suggested
|
||||
|
||||
- [Home Assistant](/docs/HOME_ASSISTANT.md)
|
||||
- [Bulk edit devices](/docs/DEVICES_BULK_EDITING.md)
|
||||
- [Home Assistant](./HOME_ASSISTANT.md)
|
||||
- [Bulk edit devices](./DEVICES_BULK_EDITING.md)
|
||||
|
||||
#### ⚙ System Management
|
||||
|
||||
- [Manage devices (legacy docs)](/docs/DEVICE_MANAGEMENT.md)
|
||||
- [Random MAC/MAC icon meaning (legacy docs)](/docs/RANDOM_MAC.md)
|
||||
- [Manage devices (legacy docs)](./DEVICE_MANAGEMENT.md)
|
||||
- [Random MAC/MAC icon meaning (legacy docs)](./RANDOM_MAC.md)
|
||||
|
||||
#### 🔎 Examples
|
||||
|
||||
- [N8N webhook example](/docs/WEBHOOK_N8N.md)
|
||||
- [N8N webhook example](./WEBHOOK_N8N.md)
|
||||
|
||||
#### ♻ Misc
|
||||
|
||||
- [Version history (legacy)](/docs/VERSIONS_HISTORY.md)
|
||||
- [Reverse proxy (Nginx, Apache, SWAG)](/docs/REVERSE_PROXY.md)
|
||||
- [Installing Updates](/docs/UPDATES.md)
|
||||
- [Setting up Authelia](/docs/AUTHELIA.md) (DRAFT)
|
||||
- [Version history (legacy)](./VERSIONS_HISTORY.md)
|
||||
- [Reverse proxy (Nginx, Apache, SWAG)](./REVERSE_PROXY.md)
|
||||
- [Installing Updates](./UPDATES.md)
|
||||
- [Setting up Authelia](./AUTHELIA.md) (DRAFT)
|
||||
|
||||
#### 👩💻For Developers👨💻
|
||||
|
||||
- [Setting up your DEV environment](/docs/DEV_ENV_SETUP.md)
|
||||
- [Setting up your DEV environment](./DEV_ENV_SETUP.md)
|
||||
- [Server APP code structure](/server/README.md)
|
||||
- [Database structure](/docs/DATABASE.md)
|
||||
- [API endpoints details](/docs/API.md)
|
||||
- [Plugin development guide](/docs/PLUGINS_DEV.md)
|
||||
- [Settings system](/docs/SETTINGS_SYSTEM.md)
|
||||
- [New Version notifications](/docs/VERSIONS.md)
|
||||
- [Frontend development tips](/docs/FRONTEND_DEVELOPMENT.md)
|
||||
- [Webhook secrets](/docs/WEBHOOK_SECRET.md)
|
||||
- [Database structure](./DATABASE.md)
|
||||
- [API endpoints details](./API.md)
|
||||
- [Plugin development guide](./PLUGINS_DEV.md)
|
||||
- [Settings system](./SETTINGS_SYSTEM.md)
|
||||
- [New Version notifications](./VERSIONS.md)
|
||||
- [Frontend development tips](./FRONTEND_DEVELOPMENT.md)
|
||||
- [Webhook secrets](./WEBHOOK_SECRET.md)
|
||||
|
||||
Feel free to suggest or submit new docs via a PR.
|
||||
|
||||
@@ -137,8 +137,8 @@ Some additional context:
|
||||
|
||||
Before submitting a new issue please spend a couple of minutes on research:
|
||||
|
||||
* Check [🛑 Common issues](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEBUG_TIPS.md#common-issues)
|
||||
* Check [🛑 Common issues](./DEBUG_TIPS.md#common-issues)
|
||||
* Check [💡 Closed issues](https://github.com/jokob-sk/NetAlertX/issues?q=is%3Aissue+is%3Aclosed) if a similar issue was solved in the past.
|
||||
* When submitting an issue ❗[enable debug](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEBUG_TIPS.md)❗
|
||||
* When submitting an issue ❗[enable debug](./DEBUG_TIPS.md)❗
|
||||
|
||||
⚠ Please follow the pre-defined issue template to resolve your issue faster.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
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.
|
||||
To scan multiple locally accessible network segments, add them as subnets according to the [subnets](./SUBNETS.md) documentation.
|
||||
|
||||
## Complex Use Cases
|
||||
|
||||
@@ -28,7 +28,7 @@ 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.
|
||||
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/docs/PLUGINS.md) to find a plugin that works with your router and network setup.
|
||||
|
||||
## Multiple NetAlertX Instances
|
||||
|
||||
@@ -38,12 +38,12 @@ If you have servers in different networks, you can set up separate NetAlertX ins
|
||||
|
||||
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.
|
||||
For more information on how to add devices manually (or dummy devices), refer to the [Device Management](./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 (vai 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.
|
||||
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
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
The **Sessions Section** provides details about a device's connection history. This data is automatically detected and cannot be edited by the user.
|
||||
|
||||

|
||||

|
||||
|
||||
---
|
||||
|
||||
@@ -57,6 +57,6 @@ When a new connection is detected, the system creates a session record:
|
||||
|
||||
The session information is then used to display the device presence under **Monitoring** -> **Presence**.
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@@ -10,11 +10,11 @@ The source of truth for user-defined values is the `app.conf` file. Editing the
|
||||
|
||||
#### Settings database table
|
||||
|
||||
The `Settings` database table contains settings for App run purposes. The table is recreated every time the App restarts. The settings are loaded from the source-of-truth, that is the `app.conf` file. A high-level overview on the database structure can be found in the [database documentation](/docs/DATABASE.md).
|
||||
The `Settings` database table contains settings for App run purposes. The table is recreated every time the App restarts. The settings are loaded from the source-of-truth, that is the `app.conf` file. A high-level overview on the database structure can be found in the [database documentation](./DATABASE.md).
|
||||
|
||||
#### table_settings.json
|
||||
|
||||
This is the [API endpoint](/docs/API.md) that reflects the state of the `Settings` database table. Settings can be accessed with the:
|
||||
This is the [API endpoint](./API.md) that reflects the state of the `Settings` database table. Settings can be accessed with the:
|
||||
|
||||
* `getSetting(key)` JavaScript method
|
||||
|
||||
@@ -32,7 +32,7 @@ The App generates two `app.conf` entries for every setting (Since version 23.8+)
|
||||
> [!NOTE]
|
||||
> This is the preferred way adding settings going forward. I'll be likely migrating all app settings into plugin-based settings.
|
||||
|
||||
Plugin settings are loaded dynamically from the `config.json` of individual plugins. If a setting isn't defined in the `app.conf` file, it is initialized via the `default_value` property of a setting from the `config.json` file. Check the [Plugins documentation](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/README.md#-setting-object-structure), section `⚙ Setting object structure` for details on the structure of the setting.
|
||||
Plugin settings are loaded dynamically from the `config.json` of individual plugins. If a setting isn't defined in the `app.conf` file, it is initialized via the `default_value` property of a setting from the `config.json` file. Check the [Plugins documentation](https://github.com/jokob-sk/NetAlertX/blob/main/docs/PLUGINS.md#-setting-object-structure), section `⚙ Setting object structure` for details on the structure of the setting.
|
||||
|
||||
![Screen 1][screen1]
|
||||
|
||||
|
||||
@@ -2,27 +2,27 @@
|
||||
|
||||
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 (Wi-Fi Extenders, VPNs and inaccessible networks), check the [remote networks documentation](https://github.com/jokob-sk/NetAlertX/blob/main/docs/REMOTE_NETWORKS.md).
|
||||
If direct scans are not possible (Wi-Fi Extenders, VPNs and inaccessible networks), check the [remote networks documentation](./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.
|
||||
> Check [debugging plugins](/docs/DEBUG_PLUGINS.md) for more tips.
|
||||
> Check [debugging plugins](./DEBUG_PLUGINS.md) for more tips.
|
||||
|
||||
## Example Values
|
||||
|
||||
> [!NOTE]
|
||||
> Please use the UI to configure settings as it ensures the config file is in the correct format. Edit `app.conf` directly only when really necessary.
|
||||
> 
|
||||
> 
|
||||
|
||||
* **Examples for one and two subnets:**
|
||||
* One subnet: `SCAN_SUBNETS = ['192.168.1.0/24 --interface=eth0']`
|
||||
* Two subnets: `SCAN_SUBNETS = ['192.168.1.0/24 --interface=eth0','192.168.1.0/24 --interface=eth1 -vlan=107']`
|
||||
* Two subnets: `SCAN_SUBNETS = ['192.168.1.0/24 --interface=eth0','192.168.1.0/24 --interface=eth1 --vlan=107']`
|
||||
|
||||
If you get timeout messages, decrease the network mask (e.g.: from `/16` to `/24`) or increase the `TIMEOUT` setting (e.g.: `ARPSCAN_RUN_TIMEOUT` to `300` (5-minute timeout)) for the plugin and the interval between scans (e.g.: `ARPSCAN_RUN_SCHD` to `*/10 * * * *` (scans every 10 minutes)).
|
||||
|
||||
@@ -45,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
|
||||
|
||||
@@ -83,7 +89,6 @@ By default, Hyper-V only allows untagged packets through to the VM interface, bl
|
||||
2. Within the VM, set up sub-interfaces for each VLAN to enable scanning. On Ubuntu 22.04, Netplan can be used. In /etc/netplan/00-installer-config.yaml, add VLAN definitions:
|
||||
|
||||
```yaml
|
||||
|
||||
network:
|
||||
ethernets:
|
||||
eth0:
|
||||
|
||||
@@ -9,18 +9,18 @@ The folders you are creating below will contain the configuration and the databa
|
||||
1. Create a parent folder named `netalertx`
|
||||
2. Create a `db` sub-folder
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
3. Create a `config` sub-folder
|
||||
|
||||

|
||||

|
||||
|
||||
4. Note down the folders Locations:
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
5. Open **Container manager** -> **Project** and click **Create**.
|
||||
6. Fill in the details:
|
||||
@@ -49,7 +49,7 @@ services:
|
||||
- PORT=20211
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
7. Replace the paths to your volume and/or comment out unnecessary line(s):
|
||||
|
||||
@@ -63,12 +63,12 @@ services:
|
||||
# - local/path/logs:/app/log <- commented out with # ⚠
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
8. (optional) Change the port number from `20211` to an unused port if this port is already used.
|
||||
9. Build the project:
|
||||
|
||||

|
||||

|
||||
|
||||
10. Navigate to `<Synology URL>:20211` (or your custom port).
|
||||
11. Read the [Subnets](/docs/SUBNETS.md) and [Plugins](/front/plugins/README.md) docs to complete your setup.
|
||||
11. Read the [Subnets](./SUBNETS.md) and [Plugins](/docs/PLUGINS.md) docs to complete your setup.
|
||||
@@ -8,17 +8,17 @@ If you are not on the latest version, the app will notify you, that a new releas
|
||||
|
||||
If any notification occurs and an email is sent, the email will contain a note that a new version is available. See the sample email below:
|
||||
|
||||

|
||||

|
||||
|
||||
### 🆕 In the UI
|
||||
|
||||
In the UI via a notification Icon and via a custom message in the Maintenance section.
|
||||
|
||||

|
||||

|
||||
|
||||
For a comparison, this is how the UI looks like if you are on the latest stable image:
|
||||
|
||||

|
||||

|
||||
|
||||
## Implementation details
|
||||
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
# Pi.Alert Version History
|
||||
<!--- --------------------------------------------------------------------- --->
|
||||
|
||||
| Version | Description |
|
||||
| ------- | --------------------------------------------------------------- |
|
||||
| v3.00 | Major set of New features & Enhancements |
|
||||
| v2.70 | New features & Usability improvements in the web prontal |
|
||||
| v2.61 | Bug fixing |
|
||||
| v2.60 | Improved the compability of installation process (Ubuntu) |
|
||||
| v2.56 | Bug fixing |
|
||||
| v2.55 | Bug fixing |
|
||||
| v2.52 | Bug fixing |
|
||||
| v2.51 | Bug fixing |
|
||||
| v2.50 | First public release |
|
||||
|
||||
|
||||
# 🆕 2022+ [Newest Release notes](https://github.com/jokob-sk/NetAlertX/issues/138)
|
||||
|
||||
## Pi.Alert v3.02
|
||||
<!--- --------------------------------------------------------------------- --->
|
||||
**PENDING UPDATE DOC**
|
||||
- Fixed: UNIQUE constraint failed with Local MAC #114
|
||||
|
||||
|
||||
## Pi.Alert v3.01
|
||||
<!--- --------------------------------------------------------------------- --->
|
||||
**PENDING UPDATE DOC**
|
||||
- Fixed: Problem with local MAC & IP (raspberry) #106
|
||||
|
||||
|
||||
## Pi.Alert v3.00
|
||||
<!--- --------------------------------------------------------------------- --->
|
||||
**PENDING UPDATE DOC**
|
||||
- `arp-scan` config options: interface, several subnets. #101 #15
|
||||
- Next/previos button while editing devices #66 #37
|
||||
- Internet presence/sessions monitoring #63
|
||||
- Logical delete / archive / hide Device #93
|
||||
- Flag to mark device with random MAC's #87
|
||||
- New Device Types predefined in combobox #92
|
||||
- Ask before leave the page with unsaved changes #104
|
||||
- Option to don't mark devices as new during installation #94
|
||||
- Uninstall script #62
|
||||
- Fixed: Error updating name of devices w/o IP #97
|
||||
- Fixed: Deleted devices reappear #84
|
||||
- Fixed: Device running Pi.Alert must be marked as "on-line" #76
|
||||
- Fixed: Incorrect calculation of presence hours #102
|
||||
- Fixed: Problem redirect to homepage clicking in logo #103
|
||||
|
||||
|
||||
## Pi.Alert v2.70
|
||||
<!--- --------------------------------------------------------------------- --->
|
||||
- Added Client names resolution #43
|
||||
- Added Check to mark devices as "known" #16
|
||||
- Remember "Show XXX entries" dropdown value #16 #26
|
||||
- Remember "sorting" in devices #16
|
||||
- Remember "Device panel " in device detail #16
|
||||
- Added "All" option to "Show x Entries" option #16
|
||||
- Added optional Location field (Door, Basement, etc.) to devices #16
|
||||
- "Device updated successfully" message now is not modal #16
|
||||
- Now is possible to delete Devices #16
|
||||
- Added Device Type Singleboard Computer (SBC) #16
|
||||
- Allowed to use " in device name #42
|
||||
|
||||
|
||||
## Pi.Alert v2.60
|
||||
<!--- --------------------------------------------------------------------- --->
|
||||
- `pialert.conf` moved from `back` to `config` folder
|
||||
- `pialert.conf` splitted in two files: `pialert.conf` and `version.conf`
|
||||
- Added compatibility with Python 3 (default version installed with Ubuntu)
|
||||
- Added compatibility in the Installation guide with Ubuntu server
|
||||
- Eliminated some unnecessary packages from the installation
|
||||
|
||||
|
||||
|
||||
### 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)
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
N8N can be used for more advanced conditional notification use cases. For example, you want only to get notified if two out of a specified list of devices is down. Or you can use other plugins to process the notifiations further. The below is a simple example of sending an email on a webhook.
|
||||
|
||||

|
||||

|
||||
|
||||
### Specify your email template
|
||||
See [sample JSON](https://github.com/jokob-sk/NetAlertX/blob/main/front/report_templates/webhook_json_sample.json) if you want to see the JSON paths used in the email template below
|
||||

|
||||

|
||||
|
||||
```
|
||||
Events count: {{ $json["body"]["attachments"][0]["text"]["events"].length }}
|
||||
@@ -14,7 +14,7 @@ New devices count: {{ $json["body"]["attachments"][0]["text"]["new_devices"].len
|
||||
```
|
||||
|
||||
### Get your webhook in n8n
|
||||

|
||||

|
||||
|
||||
### Configure NetAlertX to point to the above URL
|
||||

|
||||

|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
When opening an issue please:
|
||||
|
||||
1. Include a screenshot of what you see when accessing `HTTP://<your rpi IP>/20211` (or your custom port)
|
||||
1. [Follow steps 1, 2, 3, 4 on this page](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEBUG_TIPS.md)
|
||||
1. [Follow steps 1, 2, 3, 4 on this page](./DEBUG_TIPS.md)
|
||||
1. Execute the following in the container to see the processes and their ports and submit a screenshot of the result:
|
||||
1. `sudo apk add lsof`
|
||||
1. `sudo lsof -i`
|
||||
@@ -13,7 +13,7 @@ When opening an issue please:
|
||||
1. if you get `nginx: [emerg] bind() to 0.0.0.0:20211 failed (98: Address in use)` try using a different port number
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
## 2. JavaScript issues
|
||||
|
||||
@@ -48,4 +48,4 @@ In the container execute:
|
||||
> [!TIP]
|
||||
> You can try to start the container without mapping the `/app/config` and `/app/db` dirs and if the UI shows up then the issue is most likely related to your file system permissions or file ownership.
|
||||
|
||||
Please read the [Permissions troubleshooting guide](/docs/FILE_PERMISSIONS.md) and provide a screesnhot of the permissions and ownership in the `/app/db` and `app/config` directories.
|
||||
Please read the [Permissions troubleshooting guide](./FILE_PERMISSIONS.md) and provide a screesnhot of the permissions and ownership in the `/app/db` and `app/config` directories.
|
||||
BIN
docs/img/BACKUPS/Maintenance_Backup_Restore.png
Executable file
|
After Width: | Height: | Size: 88 KiB |
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/NetAlertX_logo.png
Executable file
|
After Width: | Height: | Size: 6.6 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 |
|
Before Width: | Height: | Size: 551 KiB After Width: | Height: | Size: 551 KiB |
BIN
docs/img/RANDOM_MAC/ios_random_mac.png
Executable file
|
After Width: | Height: | Size: 598 KiB |
BIN
docs/img/RANDOM_MAC/windows_random_mac.png
Executable file
|
After Width: | Height: | Size: 17 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 |
80
docs/index.md
Executable file
@@ -0,0 +1,80 @@
|
||||
# NetAlertX Documentation
|
||||
|
||||
Welcome to the official NetAlertX documentation! NetAlertX is a powerful tool designed to simplify the management and monitoring of your network. Below, you will find guides and resources to help you set up, configure, and troubleshoot your NetAlertX instance.
|
||||
|
||||
## In-App Help
|
||||
|
||||
NetAlertX provides contextual help within the application:
|
||||
|
||||
- **Hover over settings, fields, or labels** to see additional tooltips and guidance.
|
||||
- **Click the blue ❔ (question-mark) icons** next to various elements to view detailed information.
|
||||
- Access the in-app **Help / FAQ** section for frequently asked questions and quick answers.
|
||||
|
||||
---
|
||||
|
||||
## Installation Guides
|
||||
|
||||
### Docker (Fully Supported)
|
||||
|
||||
NetAlertX is fully supported in Docker environments, allowing for easy setup and configuration. Follow the official guide to get started:
|
||||
|
||||
- [Docker Installation Guide](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md)
|
||||
|
||||
This guide will take you through the process of setting up NetAlertX using Docker Compose or standalone Docker commands.
|
||||
|
||||
### Bare-Metal Installation (Experimental)
|
||||
|
||||
If you prefer to run NetAlertX on your own hardware, you can try the experimental bare-metal installation. Please note that this method is still under development, and we're looking for maintainers to help improve it.
|
||||
|
||||
- [Bare-Metal Installation Guide](./HW_INSTALL.md)
|
||||
|
||||
---
|
||||
|
||||
## Help and Support
|
||||
|
||||
If you need help or run into issues, here are some resources to guide you:
|
||||
|
||||
**Before opening an issue, please:**
|
||||
|
||||
- [Check common issues](./DEBUG_TIPS.md#common-issues) to see if your problem has already been reported.
|
||||
- [Look at closed issues](https://github.com/jokob-sk/NetAlertX/issues?q=is%3Aissue+is%3Aclosed) for possible solutions to past problems.
|
||||
- **Enable debugging** to gather more information: [Debug Guide](./DEBUG_TIPS.md).
|
||||
|
||||
**Need more help?** Join the community discussions or submit a support request:
|
||||
|
||||
- Visit the [GitHub Discussions](https://github.com/jokob-sk/NetAlertX/discussions) for community support.
|
||||
- If you are experiencing issues that require immediate attention, consider opening an issue on our [GitHub Issues page](https://github.com/jokob-sk/NetAlertX/issues).
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
NetAlertX is open-source and welcomes contributions from the community! If you'd like to help improve the software, please follow the guidelines below:
|
||||
|
||||
- **Fork the repository** and make your changes.
|
||||
- **Submit a pull request** with a detailed description of what you’ve changed and why.
|
||||
|
||||
For more information on contributing, check out our [Dev Guide](./DEV_ENV_SETUP.md).
|
||||
|
||||
---
|
||||
|
||||
## Stay Updated
|
||||
|
||||
To keep up with the latest changes and updates to NetAlertX, please refer to the following resources:
|
||||
|
||||
- [Releases](https://github.com/jokob-sk/NetAlertX/releases)
|
||||
|
||||
Make sure to follow the project on GitHub to get notifications for new releases and important updates.
|
||||
|
||||
---
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- **Configuration Tips**: Learn how to optimize NetAlertX for your network setup.
|
||||
- **Advanced Features**: Explore advanced functionalities like plugin development and custom configurations.
|
||||
- **Documentation Index**: Check out the full [documentation index](https://github.com/jokob-sk/NetAlertX/tree/main/docs) for all the guides available.
|
||||
|
||||
We hope you find this documentation helpful. If you have any suggestions or improvements, please don’t hesitate to contribute!
|
||||
|
||||
---
|
||||
NetAlertX is actively maintained. You can find the source code, report bugs, or request new features on our [GitHub page](https://github.com/jokob-sk/NetAlertX).
|
||||
10
front/.well-known/gpc.json
Executable file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"gpc": true,
|
||||
"compliance": [
|
||||
{
|
||||
"regulation": "GDPR",
|
||||
"status": "not applicable"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -287,11 +287,6 @@ body
|
||||
margin-left: 150px;
|
||||
}
|
||||
|
||||
#settingsPage
|
||||
{
|
||||
display: grid;
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.main-header .logo {
|
||||
@@ -620,7 +615,7 @@ body
|
||||
|
||||
.modal_red
|
||||
{
|
||||
color: rgb(245, 245, 245);
|
||||
color: #d41001;
|
||||
background-color: #e2acaa;
|
||||
border-color: #d41001;
|
||||
}
|
||||
@@ -893,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;
|
||||
@@ -926,16 +949,6 @@ height: 50px;
|
||||
top:0px;
|
||||
}
|
||||
|
||||
.overview-section
|
||||
{
|
||||
/* border-top: solid;
|
||||
border-width: medium;
|
||||
border-width: medium;
|
||||
border-width: 1px;
|
||||
border-radius: 15px;
|
||||
margin-bottom: 3px; */
|
||||
|
||||
}
|
||||
|
||||
.settings-group i{
|
||||
font-size: 16px;
|
||||
@@ -1232,6 +1245,24 @@ input[readonly] {
|
||||
/* Devices page */
|
||||
/* ----------------------------------------------------------------- */
|
||||
|
||||
|
||||
#columnFilters {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px; /* Add spacing between items */
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
box-sizing: border-box; /* Ensure padding and borders are included in the width */
|
||||
padding: 1em;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.filter-dropdown
|
||||
{
|
||||
width:7em
|
||||
}
|
||||
|
||||
.modal-header .close
|
||||
{
|
||||
display: flex;
|
||||
@@ -1287,6 +1318,11 @@ input[readonly] {
|
||||
min-width: 40px;
|
||||
}
|
||||
|
||||
.iconPreview svg{
|
||||
min-width: 20px;
|
||||
max-width: 20px;
|
||||
}
|
||||
|
||||
|
||||
.dummyDevice
|
||||
{
|
||||
@@ -1313,8 +1349,8 @@ input[readonly] {
|
||||
top: -6px;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
left: 0px;
|
||||
font-size: large;
|
||||
left: 4px;
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
.drag
|
||||
@@ -1379,12 +1415,90 @@ input[readonly] {
|
||||
{
|
||||
display: none;
|
||||
padding-top: 2em;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------- */
|
||||
/* 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_devIcon
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#panDetails #NEWDEV_devCustomProps_label
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------- */
|
||||
/* MODAL popups */
|
||||
/* ----------------------------------------------------------------- */
|
||||
|
||||
#modal-input-textarea
|
||||
{
|
||||
width: 100%;
|
||||
@@ -1415,6 +1529,14 @@ input[readonly] {
|
||||
flex-direction:column;
|
||||
justify-content:center;
|
||||
}
|
||||
.networkHelpIcon
|
||||
{
|
||||
padding: 5px;
|
||||
margin-left: 5px;
|
||||
top: 55px;
|
||||
position: absolute;
|
||||
z-index:5;
|
||||
}
|
||||
#networkTree .netNodeText
|
||||
{
|
||||
position: absolute;
|
||||
@@ -1475,7 +1597,7 @@ input[readonly] {
|
||||
|
||||
.spanNetworkTree {
|
||||
display: inline-block;
|
||||
width: 120px;
|
||||
width: 135px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis;
|
||||
@@ -1521,6 +1643,24 @@ input[readonly] {
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
.dev-detail-tab-name
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------- */
|
||||
/* EVENTS page */
|
||||
/* ----------------------------------------------------------------- */
|
||||
|
||||
.eventsPeriodSelectWrap{
|
||||
display: inline;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.eventsPage #tableEventsTitle
|
||||
{
|
||||
float: left ;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------- */
|
||||
@@ -1528,6 +1668,7 @@ input[readonly] {
|
||||
/* ----------------------------------------------------------------- */
|
||||
|
||||
|
||||
|
||||
.plugin-filters
|
||||
{
|
||||
margin: 7px;
|
||||
@@ -1555,6 +1696,7 @@ input[readonly] {
|
||||
.integrations-plugins .content
|
||||
{
|
||||
display: table;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.plugin-content .tab-content
|
||||
@@ -1606,13 +1748,6 @@ input[readonly] {
|
||||
}
|
||||
}
|
||||
|
||||
#settingsPage .small-box .inner .card-title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
.textOverflow
|
||||
{
|
||||
@@ -1710,16 +1845,14 @@ input[readonly] {
|
||||
----------------------------------------------------------------------------- */
|
||||
#multiEditPlc
|
||||
{
|
||||
position: fixed;
|
||||
bottom: 50px;
|
||||
right: 0px;
|
||||
z-index: 10;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
|
||||
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,12 @@ input[type="password"]::-webkit-caps-lock-indicator {
|
||||
background-color: #000 !important;
|
||||
}
|
||||
|
||||
.callout code {
|
||||
background-color: #fff !important;
|
||||
color:#000 !important;
|
||||
}
|
||||
|
||||
.thresholdFormControl
|
||||
{
|
||||
color:#000;
|
||||
}
|
||||
@@ -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,13 @@
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.callout code {
|
||||
background-color: #fff !important;
|
||||
color:#000 !important;
|
||||
}
|
||||
|
||||
.thresholdFormControl
|
||||
{
|
||||
color:#000;
|
||||
}
|
||||
@@ -61,12 +61,54 @@
|
||||
<!-- <div class="box-transparent"> -->
|
||||
<div id="navDevice" class="nav-tabs-custom">
|
||||
<ul class="nav nav-tabs" style="font-size:16px;">
|
||||
<li> <a id="tabDetails" href="#panDetails" data-toggle="tab"> <?= lang('DevDetail_Tab_Details');?> </a></li>
|
||||
<li> <a id="tabTools" href="#panTools" data-toggle="tab"> <?= lang('DevDetail_Tab_Tools');?> </a></li>
|
||||
<li> <a id="tabSessions" href="#panSessions" data-toggle="tab"> <?= lang('DevDetail_Tab_Sessions');?> </a></li>
|
||||
<li> <a id="tabPresence" href="#panPresence" data-toggle="tab"> <?= lang('DevDetail_Tab_Presence');?> </a></li>
|
||||
<li> <a id="tabEvents" href="#panEvents" data-toggle="tab"> <?= lang('DevDetail_Tab_Events');?> </a></li>
|
||||
<li> <a id="tabPlugins" href="#panPlugins" data-toggle="tab"> <?= lang('DevDetail_Tab_Plugins');?> </a></li>
|
||||
<li>
|
||||
<a id="tabDetails" href="#panDetails" data-toggle="tab">
|
||||
<i class="fa fa-info-circle"></i>
|
||||
<span class="dev-detail-tab-name">
|
||||
<?= lang('DevDetail_Tab_Details');?>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="tabTools" href="#panTools" data-toggle="tab">
|
||||
<i class="fa fa-screwdriver-wrench"></i>
|
||||
<span class="dev-detail-tab-name">
|
||||
<?= lang('DevDetail_Tab_Tools');?>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="tabSessions" href="#panSessions" data-toggle="tab">
|
||||
<i class="fa fa-list-ol"></i>
|
||||
<span class="dev-detail-tab-name">
|
||||
<?= lang('DevDetail_Tab_Sessions');?>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="tabPresence" href="#panPresence" data-toggle="tab">
|
||||
<i class="fa fa-calendar"></i>
|
||||
<span class="dev-detail-tab-name">
|
||||
<?= lang('DevDetail_Tab_Presence');?>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="tabEvents" href="#panEvents" data-toggle="tab">
|
||||
<i class="fa fa-bolt"></i>
|
||||
<span class="dev-detail-tab-name">
|
||||
<?= lang('DevDetail_Tab_Events');?>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="tabPlugins" href="#panPlugins" data-toggle="tab">
|
||||
<i class="fa fa-plug"></i>
|
||||
<span class="dev-detail-tab-name">
|
||||
<?= lang('DevDetail_Tab_Plugins');?>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<div class="btn-group pull-right">
|
||||
<button type="button" class="btn btn-default" style="padding: 10px; min-width: 30px;"
|
||||
|
||||
@@ -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>
|
||||
@@ -75,7 +70,7 @@
|
||||
// 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 = {
|
||||
@@ -83,32 +78,55 @@
|
||||
DevDetail_MainInfo_Title: {
|
||||
data: ["devMac", "devLastIP", "devName", "devOwner", "devType", "devVendor", "devGroup", "devIcon", "devLocation", "devComments"],
|
||||
docs: "https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICE_MANAGEMENT.md",
|
||||
iconClass: "fa fa-pencil"
|
||||
iconClass: "fa fa-pencil",
|
||||
inputGroupClasses: "field-group main-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/SESSION_INFO.md",
|
||||
iconClass: "fa fa-calendar"
|
||||
iconClass: "fa fa-calendar",
|
||||
inputGroupClasses: "field-group session-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 alert-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 network-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/DEVICE_DISPLAY_SETTINGS.md",
|
||||
iconClass: "fa fa-list-check"
|
||||
|
||||
iconClass: "fa fa-list-check",
|
||||
inputGroupClasses: "field-group display-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 cutprop-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_', '')));
|
||||
@@ -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,8 +232,8 @@
|
||||
});
|
||||
|
||||
|
||||
// wait until everything is initialized to update icon
|
||||
updateIconPreview();
|
||||
// wait until everything is initialized to update icons
|
||||
updateAllIconPreviews();
|
||||
|
||||
// update readonly fields
|
||||
handleReadOnly(settingsData, disabledFields);
|
||||
@@ -317,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 = '') {
|
||||
@@ -387,59 +348,61 @@
|
||||
const createNew = mac === 'new' ? 1 : 0;
|
||||
|
||||
const devLastIP = $('#NEWDEV_devLastIP').val();
|
||||
const newMac = $('#NEWDEV_devMac').val()
|
||||
|
||||
// Validate MAC and Last IP
|
||||
if (mac === '' || !(isValidIPv4(devLastIP) || isValidIPv6(devLastIP))) {
|
||||
if (mac === '' || !isValidMac(newMac) || !( isValidIPv4(devLastIP) || isValidIPv6(devLastIP) )) {
|
||||
showMessage(getString("DeviceEdit_ValidMacIp"), 5000, "modal_red");
|
||||
return;
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -227,11 +227,7 @@ function initializeCalendar() {
|
||||
loading: function( isLoading, view ) {
|
||||
if (isLoading) {
|
||||
showSpinner()
|
||||
} else {
|
||||
// setTimeout(() => {
|
||||
// updateIconPreview($('#txtIcon'))
|
||||
// }, 500);
|
||||
|
||||
} else {
|
||||
hideSpinner()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -28,30 +28,21 @@
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
<div class="content-wrapper">
|
||||
|
||||
<!-- Content header--------------------------------------------------------- -->
|
||||
<section class="content-header">
|
||||
<h1 id="pageTitle">
|
||||
<i class="fa fa-laptop"></i>
|
||||
<?= lang('Device_Title');?>
|
||||
</h1>
|
||||
</section>
|
||||
|
||||
<!-- Main content ---------------------------------------------------------- -->
|
||||
<section class="content">
|
||||
|
||||
<!-- Tile toggle cards ------------------------------------------------------- -->
|
||||
<div class="row" id="TileCards">
|
||||
<div class="row " id="TileCards">
|
||||
<!-- Placeholder ------------------------------------------------------- -->
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Device presence / Activity Chart ------------------------------------------------------- -->
|
||||
|
||||
<div class="row" id="DevicePresence">
|
||||
<div class="col-md-12">
|
||||
<div class="box" id="clients">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title"><?= lang('Device_Shortcut_OnlineChart');?> </h3>
|
||||
<div class="box" id="clients">
|
||||
<div class="box-header ">
|
||||
<h3 class="box-title col-md-12"><?= lang('Device_Shortcut_OnlineChart');?> </h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="chart">
|
||||
@@ -67,6 +58,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Device Filters ------------------------------------------------------- -->
|
||||
<div class="box box-aqua hidden" id="columnFiltersWrap">
|
||||
<div class="box-header ">
|
||||
<h3 class="box-title col-md-12"><?= lang('Devices_Filters');?> </h3>
|
||||
</div>
|
||||
<!-- Placeholder ------------------------------------------------------- -->
|
||||
<div id="columnFilters" ></div>
|
||||
</div>
|
||||
|
||||
<!-- datatable ------------------------------------------------------------- -->
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
@@ -78,6 +78,9 @@
|
||||
<h3 id="tableDevicesTitle" class="box-title text-gray "></h3>
|
||||
</div>
|
||||
<div class="dummyDevice col-md-3 ">
|
||||
<span id="multiEditPlc">
|
||||
<!-- multi edit button placeholder -->
|
||||
</span>
|
||||
<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>
|
||||
@@ -106,7 +109,7 @@
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
</section>
|
||||
<!-- /.content -->
|
||||
<div id="multiEditPlc" class="col-md-2"></div>
|
||||
|
||||
</div>
|
||||
<!-- /.content-wrapper -->
|
||||
|
||||
@@ -120,8 +123,8 @@
|
||||
<!-- page script ----------------------------------------------------------- -->
|
||||
<script>
|
||||
var deviceStatus = 'all';
|
||||
var tableRows = getCookie ("nax_parTableRows") == "" ? 10 : getCookie ("nax_parTableRows") ;
|
||||
var tableOrder = getCookie ("nax_parTableOrder") == "" ? [[3,'desc'], [0,'asc']] : JSON.parse(getCookie ("nax_parTableOrder")) ;
|
||||
var tableRows = getCache ("nax_parTableRows") == "" ? 10 : getCache ("nax_parTableRows") ;
|
||||
var tableOrder = getCache ("nax_parTableOrder") == "" ? [[3,'desc'], [0,'asc']] : JSON.parse(getCache ("nax_parTableOrder")) ;
|
||||
|
||||
var tableColumnHide = [];
|
||||
var tableColumnOrder = [];
|
||||
@@ -138,12 +141,18 @@ function main () {
|
||||
|
||||
showSpinner();
|
||||
|
||||
initFilters();
|
||||
|
||||
// render tiles
|
||||
getDevicesTotals();
|
||||
|
||||
//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
|
||||
@@ -181,6 +190,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)
|
||||
@@ -194,35 +206,61 @@ function mapIndx(oldIndex)
|
||||
// Query total numbers of Devices by status
|
||||
//------------------------------------------------------------------------------
|
||||
function getDevicesTotals() {
|
||||
maxDelay = 180; //cap at 180 seconds
|
||||
|
||||
// Fetch data via AJAX
|
||||
$.ajax({
|
||||
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));
|
||||
let maxRetries = Math.ceil(Math.log2(maxDelay)); // Calculate maximum retries to cap at maxDelay seconds
|
||||
let attempt = 0;
|
||||
let calledUpdateAPI = false;
|
||||
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
function fetchDataWithBackoff() {
|
||||
// Calculate the delay (2^attempt seconds, capped at maxDelay seconds)
|
||||
const delay = Math.min(2 ** attempt, maxDelay) * 1000;
|
||||
|
||||
|
||||
// Attempt to fetch data
|
||||
$.ajax({
|
||||
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) {
|
||||
const 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");
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error("Failed to fetch devices data (Attempt " + (attempt + 1) + "):", error);
|
||||
|
||||
// try updating the API once
|
||||
if(calledUpdateAPI == false)
|
||||
{
|
||||
calledUpdateAPI = true;
|
||||
updateApi("devices_tiles");
|
||||
}
|
||||
|
||||
// Retry logic
|
||||
if (attempt < maxRetries) {
|
||||
attempt++;
|
||||
setTimeout(fetchDataWithBackoff, delay);
|
||||
} else {
|
||||
console.error("Maximum retries reached. Unable to fetch devices data.");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Start the first fetch attempt
|
||||
fetchDataWithBackoff();
|
||||
}
|
||||
|
||||
function processDeviceTotals(devicesData) {
|
||||
@@ -274,6 +312,8 @@ function processDeviceTotals(devicesData) {
|
||||
//------------------------------------------------------------------------------
|
||||
// Render the info boxes/tiles on top
|
||||
function renderInfoboxes(customData) {
|
||||
if(customData.length > 0)
|
||||
{
|
||||
$.ajax({
|
||||
url: 'php/components/tile_cards.php', // PHP script URL
|
||||
type: 'POST', // Use POST method to send data
|
||||
@@ -287,49 +327,187 @@ 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;
|
||||
//Render filters if specified
|
||||
let columnFilters = [];
|
||||
|
||||
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;
|
||||
}
|
||||
function initFilters() {
|
||||
// Attempt to fetch data
|
||||
$.ajax({
|
||||
url: '/php/server/query_json.php',
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
data: {
|
||||
file: 'table_devices_filters.json', // Pass the file parameter
|
||||
nocache: Date.now() // Prevent caching with a timestamp
|
||||
},
|
||||
success: function(response) {
|
||||
if (response && response.data) {
|
||||
|
||||
let resultJSON = response.data;
|
||||
|
||||
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
|
||||
// Save the result to cache
|
||||
setCache("devicesFilters", JSON.stringify(resultJSON));
|
||||
|
||||
// Get the displayed filters from settings
|
||||
const displayedFilters = createArray(getSetting("UI_columns_filters"));
|
||||
|
||||
// Clear any existing filters in the DOM
|
||||
$('#columnFilters').empty();
|
||||
|
||||
console.log(displayedFilters);
|
||||
|
||||
// Ensure displayedFilters is an array and not empty
|
||||
if (Array.isArray(displayedFilters) && displayedFilters.length > 0) {
|
||||
$('#columnFiltersWrap').removeClass("hidden");
|
||||
|
||||
displayedFilters.forEach(columnHeaderStringKey => {
|
||||
// Get the column name using the mapping function
|
||||
const columnName = getColumnNameFromLangString(columnHeaderStringKey);
|
||||
|
||||
// Ensure columnName is valid before proceeding
|
||||
if (columnName) {
|
||||
// Add the filter to the columnFilters array as [columnName, columnHeaderStringKey]
|
||||
columnFilters.push([columnName, columnHeaderStringKey]);
|
||||
} else {
|
||||
console.warn(`Invalid column header string key: ${columnHeaderStringKey}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Filter resultJSON to include only entries with columnName in columnFilters
|
||||
resultJSON = resultJSON.filter(entry =>
|
||||
columnFilters.some(filter => filter[0] === entry.columnName)
|
||||
);
|
||||
|
||||
// Expand resultJSON to include the columnHeaderStringKey
|
||||
resultJSON.forEach(entry => {
|
||||
// Find the matching columnHeaderStringKey from columnFilters
|
||||
const matchingFilter = columnFilters.find(filter => filter[0] === entry.columnName);
|
||||
|
||||
// Add the columnHeaderStringKey to the entry
|
||||
if (matchingFilter) {
|
||||
entry['columnHeaderStringKey'] = matchingFilter[1];
|
||||
}
|
||||
});
|
||||
|
||||
console.log(resultJSON);
|
||||
|
||||
// Transforming the data
|
||||
const transformed = {
|
||||
filters: []
|
||||
};
|
||||
|
||||
// Group data by columnName
|
||||
resultJSON.forEach(entry => {
|
||||
const existingFilter = transformed.filters.find(filter => filter.column === entry.columnName);
|
||||
|
||||
if (existingFilter) {
|
||||
// Add the unique columnValue to options if not already present
|
||||
if (!existingFilter.options.includes(entry.columnValue)) {
|
||||
existingFilter.options.push(entry.columnValue);
|
||||
}
|
||||
} else {
|
||||
// Create a new filter entry
|
||||
transformed.filters.push({
|
||||
column: entry.columnName,
|
||||
headerKey: entry.columnHeaderStringKey,
|
||||
options: [entry.columnValue]
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Sort options alphabetically for better readability
|
||||
transformed.filters.forEach(filter => {
|
||||
filter.options.sort();
|
||||
});
|
||||
|
||||
// Output the result
|
||||
transformedJson = transformed
|
||||
|
||||
// Process the fetched data
|
||||
renderFilters(transformedJson);
|
||||
} else {
|
||||
console.log("No filters to display.");
|
||||
}
|
||||
} else {
|
||||
console.error("Invalid response format from API");
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error("Failed to fetch devices data 'table_devices_filters.json':", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------
|
||||
// Server side component
|
||||
function renderFilters(customData) {
|
||||
|
||||
// console.log(JSON.stringify(customData));
|
||||
|
||||
// Load filter data from the JSON file
|
||||
$.ajax({
|
||||
url: 'php/components/devices_filters.php', // PHP script URL
|
||||
data: { filterObject: JSON.stringify(customData) }, // Send customData as JSON
|
||||
type: 'POST',
|
||||
dataType: 'html',
|
||||
success: function(response) {
|
||||
// console.log(response);
|
||||
|
||||
$('#columnFilters').html(response); // Replace container content with fetched HTML
|
||||
$('#columnFilters').removeClass('hidden'); // Show the filters container
|
||||
|
||||
// Trigger the draw after select change
|
||||
$('.filter-dropdown').on('change', function() {
|
||||
// Collect filters
|
||||
const columnFilters = collectFilters();
|
||||
|
||||
// Update DataTable with the new filters or search value (if applicable)
|
||||
$('#tableDevices').DataTable().draw();
|
||||
|
||||
// Optionally, apply column filters (if using filters for individual columns)
|
||||
const table = $('#tableDevices').DataTable();
|
||||
table.columnFilters = columnFilters; // Apply your column filters logic
|
||||
table.draw();
|
||||
});
|
||||
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error fetching filters:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
// Function to collect filters
|
||||
function collectFilters() {
|
||||
const columnFilters = [];
|
||||
|
||||
// Loop through each filter group
|
||||
document.querySelectorAll('.filter-group').forEach(filterGroup => {
|
||||
const dropdown = filterGroup.querySelector('.filter-dropdown');
|
||||
|
||||
if (dropdown) {
|
||||
const filterColumn = dropdown.getAttribute('data-column');
|
||||
const filterValue = dropdown.value;
|
||||
|
||||
if (filterValue && filterColumn) {
|
||||
columnFilters.push({
|
||||
filterColumn: filterColumn,
|
||||
filterValue: filterValue
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return columnFilters;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Map column index to column name for GraphQL query
|
||||
function mapColumnIndexToFieldName(index, tableColumnVisible) {
|
||||
// the order is important, don't change it!
|
||||
@@ -359,7 +537,8 @@ function mapColumnIndexToFieldName(index, tableColumnVisible) {
|
||||
"devSSID",
|
||||
"devSourcePlugin",
|
||||
"devPresentLastScan",
|
||||
"devAlertDown"
|
||||
"devAlertDown",
|
||||
"devCustomProps"
|
||||
];
|
||||
|
||||
// console.log("OrderBy: " + columnNames[tableColumnOrder[index]]);
|
||||
@@ -369,6 +548,7 @@ function mapColumnIndexToFieldName(index, tableColumnVisible) {
|
||||
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Initializes the main devices list datatable
|
||||
function initializeDatatable (status) {
|
||||
|
||||
if(!status)
|
||||
@@ -419,8 +599,6 @@ function initializeDatatable (status) {
|
||||
}
|
||||
|
||||
// todo: dynamically filter based on status
|
||||
|
||||
|
||||
var table = $('#tableDevices').DataTable({
|
||||
"serverSide": true,
|
||||
"processing": true,
|
||||
@@ -469,6 +647,7 @@ function initializeDatatable (status) {
|
||||
devStatus
|
||||
devParentChildrenCount
|
||||
devIpLong
|
||||
devCustomProps
|
||||
}
|
||||
count
|
||||
}
|
||||
@@ -476,7 +655,13 @@ function initializeDatatable (status) {
|
||||
`;
|
||||
|
||||
console.log(d);
|
||||
|
||||
|
||||
// Handle empty filters
|
||||
let columnFilters = collectFilters();
|
||||
if (columnFilters.length === 0) {
|
||||
columnFilters = [];
|
||||
}
|
||||
|
||||
|
||||
// Prepare query variables for pagination, sorting, and search
|
||||
let query = {
|
||||
@@ -491,7 +676,8 @@ function initializeDatatable (status) {
|
||||
"order": d.order[0].dir.toUpperCase() // Sort direction (ASC/DESC)
|
||||
}] : [], // Default to an empty array if no sorting is defined
|
||||
"search": d.search.value, // Search query
|
||||
"status": deviceStatus
|
||||
"status": deviceStatus,
|
||||
"filters" : columnFilters
|
||||
}
|
||||
|
||||
}
|
||||
@@ -535,7 +721,8 @@ function initializeDatatable (status) {
|
||||
device.devSSID || "",
|
||||
device.devSourcePlugin || "",
|
||||
device.devPresentLastScan || "",
|
||||
device.devAlertDown || ""
|
||||
device.devAlertDown || "",
|
||||
device.devCustomProps || ""
|
||||
];
|
||||
|
||||
const newRow = [];
|
||||
@@ -652,6 +839,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)],
|
||||
@@ -730,18 +928,18 @@ function initializeDatatable (status) {
|
||||
|
||||
// Save cookie Rows displayed, and Parameters rows & order
|
||||
$('#tableDevices').on( 'length.dt', function ( e, settings, len ) {
|
||||
setCookie ("nax_parTableRows", len, 129600); // save for 90 days
|
||||
setCache ("nax_parTableRows", len, 129600); // save for 90 days
|
||||
} );
|
||||
|
||||
$('#tableDevices').on( 'order.dt', function () {
|
||||
setCookie ("nax_parTableOrder", JSON.stringify (table.order()), 129600); // save for 90 days
|
||||
setCache ("nax_parTableOrder", JSON.stringify (table.order()), 129600); // save for 90 days
|
||||
} );
|
||||
|
||||
// add multi-edit button
|
||||
$('#multiEditPlc').append(
|
||||
`<button type="submit" id="multiEdit" class="btn btn-primary" style="display:none" onclick="multiEditDevices();">
|
||||
<i class="fa fa-pencil pointer" ></i> ${getString("Device_MultiEdit")}
|
||||
</button>`)
|
||||
`<span type="submit" id="multiEdit" class="pointer " style="display:none" onclick="multiEditDevices();">
|
||||
<a href="#"><i class="fa fa-pencil " ></i> ${getString("Device_MultiEdit")} </a>
|
||||
</span>`)
|
||||
|
||||
// Event listener for row selection in DataTable
|
||||
$('#tableDevices').on('click', 'tr', function (e) {
|
||||
@@ -771,6 +969,11 @@ function initializeDatatable (status) {
|
||||
|
||||
hideSpinner();
|
||||
|
||||
},
|
||||
createdRow: function(row, data, dataIndex) {
|
||||
// add devMac to the table row
|
||||
$(row).attr('my-devMac', data[mapIndx(11)]);
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -782,9 +985,7 @@ function initializeDatatable (status) {
|
||||
// -----------------------------------------------------------------------------
|
||||
function handleLoadingDialog(needsReload = false)
|
||||
{
|
||||
|
||||
// console.log('needsReload:');
|
||||
// console.log(needsReload);
|
||||
// console.log(`needsReload: ${needsReload}`);
|
||||
|
||||
$.get('/php/server/query_logs.php?file=execution_queue.log&nocache=' + Date.now(), function(data) {
|
||||
|
||||
@@ -823,41 +1024,100 @@ function multiEditDevices()
|
||||
}
|
||||
|
||||
// redirect to the Maintenance section
|
||||
window.location.href = window.location.origin + '/maintenance.php#tab_multiEdit?macs=' + macs.slice(0, -1);
|
||||
window.location.href = './maintenance.php#tab_multiEdit?macs=' + macs.slice(0, -1);
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// 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();
|
||||
|
||||
console.log(devicesDataTableData);
|
||||
|
||||
|
||||
var selectedDevices = [];
|
||||
|
||||
// first row is the heading, skip
|
||||
for (var i = 1; i < rows.length; i++) {
|
||||
|
||||
// console.log(rows[i]._DT_RowIndex);
|
||||
var rowIndex = rows[i]._DT_RowIndex;
|
||||
|
||||
selectedDevices.push(devicesDataTableData[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
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
<?php
|
||||
require 'php/templates/header.php';
|
||||
?>
|
||||
|
||||
<div id="donationsPage" class="content-wrapper">
|
||||
<!-- Content header--------------------------------------------------------- -->
|
||||
<section class="content-header">
|
||||
<h1 id="pageTitle">
|
||||
<i class="fa fa-heart"></i>
|
||||
</h1>
|
||||
</section>
|
||||
|
||||
<!-- Main content ---------------------------------------------------------- -->
|
||||
<section class="content donations">
|
||||
<div id="donationsText" class="box box-solid"></div>
|
||||
<div class="content-header">
|
||||
<h3 class="box-title " id="donationsPlatforms"></h3>
|
||||
</div>
|
||||
<div class="box box-solid">
|
||||
<div class="box-body">
|
||||
<div class="col-sm-2">
|
||||
<a target="_blank" href="https://github.com/sponsors/jokob-sk">
|
||||
<img alt="Sponsor Me on GitHub" src="https://i.imgur.com/X6p5ACK.png" width="150px">
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<a target="_blank" href="https://www.buymeacoffee.com/jokobsk">
|
||||
<img alt="Buy Me A Coffee" src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" width="117px" height="30px">
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<a target="_blank" href="https://www.patreon.com/user?u=84385063">
|
||||
<img alt="Support me on patreon" src="https://upload.wikimedia.org/wikipedia/commons/thumb/8/82/Patreon_logo_with_wordmark.svg/512px-Patreon_logo_with_wordmark.svg.png" width="117px">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-header">
|
||||
<h3 class="box-title " id="donationsOthers"></h3>
|
||||
</div>
|
||||
<div class="box box-solid">
|
||||
<div class="box-body">
|
||||
<div class="col-sm-12">
|
||||
<ul>
|
||||
<li>Bitcoin: <code>1N8tupjeCK12qRVU2XrV17WvKK7LCawyZM</code></li>
|
||||
<li>Ethereum: <code>0x6e2749Cb42F4411bc98501406BdcD82244e3f9C7</code></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
</section>
|
||||
|
||||
</div> <!-- End of class="content-wrapper" -->
|
||||
|
||||
<script>
|
||||
function init()
|
||||
{
|
||||
$("#donationsText").html(getString("Donations_Text"))
|
||||
$("#pageTitle").append(getString("Donations_Title"))
|
||||
$("#donationsPlatforms").append(getString("Donations_Platforms"))
|
||||
$("#donationsOthers").append(getString("Donations_Others"))
|
||||
}
|
||||
|
||||
init();
|
||||
</script>
|
||||
|
||||
<?php
|
||||
require 'php/templates/footer.php';
|
||||
?>
|
||||
@@ -1,17 +1,3 @@
|
||||
<!--
|
||||
#---------------------------------------------------------------------------------#
|
||||
# NetAlertX #
|
||||
# Open Source Network Guard / WIFI & LAN intrusion detector #
|
||||
# #
|
||||
# events.php - Front module. Events page #
|
||||
#---------------------------------------------------------------------------------#
|
||||
# Puche 2021 pi.alert.application@gmail.com GNU GPLv3 #
|
||||
# jokob-sk 2022 jokob.sk@gmail.com GNU GPLv3 #
|
||||
# leiweibau 2022 https://github.com/leiweibau GNU GPLv3 #
|
||||
# cvc90 2023 https://github.com/cvc90 GNU GPLv3 #
|
||||
#---------------------------------------------------------------------------------#
|
||||
-->
|
||||
|
||||
<?php
|
||||
require 'php/templates/header.php';
|
||||
?>
|
||||
@@ -19,26 +5,7 @@
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
<div class="content-wrapper">
|
||||
|
||||
<!-- Content header--------------------------------------------------------- -->
|
||||
<section class="content-header">
|
||||
<h1 id="pageTitle">
|
||||
<i class="fa fa-bolt"></i>
|
||||
<?= lang('Events_Title');?>
|
||||
</h1>
|
||||
|
||||
<!-- period selector -->
|
||||
<span class="breadcrumb" style="top: 0px;">
|
||||
<select class="form-control" id="period" onchange="javascript: periodChanged();">
|
||||
<option value="1 day"><?= lang('Events_Periodselect_today');?></option>
|
||||
<option value="7 days"><?= lang('Events_Periodselect_LastWeek');?></option>
|
||||
<option value="1 month" selected><?= lang('Events_Periodselect_LastMonth');?></option>
|
||||
<option value="1 year"><?= lang('Events_Periodselect_LastYear');?></option>
|
||||
<option value="100 years"><?= lang('Events_Periodselect_All');?></option>
|
||||
</select>
|
||||
</span>
|
||||
</section>
|
||||
<div class="content-wrapper eventsPage">
|
||||
|
||||
<!-- Main content ---------------------------------------------------------- -->
|
||||
<section class="content">
|
||||
@@ -123,15 +90,31 @@
|
||||
<!-- datatable ------------------------------------------------------------- -->
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
|
||||
<div id="tableEventsBox" class="box">
|
||||
|
||||
<!-- box-header -->
|
||||
<div class="box-header">
|
||||
<h3 id="tableEventsTitle" class="box-title text-gray">Events</h3>
|
||||
<div class="box-header col-xs-12">
|
||||
<h3 id="tableEventsTitle" class="box-title text-gray col-xs-10">Events</h3>
|
||||
<div class="eventsPeriodSelectWrap col-xs-2">
|
||||
<select class="form-control" id="period" onchange="javascript: periodChanged();">
|
||||
<option value="1 day"><?= lang('Events_Periodselect_today');?></option>
|
||||
<option value="7 days"><?= lang('Events_Periodselect_LastWeek');?></option>
|
||||
<option value="1 month" selected><?= lang('Events_Periodselect_LastMonth');?></option>
|
||||
<option value="1 year"><?= lang('Events_Periodselect_LastYear');?></option>
|
||||
<option value="100 years"><?= lang('Events_Periodselect_All');?></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- table -->
|
||||
<div class="box-body table-responsive">
|
||||
|
||||
|
||||
|
||||
<table id="tableEvents" class="table table-bordered table-hover table-striped ">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
<?php
|
||||
require 'php/templates/header.php';
|
||||
require 'php/templates/notification.php';
|
||||
?>
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
<div class="content-wrapper">
|
||||
|
||||
<!-- Content header--------------------------------------------------------- -->
|
||||
<section class="content-header">
|
||||
<?php require 'php/templates/notification.php'; ?>
|
||||
<h1 id="pageTitle">
|
||||
<i class="fa fa-question"></i>
|
||||
<?= lang('HelpFAQ_Title');?>
|
||||
</h1>
|
||||
</section>
|
||||
|
||||
<!-- Main content ---------------------------------------------------------- -->
|
||||
<section class="content">
|
||||
<h4>
|
||||
|
||||
@@ -115,9 +115,9 @@ function cacheSettings()
|
||||
return new Promise((resolve, reject) => {
|
||||
if(!getCache('completedCalls').includes('cacheSettings'))
|
||||
{
|
||||
$.get('/php/server/query_json.php', { file: 'table_settings.json', nocache: Date.now() }, function(resSet) {
|
||||
$.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) {
|
||||
$.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('/php/server/query_json.php', { file: '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"];
|
||||
|
||||
@@ -353,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') {
|
||||
@@ -386,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 {
|
||||
@@ -705,7 +737,7 @@ function forceLoadUrl(relativeUrl) {
|
||||
// -----------------------------------------------------------------------------
|
||||
function navigateToDeviceWithIp (ip) {
|
||||
|
||||
$.get('/php/server/query_json.php', { file: '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"];
|
||||
|
||||
@@ -717,7 +749,7 @@ function navigateToDeviceWithIp (ip) {
|
||||
{
|
||||
mac = obj.devMac;
|
||||
|
||||
window.open(window.location.origin +'/deviceDetails.php?mac=' + mac , "_blank");
|
||||
window.open('./deviceDetails.php?mac=' + mac , "_blank");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -744,6 +776,11 @@ function checkMacOrInternet(inputStr) {
|
||||
}
|
||||
}
|
||||
|
||||
// Alias
|
||||
function isValidMac(value) {
|
||||
return checkMacOrInternet(value);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Gte MAC from query string
|
||||
function getMac(){
|
||||
@@ -927,7 +964,7 @@ function cacheDevices()
|
||||
|
||||
// if(!getCache('completedCalls').includes('cacheDevices'))
|
||||
// {
|
||||
$.get('/php/server/query_json.php', { file: '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)
|
||||
|
||||
@@ -1293,7 +1330,7 @@ function clearCache() {
|
||||
// -----------------------------------------------------------------------------
|
||||
// Function to check if cache needs to be refreshed because of setting changes
|
||||
function checkSettingChanges() {
|
||||
$.get('/php/server/query_json.php', { file: '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'));
|
||||
|
||||
@@ -1348,7 +1385,7 @@ async function waitForGraphQLServer() {
|
||||
// Returns 1 if running, 0 otherwise
|
||||
async function isGraphQLServerRunning() {
|
||||
try {
|
||||
const response = await $.get('/php/server/query_json.php', { file: '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"];
|
||||
|
||||
@@ -32,7 +32,7 @@ function renderList(
|
||||
// remove first item containing the SQL query
|
||||
options.shift();
|
||||
|
||||
const apiUrl = `php/server/dbHelper.php?action=read&rawSql=${encodeURIComponent(sqlQuery)}`;
|
||||
const apiUrl = `php/server/dbHelper.php?action=read&rawSql=${btoa(encodeURIComponent(sqlQuery))}`;
|
||||
|
||||
$.get(apiUrl, function (sqlOptionsData) {
|
||||
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
|
||||
@@ -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,8 @@ const handleElementOptions = (setKey, elementOptions, transformers, val) => {
|
||||
let onChange = "console.log('onChange - Not implemented');";
|
||||
let customParams = "";
|
||||
let customId = "";
|
||||
let columns = [];
|
||||
let base64Regex = "";
|
||||
|
||||
|
||||
elementOptions.forEach((option) => {
|
||||
@@ -708,6 +771,12 @@ const handleElementOptions = (setKey, elementOptions, transformers, val) => {
|
||||
if (option.customId) {
|
||||
customId = option.customId;
|
||||
}
|
||||
if (option.columns) {
|
||||
columns = option.columns;
|
||||
}
|
||||
if (option.base64Regex) {
|
||||
base64Regex = option.base64Regex;
|
||||
}
|
||||
});
|
||||
|
||||
if (transformers.includes("sha256")) {
|
||||
@@ -730,7 +799,9 @@ const handleElementOptions = (setKey, elementOptions, transformers, val) => {
|
||||
onClick,
|
||||
onChange,
|
||||
customParams,
|
||||
customId
|
||||
customId,
|
||||
columns,
|
||||
base64Regex
|
||||
};
|
||||
};
|
||||
|
||||
@@ -863,7 +934,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 +942,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 +977,23 @@ function generateFormHtml(set, overrideValue) {
|
||||
onClick,
|
||||
onChange,
|
||||
customParams,
|
||||
customId
|
||||
customId,
|
||||
columns,
|
||||
base64Regex
|
||||
} = 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 +1004,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 +1027,8 @@ function generateFormHtml(set, overrideValue) {
|
||||
my-data-type="${dataType}"
|
||||
my-customparams="${customParams}"
|
||||
my-customid="${customId}"
|
||||
my-originalSetKey="${originalSetKey}"
|
||||
my-base64Regex="${base64Regex}"
|
||||
id="${setKey}${suffix}"
|
||||
type="${inputType}"
|
||||
value="${val}"
|
||||
@@ -957,6 +1043,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 +1056,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 +1067,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 +1185,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
|
||||
@@ -1019,4 +1209,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
|
||||
}
|
||||
@@ -129,7 +171,46 @@ function updateIconPreview(elem) {
|
||||
tryUpdateIcon();
|
||||
}
|
||||
|
||||
// ----------------------------------------------
|
||||
// Validate the value based on regex
|
||||
// ⚠ IMPORTANT: use the below to get a valid REGEX ⚠
|
||||
// const regexStr = String.raw`^(?:\*|(?:[0-9]|[1-5][0-9]|[0-9]+-[0-9]+|\*/[0-9]+))\s+(?:\*|(?:[0-9]|1[0-9]|2[0-3]|[0-9]+-[0-9]+|\*/[0-9]+))\s+(?:\*|(?:[1-9]|[12][0-9]|3[01]|[0-9]+-[0-9]+|\*/[0-9]+))\s+(?:\*|(?:[1-9]|1[0-2]|[0-9]+-[0-9]+|\*/[0-9]+))\s+(?:\*|(?:[0-6]|[0-6]-[0-6]|\*/[0-9]+))$`;
|
||||
// console.log(btoa(regexStr));
|
||||
function validateRegex(elem) {
|
||||
const iconSpan = $(elem).parent().find(".validityCheck");
|
||||
const inputElem = $(elem);
|
||||
const regexTmp = atob($(inputElem).attr("my-base64Regex")); // Decode base64 regex
|
||||
|
||||
const regex = new RegExp(regexTmp); // Convert to a valid RegExp object
|
||||
|
||||
let attempts = 0;
|
||||
|
||||
function tryUpdateValidityResultIcon() {
|
||||
let value = inputElem.val().trim(); // Ensure trimmed value
|
||||
|
||||
if (value === "") {
|
||||
attempts++;
|
||||
if (attempts < 10) {
|
||||
setTimeout(tryUpdateValidityResultIcon, 1000); // Retry after 1 sec if empty
|
||||
} else {
|
||||
console.error("Input value is empty after 10 attempts");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate against regex
|
||||
if (regex.test(value)) {
|
||||
iconSpan.html("<i class='fa-regular fa-check'></i>");
|
||||
} else {
|
||||
iconSpan.html("<i class='fa-regular fa-xmark'></i>");
|
||||
}
|
||||
}
|
||||
|
||||
// Attach real-time validation on input change
|
||||
inputElem.on("input", tryUpdateValidityResultIcon);
|
||||
|
||||
tryUpdateValidityResultIcon(); // Initial validation
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Nice checkboxes with iCheck
|
||||
@@ -189,38 +270,65 @@ function copyToClipboard(buttonElement) {
|
||||
// Simple Sortable Table columns
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Function to handle column sorting when a user clicks on a table header
|
||||
function sortColumn(element) {
|
||||
var th = $(element).closest('th');
|
||||
var table = th.closest('table');
|
||||
var columnIndex = th.index();
|
||||
var ascending = !th.data('asc');
|
||||
var th = $(element).closest('th'); // Get the clicked table header
|
||||
var table = th.closest('table'); // Find the closest table
|
||||
var columnIndex = th.index(); // Get the index of the column
|
||||
var ascending = !th.data('asc'); // Toggle sorting order
|
||||
sortTable(table, columnIndex, ascending);
|
||||
th.data('asc', ascending);
|
||||
th.data('asc', ascending); // Store sorting order
|
||||
}
|
||||
|
||||
// Function to sort the table based on the selected column
|
||||
function sortTable(table, columnIndex, ascending) {
|
||||
var tbody = table.find('tbody');
|
||||
var rows = tbody.find('tr').toArray().sort(comparer(columnIndex));
|
||||
var tbody = table.find('tbody'); // Get the table body
|
||||
var rows = tbody.find('tr').toArray().sort(comparer(columnIndex)); // Convert rows to an array and sort
|
||||
if (!ascending) {
|
||||
rows = rows.reverse();
|
||||
rows = rows.reverse(); // Reverse order if descending
|
||||
}
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
tbody.append(rows[i]);
|
||||
tbody.append(rows[i]); // Append sorted rows back to the table
|
||||
}
|
||||
}
|
||||
|
||||
// Function to compare values in the selected column
|
||||
function comparer(index) {
|
||||
return function(a, b) {
|
||||
return function (a, b) {
|
||||
var valA = getCellValue(a, index);
|
||||
var valB = getCellValue(b, index);
|
||||
return $.isNumeric(valA) && $.isNumeric(valB) ? valA - valB : valA.localeCompare(valB);
|
||||
|
||||
// Check if both values are valid IP addresses, and sort numerically if so
|
||||
if (isIPAddress(valA) && isIPAddress(valB)) {
|
||||
return ipToNum(valA) - ipToNum(valB);
|
||||
}
|
||||
|
||||
// If both values are numbers, sort numerically
|
||||
if ($.isNumeric(valA) && $.isNumeric(valB)) {
|
||||
return valA - valB;
|
||||
}
|
||||
|
||||
// Otherwise, sort as text
|
||||
return valA.localeCompare(valB);
|
||||
};
|
||||
}
|
||||
|
||||
// Function to get the text value from a table cell
|
||||
function getCellValue(row, index) {
|
||||
return $(row).children('td').eq(index).text();
|
||||
return $(row).children('td').eq(index).text().trim(); // Get text from the specified column and trim spaces
|
||||
}
|
||||
|
||||
// Function to check if a string is a valid IPv4 address
|
||||
function isIPAddress(value) {
|
||||
return /^\d{1,3}(\.\d{1,3}){3}$/.test(value); // Regular expression to match IPv4 format
|
||||
}
|
||||
|
||||
// Function to convert an IP address to a numeric value for sorting
|
||||
function ipToNum(ip) {
|
||||
return ip.split('.').reduce((acc, octet) => (acc << 8) + parseInt(octet, 10), 0);
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// handling events
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -449,7 +557,7 @@ function showIconSelection() {
|
||||
iconDiv.addEventListener('click', () => {
|
||||
selectElement.value = value; // Update the select element value
|
||||
$(`#${modalId}`).modal('hide'); // Hide the modal
|
||||
updateIconPreview();
|
||||
updateAllIconPreviews();
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -466,8 +574,52 @@ function showIconSelection() {
|
||||
|
||||
}
|
||||
|
||||
// "Device_TableHead_Owner",
|
||||
// "Device_TableHead_Type",
|
||||
// "Device_TableHead_Group",
|
||||
// "Device_TableHead_Status",
|
||||
// "Device_TableHead_Location",
|
||||
// "Device_TableHead_Vendor",
|
||||
// "Device_TableHead_SyncHubNodeName",
|
||||
// "Device_TableHead_NetworkSite",
|
||||
// "Device_TableHead_SSID",
|
||||
// "Device_TableHead_SourcePlugin"
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Get teh correct db column code name based on table header title string
|
||||
function getColumnNameFromLangString(headStringKey) {
|
||||
columnNameMap = {
|
||||
"Device_TableHead_Name": "devName",
|
||||
"Device_TableHead_Owner": "devOwner",
|
||||
"Device_TableHead_Type": "devType",
|
||||
"Device_TableHead_Icon": "devIcon",
|
||||
"Device_TableHead_Favorite": "devFavorite",
|
||||
"Device_TableHead_Group": "devGroup",
|
||||
"Device_TableHead_FirstSession": "devFirstConnection",
|
||||
"Device_TableHead_LastSession": "devLastConnection",
|
||||
"Device_TableHead_LastIP": "devLastIP",
|
||||
"Device_TableHead_MAC": "devMac",
|
||||
"Device_TableHead_Status": "devStatus",
|
||||
"Device_TableHead_MAC_full": "devMac",
|
||||
"Device_TableHead_LastIPOrder": "devIpLong",
|
||||
"Device_TableHead_Rowid": "rowid",
|
||||
"Device_TableHead_Parent_MAC": "devParentMAC",
|
||||
"Device_TableHead_Connected_Devices": "devParentChildrenCount",
|
||||
"Device_TableHead_Location": "devLocation",
|
||||
"Device_TableHead_Vendor": "devVendor",
|
||||
"Device_TableHead_Port": "devParentPort",
|
||||
"Device_TableHead_GUID": "devGUID",
|
||||
"Device_TableHead_SyncHubNodeName": "devSyncHubNode",
|
||||
"Device_TableHead_NetworkSite": "devSite",
|
||||
"Device_TableHead_SSID": "devSSID",
|
||||
"Device_TableHead_SourcePlugin": "devSourcePlugin",
|
||||
"Device_TableHead_PresentLastScan": "devPresentLastScan",
|
||||
"Device_TableHead_AlertDown": "devAlertDown",
|
||||
"Device_TableHead_CustomProps": "devCustomProps"
|
||||
};
|
||||
|
||||
return columnNameMap[headStringKey] || "";
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
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});
|
||||
4178
front/lib/treeviz/bundle.js
Executable file
1
front/lib/treeviz/bundle.js.map
Executable file
@@ -1,47 +1,16 @@
|
||||
<?php
|
||||
#---------------------------------------------------------------------------------#
|
||||
# NetAlertX #
|
||||
# Open Source Network Guard / WIFI & LAN intrusion detector #
|
||||
# #
|
||||
# maintenance.php - Front module. Server side. Maintenance #
|
||||
#---------------------------------------------------------------------------------#
|
||||
# Puche 2021 pi.alert.application@gmail.com GNU GPLv3 #
|
||||
# jokob-sk 2022 jokob.sk@gmail.com GNU GPLv3 #
|
||||
# leiweibau 2022 https://github.com/leiweibau GNU GPLv3 #
|
||||
# cvc90 2023 https://github.com/cvc90 GNU GPLv3 #
|
||||
#---------------------------------------------------------------------------------#
|
||||
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
?>
|
||||
|
||||
|
||||
|
||||
<?php
|
||||
require 'php/templates/header.php';
|
||||
require 'php/templates/notification.php';
|
||||
?>
|
||||
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
|
||||
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
<div class="content-wrapper" id="maintenancePage">
|
||||
|
||||
<!-- Content header--------------------------------------------------------- -->
|
||||
<section class="content-header">
|
||||
<?php require 'php/templates/notification.php'; ?>
|
||||
<h1 id="pageTitle">
|
||||
<i class="fa fa-wrench"></i>
|
||||
<?= lang('Maintenance_Title');?>
|
||||
</h1>
|
||||
</section>
|
||||
|
||||
<!-- Main content ---------------------------------------------------------- -->
|
||||
<section class="content">
|
||||
<!-- Main content ---------------------------------------------------------- -->
|
||||
<section class="content">
|
||||
|
||||
|
||||
<?php
|
||||
<?php
|
||||
|
||||
// Size and last mod of DB ------------------------------------------------------
|
||||
|
||||
@@ -91,10 +60,11 @@ $db->close();
|
||||
<div class="db_info_table_row">
|
||||
<div class="db_info_table_cell" style="min-width: 140px"><?= lang('Maintenance_version');?>
|
||||
<a href="https://github.com/jokob-sk/NetAlertX/blob/main/docs/VERSIONS.md" target="_blank"> <span><i class="fa fa-circle-question"></i></a><span>
|
||||
|
||||
</div>
|
||||
<div class="db_info_table_cell">
|
||||
<div class="version" id="version" data-build-time="<?php echo file_get_contents( "buildtimestamp.txt");?>"><?php echo '<span id="new-version-text" class="myhidden">' .lang('Maintenance_new_version').'</span>'.'<span id="current-version-text" class="myhidden">' .lang('Maintenance_current_version').'</span>';?></div>
|
||||
<div class="version" id="version" data-build-time="<?php echo file_get_contents( "buildtimestamp.txt");?>">
|
||||
<?php echo '<span id="new-version-text" class="myhidden"><i class="fa-solid fa-rocket fa-beat"></i> ' .lang('Maintenance_new_version').'</span>'.'<span id="current-version-text" class="myhidden">' .lang('Maintenance_current_version').'</span>';?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="db_info_table_row">
|
||||
@@ -209,26 +179,38 @@ $db->close();
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane" id="tab_BackupRestore">
|
||||
<div class="db_info_table">
|
||||
<div class="db_info_table_row">
|
||||
<div class="db_tools_table_cell_a" >
|
||||
<button type="button" class="btn btn-default pa-btn bg-green dbtools-button" id="btnExportCSV" onclick="ExportCSV()"><?= lang('Maintenance_Tool_ExportCSV');?></button>
|
||||
</div>
|
||||
<div class="db_tools_table_cell_b"><?= lang('Maintenance_Tool_ExportCSV_text');?></div>
|
||||
</div>
|
||||
<div class="db_info_table_row">
|
||||
<div class="db_tools_table_cell_a" >
|
||||
<button type="button" class="btn btn-default pa-btn pa-btn-delete bg-red dbtools-button" id="btnImportCSV" onclick="askImportCSV()"><?= lang('Maintenance_Tool_ImportCSV');?></button>
|
||||
</div>
|
||||
<div class="db_tools_table_cell_b"><?= lang('Maintenance_Tool_ImportCSV_text');?></div>
|
||||
</div>
|
||||
<div class="db_info_table_row">
|
||||
<div class="db_tools_table_cell_a" >
|
||||
<button type="button" class="btn btn-default pa-btn pa-btn-delete bg-red dbtools-button" id="btnImportPastedCSV" onclick="askImportPastedCSV()"><?= lang('Maintenance_Tool_ImportPastedCSV');?></button>
|
||||
</div>
|
||||
<div class="db_tools_table_cell_b"><?= lang('Maintenance_Tool_ImportPastedCSV_text');?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="db_info_table">
|
||||
<div class="db_info_table_row">
|
||||
<div class="db_tools_table_cell_a" >
|
||||
<button type="button" class="btn btn-default pa-btn bg-green dbtools-button" id="btnExportCSV" onclick="ExportCSV()"><?= lang('Maintenance_Tool_ExportCSV');?></button>
|
||||
</div>
|
||||
<div class="db_tools_table_cell_b"><?= lang('Maintenance_Tool_ExportCSV_text');?></div>
|
||||
</div>
|
||||
<div class="db_info_table_row">
|
||||
<div class="db_tools_table_cell_a" >
|
||||
<button type="button" class="btn btn-default pa-btn pa-btn-delete bg-red dbtools-button" id="btnImportCSV" onclick="askImportCSV()"><?= lang('Maintenance_Tool_ImportCSV');?></button>
|
||||
</div>
|
||||
<div class="db_tools_table_cell_b"><?= lang('Maintenance_Tool_ImportCSV_text');?></div>
|
||||
</div>
|
||||
<div class="db_info_table_row">
|
||||
<div class="db_tools_table_cell_a" >
|
||||
<button type="button" class="btn btn-default pa-btn pa-btn-delete bg-red dbtools-button" id="btnImportPastedCSV" onclick="askImportPastedCSV()"><?= lang('Maintenance_Tool_ImportPastedCSV');?></button>
|
||||
</div>
|
||||
<div class="db_tools_table_cell_b"><?= lang('Maintenance_Tool_ImportPastedCSV_text');?></div>
|
||||
</div>
|
||||
<div class="db_info_table_row">
|
||||
<div class="db_tools_table_cell_a" >
|
||||
<button type="button" class="btn btn-default pa-btn bg-green dbtools-button" id="btnDownloadConfig" onclick="DownloadConfig()"><?= lang('Maintenance_Tool_DownloadConfig');?></button>
|
||||
</div>
|
||||
<div class="db_tools_table_cell_b"><?= lang('Maintenance_Tool_DownloadConfig_text');?></div>
|
||||
</div>
|
||||
<div class="db_info_table_row">
|
||||
<div class="db_tools_table_cell_a" >
|
||||
<button type="button" class="btn btn-default pa-btn pa-btn-delete bg-red dbtools-button" id="btnImportPastedConfig" onclick="askImportPastedConfig()"><?= lang('Maintenance_Tool_ImportPastedConfig');?></button>
|
||||
</div>
|
||||
<div class="db_tools_table_cell_b"><?= lang('Maintenance_Tool_ImportPastedConfig_text');?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ---------------------------Logging-------------------------------------------- -->
|
||||
<div class="tab-pane" id="tab_Logging">
|
||||
@@ -445,6 +427,44 @@ function restartBackend() {
|
||||
})
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// Import pasted Config ASK
|
||||
function askImportPastedConfig() {
|
||||
|
||||
// Add new icon as base64 string
|
||||
showModalInput ('<i class="fa fa-square-plus pointer"></i> <?= lang('Maintenance_Tool_ImportConfig_noti');?>', '<?= lang('Maintenance_Tool_ImportPastedConfig_noti_text');?>',
|
||||
'<?= lang('Gen_Cancel');?>', '<?= lang('Gen_Okay');?>', 'UploadConfig');
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// Upload Settings Config
|
||||
function UploadConfig()
|
||||
{
|
||||
// alert("aaa")
|
||||
|
||||
appConf = $('#modal-input-textarea').val()
|
||||
// encode for import
|
||||
appConfBase64 = btoa(appConf)
|
||||
|
||||
// import
|
||||
$.post('php/server/query_replace_config.php', { config: appConfBase64 }, function(msg) {
|
||||
console.log(msg);
|
||||
// showMessage(msg);
|
||||
write_notification(`[Maintenance] Settings imported from backup: ${msg}`, 'interrupt');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// Download Settings Config
|
||||
function DownloadConfig()
|
||||
{
|
||||
// Execute
|
||||
openInNewTab("php/server/query_config.php?file=app.conf&download=true")
|
||||
}
|
||||
|
||||
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// Export CSV
|
||||
function ExportCSV()
|
||||
@@ -482,8 +502,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>
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
|
||||
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_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,39 @@
|
||||
onClick,
|
||||
onChange,
|
||||
customParams,
|
||||
customId
|
||||
customId,
|
||||
columns,
|
||||
base64Regex
|
||||
} = 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 +180,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>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
require 'php/templates/header.php';
|
||||
require 'php/templates/notification.php';
|
||||
|
||||
// online / offline badges HTML snippets
|
||||
define('badge_online', '<div class="badge bg-green text-white" style="width: 60px;">Online</div>');
|
||||
@@ -11,21 +12,17 @@
|
||||
|
||||
?>
|
||||
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
<script>
|
||||
// show spinning icon
|
||||
showSpinner()
|
||||
</script>
|
||||
|
||||
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
<div class="content-wrapper">
|
||||
|
||||
<!-- Content header--------------------------------------------------------- -->
|
||||
<section class="content-header">
|
||||
<?php require 'php/templates/notification.php'; ?>
|
||||
<h1 id="pageTitle">
|
||||
<i class="fa fa-network-wired"></i> <?= lang('Network_Title');?>
|
||||
<span class="helpIconSmallTopRight"> <a target="_blank" href="https://github.com/jokob-sk/NetAlertX/blob/main/docs/NETWORK_TREE.md"><i class="fa fa-circle-question"></i></a><span>
|
||||
</h1>
|
||||
</section>
|
||||
|
||||
<span class="networkHelpIcon"> <a target="_blank" href="https://github.com/jokob-sk/NetAlertX/blob/main/docs/NETWORK_TREE.md"><i class="fa fa-circle-question"></i></a></span>
|
||||
|
||||
<div id="networkTree" class="drag"></div>
|
||||
|
||||
@@ -467,9 +464,8 @@
|
||||
require 'php/templates/footer.php';
|
||||
?>
|
||||
|
||||
|
||||
<script src="lib/treeviz/index.js"></script>
|
||||
<script src="lib/treeviz/require.js"></script>
|
||||
<script src="lib/treeviz/bundle.js"></script>
|
||||
<script src="lib/treeviz/bundle.js.map"></script>
|
||||
|
||||
|
||||
<script defer>
|
||||
@@ -484,6 +480,8 @@
|
||||
return;
|
||||
}
|
||||
|
||||
orderTopologyBy = createArray(getSetting("UI_TOPOLOGY_ORDER"))
|
||||
|
||||
devicesListnew = rawData["data"].map(item => {
|
||||
return {
|
||||
"name": item[0],
|
||||
@@ -497,20 +495,36 @@
|
||||
"port": item[18]
|
||||
};
|
||||
}).sort((a, b) => {
|
||||
// First sort by name alphabetically
|
||||
const nameCompare = a.name.localeCompare(b.name);
|
||||
if (nameCompare !== 0) {
|
||||
// Helper to safely parse port into an integer; invalid ports become Infinity for sorting
|
||||
const parsePort = (port) => {
|
||||
const parsed = parseInt(port, 10);
|
||||
return isNaN(parsed) ? Infinity : parsed;
|
||||
};
|
||||
|
||||
switch (orderTopologyBy[0]) {
|
||||
case "Name":
|
||||
// First sort by name alphabetically
|
||||
const nameCompare = a.name.localeCompare(b.name);
|
||||
if (nameCompare !== 0) {
|
||||
return nameCompare;
|
||||
}
|
||||
// If names are the same, sort by port numerically
|
||||
return a.port - b.port;
|
||||
}
|
||||
// If names are the same, sort by port numerically
|
||||
return parsePort(a.port) - parsePort(b.port);
|
||||
|
||||
case "Port":
|
||||
// Sort by port numerically
|
||||
return parsePort(a.port) - parsePort(b.port);
|
||||
|
||||
default:
|
||||
// Default: Sort by rowid (as a fallback)
|
||||
return a.rowid - b.rowid;
|
||||
}
|
||||
});
|
||||
|
||||
setCache('devicesListNew', JSON.stringify(devicesListnew));
|
||||
|
||||
// Init global variable
|
||||
deviceListGlobal = devicesListnew;
|
||||
|
||||
|
||||
// create tree
|
||||
initTree(getHierarchy());
|
||||
@@ -522,381 +536,414 @@
|
||||
|
||||
|
||||
<script defer>
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tree functionality
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tree functionality
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
var leafNodesCount = 0;
|
||||
var visibleNodesCount = 0;
|
||||
var parentNodesCount = 0;
|
||||
var hiddenMacs = []; // hidden children
|
||||
var hiddenChildren = [];
|
||||
var deviceListGlobal = null;
|
||||
|
||||
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Recursively get children nodes and build a tree
|
||||
function getChildren(node, list, path, visited = [])
|
||||
{
|
||||
var children = [];
|
||||
|
||||
// Check for infinite recursion by seeing if the node has been visited before
|
||||
if (visited.includes(node.mac.toLowerCase())) {
|
||||
console.error("Infinite recursion detected at node:", node.mac);
|
||||
write_notification("[ERROR] ⚠ Infinite recursion detected. You probably have assigned the Internet node to another children node or to itself. Please open a new issue on GitHub and describe how you did it.", 'interrupt')
|
||||
return { error: "Infinite recursion detected", node: node.mac };
|
||||
}
|
||||
|
||||
// Add current node to visited list
|
||||
visited.push(node.mac.toLowerCase());
|
||||
|
||||
// Loop through all items to find children of the current node
|
||||
for (var i in list) {
|
||||
if (list[i].parentMac.toLowerCase() == node.mac.toLowerCase() && !hiddenMacs.includes(list[i].parentMac)) {
|
||||
|
||||
visibleNodesCount++;
|
||||
|
||||
// Process children recursively, passing a copy of the visited list
|
||||
children.push(getChildren(list[i], list, path + ((path == "") ? "" : '|') + list[i].parentMac, visited));
|
||||
}
|
||||
}
|
||||
|
||||
// Track leaf and parent node counts
|
||||
if (children.length == 0) {
|
||||
leafNodesCount++;
|
||||
} else {
|
||||
parentNodesCount++;
|
||||
}
|
||||
|
||||
return {
|
||||
name: node.name,
|
||||
path: path,
|
||||
mac: node.mac,
|
||||
port: node.port,
|
||||
id: node.mac,
|
||||
parentMac: node.parentMac,
|
||||
icon: node.icon,
|
||||
type: node.type,
|
||||
status: node.status,
|
||||
hasChildren: children.length > 0 || hiddenMacs.includes(node.mac),
|
||||
hiddenChildren: hiddenMacs.includes(node.mac),
|
||||
qty: children.length,
|
||||
children: children
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
function getHierarchy()
|
||||
{
|
||||
for(i in deviceListGlobal)
|
||||
{
|
||||
if(deviceListGlobal[i].mac == 'Internet')
|
||||
{
|
||||
return (getChildren(deviceListGlobal[i], deviceListGlobal, ''))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
function getFlatData() {
|
||||
var result = [];
|
||||
var leafNodesCount = 0;
|
||||
var visibleNodesCount = 0;
|
||||
var parentNodesCount = 0;
|
||||
var hiddenMacs = []; // hidden children
|
||||
var hiddenChildren = [];
|
||||
var deviceListGlobal = null;
|
||||
var visibleNodesCount = 0;
|
||||
|
||||
for (let node of deviceListGlobal) {
|
||||
let path = "";
|
||||
let childrenCount = 0;
|
||||
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Recursively get children nodes and build a tree
|
||||
function getChildren(node, list, path, visited = [])
|
||||
{
|
||||
var children = [];
|
||||
|
||||
// Check for infinite recursion by seeing if the node has been visited before
|
||||
if (visited.includes(node.mac.toLowerCase())) {
|
||||
console.error("Infinite recursion detected at node:", node.mac);
|
||||
write_notification("[ERROR] ⚠ Infinite recursion detected. You probably have assigned the Internet node to another children node or to itself. Please open a new issue on GitHub and describe how you did it.", 'interrupt')
|
||||
return { error: "Infinite recursion detected", node: node.mac };
|
||||
}
|
||||
|
||||
// Add current node to visited list
|
||||
visited.push(node.mac.toLowerCase());
|
||||
|
||||
// Loop through all items to find children of the current node
|
||||
for (var i in list) {
|
||||
if (list[i].parentMac.toLowerCase() == node.mac.toLowerCase() && !hiddenMacs.includes(list[i].parentMac)) {
|
||||
|
||||
visibleNodesCount++;
|
||||
|
||||
// Process children recursively, passing a copy of the visited list
|
||||
children.push(getChildren(list[i], list, path + ((path == "") ? "" : '|') + list[i].parentMac, visited));
|
||||
}
|
||||
}
|
||||
|
||||
// Track leaf and parent node counts
|
||||
if (children.length == 0) {
|
||||
leafNodesCount++;
|
||||
} else {
|
||||
parentNodesCount++;
|
||||
}
|
||||
|
||||
return {
|
||||
name: node.name,
|
||||
path: path,
|
||||
mac: node.mac,
|
||||
port: node.port,
|
||||
id: node.mac,
|
||||
parentMac: node.parentMac,
|
||||
icon: node.icon,
|
||||
type: node.type,
|
||||
status: node.status,
|
||||
hasChildren: children.length > 0 || hiddenMacs.includes(node.mac),
|
||||
hiddenChildren: hiddenMacs.includes(node.mac),
|
||||
qty: children.length,
|
||||
children: children
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function getHierarchy()
|
||||
{
|
||||
for(i in deviceListGlobal)
|
||||
{
|
||||
if(deviceListGlobal[i].mac == 'Internet')
|
||||
{
|
||||
return (getChildren(deviceListGlobal[i], deviceListGlobal, ''))
|
||||
break;
|
||||
}
|
||||
// count children of this node
|
||||
for (let nodeTmp of deviceListGlobal) {
|
||||
if (nodeTmp.parentMac === node.mac) {
|
||||
childrenCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
function toggleSubTree(parentMac, treePath)
|
||||
{
|
||||
treePath = treePath.split('|')
|
||||
|
||||
if(!hiddenMacs.includes(parentMac))
|
||||
{
|
||||
hiddenMacs.push(parentMac)
|
||||
}
|
||||
else
|
||||
{
|
||||
removeItemFromArray(hiddenMacs, parentMac)
|
||||
}
|
||||
|
||||
// updatedTree = myHierarchy;
|
||||
updatedTree = getHierarchy()
|
||||
|
||||
myTree.refresh(updatedTree);
|
||||
|
||||
// re-attach any onclick events
|
||||
attachTreeEvents();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
function attachTreeEvents()
|
||||
{
|
||||
// toggle subtree functionality
|
||||
$("div[data-mytreemac]").each(function(){
|
||||
$(this).attr('onclick', 'toggleSubTree("'+$(this).attr('data-mytreemac')+'","'+ $(this).attr('data-mytreepath')+'")')
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Handle network node click - select correct tab in the bottom table
|
||||
function handleNodeClick(event)
|
||||
{
|
||||
// console.log(event.target.offsetParent.offsetParent)
|
||||
|
||||
const targetTabMAC = $(event.target.offsetParent.offsetParent).attr("data-mytreemacmain");
|
||||
|
||||
var targetTab = $(`a[data-mytabmac="${targetTabMAC}"]`);
|
||||
|
||||
// Simulate a click event on the target tab
|
||||
targetTab.click();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
var myTree;
|
||||
var visibleTreeArea = $(window).height()-155;
|
||||
var nodeWidth = 160;
|
||||
var emSize;
|
||||
var nodeHeight;
|
||||
var sizeCoefficient = 1
|
||||
|
||||
function initTree(myHierarchy)
|
||||
{
|
||||
console.log(myHierarchy)
|
||||
|
||||
// calculate the drawing area based on teh tree width and available screen size
|
||||
var treeAreaHeight = visibleTreeArea > 800 ? 800 : visibleTreeArea;
|
||||
let screenWidth = $('.content-header').width();
|
||||
let treeWidth = (nodeWidth + 20) * parentNodesCount;
|
||||
let treeAreaWidth = screenWidth < treeWidth ? treeWidth : screenWidth;
|
||||
|
||||
// init the drawing area size
|
||||
$("#networkTree").attr('style', `height:${treeAreaHeight}px; width:${treeAreaWidth}px`)
|
||||
|
||||
if(myHierarchy.type == "")
|
||||
{
|
||||
showModalOk(getString('Network_Configuration_Error'), getString('Network_Root_Not_Configured'))
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// calculate the font size of the leaf nodes to fit everything into the tree area
|
||||
leafNodesCount == 0 ? 1 : leafNodesCount;
|
||||
emSize = ((treeAreaHeight/(25*leafNodesCount)).toFixed(2));
|
||||
emSize = emSize > 1 ? 1 : emSize;
|
||||
|
||||
// nodeHeight = ((emSize*100*0.30).toFixed(0))
|
||||
nodeHeight = ((emSize*100*0.30).toFixed(0))
|
||||
|
||||
console.log(Treeviz);
|
||||
|
||||
myTree = Treeviz.create({
|
||||
htmlId: "networkTree",
|
||||
renderNode: nodeData => {
|
||||
var fontSize = "font-size:"+emSize+"em;";
|
||||
|
||||
(!emptyArr.includes(nodeData.data.port )) ? port = nodeData.data.port : port = "";
|
||||
|
||||
(port == "" || port == 0 || port == 'None' ) ? portBckgIcon = `<i class="fa fa-wifi"></i>` : portBckgIcon = `<i class="fa fa-ethernet"></i>`;
|
||||
|
||||
portHtml = (port == "" || port == 0 || port == 'None' ) ? "" : port
|
||||
|
||||
// Build HTML for individual nodes in the network diagram
|
||||
deviceIcon = (!emptyArr.includes(nodeData.data.icon )) ?
|
||||
`<div class="netIcon">
|
||||
${atob(nodeData.data.icon)}
|
||||
</div>` : "";
|
||||
devicePort = `<div class="netPort"
|
||||
style="width:${emSize*sizeCoefficient}em;height:${emSize*sizeCoefficient}em">
|
||||
${portHtml}</div>
|
||||
<div class="portBckgIcon"
|
||||
style="margin-left:-${emSize*sizeCoefficient}em;">
|
||||
${portBckgIcon}
|
||||
</div>`;
|
||||
collapseExpandIcon = nodeData.data.hiddenChildren ?
|
||||
"square-plus" : "square-minus";
|
||||
|
||||
// generate +/- icon if node has children nodes
|
||||
collapseExpandHtml = nodeData.data.hasChildren ?
|
||||
`<div class="netCollapse"
|
||||
style="font-size:${emSize*sizeCoefficient}em;top:${1/2*emSize*sizeCoefficient}em"
|
||||
data-mytreepath="${nodeData.data.path}"
|
||||
data-mytreemac="${nodeData.data.mac}">
|
||||
<i class="fa fa-${collapseExpandIcon} pointer"></i>
|
||||
</div>` : "";
|
||||
|
||||
selectedNodeMac = $(".nav-tabs-custom .active a").attr('data-mytabmac')
|
||||
|
||||
highlightedCss = nodeData.data.mac == selectedNodeMac ?
|
||||
" highlightedNode" : "";
|
||||
|
||||
// css indicating online/offline status
|
||||
statusCss = ` netStatus-${nodeData.data.status}`;
|
||||
|
||||
return result = `<div class="box ${nodeData.data.hasChildren ? "pointer":""} ${statusCss} ${highlightedCss}"
|
||||
data-mytreemacmain="${nodeData.data.mac}"
|
||||
style="height:${nodeData.settings.nodeHeight}px;${fontSize}"
|
||||
>
|
||||
<div class="netNodeText">
|
||||
<strong>${devicePort} ${deviceIcon}
|
||||
<span class="spanNetworkTree anonymizeDev" >${nodeData.data.name}</span>
|
||||
</strong>
|
||||
${collapseExpandHtml}
|
||||
</div>
|
||||
</div>`;
|
||||
},
|
||||
|
||||
onNodeClick: nodeData => {
|
||||
console.log(this)
|
||||
},
|
||||
mainAxisNodeSpacing: 'auto',
|
||||
// mainAxisNodeSpacing: 3,
|
||||
secondaryAxisNodeSpacing: 0.3,
|
||||
nodeHeight: nodeHeight.toString(),
|
||||
marginTop: '5',
|
||||
hasZoom: true,
|
||||
hasPan: true,
|
||||
// marginLeft: '15',
|
||||
idKey: "id",
|
||||
hasFlatData: false,
|
||||
linkWidth: (nodeData) => 3,
|
||||
linkColor: (nodeData) => "#ffcc80",
|
||||
onNodeClick: (nodeData) => handleNodeClick(nodeData),
|
||||
relationnalField: "children",
|
||||
});
|
||||
|
||||
|
||||
|
||||
myTree.refresh(myHierarchy);
|
||||
|
||||
// hide spinning icon
|
||||
hideSpinner()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tabs functionality
|
||||
// ---------------------------------------------------------------------------
|
||||
// Register events on tab change
|
||||
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
|
||||
initButtons()
|
||||
|
||||
});
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
function initTab()
|
||||
{
|
||||
key = "activeNetworkTab"
|
||||
|
||||
// default selection
|
||||
selectedTab = "Internet_id"
|
||||
|
||||
// the #target from the url
|
||||
target = getQueryString('mac')
|
||||
|
||||
// update cookie if target specified
|
||||
if(target != "")
|
||||
{
|
||||
setCache(key, target.replaceAll(":","_")+'_id') // _id is added so it doesn't conflict with AdminLTE tab behavior
|
||||
}
|
||||
|
||||
// get the tab id from the cookie (already overridden by the target)
|
||||
if(!emptyArr.includes(getCache(key)))
|
||||
{
|
||||
selectedTab = getCache(key);
|
||||
}
|
||||
|
||||
// Activate panel
|
||||
$('.nav-tabs a[id='+ selectedTab +']').tab('show');
|
||||
|
||||
// When changed save new current tab
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
setCache(key, $(e.target).attr('id'))
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
function initDeviceNamesFromMACs()
|
||||
{
|
||||
$('.mac-to-name').each(function() {
|
||||
var dataMacValue = $(this).attr('my-data-mac');
|
||||
|
||||
if(dataMacValue =="" )
|
||||
{
|
||||
$(this).html(getString("Network_Root"))
|
||||
}
|
||||
else{
|
||||
$(this).html(getNameByMacAddress(dataMacValue));
|
||||
}
|
||||
|
||||
// store parent and leaf node count
|
||||
if (childrenCount === 0) {
|
||||
leafNodesCount++;
|
||||
} else {
|
||||
parentNodesCount++;
|
||||
}
|
||||
|
||||
if (!hiddenMacs.includes(node.parentMac)) {
|
||||
if (!((node.parentMac == "") && node.mac != "Internet")) { // skip leaf nodes without father that are not the root
|
||||
visibleNodesCount++;
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
function initButtons()
|
||||
{
|
||||
|
||||
var currentNodeMac = $(".tab-content .active td[data-mynodemac]").attr('data-mynodemac');
|
||||
|
||||
// change highlighted node in the tree
|
||||
selNode = $("#networkTree .highlightedNode")[0]
|
||||
|
||||
// console.log(selNode)
|
||||
|
||||
if(selNode)
|
||||
{
|
||||
$(selNode).attr('class', $(selNode).attr('class').replace('highlightedNode'))
|
||||
result.push({
|
||||
name: node.name,
|
||||
path: path,
|
||||
mac: node.mac, // Replacing "mac" with "id"
|
||||
parentMac: node.mac == "Internet" ? "" : node.parentMac, // Replacing "parentMac" with "father"
|
||||
port: node.port,
|
||||
icon: node.icon,
|
||||
type: node.type,
|
||||
status: node.status,
|
||||
hasChildren: childrenCount > 0 || hiddenMacs.includes(node.mac),
|
||||
hiddenChildren: hiddenMacs.includes(node.mac),
|
||||
qty: childrenCount,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
newSelNode = $("#networkTree div[data-mytreemacmain='"+currentNodeMac+"']")[0]
|
||||
|
||||
$(newSelNode).attr('class', $(newSelNode).attr('class') + ' highlightedNode')
|
||||
|
||||
|
||||
// init the Assign buttons
|
||||
$('#unassignedDevices button[data-myleafmac]').each(function(){
|
||||
$(this).attr('onclick', `updateLeaf("${$(this).attr('data-myleafmac')}","${currentNodeMac}")`)
|
||||
});
|
||||
|
||||
// init Unassign buttons
|
||||
$('#assignedDevices button[data-myleafmac]').each(function(){
|
||||
$(this).attr('onclick', `updateLeaf("${$(this).attr('data-myleafmac')}","")`)
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
function updateLeaf(leafMac,nodeMac)
|
||||
return result;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
function toggleSubTree(parentMac, treePath)
|
||||
{
|
||||
treePath = treePath.split('|')
|
||||
|
||||
if(!hiddenMacs.includes(parentMac))
|
||||
{
|
||||
console.log(leafMac) // child
|
||||
console.log(nodeMac) // parent
|
||||
console.log(nodeMac != "") // parent
|
||||
|
||||
// prevent the assignment of the Internet root node avoiding recursion when generating the network tree topology
|
||||
if(leafMac.toLowerCase().includes('internet') && nodeMac != "")
|
||||
{
|
||||
showMessage(getString('Network_Cant_Assign'))
|
||||
}
|
||||
else{
|
||||
saveData('updateNetworkLeaf', leafMac, nodeMac);
|
||||
setTimeout("location.reload();", 500); // refresh page
|
||||
}
|
||||
hiddenMacs.push(parentMac)
|
||||
}
|
||||
else
|
||||
{
|
||||
removeItemFromArray(hiddenMacs, parentMac)
|
||||
}
|
||||
|
||||
// show spinning icon
|
||||
showSpinner()
|
||||
updatedTree = getHierarchy()
|
||||
myTree.refresh(updatedTree);
|
||||
|
||||
// init device names where macs are used
|
||||
initDeviceNamesFromMACs();
|
||||
// re-attach any onclick events
|
||||
attachTreeEvents();
|
||||
}
|
||||
|
||||
// init selected (first) tab
|
||||
initTab();
|
||||
// ---------------------------------------------------------------------------
|
||||
function attachTreeEvents()
|
||||
{
|
||||
// toggle subtree functionality
|
||||
$("div[data-mytreemac]").each(function(){
|
||||
$(this).attr('onclick', 'toggleSubTree("'+$(this).attr('data-mytreemac')+'","'+ $(this).attr('data-mytreepath')+'")')
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Handle network node click - select correct tab in the bottom table
|
||||
function handleNodeClick(nodeData)
|
||||
{
|
||||
const targetTabMAC = nodeData.data.mac;
|
||||
|
||||
var targetTab = $(`a[data-mytabmac="${targetTabMAC}"]`);
|
||||
|
||||
// Simulate a click event on the target tab
|
||||
targetTab.click();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
var myTree;
|
||||
var visibleTreeArea = $(window).height()-155;
|
||||
var nodeWidth = 120;
|
||||
var emSize;
|
||||
var nodeHeight;
|
||||
var sizeCoefficient = 1.4
|
||||
|
||||
function initTree(myHierarchy)
|
||||
{
|
||||
// calculate the drawing area based on teh tree width and available screen size
|
||||
var treeAreaHeight = visibleTreeArea > 800 ? 800 : visibleTreeArea;
|
||||
let screenWidth = $('.content-header').width();
|
||||
let treeWidth = (nodeWidth + 20) * parentNodesCount;
|
||||
let treeAreaWidth = screenWidth < treeWidth ? treeWidth : screenWidth;
|
||||
|
||||
// init the drawing area size
|
||||
$("#networkTree").attr('style', `height:${treeAreaHeight}px; width:${treeAreaWidth}px`)
|
||||
|
||||
if(myHierarchy.type == "")
|
||||
{
|
||||
showModalOk(getString('Network_Configuration_Error'), getString('Network_Root_Not_Configured'))
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// calculate the font size of the leaf nodes to fit everything into the tree area
|
||||
leafNodesCount == 0 ? 1 : leafNodesCount;
|
||||
emSize = ((treeAreaHeight/(25*leafNodesCount)).toFixed(2));
|
||||
emSize = emSize > 1 ? 1 : emSize;
|
||||
|
||||
// nodeHeight = ((emSize*100*0.30).toFixed(0))
|
||||
nodeHeight = ((emSize*100*0.30).toFixed(0))
|
||||
|
||||
console.log(Treeviz);
|
||||
|
||||
myTree = Treeviz.create({
|
||||
htmlId: "networkTree",
|
||||
renderNode: nodeData => {
|
||||
|
||||
var fontSize = "font-size:"+emSize+"em;";
|
||||
|
||||
(!emptyArr.includes(nodeData.data.port )) ? port = nodeData.data.port : port = "";
|
||||
|
||||
(port == "" || port == 0 || port == 'None' ) ? portBckgIcon = `<i class="fa fa-wifi"></i>` : portBckgIcon = `<i class="fa fa-ethernet"></i>`;
|
||||
|
||||
portHtml = (port == "" || port == 0 || port == 'None' ) ? "" : port;
|
||||
|
||||
// Build HTML for individual nodes in the network diagram
|
||||
deviceIcon = (!emptyArr.includes(nodeData.data.icon )) ?
|
||||
`<div class="netIcon">
|
||||
${atob(nodeData.data.icon)}
|
||||
</div>` : "";
|
||||
devicePort = `<div class="netPort"
|
||||
style="width:${emSize*sizeCoefficient}em;height:${emSize*sizeCoefficient}em">
|
||||
${portHtml}</div>
|
||||
<div class="portBckgIcon"
|
||||
style="margin-left:-${emSize*sizeCoefficient}em;">
|
||||
${portBckgIcon}
|
||||
</div>`;
|
||||
collapseExpandIcon = nodeData.data.hiddenChildren ?
|
||||
"square-plus" : "square-minus";
|
||||
|
||||
// generate +/- icon if node has children nodes
|
||||
collapseExpandHtml = nodeData.data.hasChildren ?
|
||||
`<div class="netCollapse"
|
||||
style="font-size:${emSize*sizeCoefficient}em;top:${emSize/6}em"
|
||||
data-mytreepath="${nodeData.data.path}"
|
||||
data-mytreemac="${nodeData.data.mac}">
|
||||
<i class="fa fa-${collapseExpandIcon} pointer"></i>
|
||||
</div>` : "";
|
||||
|
||||
selectedNodeMac = $(".nav-tabs-custom .active a").attr('data-mytabmac')
|
||||
|
||||
highlightedCss = nodeData.data.mac == selectedNodeMac ?
|
||||
" highlightedNode" : "";
|
||||
|
||||
// css indicating online/offline status
|
||||
statusCss = ` netStatus-${nodeData.data.status}`;
|
||||
|
||||
return result = `<div class="box ${nodeData.data.hasChildren ? "pointer":""} ${statusCss} ${highlightedCss}"
|
||||
data-mytreemacmain="${nodeData.data.mac}"
|
||||
style="height:${nodeData.settings.nodeHeight}px;${fontSize}"
|
||||
>
|
||||
<div class="netNodeText">
|
||||
<strong>${devicePort} ${deviceIcon}
|
||||
<span class="spanNetworkTree anonymizeDev" >${nodeData.data.name}</span>
|
||||
</strong>
|
||||
${collapseExpandHtml}
|
||||
</div>
|
||||
</div>`;
|
||||
},
|
||||
mainAxisNodeSpacing: 'auto',
|
||||
secondaryAxisNodeSpacing: 0.3,
|
||||
nodeHeight: nodeHeight.toString(),
|
||||
marginTop: '5',
|
||||
isHorizontal : true,
|
||||
hasZoom: true,
|
||||
hasPan: true,
|
||||
marginLeft: '15',
|
||||
idKey: "mac",
|
||||
hasFlatData: false,
|
||||
relationnalField: "children",
|
||||
linkWidth: (nodeData) => 3,
|
||||
linkColor: (nodeData) => "#ffcc80",
|
||||
onNodeClick: (nodeData) => handleNodeClick(nodeData),
|
||||
});
|
||||
|
||||
console.log(deviceListGlobal);
|
||||
myTree.refresh(myHierarchy);
|
||||
|
||||
// hide spinning icon
|
||||
hideSpinner()
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tabs functionality
|
||||
// ---------------------------------------------------------------------------
|
||||
// Register events on tab change
|
||||
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
|
||||
// init Assign/Unassign buttons
|
||||
initButtons()
|
||||
|
||||
});
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
function initTab()
|
||||
{
|
||||
key = "activeNetworkTab"
|
||||
|
||||
// default selection
|
||||
selectedTab = "Internet_id"
|
||||
|
||||
// the #target from the url
|
||||
target = getQueryString('mac')
|
||||
|
||||
// update cookie if target specified
|
||||
if(target != "")
|
||||
{
|
||||
setCache(key, target.replaceAll(":","_")+'_id') // _id is added so it doesn't conflict with AdminLTE tab behavior
|
||||
}
|
||||
|
||||
// get the tab id from the cookie (already overridden by the target)
|
||||
if(!emptyArr.includes(getCache(key)))
|
||||
{
|
||||
selectedTab = getCache(key);
|
||||
}
|
||||
|
||||
// Activate panel
|
||||
$('.nav-tabs a[id='+ selectedTab +']').tab('show');
|
||||
|
||||
// When changed save new current tab
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
setCache(key, $(e.target).attr('id'))
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
function initDeviceNamesFromMACs()
|
||||
{
|
||||
$('.mac-to-name').each(function() {
|
||||
var dataMacValue = $(this).attr('my-data-mac');
|
||||
|
||||
if(dataMacValue =="" )
|
||||
{
|
||||
$(this).html(getString("Network_Root"))
|
||||
}
|
||||
else{
|
||||
$(this).html(getNameByMacAddress(dataMacValue));
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
function initButtons()
|
||||
{
|
||||
|
||||
var currentNodeMac = $(".tab-content .active td[data-mynodemac]").attr('data-mynodemac');
|
||||
|
||||
// change highlighted node in the tree
|
||||
selNode = $("#networkTree .highlightedNode")[0]
|
||||
|
||||
// console.log(selNode)
|
||||
|
||||
if(selNode)
|
||||
{
|
||||
$(selNode).attr('class', $(selNode).attr('class').replace('highlightedNode'))
|
||||
}
|
||||
|
||||
newSelNode = $("#networkTree div[data-mytreemacmain='"+currentNodeMac+"']")[0]
|
||||
|
||||
$(newSelNode).attr('class', $(newSelNode).attr('class') + ' highlightedNode')
|
||||
|
||||
|
||||
// init the Assign buttons
|
||||
$('#unassignedDevices button[data-myleafmac]').each(function(){
|
||||
$(this).attr('onclick', `updateLeaf("${$(this).attr('data-myleafmac')}","${currentNodeMac}")`)
|
||||
});
|
||||
|
||||
// init Unassign buttons
|
||||
$('#assignedDevices button[data-myleafmac]').each(function(){
|
||||
$(this).attr('onclick', `updateLeaf("${$(this).attr('data-myleafmac')}","")`)
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
function updateLeaf(leafMac,nodeMac)
|
||||
{
|
||||
console.log(leafMac) // child
|
||||
console.log(nodeMac) // parent
|
||||
console.log(nodeMac != "") // parent
|
||||
|
||||
// prevent the assignment of the Internet root node avoiding recursion when generating the network tree topology
|
||||
if(leafMac.toLowerCase().includes('internet') && nodeMac != "")
|
||||
{
|
||||
showMessage(getString('Network_Cant_Assign'))
|
||||
}
|
||||
else{
|
||||
saveData('updateNetworkLeaf', leafMac, nodeMac);
|
||||
setTimeout("location.reload();", 500); // refresh page
|
||||
}
|
||||
}
|
||||
|
||||
// init device names where macs are used
|
||||
initDeviceNamesFromMACs();
|
||||
|
||||
// init selected (first) tab
|
||||
initTab();
|
||||
|
||||
// init Assign/Unassign buttons
|
||||
initButtons()
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
52
front/php/components/devices_filters.php
Executable file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Check if authenticated
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/server/db.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/language/lang.php';
|
||||
|
||||
// Function to render a filter dropdown
|
||||
function renderFilterDropdown($headerKey, $columnName, $values) {
|
||||
// Generate dropdown options
|
||||
$optionsHtml = '<option value="" selected>All</option>'; // Default "All" option
|
||||
foreach ($values as $value) {
|
||||
$escapedValue = htmlspecialchars($value);
|
||||
$optionsHtml .= '<option value="' . $escapedValue . '">' . $escapedValue . '</option>';
|
||||
}
|
||||
|
||||
// Generate the dropdown HTML
|
||||
return '
|
||||
<div class="filter-group input-group">
|
||||
<label for="filter_' . htmlspecialchars($columnName) . '">' . lang($headerKey) . '</label>
|
||||
<select id="filter_' . htmlspecialchars($columnName) . '" class="filter-dropdown" data-column="' . htmlspecialchars($columnName) . '">
|
||||
' . $optionsHtml . '
|
||||
</select>
|
||||
</div>';
|
||||
}
|
||||
|
||||
// Get filterObject from POST data
|
||||
$filterObject = isset($_POST['filterObject']) ? json_decode($_POST['filterObject'], true) : [];
|
||||
|
||||
// Validate filterObject structure
|
||||
if (!isset($filterObject['filters']) || !is_array($filterObject['filters'])) {
|
||||
echo '<p class="error">Invalid filter data provided.</p>';
|
||||
exit();
|
||||
}
|
||||
|
||||
// Generate HTML for each filter in the filterObject
|
||||
$html = '';
|
||||
foreach ($filterObject['filters'] as $filter) {
|
||||
if (isset($filter['column'], $filter['headerKey'], $filter['options'])) {
|
||||
$html .= renderFilterDropdown($filter['headerKey'], $filter['column'], $filter['options']);
|
||||
} else {
|
||||
// Skip invalid entries
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Output the generated HTML
|
||||
echo $html;
|
||||
exit();
|
||||
|
||||
?>
|
||||
@@ -10,8 +10,12 @@
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// DB File Path
|
||||
$DBFILE = dirname(__FILE__).'/../../../db/app.db';
|
||||
$DBFILE_LOCKED_FILE = dirname(__FILE__).'/../../../log/db_is_locked.log';
|
||||
// $DBFILE = dirname(__FILE__).'/../../../db/app.db';
|
||||
// $DBFILE_LOCKED_FILE = dirname(__FILE__).'/../../../log/db_is_locked.log';
|
||||
$scriptDir = realpath(dirname(__FILE__)); // Resolves symlinks to the actual physical path
|
||||
$DBFILE = $scriptDir . '/../../../db/app.db';
|
||||
$DBFILE_LOCKED_FILE = $scriptDir . '/../../../log/db_is_locked.log';
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// check if authenticated
|
||||
@@ -32,6 +36,14 @@ function SQLite3_connect($trytoreconnect = true, $retryCount = 0) {
|
||||
global $db_locked;
|
||||
$db_locked = false;
|
||||
|
||||
if (!file_exists($DBFILE)) {
|
||||
die("Database file not found: $DBFILE");
|
||||
}
|
||||
if (!file_exists(dirname($DBFILE_LOCKED_FILE))) {
|
||||
die("Log directory not found: " . dirname($DBFILE_LOCKED_FILE));
|
||||
}
|
||||
|
||||
|
||||
// Write unlock status to the locked file
|
||||
file_put_contents($DBFILE_LOCKED_FILE, '0');
|
||||
|
||||
@@ -70,7 +82,7 @@ class CustomDatabaseWrapper {
|
||||
private $maxRetries;
|
||||
private $retryDelay;
|
||||
|
||||
public function __construct($filename, $flags = SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, $maxRetries = 10, $retryDelay = 1000, $encryptionKey = null) {
|
||||
public function __construct($filename, $flags = SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, $maxRetries = 3, $retryDelay = 1000, $encryptionKey = null) {
|
||||
$this->sqlite = new SQLite3($filename, $flags, $encryptionKey);
|
||||
$this->maxRetries = $maxRetries;
|
||||
$this->retryDelay = $retryDelay;
|
||||
@@ -116,7 +128,7 @@ class CustomDatabaseWrapper {
|
||||
file_put_contents($DBFILE_LOCKED_FILE, '0');
|
||||
|
||||
$message = 'Error executing query (attempts: ' . $attempts . '), query: ' . $query;
|
||||
write_notification($message);
|
||||
// write_notification($message);
|
||||
error_log("Query failed after {$this->maxRetries} attempts: " . $this->sqlite->lastErrorMsg());
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
|
||||
}
|
||||
|
||||
if (isset ($_REQUEST['rawSql'])) {
|
||||
$rawSql = $_REQUEST['rawSql'];
|
||||
$rawSql = urldecode(base64_decode($_REQUEST['rawSql']));
|
||||
}
|
||||
|
||||
if (isset ($_REQUEST['dbtable'])) {
|
||||
|
||||
@@ -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,6 +106,7 @@ function getServerDeviceData() {
|
||||
"devSSID" => "",
|
||||
"devSyncHubNode" => "",
|
||||
"devSourcePlugin" => "",
|
||||
"devCustomProps" => "",
|
||||
"devStatus" => "Unknown",
|
||||
"devIsRandomMAC" => false,
|
||||
"devSessions" => 0,
|
||||
@@ -190,129 +192,133 @@ 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,
|
||||
devSourcePlugin
|
||||
) 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',
|
||||
'DUMMY'
|
||||
)";
|
||||
}
|
||||
|
||||
// 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 +395,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
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
53
front/php/server/query_config.php
Executable file
@@ -0,0 +1,53 @@
|
||||
<?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 parameters ?file=settings_table.json&download=true
|
||||
$file = isset($_GET['file']) ? $_GET['file'] : null;
|
||||
$download = isset($_GET['download']) ? $_GET['download'] === 'true' : false;
|
||||
|
||||
// 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)) {
|
||||
// Handle download behavior
|
||||
if ($download) {
|
||||
// Force file download
|
||||
header('Content-Description: File Transfer');
|
||||
header('Content-Type: application/octet-stream');
|
||||
header('Content-Disposition: attachment; filename="' . basename($filePath) . '"');
|
||||
header('Expires: 0');
|
||||
header('Cache-Control: must-revalidate');
|
||||
header('Pragma: public');
|
||||
header('Content-Length: ' . filesize($filePath));
|
||||
readfile($filePath);
|
||||
exit;
|
||||
} else {
|
||||
// Display file content
|
||||
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.";
|
||||
}
|
||||
?>
|
||||