Compare commits

...

10 Commits

Author SHA1 Message Date
jokob-sk
4712a2ff29 css fixes, nav menu update, searchable devParentNodeMac
Some checks failed
Deploy MkDocs / deploy (push) Has been cancelled
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
2025-08-06 09:15:45 +10:00
jokob-sk
f9179a1e89 safe device name if number #1131 2025-08-06 07:20:04 +10:00
jokob-sk
a6df204721 github timeout #1124, css fixes, change button on LOADED_PLUGINS
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
2025-08-05 21:32:35 +10:00
jokob-sk
101189ae7c devParentNodeMac chips in devices list 2025-08-05 20:54:28 +10:00
jokob-sk
f25c012fbe external ip rework #1124
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
2025-08-05 14:42:00 +10:00
jokob-sk
868a85d84c Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2025-08-05 14:03:18 +10:00
jokob-sk
771dd4b176 docs 2025-08-05 14:02:48 +10:00
Hosted Weblate
ed4d3bf17c Merge branch 'origin/main' into Weblate. 2025-08-05 03:27:56 +00:00
Massimo Pissarello
7c728fbe36 Translated using Weblate (Italian)
Currently translated at 100.0% (761 of 761 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/it/
2025-08-05 03:27:56 +00:00
jokob-sk
4ff9d01ef5 heuristics docs 2025-08-05 13:27:30 +10:00
17 changed files with 357 additions and 229 deletions

111
docs/DEVICE_HEURISTICS.md Executable file
View 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 isnt 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

View File

@@ -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
}

View File

@@ -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;

View File

@@ -20,6 +20,7 @@
--color-yellow: #f39c12;
--color-red: #dd4b39;
--color-gray: #8c8c8c;
--color-white: #fff;
}
:root {
@@ -793,5 +794,5 @@
.btn:hover
{
color: var(--color-gray);
color: var(--color-white);
}

View File

@@ -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) {

View File

@@ -715,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
@@ -817,6 +779,50 @@ function initSelect2() {
}
}
// ------------------------------------------
// Render a device link with hover-over functionality
function renderDeviceLink(data, container, useName = false) {
if (!data.id) return data.text; // default placeholder etc.
const device = getDevDataByMac(data.id);
const badge = getStatusBadgeParts(
device.devPresentLastScan,
device.devAlertDown,
device.devMac
);
// Add 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() {

View File

@@ -185,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>

View File

@@ -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>

0
front/php/templates/language/fr_fr.json Normal file → Executable file
View File

View 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."
}
}

View File

@@ -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>
@@ -242,8 +264,6 @@ function fetchUsedIps(callback) {
function renderAvailableIpsTable(allIps, usedIps) {
const availableIps = allIps.filter(row => !usedIps.includes(row.ip));
console.log(allIps);
console.log(usedIps);
console.log(availableIps);
$('#availableIpsTable').DataTable({
@@ -290,19 +310,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);
});

View File

@@ -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

View File

@@ -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()

View File

@@ -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:

View File

@@ -157,7 +157,7 @@ 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')

View File

@@ -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"))

View File

@@ -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