mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2025-12-07 01:26:11 -08:00
Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b28cd243f0 | ||
|
|
dce8c34064 | ||
|
|
9502ee0cd0 | ||
|
|
8eb4ffe3ed | ||
|
|
4be59807e5 | ||
|
|
4712a2ff29 | ||
|
|
f9179a1e89 | ||
|
|
a6df204721 | ||
|
|
101189ae7c | ||
|
|
f25c012fbe | ||
|
|
868a85d84c | ||
|
|
771dd4b176 | ||
|
|
ed4d3bf17c | ||
|
|
7c728fbe36 | ||
|
|
4ff9d01ef5 | ||
|
|
1bce2e80e8 | ||
|
|
1556d74406 | ||
|
|
9b3947cc90 | ||
|
|
18b0309ac4 | ||
|
|
0afd4ae115 | ||
|
|
09e360c746 | ||
|
|
5dbe79ba2f | ||
|
|
779707761f | ||
|
|
16992bb2bd | ||
|
|
3374f83255 | ||
|
|
8f420a14cd | ||
|
|
57024c0cb1 | ||
|
|
db7fb825fe | ||
|
|
49e8c6a4f2 | ||
|
|
66bf4241b2 | ||
|
|
76a5dda553 | ||
|
|
6393aa7f2c | ||
|
|
c5f938113f | ||
|
|
dac7eaba6d | ||
|
|
35e6059068 | ||
|
|
afebc8dc39 | ||
|
|
34151a86b1 | ||
|
|
72d6934345 | ||
|
|
f5f7031030 | ||
|
|
ffccca9424 | ||
|
|
3f5ae334a2 | ||
|
|
bb45c4d345 | ||
|
|
bad3c76de9 |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,3 +1,3 @@
|
||||
github: jokob-sk
|
||||
patreon: 84385063
|
||||
patreon: netalertx
|
||||
buy_me_a_coffee: jokobsk
|
||||
|
||||
81
.github/workflows/docker_rewrite.yml
vendored
Executable file
81
.github/workflows/docker_rewrite.yml
vendored
Executable file
@@ -0,0 +1,81 @@
|
||||
name: docker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- rewrite
|
||||
tags:
|
||||
- '*.*.*'
|
||||
pull_request:
|
||||
branches:
|
||||
- rewrite
|
||||
|
||||
jobs:
|
||||
docker_dev:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
if: >
|
||||
contains(github.event.head_commit.message, 'PUSHPROD') != 'True' &&
|
||||
github.repository == 'jokob-sk/NetAlertX'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Set up dynamic build ARGs
|
||||
id: getargs
|
||||
run: echo "version=$(cat ./stable/VERSION)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Get release version
|
||||
id: get_version
|
||||
run: echo "version=Dev" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create .VERSION file
|
||||
run: echo "${{ steps.get_version.outputs.version }}" >> .VERSION
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/jokob-sk/netalertx-dev-rewrite
|
||||
jokobsk/netalertx-dev-rewrite
|
||||
tags: |
|
||||
type=raw,value=latest
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=sha
|
||||
|
||||
- name: Log in to Github Container Registry (GHCR)
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: jokob-sk
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Log in to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@@ -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 yattag git+https://github.com/foreign-sub/aiofreepybox.git \
|
||||
RUN pip install openwrt-luci-rpc asusrouter asyncio aiohttp graphene flask flask-cors unifi-sm-api 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 {} \;"
|
||||
|
||||
@@ -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 yattag "
|
||||
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 flask-cors unifi-sm-api 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
|
||||
|
||||
@@ -24,7 +24,7 @@ LOADED_PLUGINS=['ARPSCAN', 'AVAHISCAN', 'CSVBCKP','DBCLNP', 'DIGSCAN', 'INTRNT',
|
||||
|
||||
DAYS_TO_KEEP_EVENTS=90
|
||||
# Used for generating links in emails. Make sure not to add a trailing slash!
|
||||
REPORT_DASHBOARD_URL='http://127.0.0.1'
|
||||
REPORT_DASHBOARD_URL='update_REPORT_DASHBOARD_URL_setting'
|
||||
|
||||
# Make sure at least these scanners are enabled for new installs, other defaults are taken from the config.json
|
||||
INTRNT_RUN='schedule'
|
||||
|
||||
200
back/device_heuristics_rules.json
Executable file
200
back/device_heuristics_rules.json
Executable file
@@ -0,0 +1,200 @@
|
||||
[
|
||||
{
|
||||
"dev_type": "Gateway",
|
||||
"icon_html": "<i class=\"fa fa-globe\"></i>",
|
||||
"matching_pattern": [
|
||||
{ "mac_prefix": "INTERNET", "vendor": "" }
|
||||
],
|
||||
"name_pattern": []
|
||||
},
|
||||
{
|
||||
"dev_type": "Access Point",
|
||||
"icon_html": "<i class=\"fa fa-network-wired\"></i>",
|
||||
"matching_pattern": [
|
||||
{ "mac_prefix": "74ACB9", "vendor": "Ubiquiti" },
|
||||
{ "mac_prefix": "002468", "vendor": "Cisco" },
|
||||
{ "mac_prefix": "F4F5D8", "vendor": "TP-Link" },
|
||||
{ "mac_prefix": "F88E85", "vendor": "Netgear" }
|
||||
],
|
||||
"name_pattern": ["router", "gateway", "ap", "access point", "access-point", "switch"]
|
||||
},
|
||||
{
|
||||
"dev_type": "Phone",
|
||||
"icon_html": "<i class=\"fa-brands fa-apple\"></i>",
|
||||
"matching_pattern": [
|
||||
{ "mac_prefix": "001A79", "vendor": "Apple" },
|
||||
{ "mac_prefix": "B0BE83", "vendor": "Samsung" },
|
||||
{ "mac_prefix": "BC926B", "vendor": "Motorola" }
|
||||
],
|
||||
"name_pattern": ["iphone", "ipad", "pixel", "galaxy", "redmi"]
|
||||
},
|
||||
{
|
||||
"dev_type": "Phone",
|
||||
"icon_html": "<i class=\"fa-solid fa-mobile\"></i>",
|
||||
"matching_pattern": [
|
||||
],
|
||||
"name_pattern": ["android","samsung"]
|
||||
},
|
||||
{
|
||||
"dev_type": "Tablet",
|
||||
"icon_html": "<i class=\"fa fa-tablet\"></i>",
|
||||
"matching_pattern": [
|
||||
{ "mac_prefix": "001B63", "vendor": "Apple" },
|
||||
{ "mac_prefix": "BC4C4C", "vendor": "Samsung" }
|
||||
],
|
||||
"name_pattern": ["tablet", "pad"]
|
||||
},
|
||||
{
|
||||
"dev_type": "IoT",
|
||||
"icon_html": "<i class=\"fa-brands fa-raspberry-pi\"></i>",
|
||||
"matching_pattern": [
|
||||
{ "mac_prefix": "B827EB", "vendor": "Raspberry Pi" },
|
||||
{ "mac_prefix": "DCA632", "vendor": "Raspberry Pi" }
|
||||
],
|
||||
"name_pattern": ["raspberry", "pi"]
|
||||
},
|
||||
{
|
||||
"dev_type": "IoT",
|
||||
"icon_html": "<i class=\"fa-solid fa-microchip\"></i>",
|
||||
"matching_pattern": [
|
||||
{ "mac_prefix": "840D8E", "vendor": "Espressif" },
|
||||
{ "mac_prefix": "ECFABC", "vendor": "Espressif" },
|
||||
{ "mac_prefix": "7C9EBD", "vendor": "Espressif" }
|
||||
],
|
||||
"name_pattern": ["raspberry", "pi"]
|
||||
},
|
||||
{
|
||||
"dev_type": "Desktop",
|
||||
"icon_html": "<i class=\"fa fa-desktop\"></i>",
|
||||
"matching_pattern": [
|
||||
{ "mac_prefix": "001422", "vendor": "Dell" },
|
||||
{ "mac_prefix": "001874", "vendor": "Lenovo" },
|
||||
{ "mac_prefix": "00E04C", "vendor": "Hewlett Packard" }
|
||||
],
|
||||
"name_pattern": ["desktop", "pc", "computer"]
|
||||
},
|
||||
{
|
||||
"dev_type": "Laptop",
|
||||
"icon_html": "<i class=\"fa fa-laptop\"></i>",
|
||||
"matching_pattern": [
|
||||
{ "mac_prefix": "3C0754", "vendor": "HP" },
|
||||
{ "mac_prefix": "0017A4", "vendor": "Dell" },
|
||||
{ "mac_prefix": "F4CE46", "vendor": "Lenovo" },
|
||||
{ "mac_prefix": "409F38", "vendor": "Acer" }
|
||||
],
|
||||
"name_pattern": ["macbook", "imac", "laptop", "notebook"]
|
||||
},
|
||||
{
|
||||
"dev_type": "Server",
|
||||
"icon_html": "<i class=\"fa fa-server\"></i>",
|
||||
"matching_pattern": [
|
||||
{ "mac_prefix": "001CBF", "vendor": "Supermicro" },
|
||||
{ "mac_prefix": "002186", "vendor": "Dell" },
|
||||
{ "mac_prefix": "D02788", "vendor": "Hewlett Packard" },
|
||||
{ "mac_prefix": "002590", "vendor": "IBM" }
|
||||
],
|
||||
"name_pattern": ["server", "nas"]
|
||||
},
|
||||
{
|
||||
"dev_type": "VM",
|
||||
"icon_html": "<i class=\"fa fa-server\"></i>",
|
||||
"matching_pattern": [
|
||||
{ "mac_prefix": "525400", "vendor": "QEMU" },
|
||||
{ "mac_prefix": "005056", "vendor": "VMware" },
|
||||
{ "mac_prefix": "000C29", "vendor": "VMware" },
|
||||
{ "mac_prefix": "000569", "vendor": "VMware" },
|
||||
{ "mac_prefix": "00163E", "vendor": "Xen" },
|
||||
{ "mac_prefix": "080027", "vendor": "VirtualBox" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"dev_type": "TV",
|
||||
"icon_html": "<i class=\"fa fa-tv\"></i>",
|
||||
"matching_pattern": [
|
||||
{ "mac_prefix": "0013CE", "vendor": "Samsung" },
|
||||
{ "mac_prefix": "0017C8", "vendor": "LG" },
|
||||
{ "mac_prefix": "D46E0E", "vendor": "Sony" }
|
||||
],
|
||||
"name_pattern": ["tv", "television", "smarttv"]
|
||||
},
|
||||
{
|
||||
"dev_type": "Gaming Console",
|
||||
"icon_html": "<i class=\"fa fa-gamepad\"></i>",
|
||||
"matching_pattern": [
|
||||
{ "mac_prefix": "001FA7", "vendor": "Sony" },
|
||||
{ "mac_prefix": "7C04D0", "vendor": "Nintendo" },
|
||||
{ "mac_prefix": "EC26CA", "vendor": "Sony" }
|
||||
],
|
||||
"name_pattern": ["playstation", "xbox"]
|
||||
},
|
||||
{
|
||||
"dev_type": "Camera",
|
||||
"icon_html": "<i class=\"fa fa-camera\"></i>",
|
||||
"matching_pattern": [
|
||||
{ "mac_prefix": "A45E60", "vendor": "Hikvision" },
|
||||
{ "mac_prefix": "00408C", "vendor": "Axis" },
|
||||
{ "mac_prefix": "00156D", "vendor": "Amcrest" },
|
||||
{ "mac_prefix": "AC9E17", "vendor": "Reolink" }
|
||||
],
|
||||
"name_pattern": ["camera", "cam", "webcam"]
|
||||
},
|
||||
{
|
||||
"dev_type": "Smart Speaker",
|
||||
"icon_html": "<i class=\"fa fa-volume-up\"></i>",
|
||||
"matching_pattern": [
|
||||
{ "mac_prefix": "44650D", "vendor": "Amazon" },
|
||||
{ "mac_prefix": "74ACB9", "vendor": "Google" }
|
||||
],
|
||||
"name_pattern": ["echo", "alexa", "dot"]
|
||||
},
|
||||
{
|
||||
"dev_type": "Router",
|
||||
"icon_html": "<i class=\"fa fa-random\"></i>",
|
||||
"matching_pattern": [
|
||||
{ "mac_prefix": "000C29", "vendor": "Cisco" },
|
||||
{ "mac_prefix": "00155D", "vendor": "MikroTik" }
|
||||
],
|
||||
"name_pattern": ["router", "gateway", "ap", "access point", "access-point"],
|
||||
"ip_pattern": [
|
||||
"^192\\.168\\.[0-1]\\.1$",
|
||||
"^10\\.0\\.0\\.1$"
|
||||
]
|
||||
},
|
||||
{
|
||||
"dev_type": "Smart Light",
|
||||
"icon_html": "<i class=\"fa fa-lightbulb\"></i>",
|
||||
"matching_pattern": [],
|
||||
"name_pattern": ["hue", "lifx", "bulb"]
|
||||
},
|
||||
{
|
||||
"dev_type": "Smart Home",
|
||||
"icon_html": "<i class=\"fa fa-house\"></i>",
|
||||
"matching_pattern": [],
|
||||
"name_pattern": ["google", "chromecast", "nest"]
|
||||
},
|
||||
{
|
||||
"dev_type": "Smartwatch",
|
||||
"icon_html": "<i class=\"fa fa-watch\"></i>",
|
||||
"matching_pattern": [],
|
||||
"name_pattern": ["watch", "wear"]
|
||||
},
|
||||
{
|
||||
"dev_type": "Printer",
|
||||
"icon_html": "<i class=\"fa fa-print\"></i>",
|
||||
"matching_pattern": [],
|
||||
"name_pattern": ["printer", "print"]
|
||||
},
|
||||
{
|
||||
"dev_type": "Security Device",
|
||||
"icon_html": "<i class=\"fa fa-shield-alt\"></i>",
|
||||
"matching_pattern": [],
|
||||
"name_pattern": ["doorbell", "lock", "security"]
|
||||
},
|
||||
{
|
||||
"dev_type": "Smart Light",
|
||||
"icon_html": "<i class=\"fa-solid fa-lightbulb\"></i>",
|
||||
"matching_pattern": [
|
||||
],
|
||||
"name_pattern": ["light","bulb"]
|
||||
}
|
||||
]
|
||||
@@ -59,6 +59,9 @@ services:
|
||||
- ${DEV_LOCATION}/front/presence.php:/app/front/presence.php
|
||||
- ${DEV_LOCATION}/front/settings.php:/app/front/settings.php
|
||||
- ${DEV_LOCATION}/front/systeminfo.php:/app/front/systeminfo.php
|
||||
- ${DEV_LOCATION}/front/systeminfoNetwork.php:/app/front/systeminfoNetwork.php
|
||||
- ${DEV_LOCATION}/front/systeminfoServer.php:/app/front/systeminfoServer.php
|
||||
- ${DEV_LOCATION}/front/systeminfoStorage.php:/app/front/systeminfoStorage.php
|
||||
- ${DEV_LOCATION}/front/cloud_services.php:/app/front/cloud_services.php
|
||||
- ${DEV_LOCATION}/front/report.php:/app/front/report.php
|
||||
- ${DEV_LOCATION}/front/workflows.php:/app/front/workflows.php
|
||||
|
||||
106
docs/API.md
106
docs/API.md
@@ -221,6 +221,112 @@ Example JSON of the `table_devices.json` endpoint with two Devices (database row
|
||||
|
||||
```
|
||||
|
||||
## API Endpoint: Prometheus Exporter
|
||||
|
||||
* **Endpoint URL**: `/metrics`
|
||||
* **Host**: (where NetAlertX exporter is running)
|
||||
* **Port**: as configured in the `GRAPHQL_PORT` setting (`20212` by default)
|
||||
|
||||
---
|
||||
|
||||
### Example Output of the `/metrics` Endpoint
|
||||
|
||||
Below is a representative snippet of the metrics you may find when querying the `/metrics` endpoint for `netalertx`. It includes both aggregate counters and `device_status` labels per device.
|
||||
|
||||
```
|
||||
netalertx_connected_devices 31
|
||||
netalertx_offline_devices 54
|
||||
netalertx_down_devices 0
|
||||
netalertx_new_devices 0
|
||||
netalertx_archived_devices 31
|
||||
netalertx_favorite_devices 2
|
||||
netalertx_my_devices 54
|
||||
|
||||
netalertx_device_status{device="Net - Huawei", mac="Internet", ip="1111.111.111.111", vendor="None", first_connection="2021-01-01 00:00:00", last_connection="2025-08-04 17:57:00", dev_type="Router", device_status="Online"} 1
|
||||
netalertx_device_status{device="Net - USG", mac="74:ac:74:ac:74:ac", ip="192.168.1.1", vendor="Ubiquiti Networks Inc.", first_connection="2022-02-12 22:05:00", last_connection="2025-06-07 08:16:49", dev_type="Firewall", device_status="Archived"} 1
|
||||
netalertx_device_status{device="Raspberry Pi 4 LAN", mac="74:ac:74:ac:74:74", ip="192.168.1.9", vendor="Raspberry Pi Trading Ltd", first_connection="2022-02-12 22:05:00", last_connection="2025-08-04 17:57:00", dev_type="Singleboard Computer (SBC)", device_status="Online"} 1
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Metrics Explanation
|
||||
|
||||
#### 1. Aggregate Device Counts
|
||||
|
||||
Metric names prefixed with `netalertx_` provide aggregated counts by device status:
|
||||
|
||||
* `netalertx_connected_devices`: number of devices currently connected
|
||||
* `netalertx_offline_devices`: devices currently offline
|
||||
* `netalertx_down_devices`: down/unreachable devices
|
||||
* `netalertx_new_devices`: devices recently detected
|
||||
* `netalertx_archived_devices`: archived devices
|
||||
* `netalertx_favorite_devices`: user-marked favorite devices
|
||||
* `netalertx_my_devices`: devices associated with the current user context
|
||||
|
||||
These numeric values give a high-level overview of device distribution.
|
||||
|
||||
#### 2. Per‑Device Status with Labels
|
||||
|
||||
Each individual device is represented by a `netalertx_device_status` metric, with descriptive labels:
|
||||
|
||||
* `device`: friendly name of the device
|
||||
* `mac`: MAC address (or placeholder)
|
||||
* `ip`: last recorded IP address
|
||||
* `vendor`: manufacturer or "None" if unknown
|
||||
* `first_connection`: timestamp when the device was first observed
|
||||
* `last_connection`: most recent contact timestamp
|
||||
* `dev_type`: device category or type
|
||||
* `device_status`: current status (Online / Offline / Archived / Down / ...)
|
||||
|
||||
The metric value is always `1` (indicating presence or active state) and the combination of labels identifies the device.
|
||||
|
||||
---
|
||||
|
||||
### How to Query with `curl`
|
||||
|
||||
To fetch the metrics from the NetAlertX exporter:
|
||||
|
||||
```sh
|
||||
curl 'http://<server_ip>:<GRAPHQL_PORT>/metrics' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Accept: text/plain'
|
||||
```
|
||||
|
||||
Replace:
|
||||
|
||||
* `<server_ip>`: IP or hostname of the NetAlertX server
|
||||
* `<GRAPHQL_PORT>`: port specified in your `GRAPHQL_PORT` setting (default: `20212`)
|
||||
* `<API_TOKEN>` your Bearer token from the `API_TOKEN` setting
|
||||
|
||||
---
|
||||
|
||||
### Summary
|
||||
|
||||
* **Endpoint**: `/metrics` provides both summary counters and per-device status entries.
|
||||
* **Aggregate metrics** help monitor overall device states.
|
||||
* **Detailed metrics** expose each device’s metadata via labels.
|
||||
* **Use case**: feed into Prometheus for scraping, monitoring, alerting, or charting dashboard views.
|
||||
|
||||
### Prometheus Scraping Configuration
|
||||
|
||||
```yaml
|
||||
scrape_configs:
|
||||
- job_name: 'netalertx'
|
||||
metrics_path: /metrics
|
||||
scheme: http
|
||||
scrape_interval: 60s
|
||||
static_configs:
|
||||
- targets: ['<server_ip>:<GRAPHQL_PORT>']
|
||||
authorization:
|
||||
type: Bearer
|
||||
credentials: <API_TOKEN>
|
||||
```
|
||||
|
||||
### Grafana template
|
||||
|
||||
Grafana template sample: [Download json](./samples/API/Grafana_Dashboard.json)
|
||||
|
||||
## API Endpoint: /log files
|
||||
|
||||
This API endpoint retrieves files from the `/app/log` folder.
|
||||
|
||||
34
docs/DEBUG_PHP.md
Executable file
34
docs/DEBUG_PHP.md
Executable file
@@ -0,0 +1,34 @@
|
||||
# Debugging backend PHP issues
|
||||
|
||||
## Logs in UI
|
||||
|
||||

|
||||
|
||||
You can view recent backend PHP errors directly in the **Maintenance > Logs** section of the UI. This provides quick access to logs without needing terminal access.
|
||||
|
||||
## Accessing logs directly
|
||||
|
||||
Sometimes, the UI might not be accessible. In that case, you can access the logs directly inside the container.
|
||||
|
||||
### Step-by-step:
|
||||
|
||||
1. **Open a shell into the container:**
|
||||
|
||||
```bash
|
||||
docker exec -it netalertx /bin/sh
|
||||
```
|
||||
|
||||
2. **Check the NGINX error log:**
|
||||
|
||||
```bash
|
||||
cat /var/log/nginx/error.log
|
||||
```
|
||||
|
||||
3. **Check the PHP application error log:**
|
||||
|
||||
```bash
|
||||
cat /app/log/app.php_errors.log
|
||||
```
|
||||
|
||||
These logs will help identify syntax issues, fatal errors, or startup problems when the UI fails to load properly.
|
||||
|
||||
114
docs/DEVICE_HEURISTICS.md
Executable file
114
docs/DEVICE_HEURISTICS.md
Executable file
@@ -0,0 +1,114 @@
|
||||
# Device Heuristics: Icon and Type Guessing
|
||||
|
||||
This module is responsible for inferring the most likely **device type** and **icon** based on minimal identifying data like MAC address, vendor, IP, or device name.
|
||||
|
||||
It does this using a set of heuristics defined in an external JSON rules file, which it evaluates **in priority order**.
|
||||
|
||||
>[!NOTE]
|
||||
> You can find the full source code of the heuristics module in the `device_heuristics.py` file.
|
||||
|
||||
---
|
||||
|
||||
## JSON Rule Format
|
||||
|
||||
Rules are defined in a file called `device_heuristics_rules.json` (located under `/back`), structured like:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"dev_type": "Phone",
|
||||
"icon_html": "<i class=\"fa-brands fa-apple\"></i>",
|
||||
"matching_pattern": [
|
||||
{ "mac_prefix": "001A79", "vendor": "Apple" }
|
||||
],
|
||||
"name_pattern": ["iphone", "pixel"]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
>[!NOTE]
|
||||
> Feel free to raise a PR in case you'd like to add any rules into the `device_heuristics_rules.json` file. Please place new rules into the correct position and consider the priority of already available rules.
|
||||
|
||||
### Supported fields:
|
||||
|
||||
| Field | Type | Description |
|
||||
| ------------------ | -------------------- | --------------------------------------------------------------- |
|
||||
| `dev_type` | `string` | Type to assign if rule matches (e.g. `"Gateway"`, `"Phone"`) |
|
||||
| `icon_html` | `string` | Icon (HTML string) to assign if rule matches. Encoded to base64 at load time. |
|
||||
| `matching_pattern` | `array` | List of `{ mac_prefix, vendor }` objects for first strict and then loose matching |
|
||||
| `name_pattern` | `array` *(optional)* | List of lowercase substrings (used with regex) |
|
||||
| `ip_pattern` | `array` *(optional)* | Regex patterns to match IPs |
|
||||
|
||||
**Order in this array defines priority** — rules are checked top-down and short-circuit on first match.
|
||||
|
||||
---
|
||||
|
||||
## Matching Flow (in Priority Order)
|
||||
|
||||
The function `guess_device_attributes(...)` runs a series of matching functions in strict order:
|
||||
|
||||
1. MAC + Vendor → `match_mac_and_vendor()`
|
||||
2. Vendor only → `match_vendor()`
|
||||
3. Name pattern → `match_name()`
|
||||
4. IP pattern → `match_ip()`
|
||||
5. Final fallback → defaults defined in the `NEWDEV_devIcon` and `NEWDEV_devType` settings.
|
||||
|
||||
> [!NOTE]
|
||||
> The app will try guessing the device type or icon if `devType` or `devIcon` are `""` or `"null"`.
|
||||
|
||||
### Use of default values
|
||||
|
||||
The guessing process runs for every device **as long as the current type or icon still matches the default values**. Even if earlier heuristics return a match, the system continues evaluating additional clues — like name or IP — to try and replace placeholders.
|
||||
|
||||
```python
|
||||
# Still considered a match attempt if current values are defaults
|
||||
if (not type_ or type_ == default_type) or (not icon or icon == default_icon):
|
||||
type_, icon = match_ip(ip, default_type, default_icon)
|
||||
```
|
||||
|
||||
In other words: if the type or icon is still `"unknown"` (or matches the default), the system assumes the match isn’t final — and keeps looking. It stops only when both values are non-default (defaults are defined in the `NEWDEV_devIcon` and `NEWDEV_devType` settings).
|
||||
|
||||
---
|
||||
|
||||
## Match Behavior (per function)
|
||||
|
||||
These functions are executed in the following order:
|
||||
|
||||
### `match_mac_and_vendor(mac_clean, vendor, ...)`
|
||||
|
||||
* Looks for MAC prefix **and** vendor substring match
|
||||
* Most precise
|
||||
* Stops as soon as a match is found
|
||||
|
||||
### `match_vendor(vendor, ...)`
|
||||
|
||||
* Falls back to substring match on vendor only
|
||||
* Ignores rules where `mac_prefix` is present (ensures this is really a fallback)
|
||||
|
||||
### `match_name(name, ...)`
|
||||
|
||||
* Lowercase name is compared against all `name_pattern` values using regex
|
||||
* Good for user-assigned labels (e.g. "AP Office", "iPhone")
|
||||
|
||||
### `match_ip(ip, ...)`
|
||||
|
||||
* If IP is present and matches regex patterns under any rule, it returns that type/icon
|
||||
* Usually used for gateways or local IP ranges
|
||||
|
||||
---
|
||||
|
||||
## Icons
|
||||
|
||||
* Each rule can define an `icon_html`, which is converted to a `icon_base64` on load
|
||||
* If missing, it falls back to the passed-in `default_icon` (`NEWDEV_devIcon` setting)
|
||||
* If a match is found but icon is still blank, default is used
|
||||
|
||||
**TL;DR:** Type and icon must both be matched. If only one is matched, the other falls back to the default.
|
||||
|
||||
---
|
||||
|
||||
## Priority Mechanics
|
||||
|
||||
* JSON rules are evaluated **top-to-bottom**
|
||||
* Matching is **first-hit wins** — no scoring, no weights
|
||||
* Rules that are more specific (e.g. exact MAC prefixes) should be listed earlier
|
||||
BIN
docs/img/DEBUG/maintenance_debug_php.png
Executable file
BIN
docs/img/DEBUG/maintenance_debug_php.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 160 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 7.0 KiB |
1110
docs/samples/API/Grafana_Dashboard.json
Executable file
1110
docs/samples/API/Grafana_Dashboard.json
Executable file
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
require 'php/templates/header.php';
|
||||
require 'php/templates/notification.php';
|
||||
require 'php/templates/modals.php';
|
||||
?>
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
|
||||
|
||||
@@ -387,6 +387,16 @@ body
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.plugin-content #tabs-location .nav-tabs-custom > .nav-tabs > li
|
||||
{
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.plugin-content .left-nav
|
||||
{
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.pa-small-box-2 .inner h3 {
|
||||
margin-left: 0em;
|
||||
margin-bottom: 1.3em;
|
||||
@@ -1411,6 +1421,7 @@ input[readonly] {
|
||||
.iconPreview svg{
|
||||
min-width: 20px;
|
||||
max-width: 20px;
|
||||
margin-bottom: -3px;
|
||||
}
|
||||
|
||||
|
||||
@@ -1489,7 +1500,7 @@ input[readonly] {
|
||||
}
|
||||
|
||||
#tableDevicesBox td svg, #tableDevicesBox td i{
|
||||
height: 1.5em !important;
|
||||
height: 1em !important;
|
||||
}
|
||||
|
||||
#TileCards .tile .inner
|
||||
@@ -1649,6 +1660,21 @@ input[readonly] {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.custom-badge a
|
||||
{
|
||||
color: #fff !important;
|
||||
font-size: 14px;
|
||||
}
|
||||
.custom-badge
|
||||
{
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 4px;
|
||||
border-style: solid;
|
||||
padding: 0 5px;
|
||||
font-size: 14px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#deviceDetailsEdit .form-control
|
||||
{
|
||||
min-height: 42px;
|
||||
@@ -1700,6 +1726,16 @@ input[readonly] {
|
||||
width: 92%;
|
||||
}
|
||||
|
||||
#modal-ok
|
||||
{
|
||||
z-index: 1051; /*highest priority*/
|
||||
}
|
||||
|
||||
#modal-form-plc
|
||||
{
|
||||
display: grid;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------- */
|
||||
/* NETWORK page */
|
||||
/* ----------------------------------------------------------------- */
|
||||
@@ -2093,10 +2129,10 @@ input[readonly] {
|
||||
#loadingSpinner {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* top: 0; */
|
||||
/* left: 0; */
|
||||
/* width: 100%; */
|
||||
/* height: 100%; */
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
pointer-events: none;
|
||||
@@ -2114,16 +2150,16 @@ input[readonly] {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.pa_semitransparent-panel {
|
||||
.nax_semitransparent-panel {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
opacity: 0.8;
|
||||
opacity: 0.5;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.pa_spinner {
|
||||
.nax_spinner {
|
||||
position: absolute;
|
||||
top: 100px;
|
||||
left: 50%;
|
||||
@@ -2160,9 +2196,10 @@ input[readonly] {
|
||||
|
||||
}
|
||||
|
||||
.pia-top-left-logo
|
||||
.top-left-logo
|
||||
{
|
||||
height:50px;
|
||||
height:35px;
|
||||
width:35px;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
|
||||
@@ -744,7 +744,7 @@ table.dataTable tbody tr.selected, table.dataTable tbody tr .selected
|
||||
top: 0.01em;
|
||||
font-size: 3.25em;
|
||||
}
|
||||
.pa_semitransparent-panel{
|
||||
.nax_semitransparent-panel{
|
||||
background-color: #000 !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
--color-yellow: #f39c12;
|
||||
--color-red: #dd4b39;
|
||||
--color-gray: #8c8c8c;
|
||||
--color-white: #fff;
|
||||
}
|
||||
|
||||
:root {
|
||||
@@ -746,7 +747,7 @@
|
||||
top: 0.01em;
|
||||
font-size: 3.25em;
|
||||
}
|
||||
.pa_semitransparent-panel{
|
||||
.nax_semitransparent-panel{
|
||||
background-color: #000 !important;
|
||||
}
|
||||
|
||||
@@ -793,5 +794,5 @@
|
||||
|
||||
.btn:hover
|
||||
{
|
||||
color: var(--color-gray);
|
||||
color: var(--color-white);
|
||||
}
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
<!-- Content header--------------------------------------------------------- -->
|
||||
<section class="content-header">
|
||||
<?php require 'php/templates/notification.php'; ?>
|
||||
<?php require 'php/templates/modals.php'; ?>
|
||||
|
||||
<h1 id="pageTitle">
|
||||
 <small>Quering device info...</small>
|
||||
@@ -123,7 +123,7 @@
|
||||
</div>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content" style="min-height: 430px;">
|
||||
<div class="tab-content spinnerTarget" style="min-height: 430px;">
|
||||
|
||||
<!-- tab page 1 ------------------------------------------------------------ -->
|
||||
|
||||
@@ -497,7 +497,7 @@ function updateDevicePageName(mac) {
|
||||
let owner = getDevDataByMac(mac, "devOwner");
|
||||
|
||||
// If data is missing, re-cache and retry once
|
||||
if (name === "Unknown" || owner === "Unknown") {
|
||||
if (mac != 'new' && (name === "Unknown" || owner === "Unknown")) {
|
||||
console.warn("Device not found in cache, retrying after re-cache:", mac);
|
||||
showSpinner();
|
||||
cacheDevices().then(() => {
|
||||
@@ -541,7 +541,7 @@ function updateDevicePageName(mac) {
|
||||
|
||||
window.onload = function async()
|
||||
{
|
||||
initializeTabs();
|
||||
// initializeTabs();
|
||||
updateChevrons(mac);
|
||||
updateDevicePageName(mac);
|
||||
|
||||
|
||||
@@ -290,18 +290,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------
|
||||
// Show the description of a setting
|
||||
function showDescriptionPopup(e) {
|
||||
|
||||
console.log($(e).attr("my-set-key"));
|
||||
|
||||
showModalOK("Info", getString($(e).attr("my-set-key") + '_description'))
|
||||
}
|
||||
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Save device data to DB
|
||||
function setDeviceData(direction = '', refreshCallback = '') {
|
||||
|
||||
@@ -443,6 +443,37 @@
|
||||
}
|
||||
|
||||
// init first time
|
||||
initNmapButtons();
|
||||
initCopyFromDevice();
|
||||
// -----------------------------------------------------------
|
||||
var toolsPageInitialized = false;
|
||||
|
||||
function initDeviceToolsPage()
|
||||
{
|
||||
// Only proceed if .panTools is visible
|
||||
if (!$('#panTools:visible').length) {
|
||||
return; // exit early if nothing is visible
|
||||
}
|
||||
|
||||
// init page once
|
||||
if (toolsPageInitialized) return;
|
||||
toolsPageInitialized = true;
|
||||
|
||||
initNmapButtons();
|
||||
initCopyFromDevice();
|
||||
|
||||
hideSpinner();
|
||||
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Recurring function to monitor the URL and reinitialize if needed
|
||||
function deviceToolsPageUpdater() {
|
||||
initDeviceToolsPage();
|
||||
|
||||
// Run updater again after delay
|
||||
setTimeout(deviceToolsPageUpdater, 200);
|
||||
}
|
||||
|
||||
// start updater
|
||||
deviceToolsPageUpdater();
|
||||
|
||||
</script>
|
||||
|
||||
@@ -503,36 +503,36 @@ function collectFilters() {
|
||||
function mapColumnIndexToFieldName(index, tableColumnVisible) {
|
||||
// the order is important, don't change it!
|
||||
const columnNames = [
|
||||
"devName",
|
||||
"devOwner",
|
||||
"devType",
|
||||
"devIcon",
|
||||
"devFavorite",
|
||||
"devGroup",
|
||||
"devFirstConnection",
|
||||
"devLastConnection",
|
||||
"devLastIP",
|
||||
"devIsRandomMac", // resolved on the fly
|
||||
"devStatus", // resolved on the fly
|
||||
"devMac",
|
||||
"devIpLong", //formatIPlong(device.devLastIP) || "", // IP orderable
|
||||
"rowid",
|
||||
"devParentMAC",
|
||||
"devParentChildrenCount", // resolved on the fly
|
||||
"devLocation",
|
||||
"devVendor",
|
||||
"devParentPort",
|
||||
"devGUID",
|
||||
"devSyncHubNode",
|
||||
"devSite",
|
||||
"devSSID",
|
||||
"devSourcePlugin",
|
||||
"devPresentLastScan",
|
||||
"devAlertDown",
|
||||
"devCustomProps",
|
||||
"devFQDN",
|
||||
"devParentRelType",
|
||||
"devReqNicsOnline"
|
||||
"devName", // 0
|
||||
"devOwner", // 1
|
||||
"devType", // 2
|
||||
"devIcon", // 3
|
||||
"devFavorite", // 4
|
||||
"devGroup", // 5
|
||||
"devFirstConnection", // 6
|
||||
"devLastConnection", // 7
|
||||
"devLastIP", // 8
|
||||
"devIsRandomMac", // 9 resolved on the fly
|
||||
"devStatus", // 10 resolved on the fly
|
||||
"devMac", // 11
|
||||
"devIpLong", // 12 formatIPlong(device.devLastIP) || "", // IP orderable
|
||||
"rowid", // 13
|
||||
"devParentMAC", // 14
|
||||
"devParentChildrenCount", // 15 resolved on the fly
|
||||
"devLocation", // 16
|
||||
"devVendor", // 17
|
||||
"devParentPort", // 18
|
||||
"devGUID", // 19
|
||||
"devSyncHubNode", // 20
|
||||
"devSite", // 21
|
||||
"devSSID", // 22
|
||||
"devSourcePlugin", // 23
|
||||
"devPresentLastScan", // 24
|
||||
"devAlertDown", // 25
|
||||
"devCustomProps", // 26
|
||||
"devFQDN", // 27
|
||||
"devParentRelType", // 28
|
||||
"devReqNicsOnline" // 29
|
||||
];
|
||||
|
||||
// console.log("OrderBy: " + columnNames[tableColumnOrder[index]]);
|
||||
@@ -899,6 +899,28 @@ function initializeDatatable (status) {
|
||||
}
|
||||
} },
|
||||
|
||||
// Parent Mac
|
||||
{targets: [mapIndx(14)],
|
||||
'createdCell': function (td, cellData, rowData, row, col) {
|
||||
if (!isValidMac(cellData)) {
|
||||
$(td).html('');
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
id: cellData, // MAC address
|
||||
text: cellData // Optional display text (you could use a name or something else)
|
||||
};
|
||||
|
||||
spanWrap = $(`<span class="custom-badge text-white"></span>`)
|
||||
|
||||
$(td).html(spanWrap);
|
||||
|
||||
const chipHtml = renderDeviceLink(data, spanWrap, true); // pass the td as container
|
||||
|
||||
$(spanWrap).append(chipHtml);
|
||||
}
|
||||
},
|
||||
// Status color
|
||||
{targets: [mapIndx(10)],
|
||||
'createdCell': function (td, cellData, rowData, row, col) {
|
||||
@@ -982,9 +1004,7 @@ function initializeDatatable (status) {
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"display": "standalone",
|
||||
"icons": [
|
||||
{
|
||||
"src": "",
|
||||
"src": "/img/NetAlertX_logo.png",
|
||||
"sizes": "180x180",
|
||||
"type": "image/png"
|
||||
}
|
||||
|
||||
@@ -1032,6 +1032,7 @@ function getDevDataByMac(macAddress, dbColumn) {
|
||||
}
|
||||
}
|
||||
|
||||
console.error("⚠ Device with MAC not found:" + macAddress)
|
||||
return "Unknown"; // Return a default value if MAC address is not found
|
||||
}
|
||||
|
||||
@@ -1112,38 +1113,92 @@ let animationTime = 300
|
||||
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");
|
||||
const target = $(".spinnerTarget").first(); // Only use the first one if multiple exist
|
||||
|
||||
spinner.fadeIn(animationTime);
|
||||
$("#loadingSpinnerText").text(text);
|
||||
|
||||
if (target.length) {
|
||||
// Position relative to target
|
||||
const offset = target.offset();
|
||||
const width = target.outerWidth();
|
||||
const height = target.outerHeight();
|
||||
|
||||
spinner.css({
|
||||
position: "absolute",
|
||||
top: offset.top,
|
||||
left: offset.left,
|
||||
width: width,
|
||||
height: height,
|
||||
zIndex: 800
|
||||
});
|
||||
} else {
|
||||
$("#loadingSpinnerText").text(text);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
spinner.addClass("visible");
|
||||
spinner.fadeIn(animationTime);
|
||||
// Fullscreen fallback
|
||||
spinner.css({
|
||||
position: "fixed",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
zIndex: 800
|
||||
});
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
spinner.addClass("visible");
|
||||
spinner.fadeIn(animationTime);
|
||||
});
|
||||
}
|
||||
|
||||
function hideSpinner() {
|
||||
clearTimeout(spinnerTimeout);
|
||||
|
||||
const spinner = $("#loadingSpinner");
|
||||
|
||||
if (spinner.length) {
|
||||
spinner.removeClass("visible");
|
||||
spinner.fadeOut(animationTime);
|
||||
if (!spinner.length) return;
|
||||
|
||||
spinnerTimeout = setTimeout(() => {
|
||||
spinner.removeClass("visible");
|
||||
spinner.fadeOut(animationTime); // optional remove or hide again
|
||||
}, 300);
|
||||
const target = $(".spinnerTarget").first();
|
||||
|
||||
if (target.length) {
|
||||
// Lock position to target
|
||||
const offset = target.offset();
|
||||
const width = target.outerWidth();
|
||||
const height = target.outerHeight();
|
||||
|
||||
spinner.css({
|
||||
position: "absolute",
|
||||
top: offset.top,
|
||||
left: offset.left,
|
||||
width: width,
|
||||
height: height,
|
||||
zIndex: 800
|
||||
});
|
||||
} else {
|
||||
// Fullscreen fallback
|
||||
spinner.css({
|
||||
position: "fixed",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
zIndex: 800
|
||||
});
|
||||
}
|
||||
|
||||
// Trigger fade-out and only remove styles AFTER fade completes AND display is none
|
||||
spinner.removeClass("visible").fadeOut(animationTime, () => {
|
||||
// Ensure it's really hidden before resetting styles
|
||||
spinner.css({
|
||||
display: "none"
|
||||
});
|
||||
|
||||
spinner.css({
|
||||
position: "",
|
||||
top: "",
|
||||
left: "",
|
||||
width: "",
|
||||
height: "",
|
||||
zIndex: ""
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -161,6 +161,91 @@ function showModalFieldInput(
|
||||
$(`#${prefix}`).modal("show");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
function showModalPopupForm(
|
||||
title,
|
||||
message,
|
||||
btnCancel = getString("Gen_Cancel"),
|
||||
btnOK = getString("Gen_Okay"),
|
||||
curValue = "",
|
||||
callbackFunction = null,
|
||||
triggeredBy = null,
|
||||
popupFormJson = null,
|
||||
parentSettingKey = null
|
||||
) {
|
||||
// set captions
|
||||
prefix = "modal-form";
|
||||
console.log(popupFormJson);
|
||||
|
||||
$(`#${prefix}-title`).html(title);
|
||||
$(`#${prefix}-message`).html(message);
|
||||
$(`#${prefix}-cancel`).html(btnCancel);
|
||||
$(`#${prefix}-OK`).html(btnOK);
|
||||
|
||||
if (callbackFunction != null) {
|
||||
modalCallbackFunction = callbackFunction;
|
||||
}
|
||||
|
||||
if (triggeredBy != null) {
|
||||
$('#'+prefix).attr("data-myparam-triggered-by", triggeredBy)
|
||||
}
|
||||
|
||||
outputHtml = "";
|
||||
|
||||
if (Array.isArray(popupFormJson)) {
|
||||
popupFormJson.forEach((field, index) => {
|
||||
// You'll need to define these or map them from `field`
|
||||
const setName = field.name?.find(n => n.language_code === "en_us")?.string || setKey;
|
||||
const labelClasses = "col-sm-2"; // example, or from your obj.labelClasses
|
||||
const inputClasses = "col-sm-10"; // example, or from your obj.inputClasses
|
||||
const fieldData = field.default_value ?? "";
|
||||
const fieldOptionsOverride = field.type?.elements[0]?.elementOptions || [];
|
||||
|
||||
const setKey = field.function || `field_${index}`;
|
||||
const setValue = field.default_value ?? "";
|
||||
const setType = JSON.stringify(field.type);
|
||||
const setEvents = field.events || []; // default to empty array if missing
|
||||
|
||||
const setObj = { setKey, setValue, setType, setEvents };
|
||||
|
||||
// Generate the input field HTML
|
||||
const inputFormHtml = `
|
||||
<div class="form-group col-xs-12">
|
||||
<label id="${setKey}_label" class="${labelClasses}"> ${setName}
|
||||
<i my-set-key="${parentSettingKey}_popupform_${setKey}"
|
||||
title="${getString("Settings_Show_Description")}"
|
||||
class="fa fa-circle-info pointer helpIconSmallTopRight"
|
||||
onclick="showDescriptionPopup(this)">
|
||||
</i>
|
||||
</label>
|
||||
<div class="${inputClasses}">
|
||||
${generateFormHtml(
|
||||
null, // settingsData only required for datatables
|
||||
setObj,
|
||||
fieldData.toString(),
|
||||
fieldOptionsOverride,
|
||||
null
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Append to result
|
||||
outputHtml += inputFormHtml;
|
||||
});
|
||||
}
|
||||
|
||||
$(`#modal-form-plc`).html(outputHtml);
|
||||
|
||||
// $(`#${prefix}-field`).val(curValue);
|
||||
// setTimeout(function () {
|
||||
// $(`#${prefix}-field`).focus();
|
||||
// }, 500);
|
||||
|
||||
// Show modal
|
||||
$(`#${prefix}`).modal("show");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
function modalDefaultOK() {
|
||||
// Hide modal
|
||||
@@ -224,7 +309,7 @@ function modalWarningOK() {
|
||||
} else if (typeof modalCallbackFunction === "string" && typeof window[modalCallbackFunction] === "function") {
|
||||
window[modalCallbackFunction](); // Call via window
|
||||
} else {
|
||||
console.error("Invalid callback function");
|
||||
console.error("Invalid callback function: " + modalCallbackFunction);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
@@ -67,6 +67,15 @@ function getPluginConfig(pluginsData, prefix) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// ----------------------------------------
|
||||
// Show the description of a setting
|
||||
function showDescriptionPopup(e) {
|
||||
|
||||
console.log($(e).attr("my-set-key"));
|
||||
|
||||
showModalOK("Info", getString($(e).attr("my-set-key") + '_description'))
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Generate plugin HTML card based on prefixes in an array
|
||||
function pluginCards(prefixesOfEnabledPlugins, includeSettings) {
|
||||
@@ -299,6 +308,45 @@ function removeDataTableRow(el) {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Add item via pop up form dialog
|
||||
function addViaPopupForm(element) {
|
||||
console.log(element)
|
||||
|
||||
const fromId = $(element).attr("my-input-from");
|
||||
const toId = $(element).attr("my-input-to");
|
||||
const curValue = $(`#${fromId}`).val();
|
||||
const triggeredBy = $(element).attr("id");
|
||||
const parsed = JSON.parse(atob($(element).data("elementoptionsbase64")));
|
||||
const popupFormJson = parsed.find(obj => "popupForm" in obj)?.popupForm ?? null;
|
||||
|
||||
console.log(`fromId | toId | triggeredBy | curValue: ${fromId} | ${toId} | ${triggeredBy} | ${curValue}`);
|
||||
|
||||
showModalPopupForm(
|
||||
`<i class="fa fa-pen-to-square"></i> ${getString(
|
||||
"Gen_Update_Value"
|
||||
)}`, // title
|
||||
getString("settings_update_item_warning"), // message
|
||||
getString("Gen_Cancel"), // btnCancel
|
||||
getString("Gen_Add"), // btnOK
|
||||
curValue, // curValue
|
||||
null, // callbackFunction
|
||||
triggeredBy, // triggeredBy
|
||||
popupFormJson, // popupform
|
||||
toId // parentSettingKey
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Add item to list via popup form
|
||||
function addViaPopupFormToList(element, clearInput = true) {
|
||||
|
||||
|
||||
// flag something changes to prevent navigating from page
|
||||
settingsChanged();
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Add item to list
|
||||
function addList(element, clearInput = true) {
|
||||
@@ -622,8 +670,6 @@ function generateOptionsOrSetOptions(
|
||||
// obj.push({ id: item, name: item })
|
||||
options = arrayToObject(createArray(overrideOptions ? overrideOptions : getSettingOptions(setKey)))
|
||||
|
||||
|
||||
|
||||
// Call to render lists
|
||||
renderList(
|
||||
options,
|
||||
@@ -633,8 +679,6 @@ function generateOptionsOrSetOptions(
|
||||
targetField,
|
||||
transformers
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -655,6 +699,13 @@ function applyTransformers(val, transformers) {
|
||||
val = btoa(val);
|
||||
}
|
||||
break;
|
||||
case "name|base64":
|
||||
// // Implement base64 logic
|
||||
// if (!isBase64(val)) {
|
||||
// val = btoa(val);
|
||||
// }
|
||||
val = val; // probably TODO ⚠
|
||||
break;
|
||||
case "getString":
|
||||
// no change
|
||||
val = val;
|
||||
@@ -681,6 +732,13 @@ function reverseTransformers(val, transformers) {
|
||||
val = atob(val);
|
||||
}
|
||||
break;
|
||||
case "name|base64":
|
||||
// // Implement base64 decoding logic
|
||||
// if (isBase64(val)) {
|
||||
// val = atob(val);
|
||||
// }
|
||||
val = val; // probably TODO ⚠
|
||||
break;
|
||||
case "getString":
|
||||
// retrieve string
|
||||
val = getString(val);
|
||||
@@ -720,8 +778,8 @@ const handleElementOptions = (setKey, elementOptions, transformers, val) => {
|
||||
let customParams = "";
|
||||
let customId = "";
|
||||
let columns = [];
|
||||
let base64Regex = "";
|
||||
|
||||
let base64Regex = "";
|
||||
let elementOptionsBase64 = btoa(JSON.stringify(elementOptions));
|
||||
|
||||
elementOptions.forEach((option) => {
|
||||
if (option.prefillValue) {
|
||||
@@ -804,7 +862,8 @@ const handleElementOptions = (setKey, elementOptions, transformers, val) => {
|
||||
customParams,
|
||||
customId,
|
||||
columns,
|
||||
base64Regex
|
||||
base64Regex,
|
||||
elementOptionsBase64
|
||||
};
|
||||
};
|
||||
|
||||
@@ -955,6 +1014,8 @@ function generateFormHtml(settingsData, set, overrideValue, overrideOptions, ori
|
||||
// }
|
||||
|
||||
// Parse the setType JSON string
|
||||
console.log(processQuotes(setType));
|
||||
|
||||
const setTypeObject = JSON.parse(processQuotes(setType))
|
||||
const dataType = setTypeObject.dataType;
|
||||
const elements = setTypeObject.elements || [];
|
||||
@@ -982,7 +1043,8 @@ function generateFormHtml(settingsData, set, overrideValue, overrideOptions, ori
|
||||
customParams,
|
||||
customId,
|
||||
columns,
|
||||
base64Regex
|
||||
base64Regex,
|
||||
elementOptionsBase64
|
||||
} = handleElementOptions(setKey, elementOptions, transformers, inVal);
|
||||
|
||||
// Override value
|
||||
@@ -1051,6 +1113,7 @@ function generateFormHtml(settingsData, set, overrideValue, overrideOptions, ori
|
||||
my-originalSetKey="${originalSetKey}"
|
||||
my-input-from="${sourceIds}"
|
||||
my-input-to="${setKey}"
|
||||
data-elementoptionsbase64="${elementOptionsBase64}"
|
||||
onclick="${onClick}">
|
||||
${getString(getStringKey)}
|
||||
</button>`;
|
||||
|
||||
@@ -336,7 +336,8 @@ function execute_settingEvent(element) {
|
||||
getString('DevDetail_button_OverwriteIcons_Warning'),
|
||||
getString('Gen_Cancel'),
|
||||
getString('Gen_Okay'),
|
||||
'overwriteIconType'
|
||||
'overwriteIconType',
|
||||
feSourceId // triggered by id
|
||||
);
|
||||
} else if (["go_to_device"].includes(feEvent)) {
|
||||
|
||||
@@ -347,9 +348,43 @@ function execute_settingEvent(element) {
|
||||
|
||||
} else {
|
||||
console.warn(`🔺Not implemented: ${feEvent}`)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Go to the correct network node in the Network section
|
||||
function overwriteIconType()
|
||||
{
|
||||
const mac = getMac();
|
||||
|
||||
if (!isValidMac(mac)) {
|
||||
showModalOK("Error", getString("Gen_InvalidMac"))
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Construct SQL query
|
||||
const rawSql = `
|
||||
UPDATE Devices
|
||||
SET devIcon = (
|
||||
SELECT devIcon FROM Devices WHERE devMac = "${mac}"
|
||||
)
|
||||
WHERE devType IN (
|
||||
SELECT devType FROM Devices WHERE devMac = "${mac}"
|
||||
)
|
||||
`;
|
||||
|
||||
const apiUrl = `php/server/dbHelper.php?action=write&rawSql=${btoa(encodeURIComponent(rawSql))}`;
|
||||
|
||||
$.get(apiUrl, function(response) {
|
||||
if (response === 'OK') {
|
||||
showMessage (response);
|
||||
updateApi("devices")
|
||||
} else {
|
||||
showMessage (response, 3000, "modal_red");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -680,45 +715,7 @@ function initSelect2() {
|
||||
{
|
||||
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;
|
||||
return $(renderDeviceLink(data, container));
|
||||
},
|
||||
escapeMarkup: function (m) {
|
||||
return m; // Allow HTML
|
||||
@@ -782,6 +779,50 @@ function initSelect2() {
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
// Render a device link with hover-over functionality
|
||||
function renderDeviceLink(data, container, useName = false) {
|
||||
if (!data.id || !isValidMac(data.id)) return data.text; // default placeholder etc.
|
||||
|
||||
const device = getDevDataByMac(data.id);
|
||||
|
||||
const badge = getStatusBadgeParts(
|
||||
device.devPresentLastScan,
|
||||
device.devAlertDown,
|
||||
device.devMac
|
||||
);
|
||||
|
||||
// badge class and hover-info class to container
|
||||
$(container)
|
||||
.addClass(`${badge.cssClass} hover-node-info`)
|
||||
.attr({
|
||||
'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
|
||||
});
|
||||
|
||||
return `
|
||||
<a href="${badge.url}" target="_blank">
|
||||
<span class="custom-chip">
|
||||
<span class="iconPreview">${atob(device.devIcon)}</span>
|
||||
${useName ? device.devName : data.text}
|
||||
<span>
|
||||
(${badge.iconHtml})
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
// Display device info on hover (attach only once)
|
||||
function initHoverNodeInfo() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
require 'php/templates/header.php';
|
||||
require 'php/templates/notification.php';
|
||||
require 'php/templates/modals.php';
|
||||
?>
|
||||
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
@@ -8,7 +8,10 @@
|
||||
|
||||
<!-- Main content ---------------------------------------------------------- -->
|
||||
<section class="content">
|
||||
|
||||
|
||||
<script>
|
||||
showSpinner();
|
||||
</script>
|
||||
|
||||
<?php
|
||||
|
||||
@@ -143,7 +146,7 @@ $db->close();
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-content spinnerTarget">
|
||||
<div class="tab-pane active" id="tab_DBTools">
|
||||
<div class="db_info_table">
|
||||
<div class="db_info_table_row">
|
||||
@@ -182,6 +185,12 @@ $db->close();
|
||||
</div>
|
||||
<div class="db_tools_table_cell_b"><?= lang('Maintenance_Tool_del_ActHistory_text');?></div>
|
||||
</div>
|
||||
<div class="db_info_table_row">
|
||||
<div class="db_tools_table_cell_a" >
|
||||
<button type="button" class="btn btn-default pa-btn pa-btn-delete bg-red dbtools-button" id="btnRestartServer" onclick="askRestartBackend()"><?= lang('Maint_RestartServer');?></button>
|
||||
</div>
|
||||
<div class="db_tools_table_cell_b"><?= lang('Maint_Restart_Server_noti_text');?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -709,6 +718,8 @@ window.onload = function asyncFooter() {
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<script>
|
||||
hideSpinner();
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
@@ -136,7 +136,8 @@
|
||||
customParams,
|
||||
customId,
|
||||
columns,
|
||||
base64Regex
|
||||
base64Regex,
|
||||
elementOptionsBase64
|
||||
} = handleElementOptions('none', elementOptions, transformers, val = "");
|
||||
|
||||
// render based on element type
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
require 'php/templates/header.php';
|
||||
require 'php/templates/notification.php';
|
||||
require 'php/templates/modals.php';
|
||||
?>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -76,6 +76,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
|
||||
switch ($action) {
|
||||
case 'create': create($defaultValue, $expireMinutes, $dbtable, $columns, $values ); break;
|
||||
case 'read' : read($rawSql); break;
|
||||
case 'write' : write($rawSql); break;
|
||||
case 'update': update($columnName, $id, $defaultValue, $expireMinutes, $dbtable, $columns, $values); break;
|
||||
case 'delete': delete($columnName, $id, $dbtable); break;
|
||||
case 'lockDatabase': lockDatabase($delay); break;
|
||||
@@ -120,6 +121,31 @@ function read($rawSql) {
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// write
|
||||
//------------------------------------------------------------------------------
|
||||
function write($rawSql) {
|
||||
global $db;
|
||||
|
||||
// Construct the SQL query to select values
|
||||
$sql = $rawSql;
|
||||
|
||||
// Execute the SQL query
|
||||
$result = $db->query($sql);
|
||||
|
||||
// Check if the query executed successfully
|
||||
if (! $result == TRUE) {
|
||||
// Output an error message if the query failed
|
||||
echo "Error writing data\n\n " .$sql." \n\n". $db->lastErrorMsg();
|
||||
return;
|
||||
} else
|
||||
{
|
||||
// Output
|
||||
echo "OK";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// update
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
case 'getDevicesListCalendar': getDevicesListCalendar(); break; //todo: slowly deprecate this
|
||||
|
||||
case 'updateNetworkLeaf': updateNetworkLeaf(); break;
|
||||
case 'overwriteIconType': overwriteIconType(); break;
|
||||
case 'getIcons': getIcons(); break;
|
||||
case 'getActions': getActions(); break;
|
||||
case 'getDevices': getDevices(); break;
|
||||
@@ -924,33 +923,6 @@ function updateNetworkLeaf()
|
||||
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
function overwriteIconType()
|
||||
{
|
||||
$mac = $_REQUEST['mac'];
|
||||
$icon = $_REQUEST['icon'];
|
||||
|
||||
if ((false === filter_var($mac , FILTER_VALIDATE_MAC) && $mac != "Internet" && $mac != "") ) {
|
||||
throw new Exception('Invalid mac address');
|
||||
}
|
||||
else
|
||||
{
|
||||
global $db;
|
||||
// sql
|
||||
$sql = 'UPDATE Devices SET "devIcon" = "'. $icon .'" where devType in (select devType from Devices where devMac = "' . $mac.'")' ;
|
||||
// update Data
|
||||
$result = $db->query($sql);
|
||||
|
||||
// check result
|
||||
if ($result == TRUE) {
|
||||
echo 'OK';
|
||||
} else {
|
||||
echo lang('BackDevices_Device_UpdDevError');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Wake-on-LAN
|
||||
// Inspired by @leiweibau: https://github.com/leiweibau/Pi.Alert/commit/30427c7fea180670c71a2b790699e5d9e9e88ffd
|
||||
|
||||
@@ -139,8 +139,8 @@
|
||||
<body class="hold-transition fixed <?php echo $pia_skin_selected;?> theme-<?php echo $UI_THEME;?> sidebar-mini" onLoad="update_servertime();" >
|
||||
|
||||
<div id="loadingSpinner">
|
||||
<div class="pa_semitransparent-panel"></div>
|
||||
<div class="panel panel-default pa_spinner">
|
||||
<div class="nax_semitransparent-panel"></div>
|
||||
<div class="panel panel-default nax_spinner">
|
||||
<table>
|
||||
<td id="loadingSpinnerText" width="130px" ></td>
|
||||
<td><i class="fa-solid fa-spinner fa-spin-pulse"></i></td>
|
||||
@@ -160,7 +160,7 @@
|
||||
<a href="devices.php" class="logo">
|
||||
<!-- mini logo for sidebar mini 50x50 pixels -->
|
||||
<span class="logo-mini">
|
||||
<img src="img/NetAlertX_logo.png" class="pia-top-left-logo" alt="NetAlertX Logo"/>
|
||||
<img src="img/NetAlertX_logo.png" class="top-left-logo" alt="NetAlertX Logo"/>
|
||||
</span>
|
||||
<!-- logo for regular state and mobile devices -->
|
||||
<span class="logo-lg">Net<b>Alert</b><sup>x</sup>
|
||||
@@ -436,8 +436,24 @@
|
||||
</li>
|
||||
|
||||
<!-- system info menu item -->
|
||||
<li class=" <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('systeminfo.php') ) ){ echo 'active'; } ?>">
|
||||
<a href="systeminfo.php"><i class="fa fa-fw fa-info-circle"></i> <span><?= lang('Navigation_SystemInfo');?></span></a>
|
||||
<li class=" treeview <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('systeminfo.php') ) ){ echo 'active menu-open'; } ?>">
|
||||
<a href="#">
|
||||
<i class="fa fa-fw fa-info-circle"></i> <span><?= lang('Navigation_SystemInfo');?></span>
|
||||
<span class="pull-right-container">
|
||||
<i class="fa fa-angle-left pull-right"></i>
|
||||
</span>
|
||||
</a>
|
||||
<ul class="treeview-menu " style="display: <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('systeminfo.php') ) ){ echo 'block'; } else {echo 'none';} ?>;">
|
||||
<li>
|
||||
<a href="systeminfo.php#panServer" onclick="setCache('activeSysinfoTab','tabServer');initializeTabs()"><?= lang('Systeminfo_System');?></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="systeminfo.php#panNetwork" onclick="setCache('activeSysinfoTab','tabNetwork');initializeTabs()"><?= lang('Systeminfo_Network');?></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="systeminfo.php#panStorage" onclick="setCache('activeSysinfoTab','tabStorage');initializeTabs()"><?= lang('Systeminfo_Storage');?></a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
@@ -450,24 +466,6 @@
|
||||
|
||||
<script defer>
|
||||
|
||||
// Generate work-in-progress icons
|
||||
function workInProgress() {
|
||||
|
||||
if($(".work-in-progress").length > 0 && $(".work-in-progress").html().trim() == "")
|
||||
{
|
||||
$(".work-in-progress").append(`
|
||||
<a href="https://github.com/jokob-sk/NetAlertX/issues" target="_blank">
|
||||
<b class="pointer" title="${getString("Gen_Work_In_Progress")}">🦺</b>
|
||||
</a>
|
||||
`)
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
|
||||
|
||||
//--------------------------------------------------------------
|
||||
|
||||
function toggleFullscreen() {
|
||||
|
||||
if (document.fullscreenElement) {
|
||||
@@ -485,6 +483,5 @@ function workInProgress() {
|
||||
|
||||
// Update server state in the header
|
||||
updateState()
|
||||
workInProgress()
|
||||
|
||||
</script>
|
||||
|
||||
@@ -301,6 +301,7 @@
|
||||
"Gen_Cancel": "إلغاء",
|
||||
"Gen_Change": "تغيير",
|
||||
"Gen_Copy": "نسخ",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_DataUpdatedUITakesTime": "تم تحديث البيانات. قد يستغرق تحديث واجهة المستخدم بعض الوقت",
|
||||
"Gen_Delete": "حذف",
|
||||
"Gen_DeleteAll": "حذف الكل",
|
||||
@@ -308,6 +309,7 @@
|
||||
"Gen_Error": "خطأ",
|
||||
"Gen_Filter": "تصفية",
|
||||
"Gen_Generate": "إنشاء",
|
||||
"Gen_InvalidMac": "",
|
||||
"Gen_LockedDB": "قاعدة البيانات مقفلة",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_Offline": "غير متصل",
|
||||
|
||||
@@ -301,6 +301,7 @@
|
||||
"Gen_Cancel": "Cancel·lar",
|
||||
"Gen_Change": "Canviar",
|
||||
"Gen_Copy": "Executar",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_DataUpdatedUITakesTime": "D'acord - Pot passar una estona perquè la interfície d'usuari s'actualitzi si s'està executant una exploració.",
|
||||
"Gen_Delete": "Esborrar",
|
||||
"Gen_DeleteAll": "Esborrar tot",
|
||||
@@ -308,6 +309,7 @@
|
||||
"Gen_Error": "Error",
|
||||
"Gen_Filter": "Filtrar",
|
||||
"Gen_Generate": "Generar",
|
||||
"Gen_InvalidMac": "",
|
||||
"Gen_LockedDB": "ERROR - DB podria estar bloquejada - Fes servir F12 Eines desenvolupament -> Consola o provar-ho més tard.",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_Offline": "Fora de línia",
|
||||
|
||||
@@ -301,6 +301,7 @@
|
||||
"Gen_Cancel": "Zrušit",
|
||||
"Gen_Change": "Změnit",
|
||||
"Gen_Copy": "Spustit",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_DataUpdatedUITakesTime": "OK - může zabrat chvíli aktualizovat rozhraní, pokud běží scan.",
|
||||
"Gen_Delete": "Smazat",
|
||||
"Gen_DeleteAll": "Smazat vše",
|
||||
@@ -308,6 +309,7 @@
|
||||
"Gen_Error": "Chyba",
|
||||
"Gen_Filter": "Filtr",
|
||||
"Gen_Generate": "Vygenerovat",
|
||||
"Gen_InvalidMac": "",
|
||||
"Gen_LockedDB": "CHYBA - Databáze je možná zamčená - Zkontrolujte F12 -> Nástroje pro vývojáře -> Konzole. nebo to zkuste později.",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_Offline": "Offline",
|
||||
|
||||
@@ -305,6 +305,7 @@
|
||||
"Gen_Cancel": "Abbrechen",
|
||||
"Gen_Change": "Ändern",
|
||||
"Gen_Copy": "Ausführen",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_DataUpdatedUITakesTime": "OK – Es kann einen Moment dauern, bis die Benutzeroberfläche aktualisiert wird, während ein Scan ausgeführt wird.",
|
||||
"Gen_Delete": "Löschen",
|
||||
"Gen_DeleteAll": "Alles löschen",
|
||||
@@ -312,6 +313,7 @@
|
||||
"Gen_Error": "Fehler",
|
||||
"Gen_Filter": "Filter",
|
||||
"Gen_Generate": "Generieren",
|
||||
"Gen_InvalidMac": "Ungültige MAC-Adresse.",
|
||||
"Gen_LockedDB": "ERROR - DB eventuell gesperrt - Nutze die Konsole in den Entwickler Werkzeugen (F12) zur Überprüfung oder probiere es später erneut.",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_Offline": "Offline",
|
||||
@@ -831,4 +833,4 @@
|
||||
"settings_system_label": "System",
|
||||
"settings_update_item_warning": "",
|
||||
"test_event_tooltip": "Speichere die Änderungen, bevor Sie die Einstellungen testen."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,6 +301,7 @@
|
||||
"Gen_Cancel": "Cancel",
|
||||
"Gen_Change": "Change",
|
||||
"Gen_Copy": "Run",
|
||||
"Gen_CopyToClipboard": "Copy to clipboard",
|
||||
"Gen_DataUpdatedUITakesTime": "OK - It may take a while for the UI to update if a scan is running.",
|
||||
"Gen_Delete": "Delete",
|
||||
"Gen_DeleteAll": "Delete all",
|
||||
@@ -308,6 +309,7 @@
|
||||
"Gen_Error": "Error",
|
||||
"Gen_Filter": "Filter",
|
||||
"Gen_Generate": "Generate",
|
||||
"Gen_InvalidMac": "Invalid Mac address.",
|
||||
"Gen_LockedDB": "ERROR - DB might be locked - Check F12 Dev tools -> Console or try later.",
|
||||
"Gen_NetworkMask": "Network mask",
|
||||
"Gen_Offline": "Offline",
|
||||
|
||||
@@ -303,6 +303,7 @@
|
||||
"Gen_Cancel": "Cancelar",
|
||||
"Gen_Change": "Cambiar",
|
||||
"Gen_Copy": "Ejecutar",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_DataUpdatedUITakesTime": "Correcto - La interfaz puede tardar en actualizarse si se está ejecutando un escaneo.",
|
||||
"Gen_Delete": "Eliminar",
|
||||
"Gen_DeleteAll": "Eliminar todo",
|
||||
@@ -310,6 +311,7 @@
|
||||
"Gen_Error": "Error",
|
||||
"Gen_Filter": "Filtro",
|
||||
"Gen_Generate": "Generar",
|
||||
"Gen_InvalidMac": "",
|
||||
"Gen_LockedDB": "Fallo - La base de datos puede estar bloqueada - Pulsa F1 -> Ajustes de desarrolladores -> Consola o prueba más tarde.",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_Offline": "Desconectado",
|
||||
|
||||
@@ -301,6 +301,7 @@
|
||||
"Gen_Cancel": "Annuler",
|
||||
"Gen_Change": "Changement",
|
||||
"Gen_Copy": "Lancer",
|
||||
"Gen_CopyToClipboard": "Copier vers le presse-papier",
|
||||
"Gen_DataUpdatedUITakesTime": "OK - cela peut prendre du temps à l'interface pour se mettre à jour si un scan est en cours.",
|
||||
"Gen_Delete": "Supprimer",
|
||||
"Gen_DeleteAll": "Supprimer tous",
|
||||
@@ -308,8 +309,9 @@
|
||||
"Gen_Error": "Erreur",
|
||||
"Gen_Filter": "Filtrer",
|
||||
"Gen_Generate": "Générer",
|
||||
"Gen_InvalidMac": "Adresse MAC invalide.",
|
||||
"Gen_LockedDB": "Erreur - La base de données est peut-être verrouillée - Vérifier avec les outils de dév via F12 -> Console ou essayer plus tard.",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_NetworkMask": "Masque réseau",
|
||||
"Gen_Offline": "Hors ligne",
|
||||
"Gen_Okay": "OK",
|
||||
"Gen_Online": "En ligne",
|
||||
@@ -327,7 +329,7 @@
|
||||
"Gen_SelectIcon": "<i class=\"fa-solid fa-chevron-down fa-fade\"></i>",
|
||||
"Gen_SelectToPreview": "Sélectionnez pour prévisualiser",
|
||||
"Gen_Selected_Devices": "Appareils sélectionnés :",
|
||||
"Gen_Subnet": "",
|
||||
"Gen_Subnet": "Sous-réseau",
|
||||
"Gen_Switch": "Basculer",
|
||||
"Gen_Upd": "Mise à jour réussie",
|
||||
"Gen_Upd_Fail": "Échec de la mise à jour",
|
||||
@@ -596,7 +598,7 @@
|
||||
"Settings_device_Scanners_desync": "⚠ La planification des différents scanners d'appareils est désynchronisée.",
|
||||
"Settings_device_Scanners_desync_popup": "La planification des scanners (<code>*_RUN_SCHD</code>) n'est pas identique entre scanners. Cela va entraîner des notifications en ligne/hors-ligne non cohérentes. À moins que cela soit attendu, utilisez la même planification pour tous les <b>🔍scanners d'appareils</b> activés.",
|
||||
"Speedtest_Results": "Résultats du test de débit",
|
||||
"Systeminfo_AvailableIps": "",
|
||||
"Systeminfo_AvailableIps": "Adresses IP disponibles",
|
||||
"Systeminfo_CPU": "Processeur",
|
||||
"Systeminfo_CPU_Cores": "Cœurs de processeur :",
|
||||
"Systeminfo_CPU_Name": "Nom du processeur :",
|
||||
@@ -758,4 +760,4 @@
|
||||
"settings_system_label": "Système",
|
||||
"settings_update_item_warning": "Mettre à jour la valeur ci-dessous. Veillez à bien suivre le même format qu'auparavant. <b>Il n'y a pas de pas de contrôle.</b>",
|
||||
"test_event_tooltip": "Enregistrer d'abord vos modifications avant de tester vôtre paramétrage."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,6 +301,7 @@
|
||||
"Gen_Cancel": "Annulla",
|
||||
"Gen_Change": "Modifica",
|
||||
"Gen_Copy": "Esegui",
|
||||
"Gen_CopyToClipboard": "Copia negli appunti",
|
||||
"Gen_DataUpdatedUITakesTime": "OK: l'aggiornamento dell'interfaccia utente potrebbe richiedere del tempo se è in esecuzione una scansione.",
|
||||
"Gen_Delete": "Elimina",
|
||||
"Gen_DeleteAll": "Elimina tutti",
|
||||
@@ -308,8 +309,9 @@
|
||||
"Gen_Error": "Errore",
|
||||
"Gen_Filter": "Filtro",
|
||||
"Gen_Generate": "Genera",
|
||||
"Gen_InvalidMac": "Indirizzo Mac non valido.",
|
||||
"Gen_LockedDB": "ERRORE: il DB potrebbe essere bloccato, controlla F12 Strumenti di sviluppo -> Console o riprova più tardi.",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_NetworkMask": "Maschera di rete",
|
||||
"Gen_Offline": "Offline",
|
||||
"Gen_Okay": "Ok",
|
||||
"Gen_Online": "Online",
|
||||
@@ -327,7 +329,7 @@
|
||||
"Gen_SelectIcon": "<i class=\"fa-solid fa-chevron-down fa-fade\"></i>",
|
||||
"Gen_SelectToPreview": "Seleziona per anteprima",
|
||||
"Gen_Selected_Devices": "Dispositivi selezionati:",
|
||||
"Gen_Subnet": "",
|
||||
"Gen_Subnet": "Sottorete",
|
||||
"Gen_Switch": "Cambia",
|
||||
"Gen_Upd": "Aggiornato correttamente",
|
||||
"Gen_Upd_Fail": "Aggiornamento fallito",
|
||||
@@ -596,7 +598,7 @@
|
||||
"Settings_device_Scanners_desync": "⚠ Le pianificazioni dello scanner del dispositivo non sono sincronizzate.",
|
||||
"Settings_device_Scanners_desync_popup": "Gli orari degli scanner dei dispositivi (<code>*_RUN_SCHD</code>) non sono gli stessi. Questo comporterà notifiche online/offline incoerenti del dispositivo. A meno che ciò non sia previsto, utilizza la stessa pianificazione per tutti gli <b>🔍 scanner dispositivi</b> abilitati.",
|
||||
"Speedtest_Results": "Risultati test di velocità",
|
||||
"Systeminfo_AvailableIps": "",
|
||||
"Systeminfo_AvailableIps": "IP disponibili",
|
||||
"Systeminfo_CPU": "CPU",
|
||||
"Systeminfo_CPU_Cores": "Core CPU:",
|
||||
"Systeminfo_CPU_Name": "Nome CPU:",
|
||||
@@ -758,4 +760,4 @@
|
||||
"settings_system_label": "Sistema",
|
||||
"settings_update_item_warning": "Aggiorna il valore qui sotto. Fai attenzione a seguire il formato precedente. <b>La convalida non viene eseguita.</b>",
|
||||
"test_event_tooltip": "Salva le modifiche prima di provare le nuove impostazioni."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,6 +301,7 @@
|
||||
"Gen_Cancel": "Avbryt",
|
||||
"Gen_Change": "",
|
||||
"Gen_Copy": "Kjør",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_DataUpdatedUITakesTime": "OK - Det kan ta litt tid før brukergrensesnittet oppdateres hvis en skanning kjøres.",
|
||||
"Gen_Delete": "Slett",
|
||||
"Gen_DeleteAll": "Slett alle",
|
||||
@@ -308,6 +309,7 @@
|
||||
"Gen_Error": "Feil",
|
||||
"Gen_Filter": "Filter",
|
||||
"Gen_Generate": "",
|
||||
"Gen_InvalidMac": "",
|
||||
"Gen_LockedDB": "FEIL - DB kan være låst - Sjekk F12 Dev tools -> Konsoll eller prøv senere.",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_Offline": "Frakoblet",
|
||||
|
||||
@@ -301,6 +301,7 @@
|
||||
"Gen_Cancel": "Anuluj",
|
||||
"Gen_Change": "Zmiana",
|
||||
"Gen_Copy": "Wykonaj",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_DataUpdatedUITakesTime": "OK – Może to potrwać chwilę, zanim interfejs użytkownika się zaktualizuje, jeśli trwa skan.",
|
||||
"Gen_Delete": "Usuń",
|
||||
"Gen_DeleteAll": "Usuń wszystko",
|
||||
@@ -308,6 +309,7 @@
|
||||
"Gen_Error": "Błąd",
|
||||
"Gen_Filter": "Filtr",
|
||||
"Gen_Generate": "Wygeneruj",
|
||||
"Gen_InvalidMac": "",
|
||||
"Gen_LockedDB": "Błąd - Baza danych może być zablokowana - Sprawdź narzędzia deweloperskie F12 -> Konsola lub spróbuj później.",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_Offline": "Niedostępne",
|
||||
|
||||
@@ -301,6 +301,7 @@
|
||||
"Gen_Cancel": "Cancelar",
|
||||
"Gen_Change": "Alterar",
|
||||
"Gen_Copy": "Executar",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_DataUpdatedUITakesTime": "OK - Pode levar um tempo para a interface do usuário ser atualizada se uma verificação estiver em execução.",
|
||||
"Gen_Delete": "Excluir",
|
||||
"Gen_DeleteAll": "Excluir todos",
|
||||
@@ -308,6 +309,7 @@
|
||||
"Gen_Error": "Erro",
|
||||
"Gen_Filter": "Filtro",
|
||||
"Gen_Generate": "Gerar",
|
||||
"Gen_InvalidMac": "",
|
||||
"Gen_LockedDB": "ERRO - O banco de dados pode estar bloqueado - Verifique F12 Ferramentas de desenvolvimento -> Console ou tente mais tarde.",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_Offline": "Offline",
|
||||
|
||||
@@ -301,6 +301,7 @@
|
||||
"Gen_Cancel": "Отмена",
|
||||
"Gen_Change": "Изменить",
|
||||
"Gen_Copy": "Запустить",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_DataUpdatedUITakesTime": "ОК - Обновление UI может занять некоторое время, если сканирование выполняется.",
|
||||
"Gen_Delete": "Удалить",
|
||||
"Gen_DeleteAll": "Удалить все",
|
||||
@@ -308,6 +309,7 @@
|
||||
"Gen_Error": "Ошибка",
|
||||
"Gen_Filter": "Фильтр",
|
||||
"Gen_Generate": "Генерировать",
|
||||
"Gen_InvalidMac": "",
|
||||
"Gen_LockedDB": "ОШИБКА - Возможно, база данных заблокирована. Проверьте инструменты разработчика F12 -> Консоль или повторите попытку позже.",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_Offline": "Оффлайн",
|
||||
|
||||
@@ -301,6 +301,7 @@
|
||||
"Gen_Cancel": "İptal",
|
||||
"Gen_Change": "Değiştir",
|
||||
"Gen_Copy": "Çalıştır",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_DataUpdatedUITakesTime": "TAMAM - Eğer bir tarama çalışıyorsa arayüzün güncellenmesi biraz zaman alabilir.",
|
||||
"Gen_Delete": "Sil",
|
||||
"Gen_DeleteAll": "Tümünü sil",
|
||||
@@ -308,6 +309,7 @@
|
||||
"Gen_Error": "Hata",
|
||||
"Gen_Filter": "Filtre",
|
||||
"Gen_Generate": "Oluştur",
|
||||
"Gen_InvalidMac": "",
|
||||
"Gen_LockedDB": "HATA - Veritabanı kilitlenmiş olabilir - F12 Geliştirici araçlarını -> Konsol kısmını kontrol edin veya daha sonra tekrar deneyin.",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_Offline": "Çevrimdışı",
|
||||
|
||||
6
front/php/templates/language/uk_ua.json
Normal file → Executable file
6
front/php/templates/language/uk_ua.json
Normal file → Executable file
@@ -301,6 +301,7 @@
|
||||
"Gen_Cancel": "Скасувати",
|
||||
"Gen_Change": "Зміна",
|
||||
"Gen_Copy": "Запустити",
|
||||
"Gen_CopyToClipboard": "Копіювати в буфер обміну",
|
||||
"Gen_DataUpdatedUITakesTime": "Добре. Оновлення інтерфейсу може зайняти деякий час, якщо сканування виконується.",
|
||||
"Gen_Delete": "Видалити",
|
||||
"Gen_DeleteAll": "Видалити все",
|
||||
@@ -308,8 +309,9 @@
|
||||
"Gen_Error": "Помилка",
|
||||
"Gen_Filter": "Фільтр",
|
||||
"Gen_Generate": "Генерувати",
|
||||
"Gen_InvalidMac": "Недійсна Mac-адреса.",
|
||||
"Gen_LockedDB": "ПОМИЛКА – БД може бути заблоковано – перевірте F12 Інструменти розробника -> Консоль або спробуйте пізніше.",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_NetworkMask": "Маска мережі",
|
||||
"Gen_Offline": "Офлайн",
|
||||
"Gen_Okay": "Гаразд",
|
||||
"Gen_Online": "Онлайн",
|
||||
@@ -629,7 +631,7 @@
|
||||
"Systeminfo_Network_HTTP_Referer": "HTTP реферер:",
|
||||
"Systeminfo_Network_HTTP_Referer_String": "Немає реферера HTTP",
|
||||
"Systeminfo_Network_Hardware": "Мережеве обладнання",
|
||||
"Systeminfo_Network_Hardware_Interface_Mask": "Маска мережі",
|
||||
"Systeminfo_Network_Hardware_Interface_Mask": "Маска Мережі",
|
||||
"Systeminfo_Network_Hardware_Interface_Name": "Назва інтерфейсу",
|
||||
"Systeminfo_Network_Hardware_Interface_RX": "Отримано",
|
||||
"Systeminfo_Network_Hardware_Interface_TX": "Передано",
|
||||
|
||||
@@ -301,6 +301,7 @@
|
||||
"Gen_Cancel": "取消",
|
||||
"Gen_Change": "修改",
|
||||
"Gen_Copy": "运行",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_DataUpdatedUITakesTime": "好的 - 如果扫描正在运行,UI 可能需要一段时间才能更新。",
|
||||
"Gen_Delete": "删除",
|
||||
"Gen_DeleteAll": "全部删除",
|
||||
@@ -308,6 +309,7 @@
|
||||
"Gen_Error": "错误",
|
||||
"Gen_Filter": "筛选",
|
||||
"Gen_Generate": "生成",
|
||||
"Gen_InvalidMac": "",
|
||||
"Gen_LockedDB": "错误 - DB 可能被锁定 - 检查 F12 开发工具 -> 控制台或稍后重试。",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_Offline": "离线",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
require 'php/templates/notification.php';
|
||||
require 'php/templates/modals.php';
|
||||
//------------------------------------------------------------------------------
|
||||
// check if authenticated
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
|
||||
|
||||
@@ -117,6 +117,29 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal form input -->
|
||||
<div class="modal fade" id="modal-form" data-myparam-triggered-by="" style="display: none;">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 id="modal-form-title" class="modal-title"> Modal Title </h4>
|
||||
</div>
|
||||
|
||||
<div id="modal-form-message" class="modal-body"> Modal message </div>
|
||||
|
||||
<div id="modal-form-plc"></div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button id="modal-form-cancel" type="button" class="btn btn-outline pull-left" style="min-width: 80px;" data-dismiss="modal"> Cancel </button>
|
||||
<button id="modal-form-OK" type="button" class="btn btn-outline btn-modal-submit" style="min-width: 80px;" onclick="modalDefaultForm()"> OK </button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Modal field input -->
|
||||
<div class="modal modal-warning fade" id="modal-field-input" data-myparam-triggered-by="" style="display: none;">
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
require 'php/templates/header.php';
|
||||
require 'php/templates/notification.php';
|
||||
require 'php/templates/modals.php';
|
||||
?>
|
||||
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
|
||||
26
front/plugins/unifi_api_import/README.md
Executable file
26
front/plugins/unifi_api_import/README.md
Executable file
@@ -0,0 +1,26 @@
|
||||
## Overview
|
||||
|
||||
Unifi import plugin using the Site Manager API.
|
||||
|
||||
> [!TIP]
|
||||
> The Site Manager API doesn't seems to have feature parity with the old API yet, so certain limitations apply.
|
||||
|
||||
### Quick setup guide
|
||||
|
||||
Navigate to your UniFi Site Manager _⚙️ Settings -> Control Plane -> Integrations_.
|
||||
|
||||
- `api_key` : You can generate your API key under the _Your API Keys_ section.
|
||||
- `base_url` : You can find your base url in the _API Request Format_ section, e.g. `https://192.168.100.1/proxy/network/integration/`
|
||||
- `version` : You can find your version as part of the url in the _API Request Format_ section, e.g. `v1`
|
||||
- `skip_ssl` : To skip SSL with you don't have an SSL certificate
|
||||
|
||||
|
||||
### Usage
|
||||
|
||||
- Head to **Settings** > **Plugin name** to adjust the default values.
|
||||
|
||||
### Notes
|
||||
|
||||
- Version: 1.0.0
|
||||
- Author: `jokob-sk`
|
||||
- Release Date: `Aug 2025`
|
||||
743
front/plugins/unifi_api_import/config.json
Executable file
743
front/plugins/unifi_api_import/config.json
Executable file
@@ -0,0 +1,743 @@
|
||||
{
|
||||
"code_name": "unifi_api_import",
|
||||
"unique_prefix": "UNIFIAPI",
|
||||
"plugin_type": "device_scanner",
|
||||
"execution_order": "Layer_0",
|
||||
"enabled": true,
|
||||
"data_source": "script",
|
||||
"mapped_to_table": "CurrentScan",
|
||||
"data_filters": [
|
||||
{
|
||||
"compare_column": "Object_PrimaryID",
|
||||
"compare_operator": "==",
|
||||
"compare_field_id": "txtMacFilter",
|
||||
"compare_js_template": "'{value}'.toString()",
|
||||
"compare_use_quotes": true
|
||||
}
|
||||
],
|
||||
"show_ui": true,
|
||||
"localized": [
|
||||
"display_name",
|
||||
"description",
|
||||
"icon"
|
||||
],
|
||||
"display_name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "UniFi import (API)"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "This plugin is used to import devices from an UNIFI controller via the Site Manager API."
|
||||
}
|
||||
],
|
||||
"icon": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "<i class=\"fa-solid fa-shield-halved\"></i>"
|
||||
}
|
||||
],
|
||||
"params": [],
|
||||
"settings": [
|
||||
{
|
||||
"function": "RUN",
|
||||
"events": [
|
||||
"run"
|
||||
],
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "select",
|
||||
"elementOptions": [],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": "disabled",
|
||||
"options": [
|
||||
"disabled",
|
||||
"once",
|
||||
"schedule"
|
||||
],
|
||||
"localized": [
|
||||
"name",
|
||||
"description"
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "When to run"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "When the plugin should run. Good options are <code>schedule</code>, <code>once</code>."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "RUN_SCHD",
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "span",
|
||||
"elementOptions": [
|
||||
{
|
||||
"cssClasses": "input-group-addon validityCheck"
|
||||
},
|
||||
{
|
||||
"getStringKey": "Gen_ValidIcon"
|
||||
}
|
||||
],
|
||||
"transformers": []
|
||||
},
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [
|
||||
{
|
||||
"onChange": "validateRegex(this)"
|
||||
},
|
||||
{
|
||||
"base64Regex": "Xig/OlwqfCg/OlswLTldfFsxLTVdWzAtOV18WzAtOV0rLVswLTldK3xcKi9bMC05XSspKVxzKyg/OlwqfCg/OlswLTldfDFbMC05XXwyWzAtM118WzAtOV0rLVswLTldK3xcKi9bMC05XSspKVxzKyg/OlwqfCg/OlsxLTldfFsxMl1bMC05XXwzWzAxXXxbMC05XSstWzAtOV0rfFwqL1swLTldKykpXHMrKD86XCp8KD86WzEtOV18MVswLTJdfFswLTldKy1bMC05XSt8XCovWzAtOV0rKSlccysoPzpcKnwoPzpbMC02XXxbMC02XS1bMC02XXxcKi9bMC05XSspKSQ="
|
||||
}
|
||||
],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": "*/5 * * * *",
|
||||
"options": [],
|
||||
"localized": [
|
||||
"name",
|
||||
"description"
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Schedule"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Only enabled if you select <code>schedule</code> in the <a href=\"#UNIFIAPI_RUN\"><code>UNIFIAPI_RUN</code> setting</a>. Make sure you enter the schedule in the correct cron-like format (e.g. validate at <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). For example entering <code>0 4 * * *</code> will run the scan after 4 am in the <a onclick=\"toggleAllSettings()\" href=\"#TIMEZONE\"><code>TIMEZONE</code> you set above</a>. Will be run NEXT time the time passes."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "CMD",
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [
|
||||
{
|
||||
"readonly": "true"
|
||||
}
|
||||
],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": "python3 /app/front/plugins/unifi_api_import/unifi_api_import.py",
|
||||
"options": [],
|
||||
"localized": [
|
||||
"name",
|
||||
"description"
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Command"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Command to run. This can not be changed"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "RUN_TIMEOUT",
|
||||
"type": {
|
||||
"dataType": "integer",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [
|
||||
{
|
||||
"type": "number"
|
||||
}
|
||||
],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": 10,
|
||||
"options": [],
|
||||
"localized": [
|
||||
"name",
|
||||
"description"
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Run timeout"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Maximum time in seconds to wait for the script to finish. If this time is exceeded the script is aborted."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "sites",
|
||||
"type": {
|
||||
"dataType": "array",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "button",
|
||||
"elementOptions": [
|
||||
{
|
||||
"sourceSuffixes": [
|
||||
"_in"
|
||||
]
|
||||
},
|
||||
{
|
||||
"separator": ""
|
||||
},
|
||||
{
|
||||
"cssClasses": "col-xs-12"
|
||||
},
|
||||
{
|
||||
"onClick": "addViaPopupForm(this)"
|
||||
},
|
||||
{
|
||||
"getStringKey": "Gen_Add"
|
||||
},
|
||||
{
|
||||
"popupForm": [
|
||||
{
|
||||
"function": "name",
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [
|
||||
{
|
||||
"placeholder": "Enter value"
|
||||
},
|
||||
{
|
||||
"suffix": "_in"
|
||||
},
|
||||
{
|
||||
"cssClasses": "col-sm-10"
|
||||
},
|
||||
{
|
||||
"prefillValue": "null"
|
||||
}
|
||||
],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": "default",
|
||||
"options": [],
|
||||
"localized": [
|
||||
"name",
|
||||
"description"
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Site name"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "The name of your site. Use a descriptive name."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "base_url",
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [
|
||||
{
|
||||
"placeholder": "https://host_ip/proxy/network/integration/"
|
||||
},
|
||||
{
|
||||
"suffix": "_in"
|
||||
},
|
||||
{
|
||||
"cssClasses": "col-sm-10"
|
||||
},
|
||||
{
|
||||
"prefillValue": "null"
|
||||
}
|
||||
],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": "default",
|
||||
"options": [],
|
||||
"localized": [
|
||||
"name",
|
||||
"description"
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Base URL"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "You can find your base url in the UniFi Site Manager in <i>Settings -> Control Plane -> Integrations</i> in the <i>API Request Format</i> section, (e.g. <code>https://host_ip/proxy/network/integration/</code>)."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "version",
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [
|
||||
{
|
||||
"placeholder": "v1"
|
||||
},
|
||||
{
|
||||
"suffix": "_in"
|
||||
},
|
||||
{
|
||||
"cssClasses": "col-sm-10"
|
||||
},
|
||||
{
|
||||
"prefillValue": "null"
|
||||
}
|
||||
],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": "default",
|
||||
"options": [],
|
||||
"localized": [
|
||||
"name",
|
||||
"description"
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "API version"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "The version of the API (e.g.: <code>v1</code>)."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "api_key",
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [
|
||||
{
|
||||
"placeholder": "Enter value"
|
||||
},
|
||||
{
|
||||
"suffix": "_in"
|
||||
},
|
||||
{
|
||||
"cssClasses": "col-sm-10"
|
||||
},
|
||||
{
|
||||
"prefillValue": "null"
|
||||
}
|
||||
],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": "default",
|
||||
"options": [],
|
||||
"localized": [
|
||||
"name",
|
||||
"description"
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "API key"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "You can get an API key in your UniFi Site Manager in <i>Settings -> Control Plane -> Integrations</i>."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "hide.site.verify_ssl",
|
||||
"type": {
|
||||
"dataType": "boolean",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [
|
||||
{
|
||||
"type": "checkbox"
|
||||
}
|
||||
],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": 1,
|
||||
"options": [],
|
||||
"localized": [
|
||||
"name",
|
||||
"description"
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Verify SSL"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Disable if you do not have an SSL certificate set up."
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"transformers": []
|
||||
}
|
||||
],
|
||||
"transformers": []
|
||||
},
|
||||
{
|
||||
"elementType": "select",
|
||||
"elementHasInputValue": 1,
|
||||
"elementOptions": [
|
||||
{
|
||||
"multiple": "true"
|
||||
},
|
||||
{
|
||||
"readonly": "true"
|
||||
},
|
||||
{
|
||||
"editable": "true"
|
||||
}
|
||||
],
|
||||
"transformers": [
|
||||
"name|base64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"elementType": "button",
|
||||
"elementOptions": [
|
||||
{
|
||||
"sourceSuffixes": []
|
||||
},
|
||||
{
|
||||
"separator": ""
|
||||
},
|
||||
{
|
||||
"cssClasses": "col-xs-6"
|
||||
},
|
||||
{
|
||||
"onClick": "removeFromList(this)"
|
||||
},
|
||||
{
|
||||
"getStringKey": "Gen_Remove_Last"
|
||||
}
|
||||
],
|
||||
"transformers": []
|
||||
},
|
||||
{
|
||||
"elementType": "button",
|
||||
"elementOptions": [
|
||||
{
|
||||
"sourceSuffixes": []
|
||||
},
|
||||
{
|
||||
"separator": ""
|
||||
},
|
||||
{
|
||||
"cssClasses": "col-xs-6"
|
||||
},
|
||||
{
|
||||
"onClick": "removeAllOptions(this)"
|
||||
},
|
||||
{
|
||||
"getStringKey": "Gen_Remove_All"
|
||||
}
|
||||
],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": [],
|
||||
"options": [],
|
||||
"localized": [
|
||||
"name",
|
||||
"description"
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Sites"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "UniFi sites"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"database_column_definitions": [
|
||||
{
|
||||
"column": "Index",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "none",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": [
|
||||
"name"
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Index"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"column": "Object_PrimaryID",
|
||||
"mapped_to_column": "cur_MAC",
|
||||
"css_classes": "col-sm-3",
|
||||
"show": true,
|
||||
"type": "device_name_mac",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": [
|
||||
"name"
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "MAC (name)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"column": "Object_SecondaryID",
|
||||
"mapped_to_column": "cur_IP",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "device_ip",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": [
|
||||
"name"
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "IP"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"column": "Watched_Value1",
|
||||
"mapped_to_column": "cur_Name",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "label",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": [
|
||||
"name"
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Name"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"column": "Watched_Value2",
|
||||
"mapped_to_column": "cur_Vendor",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "label",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": [
|
||||
"name"
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Vendor"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"column": "Watched_Value3",
|
||||
"mapped_to_column": "cur_Type",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "label",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": [
|
||||
"name"
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Device Type"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"column": "Watched_Value4",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": false,
|
||||
"type": "label",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": [
|
||||
"name"
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "N/A"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"column": "Dummy",
|
||||
"mapped_to_column": "cur_ScanMethod",
|
||||
"mapped_to_column_data": {
|
||||
"value": "Example Plugin"
|
||||
},
|
||||
"css_classes": "col-sm-2",
|
||||
"show": false,
|
||||
"type": "label",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": [
|
||||
"name"
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Scan method"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"column": "DateTimeCreated",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "label",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": [
|
||||
"name"
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Created"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"column": "DateTimeChanged",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "label",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": [
|
||||
"name"
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Changed"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"column": "Status",
|
||||
"css_classes": "col-sm-1",
|
||||
"show": true,
|
||||
"type": "replace",
|
||||
"default_value": "",
|
||||
"options": [
|
||||
{
|
||||
"equals": "watched-not-changed",
|
||||
"replacement": "<div style='text-align:center'><i class='fa-solid fa-square-check'></i><div></div>"
|
||||
},
|
||||
{
|
||||
"equals": "watched-changed",
|
||||
"replacement": "<div style='text-align:center'><i class='fa-solid fa-triangle-exclamation'></i></div>"
|
||||
},
|
||||
{
|
||||
"equals": "new",
|
||||
"replacement": "<div style='text-align:center'><i class='fa-solid fa-circle-plus'></i></div>"
|
||||
},
|
||||
{
|
||||
"equals": "missing-in-last-scan",
|
||||
"replacement": "<div style='text-align:center'><i class='fa-solid fa-question'></i></div>"
|
||||
}
|
||||
],
|
||||
"localized": [
|
||||
"name"
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Status"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
124
front/plugins/unifi_api_import/unifi_api_import.py
Executable file
124
front/plugins/unifi_api_import/unifi_api_import.py
Executable file
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
import json
|
||||
import sqlite3
|
||||
from pytz import timezone
|
||||
|
||||
# Define the installation path and extend the system path for plugin imports
|
||||
INSTALL_PATH = "/app"
|
||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||
|
||||
from plugin_helper import Plugin_Object, Plugin_Objects, decodeBase64
|
||||
from plugin_utils import get_plugins_configs
|
||||
from logger import mylog, Logger
|
||||
from const import pluginsPath, fullDbPath, logPath
|
||||
from helper import timeNowTZ, get_setting_value
|
||||
|
||||
from messaging.in_app import write_notification
|
||||
import conf
|
||||
|
||||
# Make sure the TIMEZONE for logging is correct
|
||||
conf.tz = timezone(get_setting_value('TIMEZONE'))
|
||||
|
||||
# Make sure log level is initialized correctly
|
||||
Logger(get_setting_value('LOG_LEVEL'))
|
||||
|
||||
pluginName = '<unique_prefix>'
|
||||
|
||||
# Define the current path and log file paths
|
||||
LOG_PATH = logPath + '/plugins'
|
||||
LOG_FILE = os.path.join(LOG_PATH, f'script.{pluginName}.log')
|
||||
RESULT_FILE = os.path.join(LOG_PATH, f'last_result.{pluginName}.log')
|
||||
|
||||
# Initialize the Plugin obj output file
|
||||
plugin_objects = Plugin_Objects(RESULT_FILE)
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
mylog('verbose', [f'[{pluginName}] In script'])
|
||||
|
||||
# Retrieve configuration settings
|
||||
some_setting = get_setting_value('SYNC_plugins')
|
||||
|
||||
mylog('verbose', [f'[{pluginName}] some_setting value {some_setting}'])
|
||||
|
||||
# retrieve data
|
||||
device_data = get_device_data(some_setting)
|
||||
|
||||
# Process the data into native application tables
|
||||
if len(device_data) > 0:
|
||||
|
||||
# insert devices into the lats_result.log
|
||||
# make sure the below mapping is mapped in config.json, for example:
|
||||
#"database_column_definitions": [
|
||||
# {
|
||||
# "column": "Object_PrimaryID", <--------- the value I save into primaryId
|
||||
# "mapped_to_column": "cur_MAC", <--------- gets inserted into the CurrentScan DB table column cur_MAC
|
||||
#
|
||||
for device in device_data:
|
||||
plugin_objects.add_object(
|
||||
primaryId = device['mac_address'],
|
||||
secondaryId = device['ip_address'],
|
||||
watched1 = device['hostname'],
|
||||
watched2 = device['vendor'],
|
||||
watched3 = device['device_type'],
|
||||
watched4 = device['last_seen'],
|
||||
extra = '',
|
||||
foreignKey = device['mac_address']
|
||||
# helpVal1 = "Something1", # Optional Helper values to be passed for mapping into the app
|
||||
# helpVal2 = "Something1", # If you need to use even only 1, add the remaining ones too
|
||||
# helpVal3 = "Something1", # and set them to 'null'. Check the the docs for details:
|
||||
# helpVal4 = "Something1", # https://github.com/jokob-sk/NetAlertX/blob/main/docs/PLUGINS_DEV.md
|
||||
)
|
||||
|
||||
mylog('verbose', [f'[{pluginName}] New entries: "{len(device_data)}"'])
|
||||
|
||||
# log result
|
||||
plugin_objects.write_result_file()
|
||||
|
||||
return 0
|
||||
|
||||
# retrieve data
|
||||
def get_device_data(some_setting):
|
||||
|
||||
device_data = []
|
||||
|
||||
# do some processing, call exteranl APIs, and return a device_data list
|
||||
# ...
|
||||
#
|
||||
# Sample data for testing purposes, you can adjust the processing in main() as needed
|
||||
# ... before adding it to the plugin_objects.add_object(...)
|
||||
device_data = [
|
||||
{
|
||||
'device_id': 'device1',
|
||||
'mac_address': '00:11:22:33:44:55',
|
||||
'ip_address': '192.168.1.2',
|
||||
'hostname': 'iPhone 12',
|
||||
'vendor': 'Apple Inc.',
|
||||
'device_type': 'Smartphone',
|
||||
'last_seen': '2024-06-27 10:00:00',
|
||||
'port': '1',
|
||||
'network_id': 'network1'
|
||||
},
|
||||
{
|
||||
'device_id': 'device2',
|
||||
'mac_address': '00:11:22:33:44:66',
|
||||
'ip_address': '192.168.1.3',
|
||||
'hostname': 'Moto G82',
|
||||
'vendor': 'Motorola Inc.',
|
||||
'device_type': 'Laptop',
|
||||
'last_seen': '2024-06-27 10:05:00',
|
||||
'port': '',
|
||||
'network_id': 'network1'
|
||||
}
|
||||
]
|
||||
|
||||
# Return the data to be detected by the main application
|
||||
return device_data
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
require 'php/templates/header.php';
|
||||
require 'php/templates/notification.php';
|
||||
require 'php/templates/modals.php';
|
||||
|
||||
?>
|
||||
|
||||
|
||||
@@ -58,6 +58,12 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
|
||||
|
||||
<div id="settingsPage" class="content-wrapper">
|
||||
|
||||
<a style="cursor:pointer">
|
||||
<span>
|
||||
<i id='toggleSettings' onclick="toggleAllSettings()" class="settings-expand-icon fa fa-angle-double-down"></i>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<!-- Content header--------------------------------------------------------- -->
|
||||
|
||||
<section class="content-header">
|
||||
@@ -597,7 +603,8 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
|
||||
customParams,
|
||||
customId,
|
||||
columns,
|
||||
base64Regex
|
||||
base64Regex,
|
||||
elementOptionsBase64
|
||||
} = handleElementOptions('none', elementOptions, transformers, val = "");
|
||||
|
||||
let value;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
require 'php/templates/header.php';
|
||||
?>
|
||||
<?php require 'php/templates/notification.php'; ?>
|
||||
<?php require 'php/templates/modals.php'; ?>
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
// show spinning icon
|
||||
showSpinner()
|
||||
|
||||
</script>
|
||||
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
@@ -28,674 +29,112 @@
|
||||
|
||||
<!-- Main content ---------------------------------------------------------- -->
|
||||
<section class="content">
|
||||
|
||||
<?php
|
||||
//General stats
|
||||
// Date & Time
|
||||
$date = new DateTime();
|
||||
$formatted_date = $date->format('l, F j, Y H:i:s'); // Get date
|
||||
$formatted_date2 = $date->format('d/m/Y H:i:s'); // Get date2
|
||||
$formatted_date3 = $date->format('Y/m/d H:i:s'); // Get date3
|
||||
//System stats
|
||||
// OS-Version
|
||||
$os_version = '';
|
||||
// Raspbian
|
||||
if ($os_version == '') {$os_version = exec('cat /etc/os-release | grep PRETTY_NAME');}
|
||||
// Dietpi
|
||||
if ($os_version == '') {$os_version = exec('uname -o');}
|
||||
//$os_version_arr = explode("\n", trim($os_version));
|
||||
$stat['os_version'] = str_replace('"', '', str_replace('PRETTY_NAME=', '', $os_version));
|
||||
$stat['uptime'] = str_replace('up ', '', shell_exec("uptime -p")); // Get system uptime
|
||||
$system_namekernel = shell_exec("uname"); // Get system name kernel
|
||||
$system_namesystem = shell_exec("uname -o"); // Get name system
|
||||
$system_full = shell_exec("uname -a"); // Get system full
|
||||
$system_architecture = shell_exec("uname -m"); // Get system Architecture
|
||||
$load_average = sys_getloadavg(); // Get load average
|
||||
$system_process_count = shell_exec("ps -e --no-headers | wc -l"); // Count processes
|
||||
//Motherboard stats
|
||||
$motherboard_name = shell_exec('cat /sys/class/dmi/id/board_name'); // Get the Motherboard name
|
||||
$motherboard_manufactured = shell_exec('cat /sys/class/dmi/id/board_vendor'); // Get the Motherboard manufactured
|
||||
$motherboard_revision = shell_exec('cat /sys/class/dmi/id/board_version'); // Get the Motherboard revision
|
||||
$motherboard_bios = shell_exec('cat /sys/class/dmi/id/bios_version'); // Get the Motherboard BIOS
|
||||
$motherboard_biosdate = shell_exec('cat /sys/class/dmi/id/bios_date'); // Get the Motherboard BIOS date
|
||||
$motherboard_biosvendor = shell_exec('cat /sys/class/dmi/id/bios_vendor'); // Get the Motherboard BIOS vendor
|
||||
//CPU stats
|
||||
$prevVal = shell_exec("cat /proc/cpuinfo | grep processor");
|
||||
$prevArr = explode("\n", trim($prevVal));
|
||||
$stat['cpu'] = sizeof($prevArr);
|
||||
$cpu_result = shell_exec("cat /proc/cpuinfo | grep Model");
|
||||
$stat['cpu_model'] = strstr($cpu_result, "\n", true);
|
||||
$stat['cpu_model'] = str_replace(":", "", trim(str_replace("Model", "", $stat['cpu_model'])));
|
||||
if ($stat['cpu_model'] == '') {
|
||||
$cpu_result = shell_exec("cat /proc/cpuinfo | grep model\ name");
|
||||
$stat['cpu_model'] = strstr($cpu_result, "\n", true);
|
||||
$stat['cpu_model'] = str_replace(":", "", trim(str_replace("model name", "", $stat['cpu_model'])));
|
||||
}
|
||||
if (file_exists('/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq')) {
|
||||
// RaspbianOS
|
||||
$stat['cpu_frequ'] = exec('cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq') / 1000;
|
||||
} elseif (is_numeric(str_replace(',', '.', exec('lscpu | grep "MHz" | awk \'{print $3}\'')))) {
|
||||
// Ubuntu Server, DietPi event. others
|
||||
$stat['cpu_frequ'] = round(exec('lscpu | grep "MHz" | awk \'{print $3}\''), 0);
|
||||
} elseif (is_numeric(str_replace(',', '.', exec('lscpu | grep "max MHz" | awk \'{print $4}\'')))) {
|
||||
// RaspbianOS and event. others
|
||||
$stat['cpu_frequ'] = round(str_replace(',', '.', exec('lscpu | grep "max MHz" | awk \'{print $4}\'')), 0);
|
||||
} else {
|
||||
// Fallback
|
||||
$stat['cpu_frequ'] = "unknown";
|
||||
}
|
||||
$cpu_temp = shell_exec('cat /sys/class/hwmon/hwmon0/temp1_input'); // Get the CPU temperature
|
||||
$cpu_temp = floatval($cpu_temp) / 1000; // Convert the temperature to degrees Celsius
|
||||
$cpu_vendor = exec('cat /proc/cpuinfo | grep "vendor_id" | cut -d ":" -f 2' ); // Get the CPU vendor
|
||||
//Memory stats
|
||||
$total_memorykb = shell_exec("cat /proc/meminfo | grep MemTotal | awk '{print $2}'");
|
||||
$total_memorykb = trim($total_memorykb);
|
||||
$total_memorykb = number_format($total_memorykb, 0, '.', '.');
|
||||
$total_memorymb = shell_exec("cat /proc/meminfo | grep MemTotal | awk '{print $2/1024}'");
|
||||
$total_memorymb = trim($total_memorymb);
|
||||
$total_memorymb = number_format($total_memorymb, 0, '.', '.');
|
||||
$mem_used = round(memory_get_usage() / 1048576 * 100, 2);
|
||||
$memory_usage_percent = round(($mem_used / $total_memorymb), 2);
|
||||
//HDD stats
|
||||
$hdd_result = shell_exec(" df -P | awk '{print $1}'");
|
||||
$hdd_devices = explode("\n", trim($hdd_result));
|
||||
$hdd_result = shell_exec(" df -P | awk '{print $2}'");
|
||||
$hdd_devices_total = explode("\n", trim($hdd_result));
|
||||
$hdd_result = shell_exec(" df -P | awk '{print $3}'");
|
||||
$hdd_devices_used = explode("\n", trim($hdd_result));
|
||||
$hdd_result = shell_exec(" df -P | awk '{print $4}'");
|
||||
$hdd_devices_free = explode("\n", trim($hdd_result));
|
||||
$hdd_result = shell_exec(" df -P | awk '{print $5}'");
|
||||
$hdd_devices_percent = explode("\n", trim($hdd_result));
|
||||
$hdd_result = shell_exec(" df -P | awk '{print $6}'");
|
||||
$hdd_devices_mount = explode("\n", trim($hdd_result));
|
||||
//Network stats
|
||||
// Check Server name
|
||||
if (!empty(gethostname())) { $network_NAME = gethostname(); } else { $network_NAME = lang('Systeminfo_Network_Server_Name_String'); }
|
||||
// Check HTTPS
|
||||
if (isset($_SERVER['HTTPS'])) { $network_HTTPS = 'Yes (HTTPS)'; } else { $network_HTTPS = lang('Systeminfo_Network_Secure_Connection_String'); }
|
||||
// Check Query String
|
||||
if (empty($_SERVER['QUERY_STRING'])) { $network_QueryString = lang('Systeminfo_Network_Server_Query_String'); } else { $network_QueryString = $_SERVER['QUERY_STRING']; }
|
||||
// Check HTTP referer
|
||||
if (empty($_SERVER['HTTP_REFERER'])) { $network_referer = lang('Systeminfo_Network_HTTP_Referer_String'); } else { $network_referer = $_SERVER['HTTP_REFERER']; }
|
||||
//Network Hardware stat
|
||||
$network_result = shell_exec("cat /proc/net/dev | tail -n +3 | awk '{print $1}'");
|
||||
$net_interfaces = explode("\n", trim($network_result));
|
||||
$network_result = shell_exec("cat /proc/net/dev | tail -n +3 | awk '{print $2}'");
|
||||
$net_interfaces_rx = explode("\n", trim($network_result));
|
||||
$network_result = shell_exec("cat /proc/net/dev | tail -n +3 | awk '{print $10}'");
|
||||
$net_interfaces_tx = explode("\n", trim($network_result));
|
||||
//USB devices
|
||||
$usb_result = shell_exec("lsusb");
|
||||
$usb_devices_mount = explode("\n", trim($usb_result));
|
||||
|
||||
// General ----------------------------------------------------------
|
||||
echo '<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title sysinfo_headline"><i class="fa fa-info-circle"></i> ' . lang('Systeminfo_General') . '</h3>
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-sm-12 col-xs-12">
|
||||
<!-- <div class="box-transparent"> -->
|
||||
<div id="navSysInfo" class="nav-tabs-custom">
|
||||
<ul class="nav nav-tabs" style="font-size:16px;">
|
||||
<li>
|
||||
<a id="tabServer" href="#panServer" data-toggle="tab">
|
||||
<i class="fa fa-info-circle"></i>
|
||||
<span class="dev-detail-tab-name">
|
||||
<?= lang('Systeminfo_System');?>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="tabNetwork" href="#panNetwork" data-toggle="tab">
|
||||
<i class="fa fa-sitemap fa-rotate-270"></i>
|
||||
<span class="dev-detail-tab-name">
|
||||
<?= lang('Systeminfo_Network');?>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="tabStorage" href="#panStorage" data-toggle="tab">
|
||||
<i class="fa fa-hdd"></i>
|
||||
<span class="dev-detail-tab-name">
|
||||
<?= lang('Systeminfo_Storage');?>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content spinnerTarget" style="min-height: 430px;">
|
||||
<div class="tab-pane fade" data-php-file="systeminfoServer.php" id="panServer">
|
||||
<!-- PLACEHOLDER -->
|
||||
</div>
|
||||
<div class="tab-pane fade" data-php-file="systeminfoNetwork.php" id="panNetwork">
|
||||
<!-- PLACEHOLDER -->
|
||||
</div>
|
||||
<div class="tab-pane fade table-responsive" data-php-file="systeminfoStorage.php" id="panStorage">
|
||||
<!-- PLACEHOLDER -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_general_a">' . lang('Systeminfo_General_Full_Date') . '</div>
|
||||
<div class="col-sm-9 sysinfo_general_b">' . $formatted_date . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_general_a">' . lang('Systeminfo_General_Date') . '</div>
|
||||
<div class="col-sm-9 sysinfo_general_b">' . $formatted_date2 . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_general_a">' . lang('Systeminfo_General_Date2') . '</div>
|
||||
<div class="col-sm-9 sysinfo_general_b">' . $formatted_date3 . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_general_a">' . lang('Systeminfo_General_TimeZone') . '</div>
|
||||
<div class="col-sm-9 sysinfo_general_b">' . $timeZone . '</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
// Network Hardware ----------------------------------------------------------
|
||||
echo '<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title sysinfo_headline"><i class="fa fa-sitemap fa-rotate-270"></i> ' . lang('Systeminfo_Network_Hardware') . '</h3>
|
||||
<!-- /.tab-content -->
|
||||
</div>
|
||||
<!-- /.nav-tabs-custom -->
|
||||
<!-- </div> -->
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<table id="networkTable" class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>' . lang('Systeminfo_Network_Hardware_Interface_Name') . '</th>
|
||||
<th>' . lang('Systeminfo_Network_Hardware_Interface_Mask') . '</th>
|
||||
<th>' . lang('Systeminfo_Network_Hardware_Interface_RX') . '</th>
|
||||
<th>' . lang('Systeminfo_Network_Hardware_Interface_TX') . '</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>';
|
||||
<!-- /.col -->
|
||||
</div>
|
||||
|
||||
for ($x = 0; $x < sizeof($net_interfaces); $x++) {
|
||||
$interface_name = str_replace(':', '', $net_interfaces[$x]);
|
||||
$interface_ip_temp = exec('ip addr show ' . $interface_name . ' | grep "inet "');
|
||||
$interface_ip_arr = explode(' ', trim($interface_ip_temp));
|
||||
|
||||
if (!isset($interface_ip_arr[1])) {
|
||||
$interface_ip_arr[1] = '--';
|
||||
}
|
||||
|
||||
if ($net_interfaces_rx[$x] == 0) {
|
||||
$temp_rx = 0;
|
||||
} else {
|
||||
$temp_rx = number_format(round(($net_interfaces_rx[$x] / 1024 / 1024), 2), 2, ',', '.');
|
||||
}
|
||||
if ($net_interfaces_tx[$x] == 0) {
|
||||
$temp_tx = 0;
|
||||
} else {
|
||||
$temp_tx = number_format(round(($net_interfaces_tx[$x] / 1024 / 1024), 2), 2, ',', '.');
|
||||
}
|
||||
echo '<tr>';
|
||||
echo '<td>' . $interface_name . '</td>';
|
||||
echo '<td>' . $interface_ip_arr[1] . '</td>';
|
||||
echo '<td>' . $temp_rx . ' MB</td>';
|
||||
echo '<td>' . $temp_tx . ' MB</td>';
|
||||
echo '</tr>';
|
||||
}
|
||||
|
||||
echo ' </tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
// Available IPs ----------------------------------------------------------
|
||||
echo '<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title availableips_headline"><i class="fa fa-list"></i> ' . lang('Systeminfo_AvailableIps') . '</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<table id="availableIpsTable" class="display" style="width:100%"></table>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
// Client ----------------------------------------------------------
|
||||
echo '<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title sysinfo_headline"><i class="fa fa-globe"></i> ' . lang('Systeminfo_This_Client') . '</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_client_a">' . lang('Systeminfo_Client_User_Agent') . '</div>
|
||||
<div class="col-sm-9 sysinfo_client_b">' . $_SERVER['HTTP_USER_AGENT'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_client_a">' . lang('Systeminfo_Client_Resolution') . '</div>
|
||||
<div class="col-sm-9 sysinfo_client_b" id="resolution"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
echo '<script>
|
||||
var ratio = window.devicePixelRatio || 1;
|
||||
var w = window.innerWidth;
|
||||
var h = window.innerHeight;
|
||||
var rw = window.innerWidth * ratio;
|
||||
var rh = window.innerHeight * ratio;
|
||||
|
||||
var resolutionDiv = document.getElementById("resolution");
|
||||
resolutionDiv.innerHTML = "Width: " + w + "px / Height: " + h + "px<br> " + "Width: " + rw + "px / Height: " + rh + "px (native)";
|
||||
</script>';
|
||||
|
||||
// System ----------------------------------------------------------
|
||||
echo '<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title sysinfo_headline"><i class="fa fa-computer"></i> ' . lang('Systeminfo_System') . '</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_Uptime') . '</div>
|
||||
<div class="col-sm-9 sysinfo_system_b">' . $stat['uptime'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_Kernel') . '</div>
|
||||
<div class="col-sm-9 sysinfo_system_b">' . $system_namekernel . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_System') . '</div>
|
||||
<div class="col-sm-9 sysinfo_system_b">' . $system_namesystem . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_OSVersion') . '</div>
|
||||
<div class="col-sm-9 sysinfo_system_b">' . $stat['os_version'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_Uname') . '</div>
|
||||
<div class="col-sm-9 sysinfo_system_b">' . $system_full . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_Architecture') . '</div>
|
||||
<div class="col-sm-9 sysinfo_system_b">' . $system_architecture . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_AVG') . '</div>
|
||||
<div class="col-sm-9 sysinfo_system_b">'. $load_average[0] .' '. $load_average[1] .' '. $load_average[2] .'</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_Running_Processes') . '</div>
|
||||
<div class="col-sm-9 sysinfo_system_b">' . $system_process_count . '</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
// Motherboard ----------------------------------------------------------
|
||||
echo '<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title sysinfo_headline"><i class="fa fa-laptop-code"></i> ' . lang('Systeminfo_Motherboard') . '</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_motherboard_a">' . lang('Systeminfo_Motherboard_Name') . '</div>
|
||||
<div class="col-sm-9 sysinfo_motherboard_b">' . $motherboard_name . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_motherboard_a">' . lang('Systeminfo_Motherboard_Manufactured') . '</div>
|
||||
<div class="col-sm-9 sysinfo_motherboard_b">' . $motherboard_manufactured . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_motherboard_a">' . lang('Systeminfo_Motherboard_Revision') . '</div>
|
||||
<div class="col-sm-9 sysinfo_motherboard_b">' . $motherboard_revision. '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_motherboard_a">' . lang('Systeminfo_Motherboard_BIOS') . '</div>
|
||||
<div class="col-sm-9 sysinfo_motherboard_b">' . $motherboard_bios . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_motherboard_a">' . lang('Systeminfo_Motherboard_BIOS_Date') . '</div>
|
||||
<div class="col-sm-9 sysinfo_motherboard_b">' . $motherboard_biosdate . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_motherboard_a">' . lang('Systeminfo_Motherboard_BIOS_Vendor') . '</div>
|
||||
<div class="col-sm-9 sysinfo_motherboard_b">' . $motherboard_biosvendor . '</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
// CPU ----------------------------------------------------------
|
||||
echo '<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title sysinfo_headline"><i class="fa fa-microchip"></i> ' . lang('Systeminfo_CPU') . '</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_cpu_a">' . lang('Systeminfo_CPU_Vendor') . '</div>
|
||||
<div class="col-sm-9 sysinfo_cpu_b">' . $cpu_vendor . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_cpu_a">' . lang('Systeminfo_CPU_Name') . '</div>
|
||||
<div class="col-sm-9 sysinfo_cpu_b">' . $stat['cpu_model'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_cpu_a">' . lang('Systeminfo_CPU_Cores') . '</div>
|
||||
<div class="col-sm-9 sysinfo_cpu_b">' . $stat['cpu'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_cpu_a">' . lang('Systeminfo_CPU_Speed') . '</div>
|
||||
<div class="col-sm-9 sysinfo_cpu_b">' . $stat['cpu_frequ'] . ' MHz</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_cpu_a">' . lang('Systeminfo_CPU_Temp') . '</div>
|
||||
<div class="col-sm-9 sysinfo_cpu_b">'. $cpu_temp .' °C</div>
|
||||
</div>';
|
||||
// Get the number of CPU cores
|
||||
$num_cpus = $stat['cpu'];
|
||||
$num_cpus = $num_cpus +2;
|
||||
|
||||
// Iterate over the CPU cores
|
||||
for ($i = 2,$a = 0; $i < $num_cpus; $i++,$a++) {
|
||||
|
||||
// Get the CPU temperature
|
||||
$cpu_tempxx = shell_exec('cat /sys/class/hwmon/hwmon0/temp' . $i . '_input');
|
||||
|
||||
// Convert the temperature to degrees Celsius
|
||||
$cpu_tempxx = floatval($cpu_tempxx) / 1000;
|
||||
|
||||
// Print the CPU temperature
|
||||
echo '<div class="row">
|
||||
<div class="col-sm-3 sysinfo_cpu_a">CPU Temp ' . $a . ':</div>
|
||||
<div class="col-sm-9 sysinfo_cpu_b">' . $cpu_tempxx . ' °C</div>
|
||||
</div>';
|
||||
}
|
||||
echo '
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
// Memory ----------------------------------------------------------
|
||||
echo '<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title sysinfo_headline"><i class="fa fa-memory"></i> ' . lang('Systeminfo_Memory') . '</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_memory_a">' . lang('Systeminfo_Memory_Usage_Percent') . '</div>
|
||||
<div class="col-sm-9 sysinfo_memory_b">' . $memory_usage_percent . ' %</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_memory_a">' . lang('Systeminfo_Memory_Usage') . '</div>
|
||||
<div class="col-sm-9 sysinfo_memory_b">' . $mem_used . ' MB / ' . $total_memorymb . ' MB</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_memory_a">' . lang('Systeminfo_Memory_Total_Memory') . '</div>
|
||||
<div class="col-sm-9 sysinfo_memory_b">' . $total_memorymb . ' MB (' . $total_memorykb . ' KB)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
// Storage ----------------------------------------------------------
|
||||
echo '<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title sysinfo_headline"><i class="fa fa-hdd"></i> ' . lang('Systeminfo_Storage') . '</h3>
|
||||
</div>
|
||||
<div class="box-body">';
|
||||
|
||||
$storage_lsblk = shell_exec("lsblk -io NAME,SIZE,TYPE,MOUNTPOINT,MODEL --list | tail -n +2 | awk '{print $1\"#\"$2\"#\"$3\"#\"$4\"#\"$5}'");
|
||||
$storage_lsblk_line = explode("\n", $storage_lsblk);
|
||||
$storage_lsblk_line = array_filter($storage_lsblk_line);
|
||||
|
||||
for ($x = 0; $x < sizeof($storage_lsblk_line); $x++) {
|
||||
$temp = array();
|
||||
$temp = explode("#", $storage_lsblk_line[$x]);
|
||||
$storage_lsblk_line[$x] = $temp;
|
||||
}
|
||||
|
||||
for ($x = 0; $x < sizeof($storage_lsblk_line); $x++) {
|
||||
echo '<div class="row">';
|
||||
if (preg_match('~[0-9]+~', $storage_lsblk_line[$x][0])) {
|
||||
echo '<div class="col-sm-4 sysinfo_storage_a">"' . lang('Systeminfo_Storage_Mount') . ' ' . $storage_lsblk_line[$x][3] . '"</div>';
|
||||
} else {
|
||||
echo '<div class="col-sm-4 sysinfo_storage_a">"' . str_replace('_', ' ', $storage_lsblk_line[$x][3]) . '"</div>';
|
||||
}
|
||||
echo '<div class="col-sm-3 sysinfo_storage_b">' . lang('Systeminfo_Storage_Device') . ' /dev/' . $storage_lsblk_line[$x][0] . '</div>';
|
||||
echo '<div class="col-sm-2 sysinfo_storage_b">' . lang('Systeminfo_Storage_Size') . ' ' . $storage_lsblk_line[$x][1] . '</div>';
|
||||
echo '<div class="col-sm-2 sysinfo_storage_b">' . lang('Systeminfo_Storage_Type') . ' ' . $storage_lsblk_line[$x][2] . '</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
echo ' </div>
|
||||
</div>';
|
||||
|
||||
// Storage usage ----------------------------------------------------------
|
||||
echo '<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title sysinfo_headline"><i class="fa fa-hdd"></i> ' . lang('Systeminfo_Storage_Usage') . '</h3>
|
||||
</div>
|
||||
<div class="box-body">';
|
||||
for ($x = 0; $x < sizeof($hdd_devices); $x++) {
|
||||
if (stristr($hdd_devices[$x], '/dev/')) {
|
||||
if (!stristr($hdd_devices[$x], '/loop')) {
|
||||
if ($hdd_devices_total[$x] == 0 || $hdd_devices_total[$x] == '') {$temp_total = 0;} else { $temp_total = number_format(round(($hdd_devices_total[$x] / 1024 / 1024), 2), 2, ',', '.'); $temp_total = trim($temp_total);}
|
||||
if ($hdd_devices_used[$x] == 0 || $hdd_devices_used[$x] == '') {$temp_used = 0;} else { $temp_used = number_format(round(($hdd_devices_used[$x] / 1024 / 1024), 2), 2, ',', '.'); $temp_used = trim($temp_total);}
|
||||
if ($hdd_devices_free[$x] == 0 || $hdd_devices_free[$x] == '') {$temp_free = 0;} else { $temp_free = number_format(round(($hdd_devices_free[$x] / 1024 / 1024), 2), 2, ',', '.'); $temp_free = trim($temp_total);}
|
||||
echo '<div class="row">';
|
||||
echo '<div class="col-sm-4 sysinfo_storage_usage_a">"' . lang('Systeminfo_Storage_Usage_Mount') . ' ' . $hdd_devices_mount[$x] . '"</div>';
|
||||
echo '<div class="col-sm-2 sysinfo_storage_usage_b">' . lang('Systeminfo_Storage_Usage_Total') . ' ' . $temp_total . ' GB</div>';
|
||||
echo '<div class="col-sm-3 sysinfo_storage_usage_b">' . lang('Systeminfo_Storage_Usage_Used') . ' ' . $temp_used . ' GB (' . $hdd_devices_percent[$x]. ')</div>';
|
||||
echo '<div class="col-sm-2 sysinfo_storage_usage_b">' . lang('Systeminfo_Storage_Usage_Free') . ' ' . $temp_free . ' GB</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
#echo '<br>' . $lang['SysInfo_storage_note'];
|
||||
echo ' </div>
|
||||
</div>';
|
||||
|
||||
// Network ----------------------------------------------------------
|
||||
echo '<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title sysinfo_headline"><i class="fas fa-ethernet"></i> ' . lang('Systeminfo_Network') . '</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_IP') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . shell_exec("curl https://ifconfig.co") . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_IP_Connection') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['REMOTE_ADDR'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_IP_Server') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['SERVER_ADDR'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Server_Name') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $network_NAME . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Connection_Port') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['REMOTE_PORT'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Secure_Connection') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $network_HTTPS . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Server_Version') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['SERVER_SOFTWARE'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_gerneral_a">' . lang('Systeminfo_Network_Request_URI') . '</div>
|
||||
<div class="col-sm-9 sysinfo_gerneral_b">' . $_SERVER['REQUEST_URI'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Server_Query') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $network_QueryString . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_HTTP_Host') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['HTTP_HOST'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_HTTP_Referer') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $network_referer . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_MIME') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['HTTP_ACCEPT'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Accept_Language') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['HTTP_ACCEPT_LANGUAGE'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Accept_Encoding') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['HTTP_ACCEPT_ENCODING'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Request_Method') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['REQUEST_METHOD'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Request_Time') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['REQUEST_TIME'] . '</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
|
||||
|
||||
|
||||
// Services ----------------------------------------------------------
|
||||
echo '<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title sysinfo_headline"><i class="fa fa-database"></i> ' . lang('Systeminfo_Services') . '</h3>
|
||||
</div>
|
||||
<div class="box-body">';
|
||||
echo '<div style="height: 300px; overflow: scroll;">';
|
||||
exec('systemctl --type=service --state=running', $running_services);
|
||||
echo '<table class="table table-bordered table-hover table-striped dataTable no-footer" style="margin-bottom: 10px;">';
|
||||
echo '<thead>
|
||||
<tr role="row">
|
||||
<th style="padding: 8px;">' . lang('Systeminfo_Services_Name') . '</th>
|
||||
<th style="padding: 8px;">' . lang('Systeminfo_Services_Description') . '</th>
|
||||
</tr>
|
||||
</thead>';
|
||||
$table_color = 'odd';
|
||||
for ($x = 0; $x < sizeof($running_services); $x++) {
|
||||
if (stristr($running_services[$x], '.service')) {
|
||||
$temp_services_arr = array_values(array_filter(explode(' ', trim($running_services[$x]))));
|
||||
$servives_name = $temp_services_arr[0];
|
||||
unset($temp_services_arr[0], $temp_services_arr[1], $temp_services_arr[2], $temp_services_arr[3]);
|
||||
$servives_description = implode(" ", $temp_services_arr);
|
||||
if ($table_color == 'odd') {$table_color = 'even';} else { $table_color = 'odd';}
|
||||
|
||||
echo '<tr class="' . $table_color . '"><td style="padding: 3px; padding-left: 10px;">' . substr($servives_name, 0, -8) . '</td><td style="padding: 3px; padding-left: 10px;">' . $servives_description . '</td></tr>';
|
||||
}
|
||||
}
|
||||
echo '</table>';
|
||||
echo '</div>';
|
||||
echo ' </div>
|
||||
</div>';
|
||||
|
||||
// USB ----------------------------------------------------------
|
||||
echo '<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title sysinfo_headline"><i class="fab fa-usb"></i> ' . lang('Systeminfo_USB_Devices') . '</h3>
|
||||
</div>
|
||||
<div class="box-body">';
|
||||
echo ' <table class="table table-bordered table-hover table-striped dataTable no-footer" style="margin-bottom: 10px;">';
|
||||
|
||||
$table_color = 'odd';
|
||||
sort($usb_devices_mount);
|
||||
for ($x = 0; $x < sizeof($usb_devices_mount); $x++) {
|
||||
$cut_pos = strpos($usb_devices_mount[$x], ':');
|
||||
$usb_bus = substr($usb_devices_mount[$x], 0, $cut_pos);
|
||||
$usb_dev = substr($usb_devices_mount[$x], $cut_pos + 1);
|
||||
|
||||
if ($table_color == 'odd') {$table_color = 'even';} else { $table_color = 'odd';}
|
||||
echo '<tr class="' . $table_color . '"><td style="padding: 3px; padding-left: 10px; width: 150px;"><b>' . str_replace('Device', 'Dev.', $usb_bus) . '</b></td><td style="padding: 3px; padding-left: 10px;">' . $usb_dev . '</td></tr>';
|
||||
}
|
||||
echo ' </table>';
|
||||
echo ' </div>
|
||||
</div>';
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
echo '<br>';
|
||||
|
||||
?>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- /.content -->
|
||||
<?php
|
||||
require 'php/templates/footer.php';
|
||||
?>
|
||||
|
||||
</div>
|
||||
<!-- /.content-wrapper -->
|
||||
|
||||
<?php
|
||||
require 'php/templates/footer.php';
|
||||
?>
|
||||
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
|
||||
<!-- DataTable initialization -->
|
||||
<script>
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Available free IPS functionality
|
||||
function inferNetworkRange(usedIps) {
|
||||
if (!usedIps || usedIps.length === 0) return [];
|
||||
function loadTabContent(target) {
|
||||
const $tab = $(target);
|
||||
const phpFile = $tab.data('php-file');
|
||||
|
||||
const subnetMap = {};
|
||||
if (phpFile && !$tab.data('loaded')) {
|
||||
showSpinner();
|
||||
$tab.load(phpFile, function () {
|
||||
$tab.data('loaded', true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Group IPs by /24 subnet
|
||||
for (const ip of usedIps) {
|
||||
const parts = ip.split('.');
|
||||
if (parts.length !== 4) continue;
|
||||
const subnet = `${parts[0]}.${parts[1]}.${parts[2]}`;
|
||||
if (!subnetMap[subnet]) subnetMap[subnet] = [];
|
||||
subnetMap[subnet].push(ip);
|
||||
function initializeTabs() {
|
||||
const key = "activeSysinfoTab";
|
||||
let selectedTab = "tabServer"; // fallback default
|
||||
|
||||
const cached = getCache(key);
|
||||
if (!emptyArr.includes(cached)) {
|
||||
selectedTab = cached;
|
||||
}
|
||||
|
||||
const result = [];
|
||||
// Activate the correct tab
|
||||
const $tabLink = $('.nav-tabs a[id="' + selectedTab + '"]');
|
||||
$tabLink.tab('show');
|
||||
|
||||
for (const [subnet, ips] of Object.entries(subnetMap)) {
|
||||
if (ips.length > 5) {
|
||||
for (let i = 2; i < 255; i++) {
|
||||
const ip = `${subnet}.${i}`;
|
||||
result.push({ subnet, ip });
|
||||
}
|
||||
}
|
||||
}
|
||||
// Get the pane's ID from the tab's href attribute
|
||||
const targetSelector = $tabLink.attr("href");
|
||||
loadTabContent(targetSelector);
|
||||
|
||||
return result;
|
||||
}
|
||||
// On tab change
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
const newTabId = $(e.target).attr('id');
|
||||
setCache(key, newTabId);
|
||||
|
||||
function fetchUsedIps(callback) {
|
||||
$.ajax({
|
||||
url: 'php/server/query_graphql.php',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({
|
||||
query: `
|
||||
query devices($options: PageQueryOptionsInput) {
|
||||
devices(options: $options) {
|
||||
devices {
|
||||
devLastIP
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
options: {
|
||||
status: "all_devices"
|
||||
}
|
||||
}
|
||||
}),
|
||||
success: function(response) {
|
||||
|
||||
console.log(response);
|
||||
|
||||
const usedIps = (response?.devices?.devices || [])
|
||||
.map(d => d.devLastIP)
|
||||
.filter(ip => ip && ip.includes('.'));
|
||||
callback(usedIps);
|
||||
},
|
||||
error: function(err) {
|
||||
console.error("Error fetching IPs:", err);
|
||||
callback([]);
|
||||
}
|
||||
const newTarget = $(e.target).attr("href");
|
||||
loadTabContent(newTarget);
|
||||
});
|
||||
}
|
||||
|
||||
function renderAvailableIpsTable(allIps, usedIps) {
|
||||
const availableIps = allIps.filter(row => !usedIps.includes(row.ip));
|
||||
|
||||
console.log(allIps);
|
||||
console.log(usedIps);
|
||||
console.log(availableIps);
|
||||
|
||||
$('#availableIpsTable').DataTable({
|
||||
destroy: true,
|
||||
data: availableIps,
|
||||
columns: [
|
||||
{ title: getString("Gen_Subnet"), data: "subnet" },
|
||||
{ title: getString("Systeminfo_AvailableIps"), data: "ip" }
|
||||
],
|
||||
pageLength: 10
|
||||
});
|
||||
window.onload = function async() {
|
||||
initializeTabs();
|
||||
}
|
||||
|
||||
|
||||
// INIT
|
||||
$(document).ready(function() {
|
||||
fetchUsedIps(usedIps => {
|
||||
const allIps = inferNetworkRange(usedIps);
|
||||
renderAvailableIpsTable(allIps, usedIps);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
$('#networkTable').DataTable({
|
||||
searching: true,
|
||||
order: [[0, "desc"]],
|
||||
initComplete: function(settings, json) {
|
||||
hideSpinner(); // Called after the DataTable is fully initialized
|
||||
}
|
||||
});
|
||||
}, 200);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
316
front/systeminfoNetwork.php
Executable file
316
front/systeminfoNetwork.php
Executable file
@@ -0,0 +1,316 @@
|
||||
<?php
|
||||
//------------------------------------------------------------------------------
|
||||
// check if authenticated
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/server/db.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/language/lang.php';
|
||||
?>
|
||||
|
||||
|
||||
<?php
|
||||
|
||||
function getExternalIp() {
|
||||
$ch = curl_init('https://api64.ipify.org?format=json');
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||
$response = curl_exec($ch);
|
||||
if (curl_errno($ch)) {
|
||||
curl_close($ch);
|
||||
return 'ERROR: ' . curl_error($ch);
|
||||
}
|
||||
curl_close($ch);
|
||||
|
||||
$data = json_decode($response, true);
|
||||
if (isset($data['ip'])) {
|
||||
return htmlspecialchars($data['ip']);
|
||||
}
|
||||
return 'ERROR: Invalid response';
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// Network
|
||||
// ----------------------------------------------------------
|
||||
|
||||
//Network stats
|
||||
// Server IP
|
||||
|
||||
$externalIp = getExternalIp();
|
||||
|
||||
// Check Server name
|
||||
if (!empty(gethostname())) { $network_NAME = gethostname(); } else { $network_NAME = lang('Systeminfo_Network_Server_Name_String'); }
|
||||
// Check HTTPS
|
||||
if (isset($_SERVER['HTTPS'])) { $network_HTTPS = 'Yes (HTTPS)'; } else { $network_HTTPS = lang('Systeminfo_Network_Secure_Connection_String'); }
|
||||
// Check Query String
|
||||
if (empty($_SERVER['QUERY_STRING'])) { $network_QueryString = lang('Systeminfo_Network_Server_Query_String'); } else { $network_QueryString = $_SERVER['QUERY_STRING']; }
|
||||
// Check HTTP referer
|
||||
if (empty($_SERVER['HTTP_REFERER'])) { $network_referer = lang('Systeminfo_Network_HTTP_Referer_String'); } else { $network_referer = $_SERVER['HTTP_REFERER']; }
|
||||
//Network Hardware stat
|
||||
$network_result = shell_exec("cat /proc/net/dev | tail -n +3 | awk '{print $1}'");
|
||||
$net_interfaces = explode("\n", trim($network_result));
|
||||
$network_result = shell_exec("cat /proc/net/dev | tail -n +3 | awk '{print $2}'");
|
||||
$net_interfaces_rx = explode("\n", trim($network_result));
|
||||
$network_result = shell_exec("cat /proc/net/dev | tail -n +3 | awk '{print $10}'");
|
||||
$net_interfaces_tx = explode("\n", trim($network_result));
|
||||
|
||||
|
||||
// Network Hardware ----------------------------------------------------------
|
||||
echo '<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title sysinfo_headline"><i class="fa fa-sitemap fa-rotate-270"></i> ' . lang('Systeminfo_Network_Hardware') . '</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<table id="networkTable" class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>' . lang('Systeminfo_Network_Hardware_Interface_Name') . '</th>
|
||||
<th>' . lang('Systeminfo_Network_Hardware_Interface_Mask') . '</th>
|
||||
<th>' . lang('Systeminfo_Network_Hardware_Interface_RX') . '</th>
|
||||
<th>' . lang('Systeminfo_Network_Hardware_Interface_TX') . '</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>';
|
||||
|
||||
for ($x = 0; $x < sizeof($net_interfaces); $x++) {
|
||||
$interface_name = str_replace(':', '', $net_interfaces[$x]);
|
||||
$interface_ip_temp = exec('ip addr show ' . $interface_name . ' | grep "inet "');
|
||||
$interface_ip_arr = explode(' ', trim($interface_ip_temp));
|
||||
|
||||
if (!isset($interface_ip_arr[1])) {
|
||||
$interface_ip_arr[1] = '--';
|
||||
}
|
||||
|
||||
if ($net_interfaces_rx[$x] == 0) {
|
||||
$temp_rx = 0;
|
||||
} else {
|
||||
$temp_rx = number_format(round(($net_interfaces_rx[$x] / 1024 / 1024), 2), 2, ',', '.');
|
||||
}
|
||||
if ($net_interfaces_tx[$x] == 0) {
|
||||
$temp_tx = 0;
|
||||
} else {
|
||||
$temp_tx = number_format(round(($net_interfaces_tx[$x] / 1024 / 1024), 2), 2, ',', '.');
|
||||
}
|
||||
echo '<tr>';
|
||||
echo '<td>' . $interface_name . '</td>';
|
||||
echo '<td>' . $interface_ip_arr[1] . '</td>';
|
||||
echo '<td>' . $temp_rx . ' MB</td>';
|
||||
echo '<td>' . $temp_tx . ' MB</td>';
|
||||
echo '</tr>';
|
||||
}
|
||||
|
||||
echo ' </tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
// Available IPs ----------------------------------------------------------
|
||||
echo '<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title availableips_headline"><i class="fa fa-list"></i> ' . lang('Systeminfo_AvailableIps') . '</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<table id="availableIpsTable" class="display table table-bordered table-hover dataTable no-footer" style="width:100%"></table>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
|
||||
|
||||
// Network ----------------------------------------------------------
|
||||
echo '<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title sysinfo_headline"><i class="fas fa-ethernet"></i> ' . lang('Systeminfo_Network') . '</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_IP') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b" id="external-ip">' .$externalIp. '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_IP_Connection') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['REMOTE_ADDR'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_IP_Server') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['SERVER_ADDR'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Server_Name') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $network_NAME . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Connection_Port') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['REMOTE_PORT'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Secure_Connection') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $network_HTTPS . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Server_Version') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['SERVER_SOFTWARE'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_gerneral_a">' . lang('Systeminfo_Network_Request_URI') . '</div>
|
||||
<div class="col-sm-9 sysinfo_gerneral_b">' . $_SERVER['REQUEST_URI'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Server_Query') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $network_QueryString . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_HTTP_Host') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['HTTP_HOST'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_HTTP_Referer') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $network_referer . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_MIME') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['HTTP_ACCEPT'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Accept_Language') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['HTTP_ACCEPT_LANGUAGE'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Accept_Encoding') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['HTTP_ACCEPT_ENCODING'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Request_Method') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['REQUEST_METHOD'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_network_a">' . lang('Systeminfo_Network_Request_Time') . '</div>
|
||||
<div class="col-sm-9 sysinfo_network_b">' . $_SERVER['REQUEST_TIME'] . '</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
|
||||
?>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Available free IPS functionality
|
||||
function inferNetworkRange(usedIps) {
|
||||
if (!usedIps || usedIps.length === 0) return [];
|
||||
|
||||
const subnetMap = {};
|
||||
|
||||
// Group IPs by /24 subnet
|
||||
for (const ip of usedIps) {
|
||||
const parts = ip.split('.');
|
||||
if (parts.length !== 4) continue;
|
||||
const subnet = `${parts[0]}.${parts[1]}.${parts[2]}`;
|
||||
if (!subnetMap[subnet]) subnetMap[subnet] = [];
|
||||
subnetMap[subnet].push(ip);
|
||||
}
|
||||
|
||||
const result = [];
|
||||
|
||||
for (const [subnet, ips] of Object.entries(subnetMap)) {
|
||||
if (ips.length > 5) {
|
||||
for (let i = 2; i < 255; i++) {
|
||||
const ip = `${subnet}.${i}`;
|
||||
result.push({ subnet, ip });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function fetchUsedIps(callback) {
|
||||
$.ajax({
|
||||
url: 'php/server/query_graphql.php',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({
|
||||
query: `
|
||||
query devices($options: PageQueryOptionsInput) {
|
||||
devices(options: $options) {
|
||||
devices {
|
||||
devLastIP
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
options: {
|
||||
status: "all_devices"
|
||||
}
|
||||
}
|
||||
}),
|
||||
success: function(response) {
|
||||
|
||||
console.log(response);
|
||||
|
||||
const usedIps = (response?.devices?.devices || [])
|
||||
.map(d => d.devLastIP)
|
||||
.filter(ip => ip && ip.includes('.'));
|
||||
callback(usedIps);
|
||||
},
|
||||
error: function(err) {
|
||||
console.error("Error fetching IPs:", err);
|
||||
callback([]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderAvailableIpsTable(allIps, usedIps) {
|
||||
const availableIps = allIps.filter(row => !usedIps.includes(row.ip));
|
||||
|
||||
console.log(availableIps);
|
||||
|
||||
$('#availableIpsTable').DataTable({
|
||||
destroy: true,
|
||||
data: availableIps,
|
||||
columns: [
|
||||
{
|
||||
title: getString("Gen_Subnet"),
|
||||
data: "subnet"
|
||||
},
|
||||
{
|
||||
title: getString("Systeminfo_AvailableIps"),
|
||||
data: "ip",
|
||||
render: function (data, type, row, meta) {
|
||||
return `
|
||||
<span>${data}</span>
|
||||
<button class="copy-btn btn btn-sm btn-info ml-2 alignRight" data-text="${data}" title="${getString("Gen_CopyToClipboard")}" onclick="copyToClipboard(this)">
|
||||
<i class="fa-solid fa-copy"></i>
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
],
|
||||
pageLength: 10
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// INIT
|
||||
$(document).ready(function() {
|
||||
|
||||
// available IPs
|
||||
fetchUsedIps(usedIps => {
|
||||
const allIps = inferNetworkRange(usedIps);
|
||||
renderAvailableIpsTable(allIps, usedIps);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
// Available IPs datatable
|
||||
$('#networkTable').DataTable({
|
||||
searching: true,
|
||||
order: [[0, "desc"]],
|
||||
initComplete: function(settings, json) {
|
||||
hideSpinner(); // Called after the DataTable is fully initialized
|
||||
}
|
||||
});
|
||||
}, 200);
|
||||
});
|
||||
|
||||
</script>
|
||||
327
front/systeminfoServer.php
Executable file
327
front/systeminfoServer.php
Executable file
@@ -0,0 +1,327 @@
|
||||
<?php
|
||||
//------------------------------------------------------------------------------
|
||||
// check if authenticated
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/server/db.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/language/lang.php';
|
||||
?>
|
||||
|
||||
<?php
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// Server
|
||||
// ----------------------------------------------------------
|
||||
|
||||
//General stats
|
||||
// Date & Time
|
||||
$date = new DateTime();
|
||||
$formatted_date = $date->format('l, F j, Y H:i:s'); // Get date
|
||||
$formatted_date2 = $date->format('d/m/Y H:i:s'); // Get date2
|
||||
$formatted_date3 = $date->format('Y/m/d H:i:s'); // Get date3
|
||||
//System stats
|
||||
// OS-Version
|
||||
$os_version = '';
|
||||
// Raspbian
|
||||
if ($os_version == '') {$os_version = exec('cat /etc/os-release | grep PRETTY_NAME');}
|
||||
// Dietpi
|
||||
if ($os_version == '') {$os_version = exec('uname -o');}
|
||||
//$os_version_arr = explode("\n", trim($os_version));
|
||||
$stat['os_version'] = str_replace('"', '', str_replace('PRETTY_NAME=', '', $os_version));
|
||||
$stat['uptime'] = str_replace('up ', '', shell_exec("uptime -p")); // Get system uptime
|
||||
$system_namekernel = shell_exec("uname"); // Get system name kernel
|
||||
$system_namesystem = shell_exec("uname -o"); // Get name system
|
||||
$system_full = shell_exec("uname -a"); // Get system full
|
||||
$system_architecture = shell_exec("uname -m"); // Get system Architecture
|
||||
$load_average = sys_getloadavg(); // Get load average
|
||||
$system_process_count = shell_exec("ps -e --no-headers | wc -l"); // Count processes
|
||||
//Motherboard stats
|
||||
$motherboard_name = shell_exec('cat /sys/class/dmi/id/board_name'); // Get the Motherboard name
|
||||
$motherboard_manufactured = shell_exec('cat /sys/class/dmi/id/board_vendor'); // Get the Motherboard manufactured
|
||||
$motherboard_revision = shell_exec('cat /sys/class/dmi/id/board_version'); // Get the Motherboard revision
|
||||
$motherboard_bios = shell_exec('cat /sys/class/dmi/id/bios_version'); // Get the Motherboard BIOS
|
||||
$motherboard_biosdate = shell_exec('cat /sys/class/dmi/id/bios_date'); // Get the Motherboard BIOS date
|
||||
$motherboard_biosvendor = shell_exec('cat /sys/class/dmi/id/bios_vendor'); // Get the Motherboard BIOS vendor
|
||||
//CPU stats
|
||||
$prevVal = shell_exec("cat /proc/cpuinfo | grep processor");
|
||||
$prevArr = explode("\n", trim($prevVal));
|
||||
$stat['cpu'] = sizeof($prevArr);
|
||||
$cpu_result = shell_exec("cat /proc/cpuinfo | grep Model");
|
||||
$stat['cpu_model'] = strstr($cpu_result, "\n", true);
|
||||
$stat['cpu_model'] = str_replace(":", "", trim(str_replace("Model", "", $stat['cpu_model'])));
|
||||
if ($stat['cpu_model'] == '') {
|
||||
$cpu_result = shell_exec("cat /proc/cpuinfo | grep model\ name");
|
||||
$stat['cpu_model'] = strstr($cpu_result, "\n", true);
|
||||
$stat['cpu_model'] = str_replace(":", "", trim(str_replace("model name", "", $stat['cpu_model'])));
|
||||
}
|
||||
if (file_exists('/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq')) {
|
||||
// RaspbianOS
|
||||
$stat['cpu_frequ'] = exec('cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq') / 1000;
|
||||
} elseif (is_numeric(str_replace(',', '.', exec('lscpu | grep "MHz" | awk \'{print $3}\'')))) {
|
||||
// Ubuntu Server, DietPi event. others
|
||||
$stat['cpu_frequ'] = round(exec('lscpu | grep "MHz" | awk \'{print $3}\''), 0);
|
||||
} elseif (is_numeric(str_replace(',', '.', exec('lscpu | grep "max MHz" | awk \'{print $4}\'')))) {
|
||||
// RaspbianOS and event. others
|
||||
$stat['cpu_frequ'] = round(str_replace(',', '.', exec('lscpu | grep "max MHz" | awk \'{print $4}\'')), 0);
|
||||
} else {
|
||||
// Fallback
|
||||
$stat['cpu_frequ'] = "unknown";
|
||||
}
|
||||
$cpu_temp = shell_exec('cat /sys/class/hwmon/hwmon0/temp1_input'); // Get the CPU temperature
|
||||
$cpu_temp = floatval($cpu_temp) / 1000; // Convert the temperature to degrees Celsius
|
||||
$cpu_vendor = exec('cat /proc/cpuinfo | grep "vendor_id" | cut -d ":" -f 2' ); // Get the CPU vendor
|
||||
//Memory stats
|
||||
$total_memorykb = shell_exec("cat /proc/meminfo | grep MemTotal | awk '{print $2}'");
|
||||
$total_memorykb = trim($total_memorykb);
|
||||
$total_memorykb = number_format($total_memorykb, 0, '.', '.');
|
||||
$total_memorymb = shell_exec("cat /proc/meminfo | grep MemTotal | awk '{print $2/1024}'");
|
||||
$total_memorymb = trim($total_memorymb);
|
||||
$total_memorymb = number_format($total_memorymb, 0, '.', '.');
|
||||
$mem_used = round(memory_get_usage() / 1048576 * 100, 2);
|
||||
$memory_usage_percent = round(($mem_used / $total_memorymb), 2);
|
||||
|
||||
|
||||
|
||||
// General ----------------------------------------------------------
|
||||
echo '<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title sysinfo_headline"><i class="fa fa-info-circle"></i> ' . lang('Systeminfo_General') . '</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_general_a">' . lang('Systeminfo_General_Full_Date') . '</div>
|
||||
<div class="col-sm-9 sysinfo_general_b">' . $formatted_date . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_general_a">' . lang('Systeminfo_General_Date') . '</div>
|
||||
<div class="col-sm-9 sysinfo_general_b">' . $formatted_date2 . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_general_a">' . lang('Systeminfo_General_Date2') . '</div>
|
||||
<div class="col-sm-9 sysinfo_general_b">' . $formatted_date3 . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_general_a">' . lang('Systeminfo_General_TimeZone') . '</div>
|
||||
<div class="col-sm-9 sysinfo_general_b">' . $timeZone . '</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
|
||||
// Client ----------------------------------------------------------
|
||||
echo '<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title sysinfo_headline"><i class="fa fa-globe"></i> ' . lang('Systeminfo_This_Client') . '</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_client_a">' . lang('Systeminfo_Client_User_Agent') . '</div>
|
||||
<div class="col-sm-9 sysinfo_client_b">' . $_SERVER['HTTP_USER_AGENT'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_client_a">' . lang('Systeminfo_Client_Resolution') . '</div>
|
||||
<div class="col-sm-9 sysinfo_client_b" id="resolution"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
echo '<script>
|
||||
var ratio = window.devicePixelRatio || 1;
|
||||
var w = window.innerWidth;
|
||||
var h = window.innerHeight;
|
||||
var rw = window.innerWidth * ratio;
|
||||
var rh = window.innerHeight * ratio;
|
||||
|
||||
var resolutionDiv = document.getElementById("resolution");
|
||||
resolutionDiv.innerHTML = "Width: " + w + "px / Height: " + h + "px<br> " + "Width: " + rw + "px / Height: " + rh + "px (native)";
|
||||
</script>';
|
||||
|
||||
// System ----------------------------------------------------------
|
||||
echo '<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title sysinfo_headline"><i class="fa fa-computer"></i> ' . lang('Systeminfo_System') . '</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_Uptime') . '</div>
|
||||
<div class="col-sm-9 sysinfo_system_b">' . $stat['uptime'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_Kernel') . '</div>
|
||||
<div class="col-sm-9 sysinfo_system_b">' . $system_namekernel . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_System') . '</div>
|
||||
<div class="col-sm-9 sysinfo_system_b">' . $system_namesystem . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_OSVersion') . '</div>
|
||||
<div class="col-sm-9 sysinfo_system_b">' . $stat['os_version'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_Uname') . '</div>
|
||||
<div class="col-sm-9 sysinfo_system_b">' . $system_full . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_Architecture') . '</div>
|
||||
<div class="col-sm-9 sysinfo_system_b">' . $system_architecture . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_AVG') . '</div>
|
||||
<div class="col-sm-9 sysinfo_system_b">'. $load_average[0] .' '. $load_average[1] .' '. $load_average[2] .'</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_system_a">' . lang('Systeminfo_System_Running_Processes') . '</div>
|
||||
<div class="col-sm-9 sysinfo_system_b">' . $system_process_count . '</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
// Motherboard ----------------------------------------------------------
|
||||
echo '<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title sysinfo_headline"><i class="fa fa-laptop-code"></i> ' . lang('Systeminfo_Motherboard') . '</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_motherboard_a">' . lang('Systeminfo_Motherboard_Name') . '</div>
|
||||
<div class="col-sm-9 sysinfo_motherboard_b">' . $motherboard_name . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_motherboard_a">' . lang('Systeminfo_Motherboard_Manufactured') . '</div>
|
||||
<div class="col-sm-9 sysinfo_motherboard_b">' . $motherboard_manufactured . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_motherboard_a">' . lang('Systeminfo_Motherboard_Revision') . '</div>
|
||||
<div class="col-sm-9 sysinfo_motherboard_b">' . $motherboard_revision. '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_motherboard_a">' . lang('Systeminfo_Motherboard_BIOS') . '</div>
|
||||
<div class="col-sm-9 sysinfo_motherboard_b">' . $motherboard_bios . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_motherboard_a">' . lang('Systeminfo_Motherboard_BIOS_Date') . '</div>
|
||||
<div class="col-sm-9 sysinfo_motherboard_b">' . $motherboard_biosdate . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_motherboard_a">' . lang('Systeminfo_Motherboard_BIOS_Vendor') . '</div>
|
||||
<div class="col-sm-9 sysinfo_motherboard_b">' . $motherboard_biosvendor . '</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
// CPU ----------------------------------------------------------
|
||||
echo '<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title sysinfo_headline"><i class="fa fa-microchip"></i> ' . lang('Systeminfo_CPU') . '</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_cpu_a">' . lang('Systeminfo_CPU_Vendor') . '</div>
|
||||
<div class="col-sm-9 sysinfo_cpu_b">' . $cpu_vendor . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_cpu_a">' . lang('Systeminfo_CPU_Name') . '</div>
|
||||
<div class="col-sm-9 sysinfo_cpu_b">' . $stat['cpu_model'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_cpu_a">' . lang('Systeminfo_CPU_Cores') . '</div>
|
||||
<div class="col-sm-9 sysinfo_cpu_b">' . $stat['cpu'] . '</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_cpu_a">' . lang('Systeminfo_CPU_Speed') . '</div>
|
||||
<div class="col-sm-9 sysinfo_cpu_b">' . $stat['cpu_frequ'] . ' MHz</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_cpu_a">' . lang('Systeminfo_CPU_Temp') . '</div>
|
||||
<div class="col-sm-9 sysinfo_cpu_b">'. $cpu_temp .' °C</div>
|
||||
</div>';
|
||||
// Get the number of CPU cores
|
||||
$num_cpus = $stat['cpu'];
|
||||
$num_cpus = $num_cpus +2;
|
||||
|
||||
// Iterate over the CPU cores
|
||||
for ($i = 2,$a = 0; $i < $num_cpus; $i++,$a++) {
|
||||
|
||||
// Get the CPU temperature
|
||||
$cpu_tempxx = shell_exec('cat /sys/class/hwmon/hwmon0/temp' . $i . '_input');
|
||||
|
||||
// Convert the temperature to degrees Celsius
|
||||
$cpu_tempxx = floatval($cpu_tempxx) / 1000;
|
||||
|
||||
// Print the CPU temperature
|
||||
echo '<div class="row">
|
||||
<div class="col-sm-3 sysinfo_cpu_a">CPU Temp ' . $a . ':</div>
|
||||
<div class="col-sm-9 sysinfo_cpu_b">' . $cpu_tempxx . ' °C</div>
|
||||
</div>';
|
||||
}
|
||||
echo '
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
// Memory ----------------------------------------------------------
|
||||
echo '<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title sysinfo_headline"><i class="fa fa-memory"></i> ' . lang('Systeminfo_Memory') . '</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_memory_a">' . lang('Systeminfo_Memory_Usage_Percent') . '</div>
|
||||
<div class="col-sm-9 sysinfo_memory_b">' . $memory_usage_percent . ' %</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_memory_a">' . lang('Systeminfo_Memory_Usage') . '</div>
|
||||
<div class="col-sm-9 sysinfo_memory_b">' . $mem_used . ' MB / ' . $total_memorymb . ' MB</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 sysinfo_memory_a">' . lang('Systeminfo_Memory_Total_Memory') . '</div>
|
||||
<div class="col-sm-9 sysinfo_memory_b">' . $total_memorymb . ' MB (' . $total_memorykb . ' KB)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
|
||||
|
||||
// Services ----------------------------------------------------------
|
||||
echo '<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title sysinfo_headline"><i class="fa fa-database"></i> ' . lang('Systeminfo_Services') . '</h3>
|
||||
</div>
|
||||
<div class="box-body">';
|
||||
echo '<div style="height: 300px; overflow: scroll;">';
|
||||
exec('systemctl --type=service --state=running', $running_services);
|
||||
echo '<table class="table table-bordered table-hover table-striped dataTable no-footer" style="margin-bottom: 10px;">';
|
||||
echo '<thead>
|
||||
<tr role="row">
|
||||
<th style="padding: 8px;">' . lang('Systeminfo_Services_Name') . '</th>
|
||||
<th style="padding: 8px;">' . lang('Systeminfo_Services_Description') . '</th>
|
||||
</tr>
|
||||
</thead>';
|
||||
$table_color = 'odd';
|
||||
for ($x = 0; $x < sizeof($running_services); $x++) {
|
||||
if (stristr($running_services[$x], '.service')) {
|
||||
$temp_services_arr = array_values(array_filter(explode(' ', trim($running_services[$x]))));
|
||||
$servives_name = $temp_services_arr[0];
|
||||
unset($temp_services_arr[0], $temp_services_arr[1], $temp_services_arr[2], $temp_services_arr[3]);
|
||||
$servives_description = implode(" ", $temp_services_arr);
|
||||
|
||||
if ($table_color == 'odd')
|
||||
{
|
||||
$table_color = 'even';
|
||||
} else
|
||||
{
|
||||
$table_color = 'odd';
|
||||
}
|
||||
echo '<tr class="' . $table_color . '"><td style="padding: 3px; padding-left: 10px;">' . substr($servives_name, 0, -8) . '</td><td style="padding: 3px; padding-left: 10px;">' . $servives_description . '</td></tr>';
|
||||
}
|
||||
}
|
||||
echo '</table>';
|
||||
echo '</div>';
|
||||
echo ' </div>
|
||||
</div>';
|
||||
|
||||
|
||||
?>
|
||||
|
||||
<script>
|
||||
hideSpinner();
|
||||
</script>
|
||||
124
front/systeminfoStorage.php
Executable file
124
front/systeminfoStorage.php
Executable file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
//------------------------------------------------------------------------------
|
||||
// check if authenticated
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/server/db.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/language/lang.php';
|
||||
?>
|
||||
|
||||
|
||||
|
||||
<?php
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// Storage
|
||||
// ----------------------------------------------------------
|
||||
|
||||
//HDD stats
|
||||
$hdd_result = shell_exec(" df -P | awk '{print $1}'");
|
||||
$hdd_devices = explode("\n", trim($hdd_result));
|
||||
$hdd_result = shell_exec(" df -P | awk '{print $2}'");
|
||||
$hdd_devices_total = explode("\n", trim($hdd_result));
|
||||
$hdd_result = shell_exec(" df -P | awk '{print $3}'");
|
||||
$hdd_devices_used = explode("\n", trim($hdd_result));
|
||||
$hdd_result = shell_exec(" df -P | awk '{print $4}'");
|
||||
$hdd_devices_free = explode("\n", trim($hdd_result));
|
||||
$hdd_result = shell_exec(" df -P | awk '{print $5}'");
|
||||
$hdd_devices_percent = explode("\n", trim($hdd_result));
|
||||
$hdd_result = shell_exec(" df -P | awk '{print $6}'");
|
||||
$hdd_devices_mount = explode("\n", trim($hdd_result));
|
||||
|
||||
// Storage ----------------------------------------------------------
|
||||
echo '<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title sysinfo_headline"><i class="fa fa-hdd"></i> ' . lang('Systeminfo_Storage') . '</h3>
|
||||
</div>
|
||||
<div class="box-body">';
|
||||
|
||||
$storage_lsblk = shell_exec("lsblk -io NAME,SIZE,TYPE,MOUNTPOINT,MODEL --list | tail -n +2 | awk '{print $1\"#\"$2\"#\"$3\"#\"$4\"#\"$5}'");
|
||||
$storage_lsblk_line = explode("\n", $storage_lsblk);
|
||||
$storage_lsblk_line = array_filter($storage_lsblk_line);
|
||||
|
||||
for ($x = 0; $x < sizeof($storage_lsblk_line); $x++) {
|
||||
$temp = array();
|
||||
$temp = explode("#", $storage_lsblk_line[$x]);
|
||||
$storage_lsblk_line[$x] = $temp;
|
||||
}
|
||||
|
||||
for ($x = 0; $x < sizeof($storage_lsblk_line); $x++) {
|
||||
echo '<div class="row">';
|
||||
if (preg_match('~[0-9]+~', $storage_lsblk_line[$x][0])) {
|
||||
echo '<div class="col-sm-4 sysinfo_storage_a">"' . lang('Systeminfo_Storage_Mount') . ' ' . $storage_lsblk_line[$x][3] . '"</div>';
|
||||
} else {
|
||||
echo '<div class="col-sm-4 sysinfo_storage_a">"' . str_replace('_', ' ', $storage_lsblk_line[$x][3]) . '"</div>';
|
||||
}
|
||||
echo '<div class="col-sm-3 sysinfo_storage_b">' . lang('Systeminfo_Storage_Device') . ' /dev/' . $storage_lsblk_line[$x][0] . '</div>';
|
||||
echo '<div class="col-sm-2 sysinfo_storage_b">' . lang('Systeminfo_Storage_Size') . ' ' . $storage_lsblk_line[$x][1] . '</div>';
|
||||
echo '<div class="col-sm-2 sysinfo_storage_b">' . lang('Systeminfo_Storage_Type') . ' ' . $storage_lsblk_line[$x][2] . '</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
echo ' </div>
|
||||
</div>';
|
||||
|
||||
// Storage usage ----------------------------------------------------------
|
||||
echo '<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title sysinfo_headline"><i class="fa fa-hdd"></i> ' . lang('Systeminfo_Storage_Usage') . '</h3>
|
||||
</div>
|
||||
<div class="box-body">';
|
||||
for ($x = 0; $x < sizeof($hdd_devices); $x++) {
|
||||
if (stristr($hdd_devices[$x], '/dev/')) {
|
||||
if (!stristr($hdd_devices[$x], '/loop')) {
|
||||
if ($hdd_devices_total[$x] == 0 || $hdd_devices_total[$x] == '') {$temp_total = 0;} else { $temp_total = number_format(round(($hdd_devices_total[$x] / 1024 / 1024), 2), 2, ',', '.'); $temp_total = trim($temp_total);}
|
||||
if ($hdd_devices_used[$x] == 0 || $hdd_devices_used[$x] == '') {$temp_used = 0;} else { $temp_used = number_format(round(($hdd_devices_used[$x] / 1024 / 1024), 2), 2, ',', '.'); $temp_used = trim($temp_total);}
|
||||
if ($hdd_devices_free[$x] == 0 || $hdd_devices_free[$x] == '') {$temp_free = 0;} else { $temp_free = number_format(round(($hdd_devices_free[$x] / 1024 / 1024), 2), 2, ',', '.'); $temp_free = trim($temp_total);}
|
||||
echo '<div class="row">';
|
||||
echo '<div class="col-sm-4 sysinfo_storage_usage_a">"' . lang('Systeminfo_Storage_Usage_Mount') . ' ' . $hdd_devices_mount[$x] . '"</div>';
|
||||
echo '<div class="col-sm-2 sysinfo_storage_usage_b">' . lang('Systeminfo_Storage_Usage_Total') . ' ' . $temp_total . ' GB</div>';
|
||||
echo '<div class="col-sm-3 sysinfo_storage_usage_b">' . lang('Systeminfo_Storage_Usage_Used') . ' ' . $temp_used . ' GB (' . $hdd_devices_percent[$x]. ')</div>';
|
||||
echo '<div class="col-sm-2 sysinfo_storage_usage_b">' . lang('Systeminfo_Storage_Usage_Free') . ' ' . $temp_free . ' GB</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
#echo '<br>' . $lang['SysInfo_storage_note'];
|
||||
echo ' </div>
|
||||
</div>';
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// USB devices
|
||||
// ----------------------------------------------------------
|
||||
|
||||
$usb_result = shell_exec("lsusb");
|
||||
$usb_devices_mount = explode("\n", trim($usb_result));
|
||||
|
||||
echo '<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title sysinfo_headline"><i class="fab fa-usb"></i> ' . lang('Systeminfo_USB_Devices') . '</h3>
|
||||
</div>
|
||||
<div class="box-body">';
|
||||
echo ' <table class="table table-bordered table-hover table-striped dataTable no-footer" style="margin-bottom: 10px;">';
|
||||
|
||||
$table_color = 'odd';
|
||||
sort($usb_devices_mount);
|
||||
for ($x = 0; $x < sizeof($usb_devices_mount); $x++) {
|
||||
$cut_pos = strpos($usb_devices_mount[$x], ':');
|
||||
$usb_bus = substr($usb_devices_mount[$x], 0, $cut_pos);
|
||||
$usb_dev = substr($usb_devices_mount[$x], $cut_pos + 1);
|
||||
|
||||
if ($table_color == 'odd') {$table_color = 'even';} else { $table_color = 'odd';}
|
||||
echo '<tr class="' . $table_color . '"><td style="padding: 3px; padding-left: 10px; width: 150px;"><b>' . str_replace('Device', 'Dev.', $usb_bus) . '</b></td><td style="padding: 3px; padding-left: 10px;">' . $usb_dev . '</td></tr>';
|
||||
}
|
||||
echo ' </table>';
|
||||
echo ' </div>
|
||||
</div>';
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
echo '<br>';
|
||||
|
||||
?>
|
||||
|
||||
<script>
|
||||
hideSpinner();
|
||||
</script>
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
require 'php/templates/header.php';
|
||||
require 'php/templates/notification.php';
|
||||
require 'php/templates/modals.php';
|
||||
?>
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
|
||||
|
||||
@@ -30,5 +30,5 @@ source myenv/bin/activate
|
||||
update-alternatives --install /usr/bin/python python /usr/bin/python3 10
|
||||
|
||||
# install packages thru pip3
|
||||
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 git+https://github.com/foreign-sub/aiofreepybox.git
|
||||
pip3 install openwrt-luci-rpc asusrouter asyncio aiohttp graphene flask flask-cors unifi-sm-api 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
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ nav:
|
||||
- Debugging Tips: DEBUG_TIPS.md
|
||||
- Debugging GraphQL: DEBUG_GRAPHQL.md
|
||||
- Debugging Invalid JSON: DEBUG_INVALID_JSON.md
|
||||
- Debugging PHP: DEBUG_PHP.md
|
||||
- Debugging Plugins: DEBUG_PLUGINS.md
|
||||
- Debugging Web UI Port: WEB_UI_PORT_DEBUG.md
|
||||
- Debugging Workflows: WORKFLOWS_DEBUGGING.md
|
||||
@@ -75,6 +76,7 @@ nav:
|
||||
- Database: DATABASE.md
|
||||
- Settings: SETTINGS_SYSTEM.md
|
||||
- Versions: VERSIONS.md
|
||||
- Icon and Type guessing: DEVICE_HEURISTICS.md
|
||||
- Integrations:
|
||||
- Webhook Secret: WEBHOOK_SECRET.md
|
||||
- API: API.md
|
||||
|
||||
@@ -13,7 +13,7 @@ from models.user_events_queue_instance import UserEventsQueueInstance
|
||||
from messaging.in_app import write_notification
|
||||
|
||||
# Import the start_server function
|
||||
from graphql_server.graphql_server_start import start_server
|
||||
from api_server.api_server_start import start_server
|
||||
|
||||
apiEndpoints = []
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import threading
|
||||
from flask import Flask, request, jsonify
|
||||
from flask import Flask, request, jsonify, Response
|
||||
from flask_cors import CORS
|
||||
from .graphql_schema import devicesSchema
|
||||
from .prometheus_metrics import getMetricStats
|
||||
from graphene import Schema
|
||||
import sys
|
||||
|
||||
@@ -15,9 +17,11 @@ from messaging.in_app import write_notification
|
||||
|
||||
# Flask application
|
||||
app = Flask(__name__)
|
||||
CORS(app, resources={r"/metrics": {"origins": "*"}}, supports_credentials=True, allow_headers=["Authorization"])
|
||||
|
||||
# Retrieve API token and port
|
||||
graphql_port_value = get_setting_value("GRAPHQL_PORT")
|
||||
# --------------------------
|
||||
# GraphQL Endpoints
|
||||
# --------------------------
|
||||
|
||||
# Endpoint used when accessed via browser
|
||||
@app.route("/graphql", methods=["GET"])
|
||||
@@ -29,10 +33,7 @@ def graphql_debug():
|
||||
@app.route("/graphql", methods=["POST"])
|
||||
def graphql_endpoint():
|
||||
# Check for API token in headers
|
||||
incoming_header_token = request.headers.get("Authorization")
|
||||
api_token_value = get_setting_value("API_TOKEN")
|
||||
|
||||
if incoming_header_token != f"Bearer {api_token_value}":
|
||||
if not is_authorized():
|
||||
msg = '[graphql_server] Unauthorized access attempt - make sure your GRAPHQL_PORT and API_TOKEN settings are correct.'
|
||||
mylog('verbose', [msg])
|
||||
return jsonify({"error": msg}), 401
|
||||
@@ -47,6 +48,32 @@ def graphql_endpoint():
|
||||
# Return the result as JSON
|
||||
return jsonify(result.data)
|
||||
|
||||
# --------------------------
|
||||
# Prometheus /metrics Endpoint
|
||||
# --------------------------
|
||||
|
||||
@app.route("/metrics")
|
||||
def metrics():
|
||||
|
||||
# Check for API token in headers
|
||||
if not is_authorized():
|
||||
msg = '[metrics] Unauthorized access attempt - make sure your GRAPHQL_PORT and API_TOKEN settings are correct.'
|
||||
mylog('verbose', [msg])
|
||||
return jsonify({"error": msg}), 401
|
||||
|
||||
|
||||
# Return Prometheus metrics as plain text
|
||||
return Response(getMetricStats(), mimetype="text/plain")
|
||||
|
||||
|
||||
# --------------------------
|
||||
# Background Server Start
|
||||
# --------------------------
|
||||
def is_authorized():
|
||||
token = request.headers.get("Authorization")
|
||||
return token == f"Bearer {get_setting_value('API_TOKEN')}"
|
||||
|
||||
|
||||
def start_server(graphql_port, app_state):
|
||||
"""Start the GraphQL server in a background thread."""
|
||||
|
||||
@@ -197,7 +197,7 @@ class Query(ObjectType):
|
||||
searchable_fields = [
|
||||
"devName", "devMac", "devOwner", "devType", "devVendor", "devLastIP",
|
||||
"devGroup", "devComments", "devLocation", "devStatus", "devSSID",
|
||||
"devSite", "devSourcePlugin", "devSyncHubNode", "devFQDN", "devParentRelType"
|
||||
"devSite", "devSourcePlugin", "devSyncHubNode", "devFQDN", "devParentRelType", "devParentMAC"
|
||||
]
|
||||
|
||||
search_term = options.search.lower()
|
||||
76
server/api_server/prometheus_metrics.py
Executable file
76
server/api_server/prometheus_metrics.py
Executable file
@@ -0,0 +1,76 @@
|
||||
import json
|
||||
import sys
|
||||
|
||||
# Register NetAlertX directories
|
||||
INSTALL_PATH = "/app"
|
||||
sys.path.extend([f"{INSTALL_PATH}/server"])
|
||||
|
||||
from logger import mylog
|
||||
from const import apiPath
|
||||
from helper import is_random_mac, get_number_of_children, format_ip_long, get_setting_value
|
||||
|
||||
def escape_label_value(val):
|
||||
"""
|
||||
Escape special characters for Prometheus labels.
|
||||
"""
|
||||
return str(val).replace('\\', '\\\\').replace('\n', '\\n').replace('"', '\\"')
|
||||
|
||||
# Define a base URL with the user's home directory
|
||||
folder = apiPath
|
||||
|
||||
def getMetricStats():
|
||||
output = []
|
||||
|
||||
# 1. Dashboard totals
|
||||
try:
|
||||
with open(folder + 'table_devices_tiles.json', 'r') as f:
|
||||
tiles_data = json.load(f)["data"]
|
||||
|
||||
if isinstance(tiles_data, list) and tiles_data:
|
||||
totals = tiles_data[0]
|
||||
output.append(f'netalertx_connected_devices {totals.get("connected", 0)}')
|
||||
output.append(f'netalertx_offline_devices {totals.get("offline", 0)}')
|
||||
output.append(f'netalertx_down_devices {totals.get("down", 0)}')
|
||||
output.append(f'netalertx_new_devices {totals.get("new", 0)}')
|
||||
output.append(f'netalertx_archived_devices {totals.get("archived", 0)}')
|
||||
output.append(f'netalertx_favorite_devices {totals.get("favorites", 0)}')
|
||||
output.append(f'netalertx_my_devices {totals.get("my_devices", 0)}')
|
||||
else:
|
||||
output.append("# Unexpected format in table_devices_tiles.json")
|
||||
except (FileNotFoundError, json.JSONDecodeError) as e:
|
||||
mylog('none', f'[metrics] Error loading tiles data: {e}')
|
||||
output.append(f"# Error loading tiles data: {e}")
|
||||
except Exception as e:
|
||||
output.append(f"# General error loading dashboard totals: {e}")
|
||||
|
||||
# 2. Device-level metrics
|
||||
try:
|
||||
with open(folder + 'table_devices.json', 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
devices = data.get("data", [])
|
||||
|
||||
for row in devices:
|
||||
name = escape_label_value(row.get("devName", "unknown"))
|
||||
mac = escape_label_value(row.get("devMac", "unknown"))
|
||||
ip = escape_label_value(row.get("devLastIP", "unknown"))
|
||||
vendor = escape_label_value(row.get("devVendor", "unknown"))
|
||||
first_conn = escape_label_value(row.get("devFirstConnection", "unknown"))
|
||||
last_conn = escape_label_value(row.get("devLastConnection", "unknown"))
|
||||
dev_type = escape_label_value(row.get("devType", "unknown"))
|
||||
raw_status = row.get("devStatus", "Unknown")
|
||||
dev_status = raw_status.replace("-", "").capitalize()
|
||||
|
||||
output.append(
|
||||
f'netalertx_device_status{{device="{name}", mac="{mac}", ip="{ip}", vendor="{vendor}", '
|
||||
f'first_connection="{first_conn}", last_connection="{last_conn}", dev_type="{dev_type}", '
|
||||
f'device_status="{dev_status}"}} 1'
|
||||
)
|
||||
|
||||
except (FileNotFoundError, json.JSONDecodeError) as e:
|
||||
mylog('none', f'[metrics] Error loading devices data: {e}')
|
||||
output.append(f"# Error loading devices data: {e}")
|
||||
except Exception as e:
|
||||
output.append(f"# General error processing device metrics: {e}")
|
||||
|
||||
return "\n".join(output) + "\n"
|
||||
@@ -667,7 +667,10 @@ def checkNewVersion():
|
||||
buildTimestamp = int(f.read().strip())
|
||||
|
||||
try:
|
||||
response = requests.get("https://api.github.com/repos/jokob-sk/NetAlertX/releases")
|
||||
response = requests.get(
|
||||
"https://api.github.com/repos/jokob-sk/NetAlertX/releases",
|
||||
timeout=5
|
||||
)
|
||||
response.raise_for_status() # Raise an exception for HTTP errors
|
||||
text = response.text
|
||||
except requests.exceptions.RequestException as e:
|
||||
|
||||
@@ -157,13 +157,13 @@ def importConfigs (db, all_plugins):
|
||||
# ----------------------------------------
|
||||
# ccd(key, default, config_dir, name, inputtype, options, group, events=[], desc = "", regex = "", setJsonMetadata = {}, overrideTemplate = {})
|
||||
|
||||
conf.LOADED_PLUGINS = ccd('LOADED_PLUGINS', [] , c_d, 'Loaded plugins', '{"dataType":"array", "elements": [{"elementType" : "select", "elementOptions" : [{"multiple":"true", "ordeable": "true"}] ,"transformers": []}]}', '[]', 'General')
|
||||
conf.LOADED_PLUGINS = ccd('LOADED_PLUGINS', [] , c_d, 'Loaded plugins', '{"dataType":"array","elements":[{"elementType":"select","elementOptions":[{"multiple":"true","ordeable":"true"}],"transformers":[]},{"elementType":"button","elementOptions":[{"sourceSuffixes":[]},{"separator":""},{"cssClasses":"col-xs-12"},{"onClick":"selectChange(this)"},{"getStringKey":"Gen_Change"}],"transformers":[]}]}', '[]', 'General')
|
||||
conf.DISCOVER_PLUGINS = ccd('DISCOVER_PLUGINS', True , c_d, 'Discover plugins', """{"dataType": "boolean","elements": [{"elementType": "input","elementOptions": [{ "type": "checkbox" }],"transformers": []}]}""", '[]', 'General')
|
||||
conf.SCAN_SUBNETS = ccd('SCAN_SUBNETS', ['192.168.1.0/24 --interface=eth1', '192.168.1.0/24 --interface=eth0'] , c_d, 'Subnets to scan', '''{"dataType": "array","elements": [{"elementType": "input","elementOptions": [{"placeholder": "192.168.1.0/24 --interface=eth1"},{"suffix": "_in"},{"cssClasses": "col-sm-10"},{"prefillValue": "null"}],"transformers": []},{"elementType": "button","elementOptions": [{"sourceSuffixes": ["_in"]},{"separator": ""},{"cssClasses": "col-xs-12"},{"onClick": "addList(this, false)"},{"getStringKey": "Gen_Add"}],"transformers": []},{"elementType": "select","elementHasInputValue": 1,"elementOptions": [{"multiple": "true"},{"readonly": "true"},{"editable": "true"}],"transformers": []},{"elementType": "button","elementOptions": [{"sourceSuffixes": []},{"separator": ""},{"cssClasses": "col-xs-6"},{"onClick": "removeAllOptions(this)"},{"getStringKey": "Gen_Remove_All"}],"transformers": []},{"elementType": "button","elementOptions": [{"sourceSuffixes": []},{"separator": ""},{"cssClasses": "col-xs-6"},{"onClick": "removeFromList(this)"},{"getStringKey": "Gen_Remove_Last"}],"transformers": []}]}''', '[]', 'General')
|
||||
conf.LOG_LEVEL = ccd('LOG_LEVEL', 'verbose' , c_d, 'Log verboseness', '{"dataType":"string", "elements": [{"elementType" : "select", "elementOptions" : [] ,"transformers": []}]}', "['none', 'minimal', 'verbose', 'debug', 'trace']", 'General')
|
||||
conf.TIMEZONE = ccd('TIMEZONE', default_tz , c_d, 'Time zone', '{"dataType":"string", "elements": [{"elementType" : "input", "elementOptions" : [] ,"transformers": []}]}', '[]', 'General')
|
||||
conf.PLUGINS_KEEP_HIST = ccd('PLUGINS_KEEP_HIST', 250 , c_d, 'Keep history entries', '{"dataType":"integer", "elements": [{"elementType" : "input", "elementOptions" : [{"type": "number"}] ,"transformers": []}]}', '[]', 'General')
|
||||
conf.REPORT_DASHBOARD_URL = ccd('REPORT_DASHBOARD_URL', 'http://127.0.0.1/' , c_d, 'NetAlertX URL', '{"dataType":"string", "elements": [{"elementType" : "input", "elementOptions" : [] ,"transformers": []}]}', '[]', 'General')
|
||||
conf.REPORT_DASHBOARD_URL = ccd('REPORT_DASHBOARD_URL', 'update_REPORT_DASHBOARD_URL_setting' , c_d, 'NetAlertX URL', '{"dataType":"string", "elements": [{"elementType" : "input", "elementOptions" : [] ,"transformers": []}]}', '[]', 'General')
|
||||
conf.DAYS_TO_KEEP_EVENTS = ccd('DAYS_TO_KEEP_EVENTS', 90 , c_d, 'Delete events days', '{"dataType":"integer", "elements": [{"elementType" : "input", "elementOptions" : [{"type": "number"}] ,"transformers": []}]}', '[]', 'General')
|
||||
conf.HRS_TO_KEEP_NEWDEV = ccd('HRS_TO_KEEP_NEWDEV', 0 , c_d, 'Keep new devices for', '{"dataType":"integer", "elements": [{"elementType" : "input", "elementOptions" : [{"type": "number"}] ,"transformers": []}]}', "[]", 'General')
|
||||
conf.HRS_TO_KEEP_OFFDEV = ccd('HRS_TO_KEEP_OFFDEV', 0 , c_d, 'Keep offline devices for', '{"dataType":"integer", "elements": [{"elementType" : "input", "elementOptions" : [{"type": "number"}] ,"transformers": []}]}', "[]", 'General')
|
||||
@@ -171,7 +171,7 @@ def importConfigs (db, all_plugins):
|
||||
conf.REFRESH_FQDN = ccd('REFRESH_FQDN', False , c_d, 'Refresh FQDN', """{"dataType": "boolean","elements": [{"elementType": "input","elementOptions": [{ "type": "checkbox" }],"transformers": []}]}""", '[]', 'General')
|
||||
conf.API_CUSTOM_SQL = ccd('API_CUSTOM_SQL', 'SELECT * FROM Devices WHERE devPresentLastScan = 0' , c_d, 'Custom endpoint', '{"dataType":"string", "elements": [{"elementType" : "input", "elementOptions" : [] ,"transformers": []}]}', '[]', 'General')
|
||||
conf.VERSION = ccd('VERSION', '' , c_d, 'Version', '{"dataType":"string", "elements": [{"elementType" : "input", "elementOptions" : [{ "readonly": "true" }] ,"transformers": []}]}', '', 'General')
|
||||
conf.NETWORK_DEVICE_TYPES = ccd('NETWORK_DEVICE_TYPES', ['AP', 'Gateway', 'Firewall', 'Hypervisor', 'Powerline', 'Switch', 'WLAN', 'PLC', 'Router','USB LAN Adapter', 'USB WIFI Adapter', 'Internet'] , c_d, 'Network device types', '{"dataType":"array","elements":[{"elementType":"input","elementOptions":[{"placeholder":"Enter value"},{"suffix":"_in"},{"cssClasses":"col-sm-10"},{"prefillValue":"null"}],"transformers":[]},{"elementType":"button","elementOptions":[{"sourceSuffixes":["_in"]},{"separator":""},{"cssClasses":"col-xs-12"},{"onClick":"addList(this,false)"},{"getStringKey":"Gen_Add"}],"transformers":[]},{"elementType":"select", "elementHasInputValue":1,"elementOptions":[{"multiple":"true"},{"readonly":"true"},{"editable":"true"}],"transformers":[]},{"elementType":"button","elementOptions":[{"sourceSuffixes":[]},{"separator":""},{"cssClasses":"col-xs-6"},{"onClick":"removeAllOptions(this)"},{"getStringKey":"Gen_Remove_All"}],"transformers":[]},{"elementType":"button","elementOptions":[{"sourceSuffixes":[]},{"separator":""},{"cssClasses":"col-xs-6"},{"onClick":"removeFromList(this)"},{"getStringKey":"Gen_Remove_Last"}],"transformers":[]}]}', '[]', 'General')
|
||||
conf.NETWORK_DEVICE_TYPES = ccd('NETWORK_DEVICE_TYPES', ['AP', 'Access Point', 'Gateway', 'Firewall', 'Hypervisor', 'Powerline', 'Switch', 'WLAN', 'PLC', 'Router','USB LAN Adapter', 'USB WIFI Adapter', 'Internet'] , c_d, 'Network device types', '{"dataType":"array","elements":[{"elementType":"input","elementOptions":[{"placeholder":"Enter value"},{"suffix":"_in"},{"cssClasses":"col-sm-10"},{"prefillValue":"null"}],"transformers":[]},{"elementType":"button","elementOptions":[{"sourceSuffixes":["_in"]},{"separator":""},{"cssClasses":"col-xs-12"},{"onClick":"addList(this,false)"},{"getStringKey":"Gen_Add"}],"transformers":[]},{"elementType":"select", "elementHasInputValue":1,"elementOptions":[{"multiple":"true"},{"readonly":"true"},{"editable":"true"}],"transformers":[]},{"elementType":"button","elementOptions":[{"sourceSuffixes":[]},{"separator":""},{"cssClasses":"col-xs-6"},{"onClick":"removeAllOptions(this)"},{"getStringKey":"Gen_Remove_All"}],"transformers":[]},{"elementType":"button","elementOptions":[{"sourceSuffixes":[]},{"separator":""},{"cssClasses":"col-xs-6"},{"onClick":"removeFromList(this)"},{"getStringKey":"Gen_Remove_Last"}],"transformers":[]}]}', '[]', 'General')
|
||||
conf.GRAPHQL_PORT = ccd('GRAPHQL_PORT', 20212 , c_d, 'GraphQL port', '{"dataType":"integer", "elements": [{"elementType" : "input", "elementOptions" : [{"type": "number"}] ,"transformers": []}]}', '[]', 'General')
|
||||
conf.API_TOKEN = ccd('API_TOKEN', 't_' + generate_random_string(20) , c_d, 'API token', '{"dataType": "string","elements": [{"elementType": "input","elementHasInputValue": 1,"elementOptions": [{ "cssClasses": "col-xs-12" }],"transformers": []},{"elementType": "button","elementOptions": [{ "getStringKey": "Gen_Generate" },{ "customParams": "API_TOKEN" },{ "onClick": "generateApiToken(this, 20)" },{ "cssClasses": "col-xs-12" }],"transformers": []}]}', '[]', 'General')
|
||||
|
||||
@@ -275,6 +275,15 @@ def importConfigs (db, all_plugins):
|
||||
# Save the user defined value into the object
|
||||
set["value"] = v
|
||||
|
||||
# Now check for popupForm inside elements → elementOptions
|
||||
elements = set.get("type", {}).get("elements", [])
|
||||
for element in elements:
|
||||
for option in element.get("elementOptions", []):
|
||||
if "popupForm" in option:
|
||||
for popup_entry in option["popupForm"]:
|
||||
popup_pref = key + "_popupform_" + popup_entry.get("function", "")
|
||||
stringSqlParams = collect_lang_strings(popup_entry, popup_pref, stringSqlParams)
|
||||
|
||||
# Collect settings related language strings
|
||||
# Creates an entry with key, for example ARPSCAN_CMD_name
|
||||
stringSqlParams = collect_lang_strings(set, pref + "_" + set["function"], stringSqlParams)
|
||||
|
||||
@@ -447,8 +447,8 @@ def create_new_devices (db):
|
||||
cur_MAC, cur_Name, cur_Vendor, cur_ScanMethod, cur_IP, cur_SyncHubNodeName, cur_NetworkNodeMAC, cur_PORT, cur_NetworkSite, cur_SSID, cur_Type = row
|
||||
|
||||
# Handle NoneType
|
||||
cur_Name = cur_Name.strip() if cur_Name else '(unknown)'
|
||||
cur_Type = cur_Type.strip() if cur_Type else get_setting_value("NEWDEV_devType")
|
||||
cur_Name = str(cur_Name).strip() if cur_Name else '(unknown)'
|
||||
cur_Type = str(cur_Type).strip() if cur_Type else get_setting_value("NEWDEV_devType")
|
||||
cur_NetworkNodeMAC = cur_NetworkNodeMAC.strip() if cur_NetworkNodeMAC else ''
|
||||
cur_NetworkNodeMAC = cur_NetworkNodeMAC if cur_NetworkNodeMAC and cur_MAC != "Internet" else (get_setting_value("NEWDEV_devParentMAC") if cur_MAC != "Internet" else "null")
|
||||
cur_SyncHubNodeName = cur_SyncHubNodeName if cur_SyncHubNodeName and cur_SyncHubNodeName != "null" else (get_setting_value("SYNC_node_name"))
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import sys
|
||||
import re
|
||||
import json
|
||||
import base64
|
||||
from pathlib import Path
|
||||
from typing import Optional, List, Tuple, Dict
|
||||
|
||||
# Register NetAlertX directories
|
||||
@@ -11,57 +14,167 @@ from const import *
|
||||
from logger import mylog
|
||||
from helper import timeNowTZ, get_setting_value
|
||||
|
||||
# Load MAC/device-type/icon rules from external file
|
||||
MAC_TYPE_ICON_PATH = Path(f"{INSTALL_PATH}/back/device_heuristics_rules.json")
|
||||
try:
|
||||
with open(MAC_TYPE_ICON_PATH, "r", encoding="utf-8") as f:
|
||||
MAC_TYPE_ICON_RULES = json.load(f)
|
||||
# Precompute base64-encoded icon_html once for each rule
|
||||
for rule in MAC_TYPE_ICON_RULES:
|
||||
icon_html = rule.get("icon_html", "")
|
||||
if icon_html:
|
||||
# encode icon_html to base64 string
|
||||
b64_bytes = base64.b64encode(icon_html.encode("utf-8"))
|
||||
rule["icon_base64"] = b64_bytes.decode("utf-8")
|
||||
else:
|
||||
rule["icon_base64"] = ""
|
||||
except Exception as e:
|
||||
MAC_TYPE_ICON_RULES = []
|
||||
mylog('none', f"[guess_device_attributes] Failed to load device_heuristics_rules.json: {e}")
|
||||
|
||||
# -----------------------------------------
|
||||
# Match device type and base64-encoded icon using MAC prefix and vendor patterns.
|
||||
def match_mac_and_vendor(
|
||||
mac_clean: str,
|
||||
vendor: str,
|
||||
default_type: str,
|
||||
default_icon: str
|
||||
) -> Tuple[str, str]:
|
||||
"""
|
||||
Match device type and base64-encoded icon using MAC prefix and vendor patterns.
|
||||
|
||||
Args:
|
||||
mac_clean: Cleaned MAC address (uppercase, no colons).
|
||||
vendor: Normalized vendor name (lowercase).
|
||||
default_type: Fallback device type.
|
||||
default_icon: Fallback base64 icon.
|
||||
|
||||
Returns:
|
||||
Tuple containing (device_type, base64_icon)
|
||||
"""
|
||||
for rule in MAC_TYPE_ICON_RULES:
|
||||
dev_type = rule.get("dev_type")
|
||||
base64_icon = rule.get("icon_base64", "")
|
||||
patterns = rule.get("matching_pattern", [])
|
||||
|
||||
for pattern in patterns:
|
||||
mac_prefix = pattern.get("mac_prefix", "").upper()
|
||||
vendor_pattern = pattern.get("vendor", "").lower()
|
||||
|
||||
if mac_clean.startswith(mac_prefix):
|
||||
if not vendor_pattern or vendor_pattern in vendor:
|
||||
|
||||
mylog('debug', f"[guess_device_attributes] Matched via MAC+Vendor")
|
||||
|
||||
type_ = dev_type
|
||||
icon = base64_icon or default_icon
|
||||
return type_, icon
|
||||
|
||||
return default_type, default_icon
|
||||
|
||||
# ---------------------------------------------------
|
||||
# Match device type and base64-encoded icon using vendor patterns.
|
||||
def match_vendor(
|
||||
vendor: str,
|
||||
default_type: str,
|
||||
default_icon: str
|
||||
) -> Tuple[str, str]:
|
||||
|
||||
vendor_lc = vendor.lower()
|
||||
|
||||
for rule in MAC_TYPE_ICON_RULES:
|
||||
dev_type = rule.get("dev_type")
|
||||
base64_icon = rule.get("icon_base64", "")
|
||||
patterns = rule.get("matching_pattern", [])
|
||||
|
||||
for pattern in patterns:
|
||||
# Only apply fallback when no MAC prefix is specified
|
||||
mac_prefix = pattern.get("mac_prefix", "")
|
||||
vendor_pattern = pattern.get("vendor", "").lower()
|
||||
|
||||
if vendor_pattern and vendor_pattern in vendor_lc:
|
||||
|
||||
mylog('debug', f"[guess_device_attributes] Matched via Vendor")
|
||||
|
||||
icon = base64_icon or default_icon
|
||||
|
||||
return dev_type, icon
|
||||
|
||||
return default_type, default_icon
|
||||
|
||||
# ---------------------------------------------------
|
||||
# Match device type and base64-encoded icon using name patterns.
|
||||
def match_name(
|
||||
name: str,
|
||||
default_type: str,
|
||||
default_icon: str
|
||||
) -> Tuple[str, str]:
|
||||
"""
|
||||
Match device type and base64-encoded icon using name patterns from global MAC_TYPE_ICON_RULES.
|
||||
|
||||
Args:
|
||||
name: Normalized device name (lowercase).
|
||||
default_type: Fallback device type.
|
||||
default_icon: Fallback base64 icon.
|
||||
|
||||
Returns:
|
||||
Tuple containing (device_type, base64_icon)
|
||||
"""
|
||||
name_lower = name.lower() if name else ""
|
||||
|
||||
for rule in MAC_TYPE_ICON_RULES:
|
||||
dev_type = rule.get("dev_type")
|
||||
base64_icon = rule.get("icon_base64", "")
|
||||
name_patterns = rule.get("name_pattern", [])
|
||||
|
||||
for pattern in name_patterns:
|
||||
# Use regex search to allow pattern substrings
|
||||
if re.search(pattern, name_lower, re.IGNORECASE):
|
||||
|
||||
mylog('debug', f"[guess_device_attributes] Matched via Name")
|
||||
|
||||
type_ = dev_type
|
||||
icon = base64_icon or default_icon
|
||||
return type_, icon
|
||||
|
||||
return default_type, default_icon
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Base64 encoded HTML strings for FontAwesome icons, now with an extended icons dictionary for broader device coverage
|
||||
ICONS = {
|
||||
"globe": "PGkgY2xhc3M9ImZhcyBmYS1nbG9iZSI+PC9pPg==", # Internet or global network
|
||||
"phone": "PGkgY2xhc3M9ImZhcyBmYS1tb2JpbGUtYWx0Ij48L2k+", # Smartphone
|
||||
"laptop": "PGkgY2xhc3M9ImZhIGZhLWxhcHRvcCI+PC9pPg==", # Laptop
|
||||
"printer": "PGkgY2xhc3M9ImZhIGZhLXByaW50ZXIiPjwvaT4=", # Printer
|
||||
"router": "PGkgY2xhc3M9ImZhcyBmYS1yYW5kb20iPjwvaT4=", # Router or network switch
|
||||
"tv": "PGkgY2xhc3M9ImZhIGZhLXR2Ij48L2k+", # Television
|
||||
"desktop": "PGkgY2xhc3M9ImZhIGZhLWRlc2t0b3AiPjwvaT4=", # Desktop PC
|
||||
"tablet": "PGkgY2xhc3M9ImZhIGZhLXRhYmxldCI+PC9pPg==", # Tablet
|
||||
"watch": "PGkgY2xhc3M9ImZhcyBmYS1jbG9jayI+PC9pPg==", # Fallback to clock since smartwatch is nonfree in FontAwesome
|
||||
"camera": "PGkgY2xhc3M9ImZhIGZhLWNhbWVyYSI+PC9pPg==", # Camera or webcam
|
||||
"home": "PGkgY2xhc3M9ImZhIGZhLWhvbWUiPjwvaT4=", # Smart home device
|
||||
"apple": "PGkgY2xhc3M9ImZhYiBmYS1hcHBsZSI+PC9pPg==", # Apple device
|
||||
"ethernet": "PGkgY2xhc3M9ImZhcyBmYS1uZXR3b3JrLXdpcmVkIj48L2k+", # Free alternative for ethernet icon in FontAwesome
|
||||
"google": "PGkgY2xhc3M9ImZhYiBmYS1nb29nbGUiPjwvaT4=", # Google device
|
||||
"raspberry": "PGkgY2xhc3M9ImZhYiBmYS1yYXNwYmVycnktcGkiPjwvaT4=", # Raspberry Pi
|
||||
"microchip": "PGkgY2xhc3M9ImZhcyBmYS1taWNyb2NoaXAiPjwvaT4=", # IoT or embedded device
|
||||
"server": "PGkgY2xhc3M9ImZhcyBmYS1zZXJ2ZXIiPjwvaT4=", # Server
|
||||
"gamepad": "PGkgY2xhc3M9ImZhcyBmYS1nYW1lcGFkIj48L2k+", # Gaming console
|
||||
"lightbulb": "PGkgY2xhc3M9ImZhcyBmYS1saWdodGJ1bGIiPjwvaT4=", # Smart light
|
||||
"speaker": "PGkgY2xhc3M9ImZhcyBmYS12b2x1bWUtdXAiPjwvaT4=", # Free speaker alt icon for smart speakers in FontAwesome
|
||||
"lock": "PGkgY2xhc3M9ImZhcyBmYS1sb2NrIj48L2k+", # Security device
|
||||
}
|
||||
#
|
||||
def match_ip(
|
||||
ip: str,
|
||||
default_type: str,
|
||||
default_icon: str
|
||||
) -> Tuple[str, str]:
|
||||
"""
|
||||
Match device type and base64-encoded icon using IP regex patterns from global JSON.
|
||||
|
||||
# Extended device types for comprehensive classification
|
||||
DEVICE_TYPES = {
|
||||
"Internet": "Internet Gateway",
|
||||
"Phone": "Smartphone",
|
||||
"Laptop": "Laptop",
|
||||
"Printer": "Printer",
|
||||
"Router": "Router",
|
||||
"TV": "Television",
|
||||
"Desktop": "Desktop PC",
|
||||
"Tablet": "Tablet",
|
||||
"Smartwatch": "Smartwatch",
|
||||
"Camera": "Camera",
|
||||
"SmartHome": "Smart Home Device",
|
||||
"Server": "Server",
|
||||
"GamingConsole": "Gaming Console",
|
||||
"IoT": "IoT Device",
|
||||
"NetworkSwitch": "Network Switch",
|
||||
"AccessPoint": "Access Point",
|
||||
"SmartLight": "Smart Light",
|
||||
"SmartSpeaker": "Smart Speaker",
|
||||
"SecurityDevice": "Security Device",
|
||||
"Unknown": "Unknown Device",
|
||||
}
|
||||
Args:
|
||||
ip: Device IP address as string.
|
||||
default_type: Fallback device type.
|
||||
default_icon: Fallback base64 icon.
|
||||
|
||||
Returns:
|
||||
Tuple containing (device_type, base64_icon)
|
||||
"""
|
||||
if not ip:
|
||||
return default_type, default_icon
|
||||
|
||||
for rule in MAC_TYPE_ICON_RULES:
|
||||
ip_patterns = rule.get("ip_pattern", [])
|
||||
dev_type = rule.get("dev_type")
|
||||
base64_icon = rule.get("icon_base64", "")
|
||||
|
||||
for pattern in ip_patterns:
|
||||
if re.match(pattern, ip):
|
||||
|
||||
mylog('debug', f"[guess_device_attributes] Matched via IP")
|
||||
|
||||
type_ = dev_type
|
||||
icon = base64_icon or default_icon
|
||||
return type_, icon
|
||||
|
||||
return default_type, default_icon
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Guess device attributes such as type of device and associated device icon
|
||||
@@ -72,197 +185,46 @@ def guess_device_attributes(
|
||||
name: Optional[str],
|
||||
default_icon: str,
|
||||
default_type: str
|
||||
) -> Tuple[str, str]:
|
||||
"""
|
||||
Guess the appropriate FontAwesome icon and device type based on device attributes.
|
||||
|
||||
Args:
|
||||
vendor: Device vendor name.
|
||||
mac: Device MAC address.
|
||||
ip: Device IP address.
|
||||
name: Device name.
|
||||
default_icon: Default icon to return if no match is found.
|
||||
default_type: Default type to return if no match is found.
|
||||
|
||||
Returns:
|
||||
Tuple[str, str]: A tuple containing the guessed icon (Base64-encoded HTML string)
|
||||
and the guessed device type (string).
|
||||
"""
|
||||
) -> Tuple[str, str]:
|
||||
mylog('debug', f"[guess_device_attributes] Guessing attributes for (vendor|mac|ip|name): ('{vendor}'|'{mac}'|'{ip}'|'{name}')")
|
||||
# Normalize inputs
|
||||
|
||||
# --- Normalize inputs ---
|
||||
vendor = str(vendor).lower().strip() if vendor else "unknown"
|
||||
mac = str(mac).upper().strip() if mac else "00:00:00:00:00:00"
|
||||
ip = str(ip).strip() if ip else "169.254.0.0" # APIPA address for unknown IPs per RFC 3927
|
||||
ip = str(ip).strip() if ip else "169.254.0.0"
|
||||
name = str(name).lower().strip() if name else "(unknown)"
|
||||
mac_clean = mac.replace(':', '').replace('-', '').upper()
|
||||
|
||||
# --- Icon Guessing Logic ---
|
||||
if mac == "INTERNET":
|
||||
icon = ICONS.get("globe", default_icon)
|
||||
else:
|
||||
# Vendor-based icon guessing
|
||||
icon_vendor_patterns = {
|
||||
"apple": "apple",
|
||||
"samsung|motorola|xiaomi|huawei": "phone",
|
||||
"dell|lenovo|asus|acer": "laptop",
|
||||
"hp|epson|canon|brother": "printer",
|
||||
"cisco|ubiquiti|netgear|tp-link|d-link|mikrotik": "router",
|
||||
"lg|samsung electronics|sony|vizio": "tv",
|
||||
"raspberry pi": "raspberry",
|
||||
"google": "google",
|
||||
"espressif|particle": "microchip",
|
||||
"intel|amd": "desktop",
|
||||
"amazon": "speaker",
|
||||
"philips hue|lifx": "lightbulb",
|
||||
"aruba|meraki": "ethernet",
|
||||
"qnap|synology": "server",
|
||||
"nintendo|sony interactive|microsoft": "gamepad",
|
||||
"ring|blink|arlo": "camera",
|
||||
"nest": "home",
|
||||
}
|
||||
for pattern, icon_key in icon_vendor_patterns.items():
|
||||
if re.search(pattern, vendor, re.IGNORECASE):
|
||||
icon = ICONS.get(icon_key, default_icon)
|
||||
break
|
||||
else:
|
||||
# MAC-based icon guessing
|
||||
mac_clean = mac.replace(':', '').replace('-', '').upper()
|
||||
icon_mac_patterns = {
|
||||
"001A79|B0BE83|BC926B": "apple",
|
||||
"001B63|BC4C4C": "tablet",
|
||||
"74ACB9|002468": "ethernet",
|
||||
"B827EB": "raspberry",
|
||||
"001422|001874": "desktop",
|
||||
"001CBF|002186": "server",
|
||||
}
|
||||
for pattern_str, icon_key in icon_mac_patterns.items():
|
||||
patterns = [p.replace(':', '').replace('-', '').upper() for p in pattern_str.split('|')]
|
||||
if any(mac_clean.startswith(p) for p in patterns):
|
||||
icon = ICONS.get(icon_key, default_icon)
|
||||
break
|
||||
else:
|
||||
# Name-based icon guessing
|
||||
icon_name_patterns = {
|
||||
"iphone|ipad|macbook|imac": "apple",
|
||||
"pixel|galaxy|redmi": "phone",
|
||||
"laptop|notebook": "laptop",
|
||||
"printer|print": "printer",
|
||||
"router|gateway|ap|access[ -]?point": "router",
|
||||
"tv|television|smarttv": "tv",
|
||||
"desktop|pc|computer": "desktop",
|
||||
"tablet|pad": "tablet",
|
||||
"watch|wear": "watch",
|
||||
"camera|cam|webcam": "camera",
|
||||
"echo|alexa|dot": "speaker",
|
||||
"hue|lifx|bulb": "lightbulb",
|
||||
"server|nas": "server",
|
||||
"playstation|xbox|switch": "gamepad",
|
||||
"raspberry|pi": "raspberry",
|
||||
"google|chromecast|nest": "google",
|
||||
"doorbell|lock|security": "lock",
|
||||
}
|
||||
for pattern, icon_key in icon_name_patterns.items():
|
||||
if re.search(pattern, name, re.IGNORECASE):
|
||||
icon = ICONS.get(icon_key, default_icon)
|
||||
break
|
||||
else:
|
||||
# IP-based icon guessing
|
||||
icon_ip_patterns = {
|
||||
r"^192\.168\.[0-1]\.1$": "router",
|
||||
r"^10\.0\.0\.1$": "router",
|
||||
r"^192\.168\.[0-1]\.[2-9]$": "desktop",
|
||||
r"^192\.168\.[0-1]\.1\d{2}$": "phone",
|
||||
}
|
||||
for pattern, icon_key in icon_ip_patterns.items():
|
||||
if re.match(pattern, ip):
|
||||
icon = ICONS.get(icon_key, default_icon)
|
||||
break
|
||||
else:
|
||||
icon = default_icon
|
||||
# # Internet shortcut
|
||||
# if mac == "INTERNET":
|
||||
# return ICONS.get("globe", default_icon), DEVICE_TYPES.get("Internet", default_type)
|
||||
|
||||
# --- Type Guessing Logic ---
|
||||
if mac == "INTERNET":
|
||||
type_ = DEVICE_TYPES.get("Internet", default_type)
|
||||
else:
|
||||
# Vendor-based type guessing
|
||||
type_vendor_patterns = {
|
||||
"apple|samsung|motorola|xiaomi|huawei": "Phone",
|
||||
"dell|lenovo|asus|acer|hp": "Laptop",
|
||||
"epson|canon|brother": "Printer",
|
||||
"cisco|ubiquiti|netgear|tp-link|d-link|mikrotik|aruba|meraki": "Router",
|
||||
"lg|samsung electronics|sony|vizio": "TV",
|
||||
"raspberry pi": "IoT",
|
||||
"google|nest": "SmartHome",
|
||||
"espressif|particle": "IoT",
|
||||
"intel|amd": "Desktop",
|
||||
"amazon": "SmartSpeaker",
|
||||
"philips hue|lifx": "SmartLight",
|
||||
"qnap|synology": "Server",
|
||||
"nintendo|sony interactive|microsoft": "GamingConsole",
|
||||
"ring|blink|arlo": "Camera",
|
||||
}
|
||||
for pattern, type_key in type_vendor_patterns.items():
|
||||
if re.search(pattern, vendor, re.IGNORECASE):
|
||||
type_ = DEVICE_TYPES.get(type_key, default_type)
|
||||
break
|
||||
else:
|
||||
# MAC-based type guessing
|
||||
mac_clean = mac.replace(':', '').replace('-', '').upper()
|
||||
type_mac_patterns = {
|
||||
"00:1A:79|B0:BE:83|BC:92:6B": "Phone",
|
||||
"00:1B:63|BC:4C:4C": "Tablet",
|
||||
"74:AC:B9|00:24:68": "AccessPoint",
|
||||
"B8:27:EB": "IoT",
|
||||
"00:14:22|00:18:74": "Desktop",
|
||||
"00:1C:BF|00:21:86": "Server",
|
||||
}
|
||||
for pattern_str, type_key in type_mac_patterns.items():
|
||||
patterns = [p.replace(':', '').replace('-', '').upper() for p in pattern_str.split('|')]
|
||||
if any(mac_clean.startswith(p) for p in patterns):
|
||||
type_ = DEVICE_TYPES.get(type_key, default_type)
|
||||
break
|
||||
else:
|
||||
# Name-based type guessing
|
||||
type_name_patterns = {
|
||||
"iphone|ipad": "Phone",
|
||||
"macbook|imac": "Laptop",
|
||||
"pixel|galaxy|redmi": "Phone",
|
||||
"laptop|notebook": "Laptop",
|
||||
"printer|print": "Printer",
|
||||
"router|gateway|ap|access[ -]?point": "Router",
|
||||
"tv|television|smarttv": "TV",
|
||||
"desktop|pc|computer": "Desktop",
|
||||
"tablet|pad": "Tablet",
|
||||
"watch|wear": "Smartwatch",
|
||||
"camera|cam|webcam": "Camera",
|
||||
"echo|alexa|dot": "SmartSpeaker",
|
||||
"hue|lifx|bulb": "SmartLight",
|
||||
"server|nas": "Server",
|
||||
"playstation|xbox|switch": "GamingConsole",
|
||||
"raspberry|pi": "IoT",
|
||||
"google|chromecast|nest": "SmartHome",
|
||||
"doorbell|lock|security": "SecurityDevice",
|
||||
}
|
||||
for pattern, type_key in type_name_patterns.items():
|
||||
if re.search(pattern, name, re.IGNORECASE):
|
||||
type_ = DEVICE_TYPES.get(type_key, default_type)
|
||||
break
|
||||
else:
|
||||
# IP-based type guessing
|
||||
type_ip_patterns = {
|
||||
r"^192\.168\.[0-1]\.1$": "Router",
|
||||
r"^10\.0\.0\.1$": "Router",
|
||||
r"^192\.168\.[0-1]\.[2-9]$": "Desktop",
|
||||
r"^192\.168\.[0-1]\.1\d{2}$": "Phone",
|
||||
}
|
||||
for pattern, type_key in type_ip_patterns.items():
|
||||
if re.match(pattern, ip):
|
||||
type_ = DEVICE_TYPES.get(type_key, default_type)
|
||||
break
|
||||
else:
|
||||
type_ = default_type
|
||||
type_ = None
|
||||
icon = None
|
||||
|
||||
# --- Strict MAC + vendor rule matching from external file ---
|
||||
type_, icon = match_mac_and_vendor(mac_clean, vendor, default_type, default_icon)
|
||||
|
||||
# --- Loose Vendor-based fallback ---
|
||||
if not type_ or type_ == default_type:
|
||||
type_, icon = match_vendor(vendor, default_type, default_icon)
|
||||
|
||||
# --- Loose Name-based fallback ---
|
||||
if not type_ or type_ == default_type:
|
||||
type_, icon = match_name(name, default_type, default_icon)
|
||||
|
||||
# --- Loose IP-based fallback ---
|
||||
if (not type_ or type_ == default_type) or (not icon or icon == default_icon):
|
||||
type_, icon = match_ip(ip, default_type, default_icon)
|
||||
|
||||
# Final fallbacks
|
||||
type_ = type_ or default_type
|
||||
icon = icon or default_icon
|
||||
|
||||
mylog('debug', f"[guess_device_attributes] Guessed attributes (icon|type_): ('{icon}'|'{type_}')")
|
||||
return icon, type_
|
||||
|
||||
|
||||
# Deprecated functions with redirects (To be removed once all calls for these have been adjusted to use the updated function)
|
||||
def guess_icon(
|
||||
vendor: Optional[str],
|
||||
@@ -308,7 +270,7 @@ def guess_type(
|
||||
default: Default type to return if no match is found.
|
||||
|
||||
Returns:
|
||||
str: Device type from DEVICE_TYPES dictionary.
|
||||
str: Device type.
|
||||
"""
|
||||
|
||||
_, type_ = guess_device_attributes(vendor, mac, ip, name, "unknown_icon", default)
|
||||
|
||||
Reference in New Issue
Block a user