diff --git a/back/app.conf b/back/app.conf index a37991a1..105e4159 100755 --- a/back/app.conf +++ b/back/app.conf @@ -23,8 +23,8 @@ TIMEZONE='Europe/Berlin' LOADED_PLUGINS=['ARPSCAN', 'AVAHISCAN', 'CSVBCKP','DBCLNP', 'DIGSCAN', 'INTRNT', 'MAINT', 'NEWDEV', 'NBTSCAN', 'NSLOOKUP','NTFPRCS', 'SETPWD', 'SMTP', 'SYNC', 'VNDRPDT', 'WORKFLOWS', 'UI'] DAYS_TO_KEEP_EVENTS=90 -DEV_HIST_DAYS=14 -DEV_HIST_TRACKED=['devMac','devName','devOwner','devType','devVendor','devFavorite','devGroup','devComments','devLastIP','devFQDN','devPrimaryIPv4','devPrimaryIPv6','devVlan','devForceStatus','devStaticIP','devScan','devAlertDown','devCanSleep','devSkipRepeated','devLocation','devIsArchived','devParentMAC','devParentPort','devParentRelType','devReqNicsOnline','devIcon','devSite','devSSID','devSyncHubNode','devSourcePlugin','devMacSource','devNameSource','devFQDNSource','devLastIPSource','devVendorSource','devSSIDSource','devParentMACSource','devParentPortSource','devParentRelTypeSource','devVlanSource','devCustomProps'] +DEV_HIST_DAYS=1 +DEV_HIST_TRACKED=['devMac','devName','devOwner','devType','devVendor','devFavorite','devGroup','devComments','devLastIP','devFQDN','devPrimaryIPv4','devPrimaryIPv6','devVlan','devForceStatus','devStaticIP','devScan','devAlertDown','devCanSleep','devSkipRepeated','devLocation','devIsArchived','devParentMAC','devParentPort','devParentRelType','devReqNicsOnline','devIcon','devSite','devSSID','devSyncHubNode'] # Used for generating links in emails. Make sure not to add a trailing slash! REPORT_DASHBOARD_URL='update_REPORT_DASHBOARD_URL_setting' diff --git a/docs/API_GRAPHQL.md b/docs/API_GRAPHQL.md index 199cd30f..ff36a3f2 100755 --- a/docs/API_GRAPHQL.md +++ b/docs/API_GRAPHQL.md @@ -427,9 +427,9 @@ Device field change history is stored in `DevicesHistory` and exposed via two qu Returns grouped change events for one device. Events are grouped by `(timestamp, changedBy)` so that simultaneous field changes are returned as a single object. ```graphql -query DeviceHistory($devGuid: String!, $changedColumn: String, $changedBy: String, $limit: Int, $offset: Int) { +query DeviceHistory($devGUID: String!, $changedColumn: String, $changedBy: String, $limit: Int, $offset: Int) { deviceHistoryGrouped( - devGuid: $devGuid + devGUID: $devGUID changedColumn: $changedColumn changedBy: $changedBy limit: $limit @@ -454,7 +454,7 @@ query DeviceHistory($devGuid: String!, $changedColumn: String, $changedBy: Strin | Parameter | Type | Required | Description | |-----------------|----------|----------|----------------------------------------------------------------------| -| `devGuid` | `String` | ✅ | GUID of the device to fetch history for | +| `devGUID` | `String` | No | GUID of the device to fetch history for | | `changedColumn` | `String` | No | Filter to groups containing a change to this specific column | | `changedBy` | `String` | No | Filter to a specific attribution source (`USER`, `ARPSCAN`, etc.) | | `limit` | `Int` | No | Max grouped events to return (default `50`) | @@ -479,9 +479,9 @@ curl -X POST http://localhost:20212/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -d '{ - "query": "query($devGuid:String!,$limit:Int,$offset:Int){deviceHistoryGrouped(devGuid:$devGuid,limit:$limit,offset:$offset){count history{timestamp changedBy changes{changedColumn oldValue newValue}}}}", + "query": "query($devGUID:String!,$limit:Int,$offset:Int){deviceHistoryGrouped(devGUID:$devGUID,limit:$limit,offset:$offset){count history{timestamp changedBy changes{changedColumn oldValue newValue}}}}", "variables": { - "devGuid": "your-device-guid-here", + "devGUID": "your-device-guid-here", "limit": 25, "offset": 0 } @@ -490,47 +490,7 @@ curl -X POST http://localhost:20212/graphql \ --- -### `allDeviceHistoryGrouped` — All Devices (Global View) -Identical to `deviceHistoryGrouped` but returns changes across **all devices**. Used by the Change History page under Monitoring. Does not accept a `devGuid` parameter. - -```graphql -query AllHistory($changedColumn: String, $changedBy: String, $limit: Int, $offset: Int) { - allDeviceHistoryGrouped( - changedColumn: $changedColumn - changedBy: $changedBy - limit: $limit - offset: $offset - ) { - count - history { - devGUID - timestamp - changedBy - changes { - changedColumn - oldValue - newValue - } - } - } -} -``` - -#### `curl` Example - -```sh -curl -X POST http://localhost:20212/graphql \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer YOUR_API_TOKEN" \ - -d '{ - "query": "query($changedBy:String,$limit:Int){allDeviceHistoryGrouped(changedBy:$changedBy,limit:$limit){count history{devGUID timestamp changedBy changes{changedColumn oldValue newValue}}}}", - "variables": { - "changedBy": "USER", - "limit": 50 - } - }' -``` --- @@ -539,7 +499,7 @@ curl -X POST http://localhost:20212/graphql \ To populate dynamic filter dropdowns, fetch available `changedBy` and `changedColumn` values via the REST endpoint: ```sh -GET /devices/history/filters?devGuid= # scoped to one device +GET /devices/history/filters?devGUID= # scoped to one device GET /devices/history/filters # all devices ``` diff --git a/docs/PERFORMANCE.md b/docs/PERFORMANCE.md index 8abd4d34..a6c74f6d 100755 --- a/docs/PERFORMANCE.md +++ b/docs/PERFORMANCE.md @@ -132,6 +132,14 @@ When **any** name-resolution plugin has `devName` in its `SET_ALWAYS` list, the The actual number of DB rows updated is logged at `verbose` level under `[Update Device Name] SET_ALWAYS re-resolve - DB rows updated`. + +--- + +## Disable change log tracking + +To improve performance, you can reduce or disable change log tracking. Use the `DEV_HIST_TRACKED` setting to limit tracking to specific columns, and adjust `DEV_HIST_DAYS` to control how long history is retained. Setting `DEV_HIST_DAYS` to `0` disables history tracking entirely. + + --- ## Storing Temporary Files in Memory diff --git a/docs/WHERE_NETALERTX_FITS.md b/docs/WHERE_NETALERTX_FITS.md new file mode 100644 index 00000000..0b56bd63 --- /dev/null +++ b/docs/WHERE_NETALERTX_FITS.md @@ -0,0 +1,284 @@ +# Where NetAlertX Fits + +Understanding where NetAlertX fits in your network management toolkit makes it easier to get the most from it. + +Modern networks are typically managed using several specialized tools. One application may monitor uptime, another may capture packets, another may enforce firewall rules, while another documents the network. + +NetAlertX fills a different role: it provides **continuous network visibility** by discovering devices, maintaining an accurate asset inventory, detecting changes, and notifying you when something important happens. + +It answers one of the most common questions in network administration: + +> **What is on my network, and what has changed?** + +--- + +## What NetAlertX Does + +NetAlertX continuously discovers devices using one or more discovery plugins and builds a living inventory of your network. + +Depending on your configuration, it can collect information including: + +* IP addresses +* MAC addresses +* Hostnames +* Vendors +* Device types +* Open ports +* Network interfaces +* Parent/child relationships +* First and last seen timestamps +* Custom metadata + +Because discovery runs continuously, your inventory stays up to date automatically. + +Learn more about the available discovery methods in the [Plugins](./PLUGINS.md) documentation. + +--- + +## Detecting Change + +Knowing what exists on your network is useful. + +Knowing **what changed** is often even more valuable. + +NetAlertX continuously records changes such as: + +* New devices +* Devices going offline or coming back online +* IP address changes +* Hostname changes +* Vendor identification +* Open port changes (via the `NMAP` [plugin](./PLUGINS.md)) +* Devices returning after long periods offline +* Configuration updates + +These changes become events that can be viewed historically, filtered, searched, or used to trigger automation. + +--- + +## Notifications + +![Global notification settings](./img/NOTIFICATIONS/Global-notification-settings.png) + +Not every event deserves immediate attention. + +NetAlertX lets you choose which events [generate notifications](./NOTIFICATIONS.md) so that important alerts stand out without creating unnecessary noise. + +Examples include: + +* Unknown devices joining the network +* Critical infrastructure becoming unavailable +* IP address changes +* Port changes +* Custom workflow events + +Notification publishers can be enabled independently, allowing NetAlertX to integrate with your existing communication channels. + +--- + +## Automation + +![Workflow Action target](./img/WORKFLOWS/actions_target.png) + +As networks grow, manually responding to every event quickly becomes impractical. + +NetAlertX includes a [workflow engine](./WORKFLOWS.md) that can automatically react to events. + +Typical automations include: + +* Mark trusted devices automatically +* Assign devices to groups +* Update fields +* Archive old devices with the same IP +* Assign devices to the right parent node + +Automation helps keep device management consistent while reducing repetitive administrative work. + +--- + +## Device Relationships + +![Hover relationships detail](./img/NETWORK_TREE/Network_tree_setup_hover.png) + +Networks are more than collections of independent devices. + +Devices often depend on one another. + +Examples include: + +* Access points connected to switches +* Virtual machines hosted by physical servers +* Containers running on hosts +* Network interfaces belonging to a parent device +* IoT gateways connected to sensors + +NetAlertX supports [device relationships](./NETWORK_TREE.md) to better represent how devices are connected and how they depend on each other. + +--- + +## Historical Visibility + +![Device presence](./img/DEVICE_MANAGEMENT/device_presence.png) + +Many monitoring systems focus only on the current state. + +NetAlertX also records historical information so you can answer questions like: + +* When was this device first discovered? +* When was it last online? +* Which ports have changed over time? + +Historical information provides valuable operational context without requiring a separate logging platform. + +--- + +## Plugin Architecture + +![Action target](./img/plugins_json_settings.png) + +Every environment is different. + +Some users prefer simple ARP scanning. + +Others rely on: + +* SNMP +* DHCP leases +* Router integrations +* Pi-hole +* Virtualization platforms +* Cloud services +* External asset inventories + +NetAlertX is built around a plugin architecture that allows multiple discovery sources to work together, improving accuracy while remaining flexible. + +See the complete list in [Plugins](./PLUGINS.md). + +--- + +## Integrations + +NetAlertX is designed to complement your existing infrastructure rather than replace it. + +It integrates with many popular platforms, including: + +* Home Assistant +* Pi-hole +* MQTT +* Notification services +* REST APIs +* Custom workflows + +This allows NetAlertX to become part of your existing automation and monitoring ecosystem. + +--- + +## How NetAlertX Compares + +The following diagram illustrates how NetAlertX complements other common network tools. + +| Need | Typical Tool | NetAlertX Role | +| --- | --- | --- | +| Device discovery | ✅ | **Core capability** | +| Asset inventory | ✅ | **Core capability** | +| Detect network changes | ✅ | **Core capability** | +| Historical device tracking | ✅ | **Core capability** | +| Notifications | ✅ | **Core capability** | +| Automation | ✅ | **Core capability** | +| Packet inspection | Wireshark | Not intended | +| Firewall enforcement | pfSense, OPNsense | Not intended | +| IDS/IPS | Suricata, Snort | Not intended | +| Network documentation | NetBox, NetMap | Complements them | +| Uptime monitoring | Uptime Kuma, Nagios | Complements them | + +NetAlertX works best alongside these tools, providing the continuously updated device inventory that many monitoring platforms lack. + +--- + +## Typical Deployments + +### 🏠 Home Networks + +Monitor: + +* Family devices +* Smart home equipment +* NAS systems +* TVs and media devices +* Game consoles +* IoT devices + +Receive alerts whenever new or unknown devices join your network. + +--- + +### 🧪 Homelabs + +Track constantly changing environments including: + +* Docker containers +* Virtual machines +* Kubernetes nodes +* Development systems +* Test networks + +Maintain an accurate inventory without manual documentation. + +--- + +### 🏢 Small Businesses + +Maintain visibility across office networks by tracking: + +* Unauthorized devices and shadow IT +* Office infrastructure and critical core systems +* Real-time asset inventory drift + +--- + +### 🌍 Remote Sites + +Deploy NetAlertX at branch offices or remote locations to maintain local visibility while monitoring multiple sites centrally. + +--- + +## What NetAlertX Is Designed To Do + +NetAlertX is designed to excel at: + +* Continuous device discovery +* Asset inventory +* Network visibility +* Change detection +* Historical tracking +* Notifications +* Workflow automation +* Plugin extensibility + +By focusing on these capabilities, NetAlertX remains lightweight, flexible, and easy to integrate into existing environments. + +--- + +## What NetAlertX Is Not + +NetAlertX is **not** intended to replace: + +* Firewalls +* Packet analyzers +* IDS/IPS platforms +* Configuration management systems +* Network documentation platforms +* Dedicated uptime monitoring systems + +Instead, it complements these tools by providing continuous awareness of the devices connected to your network. + +--- + +## Next Steps + +Now that you understand where NetAlertX fits, continue with one of the following guides: + +* **[Installation](INSTALLATION.md)** — Install NetAlertX on your platform. +* **[Plugins](./PLUGINS.md)** — Configure device discovery. +* **[Features](FEATURES.md)** — Explore everything NetAlertX can do. +* **[API](API.md)** — Integrate NetAlertX with your own applications. \ No newline at end of file diff --git a/docs/img/DEVICE_MANAGEMENT/device_presence.png b/docs/img/DEVICE_MANAGEMENT/device_presence.png new file mode 100644 index 00000000..493a31d3 Binary files /dev/null and b/docs/img/DEVICE_MANAGEMENT/device_presence.png differ diff --git a/docs/index.md b/docs/index.md index 720842a2..dc8e80e9 100755 --- a/docs/index.md +++ b/docs/index.md @@ -56,6 +56,23 @@ hide: --- +## What is NetAlertX? + +NetAlertX continuously discovers the devices on your network, builds a living asset inventory, detects changes, and alerts you when something important happens. + +Whether you're monitoring a home network, homelab, business, or remote site, NetAlertX helps answer questions like: + +- What devices are currently connected? +- Has a new or unknown device appeared? +- Which servers or services are offline? +- Has a device changed IP address, hostname, or open ports? +- When was a device first or last seen? + +Unlike packet analyzers or firewall appliances, NetAlertX focuses on **network visibility**, **asset discovery**, **change detection**, **historical tracking**, and **automation**. + +> [!TIP] +> **New to NetAlertX?** Start with **[Where NetAlertX Fits](./WHERE_NETALERTX_FITS)** to understand how it complements the rest of your network tools. + ## Help and Support If you need help or run into issues, here are some resources to guide you: diff --git a/front/changeLog.php b/front/changeLog.php new file mode 100644 index 00000000..c5003212 --- /dev/null +++ b/front/changeLog.php @@ -0,0 +1,17 @@ + +
+
+

