mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2025-12-07 09:36:05 -08:00
Compare commits
6 Commits
1bce2e80e8
...
f25c012fbe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f25c012fbe | ||
|
|
868a85d84c | ||
|
|
771dd4b176 | ||
|
|
ed4d3bf17c | ||
|
|
7c728fbe36 | ||
|
|
4ff9d01ef5 |
111
docs/DEVICE_HEURISTICS.md
Executable file
111
docs/DEVICE_HEURISTICS.md
Executable file
@@ -0,0 +1,111 @@
|
||||
# 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.
|
||||
|
||||
### 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
|
||||
@@ -1,47 +1,4 @@
|
||||
{
|
||||
"__inputs": [
|
||||
{
|
||||
"name": "DS_PROMETHEUS",
|
||||
"label": "Prometheus",
|
||||
"description": "",
|
||||
"type": "datasource",
|
||||
"pluginId": "prometheus",
|
||||
"pluginName": "Prometheus"
|
||||
}
|
||||
],
|
||||
"__elements": {},
|
||||
"__requires": [
|
||||
{
|
||||
"type": "panel",
|
||||
"id": "barchart",
|
||||
"name": "Bar chart",
|
||||
"version": ""
|
||||
},
|
||||
{
|
||||
"type": "grafana",
|
||||
"id": "grafana",
|
||||
"name": "Grafana",
|
||||
"version": "12.0.0"
|
||||
},
|
||||
{
|
||||
"type": "datasource",
|
||||
"id": "prometheus",
|
||||
"name": "Prometheus",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"type": "panel",
|
||||
"id": "stat",
|
||||
"name": "Stat",
|
||||
"version": ""
|
||||
},
|
||||
{
|
||||
"type": "panel",
|
||||
"id": "table",
|
||||
"name": "Table",
|
||||
"version": ""
|
||||
}
|
||||
],
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
@@ -61,14 +18,11 @@
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"id": 7,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"datasource": "Prometheus",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -77,8 +31,6 @@
|
||||
},
|
||||
"decimals": 0,
|
||||
"mappings": [],
|
||||
"max": 105,
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -122,12 +74,8 @@
|
||||
"pluginVersion": "12.0.0",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "netalertx_total_devices",
|
||||
"refId": "A",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
}
|
||||
"expr": "netalertx_connected_devices + netalertx_offline_devices",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Total Devices",
|
||||
@@ -136,7 +84,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "PBFA97CFB590B2093"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -190,12 +138,12 @@
|
||||
"pluginVersion": "12.0.0",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "netalertx_connected_devices",
|
||||
"refId": "A",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
}
|
||||
"uid": "PBFA97CFB590B2093"
|
||||
},
|
||||
"expr": "netalertx_connected_devices",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Connected",
|
||||
@@ -204,7 +152,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "PBFA97CFB590B2093"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -259,12 +207,12 @@
|
||||
"pluginVersion": "12.0.0",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "netalertx_favorite_devices",
|
||||
"refId": "A",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
}
|
||||
"uid": "PBFA97CFB590B2093"
|
||||
},
|
||||
"expr": "netalertx_favorite_devices",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Favorites",
|
||||
@@ -273,7 +221,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "PBFA97CFB590B2093"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -328,12 +276,12 @@
|
||||
"pluginVersion": "12.0.0",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "netalertx_new_devices",
|
||||
"refId": "A",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
}
|
||||
"uid": "PBFA97CFB590B2093"
|
||||
},
|
||||
"expr": "netalertx_new_devices",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "New Devices",
|
||||
@@ -342,7 +290,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "PBFA97CFB590B2093"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -397,12 +345,12 @@
|
||||
"pluginVersion": "12.0.0",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "netalertx_down_devices",
|
||||
"refId": "A",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
}
|
||||
"uid": "PBFA97CFB590B2093"
|
||||
},
|
||||
"expr": "netalertx_down_devices",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Down",
|
||||
@@ -411,7 +359,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "PBFA97CFB590B2093"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -466,12 +414,12 @@
|
||||
"pluginVersion": "12.0.0",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "netalertx_archived_devices",
|
||||
"refId": "A",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
}
|
||||
"uid": "PBFA97CFB590B2093"
|
||||
},
|
||||
"expr": "netalertx_archived_devices",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Archived",
|
||||
@@ -480,7 +428,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "PBFA97CFB590B2093"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -605,31 +553,31 @@
|
||||
"pluginVersion": "12.0.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "PBFA97CFB590B2093"
|
||||
},
|
||||
"expr": "netalertx_connected_devices",
|
||||
"legendFormat": "Connected",
|
||||
"refId": "A",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
}
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "PBFA97CFB590B2093"
|
||||
},
|
||||
"expr": "netalertx_total_devices - (netalertx_connected_devices + netalertx_down_devices)",
|
||||
"legendFormat": "Offline",
|
||||
"refId": "B",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
}
|
||||
"refId": "B"
|
||||
},
|
||||
{
|
||||
"expr": "netalertx_down_devices",
|
||||
"legendFormat": "Down Devices",
|
||||
"refId": "C",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
}
|
||||
"uid": "PBFA97CFB590B2093"
|
||||
},
|
||||
"expr": "netalertx_down_devices",
|
||||
"legendFormat": "Down Devices",
|
||||
"refId": "C"
|
||||
}
|
||||
],
|
||||
"title": "Device Presence",
|
||||
@@ -638,7 +586,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "PBFA97CFB590B2093"
|
||||
},
|
||||
"description": "Connected (Online) Devices",
|
||||
"fieldConfig": {
|
||||
@@ -826,15 +774,15 @@
|
||||
"pluginVersion": "12.0.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "PBFA97CFB590B2093"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "netalertx_device_status{device_status=\"Online\"}",
|
||||
"format": "table",
|
||||
"range": true,
|
||||
"refId": "A",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
}
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Connected Devices",
|
||||
@@ -892,7 +840,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "PBFA97CFB590B2093"
|
||||
},
|
||||
"description": "Disconnected(Offline) Devices",
|
||||
"fieldConfig": {
|
||||
@@ -1079,15 +1027,15 @@
|
||||
"pluginVersion": "12.0.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "PBFA97CFB590B2093"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "netalertx_device_status{device_status=\"Offline\"}",
|
||||
"format": "table",
|
||||
"range": true,
|
||||
"refId": "A",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
}
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Disconnected Devices",
|
||||
@@ -1143,6 +1091,7 @@
|
||||
"type": "table"
|
||||
}
|
||||
],
|
||||
"preload": false,
|
||||
"refresh": "30s",
|
||||
"schemaVersion": 41,
|
||||
"tags": [],
|
||||
@@ -1156,7 +1105,6 @@
|
||||
"timepicker": {},
|
||||
"timezone": "browser",
|
||||
"title": "NetAlertX Overview",
|
||||
"uid": "netalertx-overview_7_26_2025",
|
||||
"version": 7,
|
||||
"weekStart": ""
|
||||
"uid": "netalertx-overview_8_4_2025",
|
||||
"version": 2
|
||||
}
|
||||
0
front/php/templates/language/fr_fr.json
Normal file → Executable file
0
front/php/templates/language/fr_fr.json
Normal file → Executable file
@@ -301,7 +301,7 @@
|
||||
"Gen_Cancel": "Annulla",
|
||||
"Gen_Change": "Modifica",
|
||||
"Gen_Copy": "Esegui",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"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",
|
||||
@@ -760,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."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,33 @@
|
||||
|
||||
<?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
|
||||
@@ -100,7 +122,7 @@ echo '<div class="box box-solid">
|
||||
<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">Loading...</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>
|
||||
@@ -290,19 +312,6 @@ $(document).ready(function() {
|
||||
hideSpinner(); // Called after the DataTable is fully initialized
|
||||
}
|
||||
});
|
||||
|
||||
// external IP
|
||||
$.ajax({
|
||||
url: 'https://api64.ipify.org?format=json',
|
||||
method: 'GET',
|
||||
timeout: 10000, // 10 seconds timeout
|
||||
success: function (response) {
|
||||
$('#external-ip').text(response.ip);
|
||||
},
|
||||
error: function() {
|
||||
$('#external-ip').text('ERROR');
|
||||
}
|
||||
});
|
||||
}, 200);
|
||||
});
|
||||
|
||||
|
||||
@@ -75,6 +75,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
|
||||
|
||||
@@ -141,7 +141,7 @@ def match_name(
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
#
|
||||
def match_ip_rule(
|
||||
def match_ip(
|
||||
ip: str,
|
||||
default_type: str,
|
||||
default_icon: str
|
||||
@@ -215,7 +215,7 @@ def guess_device_attributes(
|
||||
|
||||
# --- Loose IP-based fallback ---
|
||||
if (not type_ or type_ == default_type) or (not icon or icon == default_icon):
|
||||
type_, icon = match_ip_rule(ip, default_type, default_icon)
|
||||
type_, icon = match_ip(ip, default_type, default_icon)
|
||||
|
||||
# Final fallbacks
|
||||
type_ = type_ or default_type
|
||||
|
||||
Reference in New Issue
Block a user