Compare commits
391 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ee652cfda | ||
|
|
abaffa4042 | ||
|
|
ad4b5d7c64 | ||
|
|
3b38476c5a | ||
|
|
a42f6a20e4 | ||
|
|
da2afb2fb7 | ||
|
|
dda0d6a898 | ||
|
|
36068aaf77 | ||
|
|
3cb65fa4ec | ||
|
|
26cc757f75 | ||
|
|
2337f96685 | ||
|
|
82ec3b239e | ||
|
|
aa72b0216d | ||
|
|
b002bc34ac | ||
|
|
a84f0d4faf | ||
|
|
a9715cb087 | ||
|
|
827b0d15d1 | ||
|
|
4b4b2f914f | ||
|
|
bf679cdc5d | ||
|
|
4c430c6d5d | ||
|
|
905279aabe | ||
|
|
d92a5da029 | ||
|
|
a3a27fc27a | ||
|
|
0d6bc71d2b | ||
|
|
41397be1bd | ||
|
|
8fbcb07267 | ||
|
|
3c18540c8c | ||
|
|
ab9c940d01 | ||
|
|
7e573282d0 | ||
|
|
d08368e4f5 | ||
|
|
2c1718bb0e | ||
|
|
5a0bf03b81 | ||
|
|
6978c9446c | ||
|
|
d3fd160cf3 | ||
|
|
c3421c8699 | ||
|
|
0a3ebc931b | ||
|
|
83c593a1e2 | ||
|
|
60c812327a | ||
|
|
d27ba5c046 | ||
|
|
120a88d12d | ||
|
|
054df2ed79 | ||
|
|
94240f61ca | ||
|
|
9c77a25d9a | ||
|
|
7819f2774c | ||
|
|
a07bdd7469 | ||
|
|
68c3712539 | ||
|
|
be5fc6dccb | ||
|
|
414110e575 | ||
|
|
bd641273ff | ||
|
|
404a97fb89 | ||
|
|
e3cab610ec | ||
|
|
cd87f6db0d | ||
|
|
dc015077e4 | ||
|
|
f778932fd6 | ||
|
|
c284d27d31 | ||
|
|
acac02a672 | ||
|
|
e8d3d5c2a9 | ||
|
|
d4b898358f | ||
|
|
bd5a9b4f72 | ||
|
|
5f1d2ee26c | ||
|
|
9175a5a45f | ||
|
|
2af60c034f | ||
|
|
c503aeaf00 | ||
|
|
3f9922b7df | ||
|
|
91fc1da896 | ||
|
|
3d2e4f6343 | ||
|
|
39d44689de | ||
|
|
2547e6e805 | ||
|
|
3c5a76b512 | ||
|
|
dc76ba2fda | ||
|
|
386ee473bd | ||
|
|
feebe96fec | ||
|
|
86f83eff5b | ||
|
|
54fa2743f9 | ||
|
|
2475133405 | ||
|
|
cdb3dee8ed | ||
|
|
e667abf6fb | ||
|
|
d5b2e2f0ee | ||
|
|
cd7cbcc4c8 | ||
|
|
a055c2450a | ||
|
|
170a3c0ae1 | ||
|
|
6fe865e115 | ||
|
|
1c1c5bd32b | ||
|
|
d40ad8bd09 | ||
|
|
2b70e1c2e5 | ||
|
|
da8ea98c28 | ||
|
|
caac65f4f9 | ||
|
|
a92d66c981 | ||
|
|
5fd709ed35 | ||
|
|
29f120e66b | ||
|
|
74f5933627 | ||
|
|
56a93ee75b | ||
|
|
3a8634844f | ||
|
|
26d546f6ec | ||
|
|
0265c41612 | ||
|
|
a53b410713 | ||
|
|
3035b5b6b2 | ||
|
|
266d7c25da | ||
|
|
77b25a9740 | ||
|
|
618bafa514 | ||
|
|
415f589716 | ||
|
|
54c7c820b8 | ||
|
|
89864f7070 | ||
|
|
b4916cd3b6 | ||
|
|
97567ad472 | ||
|
|
b00dbd560f | ||
|
|
c41fbab8ee | ||
|
|
771db9fa0e | ||
|
|
dd6ccf830c | ||
|
|
b0e079aeb2 | ||
|
|
d1d49572e2 | ||
|
|
774078df9c | ||
|
|
8c708f2c96 | ||
|
|
3c68b0151d | ||
|
|
6cb252c0ed | ||
|
|
11f2a74b5d | ||
|
|
9bba3c9e50 | ||
|
|
b3d71a5fec | ||
|
|
a111ed929b | ||
|
|
21dd85f62f | ||
|
|
b08bca5ce4 | ||
|
|
dff6cba2d8 | ||
|
|
10a0921e35 | ||
|
|
70443942ff | ||
|
|
7d26966250 | ||
|
|
9ada27cf7e | ||
|
|
752322bbad | ||
|
|
0444e338ec | ||
|
|
a669abd47e | ||
|
|
4e46fcb9e6 | ||
|
|
31d7d0c143 | ||
|
|
b470b985e9 | ||
|
|
c90c6b5c90 | ||
|
|
26f0d0ac2f | ||
|
|
5e3365935e | ||
|
|
5b6424d405 | ||
|
|
698ad8e45d | ||
|
|
09fd345528 | ||
|
|
edfba9f1bc | ||
|
|
bb844ceac4 | ||
|
|
c6f3b60671 | ||
|
|
3178a65e72 | ||
|
|
aedfb4e5dd | ||
|
|
e0dcc191c7 | ||
|
|
c80e6d3474 | ||
|
|
46cd4887a3 | ||
|
|
bfbf70cf1a | ||
|
|
61de54bc34 | ||
|
|
e27af88690 | ||
|
|
393c3fd3b6 | ||
|
|
0e53aef9ea | ||
|
|
8a742b0ec0 | ||
|
|
b17b70a91f | ||
|
|
6f536f9952 | ||
|
|
034caf965a | ||
|
|
6322e3f4cf | ||
|
|
6b78adb702 | ||
|
|
6e8c931bf3 | ||
|
|
b80fe44c08 | ||
|
|
0921773666 | ||
|
|
13e960f5cb | ||
|
|
094583b8f6 | ||
|
|
fd7ec5d2cf | ||
|
|
370659f461 | ||
|
|
1f853a8bb1 | ||
|
|
b93c3b6093 | ||
|
|
6145fff2fd | ||
|
|
48687dc6dd | ||
|
|
4591cc87e2 | ||
|
|
67491615c0 | ||
|
|
fadf64450b | ||
|
|
34bb7bb93f | ||
|
|
67f8401dce | ||
|
|
f9fb711881 | ||
|
|
26c35a01f3 | ||
|
|
9538842fcb | ||
|
|
8ca31ab049 | ||
|
|
b19c9b5eb6 | ||
|
|
896ead0bb8 | ||
|
|
9835381186 | ||
|
|
d49ced8942 | ||
|
|
9a01263d70 | ||
|
|
7980554924 | ||
|
|
8949bcb567 | ||
|
|
ac90bb702e | ||
|
|
088c913ede | ||
|
|
7554a7f246 | ||
|
|
31e5c9fe96 | ||
|
|
e21c1771c7 | ||
|
|
502a331754 | ||
|
|
6203c3c257 | ||
|
|
c7d9ef97ee | ||
|
|
608fc5bbd0 | ||
|
|
dbf7104dd0 | ||
|
|
1f26de4b76 | ||
|
|
ad43e4a2b2 | ||
|
|
171feda4fe | ||
|
|
e5a4e07b8b | ||
|
|
44a5600108 | ||
|
|
d059c5c584 | ||
|
|
523ff8e877 | ||
|
|
ba9115bac1 | ||
|
|
11f6f50748 | ||
|
|
d85c52bceb | ||
|
|
9cf1d7b461 | ||
|
|
e7a2a53d18 | ||
|
|
1ffdfc17fb | ||
|
|
190c6fb007 | ||
|
|
0cd806fb74 | ||
|
|
9f55471f0f | ||
|
|
4add27e83c | ||
|
|
7a3ab14fca | ||
|
|
589af685ac | ||
|
|
8cb861df6c | ||
|
|
5ebda7eb1a | ||
|
|
e3bbd509c3 | ||
|
|
65acca5380 | ||
|
|
e1d4a80e57 | ||
|
|
08f1db2641 | ||
|
|
d78b0b3929 | ||
|
|
14f376cacb | ||
|
|
8ccbc12024 | ||
|
|
c646c36f4f | ||
|
|
d5a5e7bcf9 | ||
|
|
73f6e6d785 | ||
|
|
87b36562cc | ||
|
|
7406b6688d | ||
|
|
f58dd121d5 | ||
|
|
443b10a990 | ||
|
|
e8e48a2cc4 | ||
|
|
94edcee382 | ||
|
|
737a53a589 | ||
|
|
85bff56bd5 | ||
|
|
0f149098a3 | ||
|
|
13b5b145e4 | ||
|
|
a050beea72 | ||
|
|
3952e8dd91 | ||
|
|
68f3c02eb9 | ||
|
|
b4c2703cbb | ||
|
|
cb4fda1786 | ||
|
|
058b17fcbc | ||
|
|
9bfdc7209b | ||
|
|
1a44e84112 | ||
|
|
b722fcbe6e | ||
|
|
d1d26409fc | ||
|
|
fef1e17935 | ||
|
|
5a3782c9f1 | ||
|
|
27e9472ce1 | ||
|
|
23a0a98b4f | ||
|
|
e724c22941 | ||
|
|
27d69ff5ed | ||
|
|
cb2cce5326 | ||
|
|
e790ca2257 | ||
|
|
70a0f9260a | ||
|
|
23003aa82f | ||
|
|
cc51391d14 | ||
|
|
9242a8f55d | ||
|
|
6b32190acd | ||
|
|
69834c7771 | ||
|
|
3513fc9617 | ||
|
|
1348987f08 | ||
|
|
b31dff2815 | ||
|
|
3483d833a0 | ||
|
|
ddad1468d9 | ||
|
|
f20fc8e123 | ||
|
|
3bffb2d8f5 | ||
|
|
0bd4a7b8dd | ||
|
|
69d79dbd7c | ||
|
|
31806c707f | ||
|
|
2a4198c2c8 | ||
|
|
cecfe60bac | ||
|
|
ef42eb1fef | ||
|
|
1e2be52371 | ||
|
|
0034e49c1a | ||
|
|
350412be33 | ||
|
|
c9312719ea | ||
|
|
3010bbf1df | ||
|
|
59d5f1053f | ||
|
|
ae81b86e78 | ||
|
|
1f80a7d8ca | ||
|
|
047797daf2 | ||
|
|
f62e0513f9 | ||
|
|
b7471fd91c | ||
|
|
0e8f8a09cb | ||
|
|
8cc85a3203 | ||
|
|
8f41d71ac4 | ||
|
|
470d362ab4 | ||
|
|
a342f73f68 | ||
|
|
05842ab4a0 | ||
|
|
b54d95e5af | ||
|
|
202dcf16b9 | ||
|
|
153383343b | ||
|
|
6f138d95ca | ||
|
|
e3bbb0afff | ||
|
|
8e05e5739b | ||
|
|
7a2c4942bf | ||
|
|
95189a9d4b | ||
|
|
ded15aa628 | ||
|
|
b1d74dcfea | ||
|
|
f4db748eae | ||
|
|
b797713b2d | ||
|
|
69cf4057ac | ||
|
|
a1d5341840 | ||
|
|
8b1e705a96 | ||
|
|
dff63b74f5 | ||
|
|
f709c97602 | ||
|
|
0b2a722218 | ||
|
|
168275343c | ||
|
|
05335df9bf | ||
|
|
de2e924aa2 | ||
|
|
6fd8c2fbd9 | ||
|
|
a3dba96908 | ||
|
|
ae6be2f525 | ||
|
|
16a45c7826 | ||
|
|
5676b50d5d | ||
|
|
7940038728 | ||
|
|
1e13bf6629 | ||
|
|
7dcec16152 | ||
|
|
01f1893431 | ||
|
|
d51f79a154 | ||
|
|
4bc5c9c8ab | ||
|
|
d6260e6fb2 | ||
|
|
ce05ce240c | ||
|
|
10e8c08ce3 | ||
|
|
71a36f2fe6 | ||
|
|
a282d2ff08 | ||
|
|
f3aeaa6344 | ||
|
|
503027c06e | ||
|
|
8d58224a95 | ||
|
|
415394fce2 | ||
|
|
7826139a7c | ||
|
|
75fc11f008 | ||
|
|
7fa76346b4 | ||
|
|
b4addd9630 | ||
|
|
d6af3363ed | ||
|
|
f4a3717859 | ||
|
|
692070de21 | ||
|
|
5dcfb37c4b | ||
|
|
941e838c74 | ||
|
|
481e236faf | ||
|
|
06ec6884a4 | ||
|
|
84d8363383 | ||
|
|
3830b00c33 | ||
|
|
84103bb8ed | ||
|
|
0d7202f7a2 | ||
|
|
a1f4f9b92f | ||
|
|
82410814de | ||
|
|
98cf0a4bc0 | ||
|
|
5efc9dcb16 | ||
|
|
cfa7fb47e4 | ||
|
|
29d677253e | ||
|
|
210a5e9ae2 | ||
|
|
49465715a6 | ||
|
|
a0a14f24cc | ||
|
|
209d06421c | ||
|
|
cac2af8422 | ||
|
|
57a9b269a0 | ||
|
|
17b11a016c | ||
|
|
d05de62cc7 | ||
|
|
3c28f9ed36 | ||
|
|
56bd1d2772 | ||
|
|
7e65b06ff2 | ||
|
|
78cb68d2c4 | ||
|
|
f8df8dc41a | ||
|
|
4199f8891c | ||
|
|
7aa5f499b9 | ||
|
|
990f490fb3 | ||
|
|
7dba186e39 | ||
|
|
7443c52021 | ||
|
|
03822ac8fa | ||
|
|
01f7a18dce | ||
|
|
a392803478 | ||
|
|
c9ef1b1bce | ||
|
|
87eda72a62 | ||
|
|
984bc58cf2 | ||
|
|
4d7a55e4f6 | ||
|
|
79d67d1cbe | ||
|
|
0bc93a2352 | ||
|
|
27443c441a | ||
|
|
7894b08051 | ||
|
|
31089e2aa6 | ||
|
|
46cbf85584 | ||
|
|
9d3a537b10 | ||
|
|
a3fcd4373f | ||
|
|
fb8876df0d | ||
|
|
8b617ffb8d | ||
|
|
55fa24bced | ||
|
|
b1cfaac33b | ||
|
|
476dd67796 | ||
|
|
f52ae328bc | ||
|
|
cbf626a5b6 |
1
FUNDING.yml → .github/FUNDING.yml
vendored
@@ -1,2 +1,3 @@
|
||||
github: jokob-sk
|
||||
patreon: 84385063
|
||||
buy_me_a_coffee: jokobsk
|
||||
56
.github/ISSUE_TEMPLATE/documentation-feedback.yml
vendored
Executable file
@@ -0,0 +1,56 @@
|
||||
name: Documentation Feedback 📝
|
||||
description: Suggest improvements, clarify inconsistencies, or report issues related to the documentation.
|
||||
labels: ['documentation 📚']
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an open or closed issue already exists for the documentation change you're suggesting.
|
||||
options:
|
||||
- label: I have searched the existing open and closed issues
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: What document or section does this relate to?
|
||||
description: |
|
||||
Please include a link to the file and section, if applicable. Be specific about what part of the documentation you are referencing.
|
||||
placeholder: e.g. https://github.com/jokob-sk/NetAlertX/blob/main/docs/FRONTEND_DEVELOPMENT.md
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the issue
|
||||
description: A clear and concise explanation of the issue or inconsistency you found in the documentation.
|
||||
placeholder: e.g. The linked file is referred to as "Contributor Guidelines" but only covers frontend topics.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Your suggestion or proposed solution
|
||||
description: Suggest how the documentation could be improved, clarified, or reorganized.
|
||||
placeholder: e.g. Combine frontend and backend development into a single CONTRIBUTING.md file with common sections to reduce fragmentation.
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: What type of issue is this?
|
||||
options:
|
||||
- label: Missing information
|
||||
- label: Inaccurate or outdated information
|
||||
- label: Unclear or confusing content
|
||||
- label: Structure or organization improvements
|
||||
- label: Other (explain in issue)
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: |
|
||||
Additional context, references, screenshots, or related issues. You can also mention if you’re willing to help implement the suggestion.
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Can I help implement this? 👩💻👨💻
|
||||
description: The maintainer can provide guidance and review your changes.
|
||||
options:
|
||||
- label: "Yes, I’d like to help implement the improvement"
|
||||
- label: "No, I’m just suggesting the idea"
|
||||
33
.github/ISSUE_TEMPLATE/enhancement-request.yml
vendored
Executable file
@@ -0,0 +1,33 @@
|
||||
name: Enhancement Request
|
||||
description: Propose an improvement to an existing feature or UX behavior.
|
||||
labels: ['enhancement ♻️']
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
options:
|
||||
- label: I have searched existing open and closed issues
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: What is the enhancement?
|
||||
description: Describe the change or optimization you’d like to see to an existing feature.
|
||||
placeholder: e.g. Make scan intervals configurable from UI instead of just `app.conf`
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: What problem does this solve or improve?
|
||||
description: Describe why this change would improve user experience or project maintainability.
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context or examples
|
||||
description: |
|
||||
Screenshots? Comparisons? Reference repos?
|
||||
required: false
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Are you willing to help implement this?
|
||||
options:
|
||||
- label: "Yes"
|
||||
- label: "No"
|
||||
37
.github/ISSUE_TEMPLATE/refactor-codequality-request.yml
vendored
Executable file
@@ -0,0 +1,37 @@
|
||||
name: Refactor / Code Quality Request ♻️
|
||||
description: Suggest improvements to code structure, style, or maintainability.
|
||||
labels: ['enhancement ♻️']
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please check if a similar request already exists.
|
||||
options:
|
||||
- label: I have searched the existing open and closed issues
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: What part of the code needs refactoring or improvement?
|
||||
description: Specify files, modules, or components.
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the proposed changes
|
||||
description: Explain the refactoring or quality improvements you suggest.
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Why is this improvement needed?
|
||||
description: Benefits such as maintainability, readability, performance, or scalability.
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context or examples
|
||||
description: Any relevant links, references, or related issues.
|
||||
required: false
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Can you help implement this change?
|
||||
options:
|
||||
- label: Yes
|
||||
- label: No
|
||||
28
.github/ISSUE_TEMPLATE/security-report.yml
vendored
Executable file
@@ -0,0 +1,28 @@
|
||||
name: Security Report 🔐
|
||||
description: Report a security vulnerability or concern privately.
|
||||
labels: ['security 🔐']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**Important:** For security reasons, please do **not** post sensitive security issues publicly in the issue tracker.
|
||||
Instead, send details to our security contact email: [jokob@duck.com](mailto:jokob@duck.com).
|
||||
|
||||
We appreciate your responsible disclosure.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Brief summary (non-sensitive)
|
||||
description: Provide a non-sensitive overview of the security issue.
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context or references
|
||||
description: Any other information or related reports.
|
||||
required: false
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Have you sent this report via email to the security contact?
|
||||
options:
|
||||
- label: Yes, I have sent the details to jokob@duck.com
|
||||
required: true
|
||||
- label: Not yet, I will send it after opening this issue
|
||||
36
.github/ISSUE_TEMPLATE/translation-request.yml
vendored
Executable file
@@ -0,0 +1,36 @@
|
||||
name: Translation / Localization Request 🌐
|
||||
description: Suggest adding or improving translations or localization support.
|
||||
labels: ['enhancement 🌐']
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Have you checked for existing translation efforts or related issues?
|
||||
options:
|
||||
- label: I have searched existing open and closed issues
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Language(s) involved
|
||||
description: Specify the language(s) this request pertains to.
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the translation or localization improvement
|
||||
description: Examples include adding new language support, fixing translation errors, or improving formatting.
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Why is this important for the project or users?
|
||||
description: Describe the benefits or target audience.
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context or references
|
||||
description: Link to files, previous translation PRs, or external resources.
|
||||
required: false
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Can you help with translation or review?
|
||||
options:
|
||||
- label: Yes
|
||||
- label: No
|
||||
53
.github/PULL_REQUEST_TEMPLATE/code-pr-template.md
vendored
Executable file
@@ -0,0 +1,53 @@
|
||||
## 📌 Description
|
||||
|
||||
<!-- Provide a brief description of the changes you're introducing. Be clear and concise. -->
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Related Issues
|
||||
|
||||
<!-- Reference any related issues (e.g., closes #123, fixes #456) -->
|
||||
|
||||
---
|
||||
|
||||
## 📋 Type of Change
|
||||
|
||||
Please check the relevant option(s):
|
||||
|
||||
- [ ] 🐛 Bug fix
|
||||
- [ ] ✨ New feature
|
||||
- [ ] ♻️ Code refactor
|
||||
- [ ] 📚 Documentation update
|
||||
- [ ] 🧪 Test addition or change
|
||||
- [ ] 🔧 Build/config update
|
||||
- [ ] 🚀 Performance improvement
|
||||
- [ ] 🔨 CI/CD or automation
|
||||
- [ ] 🧹 Cleanup / chore
|
||||
|
||||
---
|
||||
|
||||
## 📷 Screenshots or Logs (if applicable)
|
||||
|
||||
<!-- Add screenshots, terminal output, logs, or anything that helps understand your change -->
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing Steps
|
||||
|
||||
<!-- Describe how the change was tested. Manual steps, test cases, or automated test runs -->
|
||||
|
||||
---
|
||||
|
||||
## ✅ Checklist
|
||||
|
||||
- [ ] I have read the [Contribution Guidelines](../../CONTRIBUTING)
|
||||
- [ ] I have tested my changes locally
|
||||
- [ ] I have updated relevant documentation (if applicable)
|
||||
- [ ] I have verified my changes do not break existing behavior
|
||||
- [ ] I am willing to respond to requested changes and feedback
|
||||
|
||||
---
|
||||
|
||||
## 🙋 Additional Notes
|
||||
|
||||
<!-- Anything else you want reviewers to know? Future follow-ups? Questions? -->
|
||||
37
.github/PULL_REQUEST_TEMPLATE/docs-pr-template.md
vendored
Executable file
@@ -0,0 +1,37 @@
|
||||
## 📚 Documentation Update
|
||||
|
||||
<!-- Describe the purpose of this PR in one or two sentences. Example: "This PR updates the contributor guidelines by merging frontend and backend sections." -->
|
||||
|
||||
---
|
||||
|
||||
## 📝 What’s Changed?
|
||||
|
||||
<!-- Briefly outline what parts of the documentation were added, changed, removed, or reorganized -->
|
||||
|
||||
- Combined frontend and backend development guidelines into a single file
|
||||
- Updated `mkdocs.yml` to reflect new structure
|
||||
- Added clarification on contribution process
|
||||
- Fixed outdated links in sidebar
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Related Issue(s)
|
||||
|
||||
<!-- Link to related issues, discussions, or context (e.g., closes #123) -->
|
||||
|
||||
---
|
||||
|
||||
## ✅ Checklist
|
||||
|
||||
- [ ] I followed the formatting/style of existing documentation
|
||||
- [ ] I have read the [Contribution Guidelines](../../CONTRIBUTING)
|
||||
- [ ] I updated `mkdocs.yml` if necessary
|
||||
- [ ] I verified links and references still work
|
||||
- [ ] I checked that my changes improve clarity, structure, or accuracy
|
||||
- [ ] I'm open to feedback and suggestions
|
||||
|
||||
---
|
||||
|
||||
## 🙋 Additional Notes
|
||||
|
||||
<!-- Optional: Include anything you want reviewers to be aware of -->
|
||||
2
.github/tweet.md
vendored
@@ -1,2 +0,0 @@
|
||||
🎉 New release: **v25.4.1 - 🔀 Workflows - automate device management ** is live! 🚀
|
||||
Check it out here: https://github.com/jokob-sk/NetAlertX/releases/tag/v25.4.1
|
||||
16
.github/workflows/code_checks.yml
vendored
@@ -17,14 +17,22 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check for absolute path URLs
|
||||
- name: Check for incorrect absolute '/php/' URLs in frontend code
|
||||
run: |
|
||||
if grep -r -E "\burl:\s*['\"]\/php" --include=\*.{js,php} .; then
|
||||
echo "❌ Found absolute path URLs starting with '/php/'. Please use relative paths."
|
||||
echo "🔍 Checking for incorrect absolute '/php/' URLs (should be 'php/' or './php/')..."
|
||||
|
||||
MATCHES=$(grep -rE "['\"]\/php\/" --include=\*.{js,php,html} ./front | grep -E "\.get|\.post|\.ajax|fetch|url\s*:") || true
|
||||
|
||||
if [ -n "$MATCHES" ]; then
|
||||
echo "$MATCHES"
|
||||
echo "❌ Found incorrectly absolute '/php/' URLs. Use 'php/' or './php/' for relative paths."
|
||||
exit 1
|
||||
else
|
||||
echo "✅ No absolute path URLs found."
|
||||
echo "✅ No bad '/php/' URLs found."
|
||||
fi
|
||||
|
||||
|
||||
|
||||
- name: Check Python syntax
|
||||
run: |
|
||||
set -e
|
||||
|
||||
37
.github/workflows/social_post_on_release.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: 📧 Twitter and Discord Posts
|
||||
name: 📧 Social Posts
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
@@ -16,38 +16,3 @@ jobs:
|
||||
-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: Set Git config
|
||||
run: |
|
||||
git config --global user.email "github-actions@github.com"
|
||||
git config --global user.name "GitHub Actions"
|
||||
|
||||
- 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 }}"
|
||||
|
||||
- name: Push changes
|
||||
run: |
|
||||
git push https://github.com/${{ github.repository }}.git HEAD:main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- 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 }}
|
||||
|
||||
137
CODE_OF_CONDUCT.md
Executable file
@@ -0,0 +1,137 @@
|
||||
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
- Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
- The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official email address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at <jokob@duck.com>.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Ethical Use Clause (Project-Specific)
|
||||
|
||||
While NetAlertX is a tool designed to empower users with greater insight into their own networks, we expect and encourage all users to use this software **ethically and legally**.
|
||||
|
||||
- Do not use this software to scan or monitor networks without **explicit authorization**.
|
||||
- Respect privacy, consent, and data protection laws applicable in your jurisdiction.
|
||||
- Any use of NetAlertX for malicious surveillance, stalking, or unauthorized access is explicitly discouraged and may be grounds for removal from the community and revocation of support.
|
||||
|
||||
We reserve the right to take appropriate action to uphold the ethical integrity of this project.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the
|
||||
[Contributor Covenant](https://www.contributor-covenant.org/), version 2.1,
|
||||
available at
|
||||
<https://www.contributor-covenant.org/version/2/1/code_of_conduct/>.
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/inclusion).
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
<https://www.contributor-covenant.org/faq/>. Translations are available at
|
||||
<https://www.contributor-covenant.org/translations/>.
|
||||
55
CONTRIBUTING
@@ -1,14 +1,53 @@
|
||||
# Contributing to this project
|
||||
# 🤝 Contributing to NetAlertX
|
||||
|
||||
## Issues, bugs, feature requests
|
||||
First off, **thank you** for taking the time to contribute! NetAlertX is built and improved with the help of passionate people like you.
|
||||
|
||||
The issue tracker is the preferred channel for bug reports, features requests and submitting pull requests.
|
||||
---
|
||||
|
||||
Before submitting a new issue please spend a couple of minutes on research:
|
||||
## 📂 Issues, Bugs, and Feature Requests
|
||||
|
||||
* Check [🛑 Common issues](https://github.com/jokob-sk/NetAlertX/blob/main/docs/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.
|
||||
Please use the [GitHub Issue Tracker](https://github.com/jokob-sk/NetAlertX/issues) for:
|
||||
- Bug reports 🐞
|
||||
- Feature requests 💡
|
||||
- Documentation feedback 📖
|
||||
|
||||
## Pull-requests (PRs)
|
||||
Before opening a new issue:
|
||||
- 🛑 [Check Common Issues & Debug Tips](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEBUG_TIPS.md#common-issues)
|
||||
- 🔍 [Search Closed Issues](https://github.com/jokob-sk/NetAlertX/issues?q=is%3Aissue+is%3Aclosed)
|
||||
|
||||
If you submit a PR please do check that your changes are backward compatible with existing installations. Existing features should be always preserved.
|
||||
---
|
||||
|
||||
## 🚀 Submitting Pull Requests (PRs)
|
||||
|
||||
We welcome PRs to improve the code, docs, or UI!
|
||||
|
||||
Please:
|
||||
- Ensure **backward compatibility** with existing installations
|
||||
- Preserve existing features unless a breaking change is intentional and discussed
|
||||
- Follow existing **code style and structure**
|
||||
- Provide a clear title and description for your PR
|
||||
- If relevant, add or update tests and documentation
|
||||
- For plugins, refer to the [Plugin Dev Guide](https://github.com/jokob-sk/NetAlertX/blob/main/docs/PLUGINS_DEV.md)
|
||||
|
||||
---
|
||||
|
||||
## 🌟 First-Time Contributors
|
||||
|
||||
New to open source? Check out these resources:
|
||||
- [How to Fork and Submit a PR](https://opensource.guide/how-to-contribute/)
|
||||
- Ask questions or get support in our [Discord](https://discord.gg/NczTUTWyRr)
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Code of Conduct
|
||||
|
||||
By participating, you agree to follow our [Code of Conduct](./CODE_OF_CONDUCT.md), which ensures a respectful and welcoming community.
|
||||
|
||||
---
|
||||
|
||||
## 📬 Contact
|
||||
|
||||
If you have more in-depth questions or want to discuss contributing in other ways, feel free to reach out at:
|
||||
📧 [jokob@duck.com](mailto:jokob@duck.com?subject=NetAlertX%20Contribution)
|
||||
|
||||
We appreciate every contribution, big or small! 💙
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM alpine:3.21 AS builder
|
||||
FROM alpine:3.22 AS builder
|
||||
|
||||
ARG INSTALL_DIR=/app
|
||||
|
||||
@@ -13,7 +13,7 @@ ENV PATH="/opt/venv/bin:$PATH"
|
||||
|
||||
COPY . ${INSTALL_DIR}/
|
||||
|
||||
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 \
|
||||
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 yattag 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 {} \;"
|
||||
@@ -22,7 +22,7 @@ RUN pip install openwrt-luci-rpc asusrouter asyncio aiohttp graphene flask tplin
|
||||
RUN cat ${INSTALL_DIR}/install/freebox_certificate.pem >> /opt/venv/lib/python3.12/site-packages/aiofreepybox/freebox_certificates.pem
|
||||
|
||||
# second stage
|
||||
FROM alpine:3.21 AS runner
|
||||
FROM alpine:3.22 AS runner
|
||||
|
||||
ARG INSTALL_DIR=/app
|
||||
|
||||
|
||||
@@ -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 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 "
|
||||
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 yattag "
|
||||
|
||||
# Create a buildtimestamp.txt to later check if a new version was released
|
||||
RUN date +%s > ${INSTALL_DIR}/front/buildtimestamp.txt
|
||||
|
||||
82
README.md
@@ -8,6 +8,42 @@
|
||||
|
||||
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).
|
||||
|
||||
## 📋 Table of Contents
|
||||
|
||||
- [Features](#-features)
|
||||
- [Documentation](#-documentation)
|
||||
- [Quick Start](#-quick-start)
|
||||
- [Alternative Apps](#-other-alternative-apps)
|
||||
- [Security & Privacy](#-security--privacy)
|
||||
- [FAQ](#-faq)
|
||||
- [Known Issues](#-known-issues)
|
||||
- [Donations](#-donations)
|
||||
- [Contributors](#-contributors)
|
||||
- [Translations](#-translations)
|
||||
- [License](#license)
|
||||
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
Start NetAlertX in seconds with Docker:
|
||||
|
||||
```bash
|
||||
docker run -d --rm --network=host \
|
||||
-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 \
|
||||
ghcr.io/jokob-sk/netalertx:latest
|
||||
```
|
||||
|
||||
Need help configuring it? Check the [usage guide](https://github.com/jokob-sk/NetAlertX/blob/main/docs/README.md) or [full documentation](https://jokob-sk.github.io/NetAlertX/).
|
||||
|
||||
For Home Assistant users: [Click here to add NetAlertX](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Falexbelgium%2Fhassio-addons)
|
||||
|
||||
For other install methods, check the [installation docs](#-documentation)
|
||||
|
||||
|
||||
| [📑 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)
|
||||
|----------------------| ----------------------| ----------------------| ----------------------| ----------------------|
|
||||
@@ -30,7 +66,7 @@ Get visibility of what's going on on your WIFI/LAN network and enable presence d
|
||||
|
||||
### 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.
|
||||
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 list of avaliable plugins.
|
||||
|
||||
### Notification gateways
|
||||
|
||||
@@ -43,7 +79,7 @@ build your own scanners with the [Plugin system](https://github.com/jokob-sk/Net
|
||||
|
||||
### Workflows
|
||||
|
||||
The [workflows module](https://github.com/jokob-sk/NetAlertX/blob/main/docs/WORKFLOWS.md) in NetAlertX allows to automate repetitive tasks, making network management more efficient. Whether you need to assign newly discovered devices to a specific Network Node, auto-group devices from a given vendor, unarchive a device if detected online, or automatically delete devices, this module provides the flexibility to tailor the automations to your needs.
|
||||
The [workflows module](https://github.com/jokob-sk/NetAlertX/blob/main/docs/WORKFLOWS.md) allows to automate repetitive tasks, making network management more efficient. Whether you need to assign newly discovered devices to a specific Network Node, auto-group devices from a given vendor, unarchive a device if detected online, or automatically delete devices, this module provides the flexibility to tailor the automations to your needs.
|
||||
|
||||
|
||||
## 📚 Documentation
|
||||
@@ -59,6 +95,46 @@ Supported browsers: Chrome, Firefox
|
||||
- [[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)
|
||||
|
||||
...or explore all the [documentation here](https://jokob-sk.github.io/NetAlertX/).
|
||||
|
||||
## 🔐 Security & Privacy
|
||||
|
||||
NetAlertX scans your local network and can store metadata about connected devices. By default, all data is stored **locally**. No information is sent to external services unless you explicitly configure notifications or integrations.
|
||||
|
||||
To further secure your installation:
|
||||
- Run it behind a reverse proxy with authentication
|
||||
- Use firewalls to restrict access to the web UI
|
||||
- Regularly update to the latest version for security patches
|
||||
|
||||
See [Security Best Practices](https://github.com/jokob-sk/NetAlertX/security) for more details.
|
||||
|
||||
|
||||
## ❓ FAQ
|
||||
|
||||
**Q: Why don’t I see any devices?**
|
||||
A: Ensure the container has proper network access (e.g., use `--network host` on Linux). Also check that your scan method is properly configured in the UI.
|
||||
|
||||
**Q: Does this work on Wi-Fi-only devices like Raspberry Pi?**
|
||||
A: Yes, but some scanners (e.g. ARP) work best on Ethernet. For Wi-Fi, try SNMP, DHCP, or Pi-hole import.
|
||||
|
||||
**Q: Will this send any data to the internet?**
|
||||
A: No. All scans and data remain local, unless you set up cloud-based notifications.
|
||||
|
||||
**Q: Can I use this without Docker?**
|
||||
A: Yes! You can install it bare-metal. See the [bare metal installation guide](https://github.com/jokob-sk/NetAlertX/blob/main/docs/HW_INSTALL.md).
|
||||
|
||||
**Q: Where is the data stored?**
|
||||
A: In the `/config` and `/db` folders, mapped in Docker. Back up these folders regularly.
|
||||
|
||||
|
||||
## 🐞 Known Issues
|
||||
|
||||
- Some scanners (e.g. ARP) may not detect devices on different subnets. See the [Remote networks guide](https://github.com/jokob-sk/NetAlertX/blob/main/docs/REMOTE_NETWORKS.md) for workarounds.
|
||||
- Wi-Fi-only networks may require alternate scanners for accurate detection.
|
||||
- Notification throttling may be needed for large networks to prevent spam.
|
||||
- On some systems, elevated permissions (like `CAP_NET_RAW`) may be needed for low-level scanning.
|
||||
|
||||
Check the [GitHub Issues](https://github.com/jokob-sk/NetAlertX/issues) for the latest bug reports and solutions and consult [the official documentation](https://jokob-sk.github.io/NetAlertX/).
|
||||
|
||||
## 📃 Everything else
|
||||
<!--- --------------------------------------------------------------------- --->
|
||||
@@ -111,7 +187,6 @@ Proudly using [Weblate](https://hosted.weblate.org/projects/pialert/). Help out
|
||||
|
||||
### 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)
|
||||
|
||||
|
||||
|
||||
<!--- --------------------------------------------------------------------- --->
|
||||
@@ -131,4 +206,3 @@ Proudly using [Weblate](https://hosted.weblate.org/projects/pialert/). Help out
|
||||
[main_dark]: /docs/img/1_devices_dark.jpg "Main screen dark"
|
||||
[maintain_dark]: /docs/img/5_maintain.jpg "Maintain screen dark"
|
||||
[follow_star]: /docs/img/Follow_Releases_and_Star.gif "Follow and Star"
|
||||
|
||||
|
||||
@@ -18,18 +18,20 @@
|
||||
# SCAN_SUBNETS = [ '192.168.1.0/24 --interface=eth1', '192.168.1.0/24 --interface=eth0' ]
|
||||
|
||||
DISCOVER_PLUGINS=True
|
||||
SCAN_SUBNETS=['192.168.1.0/24 --interface=eth0']
|
||||
SCAN_SUBNETS=['--localnet']
|
||||
TIMEZONE='Europe/Berlin'
|
||||
LOADED_PLUGINS=['ARPSCAN','CSVBCKP','DBCLNP', 'INTRNT','MAINT','NEWDEV','NSLOOKUP','NTFPRCS', 'AVAHISCAN', 'SETPWD','SMTP', 'SYNC', 'VNDRPDT', 'WORKFLOWS', 'UI']
|
||||
LOADED_PLUGINS=['ARPSCAN', 'AVAHISCAN', 'CSVBCKP','DBCLNP', 'DIGSCAN', 'INTRNT', 'MAINT', 'NEWDEV', 'NBTSCAN', 'NSLOOKUP','NTFPRCS', 'SETPWD', 'SMTP', 'SYNC', 'VNDRPDT', 'WORKFLOWS', 'UI']
|
||||
|
||||
DAYS_TO_KEEP_EVENTS=90
|
||||
# Used for generating links in emails. Make sure not to add a trailing slash!
|
||||
REPORT_DASHBOARD_URL='http://netalertx'
|
||||
REPORT_DASHBOARD_URL='http://127.0.0.1'
|
||||
|
||||
# Make sure at least these scanners are enabled for new installs, other defaults are taken from the config.json
|
||||
INTRNT_RUN='schedule'
|
||||
ARPSCAN_RUN='schedule'
|
||||
NSLOOKUP_RUN='before_name_updates'
|
||||
AVAHISCAN_RUN='before_name_updates'
|
||||
NBTSCAN_RUN='before_name_updates'
|
||||
|
||||
# Email
|
||||
#-------------------------------------
|
||||
|
||||
BIN
back/app.db
BIN
back/app_clean.db
Executable file
BIN
back/app_old.db
Executable file
@@ -23,11 +23,11 @@ services:
|
||||
# - ${DEV_LOCATION}/api:/app/api
|
||||
# ---------------------------------------------------------------------------
|
||||
# DELETE START anyone trying to use this file: comment out / delete BELOW lines, they are only for development purposes
|
||||
- ${APP_DATA_LOCATION}/netalertx/dhcp_samples/dhcp1.leases:/mnt/dhcp1.leases
|
||||
- ${APP_DATA_LOCATION}/netalertx/dhcp_samples/dhcp2.leases:/mnt/dhcp2.leases
|
||||
- ${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
|
||||
- ${APP_DATA_LOCATION}/netalertx/dhcp_samples/dhcp1.leases:/mnt/dhcp1.leases # test data for DCPLSS plugin
|
||||
- ${APP_DATA_LOCATION}/netalertx/dhcp_samples/dhcp2.leases:/mnt/dhcp2.leases # test data for DCPLSS plugin
|
||||
- ${APP_DATA_LOCATION}/netalertx/dhcp_samples/pihole_dhcp_full.leases:/etc/pihole/dhcp.leases # test data for DCPLSS plugin
|
||||
- ${APP_DATA_LOCATION}/netalertx/dhcp_samples/pihole_dhcp_2.leases:/etc/pihole/dhcp2.leases # test data for DCPLSS plugin
|
||||
- ${APP_DATA_LOCATION}/pihole/etc-pihole/pihole-FTL.db:/etc/pihole/pihole-FTL.db # test data for PIHOLE plugin
|
||||
- ${DEV_LOCATION}/mkdocs.yml:/app/mkdocs.yml
|
||||
- ${DEV_LOCATION}/docs:/app/docs
|
||||
- ${DEV_LOCATION}/server:/app/server
|
||||
@@ -53,6 +53,7 @@ services:
|
||||
- ${DEV_LOCATION}/front/plugins.php:/app/front/plugins.php
|
||||
- ${DEV_LOCATION}/front/pluginsCore.php:/app/front/pluginsCore.php
|
||||
- ${DEV_LOCATION}/front/index.php:/app/front/index.php
|
||||
- ${DEV_LOCATION}/front/initCheck.php:/app/front/initCheck.php
|
||||
- ${DEV_LOCATION}/front/maintenance.php:/app/front/maintenance.php
|
||||
- ${DEV_LOCATION}/front/network.php:/app/front/network.php
|
||||
- ${DEV_LOCATION}/front/presence.php:/app/front/presence.php
|
||||
@@ -65,7 +66,7 @@ services:
|
||||
- ${DEV_LOCATION}/front/appEvents.php:/app/front/appEvents.php
|
||||
- ${DEV_LOCATION}/front/appEventsCore.php:/app/front/appEventsCore.php
|
||||
- ${DEV_LOCATION}/front/multiEditCore.php:/app/front/multiEditCore.php
|
||||
- ${DEV_LOCATION}/front/plugins:/app/front/plugins
|
||||
- ${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:
|
||||
|
||||
@@ -49,6 +49,8 @@ query GetDevices($options: PageQueryOptionsInput) {
|
||||
}
|
||||
```
|
||||
|
||||
See also: [Debugging GraphQL issues](./DEBUG_GRAPHQL.md)
|
||||
|
||||
### `curl` Command
|
||||
|
||||
You can use the following `curl` command to execute the query.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
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)
|
||||
|
||||
- ▶ [Discover & Monitor Your Network with This Self-Hosted Open Source Tool - Lawrence Systems](https://www.youtube.com/watch?v=R3b5cxLZMpo) (June 2025)
|
||||
- ▶ [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/)
|
||||
|
||||
@@ -38,6 +38,9 @@
|
||||
| `devSyncHubNode` | The NetAlertX node ID used for synchronization between NetAlertX instances. | `node_1` |
|
||||
| `devSourcePlugin` | Source plugin that discovered the device. | `ARPSCAN` |
|
||||
| `devCustomProps` | [Custom properties](./CUSTOM_PROPERTIES.md) related to the device. The value is a base64-encoded JSON object. | `PHN2ZyB...` |
|
||||
| `devFQDN` | Fully qualified domain name. | `raspberrypi.local` |
|
||||
| `devParentRelType` | The type of relationship between the current device and it's parent node. By default, selecting `nic` will hide it from lists. | `nic` |
|
||||
| `devReqNicsOnline` | If all NICs are required to be online to mark teh current device online. | `0` |
|
||||
|
||||
|
||||
To understand how values of these fields influuence application behavior, such as Notifications or Network topology, see also:
|
||||
|
||||
64
docs/DEBUG_GRAPHQL.md
Executable file
@@ -0,0 +1,64 @@
|
||||
# Debugging GraphQL server issues
|
||||
|
||||
The GraphQL server is an API middle layer, running on it's own port specified by `GRAPHQL_PORT`, to retrieve and show the data in the UI. It can also be used to retrieve data for custom third party integarions. Check the [API documentation](./API.md) for details.
|
||||
|
||||
The most common issue is that the GraphQL server doesn't start properly, usually due to a **port conflict**. If you are running multiple NetAlertX instances, make sure to use **unique ports** by changing the `GRAPHQL_PORT` setting. The default is `20212`.
|
||||
|
||||
## How to update the `GRAPHQL_PORT` in case of issues
|
||||
|
||||
As a first troubleshooting step try changing the default `GRAPHQL_PORT` setting. Please remember NetAlertX is running on the host so any application uising the same port will cause issues.
|
||||
|
||||
### Updating the setting via the Settings UI
|
||||
|
||||
Ideally use the Settings UI to update the setting under General -> Core -> GraphQL port:
|
||||
|
||||

|
||||
|
||||
You might need to temporarily stop other applications or NetAlertX instances causing conflicts to update the setting. The `API_TOKEN` is used to authenticate any API calls, including GraphQL requests.
|
||||
|
||||
### Updating the `app.conf` file
|
||||
|
||||
If the UI is not accessible, you can directly edit the `app.conf` file in your `/config` folder:
|
||||
|
||||

|
||||
|
||||
### Using a docker variable
|
||||
|
||||
All application settings can also be initialized via the `APP_CONF_OVERRIDE` docker env variable.
|
||||
|
||||
```yaml
|
||||
...
|
||||
environment:
|
||||
- TZ=Europe/Berlin
|
||||
- PORT=20213
|
||||
- APP_CONF_OVERRIDE={"GRAPHQL_PORT":"20214"}
|
||||
...
|
||||
```
|
||||
|
||||
## How to check the GraphQL server is running?
|
||||
|
||||
There are several ways to check if the GraphQL server is running.
|
||||
|
||||
### Init Check
|
||||
|
||||
You can navigate to Maintenance -> Init Check to see if `isGraphQLServerRunning` is ticked:
|
||||
|
||||

|
||||
|
||||
### Checking the Logs
|
||||
|
||||
You can navigate to Maintenance -> Logs and search for `graphql` to see if it started correctly and serving requests:
|
||||
|
||||

|
||||
|
||||
### Inspecting the Browser console
|
||||
|
||||
In your browser open the dev console (usually F12) and navigate to the Network tab where you can filter GraphQL requests (e.g., reload the Devices page).
|
||||
|
||||

|
||||
|
||||
You can then inspect any of the POST requests by opening them in a new tab.
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ Check the the HTTP response of the failing backend call by following these steps
|
||||
- Copy the URL causing the error and enter it in the address bar of your browser directly and hit enter. The copied URLs could look something like this (notice the query strings at the end):
|
||||
- `http://<NetAlertX URL>:20211/api/table_devices.json?nocache=1704141103121`
|
||||
- `http://<NetAlertX URL>:20211/php/server/devices.php?action=getDevicesTotals`
|
||||
- `http://<NetAlertX URL>:20211/php/server/devices.php?action=getDevicesList&status=all`
|
||||
|
||||
|
||||
- Post the error response in the existing issue thread on GitHub or create a new issue and include the redacted response of the failing query.
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ You can select devices in the _Devices_ view by selecting devices to edit and th
|
||||
|
||||
## CSV bulk edit
|
||||
|
||||
The database and device structure may change with new releases. When using the CSV import functionality, ensure the format matches what the application expects. To avoid issues, you can first export the devices and review the column formats before importing any custom data.
|
||||
|
||||
> [!NOTE]
|
||||
> As always, backup everything, just in case.
|
||||
|
||||
|
||||
@@ -3,4 +3,18 @@
|
||||
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.
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
## Status Colors
|
||||
|
||||

|
||||
|
||||
1. 🔌 Online (Green) = A device that is no longer marked as a "New Device".
|
||||
2. 🔌 New (Green) = A newly discovered device that is online and is still marked as a "New Device".
|
||||
3. ✖ New (Grey) = Same as No.2 but device is now offline.
|
||||
4. ✖ Offline (Grey) = A device that was not detected online in the last scan.
|
||||
5. ⚠ Down (Red) = A device that has "Alert Down" marked and has been offline for the time set in the Setting `NTFPRCS_alert_down_time`.
|
||||
|
||||
|
||||
See also [Notification guide](./NOTIFICATIONS.md).
|
||||
@@ -69,7 +69,7 @@ services:
|
||||
network_mode: "host"
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ${APP_DATA_LOCATION}/netalertx/config:/app/config
|
||||
- ${APP_CONFIG_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
|
||||
|
||||
79
docs/DOCKER_SWARM.md
Executable file
@@ -0,0 +1,79 @@
|
||||
# Docker Swarm Deployment Guide (IPvlan)
|
||||
|
||||
This guide describes how to deploy **NetAlertX** in a **Docker Swarm** environment using an `ipvlan` network. This enables the container to receive a LAN IP address directly, which is ideal for network monitoring.
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Step 1: Create an IPvlan Config-Only Network on All Nodes
|
||||
|
||||
> Run this command on **each node** in the Swarm.
|
||||
|
||||
```bash
|
||||
docker network create -d ipvlan \
|
||||
--subnet=192.168.1.0/24 \ # 🔧 Replace with your LAN subnet
|
||||
--gateway=192.168.1.1 \ # 🔧 Replace with your LAN gateway
|
||||
-o ipvlan_mode=l2 \
|
||||
-o parent=eno1 \ # 🔧 Replace with your network interface (e.g., eth0, eno1)
|
||||
--config-only \
|
||||
ipvlan-swarm-config
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ Step 2: Create the Swarm-Scoped IPvlan Network (One-Time Setup)
|
||||
|
||||
> Run this on **one Swarm manager node only**.
|
||||
|
||||
```bash
|
||||
docker network create -d ipvlan \
|
||||
--scope swarm \
|
||||
--config-from ipvlan-swarm-config \
|
||||
swarm-ipvlan
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧾 Step 3: Deploy NetAlertX with Docker Compose
|
||||
|
||||
Use the following Compose snippet to deploy NetAlertX with a **static LAN IP** assigned via the `swarm-ipvlan` network.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
netalertx:
|
||||
image: ghcr.io/jokob-sk/netalertx:latest
|
||||
ports:
|
||||
- 20211:20211
|
||||
volumes:
|
||||
- /mnt/YOUR_SERVER/netalertx/config:/app/config:rw
|
||||
- /mnt/YOUR_SERVER/netalertx/db:/netalertx/app/db:rw
|
||||
- /mnt/YOUR_SERVER/netalertx/logs:/netalertx/app/log:rw
|
||||
environment:
|
||||
- TZ=Europe/London
|
||||
- PORT=20211
|
||||
networks:
|
||||
swarm-ipvlan:
|
||||
ipv4_address: 192.168.1.240 # ⚠️ Choose a free IP from your LAN
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
placement:
|
||||
constraints:
|
||||
- node.role == manager # 🔄 Or use: node.labels.netalertx == true
|
||||
|
||||
networks:
|
||||
swarm-ipvlan:
|
||||
external: true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Notes
|
||||
|
||||
* The `ipvlan` setup allows **NetAlertX** to have a direct IP on your LAN.
|
||||
* Replace `eno1` with your interface, IP addresses, and volume paths to match your environment.
|
||||
* Make sure the assigned IP (`192.168.1.240` above) is not in use or managed by DHCP.
|
||||
* You may also use a node label constraint instead of `node.role == manager` for more control.
|
||||
|
||||
|
||||
78
docs/FIX_OFFLINE_DETECTION.md
Executable file
@@ -0,0 +1,78 @@
|
||||
# Troubleshooting: Devices Show Offline When They Are Online
|
||||
|
||||
In some network setups, certain devices may intermittently appear as **offline** in NetAlertX, even though they are connected and responsive. This issue is often more noticeable with devices that have **higher IP addresses** within the subnet.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Network presence graph showing increased drop outs before enabling additional `ICMP` scans and continuous online presence after following this guide. This graph shows a sudden spike in drop outs probably caused by a device software update.
|
||||
> 
|
||||
|
||||
## Symptoms
|
||||
|
||||
* Devices sporadically show as offline in the presence timeline.
|
||||
* This behavior often affects devices with higher IPs (e.g., `192.168.1.240+`).
|
||||
* Presence data appears inconsistent or unreliable despite the device being online.
|
||||
|
||||
## Cause
|
||||
|
||||
This issue is typically related to scanning limitations:
|
||||
|
||||
* **ARP scan timeouts** may prevent full subnet coverage.
|
||||
* **Sole reliance on ARP** can result in missed detections:
|
||||
|
||||
* Some devices (like iPhones) suppress or reject frequent ARP requests.
|
||||
* ARP responses may be blocked or delayed due to power-saving features or OS behavior.
|
||||
|
||||
* **Scanning frequency conflicts**, where devices ignore repeated scans within a short period.
|
||||
|
||||
## Recommended Fixes
|
||||
|
||||
To improve presence accuracy and reduce false offline states:
|
||||
|
||||
### ✅ Increase ARP Scan Timeout
|
||||
|
||||
Extend the ARP scanner timeout to ensure full subnet coverage:
|
||||
|
||||
```env
|
||||
ARPSCAN_RUN_TIMEOUT=360
|
||||
```
|
||||
|
||||
> Adjust based on your network size and device count.
|
||||
|
||||
### ✅ Add ICMP (Ping) Scanning
|
||||
|
||||
Enable the `ICMP` scan plugin to complement ARP detection. ICMP is often more reliable for detecting active hosts, especially when ARP fails.
|
||||
|
||||
### ✅ Use Multiple Detection Methods
|
||||
|
||||
A combined approach greatly improves detection robustness:
|
||||
|
||||
* `ARPSCAN` (default)
|
||||
* `ICMP` (ping)
|
||||
* `NMAPDEV` (nmap)
|
||||
|
||||
This hybrid strategy increases reliability, especially for down detection and alerting. See [other plugins](./PLUGINS.md) that might be compatible with your setup. See benefits and drawbacks of individual scan methods in their respective docs.
|
||||
|
||||
## Results
|
||||
|
||||
After increasing the ARP timeout and adding ICMP scanning (on select IP ranges), users typically report:
|
||||
|
||||
* More consistent presence graphs
|
||||
* Fewer false offline events
|
||||
* Better coverage across all IP ranges
|
||||
|
||||
## Summary
|
||||
|
||||
| Setting | Recommendation |
|
||||
| --------------------- | --------------------------------------------- |
|
||||
| `ARPSCAN_RUN_TIMEOUT` | Increase to ensure scans reach all IPs |
|
||||
| `ICMP` Scan | Enable to detect devices ARP might miss |
|
||||
| Multi-method Scanning | Use a mix of ARP, ICMP, and NMAP-based methods |
|
||||
|
||||
---
|
||||
|
||||
**Tip:** Each environment is unique. Consider fine-tuning scan settings based on your network size, device behavior, and desired detection accuracy.
|
||||
|
||||
Let us know in the [NetAlertX Discussions](https://github.com/jokob-sk/NetAlertX/discussions) if you have further feedback or edge cases.
|
||||
|
||||
See also [Remote Networks](./REMOTE_NETWORKS.md) for more advanced setups.
|
||||
@@ -1,4 +1,4 @@
|
||||
# Overview
|
||||
# Home Assistant integration overview
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@ 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 🙂 Current community volunteers:
|
||||
> - [slammingprogramming](https://github.com/slammingprogramming)
|
||||
>
|
||||
> 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**.
|
||||
|
||||
@@ -1,42 +1,109 @@
|
||||
# ⚙ Initial Setup
|
||||
# ⚡ Quick Start Guide
|
||||
|
||||
## 📁 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/`.
|
||||
Get **NetAlertX** up and running in a few simple steps.
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ Setting Up Scanners
|
||||
### 1. Configure Scanner Plugin(s)
|
||||
|
||||
- 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).
|
||||
> [!TIP]
|
||||
> Enable additional plugins under **Settings → `LOADED_PLUGINS`**.
|
||||
> Make sure to **save** your changes and **reload the page** to activate them.
|
||||
> 
|
||||
|
||||
### 🔄 PiHole Sync
|
||||
- If using **PiHole**, devices can be synced automatically.
|
||||
- 📖 [PiHole configuration guide](./PIHOLE_GUIDE.md).
|
||||
**Initial configuration**: `ARPSCAN`, `INTRNT`
|
||||
|
||||
### 📦 Bulk Import
|
||||
> [!NOTE]
|
||||
> You can bulk-import devices via the [CSV import method](./DEVICES_BULK_EDITING.md).
|
||||
> [!NOTE]
|
||||
> `ARPSCAN` and `INTRNT` scan the current network. You can complement them with other `🔍 dev scanner` plugins like `NMAPDEV`, or import devices using `📥 importer` plugins.
|
||||
> See the [Subnet & VLAN Setup Guide](./SUBNETS.md) and [Remote Networks](./REMOTE_NETWORKS.md) for advanced configurations.
|
||||
|
||||
---
|
||||
|
||||
## 🌍 Community Guides
|
||||
### 2. Choose a Publisher Plugin
|
||||
|
||||
- Various community-written configuration guides in **Chinese, Korean, German, French**.
|
||||
- 📖 [Community Guides](./COMMUNITY_GUIDES.md)
|
||||
**Initial configuration**: `SMTP`
|
||||
|
||||
> ⚠️ **Note:** These guides may be outdated. Always refer to the official documentation first.
|
||||
> [!NOTE]
|
||||
> Configure your SMTP settings or enable additional `▶️ publisher` plugins to send alerts.
|
||||
> For more flexibility, try [📚 `_publisher_apprise`](/front/plugins/_publisher_apprise/), which supports over 80 notification services.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Common Issues
|
||||
### 3. Set Up a Network Topology Diagram
|
||||
|
||||
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)
|
||||
**Initial configuration**: The app auto-selects a root node (MAC `internet`) and attempts to identify other network devices by vendor or name.
|
||||
|
||||
> [!NOTE]
|
||||
> Visualize and manage your network using the [Network Guide](./NETWORK_TREE.md).
|
||||
> Some plugins (e.g., `UNFIMP`) build the topology automatically, or you can use [Custom Workflows](./WORKFLOWS.md) to generate it based on your own rules.
|
||||
|
||||
---
|
||||
|
||||
### 4. Configure Notifications
|
||||
|
||||

|
||||
|
||||
**Initial configuration**: Notifies on `new_devices`, `down_devices`, and `events` as defined in `NTFPRCS_INCLUDED_SECTIONS`.
|
||||
|
||||
> [!NOTE]
|
||||
> Notification settings support global, plugin-specific, and per-device rules.
|
||||
> For fine-tuning, refer to the [Notification Guide](./NOTIFICATIONS.md).
|
||||
|
||||
---
|
||||
|
||||
### 5. Set Up Workflows
|
||||
|
||||

|
||||
|
||||
**Initial configuration**: N/A
|
||||
|
||||
> [!NOTE]
|
||||
> Automate responses to device status changes, group management, topology updates, and more.
|
||||
> See the [Workflows Guide](./WORKFLOWS.md) to simplify your network operations.
|
||||
|
||||
---
|
||||
|
||||
### 6. Backup Your Configuration
|
||||
|
||||

|
||||
|
||||
**Initial configuration**: The `CSVBCKP` plugin creates a daily backup to `/config/devices.csv`.
|
||||
|
||||
> [!NOTE]
|
||||
> For a complete backup strategy, follow the [Backup Guide](./BACKUPS.md).
|
||||
|
||||
---
|
||||
|
||||
### 7. (Optional) Create Custom Plugins
|
||||
|
||||
[](https://youtu.be/cdbxlwiWhv8)
|
||||
|
||||
**Initial configuration**: N/A
|
||||
|
||||
> [!NOTE]
|
||||
> Build your own scanner, importer, or publisher plugin.
|
||||
> See the [Plugin Development Guide](./PLUGINS_DEV.md) and included video tutorials.
|
||||
|
||||
---
|
||||
|
||||
## 📁 Recommended Guides
|
||||
|
||||
* 📘 [PiHole Setup Guide](./PIHOLE_GUIDE.md)
|
||||
* 📘 [CSV Import Method](./DEVICES_BULK_EDITING.md)
|
||||
* 📘 [Community Guides (Chinese, Korean, German, French)](./COMMUNITY_GUIDES.md)
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Troubleshooting & Help
|
||||
|
||||
Before opening a new issue:
|
||||
|
||||
* 📘 [Common Issues](./COMMON_ISSUES.md)
|
||||
* 🧰 [Debugging Tips](./DEBUG_TIPS.md)
|
||||
* ✅ [Browse resolved GitHub issues](https://github.com/jokob-sk/NetAlertX/issues?q=is%3Aissue+is%3Aclosed)
|
||||
|
||||
---
|
||||
|
||||
Let me know if you want a condensed README version, separate pages for each section, or UI copy based on this!
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Migration form PiAlert to NetAlertX
|
||||
|
||||
> [!WARNING]
|
||||
> Follow this guide only after you you downloaded and started NetAlert X at least once after previously using the PiAlert image.
|
||||
> Follow this guide only after you you downloaded and started a version of NetAlertX prior to v25.6.7 (e.g. `docker pull ghcr.io/jokob-sk/netalertx:25.5.24`) at least once after previously using the PiAlert image. Later versions don't support migration and devices and settings will have to migrated manually, e.g. via [CSV import](./DEVICES_BULK_EDITING.md).
|
||||
|
||||
## STEPS:
|
||||
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
|
||||
Name resolution in NetAlertX relies on multiple plugins to resolve device names from IP addresses. If you are seeing `(name not found)` as device names, follow these steps to diagnose and fix the issue.
|
||||
|
||||
> [!TIP]
|
||||
> Before proceeding, make sure [Reverse DNS](./REVERSE_DNS.md) is enabled on your network.
|
||||
> You can control how names are handled and cleaned using the `NEWDEV_NAME_CLEANUP_REGEX` setting.
|
||||
> To auto-update Fully Qualified Domain Names (FQDN), enable the `REFRESH_FQDN` setting.
|
||||
|
||||
|
||||
## Required Plugins
|
||||
|
||||
For best results, ensure the following name resolution plugins are enabled:
|
||||
@@ -9,6 +15,7 @@ For best results, ensure the following name resolution plugins are enabled:
|
||||
- **AVAHISCAN** – Uses mDNS/Avahi to resolve local network names.
|
||||
- **NBTSCAN** – Queries NetBIOS to find device names.
|
||||
- **NSLOOKUP** – Performs standard DNS lookups.
|
||||
- **DIGSCAN** – Performs Name Resolution with the Dig utility (DNS).
|
||||
|
||||
You can check which plugins are active in your _Settings_ section and enable any that are missing.
|
||||
|
||||
|
||||
@@ -1,63 +1,104 @@
|
||||
## How to setup your Network page
|
||||
## How to Set Up Your Network Page
|
||||
|
||||
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`).
|
||||
The **Network** page lets you map how devices connect — visually and logically.
|
||||
It’s especially useful for planning infrastructure, assigning parent-child relationships, and spotting gaps.
|
||||
|
||||
> 💡 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**.
|
||||
To get started, you’ll need to define at least one root node and mark certain devices as network nodes (like Switches or Routers).
|
||||
|
||||
## ⚡Quick setup:
|
||||
---
|
||||
|
||||
* Go to a Device you want to use as network device (network nodes, such as a Switch).
|
||||
* Set the **Type** of such a device to one of the following: AP, Firewall, Gateway, PLC, Powerline, Router, Switch, USB LAN Adapter, USB WIFI Adapter and WLAN (you can create a custom network type device with in Settings -> General -> `NETWORK_DEVICE_TYPES`).
|
||||
* Save and go to Network where the devices you've marked as network devices (by selecting the Type as mentioned above) will show up as tabs.
|
||||
* You can now assign the Unassigend devices to the network node.
|
||||
* If port is empty or 0 a wifi icon is rendered, otherwise a ethernet port icon.
|
||||
Start by creating a root device with the MAC address `Internet`, if the application didn’t create one already.
|
||||
This special MAC address (`Internet`) is required for the root network node — no other value is currently supported.
|
||||
Set its **Type** to a valid network type — such as `Router` or `Gateway`.
|
||||
|
||||
> [!TIP]
|
||||
> If you don’t have one, use the [Create new device](./DEVICE_MANAGEMENT.md#dummy-devices) button on the **Devices** page to add a root device.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> [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:
|
||||
## ⚡ Quick Setup
|
||||
|
||||
In this example you will setup a device named `rapberrypi` as a `Switch` in our network.
|
||||
1. Open the device you want to use as a network node (e.g. a Switch).
|
||||
2. Set its **Type** to one of the following:
|
||||
`AP`, `Firewall`, `Gateway`, `PLC`, `Powerline`, `Router`, `Switch`, `USB LAN Adapter`, `USB WIFI Adapter`, `WLAN`
|
||||
*(Or add custom types under **Settings → General → `NETWORK_DEVICE_TYPES`**.)*
|
||||
3. Save the device.
|
||||
4. Go to the **Network** page — supported device types will appear as tabs.
|
||||
5. Use the **Assign** button to connect unassigned devices to a network node.
|
||||
6. If the **Port** is `0` or empty, a Wi-Fi icon is shown. Otherwise, an Ethernet icon appears.
|
||||
|
||||
### 1. Device details page
|
||||
> [!NOTE]
|
||||
> Use [bulk editing](./DEVICES_BULK_EDITING.md) with _CSV Export_ to fix `Internet` root assignments or update many devices at once.
|
||||
|
||||
- Go to the `Devices` (1) page:
|
||||
---
|
||||
|
||||

|
||||
## Example: Setting up a `raspberrypi` as a Switch
|
||||
|
||||
- In the (2) `Details` tab navigate to the the `Type` (3) dropdown and select the type `Switch` (4).
|
||||
Let’s walk through setting up a device named `raspberrypi` to act as a network Switch that other devices connect through.
|
||||
|
||||
> Note: Only the following device types will show up as selectable Network nodes ( = devices you can connect other devices to):
|
||||
> AP, Firewall, Gateway, Hypervisor, PLC, Powerline, Router, Switch, USB LAN Adapter, USB WIFI Adapter and WLAN. Custom types can be added via the `NETWORK_DEVICE_TYPES` setting.
|
||||
---
|
||||
|
||||
- Assign a device to your root device from the `Node` (5) dropdown which has the MAC `Internet` (6) (Your name may differ, but the MAC needs to be set to `Internet` - this is done by default).
|
||||
### 1. Set Device Type and Parent
|
||||
|
||||
- Save your changes (7)
|
||||
- Go to the **Devices** page
|
||||
- Open the device detail view for `raspberrypi`
|
||||
- In the **Type** dropdown, select `Switch`
|
||||
|
||||
### 2. Network page
|
||||

|
||||
|
||||
- Navigate to your `Network` (1) page:
|
||||
- Optionally assign a **Parent Node** (where this device connects to) and the **Relationship type** of the connection.
|
||||
The `nic` relationship type can affect parent notifications — see the setting description and [Notifications documentation](./NOTIFICATIONS.md) for more.
|
||||
|
||||

|
||||

|
||||
|
||||
- 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)
|
||||
- We can now assign the device `(AppleTV)` (5) to this `raspberrypi` node, representing a network Switch in this example
|
||||
> [!NOTE]
|
||||
> Only certain device types can act as network nodes:
|
||||
> `AP`, `Firewall`, `Gateway`, `Hypervisor`, `PLC`, `Powerline`, `Router`, `Switch`, `USB LAN Adapter`, `USB WIFI Adapter`, `WLAN`
|
||||
> You can add custom types via the `NETWORK_DEVICE_TYPES` setting.
|
||||
|
||||
### 3. Network page with 2 levels
|
||||
- Click **Save**
|
||||
|
||||
- After clicking the `Assign` button in the previous section, the `(AppleTV)` (1) device is now connected to our `raspberrypi` (2).
|
||||
---
|
||||
|
||||

|
||||
### 2. Confirm The Device Appears as a Network Node
|
||||
|
||||
- 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).
|
||||
- You can also see that our `raspberrypi` node is connected to it's Parent network device node with the MAC `Internet` (5). This connection again shows up in the tree (6) as well.
|
||||
You can confirm that `raspberrypi` now acts as a network device in two places:
|
||||
|
||||
- Navigate to a different device and verify that `raspberrypi` now appears as an option for a **Parent Node**:
|
||||
|
||||

|
||||
|
||||
- Go to the **Network** page — you'll now see a `raspberrypi` tab, meaning it's recognized as a network node (Switch):
|
||||
|
||||

|
||||
|
||||
- You can now assign other devices to it.
|
||||
|
||||
---
|
||||
|
||||
### 3. Assign Connected Devices
|
||||
|
||||
- Use the **Assign** button to link other devices (e.g. PCs) to `raspberrypi`.
|
||||
- After assigning, connected devices will appear beneath the `raspberrypi` switch node.
|
||||
|
||||

|
||||
|
||||
- Relationship lines may vary in color based on the selected Relationship type. These are editable on the device details page where you can also assign a parent node.
|
||||
|
||||

|
||||
|
||||
> Hovering over devices in the tree reveals connection details and tooltips for quick inspection.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Summary
|
||||
|
||||
To configure devices on the **Network** page:
|
||||
|
||||
- Ensure a device with MAC `Internet` is set up as the root
|
||||
- Assign valid **Type** values to switches, routers, and other supported nodes that represent network devices
|
||||
- Use the **Assign** button to connect devices logically to their parent node
|
||||
|
||||
Need to reset or undo changes? [Use backups](./BACKUPS.md) or [bulk editing](./DEVICES_BULK_EDITING.md) to manage devices at scale. You can also automate device assignment with [Workflows](./WORKFLOWS.md).
|
||||
|
||||
@@ -15,11 +15,12 @@ There are 4 ways how to influence notifications:
|
||||
|
||||

|
||||
|
||||
There are 4 settings on the device for influencing notifications. You can:
|
||||
The following device properties influence notifications. You can:
|
||||
|
||||
1. **Alert Events** - Enables alerts of connections, disconnections, IP changes (down and down reconnected notifications are still sent even if this is disabled).
|
||||
2. **Alert Down** - Alerts when a device goes down. This setting overrides a disabled **Alert Events** setting, so you will get a notification of a device going down even if you don't have **Alert Events** ticked. Disabling this will disable down and down reconnected notifications on the device.
|
||||
3. **Skip repeated notifications**, if for example you know there is a temporary issue and want to pause the same notification for this device for a given time.
|
||||
4. **Require NICs Online** - Indicates whether this device should be considered online only if all associated NICs (devices with the `nic` relationship type) are online. If disabled, the device is considered online if any NIC is online. If a NIC is online it sets the parent (this) device's status to online irrespectivelly of the detected device's status. The Relationship type is set on the childern device.
|
||||
|
||||
> [!NOTE]
|
||||
> Please read through the [NTFPRCS plugin](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/notification_processing/README.md) documentation to understand how device and global settings influence the notification processing.
|
||||
|
||||
@@ -85,8 +85,8 @@ services:
|
||||
# (Optional) Useful for debugging setup issues
|
||||
- local/path/logs:/app/log
|
||||
# (API: OPTION 1) Store temporary files in memory (recommended for performance)
|
||||
- type: tmpfs # ◀
|
||||
target: /app/api # ◀
|
||||
- type: tmpfs # ◀ 🔺
|
||||
target: /app/api # ◀ 🔺
|
||||
# (API: OPTION 2) Store API data on disk (useful for debugging)
|
||||
# - local/path/api:/app/api
|
||||
environment:
|
||||
|
||||
@@ -9,7 +9,7 @@ NetAlertX supports additional plugins to extend its functionality, each with its
|
||||
|
||||
> [!TIP]
|
||||
> You can load additional Plugins via the General -> `LOADED_PLUGINS` setting. You need to save the settings for the new plugins to load (cache/page reload may be necessary).
|
||||
> 
|
||||
> 
|
||||
|
||||
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.
|
||||
@@ -43,54 +43,54 @@ NetAlertX supports additional plugins to extend its functionality, each with its
|
||||
|
||||
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/) |
|
||||
| `OMDSDNOPENAPI`| 📥/🆎 | OMADA TP-Link import via OpenAPI | 🖧 | | Script | [omada_sdn_openapi](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/omada_sdn_openapi/) |
|
||||
| `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/) |
|
||||
| ID | Plugin docs | Type | Description | Features | Required |
|
||||
| --------------- | ------------------------------------------------------------------------------------------------------------------ | -------- | ----------------------------------------- | -------- | -------- |
|
||||
| `APPRISE` | [_publisher_apprise](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/_publisher_apprise/) | ▶️ | Apprise notification proxy | | |
|
||||
| `ARPSCAN` | [arp_scan](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/arp_scan/) | 🔍 | ARP-scan on current network | | |
|
||||
| `AVAHISCAN` | [avahi_scan](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/avahi_scan/) | 🆎 | Avahi (mDNS-based) name resolution | | |
|
||||
| `ASUSWRT` | [asuswrt_import](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/asuswrt_import/) | 🔍 | Import connected devices from AsusWRT | | |
|
||||
| `CSVBCKP` | [csv_backup](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/csv_backup/) | ⚙ | CSV devices backup | | |
|
||||
| `CUSTPROP` | [custom_props](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/custom_props/) | ⚙ | Managing custom device properties values | | Yes |
|
||||
| `DBCLNP` | [db_cleanup](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/db_cleanup/) | ⚙ | Database cleanup | | Yes\* |
|
||||
| `DDNS` | [ddns_update](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/ddns_update/) | ⚙ | DDNS update | | |
|
||||
| `DHCPLSS` | [dhcp_leases](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/dhcp_leases/) | 🔍/📥/🆎 | Import devices from DHCP leases | | |
|
||||
| `DHCPSRVS` | [dhcp_servers](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/dhcp_servers/) | ♻ | DHCP servers | | |
|
||||
| `DIGSCAN` | [dig_scan](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/dig_scan/) | 🆎 | Dig (DNS) Name resolution | | |
|
||||
| `FREEBOX` | [freebox](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/freebox/) | 🔍/♻/🆎 | Pull data and names from Freebox/Iliadbox | | |
|
||||
| `ICMP` | [icmp_scan](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/icmp_scan/) | ♻ | ICMP (ping) status checker | | |
|
||||
| `INTRNT` | [internet_ip](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/internet_ip/) | 🔍 | Internet IP scanner | | |
|
||||
| `INTRSPD` | [internet_speedtest](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/internet_speedtest/) | ♻ | Internet speed test | | |
|
||||
| `IPNEIGH` | [ipneigh](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/ipneigh/) | 🔍 | Scan ARP (IPv4) and NDP (IPv6) tables | | |
|
||||
| `LUCIRPC` | [luci_import](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/luci_import/) | 🔍 | Import connected devices from OpenWRT | | |
|
||||
| `MAINT` | [maintenance](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/maintenance/) | ⚙ | Maintenance of logs, etc. | | |
|
||||
| `MQTT` | [_publisher_mqtt](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/_publisher_mqtt/) | ▶️ | MQTT for synching to Home Assistant | | |
|
||||
| `NBTSCAN` | [nbtscan_scan](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/nbtscan_scan/) | 🆎 | Nbtscan (NetBIOS-based) name resolution | | |
|
||||
| `NEWDEV` | [newdev_template](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/newdev_template/) | ⚙ | New device template | | Yes |
|
||||
| `NMAP` | [nmap_scan](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/nmap_scan/) | ♻ | Nmap port scanning & discovery | | |
|
||||
| `NMAPDEV` | [nmap_dev_scan](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/nmap_dev_scan/) | 🔍 | Nmap dev scan on current network | | |
|
||||
| `NSLOOKUP` | [nslookup_scan](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/nslookup_scan/) | 🆎 | NSLookup (DNS-based) name resolution | | |
|
||||
| `NTFPRCS` | [notification_processing](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/notification_processing/) | ⚙ | Notification processing | | Yes |
|
||||
| `NTFY` | [_publisher_ntfy](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/_publisher_ntfy/) | ▶️ | NTFY notifications | | |
|
||||
| `OMDSDN` | [omada_sdn_imp](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/omada_sdn_imp/) | 📥/🆎 ❌ | UNMAINTAINED use `OMDSDNOPENAPI` | 🖧 🔄 | |
|
||||
| `OMDSDNOPENAPI` | [omada_sdn_openapi](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/omada_sdn_openapi/) | 📥/🆎 | OMADA TP-Link import via OpenAPI | 🖧 | |
|
||||
| `PIHOLE` | [pihole_scan](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/pihole_scan/) | 🔍/🆎/📥 | Pi-hole device import & sync | | |
|
||||
| `PUSHSAFER` | [_publisher_pushsafer](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/_publisher_pushsafer/) | ▶️ | Pushsafer notifications | | |
|
||||
| `PUSHOVER` | [_publisher_pushover](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/_publisher_pushover/) | ▶️ | Pushover notifications | | |
|
||||
| `SETPWD` | [set_password](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/set_password/) | ⚙ | Set password | | Yes |
|
||||
| `SMTP` | [_publisher_email](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/_publisher_email/) | ▶️ | Email notifications | | |
|
||||
| `SNMPDSC` | [snmp_discovery](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/snmp_discovery/) | 🔍/📥 | SNMP device import & sync | | |
|
||||
| `SYNC` | [sync](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/sync/) | 🔍/⚙/📥 | Sync & import from NetAlertX instances | 🖧 🔄 | Yes |
|
||||
| `TELEGRAM` | [_publisher_telegram](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/_publisher_telegram/) | ▶️ | Telegram notifications | | |
|
||||
| `UI` | [ui_settings](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/ui_settings/) | ♻ | UI specific settings | | Yes |
|
||||
| `UNFIMP` | [unifi_import](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/unifi_import/) | 🔍/📥/🆎 | UniFi device import & sync | 🖧 | |
|
||||
| `VNDRPDT` | [vendor_update](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/vendor_update/) | ⚙ | Vendor database update | | |
|
||||
| `WEBHOOK` | [_publisher_webhook](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/_publisher_webhook/) | ▶️ | Webhook notifications | | |
|
||||
| `WEBMON` | [website_monitor](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/website_monitor/) | ♻ | Website down monitoring | | |
|
||||
| `WOL` | [wake_on_lan](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/wake_on_lan/) | ♻ | Automatic 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
|
||||
> ❌ marked for removal/unmaintained - looking for help
|
||||
> ⌚It's recommended to use the same schedule interval for all plugins responsible for discovering new devices.
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
|
||||
If you are running a DNS server, such as **AdGuard**, set up **Private reverse DNS servers** for a better name resolution on your network. Enabling this setting will enable NetAlertX to execute dig and nslookup commands to automatically resolve device names based on their IP addresses.
|
||||
|
||||
> [!TIP]
|
||||
> Before proceeding, ensure that [name resolution plugins](./NAME_RESOLUTION.md) are enabled.
|
||||
> You can customize how names are cleaned using the `NEWDEV_NAME_CLEANUP_REGEX` setting.
|
||||
> To auto-update Fully Qualified Domain Names (FQDN), enable the `REFRESH_FQDN` setting.
|
||||
|
||||
|
||||
> Example 1: Reverse DNS `disabled`
|
||||
>
|
||||
|
||||
107
docs/SECURITY.md
@@ -1,29 +1,102 @@
|
||||
# Securing your NetAlertX instance
|
||||
## 🧭 Responsibility Disclaimer
|
||||
|
||||
NetAlertX is an execution framework. In order to run scanners and plugins, the application has to have access to privileged system resources. It is not recommended to expose NetAlertX to the internet without taking basic security precautions. It is highly recommended to use a VPN to access the application and to set up a password for the web interface before exposing the UI online.
|
||||
NetAlertX provides powerful tools for network scanning, presence detection, and automation. However, **it is up to you—the deployer—to ensure that your instance is properly secured**.
|
||||
|
||||
## VPN
|
||||
This includes (but is not limited to):
|
||||
- Controlling who has access to the UI and API
|
||||
- Following network and container security best practices
|
||||
- Running NetAlertX only on networks where you have legal authorization
|
||||
- Keeping your deployment up to date with the latest patches
|
||||
|
||||
VPNs allow you to securely access your NetAlertX instance from remote locations without exposing it to the internet. A VPN encrypts your connection and prevents unauthorized access.
|
||||
> NetAlertX is not responsible for misuse, misconfiguration, or unsecure deployments. Always test and secure your setup before exposing it to the outside world.
|
||||
|
||||
### Tailscale as an Alternative
|
||||
# 🔐 Securing Your NetAlertX Instance
|
||||
|
||||
If setting up a traditional VPN is not ideal, you can use [Tailscale](https://tailscale.com/) as an easy alternative. Tailscale creates a secure, encrypted connection between your devices without complex configuration. Since NetAlertX is designed to be run on private networks, Tailscale can provide a simple way to securely connect to your instance from anywhere.
|
||||
NetAlertX is a powerful network scanning and automation framework. With that power comes responsibility. **It is your responsibility to secure your deployment**, especially if you're running it outside a trusted local environment.
|
||||
|
||||
## Setting a Password
|
||||
---
|
||||
|
||||
By default, NetAlertX does not enforce authentication, but it is highly recommended to set a password before exposing the web interface.
|
||||
## ⚠️ TL;DR – Key Security Recommendations
|
||||
|
||||
Configure `SETPWD_enable_password` to `true` and enter your password in `SETPWD_password`. When enabled, a login dialog is displayed. If facing issues, you can always disable the login by setting `SETPWD_enable_password=false` in your `app.conf` file.
|
||||
- ✅ **NEVER expose NetAlertX directly to the internet without protection**
|
||||
- ✅ Use a **VPN or Tailscale** to access remotely
|
||||
- ✅ Enable **password protection** for the web UI
|
||||
- ✅ Harden your container environment (e.g., no unnecessary privileges)
|
||||
- ✅ Use **firewalls and IP whitelisting**
|
||||
- ✅ Keep the software **updated**
|
||||
- ✅ Limit the scope of **plugins and API keys**
|
||||
|
||||
- The default password is `123456`.
|
||||
- Passwords are stored as SHA256 hashes for security.
|
||||
---
|
||||
|
||||
## Additional Security Measures
|
||||
## 🔗 Access Control with VPN (or Tailscale)
|
||||
|
||||
- **Firewall Rules**: Ensure that only trusted IPs can access the NetAlertX instance.
|
||||
- **Limit Plugin Permissions**: Only enable the plugins necessary for your setup.
|
||||
- **Keep Software Updated**: Regularly update NetAlertX to receive the latest security patches.
|
||||
- **Use Read-Only API Keys**: If exposing APIs, limit privileges with read-only keys where applicable.
|
||||
NetAlertX is designed to be run on **private LANs**, not the open internet.
|
||||
|
||||
By following these security recommendations, you can help protect your NetAlertX instance from unauthorized access and potential misuse.
|
||||
**Recommended**: Use a VPN to access NetAlertX from remote locations.
|
||||
|
||||
### ✅ Tailscale (Easy VPN Alternative)
|
||||
|
||||
Tailscale sets up a private mesh network between your devices. It's fast to configure and ideal for NetAlertX.
|
||||
👉 [Get started with Tailscale](https://tailscale.com/)
|
||||
|
||||
---
|
||||
|
||||
## 🔑 Web UI Password Protection
|
||||
|
||||
By default, NetAlertX does **not** require login. Before exposing the UI in any way:
|
||||
|
||||
1. Enable password protection:
|
||||
```ini
|
||||
SETPWD_enable_password=true
|
||||
SETPWD_password=your_secure_password
|
||||
```
|
||||
|
||||
2. Passwords are stored as SHA256 hashes
|
||||
|
||||
3. Default password (if not changed): 123456 — change it ASAP!
|
||||
|
||||
|
||||
> To disable authenticated login, set `SETPWD_enable_password=false` in `app.conf`
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 🔥 Additional Security Measures
|
||||
|
||||
- **Firewall / Network Rules**
|
||||
Restrict UI/API access to trusted IPs only.
|
||||
|
||||
- **Limit Docker Capabilities**
|
||||
Avoid `--privileged`. Use `--cap-add=NET_RAW` and others **only if required** by your scan method.
|
||||
|
||||
- **Keep NetAlertX Updated**
|
||||
Regular updates contain bug fixes and security patches.
|
||||
|
||||
- **Plugin Permissions**
|
||||
Disable unused plugins. Only install from trusted sources.
|
||||
|
||||
- **Use Read-Only API Keys**
|
||||
When integrating NetAlertX with other tools, scope keys tightly.
|
||||
|
||||
---
|
||||
|
||||
## 🧱 Docker Hardening Tips
|
||||
|
||||
- Use `read-only` mount options where possible (`:ro`)
|
||||
- Avoid running as `root` unless absolutely necessary
|
||||
- Consider using `docker scan` or other container image vulnerability scanners
|
||||
- Run with `--network host` **only on trusted networks** and only if needed for ARP-based scans
|
||||
|
||||
---
|
||||
|
||||
## 📣 Responsible Disclosure
|
||||
|
||||
If you discover a vulnerability or security concern, please report it **privately** to:
|
||||
|
||||
📧 [jokob@duck.com](mailto:jokob@duck.com?subject=NetAlertX%20Security%20Disclosure)
|
||||
|
||||
We take security seriously and will work to patch confirmed issues promptly. Your help in responsible disclosure is appreciated!
|
||||
|
||||
---
|
||||
|
||||
By following these recommendations, you can ensure your NetAlertX deployment is both powerful **and** secure.
|
||||
65
docs/SMTP.md
@@ -1,6 +1,49 @@
|
||||
# 📧 SMTP guides
|
||||
# 📧 SMTP server guides
|
||||
|
||||
## Using the GMX SMTP server
|
||||
The SMTP plugin supports any SMTP server. Here are some commonly used services to help speed up your configuration.
|
||||
|
||||
> [!NOTE]
|
||||
> If you are using a self hosted SMTP server ssh into the container and verify (e.g. via ping) that your server is reachable from within the NetAlertX container. See also how to ssh into the container if you are running it as a [Home Assistant](./HOME_ASSISTANT.md) addon.
|
||||
|
||||
## Gmail
|
||||
|
||||
1. Create an app password by following the instructions from Google, you need to Enable 2FA for this to work.
|
||||
[https://support.google.com/accounts/answer/185833](https://support.google.com/accounts/answer/185833)
|
||||
|
||||
2. Specify the following settings:
|
||||
|
||||
```python
|
||||
SMTP_RUN='on_notification'
|
||||
SMTP_SKIP_TLS=True
|
||||
SMTP_FORCE_SSL=True
|
||||
SMTP_PORT=465
|
||||
SMTP_SERVER='smtp.gmail.com'
|
||||
SMTP_PASS='16-digit passcode from google'
|
||||
SMTP_REPORT_TO='some_target_email@gmail.com'
|
||||
```
|
||||
|
||||
## Brevo
|
||||
|
||||
Brevo allows for 300 free emails per day as of time of writing.
|
||||
|
||||
1. Create an account on Brevo: https://www.brevo.com/free-smtp-server/
|
||||
2. Click your name -> SMTP & API
|
||||
3. Click Generate a new SMTP key
|
||||
4. Save the details and fill in the NetAlertX settings as below.
|
||||
|
||||
```python
|
||||
SMTP_SERVER='smtp-relay.brevo.com'
|
||||
SMTP_PORT=587
|
||||
SMTP_SKIP_LOGIN=False
|
||||
SMTP_USER='user@email.com'
|
||||
SMTP_PASS='xsmtpsib-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxx'
|
||||
SMTP_SKIP_TLS=False
|
||||
SMTP_FORCE_SSL=False
|
||||
SMTP_REPORT_TO='some_target_email@gmail.com'
|
||||
SMTP_REPORT_FROM='NetAlertX <user@email.com>'
|
||||
```
|
||||
|
||||
## GMX
|
||||
|
||||
1. Go to your GMX account https://account.gmx.com
|
||||
2. Under Security Options enable 2FA (Two-factor authentication)
|
||||
@@ -21,21 +64,3 @@
|
||||
SMTP_REPORT_TO='some_target_email@gmail.com'
|
||||
```
|
||||
|
||||
|
||||
## Using the Gmail SMTP server
|
||||
|
||||
1. Create an app password by following the instructions from Google, you need to Enable 2FA for this to work.
|
||||
[https://support.google.com/accounts/answer/185833](https://support.google.com/accounts/answer/185833)
|
||||
|
||||
2. Specify the following settings:
|
||||
|
||||
```python
|
||||
SMTP_RUN='on_notification'
|
||||
SMTP_SKIP_TLS=True
|
||||
SMTP_FORCE_SSL=True
|
||||
SMTP_PORT=465
|
||||
SMTP_SERVER='smtp.gmail.com'
|
||||
SMTP_PASS='16-digit passcode from google'
|
||||
SMTP_REPORT_TO='some_target_email@gmail.com'
|
||||
```
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
# Docker Update Strategies to upgrade NetAlertX
|
||||
|
||||
> [!WARNING]
|
||||
> For versions prior to `v25.6.7` upgrade to version `v25.5.24` first (`docker pull ghcr.io/jokob-sk/netalertx:25.5.24`) as later versions don't support a full upgrade. Alternatively, devices and settings can be migrated manually, e.g. via [CSV import](./DEVICES_BULK_EDITING.md).
|
||||
|
||||
This guide outlines approaches for updating Docker containers, usually when upgrading to a newer version of NetAlertX. Each method offers different benefits depending on the situation. Here are the methods:
|
||||
|
||||
- Manual: Direct commands to stop, remove, and rebuild containers.
|
||||
- Dockcheck: Semi-automated with more control, suited for bulk updates.
|
||||
- Watchtower: Fully automated, runs continuously to check and update containers.
|
||||
- Portainer: Manual with UI.
|
||||
|
||||
You can choose any approach that fits your workflow.
|
||||
|
||||
@@ -104,10 +108,42 @@ docker run -d \
|
||||
|
||||
```
|
||||
|
||||
## 4. Portainer controlled image
|
||||
|
||||
This assumes you're using Portainer to manage Docker (or Docker Swarm) and want to pull the latest version of an image and redeploy the container.
|
||||
|
||||
> [!NOTE]
|
||||
> * Portainer does **not auto-update** containers. For automation, use **Watchtower** or similar tools.
|
||||
> * Make sure you have the [persistent volumes mounted or backups ready](BACKUPS.md) before recreating.
|
||||
|
||||
### 4.1 Steps to Update an Image in Portainer (Standalone Docker)
|
||||
|
||||
1. **Login to Portainer.**
|
||||
2. Go to **"Containers"** in the left sidebar.
|
||||
3. Find the container you want to update, click its name.
|
||||
4. Click **"Recreate"** (top right).
|
||||
5. **Tick**: _Pull latest image_ (this ensures Portainer fetches the newest version from Docker Hub or your registry).
|
||||
6. Click **"Recreate"** again.
|
||||
7. Wait for the container to be stopped, removed, and recreated with the updated image.
|
||||
|
||||
### 4.2 For Docker Swarm Services
|
||||
|
||||
If you're using Docker Swarm (under **"Stacks"** or **"Services"**):
|
||||
|
||||
1. Go to **"Stacks"**.
|
||||
2. Select the stack managing the container.
|
||||
3. Click **"Editor"** (or "Update the Stack").
|
||||
4. Add a version tag or use `:latest` if your image tag is `latest` (not recommended for production).
|
||||
5. Click **"Update the Stack"**. ⚠ Portainer will not pull the new image unless the tag changes OR the stack is forced to recreate.
|
||||
6. If image tag hasn't changed, go to **"Services"**, find the service, and click **"Force Update"**.
|
||||
|
||||
## Summary
|
||||
|
||||
- Manual: Ideal for individual or critical updates.
|
||||
- Dockcheck: Suitable for controlled, mass updates.
|
||||
- Watchtower: Fully automated, best for continuous deployment setups.
|
||||
| Method | Type | Pros | Cons |
|
||||
|------------|--------------|----------------------------------|------------------------------|
|
||||
| Manual | CLI | Full control, no dependencies | Tedious for many containers |
|
||||
| Dockcheck | CLI Script | Great for batch updates | Needs setup, semi-automated |
|
||||
| Watchtower | Daemonized | Fully automated updates | Less control, version drift |
|
||||
| Portainer | UI | Easy via web interface | No auto-updates |
|
||||
|
||||
These approaches allow you to maintain flexibility in how you update Docker containers, depending on the urgency and scale of the update.
|
||||
|
||||
@@ -22,4 +22,4 @@ For a comparison, this is how the UI looks like if you are on the latest stable
|
||||
|
||||
## Implementation details
|
||||
|
||||
During build a [/app/front/buildtimestamp.txt](https://github.com/jokob-sk/NetAlertX/blob/092797e75ccfa8359444ad149e727358ac4da05f/Dockerfile#L44) file is created. The app then periodically checks if a new release is available with a newer timestamp in GitHub's rest-based JSON endpoint (check the `def isNewVersion():` method for details).
|
||||
During build a [/app/front/buildtimestamp.txt](https://github.com/jokob-sk/NetAlertX/blob/092797e75ccfa8359444ad149e727358ac4da05f/Dockerfile#L44) file is created. The app then periodically checks if a new release is available with a newer timestamp in GitHub's rest-based JSON endpoint (check the `def isNewVersion:` method for details).
|
||||
@@ -1,5 +1,8 @@
|
||||
### Create a simple n8n workflow
|
||||
|
||||
> [!NOTE]
|
||||
> You need to enable the `WEBHOOK` plugin first in order to follow this guide. See the [Plugins guide](./PLUGINS.md) for details.
|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
# Webhook Secrets
|
||||
|
||||
> [!NOTE]
|
||||
> You need to enable the `WEBHOOK` plugin first in order to follow this guide. See the [Plugins guide](./PLUGINS.md) for details.
|
||||
|
||||
## How does the signing work?
|
||||
|
||||
NetAlertX will use the configured secret to create a hash signature of the request body. This SHA256-HMAC signature will appear in the `X-Webhook-Signature` header of each request to the webhook target URL. You can use the value of this header to validate the request was sent by NetAlertX.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Workflows Overview
|
||||
|
||||
The workflows module in NetAlertX allows to automate repetitive tasks, making network management more efficient. Whether you need to assign newly discovered devices to a specific Network Node, auto-group devices from a given vendor, unarchive a device if detected online, or automatically delete devices, this module provides the flexibility to tailor the automations to your needs.
|
||||
The workflows module in allows to automate repetitive tasks, making network management more efficient. Whether you need to assign newly discovered devices to a specific Network Node, auto-group devices from a given vendor, unarchive a device if detected online, or automatically delete devices, this module provides the flexibility to tailor the automations to your needs.
|
||||
|
||||

|
||||
|
||||
@@ -63,68 +63,8 @@ You can include multiple actions that should execute once the conditions are met
|
||||
|
||||
# Examples
|
||||
|
||||
Below you can find a couple of configuration examples.
|
||||
You can find a couple of configuration examples in [Workflow Examples](WORKFLOW_EXAMPLES.md).
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Example 1: Assign Device to Network Node Based on IP
|
||||
|
||||
This workflow assigns newly added devices with IP addresses in the `192.168.1.*` range to the device with the MAC address `6c:6d:6d:6c:6c:6c`.
|
||||
|
||||
### Trigger:
|
||||
- **Object Type**: `Devices`
|
||||
- **Event Type**: `insert`
|
||||
|
||||
### Conditions:
|
||||
- **Logic**: `AND`
|
||||
- `Field`: `devLastIP`
|
||||
- `Operator`: `contains`
|
||||
- `Value`: `192.168.1.`
|
||||
|
||||
This condition ensures that the workflow only applies to devices with an IP address in the `192.168.1.*` range.
|
||||
|
||||
### Actions:
|
||||
- **Action Type**: `update_field`
|
||||
- **Field**: `devNetworkNode`
|
||||
- **Value**: `6c:6d:6d:6c:6c:6c`
|
||||
|
||||
---
|
||||
|
||||
## Example 2: Mark Device as Not New and Delete If from Google Vendor
|
||||
|
||||
This workflow automates the process of marking Google devices as not new and deleting them if they meet the criteria.
|
||||
|
||||
### Trigger:
|
||||
- **Object Type**: `Devices`
|
||||
- **Event Type**: `update`
|
||||
|
||||
### Conditions:
|
||||
- **Logic**: `AND`
|
||||
- `Field`: `devVendor`
|
||||
- `Operator`: `contains`
|
||||
- `Value`: `Google`
|
||||
|
||||
This condition checks if the device's vendor is `Google`.
|
||||
|
||||
- **Logic**: `AND`
|
||||
- `Field`: `devIsNew`
|
||||
- `Operator`: `equals`
|
||||
- `Value`: `1`
|
||||
|
||||
This ensures the workflow applies only to new devices.
|
||||
|
||||
### Actions:
|
||||
1. **Action Type**: `update_field`
|
||||
- **Field**: `devIsNew`
|
||||
- **Value**: `0`
|
||||
|
||||
This action marks the device as no longer new.
|
||||
|
||||
2. **Action Type**: `delete_device`
|
||||
|
||||
This action deletes the device after it is marked as not new.
|
||||
|
||||
> [!TIP]
|
||||
> Share your workflows in [Discord](https://discord.com/invite/NczTUTWyRr) or [GitHub Discussions](https://github.com/jokob-sk/NetAlertX/discussions).
|
||||
|
||||
185
docs/WORKFLOW_EXAMPLES.md
Executable file
@@ -0,0 +1,185 @@
|
||||
# Workflow examples
|
||||
|
||||
Workflows in NetAlertX automate actions based on real-time events and conditions. Below are practical examples that demonstrate how to build automation using triggers, conditions, and actions.
|
||||
|
||||
## Example 1: Un-archive devices if detected online
|
||||
|
||||
This workflow automatically unarchives a device if it was previously archived but has now been detected as online.
|
||||
|
||||
### 📋 Use Case
|
||||
|
||||
Sometimes devices are manually archived (e.g., no longer expected on the network), but they reappear unexpectedly. This workflow reverses the archive status when such devices are detected during a scan.
|
||||
|
||||
### ⚙️ Workflow Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Un-archive devices if detected online",
|
||||
"trigger": {
|
||||
"object_type": "Devices",
|
||||
"event_type": "update"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"logic": "AND",
|
||||
"conditions": [
|
||||
{
|
||||
"field": "devIsArchived",
|
||||
"operator": "equals",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"field": "devPresentLastScan",
|
||||
"operator": "equals",
|
||||
"value": "1"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"type": "update_field",
|
||||
"field": "devIsArchived",
|
||||
"value": "0"
|
||||
}
|
||||
],
|
||||
"enabled": "Yes"
|
||||
}
|
||||
```
|
||||
|
||||
### 🔍 Explanation
|
||||
|
||||
- Trigger: Listens for updates to device records.
|
||||
- Conditions:
|
||||
- `devIsArchived` is `1` (archived).
|
||||
- `devPresentLastScan` is `1` (device was detected in the latest scan).
|
||||
- Action: Updates the device to set `devIsArchived` to `0` (unarchived).
|
||||
|
||||
### ✅ Result
|
||||
|
||||
Whenever a previously archived device shows up during a network scan, it will be automatically unarchived — allowing it to reappear in your device lists and dashboards.
|
||||
|
||||
|
||||
Here is your updated version of **Example 2** and **Example 3**, fully aligned with the format and structure of **Example 1** for consistency and professionalism:
|
||||
|
||||
---
|
||||
|
||||
## Example 2: Assign Device to Network Node Based on IP
|
||||
|
||||
This workflow assigns newly added devices with IP addresses in the `192.168.1.*` range to a specific network node with MAC address `6c:6d:6d:6c:6c:6c`.
|
||||
|
||||
### 📋 Use Case
|
||||
|
||||
When new devices join your network, assigning them to the correct network node is important for accurate topology and grouping. This workflow ensures devices in a specific subnet are automatically linked to the intended node.
|
||||
|
||||
### ⚙️ Workflow Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Assign Device to Network Node Based on IP",
|
||||
"trigger": {
|
||||
"object_type": "Devices",
|
||||
"event_type": "insert"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"logic": "AND",
|
||||
"conditions": [
|
||||
{
|
||||
"field": "devLastIP",
|
||||
"operator": "contains",
|
||||
"value": "192.168.1."
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"type": "update_field",
|
||||
"field": "devNetworkNode",
|
||||
"value": "6c:6d:6d:6c:6c:6c"
|
||||
}
|
||||
],
|
||||
"enabled": "Yes"
|
||||
}
|
||||
```
|
||||
|
||||
### 🔍 Explanation
|
||||
|
||||
* **Trigger**: Activates when a new device is added.
|
||||
* **Condition**:
|
||||
|
||||
* `devLastIP` contains `192.168.1.` (matches subnet).
|
||||
* **Action**:
|
||||
|
||||
* Sets `devNetworkNode` to the specified MAC address.
|
||||
|
||||
### ✅ Result
|
||||
|
||||
New devices with IPs in the `192.168.1.*` subnet are automatically assigned to the correct network node, streamlining device organization and reducing manual work.
|
||||
|
||||
---
|
||||
|
||||
## Example 3: Mark Device as Not New and Delete If from Google Vendor
|
||||
|
||||
This workflow automatically marks newly detected Google devices as not new and deletes them immediately.
|
||||
|
||||
### 📋 Use Case
|
||||
|
||||
You may want to automatically clear out newly detected Google devices (such as Chromecast or Google Home) if they’re not needed in your device database. This workflow handles that clean-up automatically.
|
||||
|
||||
### ⚙️ Workflow Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Mark Device as Not New and Delete If from Google Vendor",
|
||||
"trigger": {
|
||||
"object_type": "Devices",
|
||||
"event_type": "update"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"logic": "AND",
|
||||
"conditions": [
|
||||
{
|
||||
"field": "devVendor",
|
||||
"operator": "contains",
|
||||
"value": "Google"
|
||||
},
|
||||
{
|
||||
"field": "devIsNew",
|
||||
"operator": "equals",
|
||||
"value": "1"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"type": "update_field",
|
||||
"field": "devIsNew",
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"type": "delete_device"
|
||||
}
|
||||
],
|
||||
"enabled": "Yes"
|
||||
}
|
||||
```
|
||||
|
||||
### 🔍 Explanation
|
||||
|
||||
* **Trigger**: Runs on device updates.
|
||||
* **Conditions**:
|
||||
|
||||
* Vendor contains `Google`.
|
||||
* Device is marked as new (`devIsNew` is `1`).
|
||||
* **Actions**:
|
||||
|
||||
1. Set `devIsNew` to `0` (mark as not new).
|
||||
2. Delete the device.
|
||||
|
||||
### ✅ Result
|
||||
|
||||
Any newly detected Google devices are cleaned up instantly — first marked as not new, then deleted — helping you avoid clutter in your device records.
|
||||
BIN
docs/img/DEBUG_GRAPHQL/Init_check.png
Executable file
|
After Width: | Height: | Size: 135 KiB |
BIN
docs/img/DEBUG_GRAPHQL/app_conf_graphql_port.png
Executable file
|
After Width: | Height: | Size: 11 KiB |
BIN
docs/img/DEBUG_GRAPHQL/dev_console_graphql_json.png
Executable file
|
After Width: | Height: | Size: 32 KiB |
BIN
docs/img/DEBUG_GRAPHQL/graphql_running_logs.png
Executable file
|
After Width: | Height: | Size: 36 KiB |
BIN
docs/img/DEBUG_GRAPHQL/graphql_settings_port_token.png
Executable file
|
After Width: | Height: | Size: 34 KiB |
BIN
docs/img/DEBUG_GRAPHQL/network_graphql.png
Executable file
|
After Width: | Height: | Size: 35 KiB |
BIN
docs/img/DEVICE_MANAGEMENT/device_management_status_colors.png
Executable file
|
After Width: | Height: | Size: 39 KiB |
BIN
docs/img/FIX_OFFLINE_DETECTION/presence_graph_before_after.png
Executable file
|
After Width: | Height: | Size: 486 KiB |
|
Before Width: | Height: | Size: 100 KiB |
BIN
docs/img/NETWORK_TREE/Network_Assign.png
Executable file
|
After Width: | Height: | Size: 73 KiB |
BIN
docs/img/NETWORK_TREE/Network_Assigned_Nodes.png
Executable file
|
After Width: | Height: | Size: 92 KiB |
BIN
docs/img/NETWORK_TREE/Network_Device_Details.png
Executable file
|
After Width: | Height: | Size: 58 KiB |
BIN
docs/img/NETWORK_TREE/Network_Device_Details_Parent.png
Executable file
|
After Width: | Height: | Size: 34 KiB |
BIN
docs/img/NETWORK_TREE/Network_Device_ParentDropdown.png
Executable file
|
After Width: | Height: | Size: 42 KiB |
BIN
docs/img/NETWORK_TREE/Network_Device_type.png
Executable file
|
After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 185 KiB |
|
Before Width: | Height: | Size: 89 KiB |
BIN
docs/img/NETWORK_TREE/Network_Sample.png
Executable file
|
After Width: | Height: | Size: 81 KiB |
BIN
docs/img/NETWORK_TREE/Network_tree_details.png
Executable file
|
After Width: | Height: | Size: 93 KiB |
BIN
docs/img/NETWORK_TREE/Network_tree_setup_hover.png
Executable file
|
After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 71 KiB |
BIN
docs/img/PLUGINS/enable_plugin.gif
Executable file
|
After Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 8.0 KiB |
BIN
docs/img/netalertx_docs_old.png
Executable file
|
After Width: | Height: | Size: 7.5 KiB |
@@ -2,6 +2,8 @@
|
||||
|
||||
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:
|
||||
@@ -13,6 +15,8 @@ NetAlertX provides contextual help within the application:
|
||||
|
||||
## Installation Guides
|
||||
|
||||
The app can be installed different ways, with the best support of the docker-based deployments. This includes the Home Assistant and Unraid installation approaches. See details below.
|
||||
|
||||
### Docker (Fully Supported)
|
||||
|
||||
NetAlertX is fully supported in Docker environments, allowing for easy setup and configuration. Follow the official guide to get started:
|
||||
@@ -21,12 +25,25 @@ NetAlertX is fully supported in Docker environments, allowing for easy setup and
|
||||
|
||||
This guide will take you through the process of setting up NetAlertX using Docker Compose or standalone Docker commands.
|
||||
|
||||
### Home Assistant (Fully Supported)
|
||||
|
||||
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/) repository. 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.
|
||||
|
||||
- [[Installation] Home Assistant](https://github.com/alexbelgium/hassio-addons/tree/master/netalertx)
|
||||
|
||||
### Unraid (Partial Support)
|
||||
|
||||
The Unraid template was created by the community, so it's only partially supported. Alternatively, here is [another version of the Unraid template](https://github.com/jokob-sk/NetAlertX-unraid).
|
||||
|
||||
- [[Installation] Unraid App](https://unraid.net/community/apps)
|
||||
|
||||
### 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.
|
||||
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 are looking for maintainers to help improve it.
|
||||
|
||||
- [Bare-Metal Installation Guide](./HW_INSTALL.md)
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Help and Support
|
||||
@@ -66,14 +83,10 @@ To keep up with the latest changes and updates to NetAlertX, please refer to the
|
||||
Make sure to follow the project on GitHub to get notifications for new releases and important updates.
|
||||
|
||||
---
|
||||
## Additional info
|
||||
|
||||
## 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!
|
||||
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).
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
<span class="helpIcon">
|
||||
<a target="_blank" href="https://github.com/jokob-sk/NetAlertX/blob/main/docs/WORKFLOWS_DEBUGGING.md">
|
||||
<i class="fa fa-circle-question"></i>
|
||||
</a>
|
||||
</span>
|
||||
<section class="content">
|
||||
<div class="nav-tabs-custom app-event-content" style="margin-bottom: 0px;">
|
||||
<ul id="tabs-location" class="nav nav-tabs col-sm-2 hidden">
|
||||
@@ -19,7 +24,7 @@ showSpinner()
|
||||
$(document).ready(function() {
|
||||
|
||||
// Load JSON data from the provided URL
|
||||
$.getJSON('/php/server/query_json.php?file=table_appevents.json', function(data) {
|
||||
$.getJSON('php/server/query_json.php?file=table_appevents.json', function(data) {
|
||||
// Process the JSON data and generate UI dynamically
|
||||
processData(data)
|
||||
|
||||
|
||||
@@ -12,10 +12,12 @@
|
||||
----------------------------------------------------------------------------- */
|
||||
:root {
|
||||
--color-aqua: #00c0ef;
|
||||
--color-lightblue: #3c8dbc;
|
||||
--color-blue: #0060df;
|
||||
--color-green: #00a65a;
|
||||
--color-yellow: #f39c12;
|
||||
--color-red: #dd4b39;
|
||||
--color-gray: #8c8c8c;
|
||||
}
|
||||
|
||||
.input-group .checkbox
|
||||
@@ -28,6 +30,45 @@ h5
|
||||
font-size: medium;
|
||||
}
|
||||
|
||||
a[target="_blank"] {
|
||||
position: relative;
|
||||
display: inline-block; /* Needed for positioning */
|
||||
padding-right: 0.6em; /* Space for the icon */
|
||||
}
|
||||
|
||||
a[target="_blank"]::after {
|
||||
content: '↗';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
font-size: 0.75em;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.select2 .hover-node-info::after {
|
||||
padding-left: 1px ;
|
||||
}
|
||||
|
||||
/* .node-standard-device .netNodeText::after
|
||||
{
|
||||
right: -7px;
|
||||
top: 1px;
|
||||
} */
|
||||
|
||||
/* .select2-container--default .select2-selection--multiple .select2-selection__choice
|
||||
{
|
||||
padding-right: 15px !important;
|
||||
} */
|
||||
|
||||
.hoverHighlight
|
||||
{
|
||||
opacity: 0.7;
|
||||
}
|
||||
.hoverHighlight:hover
|
||||
{
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Helper Classes
|
||||
----------------------------------------------------------------------------- */
|
||||
@@ -48,6 +89,7 @@ h5
|
||||
float: inline-end;
|
||||
}
|
||||
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Text Classes
|
||||
----------------------------------------------------------------------------- */
|
||||
@@ -340,6 +382,11 @@ body
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.networkTable .nav-tabs-custom
|
||||
{
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.pa-small-box-2 .inner h3 {
|
||||
margin-left: 0em;
|
||||
margin-bottom: 1.3em;
|
||||
@@ -474,7 +521,7 @@ body
|
||||
}
|
||||
|
||||
.bottom-border-primary {
|
||||
border-bottom-color: #3c8dbc;
|
||||
border-bottom-color: var(--color-lightblue);
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 3px
|
||||
}
|
||||
@@ -695,6 +742,39 @@ body
|
||||
|
||||
/* maintenance buttons */
|
||||
|
||||
#file-check-list{
|
||||
display: block;
|
||||
}
|
||||
|
||||
.file-checking .icon-wrap{
|
||||
width: 200px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.file-checking .icon-wrap i{
|
||||
position: absolute;
|
||||
font-size: xx-large;
|
||||
right: 0;
|
||||
top: 0;
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.file-checking .file-name-wrap{
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: flex;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.file-checking{
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
|
||||
.dbtools-button {
|
||||
display: inline-block;
|
||||
width: 160px;
|
||||
@@ -842,6 +922,15 @@ height: 50px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#notifications .notification-buttons{
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
#notifications .notification-box{
|
||||
min-height: 90vh;
|
||||
}
|
||||
|
||||
|
||||
#notificationData textarea{
|
||||
width: 100%;
|
||||
}
|
||||
@@ -994,7 +1083,7 @@ height: 50px;
|
||||
|
||||
.myhidden
|
||||
{
|
||||
display:none;
|
||||
display:none !important;
|
||||
}
|
||||
|
||||
.center
|
||||
@@ -1098,7 +1187,7 @@ height: 50px;
|
||||
input[readonly] {
|
||||
/* Apply styles to the readonly input */
|
||||
background-color: #646566 !important;
|
||||
color: #000;
|
||||
color: #e6e6e6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@@ -1316,6 +1405,7 @@ input[readonly] {
|
||||
|
||||
.iconPreview {
|
||||
min-width: 40px;
|
||||
/* display: inherit; */
|
||||
}
|
||||
|
||||
.iconPreview svg{
|
||||
@@ -1361,7 +1451,8 @@ input[readonly] {
|
||||
cursor: -webkit-grab;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection--multiple .select2-selection__choice
|
||||
#settingsPage .select2-container--default .select2-selection--multiple .select2-selection__choice,
|
||||
#maintenancePage .select2-container--default .select2-selection--multiple .select2-selection__choice
|
||||
{
|
||||
background-color:#258744 !important;
|
||||
}
|
||||
@@ -1375,6 +1466,15 @@ input[readonly] {
|
||||
background-color:#606060 !important;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection--multiple,
|
||||
.select2-container--default .select2-selection--single
|
||||
{
|
||||
border-radius: 0px !important;
|
||||
border-color: #d2d6de !important;
|
||||
min-height: 42px;
|
||||
}
|
||||
|
||||
|
||||
.helpIconSmallTopRight{
|
||||
position: absolute;
|
||||
font-size: x-small;
|
||||
@@ -1411,6 +1511,11 @@ input[readonly] {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#panDetails .input-group {
|
||||
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
#devicePageInfoPlc
|
||||
{
|
||||
display: none;
|
||||
@@ -1418,6 +1523,11 @@ input[readonly] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.small-box:hover
|
||||
{
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------- */
|
||||
/* Device details */
|
||||
/* ----------------------------------------------------------------- */
|
||||
@@ -1484,18 +1594,98 @@ input[readonly] {
|
||||
}
|
||||
/* #panDetails .dataTables_wrapper .bottom .paging_simple_numbers */
|
||||
|
||||
#panDetails #NEWDEV_devIcon
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#panDetails #NEWDEV_devCustomProps_label
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
#deviceDetailsEdit .select2-container--default .select2-selection--multiple .select2-selection__choice a
|
||||
{
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#deviceDetailsEdit .iconPreview svg
|
||||
{
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
#deviceDetailsEdit .select2-container--default .select2-selection--multiple .select2-selection__choice
|
||||
{
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
#deviceDetailsEdit .select2-container--disabled
|
||||
{
|
||||
background-color: #606060;
|
||||
}
|
||||
|
||||
#deviceDetailsEdit .select2-container--default .select2-selection--multiple .select2-selection__choice span
|
||||
{
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#deviceDetailsEdit .select2-selection
|
||||
{
|
||||
width: initial;
|
||||
display: inline-block;
|
||||
min-height: 42px;
|
||||
}
|
||||
|
||||
/* Remove the default Select2 chevron (the down arrow) */
|
||||
.select2-container .select2-selection__arrow b {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Add custom icon */
|
||||
.select2-container .select2-selection__arrow::after {
|
||||
font-family: 'Font Awesome 6 Free';
|
||||
content: "\f078"; /* fa-chevron-down */
|
||||
font-weight: 700;
|
||||
position: absolute;
|
||||
top: 75%;
|
||||
left: 30%;
|
||||
transform: translate(-50%, -50%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#deviceDetailsEdit .form-control
|
||||
{
|
||||
min-height: 42px;
|
||||
}
|
||||
|
||||
.select2-selection--single .custom-chip
|
||||
{
|
||||
margin-top: 11px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection--single .select2-selection__rendered,
|
||||
.select2-container--default .select2-selection--single, .select2-selection .select2-selection--single
|
||||
{
|
||||
padding: 0px 0px;
|
||||
min-height: 42px;
|
||||
}
|
||||
|
||||
/* .select2-container--default .select2-selection--single .select2-selection__rendered, */
|
||||
.select2-container--default .select2-selection--single
|
||||
{
|
||||
/* color:initial !important; */
|
||||
background-color:initial !important;
|
||||
}
|
||||
|
||||
|
||||
#deviceDetailsEdit .select2-container
|
||||
{
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
#deviceDetailsEdit .select2-container .selection
|
||||
{
|
||||
width: 100% !important;
|
||||
display: inline-grid;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------- */
|
||||
/* MODAL popups */
|
||||
/* ----------------------------------------------------------------- */
|
||||
@@ -1514,10 +1704,30 @@ input[readonly] {
|
||||
/* NETWORK page */
|
||||
/* ----------------------------------------------------------------- */
|
||||
|
||||
.hide-node-names .node-name {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#toggleFilters
|
||||
{
|
||||
display: block;
|
||||
position: fixed;
|
||||
padding-left: 32px;
|
||||
padding-top: 10px;
|
||||
background-color: inherit;
|
||||
z-index: 3;
|
||||
width: 190px;
|
||||
box-shadow: 4px 4px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* AdminLTE overrides */
|
||||
.content-wrapper {
|
||||
min-height: calc(100vh - 31px) !important;
|
||||
}
|
||||
|
||||
#networkTree .box
|
||||
{
|
||||
/* border-top:1px; */
|
||||
border-width:1px;
|
||||
border-top-color:grey;
|
||||
padding:0px;
|
||||
margin:0px;
|
||||
@@ -1553,6 +1763,7 @@ input[readonly] {
|
||||
opacity: 0.3;
|
||||
display: initial;
|
||||
float: left;
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
#networkTree
|
||||
@@ -1576,6 +1787,42 @@ input[readonly] {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#hover-box
|
||||
{
|
||||
background-color: #ffffff;;
|
||||
}
|
||||
|
||||
#hover-box .iconPreview
|
||||
{
|
||||
padding: 0px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#hover-box .devName
|
||||
{
|
||||
font-size: larger;
|
||||
display: contents;
|
||||
}
|
||||
|
||||
#hover-box b
|
||||
{
|
||||
float: left;
|
||||
}
|
||||
|
||||
#hover-box .line
|
||||
{
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#hover-box span
|
||||
{
|
||||
float: right;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
#networkTree .netCollapse
|
||||
{
|
||||
@@ -1583,11 +1830,39 @@ input[readonly] {
|
||||
right: 0;
|
||||
margin-right: -3px;
|
||||
}
|
||||
|
||||
/* var(--color-aqua);
|
||||
--color-aqua: #00c0ef;
|
||||
--color-blue: #0060df;
|
||||
--color-green: #00a65a;
|
||||
--color-yellow: #f39c12;
|
||||
--color-red: #dd4b39; */
|
||||
|
||||
#networkTree .node-inner.node-network-device:hover
|
||||
{
|
||||
box-shadow: var(--color-aqua) 0px 0px 20px;
|
||||
}
|
||||
|
||||
#networkTree .node-inner.node-standard-device:hover
|
||||
{
|
||||
box-shadow: var(--color-gray) 0px 0px 10px;
|
||||
}
|
||||
|
||||
#networkTree .network-hw-icon
|
||||
{
|
||||
position: absolute;
|
||||
margin-left: -0.4em;
|
||||
opacity: 0.3;
|
||||
margin-top: 0.1em;
|
||||
}
|
||||
|
||||
#networkTree .highlightedNode
|
||||
{
|
||||
/* border: solid; */
|
||||
border-color:cyan;
|
||||
border-color:var(--color-lightblue);
|
||||
box-shadow: var(--color-lightblue) 0px 0px 20px;
|
||||
}
|
||||
|
||||
#networkTree .netStatus-Off-line i,
|
||||
#networkTree .netStatus-Off-line svg
|
||||
{
|
||||
@@ -1611,6 +1886,23 @@ input[readonly] {
|
||||
/* margin-left: 0.2em; */
|
||||
}
|
||||
|
||||
.networkTable
|
||||
{
|
||||
padding-bottom: 1px;
|
||||
z-index: 3;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.networkNodeTabHeaders .icon i
|
||||
{
|
||||
padding-top: 8px !important;
|
||||
padding-left: 6px !important;
|
||||
}
|
||||
|
||||
.networkTable .box-body {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.networkTable .networkNodeTabHeaders a {
|
||||
display: block;
|
||||
height: 3em;
|
||||
@@ -1639,6 +1931,8 @@ input[readonly] {
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media (max-width: 767px) {
|
||||
|
||||
.networkNodeTabHeaders .node-name
|
||||
@@ -1669,7 +1963,10 @@ input[readonly] {
|
||||
/* PLUGINS page */
|
||||
/* ----------------------------------------------------------------- */
|
||||
|
||||
|
||||
#tabs-location
|
||||
{
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.plugin-filters
|
||||
{
|
||||
@@ -1684,15 +1981,31 @@ input[readonly] {
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
.plugin-content .left-nav{
|
||||
.plugin-content .nav-tabs li a
|
||||
{
|
||||
border-right-width: 0px;
|
||||
}
|
||||
|
||||
#tabs-content-location-wrap
|
||||
{
|
||||
min-height: 90vh;
|
||||
}
|
||||
|
||||
#tabs-content-location textarea {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.plugin-content .left-nav{
|
||||
width: calc(100%);
|
||||
padding-right: 0px;
|
||||
z-index: 2;
|
||||
background-color: inherit !important;
|
||||
}
|
||||
|
||||
.plugin-content #tabs-content-location
|
||||
{
|
||||
margin: 0px;
|
||||
/* padding-top: 0; */
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.integrations-plugins .content
|
||||
@@ -1760,6 +2073,9 @@ input[readonly] {
|
||||
.pluginBadge
|
||||
{
|
||||
float: right;
|
||||
margin-right: 10px;
|
||||
opacity: 0.6;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.pluginBadgeWrap
|
||||
@@ -1768,42 +2084,56 @@ input[readonly] {
|
||||
display: ruby;
|
||||
z-index: 1;
|
||||
position: sticky;
|
||||
margin-top: 1px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Spin
|
||||
----------------------------------------------------------------------------- */
|
||||
.pa_semitransparent-panel {
|
||||
position: absolute;
|
||||
width: 100%; /*calc (100% -40px);*/
|
||||
height: 100%;
|
||||
left: 0;
|
||||
#loadingSpinner {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
pointer-events: none;
|
||||
display: block;
|
||||
|
||||
opacity: 0.8;
|
||||
background-color: #fff;
|
||||
z-index: 800;
|
||||
}
|
||||
|
||||
.fa-spinner
|
||||
{
|
||||
font-size: initial;
|
||||
}
|
||||
|
||||
#loadingSpinner.visible {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.pa_semitransparent-panel {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
opacity: 0.8;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.pa_spinner {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
position: absolute;
|
||||
top: 100px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
padding: 15px;
|
||||
width: 200px;
|
||||
background-color: #fff;
|
||||
z-index: 801;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#loadingSpinner
|
||||
{
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
/* Multi-edit adjustements */
|
||||
.box-header
|
||||
|
||||
@@ -10,6 +10,16 @@
|
||||
*
|
||||
* Additional fixes For Pi.Alert UI by leiweibau */
|
||||
|
||||
:root {
|
||||
--color-aqua: #00c0ef;
|
||||
--color-lightblue: #3c8dbc;
|
||||
--color-blue: #0060df;
|
||||
--color-green: #00a65a;
|
||||
--color-yellow: #f39c12;
|
||||
--color-red: #dd4b39;
|
||||
--color-gray: #8c8c8c;
|
||||
}
|
||||
|
||||
:root {
|
||||
--datatable-bgcolor: rgba(64, 76, 88, 0.8);
|
||||
}
|
||||
@@ -649,8 +659,14 @@ input[type="password"]::-webkit-caps-lock-indicator {
|
||||
border-color: #888888;
|
||||
}
|
||||
.table-hover tbody tr:hover td, .table-hover tbody tr:hover th {
|
||||
background-color: rgb(189,192,198);
|
||||
color: #444;
|
||||
background-color: var(--datatable-bgcolor);
|
||||
color: var(--fbc-white);
|
||||
}
|
||||
|
||||
table.dataTable tbody tr.selected, table.dataTable tbody tr .selected
|
||||
{
|
||||
background-color: var(--datatable-bgcolor);
|
||||
color: var(--fbc-white);
|
||||
}
|
||||
|
||||
.db_info_table_cell:nth-child(1) {background: #272c30}
|
||||
@@ -722,7 +738,7 @@ input[type="password"]::-webkit-caps-lock-indicator {
|
||||
margin-left: 0px;
|
||||
}
|
||||
.small-box:hover .icon {
|
||||
font-size: 3.74em;
|
||||
font-size: 3em;
|
||||
}
|
||||
.small-box .icon {
|
||||
top: 0.01em;
|
||||
@@ -732,6 +748,36 @@ input[type="password"]::-webkit-caps-lock-indicator {
|
||||
background-color: #000 !important;
|
||||
}
|
||||
|
||||
|
||||
.select2-container--default .select2-selection--single {
|
||||
color: initial !important;
|
||||
background-color: #353c42 !important;
|
||||
}
|
||||
|
||||
/* Chevron color */
|
||||
.select2-container .select2-selection__arrow::after {
|
||||
color: #bec5cb;
|
||||
}
|
||||
|
||||
/* Chevron color */
|
||||
.select2-selection .select2-selection--single {
|
||||
color: #bec5cb;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection--multiple, .select2-container--default .select2-selection--single {
|
||||
border-color: #3d444b !important;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection--single .select2-selection__rendered .custom-chip
|
||||
{
|
||||
color: #bec5cb;
|
||||
}
|
||||
|
||||
#hover-box
|
||||
{
|
||||
background-color: #353c42 !important;
|
||||
}
|
||||
|
||||
.callout code {
|
||||
background-color: #fff !important;
|
||||
color:#000 !important;
|
||||
@@ -740,4 +786,9 @@ input[type="password"]::-webkit-caps-lock-indicator {
|
||||
.thresholdFormControl
|
||||
{
|
||||
color:#000;
|
||||
}
|
||||
|
||||
.btn:hover
|
||||
{
|
||||
color: var(--color-gray);
|
||||
}
|
||||
@@ -11,6 +11,16 @@
|
||||
* Additional fixes For Pi.Alert UI by leiweibau */
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
|
||||
:root {
|
||||
--color-aqua: #00c0ef;
|
||||
--color-lightblue: #3c8dbc;
|
||||
--color-blue: #0060df;
|
||||
--color-green: #00a65a;
|
||||
--color-yellow: #f39c12;
|
||||
--color-red: #dd4b39;
|
||||
--color-gray: #8c8c8c;
|
||||
}
|
||||
|
||||
:root {
|
||||
--datatable-bgcolor: rgba(64, 76, 88, 0.8);
|
||||
@@ -651,8 +661,14 @@
|
||||
border-color: #888888;
|
||||
}
|
||||
.table-hover tbody tr:hover td, .table-hover tbody tr:hover th {
|
||||
background-color: rgb(189,192,198);
|
||||
color: #444;
|
||||
background-color: var(--datatable-bgcolor);
|
||||
color: var(--fbc-white);
|
||||
}
|
||||
|
||||
table.dataTable tbody tr.selected, table.dataTable tbody tr .selected
|
||||
{
|
||||
background-color: var(--datatable-bgcolor);
|
||||
color: var(--fbc-white);
|
||||
}
|
||||
|
||||
.db_info_table_cell:nth-child(1) {background: #272c30}
|
||||
@@ -724,7 +740,7 @@
|
||||
margin-left: 0px;
|
||||
}
|
||||
.small-box:hover .icon {
|
||||
font-size: 3.74em;
|
||||
font-size: 3em;
|
||||
}
|
||||
.small-box .icon {
|
||||
top: 0.01em;
|
||||
@@ -734,6 +750,35 @@
|
||||
background-color: #000 !important;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection--single {
|
||||
color: initial !important;
|
||||
background-color: #353c42 !important;
|
||||
}
|
||||
|
||||
/* Chevron color */
|
||||
.select2-container .select2-selection__arrow::after {
|
||||
color: #bec5cb;
|
||||
}
|
||||
|
||||
/* Chevron color */
|
||||
.select2-selection .select2-selection--single {
|
||||
color: #bec5cb;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection--multiple, .select2-container--default .select2-selection--single {
|
||||
border-color: #3d444b !important;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection--single .select2-selection__rendered .custom-chip
|
||||
{
|
||||
color: #bec5cb;
|
||||
}
|
||||
|
||||
#hover-box
|
||||
{
|
||||
background-color: #353c42 !important;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.callout code {
|
||||
@@ -746,3 +791,7 @@
|
||||
color:#000;
|
||||
}
|
||||
|
||||
.btn:hover
|
||||
{
|
||||
color: var(--color-gray);
|
||||
}
|
||||
@@ -16,8 +16,9 @@
|
||||
require 'php/templates/header.php';
|
||||
?>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
showSpinner();
|
||||
</script>
|
||||
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
<div class="content-wrapper">
|
||||
@@ -125,15 +126,11 @@
|
||||
<div class="tab-content" style="min-height: 430px;">
|
||||
|
||||
<!-- tab page 1 ------------------------------------------------------------ -->
|
||||
<!--
|
||||
<div class="tab-pane fade in active" id="panDetails">
|
||||
-->
|
||||
<div class="tab-pane fade" id="panDetails">
|
||||
|
||||
<div class="tab-pane fade" id="panDetails">
|
||||
<?php
|
||||
require 'deviceDetailsEdit.php';
|
||||
?>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- tab page 2 ------------------------------------------------------------ -->
|
||||
@@ -141,51 +138,38 @@
|
||||
<?php
|
||||
require 'deviceDetailsSessions.php';
|
||||
?>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- tab page "Tools" ------------------------------------------------------------ -->
|
||||
|
||||
<div class="tab-pane fade" id="panTools">
|
||||
|
||||
<?php
|
||||
require 'deviceDetailsTools.php';
|
||||
?>
|
||||
|
||||
|
||||
?>
|
||||
</div>
|
||||
|
||||
<!-- tab page 3 ------------------------------------------------------------ -->
|
||||
<div class="tab-pane fade table-responsive" id="panPresence">
|
||||
|
||||
<div class="tab-pane fade table-responsive" id="panPresence">
|
||||
<?php
|
||||
// Include the other page
|
||||
include 'deviceDetailsPresence.php';
|
||||
?>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- tab page 4 ------------------------------------------------------------ -->
|
||||
<div class="tab-pane fade table-responsive" id="panEvents">
|
||||
|
||||
<?php
|
||||
// Include the other page
|
||||
include 'deviceDetailsEvents.php';
|
||||
?>
|
||||
|
||||
|
||||
?>
|
||||
</div>
|
||||
|
||||
<!-- tab page 7 ------------------------------------------------------------ -->
|
||||
<div class="tab-pane fade table-responsive" id="panPlugins">
|
||||
|
||||
|
||||
<?php
|
||||
// Include the other page
|
||||
include 'pluginsCore.php';
|
||||
?>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -241,13 +225,6 @@ switch ($UI_THEME) {
|
||||
var selectedTab = 'tabDetails';
|
||||
var emptyArr = ['undefined', "", undefined, null];
|
||||
|
||||
|
||||
// Call renderSmallBoxes, then main
|
||||
(async () => {
|
||||
await renderSmallBoxes();
|
||||
main();
|
||||
})();
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
function main () {
|
||||
|
||||
@@ -273,7 +250,7 @@ function main () {
|
||||
period = '1 day';
|
||||
sessionsRows = 50;
|
||||
eventsRows = 50;
|
||||
$('#chkHideConnectionEvents')[0].checked = eval(eventsHide == 'true');
|
||||
// $('#chkHideConnectionEvents')[0].checked = eval(eventsHide == 'true');
|
||||
|
||||
// Initialize components with parameters
|
||||
|
||||
@@ -282,26 +259,7 @@ function main () {
|
||||
$( document ).ready(function() {
|
||||
initializeTabs();
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
// Events tab toggle conenction events
|
||||
$('input').on('ifToggled', function(event){
|
||||
// Hide / Show Events
|
||||
if (event.currentTarget.id == 'chkHideConnectionEvents') {
|
||||
getDeviceEvents();
|
||||
} else {
|
||||
// Activate save & restore
|
||||
// activateSaveRestoreData();
|
||||
|
||||
// Ask skip notifications
|
||||
// if (event.currentTarget.id == 'chkArchived' ) {
|
||||
// askSkipNotifications();
|
||||
// }
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -328,6 +286,63 @@ function recordSwitch(direction) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------
|
||||
// Handle previous/next arrows/chevrons
|
||||
function updateChevrons(currentMac) {
|
||||
const devicesList = getDevicesList();
|
||||
|
||||
pos = devicesList.findIndex(item => item.devMac === currentMac);
|
||||
|
||||
if (pos === -1) {
|
||||
console.warn('Device not found in cache. Re-caching devices...', currentMac);
|
||||
|
||||
showSpinner();
|
||||
|
||||
cacheDevices().then(() => {
|
||||
hideSpinner();
|
||||
|
||||
// Retry after re-caching
|
||||
const refreshedList = getDevicesList();
|
||||
pos = refreshedList.findIndex(item => item.devMac === currentMac);
|
||||
|
||||
if (pos === -1) {
|
||||
console.error('Still not found after re-cache:', currentMac);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Device found after re-cache:', refreshedList[pos]);
|
||||
// Proceed with using `refreshedList[pos]`
|
||||
}).catch((err) => {
|
||||
hideSpinner();
|
||||
console.error('Failed to cache devices:', err);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the record number display
|
||||
$('#txtRecord').html((pos + 1) + ' / ' + devicesList.length);
|
||||
|
||||
// Enable/disable previous button
|
||||
if (pos <= 0) {
|
||||
$('#btnPrevious').attr('disabled', '');
|
||||
$('#btnPrevious').addClass('text-gray50');
|
||||
} else {
|
||||
$('#btnPrevious').removeAttr('disabled');
|
||||
$('#btnPrevious').removeClass('text-gray50');
|
||||
}
|
||||
|
||||
// Enable/disable next button
|
||||
if (pos >= devicesList.length - 1) {
|
||||
$('#btnNext').attr('disabled', '');
|
||||
$('#btnNext').addClass('text-gray50');
|
||||
} else {
|
||||
$('#btnNext').removeAttr('disabled');
|
||||
$('#btnNext').removeClass('text-gray50');
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
function performSwitch(direction)
|
||||
@@ -338,7 +353,9 @@ function performSwitch(direction)
|
||||
|
||||
// Update the global position in the devices list variable 'pos'
|
||||
if (direction === "next") {
|
||||
if (pos < devicesList.length - 1) {
|
||||
console.log("direction:" + direction);
|
||||
|
||||
if (pos < devicesList.length) {
|
||||
pos++;
|
||||
}
|
||||
} else if (direction === "prev") {
|
||||
@@ -358,15 +375,12 @@ function performSwitch(direction)
|
||||
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Activate save & restore on any value change
|
||||
$(document).on('input', 'input:text', function() {
|
||||
settingsChanged();
|
||||
});
|
||||
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
function initializeTabs () {
|
||||
@@ -380,8 +394,6 @@ function initializeTabs () {
|
||||
}
|
||||
|
||||
$('.nav-tabs a[id='+ selectedTab +']').tab('show');
|
||||
// $('.nav-tabs a[id='+ selectedTab +']').parent().click();
|
||||
// $('.nav-tabs a[id="tabPlugins"]').tab('show');
|
||||
|
||||
// When changed save new current tab
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
@@ -391,11 +403,6 @@ function initializeTabs () {
|
||||
// events on tab change
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
var target = $(e.target).attr("href") // activated tab
|
||||
|
||||
// if(target == "#panTools")
|
||||
// {
|
||||
// // loadTools();
|
||||
// }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -415,7 +422,6 @@ async function renderSmallBoxes() {
|
||||
}
|
||||
|
||||
const deviceData = await response.json();
|
||||
console.log(deviceData);
|
||||
|
||||
// Prepare custom data
|
||||
const customData = [
|
||||
@@ -447,7 +453,7 @@ async function renderSmallBoxes() {
|
||||
"labelLang": "DevDetail_Shortcut_Presence",
|
||||
"iconId": "deviceEventsIcon",
|
||||
"iconClass": "fa fa-calendar",
|
||||
"dataValue": `${deviceData.devPresenceHours}h`
|
||||
"dataValue": `${deviceData.devPresenceHours ?? 0}h`
|
||||
},
|
||||
{
|
||||
"onclickEvent": "$('#tabEvents').trigger('click');",
|
||||
@@ -482,22 +488,65 @@ async function renderSmallBoxes() {
|
||||
console.error('Error in renderSmallBoxes:', error);
|
||||
} finally {
|
||||
// Hide loading dialog
|
||||
hideSpinner();
|
||||
// hideSpinner();
|
||||
}
|
||||
}
|
||||
|
||||
function updateDevicePageName(mac) {
|
||||
let name = getDevDataByMac(mac, "devName");
|
||||
let owner = getDevDataByMac(mac, "devOwner");
|
||||
|
||||
// If data is missing, re-cache and retry once
|
||||
if (name === "Unknown" || owner === "Unknown") {
|
||||
console.warn("Device not found in cache, retrying after re-cache:", mac);
|
||||
showSpinner();
|
||||
cacheDevices().then(() => {
|
||||
hideSpinner();
|
||||
// Retry after successful cache
|
||||
updateDevicePageName(mac);
|
||||
}).catch((err) => {
|
||||
hideSpinner();
|
||||
console.error("Failed to refresh devices:", err);
|
||||
});
|
||||
return; // Exit early to avoid showing bad data
|
||||
}
|
||||
|
||||
// Page title - Name
|
||||
if (mac == "new") {
|
||||
$('#pageTitle').html(
|
||||
`<i title="${getString("Gen_create_new_device")}" class="fa fa-square-plus"></i> ` + getString("Gen_create_new_device")
|
||||
);
|
||||
$('#devicePageInfoPlc .inner').html(
|
||||
`<i class="fa fa-circle-info"></i> ` + getString("Gen_create_new_device_info")
|
||||
);
|
||||
$('#devicePageInfoPlc').show();
|
||||
} else if (!owner || (name.toString()).indexOf(owner) !== -1) {
|
||||
$('#pageTitle').html(name);
|
||||
$('#devicePageInfoPlc').hide();
|
||||
} else {
|
||||
$('#pageTitle').html(name + ' (' + owner + ')');
|
||||
$('#devicePageInfoPlc').hide();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
// Call renderSmallBoxes, then main
|
||||
(async () => {
|
||||
await renderSmallBoxes();
|
||||
main();
|
||||
})();
|
||||
|
||||
|
||||
window.onload = function async()
|
||||
{
|
||||
initializeTabs();
|
||||
updateChevrons(mac);
|
||||
updateDevicePageName(mac);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
?>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="row" id="deviceDetailsEdit">
|
||||
<div class="box-body form-horizontal">
|
||||
<form id="edit-form">
|
||||
<!-- Form fields will be appended here -->
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Get plugin and settings data from API endpoints
|
||||
function getDeviceData(readAllData){
|
||||
function getDeviceData(){
|
||||
|
||||
mac = getMac()
|
||||
|
||||
@@ -53,24 +53,28 @@
|
||||
|
||||
var deviceData = JSON.parse(data);
|
||||
|
||||
// Deactivate next previous buttons
|
||||
if (readAllData) {
|
||||
$('#btnPrevious').attr ('disabled','');
|
||||
$('#btnPrevious').addClass ('text-gray50');
|
||||
$('#btnNext').attr ('disabled','');
|
||||
$('#btnNext').addClass ('text-gray50');
|
||||
}
|
||||
// // Deactivate next previous buttons
|
||||
// if (readAllData) {
|
||||
// $('#btnPrevious').attr ('disabled','');
|
||||
// $('#btnPrevious').addClass ('text-gray50');
|
||||
// $('#btnNext').attr ('disabled','');
|
||||
// $('#btnNext').addClass ('text-gray50');
|
||||
// }
|
||||
|
||||
// some race condition, need to implement delay
|
||||
setTimeout(() => {
|
||||
$.get('/php/server/query_json.php', { file: 'table_settings.json', nocache: Date.now() }, function(res) {
|
||||
$.get('php/server/query_json.php', {
|
||||
file: 'table_settings.json',
|
||||
// nocache: Date.now()
|
||||
},
|
||||
function(res) {
|
||||
|
||||
settingsData = res["data"];
|
||||
|
||||
// columns to hide
|
||||
hiddenFields = ["NEWDEV_devScan", "NEWDEV_devPresentLastScan" ]
|
||||
// columns to disable - conditional depending if a new dummy device is created
|
||||
disabledFields = mac == "new" ? ["NEWDEV_devLastNotification", "NEWDEV_devFirstConnection", "NEWDEV_devLastConnection"] : ["NEWDEV_devLastNotification", "NEWDEV_devFirstConnection", "NEWDEV_devLastConnection", "NEWDEV_devMac", "NEWDEV_devLastIP", "NEWDEV_devSyncHubNode" ];
|
||||
// columns to disable/readonly - 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", "NEWDEV_devSyncHubNode", "NEWDEV_devFQDN" ];
|
||||
|
||||
// Grouping of fields into categories with associated documentation links
|
||||
const fieldGroups = {
|
||||
@@ -82,19 +86,10 @@
|
||||
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",
|
||||
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"],
|
||||
data: ["devAlertEvents", "devAlertDown", "devSkipRepeated", "devReqNicsOnline", "devChildrenNicsDynamic"],
|
||||
docs: "https://github.com/jokob-sk/NetAlertX/blob/main/docs/NOTIFICATIONS.md",
|
||||
iconClass: "fa fa-bell",
|
||||
inputGroupClasses: "field-group alert-group col-lg-4 col-sm-6 col-xs-12",
|
||||
@@ -103,9 +98,9 @@
|
||||
},
|
||||
// Group for network details
|
||||
DevDetail_MainInfo_Network_Title: {
|
||||
data: ["devParentMAC", "devParentPort", "devSSID", "devSite", "devSyncHubNode"],
|
||||
data: ["devParentMAC", "devParentRelType", "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-sitemap fa-rotate-270",
|
||||
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"
|
||||
@@ -119,12 +114,30 @@
|
||||
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", "devFQDN"],
|
||||
docs: "https://github.com/jokob-sk/NetAlertX/blob/main/docs/SESSION_INFO.md",
|
||||
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 Children.
|
||||
DevDetail_Children_Title: {
|
||||
data: ["devChildrenDynamic"],
|
||||
docs: "https://github.com/jokob-sk/NetAlertX/blob/main/docs/NETWORK_TREE.md",
|
||||
iconClass: "fa fa-list",
|
||||
inputGroupClasses: "field-group cutprop-group col-lg-6 col-sm-12 col-xs-12",
|
||||
labelClasses: "col-sm-12 col-xs-12 control-label",
|
||||
inputClasses: "col-sm-12 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",
|
||||
inputGroupClasses: "field-group cutprop-group col-lg-6 col-sm-12 col-xs-12",
|
||||
labelClasses: "col-sm-12 col-xs-12 control-label",
|
||||
inputClasses: "col-sm-12 col-xs-12 input-group"
|
||||
}
|
||||
@@ -167,13 +180,14 @@
|
||||
// Get the field data (replace 'NEWDEV_' prefix from the key)
|
||||
fieldData = deviceData[setting.setKey.replace('NEWDEV_', '')]
|
||||
fieldData = fieldData == null ? "" : fieldData;
|
||||
fieldOptionsOverride = null;
|
||||
|
||||
// console.log(setting.setKey);
|
||||
// console.log(fieldData);
|
||||
|
||||
// Additional form elements like the random MAC address button for devMac
|
||||
let inlineControl = "";
|
||||
// handle rendom mac
|
||||
// handle random mac
|
||||
if (setting.setKey == "NEWDEV_devMac" && deviceData["devIsRandomMAC"] == true) {
|
||||
inlineControl += `<span class="input-group-addon pointer"
|
||||
title="${getString("RandomMAC_hover")}">
|
||||
@@ -198,16 +212,21 @@
|
||||
<i class="fa-solid fa-dice" ></i>
|
||||
</span>`;
|
||||
}
|
||||
|
||||
// handle generate IP for new device
|
||||
if (setting.setKey == "NEWDEV_devIcon") {
|
||||
inlineControl += `<span class="input-group-addon pointer"
|
||||
onclick="showIconSelection()"
|
||||
title="${getString("Gen_Select")}">
|
||||
<i class="fa-solid fa-chevron-down" ></i>
|
||||
</span>`;
|
||||
}
|
||||
|
||||
// handle devChildrenDynamic or NEWDEV_devChildrenNicsDynamic - selected values and options are the same
|
||||
if (
|
||||
Array.isArray(fieldData) &&
|
||||
(setting.setKey == "NEWDEV_devChildrenDynamic" ||
|
||||
setting.setKey == "NEWDEV_devChildrenNicsDynamic" )
|
||||
)
|
||||
{
|
||||
fieldDataNew = []
|
||||
fieldData.forEach(child => {
|
||||
fieldDataNew.push(child.devMac)
|
||||
})
|
||||
fieldData = fieldDataNew;
|
||||
fieldOptionsOverride = fieldDataNew;
|
||||
}
|
||||
|
||||
// Generate the input field HTML
|
||||
const inputFormHtml = `<div class="form-group col-xs-12">
|
||||
@@ -219,7 +238,7 @@
|
||||
</i>
|
||||
</label>
|
||||
<div class="${obj.inputClasses}">
|
||||
${generateFormHtml(settingsData, setting, fieldData.toString(), null, null)}
|
||||
${generateFormHtml(settingsData, setting, fieldData.toString(), fieldOptionsOverride, null)}
|
||||
${inlineControl}
|
||||
</div>
|
||||
</div>`;
|
||||
@@ -236,32 +255,18 @@
|
||||
updateAllIconPreviews();
|
||||
|
||||
// update readonly fields
|
||||
handleReadOnly(settingsData, disabledFields);
|
||||
|
||||
// Page title - Name
|
||||
if (mac == "new") {
|
||||
$('#pageTitle').html(`<i title="${getString("Gen_create_new_device")}" class="fa fa-square-plus"></i> ` + getString("Gen_create_new_device"));
|
||||
$('#devicePageInfoPlc .inner').html(`<i class="fa fa-circle-info"></i> ` + getString("Gen_create_new_device_info"));
|
||||
$('#devicePageInfoPlc').show();
|
||||
} else if (deviceData['devOwner'] == null || deviceData['devOwner'] == '' ||
|
||||
(deviceData['devName'].toString()).indexOf(deviceData['devOwner']) != -1) {
|
||||
$('#pageTitle').html(deviceData['devName']);
|
||||
$('#devicePageInfoPlc').hide();
|
||||
} else {
|
||||
$('#pageTitle').html(deviceData['devName'] + ' (' + deviceData['devOwner'] + ')');
|
||||
$('#devicePageInfoPlc').hide();
|
||||
}
|
||||
handleReadOnly(settingsData, disabledFields);
|
||||
};
|
||||
|
||||
// console.log(relevantSettings)
|
||||
|
||||
generateSimpleForm(relevantSettings);
|
||||
|
||||
// <> chevrons
|
||||
updateChevrons(deviceData)
|
||||
|
||||
toggleNetworkConfiguration(mac == 'Internet')
|
||||
|
||||
initSelect2();
|
||||
initHoverNodeInfo();
|
||||
|
||||
hideSpinner();
|
||||
|
||||
})
|
||||
@@ -272,46 +277,6 @@
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------
|
||||
// Handle previous/next arrows/chevrons
|
||||
function updateChevrons(deviceData) {
|
||||
|
||||
devicesList = getDevicesList();
|
||||
|
||||
// console.log(devicesList);
|
||||
|
||||
// Check if device is part of the devicesList
|
||||
pos = devicesList.findIndex(item => item.rowid == deviceData['rowid']);
|
||||
|
||||
// console.log(pos);
|
||||
|
||||
if (pos == -1) {
|
||||
devicesList.push({"rowid" : deviceData['rowid'], "mac" : deviceData['devMac'], "name": deviceData['devName'], "type": deviceData['devType']});
|
||||
pos=0;
|
||||
}
|
||||
|
||||
// Record number
|
||||
$('#txtRecord').html (pos+1 +' / '+ devicesList.length);
|
||||
|
||||
// Deactivate previous button
|
||||
if (pos <= 0) {
|
||||
$('#btnPrevious').attr ('disabled','');
|
||||
$('#btnPrevious').addClass ('text-gray50');
|
||||
} else {
|
||||
$('#btnPrevious').removeAttr ('disabled');
|
||||
$('#btnPrevious').removeClass ('text-gray50');
|
||||
}
|
||||
|
||||
// Deactivate next button
|
||||
if (pos >= (devicesList.length-1)) {
|
||||
$('#btnNext').attr ('disabled','');
|
||||
$('#btnNext').addClass ('text-gray50');
|
||||
} else {
|
||||
$('#btnNext').removeAttr ('disabled');
|
||||
$('#btnNext').removeClass ('text-gray50');
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------
|
||||
// Handle the read-only fields
|
||||
function handleReadOnly(settingsData, disabledFields) {
|
||||
@@ -364,7 +329,7 @@
|
||||
mac: $('#NEWDEV_devMac').val(),
|
||||
name: encodeURIComponent($('#NEWDEV_devName').val().replace(/'/g, "’")),
|
||||
owner: encodeURIComponent($('#NEWDEV_devOwner').val().replace(/'/g, "’")),
|
||||
type: $('#NEWDEV_devType').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),
|
||||
@@ -380,6 +345,8 @@
|
||||
alertevents: ($('#NEWDEV_devAlertEvents')[0].checked * 1),
|
||||
alertdown: ($('#NEWDEV_devAlertDown')[0].checked * 1),
|
||||
skiprepeated: $('#NEWDEV_devSkipRepeated').val().split(' ')[0],
|
||||
relType: $('#NEWDEV_devParentRelType').val().replace(/'/g, ""),
|
||||
reqNics: ($('#NEWDEV_devReqNicsOnline')[0].checked * 1),
|
||||
newdevice: ($('#NEWDEV_devIsNew')[0].checked * 1),
|
||||
archived: ($('#NEWDEV_devIsArchived')[0].checked * 1),
|
||||
devFirstConnection: ($('#NEWDEV_devFirstConnection').val()),
|
||||
@@ -427,9 +394,47 @@
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------
|
||||
// INIT with polling for panel element visibility
|
||||
// -----------------------------------------------
|
||||
|
||||
var deviceDetailsPageInitialized = false;
|
||||
|
||||
function initdeviceDetailsPage()
|
||||
{
|
||||
// Only proceed if .plugin-content is visible
|
||||
if (!$('#panDetails:visible').length) {
|
||||
return; // exit early if nothing is visible
|
||||
}
|
||||
|
||||
// init page once
|
||||
if (deviceDetailsPageInitialized) return; // ENSURE ONCE
|
||||
deviceDetailsPageInitialized = true;
|
||||
|
||||
showSpinner();
|
||||
|
||||
getDeviceData();
|
||||
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Recurring function to monitor the URL and reinitialize if needed
|
||||
function deviceDetailsPageUpdater() {
|
||||
initdeviceDetailsPage();
|
||||
|
||||
// Run updater again after delay
|
||||
setTimeout(deviceDetailsPageUpdater, 200);
|
||||
}
|
||||
|
||||
// if visible, load immediately, if not start updater
|
||||
if (!$('#panDetails:visible').length) {
|
||||
deviceDetailsPageUpdater();
|
||||
}
|
||||
else
|
||||
{
|
||||
getDeviceData();
|
||||
}
|
||||
|
||||
// -------------------- INIT ------------------------
|
||||
getDeviceData(true);
|
||||
|
||||
|
||||
</script>
|
||||
@@ -7,11 +7,11 @@
|
||||
|
||||
|
||||
<!-- Hide Connections -->
|
||||
<div class="text-center">
|
||||
<label>
|
||||
<input class="checkbox blue hidden" id="chkHideConnectionEvents" type="checkbox" checked>
|
||||
<?= lang('DevDetail_Events_CheckBox');?>
|
||||
<div class="col-sm-12 col-xs-12">
|
||||
<label class="col-sm-3 col-xs-10">
|
||||
<?= lang('DevDetail_Events_CheckBox');?>
|
||||
</label>
|
||||
<input class="checkbox blue col-sm-1 col-xs-2" id="chkHideConnectionEvents" type="checkbox" onChange="loadEventsData()">
|
||||
</div>
|
||||
|
||||
<!-- Datatable Events -->
|
||||
@@ -19,6 +19,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= lang("DevDetail_Tab_EventsTableDate");?></th>
|
||||
<th><?= lang("DevDetail_Tab_EventsTableDate");?></th>
|
||||
<th><?= lang("DevDetail_Tab_EventsTableEvent");?></th>
|
||||
<th><?= lang("DevDetail_Tab_EventsTableIP");?></th>
|
||||
<th><?= lang("DevDetail_Tab_EventsTableInfo");?></th>
|
||||
@@ -29,66 +30,135 @@
|
||||
|
||||
<script>
|
||||
|
||||
var eventsRows = 10;
|
||||
var eventsHide = true;
|
||||
var parEventsRows = 'Front_Details_Events_Rows';
|
||||
var parEventsHide = 'Front_Details_Events_Hide';
|
||||
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
function loadEventsData() {
|
||||
const hideConnections = $('#chkHideConnectionEvents')[0].checked;
|
||||
const hideConnectionsStr = hideConnections ? 'true' : 'false';
|
||||
|
||||
function loadEventsData() {
|
||||
// Define Events datasource and query dada
|
||||
hideConnections = $('#chkHideConnectionEvents')[0].checked;
|
||||
$('#tableEvents').DataTable().ajax.url('php/server/events.php?action=getDeviceEvents&mac=' + mac +'&period='+ period +'&hideConnections='+ hideConnections).load();
|
||||
}
|
||||
mac = getMac()
|
||||
|
||||
function initializeSessionsDatatable () {
|
||||
const rawSql = `
|
||||
SELECT eve_DateTime, eve_DateTime, eve_EventType, eve_IP, eve_AdditionalInfo
|
||||
FROM Events
|
||||
WHERE eve_MAC = "${mac}"
|
||||
AND (
|
||||
(eve_EventType NOT IN ("Connected", "Disconnected", "VOIDED - Connected", "VOIDED - Disconnected"))
|
||||
OR "${hideConnectionsStr}" = "false"
|
||||
)
|
||||
`;
|
||||
|
||||
// Events datatable
|
||||
$('#tableEvents').DataTable({
|
||||
'paging' : true,
|
||||
'lengthChange': true,
|
||||
'lengthMenu' : [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, 'All']],
|
||||
'searching' : true,
|
||||
'ordering' : true,
|
||||
'info' : true,
|
||||
'autoWidth' : false,
|
||||
'order' : [[0,'desc']],
|
||||
const apiUrl = `php/server/dbHelper.php?action=read&rawSql=${btoa(encodeURIComponent(rawSql))}`;
|
||||
|
||||
// Parameters
|
||||
'pageLength' : eventsRows,
|
||||
// Manually load the data first
|
||||
$.get(apiUrl, function (data) {
|
||||
const parsed = JSON.parse(data);
|
||||
|
||||
const rows = parsed.map(row => {
|
||||
const rawDate = row.eve_DateTime;
|
||||
const formattedDate = rawDate ? localizeTimestamp(rawDate) : '-';
|
||||
return [
|
||||
formattedDate,
|
||||
row.eve_DateTime,
|
||||
row.eve_EventType,
|
||||
row.eve_IP,
|
||||
row.eve_AdditionalInfo
|
||||
];
|
||||
});
|
||||
|
||||
'columnDefs' : [
|
||||
// Replace HTML codes
|
||||
{targets: [0],
|
||||
'createdCell': function (td, cellData, rowData, row, col) {
|
||||
$(td).html (translateHTMLcodes (cellData));
|
||||
} }
|
||||
],
|
||||
|
||||
// Processing
|
||||
'processing' : true,
|
||||
'language' : {
|
||||
processing: '<table><td width="130px" align="middle"><?= lang("DevDetail_Loading");?></td>'+
|
||||
'<td><i class="ion ion-ios-loop-strong fa-spin fa-2x fa-fw">'+
|
||||
'</td></table>',
|
||||
emptyTable: 'No data',
|
||||
"lengthMenu": "<?= lang('Events_Tablelenght');?>",
|
||||
"search": "<?= lang('Events_Searchbox');?>: ",
|
||||
"paginate": {
|
||||
"next": "<?= lang('Events_Table_nav_next');?>",
|
||||
"previous": "<?= lang('Events_Table_nav_prev');?>"
|
||||
},
|
||||
"info": "<?= lang('Events_Table_info');?>",
|
||||
}
|
||||
});
|
||||
// Fill the table manually
|
||||
const table = $('#tableEvents').DataTable();
|
||||
table.clear();
|
||||
table.rows.add(rows); // assuming each row is an array
|
||||
table.draw();
|
||||
|
||||
hideSpinner();
|
||||
});
|
||||
}
|
||||
|
||||
initializeSessionsDatatable();
|
||||
loadEventsData();
|
||||
function initializeEventsDatatable (eventsRows) {
|
||||
|
||||
if ($.fn.dataTable.isDataTable('#tableEvents')) {
|
||||
$('#tableEvents').DataTable().clear().destroy();
|
||||
}
|
||||
|
||||
$('#tableEvents').DataTable({
|
||||
'paging' : true,
|
||||
'lengthChange': true,
|
||||
'lengthMenu' : [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, 'All']],
|
||||
'searching' : true,
|
||||
'ordering' : true,
|
||||
'info' : true,
|
||||
'autoWidth' : false,
|
||||
'order' : [[0,'desc']],
|
||||
'pageLength' : eventsRows,
|
||||
|
||||
'columnDefs' : [
|
||||
{ orderData: [1], targets: [0] },
|
||||
{ visible: false, targets: [1] },
|
||||
{
|
||||
targets: [0],
|
||||
'createdCell': function (td, cellData, rowData, row, col) {
|
||||
$(td).html(translateHTMLcodes(localizeTimestamp(cellData)));
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
'processing' : true,
|
||||
'language' : {
|
||||
processing: '<table><td width="130px" align="middle"><?= lang("DevDetail_Loading");?></td>'+
|
||||
'<td><i class="fa-solid fa-spinner fa-spin-pulse"></i></td></table>',
|
||||
emptyTable: 'No data',
|
||||
"lengthMenu": "<?= lang('Events_Tablelenght');?>",
|
||||
"search": "<?= lang('Events_Searchbox');?>: ",
|
||||
"paginate": {
|
||||
"next": "<?= lang('Events_Table_nav_next');?>",
|
||||
"previous": "<?= lang('Events_Table_nav_prev');?>"
|
||||
},
|
||||
"info": "<?= lang('Events_Table_info');?>",
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// -----------------------------------------------
|
||||
// INIT with polling for panel element visibility
|
||||
// -----------------------------------------------
|
||||
|
||||
var eventsPageInitialized = false;
|
||||
|
||||
function initDeviceEventsPage()
|
||||
{
|
||||
// Only proceed if .plugin-content is visible
|
||||
if (!$('#panEvents:visible').length) {
|
||||
return; // exit early if nothing is visible
|
||||
}
|
||||
|
||||
// init page once
|
||||
if (eventsPageInitialized) return; // ENSURE ONCE
|
||||
eventsPageInitialized = true;
|
||||
|
||||
showSpinner();
|
||||
|
||||
var eventsRows = 10;
|
||||
var eventsHide = true;
|
||||
|
||||
initializeEventsDatatable(eventsRows);
|
||||
loadEventsData();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Recurring function to monitor the URL and reinitialize if needed
|
||||
function deviceEventsPageUpdater() {
|
||||
initDeviceEventsPage();
|
||||
|
||||
// Run updater again after delay
|
||||
setTimeout(deviceEventsPageUpdater, 200);
|
||||
}
|
||||
|
||||
deviceEventsPageUpdater();
|
||||
|
||||
|
||||
</script>
|
||||
@@ -23,8 +23,6 @@
|
||||
|
||||
|
||||
<script>
|
||||
initializeCalendar();
|
||||
loadPresenceData();
|
||||
|
||||
// Force re-render calendar on tab change
|
||||
// (bugfix for render error at left panel)
|
||||
@@ -219,9 +217,8 @@ function initializeCalendar() {
|
||||
},
|
||||
|
||||
eventRender: function (event, element) {
|
||||
$(element).tooltip({container: 'body', placement: 'bottom',
|
||||
title: event.tooltip});
|
||||
// element.attr ('title', event.tooltip); // Alternative tooltip
|
||||
// $(element).tooltip({container: 'body', placement: 'bottom', title: event.tooltip});
|
||||
element.attr ('title', event.tooltip); // Alternative tooltip
|
||||
},
|
||||
|
||||
loading: function( isLoading, view ) {
|
||||
@@ -235,6 +232,37 @@ function initializeCalendar() {
|
||||
})
|
||||
}
|
||||
|
||||
// -----------------------------------------------
|
||||
// INIT with polling for panel element visibility
|
||||
// -----------------------------------------------
|
||||
|
||||
var presencePageInitialized = false;
|
||||
|
||||
function initDevicePresencePage() {
|
||||
// Only proceed if the Presence tab is visible
|
||||
if (!$('#panPresence:visible').length) {
|
||||
return; // Exit early if nothing is visible
|
||||
}
|
||||
|
||||
// Ensure initialization only happens once
|
||||
if (presencePageInitialized) return;
|
||||
presencePageInitialized = true;
|
||||
|
||||
showSpinner();
|
||||
|
||||
initializeCalendar();
|
||||
loadPresenceData();
|
||||
}
|
||||
|
||||
// Recurring check to initialize when visible
|
||||
function devicePresencePageUpdater() {
|
||||
initDevicePresencePage();
|
||||
|
||||
setTimeout(devicePresencePageUpdater, 200);
|
||||
}
|
||||
|
||||
devicePresencePageUpdater();
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
@@ -24,58 +24,126 @@
|
||||
|
||||
<script>
|
||||
|
||||
var parSessionsRows = 'Front_Details_Sessions_Rows';
|
||||
|
||||
|
||||
function initializeSessionsDatatable (sessionsRows) {
|
||||
// Sessions datatable
|
||||
$('#tableSessions').DataTable({
|
||||
'paging' : true,
|
||||
'lengthChange': true,
|
||||
'lengthMenu' : [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, 'All']],
|
||||
'searching' : true,
|
||||
'ordering' : true,
|
||||
'info' : true,
|
||||
'autoWidth' : false,
|
||||
'order' : [[0,'desc'], [1,'desc']],
|
||||
|
||||
// Parameters
|
||||
'pageLength' : sessionsRows,
|
||||
|
||||
'columnDefs' : [
|
||||
{visible: false, targets: [0]},
|
||||
|
||||
// Replace HTML codes
|
||||
{targets: [3,5],
|
||||
'createdCell': function (td, cellData, rowData, row, col) {
|
||||
$(td).html (translateHTMLcodes (cellData));
|
||||
} },
|
||||
// Date
|
||||
{targets: [1,2],
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
// console.log(cellData);
|
||||
|
||||
if (!cellData.includes("missing event") && !cellData.includes("..."))
|
||||
{
|
||||
if (cellData.includes("+")) { // Check if timezone offset is present
|
||||
cellData = cellData.split('+')[0]; // Remove timezone offset
|
||||
}
|
||||
// console.log(cellData);
|
||||
result = localizeTimestamp(cellData);
|
||||
} else
|
||||
{
|
||||
result = translateHTMLcodes(cellData)
|
||||
}
|
||||
|
||||
$(td).html (result);
|
||||
} }
|
||||
],
|
||||
|
||||
// Processing
|
||||
'processing' : true,
|
||||
'language' : {
|
||||
processing: '<table><td width="130px" align="middle"><?= lang("DevDetail_Loading");?></td>'+
|
||||
'<td><i class="fa-solid fa-spinner fa-spin-pulse"></i>'+
|
||||
'</td></table>',
|
||||
emptyTable: 'No data',
|
||||
"lengthMenu": "<?= lang('Events_Tablelenght');?>",
|
||||
"search": "<?= lang('Events_Searchbox');?>: ",
|
||||
"paginate": {
|
||||
"next": "<?= lang('Events_Table_nav_next');?>",
|
||||
"previous": "<?= lang('Events_Table_nav_prev');?>"
|
||||
},
|
||||
"info": "<?= lang('Events_Table_info');?>",
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
// -----------------------------------------------
|
||||
// INIT with polling for panel element visibility
|
||||
// -----------------------------------------------
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// Init datatable
|
||||
function loadSessionsData(period){
|
||||
const table = $('#tableSessions').DataTable();
|
||||
|
||||
showSpinner();
|
||||
|
||||
// table.clear().draw(); // Clear existing data before reloading
|
||||
|
||||
table.ajax
|
||||
.url('php/server/events.php?action=getDeviceSessions&mac=' + getMac() + '&period=' + period)
|
||||
.load(function () {
|
||||
hideSpinner();
|
||||
});
|
||||
}
|
||||
|
||||
var sessionsPageInitialized = false;
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// Main init function
|
||||
function initDeviceSessionsPage()
|
||||
{
|
||||
// Only proceed if .plugin-content is visible
|
||||
if (!$('#panSessions:visible').length) {
|
||||
return; // exit early if nothing is visible
|
||||
}
|
||||
|
||||
// init page once
|
||||
if (sessionsPageInitialized) return;
|
||||
sessionsPageInitialized = true;
|
||||
|
||||
showSpinner();
|
||||
|
||||
var sessionsRows = 10;
|
||||
var period = '1 month';
|
||||
|
||||
function initializeSessionsDatatable () {
|
||||
// Sessions datatable
|
||||
$('#tableSessions').DataTable({
|
||||
'paging' : true,
|
||||
'lengthChange': true,
|
||||
'lengthMenu' : [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, 'All']],
|
||||
'searching' : true,
|
||||
'ordering' : true,
|
||||
'info' : true,
|
||||
'autoWidth' : false,
|
||||
'order' : [[0,'desc'], [1,'desc']],
|
||||
initializeSessionsDatatable(sessionsRows);
|
||||
loadSessionsData(period);
|
||||
}
|
||||
|
||||
// Parameters
|
||||
'pageLength' : sessionsRows,
|
||||
// -----------------------------------------------------------------------------
|
||||
// Recurring function to monitor the URL and reinitialize if needed
|
||||
function deviceSessionsPageUpdater() {
|
||||
initDeviceSessionsPage();
|
||||
|
||||
'columnDefs' : [
|
||||
{visible: false, targets: [0]},
|
||||
// Run updater again after delay
|
||||
setTimeout(deviceSessionsPageUpdater, 200);
|
||||
}
|
||||
|
||||
// Replace HTML codes
|
||||
{targets: [1,2,3,5],
|
||||
'createdCell': function (td, cellData, rowData, row, col) {
|
||||
$(td).html (translateHTMLcodes (cellData));
|
||||
} }
|
||||
],
|
||||
|
||||
// Processing
|
||||
'processing' : true,
|
||||
'language' : {
|
||||
processing: '<table><td width="130px" align="middle"><?= lang("DevDetail_Loading");?></td>'+
|
||||
'<td><i class="ion ion-ios-loop-strong fa-spin fa-2x fa-fw">'+
|
||||
'</td></table>',
|
||||
emptyTable: 'No data',
|
||||
"lengthMenu": "<?= lang('Events_Tablelenght');?>",
|
||||
"search": "<?= lang('Events_Searchbox');?>: ",
|
||||
"paginate": {
|
||||
"next": "<?= lang('Events_Table_nav_next');?>",
|
||||
"previous": "<?= lang('Events_Table_nav_prev');?>"
|
||||
},
|
||||
"info": "<?= lang('Events_Table_info');?>",
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadSessionsData(){
|
||||
$('#tableSessions').DataTable().ajax.url('php/server/events.php?action=getDeviceSessions&mac=' + getMac() +'&period='+ period).load();
|
||||
}
|
||||
|
||||
initializeSessionsDatatable();
|
||||
loadSessionsData();
|
||||
// start updater
|
||||
deviceSessionsPageUpdater();
|
||||
|
||||
</script>
|
||||
@@ -14,7 +14,7 @@
|
||||
<?= lang("DevDetail_Tab_Tools_Internet_Info_Description") ?>
|
||||
</h5>
|
||||
<br>
|
||||
<div style="width:100%; text-align: center; margin-bottom: 50px;">
|
||||
<div style="width:100%; text-align: center;">
|
||||
<button type="button" id="internetinfo" class="btn btn-primary pa-btn" style="margin: auto;" onclick="internetinfo()">
|
||||
<?= lang("DevDetail_Tab_Tools_Internet_Info_Start") ?></button>
|
||||
<br>
|
||||
@@ -33,13 +33,13 @@
|
||||
<?= lang("DevDetail_Copy_Device_Tooltip") ?>
|
||||
</h5>
|
||||
<br>
|
||||
<div style="width:100%; text-align: center; margin-bottom: 50px;">
|
||||
<div style="width:100%; text-align: center;">
|
||||
<select class="form-control"
|
||||
title="<?= lang('DevDetail_Copy_Device_Tooltip');?>"
|
||||
id="txtCopyFromDevice" >
|
||||
<option value="lemp_loading" id="lemp_loading">Loading</option>
|
||||
</select>
|
||||
<button type="button" id="internetinfo" class="btn btn-primary pa-btn" style="margin: auto;" onclick="()">
|
||||
<button type="button" id="internetinfo" class="btn btn-primary pa-btn" style="margin: auto; margin-top:10px;" onclick="copyFromDevice()">
|
||||
<?= lang("BackDevDetail_Copy_Title") ?></button>
|
||||
<br>
|
||||
</div>
|
||||
@@ -56,7 +56,7 @@
|
||||
<?= lang("DevDetail_Tools_WOL_noti_text") ?>
|
||||
</h5>
|
||||
<br>
|
||||
<div style="width:100%; text-align: center; margin-bottom: 50px;">
|
||||
<div style="width:100%; text-align: center;">
|
||||
<button type="button" id="internetinfo" class="btn btn-primary pa-btn" style="margin: auto;" onclick="wakeonlan()">
|
||||
<?= lang("DevDetail_Tools_WOL_noti") ?></button>
|
||||
<br>
|
||||
@@ -74,7 +74,7 @@
|
||||
<?= lang("DevDetail_button_DeleteEvents_Warning") ?>
|
||||
</h5>
|
||||
<br>
|
||||
<div style="width:100%; text-align: center; margin-bottom: 50px;">
|
||||
<div style="width:100%; text-align: center;">
|
||||
<button type="button"
|
||||
class="btn btn-default pa-btn pa-btn-delete"
|
||||
style="margin-left:0px;"
|
||||
@@ -94,7 +94,7 @@
|
||||
<?= lang("DevDetail_CustomProps_reset_info") ?>
|
||||
</h5>
|
||||
<br>
|
||||
<div style="width:100%; text-align: center; margin-bottom: 50px;">
|
||||
<div style="width:100%; text-align: center;">
|
||||
<button type="button"
|
||||
class="btn btn-default pa-btn pa-btn-delete"
|
||||
style="margin-left:0px;"
|
||||
@@ -116,7 +116,7 @@
|
||||
<?= lang("DevDetail_Tab_Tools_Speedtest_Description") ?>
|
||||
</h5>
|
||||
<br>
|
||||
<div style="width:100%; text-align: center; margin-bottom: 50px;">
|
||||
<div style="width:100%; text-align: center;">
|
||||
<button type="button" id="speedtestcli" class="btn btn-primary pa-btn" style="margin: auto;" onclick="speedtestcli()">
|
||||
<?= lang("DevDetail_Tab_Tools_Speedtest_Start") ?></button>
|
||||
<br>
|
||||
@@ -133,7 +133,7 @@
|
||||
<h5 class="">
|
||||
<?= lang("DevDetail_Tab_Tools_Traceroute_Description") ?>
|
||||
</h5>
|
||||
<div style="width:100%; text-align: center; margin-bottom: 50px;">
|
||||
<div style="width:100%; text-align: center;">
|
||||
<button type="button" id="traceroute" class="btn btn-primary pa-btn" style="margin: auto;" onclick="traceroute()">
|
||||
<?= lang("DevDetail_Tab_Tools_Traceroute_Start") ?>
|
||||
</button>
|
||||
@@ -151,7 +151,7 @@
|
||||
<h5 class="">
|
||||
<?= lang("DevDetail_Tab_Tools_Nslookup_Description") ?>
|
||||
</h5>
|
||||
<div style="width:100%; text-align: center; margin-bottom: 50px;">
|
||||
<div style="width:100%; text-align: center;">
|
||||
<button type="button" id="nslookup" class="btn btn-primary pa-btn" style="margin: auto;" onclick="nslookup()">
|
||||
<?= lang("DevDetail_Tab_Tools_Nslookup_Start") ?>
|
||||
</button>
|
||||
@@ -295,7 +295,7 @@
|
||||
function initCopyFromDevice() {
|
||||
|
||||
const devices = getVisibleDevicesList()
|
||||
console.log(devices);
|
||||
// console.log(devices);
|
||||
|
||||
const $select = $('#txtCopyFromDevice');
|
||||
$select.empty(); // Clear existing options
|
||||
|
||||
@@ -148,10 +148,7 @@ function main () {
|
||||
|
||||
//initialize the table headers in the correct order
|
||||
var availableColumns = getSettingOptions("UI_device_columns").split(",");
|
||||
headersDefaultOrder = availableColumns.map(val => getString(val));
|
||||
|
||||
console.log(headersDefaultOrder);
|
||||
|
||||
headersDefaultOrder = availableColumns.map(val => getString(val));
|
||||
|
||||
var selectedColumns = JSON.parse(getSetting("UI_device_columns").replace(/'/g, '"'));
|
||||
|
||||
@@ -266,14 +263,16 @@ function getDevicesTotals() {
|
||||
function processDeviceTotals(devicesData) {
|
||||
// Define filter conditions and corresponding objects
|
||||
const filters = [
|
||||
{ status: 'my_devices', color: 'bg-aqua', label: getString('Device_Shortcut_AllDevices'), icon: 'fa-laptop' },
|
||||
{ status: 'all', color: 'bg-aqua', label: getString('Gen_All_Devices'), icon: 'fa-laptop' },
|
||||
{ status: 'connected', color: 'bg-green', label: getString('Device_Shortcut_Connected'), icon: 'fa-plug' },
|
||||
{ status: 'favorites', color: 'bg-yellow', label: getString('Device_Shortcut_Favorites'), icon: 'fa-star' },
|
||||
{ status: 'new', color: 'bg-yellow', label: getString('Device_Shortcut_NewDevices'), icon: 'fa-plus' },
|
||||
{ status: 'down', color: 'bg-red', label: getString('Device_Shortcut_DownOnly'), icon: 'fa-warning' },
|
||||
{ status: 'archived', color: 'bg-gray', label: getString('Device_Shortcut_Archived'), icon: 'fa-eye-slash' },
|
||||
{ status: 'offline', color: 'bg-gray', label: getString('Gen_Offline'), icon: 'fa-xmark' }
|
||||
{ status: 'my_devices', color: 'bg-aqua', label: getString('Device_Shortcut_AllDevices'), icon: 'fa-laptop' },
|
||||
{ status: 'all', color: 'bg-aqua', label: getString('Gen_All_Devices'), icon: 'fa-laptop' },
|
||||
{ status: 'connected', color: 'bg-green', label: getString('Device_Shortcut_Connected'), icon: 'fa-plug' },
|
||||
{ status: 'favorites', color: 'bg-yellow', label: getString('Device_Shortcut_Favorites'), icon: 'fa-star' },
|
||||
{ status: 'new', color: 'bg-yellow', label: getString('Device_Shortcut_NewDevices'), icon: 'fa-plus' },
|
||||
{ status: 'down', color: 'bg-red', label: getString('Device_Shortcut_DownOnly'), icon: 'fa-warning' },
|
||||
{ status: 'archived', color: 'bg-gray', label: getString('Device_Shortcut_Archived'), icon: 'fa-eye-slash' },
|
||||
{ status: 'offline', color: 'bg-gray', label: getString('Gen_Offline'), icon: 'fa-xmark' },
|
||||
{ status: 'all_devices', color: 'bg-gray', label: getString('Gen_All_Devices'), icon: 'fa-laptop' },
|
||||
{ status: 'network_devices', color: 'bg-aqua', label: getString('Network_Devices'), icon: 'fa-sitemap fa-rotate-270' }
|
||||
];
|
||||
|
||||
// Initialize an empty array to store the final objects
|
||||
@@ -299,13 +298,7 @@ function processDeviceTotals(devicesData) {
|
||||
}
|
||||
});
|
||||
|
||||
// Render info boxes/tile cards
|
||||
console.log(getSetting('UI_hide_empty'));
|
||||
|
||||
console.log(dataArray);
|
||||
console.log(devicesData);
|
||||
|
||||
|
||||
// Render info boxes/tile cards
|
||||
renderInfoboxes(dataArray);
|
||||
}
|
||||
|
||||
@@ -357,8 +350,6 @@ function initFilters() {
|
||||
// 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");
|
||||
@@ -538,7 +529,10 @@ function mapColumnIndexToFieldName(index, tableColumnVisible) {
|
||||
"devSourcePlugin",
|
||||
"devPresentLastScan",
|
||||
"devAlertDown",
|
||||
"devCustomProps"
|
||||
"devCustomProps",
|
||||
"devFQDN",
|
||||
"devParentRelType",
|
||||
"devReqNicsOnline"
|
||||
];
|
||||
|
||||
// console.log("OrderBy: " + columnNames[tableColumnOrder[index]]);
|
||||
@@ -561,15 +555,17 @@ function initializeDatatable (status) {
|
||||
|
||||
// Define color & title for the status selected
|
||||
switch (deviceStatus) {
|
||||
case 'my_devices': tableTitle = getString('Device_Shortcut_AllDevices'); color = 'aqua'; break;
|
||||
case 'connected': tableTitle = getString('Device_Shortcut_Connected'); color = 'green'; break;
|
||||
case 'all': tableTitle = getString('Gen_All_Devices'); color = 'aqua'; break;
|
||||
case 'favorites': tableTitle = getString('Device_Shortcut_Favorites'); color = 'yellow'; break;
|
||||
case 'new': tableTitle = getString('Device_Shortcut_NewDevices'); color = 'yellow'; break;
|
||||
case 'down': tableTitle = getString('Device_Shortcut_DownOnly'); color = 'red'; break;
|
||||
case 'archived': tableTitle = getString('Device_Shortcut_Archived'); color = 'gray'; break;
|
||||
case 'offline': tableTitle = getString('Gen_Offline'); color = 'gray'; break;
|
||||
default: tableTitle = getString('Device_Shortcut_Devices'); color = 'gray'; break;
|
||||
case 'my_devices': tableTitle = getString('Device_Shortcut_AllDevices'); color = 'aqua'; break;
|
||||
case 'connected': tableTitle = getString('Device_Shortcut_Connected'); color = 'green'; break;
|
||||
case 'all': tableTitle = getString('Gen_All_Devices'); color = 'aqua'; break;
|
||||
case 'favorites': tableTitle = getString('Device_Shortcut_Favorites'); color = 'yellow'; break;
|
||||
case 'new': tableTitle = getString('Device_Shortcut_NewDevices'); color = 'yellow'; break;
|
||||
case 'down': tableTitle = getString('Device_Shortcut_DownOnly'); color = 'red'; break;
|
||||
case 'archived': tableTitle = getString('Device_Shortcut_Archived'); color = 'gray'; break;
|
||||
case 'offline': tableTitle = getString('Gen_Offline'); color = 'gray'; break;
|
||||
case 'all_devices': tableTitle = getString('Gen_All_Devices'); color = 'gray'; break;
|
||||
case 'network_devices': tableTitle = getString('Network_Devices'); color = 'aqua'; break;
|
||||
default: tableTitle = getString('Device_Shortcut_Devices'); color = 'gray'; break;
|
||||
}
|
||||
|
||||
// Set title and color
|
||||
@@ -598,7 +594,6 @@ function initializeDatatable (status) {
|
||||
}
|
||||
}
|
||||
|
||||
// todo: dynamically filter based on status
|
||||
var table = $('#tableDevices').DataTable({
|
||||
"serverSide": true,
|
||||
"processing": true,
|
||||
@@ -648,6 +643,9 @@ function initializeDatatable (status) {
|
||||
devParentChildrenCount
|
||||
devIpLong
|
||||
devCustomProps
|
||||
devFQDN
|
||||
devParentRelType
|
||||
devReqNicsOnline
|
||||
}
|
||||
count
|
||||
}
|
||||
@@ -686,8 +684,6 @@ function initializeDatatable (status) {
|
||||
return JSON.stringify(query); // Send the JSON request
|
||||
},
|
||||
"dataSrc": function (json) {
|
||||
console.log(json);
|
||||
|
||||
// Set the total number of records for pagination
|
||||
json.recordsTotal = json.devices.count || 0;
|
||||
json.recordsFiltered = json.devices.count || 0;
|
||||
@@ -722,7 +718,10 @@ function initializeDatatable (status) {
|
||||
device.devSourcePlugin || "",
|
||||
device.devPresentLastScan || "",
|
||||
device.devAlertDown || "",
|
||||
device.devCustomProps || ""
|
||||
device.devCustomProps || "",
|
||||
device.devFQDN || "",
|
||||
device.devParentRelType || "",
|
||||
device.devReqNicsOnline || 0
|
||||
];
|
||||
|
||||
const newRow = [];
|
||||
@@ -760,24 +759,41 @@ function initializeDatatable (status) {
|
||||
{visible: false, targets: tableColumnHide },
|
||||
{className: 'text-center', targets: [mapIndx(4), mapIndx(9), mapIndx(10), mapIndx(15), mapIndx(18)] },
|
||||
{className: 'iconColumn text-center', targets: [mapIndx(3)]},
|
||||
{width: '80px', targets: [mapIndx(6), mapIndx(7), mapIndx(15)] },
|
||||
{width: '80px', targets: [mapIndx(6), mapIndx(7), mapIndx(15), mapIndx(27)] },
|
||||
{width: '85px', targets: [mapIndx(9)] },
|
||||
{width: '30px', targets: [mapIndx(3), mapIndx(10), mapIndx(13), mapIndx(18)] },
|
||||
{orderData: [mapIndx(12)], targets: mapIndx(8) },
|
||||
|
||||
// Device Name
|
||||
{targets: [mapIndx(0)],
|
||||
'createdCell': function (td, cellData, rowData, row, col) {
|
||||
|
||||
// Device Name and FQDN
|
||||
{targets: [mapIndx(0), mapIndx(27)],
|
||||
'createdCell': function (td, cellData, rowData, row, col) {
|
||||
|
||||
// console.log(cellData)
|
||||
$(td).html ('<b class="anonymizeDev"><a href="deviceDetails.php?mac='+ rowData[mapIndx(11)] +'" class="">'+ cellData +'</a></b>');
|
||||
$(td).html (
|
||||
`<b class="anonymizeDev "
|
||||
>
|
||||
<a href="deviceDetails.php?mac=${rowData[mapIndx(11)]}" class="hover-node-info"
|
||||
data-name="${cellData}"
|
||||
data-ip="${rowData[mapIndx(8)]}"
|
||||
data-mac="${rowData[mapIndx(11)]}"
|
||||
data-vendor="${rowData[mapIndx(17)]}"
|
||||
data-type="${rowData[mapIndx(2)]}"
|
||||
data-firstseen="${rowData[mapIndx(6)]}"
|
||||
data-lastseen="${rowData[mapIndx(7)]}"
|
||||
data-relationship="${rowData[mapIndx(28)]}"
|
||||
data-status="${rowData[mapIndx(10)]}"
|
||||
data-present="${rowData[mapIndx(24)]}"
|
||||
data-alert="${rowData[mapIndx(25)]}"
|
||||
data-icon="${rowData[mapIndx(3)]}">
|
||||
${cellData}
|
||||
</a>
|
||||
</b>`
|
||||
);
|
||||
} },
|
||||
|
||||
// Connected Devices
|
||||
{targets: [mapIndx(15)],
|
||||
'createdCell': function (td, cellData, rowData, row, col) {
|
||||
|
||||
|
||||
'createdCell': function (td, cellData, rowData, row, col) {
|
||||
// check if this is a network device
|
||||
if(getSetting("NETWORK_DEVICE_TYPES").includes(`'${rowData[mapIndx(2)]}'`) )
|
||||
{
|
||||
@@ -819,7 +835,7 @@ function initializeDatatable (status) {
|
||||
<a href="http://${cellData}" class="pointer" target="_blank">
|
||||
${cellData}
|
||||
</a>
|
||||
<span class="alignRight">
|
||||
<span class="alignRight lockIcon">
|
||||
<a href="https://${cellData}" class="pointer" target="_blank">
|
||||
<i class="fa fa-lock "></i>
|
||||
</a>
|
||||
@@ -845,7 +861,7 @@ function initializeDatatable (status) {
|
||||
{targets: [mapIndx(26)],
|
||||
'createdCell': function (td, cellData, rowData, row, col) {
|
||||
if (!emptyArr.includes(cellData)){
|
||||
$(td).html (`<span>${renderCustomProps(cellData, rowData[mapIndx(11)])}<span>`);
|
||||
$(td).html (`<span>${renderCustomProps(cellData, rowData[mapIndx(11)])}</span>`);
|
||||
} else {
|
||||
$(td).html ('');
|
||||
}
|
||||
@@ -890,25 +906,14 @@ function initializeDatatable (status) {
|
||||
tmp_devPresentLastScan = rowData[mapIndx(24)]
|
||||
tmp_devAlertDown = rowData[mapIndx(25)]
|
||||
|
||||
if (tmp_devPresentLastScan == 1)
|
||||
{
|
||||
css = "green text-white statusOnline"
|
||||
icon = '<i class="fa-solid fa-plug"></i>'
|
||||
} else if (tmp_devPresentLastScan != 1 && tmp_devAlertDown == 1)
|
||||
{
|
||||
css = "red text-white statusDown"
|
||||
icon = '<i class="fa-solid fa-triangle-exclamation"></i>'
|
||||
} else if(tmp_devPresentLastScan != 1)
|
||||
{
|
||||
css = "gray text-white statusOffline"
|
||||
icon = '<i class="fa-solid fa-xmark"></i>'
|
||||
} else
|
||||
{
|
||||
css = "gray text-white statusUnknown"
|
||||
icon = '<i class="fa-solid fa-question"></i>'
|
||||
}
|
||||
const badge = getStatusBadgeParts(
|
||||
rowData[mapIndx(24)], // tmp_devPresentLastScan
|
||||
rowData[mapIndx(25)], // tmp_devAlertDown
|
||||
rowData[mapIndx(11)], // MAC
|
||||
cellData // optional text
|
||||
);
|
||||
|
||||
$(td).html (`<a href="deviceDetails.php?mac=${rowData[mapIndx(11)]}" class="badge bg-${css}">${icon} ${cellData.replace('-', '')}</a>`);
|
||||
$(td).html (`<a href="${badge.url}" class="badge ${badge.cssClass}">${badge.iconHtml} ${badge.text}</a>`);
|
||||
} },
|
||||
],
|
||||
|
||||
@@ -967,7 +972,7 @@ function initializeDatatable (status) {
|
||||
}, debounceTime);
|
||||
});
|
||||
|
||||
|
||||
initHoverNodeInfo();
|
||||
hideSpinner();
|
||||
|
||||
},
|
||||
@@ -1032,40 +1037,24 @@ function multiEditDevices()
|
||||
// -----------------------------------------------------------------------------
|
||||
// Function collects shown devices from the DataTable
|
||||
function getMacsOfShownDevices() {
|
||||
rows = $('#tableDevices')[0].rows;
|
||||
macs = [];
|
||||
var table = $('#tableDevices').DataTable();
|
||||
|
||||
// var devicesDataTableData = $('#tableDevices').dataTable().fnGetData();
|
||||
var devicesDataTableData = $('#tableDevices').DataTable().rows({ selected: false, page: 'current' }).data().toArray();
|
||||
var macs = [];
|
||||
|
||||
console.log(devicesDataTableData);
|
||||
// Get all row indexes on current page, in display order
|
||||
var allIndexes = table.rows({ page: 'current' }).indexes();
|
||||
|
||||
var selectedDevices = [];
|
||||
|
||||
// first row is the heading, skip
|
||||
for (var i = 1; i < rows.length; i++) {
|
||||
var rowIndex = rows[i]._DT_RowIndex;
|
||||
|
||||
// Ensure the rowIndex is valid and within bounds of devicesDataTableData
|
||||
if (rowIndex >= 0 && rowIndex < devicesDataTableData.length) {
|
||||
selectedDevices.push(devicesDataTableData[rowIndex]);
|
||||
} else {
|
||||
console.log(`Invalid rowIndex: ${rowIndex} at row ${i}`);
|
||||
allIndexes.each(function(idx) {
|
||||
var rowData = table.row(idx).data();
|
||||
if (rowData) {
|
||||
macs.push(rowData[mapIndx(11)]); // mapIndx(11) == MAC column
|
||||
}
|
||||
}
|
||||
|
||||
for (var j = 0; j < selectedDevices.length; j++) {
|
||||
// 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) {
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
require 'php/templates/header.php';
|
||||
?>
|
||||
|
||||
<script>
|
||||
showSpinner();
|
||||
</script>
|
||||
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
@@ -67,7 +71,7 @@
|
||||
<div class="inner"> <h3 id="eventsNewDevices"> -- </h3>
|
||||
<p class="infobox_label"><?= lang('Events_Shortcut_NewDevices');?></p>
|
||||
</div>
|
||||
<div class="icon"> <i class="ion ion-plus-round text-yellow-40"></i> </div>
|
||||
<div class="icon"> <i class="fa-solid fa-circle-plus text-yellow-40"></i> </div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
@@ -216,7 +220,7 @@ function initializeDatatable () {
|
||||
} },
|
||||
|
||||
// Replace HTML codes
|
||||
{targets: [3,4,5,6,7],
|
||||
{targets: [4,5,6,7],
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
$(td).html (translateHTMLcodes (cellData));
|
||||
} },
|
||||
@@ -226,13 +230,19 @@ function initializeDatatable () {
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
// console.log(cellData);
|
||||
$(td).html (cellData);
|
||||
} },
|
||||
// Date
|
||||
{targets: [3],
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
// console.log(cellData);
|
||||
$(td).html (localizeTimestamp(cellData));
|
||||
} }
|
||||
],
|
||||
|
||||
// Processing
|
||||
'processing' : true,
|
||||
'language' : {
|
||||
processing: '<table><td width="130px" align="middle"><?= lang("Events_Loading");?></td><td><i class="ion ion-ios-loop-strong fa-spin fa-2x fa-fw"></td></table>',
|
||||
processing: '<table><td width="130px" align="middle"><?= lang("Events_Loading");?></td><td><i class="fa-solid fa-spinner fa-spin-pulse"></i></td></table>',
|
||||
emptyTable: 'No data',
|
||||
"lengthMenu": "<?= lang('Events_Tablelenght');?>",
|
||||
"search": "<?= lang('Events_Searchbox');?>: ",
|
||||
@@ -241,6 +251,9 @@ function initializeDatatable () {
|
||||
"previous": "<?= lang('Events_Table_nav_prev');?>"
|
||||
},
|
||||
"info": "<?= lang('Events_Table_info');?>",
|
||||
},
|
||||
initComplete: function(settings, json) {
|
||||
hideSpinner(); // Called after the DataTable is fully initialized
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ if (isset ($_GET["action"]) && $_GET["action"] == 'logout')
|
||||
}
|
||||
|
||||
// Password without Cookie check -> pass and set initial cookie
|
||||
if (isset ($_POST["loginpassword"]) && $nax_Password == hash('sha256',$_POST["loginpassword"]))
|
||||
if (isset ($_POST["loginpassword"]) && $nax_Password === hash('sha256',$_POST["loginpassword"]))
|
||||
{
|
||||
header('Location: devices.php');
|
||||
$_SESSION["login"] = 1;
|
||||
@@ -37,7 +37,7 @@ if (isset ($_POST["loginpassword"]) && $nax_Password == hash('sha256',$_POST["lo
|
||||
}
|
||||
|
||||
// active Session or valid cookie (cookie not extends)
|
||||
if (( isset ($_SESSION["login"]) && ($_SESSION["login"] == 1)) || (isset ($_COOKIE[$CookieSaveLoginName]) && $nax_Password == $_COOKIE[$CookieSaveLoginName]))
|
||||
if (( isset ($_SESSION["login"]) && ($_SESSION["login"] == 1)) || (isset ($_COOKIE[$CookieSaveLoginName]) && $nax_Password === $_COOKIE[$CookieSaveLoginName]))
|
||||
{
|
||||
header('Location: devices.php');
|
||||
$_SESSION["login"] = 1;
|
||||
@@ -53,7 +53,7 @@ $login_icon = 'fa-info';
|
||||
// no active session, cookie not checked
|
||||
if (isset ($_SESSION["login"]) == FALSE || $_SESSION["login"] != 1)
|
||||
{
|
||||
if ($nax_Password == '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92')
|
||||
if ($nax_Password === '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92')
|
||||
{
|
||||
$login_info = lang('Login_Default_PWD');
|
||||
$login_mode = 'danger';
|
||||
@@ -99,18 +99,6 @@ if (isset ($_SESSION["login"]) == FALSE || $_SESSION["login"] != 1)
|
||||
|
||||
<!-- Favicon -->
|
||||
<link id="favicon" rel="icon" type="image/x-icon" href="img/NetAlertX_logo.png">
|
||||
|
||||
<!-- Dark-Mode Patch -->
|
||||
<?php
|
||||
switch ($UI_THEME) {
|
||||
case "Dark":
|
||||
echo '<link rel="stylesheet" href="css/dark-patch.css">';
|
||||
break;
|
||||
case "System":
|
||||
echo '<link rel="stylesheet" href="css/system-dark-patch.css">';
|
||||
break;
|
||||
}
|
||||
?>
|
||||
<link rel="stylesheet" href="/css/offline-font.css">
|
||||
</head>
|
||||
<body class="hold-transition login-page col-sm-12 col-sx-12">
|
||||
|
||||
52
front/initCheck.php
Executable file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
//------------------------------------------------------------------------------
|
||||
// check if authenticated
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
|
||||
?>
|
||||
|
||||
<div class="col-md-12">
|
||||
<div class="py-5">
|
||||
<div class="mt-4 text-center" id="check-status-plc" >
|
||||
<div class="alert alert-warning">
|
||||
<i class="fa-solid fa-spinner fa-spin text-secondary"></i> <?= lang('Maintenance_InitCheck_Checking');?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 text-center" id="check-status" style="display: none;">
|
||||
<div class="alert alert-success">
|
||||
<i class="fa-solid fa-check text-success"></i> <?= lang('Maintenance_InitCheck_Success');?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="d-flex flex-wrap gap-2 w-100" id="file-check-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="text-center box box-secondary col-md-6">
|
||||
<p class="text-muted"><?= lang('Maintenance_InitCheck_QuickSetupGuide');?></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 center text-center" >
|
||||
<button type="button" class=" col-md-12 btn btn-default bg-green " onclick="retryCheck()"><?= lang('Maintenance_ReCheck');?></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
function retryCheck() {
|
||||
// re-set page
|
||||
$('#file-check-list').empty();
|
||||
$('#check-status').hide();
|
||||
$('#check-status-plc').show();
|
||||
// re-run check
|
||||
checkAppInitializedJsonInit();
|
||||
}
|
||||
|
||||
$(document).ready(() => {
|
||||
checkAppInitializedJsonInit();
|
||||
});
|
||||
</script>
|
||||
@@ -113,7 +113,7 @@ function deleteAllCookies() {
|
||||
function cacheSettings()
|
||||
{
|
||||
return new Promise((resolve, reject) => {
|
||||
if(!getCache('completedCalls').includes('cacheSettings'))
|
||||
if(!getCache('cacheSettings_completed') === true)
|
||||
{
|
||||
$.get('php/server/query_json.php', { file: 'table_settings.json', nocache: Date.now() }, function(resSet) {
|
||||
|
||||
@@ -211,8 +211,18 @@ function getSetting (key) {
|
||||
function cacheStrings() {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
// Create a promise for each language
|
||||
languagesToLoad = ['en_us', getLangCode()]
|
||||
// Create a promise for each language (include en_us by default as fallback)
|
||||
languagesToLoad = ['en_us']
|
||||
|
||||
additionalLanguage = getLangCode()
|
||||
|
||||
if(additionalLanguage != 'en_us')
|
||||
{
|
||||
languagesToLoad.push(additionalLanguage)
|
||||
}
|
||||
|
||||
console.log(languagesToLoad);
|
||||
|
||||
const languagePromises = languagesToLoad.map((language_code) => {
|
||||
return new Promise((resolveLang, rejectLang) => {
|
||||
// Fetch core strings and translations
|
||||
@@ -235,7 +245,7 @@ function cacheStrings() {
|
||||
});
|
||||
|
||||
// Handle successful completion of language processing
|
||||
handleSuccess(`cacheStrings[${language_code}]`, resolveLang);
|
||||
handleSuccess(`cacheStrings`, resolveLang);
|
||||
})
|
||||
.fail((pluginError) => {
|
||||
// Handle failure in plugin strings fetching
|
||||
@@ -352,6 +362,76 @@ function getLangCode() {
|
||||
// -----------------------------------------------------------------------------
|
||||
// String utilities
|
||||
// -----------------------------------------------------------------------------
|
||||
function localizeTimestamp(input) {
|
||||
let tz = getSetting("TIMEZONE") || 'Europe/Berlin';
|
||||
|
||||
// Convert to string and trim
|
||||
input = String(input || '').trim();
|
||||
|
||||
// Normalize multiple spaces and remove commas
|
||||
const cleaned = input.replace(',', ' ').replace(/\s+/g, ' ');
|
||||
|
||||
// DD/MM/YYYY format check
|
||||
const dateTimeParts = cleaned.split(' ');
|
||||
if (dateTimeParts.length >= 2 && dateTimeParts[0].includes('/')) {
|
||||
const [day, month, year] = dateTimeParts[0].split('/');
|
||||
const timePart = dateTimeParts[1];
|
||||
|
||||
if (day && month && year && timePart) {
|
||||
const isoString = `${year}-${month}-${day}T${timePart.length === 5 ? timePart + ':00' : timePart}`;
|
||||
const date = new Date(isoString);
|
||||
if (!isFinite(date)) return 'b-';
|
||||
|
||||
return new Intl.DateTimeFormat('default', {
|
||||
timeZone: tz,
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false
|
||||
}).format(date);
|
||||
}
|
||||
}
|
||||
|
||||
// ISO style YYYY-MM-DD HH:mm(:ss)?
|
||||
const match = cleaned.match(/^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2})(:\d{2})?$/);
|
||||
if (match) {
|
||||
let iso = `${match[1]}T${match[2]}${match[3] || ':00'}`;
|
||||
|
||||
const date = new Date(iso);
|
||||
if (!isFinite(date)) return 'c-';
|
||||
|
||||
return new Intl.DateTimeFormat('default', {
|
||||
timeZone: tz,
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
// Fallback: try to parse any other string input
|
||||
const date = new Date(input);
|
||||
if (!isFinite(date)) return 'Failed conversion: ' + input;
|
||||
|
||||
return new Intl.DateTimeFormat('default', {
|
||||
timeZone: tz,
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ----------------------------------------------------
|
||||
/**
|
||||
@@ -959,11 +1039,8 @@ function getDevDataByMac(macAddress, dbColumn) {
|
||||
// Cache the devices as one JSON
|
||||
function cacheDevices()
|
||||
{
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
// if(!getCache('completedCalls').includes('cacheDevices'))
|
||||
// {
|
||||
$.get('php/server/query_json.php', { file: 'table_devices.json', nocache: Date.now() }, function(data) {
|
||||
|
||||
// console.log(data)
|
||||
@@ -987,8 +1064,7 @@ function cacheDevices()
|
||||
// console.log(getCache('devicesListAll_JSON'))
|
||||
}).then(() => handleSuccess('cacheDevices', resolve())).catch(() => handleFailure('cacheDevices', reject("cacheDevices already completed"))); // handle AJAX synchronization
|
||||
}
|
||||
// }
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
var devicesListAll_JSON = []; // this will contain a list off all devices
|
||||
@@ -1029,47 +1105,48 @@ function getGuid() {
|
||||
// -----------------------------------------------------------------------------
|
||||
// Loading Spinner overlay
|
||||
// -----------------------------------------------------------------------------
|
||||
spinnerHtml = `
|
||||
<!-- spinner -->
|
||||
<div id="loadingSpinner" style="display: block">
|
||||
<div class="pa_semitransparent-panel"></div>
|
||||
<div class="panel panel-default pa_spinner">
|
||||
<table>
|
||||
<td width="130px" align="middle">_text_</td>
|
||||
<td><i class="ion ion-ios-loop-strong fa-spin fa-2x fa-fw"></td>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
function showSpinner(stringKey='Loading')
|
||||
{
|
||||
let spinnerTimeout = null;
|
||||
let animationTime = 300
|
||||
|
||||
if(stringKey == "")
|
||||
{
|
||||
text = ''
|
||||
} else
|
||||
{
|
||||
text = getString(stringKey)
|
||||
}
|
||||
function showSpinner(stringKey = 'Loading') {
|
||||
const text = isEmpty(stringKey) ? "Loading" : getString(stringKey || "Loading");
|
||||
const spinner = $("#loadingSpinner");
|
||||
|
||||
if (spinner.length && spinner.is(':visible')) {
|
||||
clearTimeout(spinnerTimeout);
|
||||
|
||||
$("#loadingSpinnerText").text(text);
|
||||
spinner.addClass("visible");
|
||||
|
||||
text = isEmpty(text) ? "Loading" : text;
|
||||
spinner.fadeIn(animationTime);
|
||||
} else {
|
||||
$("#loadingSpinnerText").text(text);
|
||||
|
||||
if($("#loadingSpinner").length)
|
||||
{
|
||||
$("#loadingSpinner").show();
|
||||
}
|
||||
else{
|
||||
$(".wrapper").append(spinnerHtml.replace('_text_',text))
|
||||
requestAnimationFrame(() => {
|
||||
spinner.addClass("visible");
|
||||
spinner.fadeIn(animationTime);
|
||||
});
|
||||
}
|
||||
}
|
||||
// -----------------------------------------------------------------------------
|
||||
function hideSpinner()
|
||||
{
|
||||
$("#loadingSpinner").hide()
|
||||
|
||||
function hideSpinner() {
|
||||
clearTimeout(spinnerTimeout);
|
||||
|
||||
const spinner = $("#loadingSpinner");
|
||||
|
||||
if (spinner.length) {
|
||||
spinner.removeClass("visible");
|
||||
spinner.fadeOut(animationTime);
|
||||
|
||||
spinnerTimeout = setTimeout(() => {
|
||||
spinner.removeClass("visible");
|
||||
spinner.fadeOut(animationTime); // optional remove or hide again
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Calls a backend function to add a front-end event to an execution queue
|
||||
function updateApi(apiEndpoints)
|
||||
@@ -1344,8 +1421,8 @@ function restartBackend() {
|
||||
const sessionStorageKey = "myScriptExecuted_common_js";
|
||||
var completedCalls = []
|
||||
var completedCalls_final = ['cacheSettings', 'cacheStrings', 'cacheDevices'];
|
||||
var completedCallsCount = 0;
|
||||
var completedCallsCount_final;
|
||||
var lang_completedCalls = 0;
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Clearing all the caches
|
||||
@@ -1431,12 +1508,27 @@ async function isGraphQLServerRunning() {
|
||||
// Check if the code has been executed before by checking sessionStorage
|
||||
function isAppInitialized() {
|
||||
|
||||
completedCalls = parseInt(getCache("completedCallsCount"));
|
||||
shouldBeCompletedCalls = getLangCode() == 'en_us' ? 3 : 4;
|
||||
lang_shouldBeCompletedCalls = getLangCode() == 'en_us' ? 1 : 2;
|
||||
|
||||
return (
|
||||
completedCalls >= shouldBeCompletedCalls
|
||||
);
|
||||
// check if each ajax call completed succesfully
|
||||
$.each(completedCalls_final, function(index, call_name){
|
||||
|
||||
if(getCache(call_name + "_completed") != "true")
|
||||
{
|
||||
console.log(`[isAppInitialized] AJAX call ${call_name} unsuccesful: ${getCache(call_name + "_completed")}`)
|
||||
return false;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// check if all required languages chached
|
||||
if(parseInt(getCache("cacheStringsCountCompleted")) != lang_shouldBeCompletedCalls)
|
||||
{
|
||||
console.log(`[isAppInitialized] AJAX call cacheStrings unsuccesful: ${getCache("cacheStringsCountCompleted")} out of ${lang_shouldBeCompletedCalls}`)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -1467,27 +1559,25 @@ async function executeOnce() {
|
||||
// Function to handle successful completion of an AJAX call
|
||||
const handleSuccess = (callName) => {
|
||||
console.log(`AJAX call successful: ${callName}`);
|
||||
// completedCalls.push(callName);
|
||||
// setCache('completedCalls', mergeUniqueArrays(getCache('completedCalls').split(','), [callName]));
|
||||
|
||||
val = getCache('completedCallsCount');
|
||||
|
||||
if(val == "")
|
||||
if(callName.includes("cacheStrings"))
|
||||
{
|
||||
val = 0;
|
||||
} else
|
||||
{
|
||||
val = parseInt(val)
|
||||
completed_tmp = getCache("cacheStringsCountCompleted");
|
||||
completed_tmp == "" ? completed_tmp = 0 : completed_tmp = completed_tmp;
|
||||
completed_tmp++;
|
||||
setCache("cacheStringsCountCompleted", completed_tmp);
|
||||
}
|
||||
|
||||
setCache('completedCallsCount', val + 1)
|
||||
setCache(callName + "_completed", true)
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Function to handle failure of an AJAX call
|
||||
const handleFailure = (callName, callback) => {
|
||||
console.error(`AJAX call ${callName} failed`);
|
||||
msg = `AJAX call ${callName} failed`
|
||||
console.error(msg);
|
||||
// Implement retry logic here if needed
|
||||
// write_notification(msg, 'interrupt')
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@@ -33,7 +33,7 @@ function versionUpdateUI(){
|
||||
// Checks if a new version is available via the global app_state.json
|
||||
function checkIfNewVersionAvailable()
|
||||
{
|
||||
$.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) {
|
||||
|
||||
// console.log(appState["isNewVersionChecked"])
|
||||
// console.log(appState["isNewVersion"])
|
||||
@@ -48,4 +48,4 @@ function checkIfNewVersionAvailable()
|
||||
}
|
||||
|
||||
// handle the dispaly of the NEW icon
|
||||
checkIfNewVersionAvailable()
|
||||
checkIfNewVersionAvailable()
|
||||
|
||||
@@ -363,8 +363,6 @@ function removeAllOptions(element) {
|
||||
function selectAll(element) {
|
||||
settingsChanged();
|
||||
|
||||
// Get the <select> element with the class 'deviceSelector'
|
||||
// var selectElement = $('.deviceSelector select');
|
||||
var selectElement = $(`#${$(element).attr("my-input-to")}`);
|
||||
|
||||
// Iterate over each option within the select element
|
||||
@@ -381,8 +379,6 @@ function selectAll(element) {
|
||||
// UN-Select All
|
||||
function unselectAll(element) {
|
||||
settingsChanged();
|
||||
// Get the <select> element with the class 'deviceSelector'
|
||||
// var selectElement = $('.deviceSelector select');
|
||||
var selectElement = $(`#${$(element).attr("my-input-to")}`);
|
||||
|
||||
// Iterate over each option within the select element
|
||||
@@ -399,8 +395,7 @@ function unselectAll(element) {
|
||||
// Trigger change to open up the dropdown filed
|
||||
function selectChange(element) {
|
||||
settingsChanged();
|
||||
// Get the <select> element with the class 'deviceSelector'
|
||||
// var selectElement = $('.deviceSelector select');
|
||||
|
||||
var selectElement = $(`#${$(element).attr("my-input-to")}`);
|
||||
|
||||
selectElement.parent().find("input").focus().click();
|
||||
@@ -433,7 +428,7 @@ function initListInteractionOptions(element) {
|
||||
if (clickCounter === 1) {
|
||||
// Single-click action
|
||||
showModalFieldInput(
|
||||
`<i class="fa-regular fa-pen-to-square"></i> ${getString(
|
||||
`<i class="fa fa-pen-to-square"></i> ${getString(
|
||||
"Gen_Update_Value"
|
||||
)}`,
|
||||
getString("settings_update_item_warning"),
|
||||
@@ -624,6 +619,7 @@ function generateOptionsOrSetOptions(
|
||||
// console.log( setKey);
|
||||
|
||||
// NOTE {value} options to replace with a setting or SQL value are handled in the cacheSettings() function
|
||||
// obj.push({ id: item, name: item })
|
||||
options = arrayToObject(createArray(overrideOptions ? overrideOptions : getSettingOptions(setKey)))
|
||||
|
||||
|
||||
@@ -689,6 +685,13 @@ function reverseTransformers(val, transformers) {
|
||||
// retrieve string
|
||||
val = getString(val);
|
||||
break;
|
||||
case "deviceChip":
|
||||
mac = val // value is mac
|
||||
val = `${getDevDataByMac(mac, "devName")}`
|
||||
break;
|
||||
case "deviceRelType":
|
||||
val = val; // nothing to do
|
||||
break;
|
||||
default:
|
||||
console.warn(`Unknown transformer: ${transformer}`);
|
||||
}
|
||||
@@ -822,13 +825,14 @@ function arrayToObject(array) {
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Processor to generate options
|
||||
// options - available options
|
||||
// valuesArray - values = selected options
|
||||
function generateOptions(options, valuesArray, targetField, transformers, placeholder) {
|
||||
var optionsHtml = "";
|
||||
|
||||
resultArray = []
|
||||
selectedArray = []
|
||||
cssClass = ""
|
||||
|
||||
cssClass = ""
|
||||
|
||||
// determine if options or values are used in the listing
|
||||
if (valuesArray.length > 0 && options.length > 0){
|
||||
@@ -847,7 +851,6 @@ function generateOptions(options, valuesArray, targetField, transformers, placeh
|
||||
// dropdown -> options only (value == 1 STRING not ARRAY)
|
||||
resultArray = options;
|
||||
}
|
||||
|
||||
|
||||
// Create a map to track the index of each item in valuesArray
|
||||
const orderMap = new Map(valuesArray.map((item, index) => [item, index]));
|
||||
@@ -1007,10 +1010,12 @@ function generateFormHtml(settingsData, set, overrideValue, overrideOptions, ori
|
||||
class="form-control ${addCss} ${cssClasses}"
|
||||
name="${setKey}"
|
||||
id="${setKey}"
|
||||
my-transformers=${transformers}
|
||||
my-customparams="${customParams}"
|
||||
my-customid="${customId}"
|
||||
my-originalSetKey="${originalSetKey}"
|
||||
${multi}>
|
||||
${multi}
|
||||
${readOnly ? "disabled" : ""}>
|
||||
<option value="" id="${setKey + "_temp_"}"></option>
|
||||
</select>`;
|
||||
|
||||
@@ -1188,22 +1193,49 @@ function generateFormHtml(settingsData, set, overrideValue, overrideOptions, ori
|
||||
|
||||
const eventsList = createArray(set['setEvents']);
|
||||
// inline buttons events
|
||||
|
||||
if (eventsList.length > 0) {
|
||||
eventsList.forEach(event => {
|
||||
let eventIcon = "fa-play";
|
||||
|
||||
if (eventsList.length > 0) {
|
||||
eventsList.forEach(event => {
|
||||
switch (event) {
|
||||
case "select_icon":
|
||||
eventIcon = "fa-chevron-down";
|
||||
break;
|
||||
case "add_icon":
|
||||
case "add_option":
|
||||
eventIcon = "fa-square-plus";
|
||||
break;
|
||||
case "copy_icons":
|
||||
eventIcon = "fa-copy";
|
||||
break;
|
||||
case "go_to_device":
|
||||
eventIcon = "fa-square-up-right";
|
||||
break;
|
||||
case "go_to_node":
|
||||
eventIcon = "fa-sitemap fa-rotate-270";
|
||||
break;
|
||||
case "run":
|
||||
eventIcon = "fa-play";
|
||||
break;
|
||||
case "test":
|
||||
eventIcon = "fa-vial-circle-check";
|
||||
break;
|
||||
default:
|
||||
eventIcon = "fa-play";
|
||||
break;
|
||||
}
|
||||
|
||||
eventsHtml += `<span class="input-group-addon pointer"
|
||||
id="${`${event}_${setKey}`}"
|
||||
data-myparam-setkey="${setKey}"
|
||||
data-myparam="${setKey}"
|
||||
data-myparam-plugin="${setKey.split('_')[0] || ''}"
|
||||
data-myevent="${event}"
|
||||
onclick="execute_settingEvent(this)">
|
||||
<i title="${getString(event + "_event_tooltip")}" class="fa ${getString(event + "_event_icon")}"></i>
|
||||
</span>`;
|
||||
});
|
||||
}
|
||||
eventsHtml += `<span class="input-group-addon pointer"
|
||||
id="${`${event}_${setKey}`}"
|
||||
data-myparam-setkey="${setKey}"
|
||||
data-myparam="${setKey}"
|
||||
data-myparam-plugin="${setKey.split('_')[0] || ''}"
|
||||
data-myevent="${event}"
|
||||
onclick="execute_settingEvent(this)">
|
||||
<i title="${getString(event + "_event_tooltip")}" class="fa ${eventIcon}"></i>
|
||||
</span>`;
|
||||
});
|
||||
}
|
||||
|
||||
// Combine and return the final HTML
|
||||
return inputHtml + eventsHtml;
|
||||
|
||||
@@ -26,6 +26,134 @@ function lockDatabase(delay=20) {
|
||||
}
|
||||
|
||||
|
||||
const requiredFiles = [
|
||||
'app_state.json',
|
||||
'plugins.json',
|
||||
'table_devices.json',
|
||||
'table_devices_filters.json',
|
||||
'table_devices_tiles.json',
|
||||
'table_notifications.json',
|
||||
'table_online_history.json',
|
||||
'table_appevents.json',
|
||||
'table_custom_endpoint.json',
|
||||
'table_events_pending_alert.json',
|
||||
'table_plugins_events.json',
|
||||
'table_plugins_history.json',
|
||||
'table_plugins_language_strings.json',
|
||||
'table_plugins_objects.json',
|
||||
'table_settings.json',
|
||||
'user_notifications.json'
|
||||
];
|
||||
|
||||
const internalChecks = ['isAppInitialized', 'isGraphQLServerRunning'];
|
||||
|
||||
const fileStatus = {}; // Track file check results
|
||||
|
||||
function updateFileStatusUI(file, status) {
|
||||
const item = $(`#file-${file.replace(/[^a-z0-9]/gi, '-')}`);
|
||||
const icon = item.find('span.icon-wrap');
|
||||
|
||||
if (status === 'ok') {
|
||||
icon.html('<i class="fa-solid fa-check "></i>');
|
||||
} else if (status === 'fail') {
|
||||
icon.html('<i class="fa-solid fa-xmark "></i>');
|
||||
} else {
|
||||
icon.html('<i class="fa-solid fa-spinner fa-spin text-secondary"></i>');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function checkAppInitializedJson() {
|
||||
requiredFiles.forEach(file => {
|
||||
$.get('php/server/query_json.php', { file, nocache: Date.now() })
|
||||
.done(() => {
|
||||
if (fileStatus[file] !== 'ok') {
|
||||
fileStatus[file] = 'ok';
|
||||
updateFileStatusUI(file, 'ok');
|
||||
}
|
||||
})
|
||||
.fail(() => {
|
||||
fileStatus[file] = 'fail';
|
||||
updateFileStatusUI(file, 'fail');
|
||||
});
|
||||
});
|
||||
|
||||
const allOk = requiredFiles.every(file => fileStatus[file] === 'ok');
|
||||
|
||||
if (allOk) {
|
||||
checkInternalStatusAfterFiles();
|
||||
} else {
|
||||
setTimeout(checkAppInitializedJson, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function checkInternalStatusAfterFiles() {
|
||||
const promises = [
|
||||
waitForAppInitialized().then(() => {
|
||||
fileStatus['isAppInitialized'] = 'ok';
|
||||
updateFileStatusUI('isAppInitialized', 'ok');
|
||||
}).catch(() => {
|
||||
fileStatus['isAppInitialized'] = 'fail';
|
||||
updateFileStatusUI('isAppInitialized', 'fail');
|
||||
}),
|
||||
|
||||
waitForGraphQLServer().then(() => {
|
||||
fileStatus['isGraphQLServerRunning'] = 'ok';
|
||||
updateFileStatusUI('isGraphQLServerRunning', 'ok');
|
||||
}).catch(() => {
|
||||
fileStatus['isGraphQLServerRunning'] = 'fail';
|
||||
updateFileStatusUI('isGraphQLServerRunning', 'fail');
|
||||
})
|
||||
];
|
||||
|
||||
Promise.allSettled(promises).then(() => {
|
||||
const allPassed = internalChecks.every(key => fileStatus[key] === 'ok');
|
||||
if (allPassed) {
|
||||
$('#check-status').show();
|
||||
$('#check-status-plc').hide();
|
||||
} else {
|
||||
setTimeout(checkInternalStatusAfterFiles, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
function waitForAppInitialized() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (isAppInitialized()) {
|
||||
resolve();
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initial UI setup for all items
|
||||
function checkAppInitializedJsonInit() {
|
||||
const allItems = [...requiredFiles, ...internalChecks];
|
||||
|
||||
allItems.forEach(file => {
|
||||
|
||||
|
||||
$('#file-check-list').append(`
|
||||
<div class="panel panel-secondary col-xs-6 col-sm-4 col-md-3 col-lg-2 col-xxl-1 padding-5px">
|
||||
<div class="file-checking border rounded p-2 d-flex flex-column justify-content-between h-100" id="file-${file.replace(/[^a-z0-9]/gi, '-')}">
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
<span class="file-name-wrap flex-grow-1 text-truncate" title="${file}">${file}</span>
|
||||
<span class="icon-wrap align-items-center text-center"><i class="fa-solid fa-spinner fa-spin text-secondary"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
fileStatus[file] = 'checking';
|
||||
});
|
||||
|
||||
checkAppInitializedJson();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -8,64 +8,6 @@
|
||||
----------------------------------------------------------------------------- */
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Initialize device selectors / pickers fields
|
||||
// -----------------------------------------------------------------------------
|
||||
function initDeviceSelectors(devicesListAll_JSON) {
|
||||
|
||||
// Check if both device list exists
|
||||
if (devicesListAll_JSON) {
|
||||
// Parse the JSON string to get the device list array
|
||||
var devicesList = JSON.parse(devicesListAll_JSON);
|
||||
|
||||
var selectorFieldsHTML = ''
|
||||
|
||||
// Loop through the devices list
|
||||
devicesList.forEach(function(device) {
|
||||
|
||||
selectorFieldsHTML += `<option value="${device.devMac}">${device.devName}</option>`;
|
||||
});
|
||||
|
||||
selector = `<div class="db_info_table_row col-sm-12" >
|
||||
<div class="form-group" >
|
||||
<div class="input-group col-sm-12 " >
|
||||
<select class="form-control select2 select2-hidden-accessible" multiple="" style="width: 100%;" tabindex="-1" aria-hidden="true">
|
||||
${selectorFieldsHTML}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
|
||||
// Find HTML elements with class "deviceSelector" and append selector field
|
||||
$('.deviceSelector').append(selector);
|
||||
}
|
||||
|
||||
// Initialize selected items after a delay so selected macs are available in the context
|
||||
setTimeout(function(){
|
||||
// Retrieve MAC addresses from query string or cache
|
||||
var macs = getQueryString('macs') || getCache('selectedDevices');
|
||||
|
||||
if(macs)
|
||||
{
|
||||
// Split MAC addresses if they are comma-separated
|
||||
macs = macs.split(',');
|
||||
|
||||
console.log(macs)
|
||||
|
||||
// Loop through macs to be selected list
|
||||
macs.forEach(function(mac) {
|
||||
|
||||
// Create the option and append to Select2
|
||||
var option = new Option($('.deviceSelector select option[value="' + mac + '"]').html(), mac, true, true);
|
||||
|
||||
$('.deviceSelector select').append(option).trigger('change');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}, 10);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Utility function to generate a random API token in the format t_<random string of specified length>
|
||||
@@ -128,9 +70,6 @@ function getRandomBytes(elem, length) {
|
||||
targetElement.val(formattedHex);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// ----------------------------------------------
|
||||
// Updates the icon preview
|
||||
function updateAllIconPreviews() {
|
||||
@@ -200,9 +139,9 @@ function validateRegex(elem) {
|
||||
|
||||
// Validate against regex
|
||||
if (regex.test(value)) {
|
||||
iconSpan.html("<i class='fa-regular fa-check'></i>");
|
||||
iconSpan.html("<i class='fa fa-check'></i>");
|
||||
} else {
|
||||
iconSpan.html("<i class='fa-regular fa-xmark'></i>");
|
||||
iconSpan.html("<i class='fa fa-xmark'></i>");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,6 +281,7 @@ function execute_settingEvent(element) {
|
||||
feSetKey = $(element).attr('data-myparam-setkey');
|
||||
feParam = $(element).attr('data-myparam');
|
||||
feSourceId = $(element).attr('id');
|
||||
feValue = $("#"+feSetKey).val();
|
||||
|
||||
if (["test", "run"].includes(feEvent)) {
|
||||
// Calls a backend function to add a front-end event (specified by the attributes 'data-myevent' and 'data-myparam-plugin' on the passed element) to an execution queue
|
||||
@@ -383,8 +323,12 @@ function execute_settingEvent(element) {
|
||||
() => addIconAsBase64(element), // Wrap in an arrow function
|
||||
feSourceId // triggered by id
|
||||
);
|
||||
} else if (["copy_icons"].includes(feEvent)) {
|
||||
} else if (["select_icon"].includes(feEvent)) {
|
||||
|
||||
showIconSelection(feSetKey)
|
||||
// myparam-setkey
|
||||
|
||||
} else if (["copy_icons"].includes(feEvent)) {
|
||||
|
||||
// Ask overwrite icon types
|
||||
showModalWarning (
|
||||
@@ -394,9 +338,12 @@ function execute_settingEvent(element) {
|
||||
getString('Gen_Okay'),
|
||||
'overwriteIconType'
|
||||
);
|
||||
} else if (["go_to_node"].includes(feEvent)) {
|
||||
} else if (["go_to_device"].includes(feEvent)) {
|
||||
|
||||
goToNetworkNode('NEWDEV_devParentMAC');
|
||||
goToDevice(feValue);
|
||||
} else if (["go_to_node"].includes(feEvent)) {
|
||||
|
||||
goToNetworkNode(feValue);
|
||||
|
||||
} else {
|
||||
console.warn(`🔺Not implemented: ${feEvent}`)
|
||||
@@ -408,12 +355,24 @@ function execute_settingEvent(element) {
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Go to the correct network node in the Network section
|
||||
function goToNetworkNode(dropdownId)
|
||||
{
|
||||
setCache('activeNetworkTab', $('#'+dropdownId).val().replaceAll(":","_")+'_id');
|
||||
function goToNetworkNode(mac)
|
||||
{
|
||||
setCache('activeNetworkTab', mac.replaceAll(":","_")+'_id');
|
||||
window.location.href = './network.php';
|
||||
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Go to the device
|
||||
function goToDevice(mac, newtab = false) {
|
||||
const url = './deviceDetails.php?mac=' + encodeURIComponent(mac);
|
||||
|
||||
if (newtab) {
|
||||
window.open(url, '_blank');
|
||||
} else {
|
||||
window.location.href = url;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------------------------
|
||||
@@ -494,10 +453,11 @@ function addIconAsBase64 (el) {
|
||||
|
||||
}
|
||||
|
||||
// -----------------------------------------------
|
||||
// modal pop up for icon selection
|
||||
function showIconSelection(setKey) {
|
||||
|
||||
|
||||
function showIconSelection() {
|
||||
const selectElement = document.getElementById('NEWDEV_devIcon');
|
||||
const selectElement = document.getElementById(setKey);
|
||||
const modalId = 'dynamicIconModal';
|
||||
|
||||
// Create modal HTML dynamically
|
||||
@@ -574,19 +534,9 @@ 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
|
||||
// Get the correct db column code name based on table header title string
|
||||
function getColumnNameFromLangString(headStringKey) {
|
||||
columnNameMap = {
|
||||
"Device_TableHead_Name": "devName",
|
||||
@@ -615,12 +565,96 @@ function getColumnNameFromLangString(headStringKey) {
|
||||
"Device_TableHead_SourcePlugin": "devSourcePlugin",
|
||||
"Device_TableHead_PresentLastScan": "devPresentLastScan",
|
||||
"Device_TableHead_AlertDown": "devAlertDown",
|
||||
"Device_TableHead_CustomProps": "devCustomProps"
|
||||
"Device_TableHead_CustomProps": "devCustomProps",
|
||||
"Device_TableHead_FQDN": "devFQDN",
|
||||
"Device_TableHead_ParentRelType": "devParentRelType",
|
||||
"Device_TableHead_ReqNicsOnline": "devReqNicsOnline"
|
||||
};
|
||||
|
||||
return columnNameMap[headStringKey] || "";
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
// Generating the device status chip
|
||||
function getStatusBadgeParts(devPresentLastScan, devAlertDown, devMac, statusText = '') {
|
||||
let css = 'bg-gray text-white statusUnknown';
|
||||
let icon = '<i class="fa-solid fa-question"></i>';
|
||||
let status = 'unknown';
|
||||
let cssText = '';
|
||||
|
||||
if (devPresentLastScan == 1) {
|
||||
css = 'bg-green text-white statusOnline';
|
||||
cssText = 'text-green';
|
||||
icon = '<i class="fa-solid fa-plug"></i>';
|
||||
status = 'online';
|
||||
} else if (devAlertDown == 1) {
|
||||
css = 'bg-red text-white statusDown';
|
||||
cssText = 'text-red';
|
||||
icon = '<i class="fa-solid fa-triangle-exclamation"></i>';
|
||||
status = 'down';
|
||||
} else if (devPresentLastScan != 1) {
|
||||
css = 'bg-gray text-white statusOffline';
|
||||
cssText = 'text-gray50';
|
||||
icon = '<i class="fa-solid fa-xmark"></i>';
|
||||
status = 'offline';
|
||||
}
|
||||
|
||||
const cleanedText = statusText.replace(/-/g, '');
|
||||
const url = `deviceDetails.php?mac=${encodeURIComponent(devMac)}`;
|
||||
|
||||
return {
|
||||
cssClass: css,
|
||||
cssText: cssText,
|
||||
iconHtml: icon,
|
||||
mac: devMac,
|
||||
text: cleanedText,
|
||||
status: status,
|
||||
url: url
|
||||
};
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
// Getting the color and css class for device relationships
|
||||
function getRelationshipConf(relType) {
|
||||
let cssClass = '';
|
||||
let color = '';
|
||||
|
||||
// --color-aqua: #00c0ef;
|
||||
// --color-blue: #0060df;
|
||||
// --color-green: #00a65a;
|
||||
// --color-yellow: #f39c12;
|
||||
// --color-red: #dd4b39;
|
||||
|
||||
switch (relType) {
|
||||
|
||||
case "child":
|
||||
color = "#f39c12"; // yellow
|
||||
cssClass = "text-yellow";
|
||||
break;
|
||||
case "nic":
|
||||
color = "#dd4b39"; // red
|
||||
cssClass = "text-red";
|
||||
break;
|
||||
case "virtual":
|
||||
color = "#0060df"; // blue
|
||||
cssClass = "text-blue";
|
||||
break;
|
||||
case "logical":
|
||||
color = "#00a65a"; // green
|
||||
cssClass = "text-green";
|
||||
break;
|
||||
default:
|
||||
color = "#5B5B66"; // grey
|
||||
cssClass = "text-light-grey";
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
cssClass: cssClass,
|
||||
color: color
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// initialize
|
||||
@@ -634,17 +668,90 @@ function initSelect2() {
|
||||
// check if cache ready
|
||||
if(isValidJSON(devicesListAll_JSON))
|
||||
{
|
||||
// prepare HTML DOM before initializing the frotend
|
||||
initDeviceSelectors(devicesListAll_JSON)
|
||||
|
||||
|
||||
// --------------------------------------------------------
|
||||
//Initialize Select2 Elements and make them sortable
|
||||
|
||||
$(function () {
|
||||
// Iterate over each Select2 dropdown
|
||||
$('.select2').each(function() {
|
||||
var selectEl = $(this).select2();
|
||||
$('.select2').each(function() {
|
||||
// handle Device chips, if my-transformers="deviceChip"
|
||||
if($(this).attr("my-transformers") == "deviceChip")
|
||||
{
|
||||
var selectEl = $(this).select2({
|
||||
templateSelection: function (data, container) {
|
||||
if (!data.id) return data.text; // default for placeholder etc.
|
||||
|
||||
const device = getDevDataByMac(data.id);
|
||||
|
||||
const badge = getStatusBadgeParts(
|
||||
device.devPresentLastScan,
|
||||
device.devAlertDown,
|
||||
device.devMac
|
||||
)
|
||||
|
||||
$(container).addClass(badge.cssClass);
|
||||
|
||||
// Custom HTML
|
||||
const html = $(`
|
||||
<a href="${badge.url}" target="_blank">
|
||||
<span class="custom-chip hover-node-info"
|
||||
data-name="${device.devName}"
|
||||
data-ip="${device.devLastIP}"
|
||||
data-mac="${device.devMac}"
|
||||
data-vendor="${device.devVendor}"
|
||||
data-type="${device.devType}"
|
||||
data-lastseen="${device.devLastConnection}"
|
||||
data-firstseen="${device.devFirstConnection}"
|
||||
data-relationship="${device.devParentRelType}"
|
||||
data-status="${device.devStatus}"
|
||||
data-present="${device.devPresentLastScan}"
|
||||
data-alert="${device.devAlertDown}"
|
||||
data-icon="${device.devIcon}"
|
||||
>
|
||||
<span class="iconPreview">${atob(device.devIcon)}</span>
|
||||
${data.text}
|
||||
<span>
|
||||
(${badge.iconHtml})
|
||||
</span
|
||||
</span>
|
||||
</a>
|
||||
`);
|
||||
|
||||
return html;
|
||||
},
|
||||
escapeMarkup: function (m) {
|
||||
return m; // Allow HTML
|
||||
}
|
||||
});
|
||||
|
||||
} else if($(this).attr("my-transformers") == "deviceRelType") // handling dropdown for relationships
|
||||
{
|
||||
var selectEl = $(this).select2({
|
||||
minimumResultsForSearch: Infinity,
|
||||
templateSelection: function (data, container) {
|
||||
if (!data.id) return data.text; // default for placeholder etc.
|
||||
|
||||
const relConf = getRelationshipConf(data.text);
|
||||
|
||||
// Custom HTML
|
||||
const html = $(`
|
||||
<span class="custom-chip ${relConf.cssClass}" >
|
||||
${data.text}
|
||||
</span>
|
||||
`);
|
||||
|
||||
return html;
|
||||
},
|
||||
escapeMarkup: function (m) {
|
||||
return m; // Allow HTML
|
||||
}
|
||||
});
|
||||
|
||||
} else // default handling - default template
|
||||
{
|
||||
var selectEl = $(this).select2();
|
||||
}
|
||||
|
||||
// Apply sortable functionality to the dropdown's dropdown-container
|
||||
selectEl.next().children().children().children().sortable({
|
||||
@@ -675,14 +782,127 @@ function initSelect2() {
|
||||
}
|
||||
}
|
||||
|
||||
// init functions after dom loaded
|
||||
window.addEventListener("load", function() {
|
||||
// try to initialize
|
||||
setTimeout(() => {
|
||||
initSelect2()
|
||||
// initializeiCheck();
|
||||
}, 1000);
|
||||
});
|
||||
// ------------------------------------------
|
||||
// Display device info on hover (attach only once)
|
||||
function initHoverNodeInfo() {
|
||||
if ($('#hover-box').length === 0) {
|
||||
$('<div id="hover-box"></div>').appendTo('body').hide().css({
|
||||
position: 'absolute',
|
||||
zIndex: 9999,
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: '8px',
|
||||
padding: '10px',
|
||||
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
|
||||
minWidth: '200px',
|
||||
maxWidth: '300px',
|
||||
fontSize: '14px',
|
||||
pointerEvents: 'none',
|
||||
backgroundColor: '#fff'
|
||||
});
|
||||
}
|
||||
|
||||
// check if handlers already attached to prevent flickering
|
||||
if (initHoverNodeInfo._handlersAttached) return;
|
||||
initHoverNodeInfo._handlersAttached = true;
|
||||
|
||||
let hoverTimeout = null;
|
||||
let lastTarget = null;
|
||||
|
||||
// remove title as it's replaced by the hover-box
|
||||
$(document).on('mouseover', '.hover-node-info', function () {
|
||||
this.removeAttribute('title');
|
||||
|
||||
$(this).attr("title", ""); // remove title as it's replaced by the hover-box
|
||||
});
|
||||
|
||||
$(document).on('mouseenter', '.hover-node-info', function (e) {
|
||||
const $el = $(this);
|
||||
lastTarget = this;
|
||||
|
||||
// use timeout to prevent a quick hover and exit toi flash a card when navigating to a target node with your mouse
|
||||
clearTimeout(hoverTimeout);
|
||||
|
||||
hoverTimeout = setTimeout(() => {
|
||||
if (lastTarget !== this) return;
|
||||
|
||||
const icon = $el.data('icon');
|
||||
const name = $el.data('name') || 'Unknown';
|
||||
const ip = $el.data('ip') || 'N/A';
|
||||
const mac = $el.data('mac') || 'N/A';
|
||||
const vendor = $el.data('vendor') || 'Unknown';
|
||||
const type = $el.data('type') || 'Unknown';
|
||||
const lastseen = $el.data('lastseen') || 'Unknown';
|
||||
const firstseen = $el.data('firstseen') || 'Unknown';
|
||||
const relationship = $el.data('relationship') || 'Unknown';
|
||||
const badge = getStatusBadgeParts( $el.data('present'), $el.data('alert'), $el.data('mac'))
|
||||
const status =`<span class="badge ${badge.cssClass}">${badge.iconHtml} ${badge.status}</span>`
|
||||
|
||||
const html = `
|
||||
<div>
|
||||
<b> <div class="iconPreview">${atob(icon)}</div> </b><b class="devName"> ${name}</b><br>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="line">
|
||||
<b>Status:</b> <span>${status}</span><br>
|
||||
</div>
|
||||
<div class="line">
|
||||
<b>IP:</b> <span>${ip}</span><br>
|
||||
</div>
|
||||
<div class="line">
|
||||
<b>MAC:</b> <span>${mac}</span><br>
|
||||
</div>
|
||||
<div class="line">
|
||||
<b>Vendor:</b> <span>${vendor}</span><br>
|
||||
</div>
|
||||
<div class="line">
|
||||
<b>Type:</b> <span>${type}</span><br>
|
||||
</div>
|
||||
<div class="line">
|
||||
<b>First seen:</b> <span>${firstseen}</span><br>
|
||||
</div>
|
||||
<div class="line">
|
||||
<b>Last seen:</b> <span>${lastseen}</span><br>
|
||||
</div>
|
||||
<div class="line">
|
||||
<b>Relationship:</b> <span class="${getRelationshipConf(relationship).cssClass}">${relationship}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
$('#hover-box').html(html).fadeIn(150);
|
||||
}, 300);
|
||||
});
|
||||
|
||||
$(document).on('mousemove', '.hover-node-info', function (e) {
|
||||
const hoverBox = $('#hover-box');
|
||||
const boxWidth = hoverBox.outerWidth();
|
||||
const boxHeight = hoverBox.outerHeight();
|
||||
const padding = 15;
|
||||
|
||||
const winWidth = $(window).width();
|
||||
const winHeight = $(window).height();
|
||||
|
||||
let left = e.pageX + padding;
|
||||
let top = e.pageY + padding;
|
||||
|
||||
// Position leftward if close to right edge
|
||||
if (e.pageX + boxWidth + padding > winWidth) {
|
||||
left = e.pageX - boxWidth - padding;
|
||||
}
|
||||
|
||||
// Position upward if close to bottom edge
|
||||
if (e.pageY + boxHeight + padding > winHeight) {
|
||||
top = e.pageY - boxHeight - padding;
|
||||
}
|
||||
|
||||
hoverBox.css({ top: top + 'px', left: left + 'px' });
|
||||
});
|
||||
|
||||
$(document).on('mouseleave', '.hover-node-info', function () {
|
||||
clearTimeout(hoverTimeout);
|
||||
lastTarget = null;
|
||||
$('#hover-box').fadeOut(100);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
console.log("init ui_components.js")
|
||||
|
Before Width: | Height: | Size: 326 KiB After Width: | Height: | Size: 305 KiB |