+ + +

+
+ +
+ + + diff --git a/front/changeLogCore.php b/front/changeLogCore.php new file mode 100644 index 00000000..0d320f69 --- /dev/null +++ b/front/changeLogCore.php @@ -0,0 +1,311 @@ + + +
+
+
+
+ + +
+ + +
+ + +
+ + + + + + + + + + + + +
+ +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/front/change_history.php b/front/change_history.php deleted file mode 100644 index 678b113f..00000000 --- a/front/change_history.php +++ /dev/null @@ -1,210 +0,0 @@ - - - - -
- -
-

- - -

-
- -
-
-
-
- - -
- - -
- - -
- - - - - - - - - - - - -
Device
- -
-
- - - - -
-
-
-
-
- - - - diff --git a/front/css/app.css b/front/css/app.css index b851c5ed..5824f4b5 100755 --- a/front/css/app.css +++ b/front/css/app.css @@ -2677,7 +2677,7 @@ table.dataTable tbody > tr.selected /* Page-level overlay base */ #settings-skeleton, #devices-skeleton, #device-details-skeleton, -#events-skeleton, #presence-skeleton, #report-skeleton, #skel-app-events, +#events-skeleton, #presence-skeleton, #report-skeleton, #skel-app-events, #change-log-skeleton #notifications-skeleton, #workflows-skeleton, #plugins-skeleton, .skel-tab-pane { position: absolute; top: 0; @@ -2699,6 +2699,7 @@ table.dataTable tbody > tr.selected #notifications-skeleton { margin: 15px; top: 50px; min-height: 400px; } #workflows-skeleton { top: 50px; width: 770px; margin: 0 auto; min-height: 300px; } #plugins-skeleton { top: 50px; padding: 0; min-height: 400px; } +#change-log-skeleton { display: block; min-height: 400px; z-index: 1; position: absolute; margin-left: 15px; padding-top: 15px; width:100%;} #skel-tab-sessions, #skel-tab-events, #skel-tab-presence {margin: -10px; padding: 20px;} /* Hide scrollbars */ diff --git a/front/deviceDetails.php b/front/deviceDetails.php index 93cf58c3..5e0b76e4 100755 --- a/front/deviceDetails.php +++ b/front/deviceDetails.php @@ -168,44 +168,10 @@
- - -
- - -
- - - - - - - - - - - - -
- -
- -
- - 1 - -
-
+
@@ -647,136 +613,11 @@ setTimeout(function() { }, 15000); } -// --------------------------------------------------------------------------- -// Change Log tab — device-scoped history -// --------------------------------------------------------------------------- - -var _histPage = 0; -var _histLimit = 50; -var _histTotal = 0; -var _histLoaded = false; - -function loadHistoryData() { - var devGuid = getDevDataByMac(mac, 'devGUID'); - // Devices without a GUID (created before GUIDs were introduced) cannot be - // queried — show a clear message instead of spinning indefinitely. - if (devGuid === null || devGuid === undefined || devGuid === '') { - _renderHistoryTable([]); - return; - } - - var changedBy = (document.getElementById('histFilterBy') || {}).value || null; - var changedColumn = (document.getElementById('histFilterCol') || {}).value || null; - - var query = [ - 'query($devGuid:String!,$changedBy:String,$changedColumn:String,$limit:Int,$offset:Int){', - ' deviceHistoryGrouped(devGuid:$devGuid,changedBy:$changedBy,changedColumn:$changedColumn,limit:$limit,offset:$offset){', - ' count history{timestamp changedBy changes{changedColumn oldValue newValue}}', - ' }', - '}' - ].join(''); - - var variables = { - devGuid: devGuid, - changedBy: changedBy || null, - changedColumn: changedColumn || null, - limit: _histLimit, - offset: _histPage * _histLimit - }; - - $.ajax({ - url: getApiBase() + '/graphql', - method: 'POST', - contentType: 'application/json', - headers: { 'Authorization': 'Bearer ' + getSetting('API_TOKEN') }, - data: JSON.stringify({ query: query, variables: variables }), - success: function(resp) { - if (!resp.data || !resp.data.deviceHistoryGrouped) return; - var result = resp.data.deviceHistoryGrouped; - _histTotal = result.count; - _renderHistoryTable(result.history); - _updateHistoryPagination(); - - // Populate filter dropdowns once on first load - if (!_histLoaded) { - _histLoaded = true; - _populateHistoryFilters(devGuid); - } - } - }); -} - -function _populateHistoryFilters(devGuid) { - $.ajax({ - url: getApiBase() + '/devices/history/filters?devGuid=' + encodeURIComponent(devGuid), - method: 'GET', - headers: { 'Authorization': 'Bearer ' + getSetting('API_TOKEN') }, - success: function(resp) { - if (!resp.data) return; - var byEl = document.getElementById('histFilterBy'); - var colEl = document.getElementById('histFilterCol'); - (resp.data.changedBy || []).forEach(function(v) { byEl.add(new Option(v, v)); }); - (resp.data.changedColumn || []).forEach(function(v) { colEl.add(new Option(v, v)); }); - } - }); -} - -function _renderHistoryTable(groups) { - var tbody = document.getElementById('historyTableBody'); - if (!tbody) return; - // Always hide the loading skeleton once we have a result (even empty) - $('#skel-tab-history').fadeOut(0, function() { $(this).hide(); }); - if (!groups || groups.length === 0) { - tbody.innerHTML = ''; - return; - } - var html = ''; - groups.forEach(function(g) { - var iconHtml = (g.changedBy === 'user:api' || g.changedBy === 'USER') - ? '' - : ''; - var changesHtml = g.changes.map(function(c) { - return '
' + _histEsc(c.changedColumn) + ': ' - + '' + _histEsc(c.oldValue || '\u2205') + '' - + ' \u2192 ' - + '' + _histEsc(c.newValue || '\u2205') + '' - + '
'; - }).join(''); - html += '' - + '' + _histEsc(g.timestamp) + '' - + '' + iconHtml + ' ' + _histEsc(g.changedBy) + '' - + '' + changesHtml + '' - + ''; - }); - tbody.innerHTML = html; -} - -function _updateHistoryPagination() { - var totalPages = Math.max(1, Math.ceil(_histTotal / _histLimit)); - var lbl = document.getElementById('histPageLabel'); - var info = document.getElementById('histInfo'); - var prev = document.getElementById('histBtnPrev'); - var next = document.getElementById('histBtnNext'); - if (lbl) lbl.textContent = (_histPage + 1) + ' / ' + totalPages; - if (info) info.textContent = _histTotal + ' '; - if (prev) prev.disabled = _histPage <= 0; - if (next) next.disabled = (_histPage + 1) >= totalPages; -} - -function histChangePage(delta) { - _histPage = Math.max(0, _histPage + delta); - loadHistoryData(); -} - -function _histEsc(str) { - if (str === null || str === undefined) return ''; - return String(str).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); -} // Load history when the tab becomes visible $(document).on('shown.bs.tab', 'a[id="tabHistory"]', function() { _histPage = 0; - loadHistoryData(); + // loadHistoryData(); }); diff --git a/front/js/cache.js b/front/js/cache.js index dcae0d18..8fb675e6 100644 --- a/front/js/cache.js +++ b/front/js/cache.js @@ -509,6 +509,45 @@ function getDevDataByMac(macAddress, dbColumn) { return null; // Return a default value if MAC address is not found } + +// ----------------------------------------------------------------------------- +// Get a device property using GUID as key and DB column name as parameter +function getDevDataByGuid(devGUID, dbColumn) { + + const sessionDataKey = CACHE_KEYS.DEVICES_ALL; + const devicesCache = getCache(sessionDataKey); + + if (!devicesCache || devicesCache == "") { + console.warn(`[getDevDataByGuid] Cache key "${sessionDataKey}" is empty — cache may not be initialized yet.`); + return null; + } + + const devices = parseDeviceCache(devicesCache); + + if (devices.length === 0) { + return null; + } + + const guid = devGUID?.toLowerCase(); + + + for (const device of devices) { + + if ((device["devGUID"] || "").toLowerCase() === guid) { + + if (dbColumn) { + return device[dbColumn]; + } + + return device; + } + } + + console.error("⚠ Device with GUID not found: " + devGUID); + return null; +} + + // ----------------------------------------------------------------------------- /** * Fetches the full device list from table_devices.json and stores it in diff --git a/front/php/templates/header.php b/front/php/templates/header.php index c401754d..577ba986 100755 --- a/front/php/templates/header.php +++ b/front/php/templates/header.php @@ -332,14 +332,14 @@ -