mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-03-30 23:03:03 -07:00
sleeping devices status #1519
This commit is contained in:
@@ -23,6 +23,7 @@
|
|||||||
| `devLogEvents` | Whether events related to the device should be logged. | `0` |
|
| `devLogEvents` | Whether events related to the device should be logged. | `0` |
|
||||||
| `devAlertEvents` | Whether alerts should be generated for events. | `1` |
|
| `devAlertEvents` | Whether alerts should be generated for events. | `1` |
|
||||||
| `devAlertDown` | Whether an alert should be sent when the device goes down. | `0` |
|
| `devAlertDown` | Whether an alert should be sent when the device goes down. | `0` |
|
||||||
|
| `devCanSleep` | Whether the device can enter a sleep window. When `1`, offline periods within the `NTFPRCS_sleep_time` window are shown as **Sleeping** instead of **Down** and no down alert is fired. | `0` |
|
||||||
| `devSkipRepeated` | Whether to skip repeated alerts for this device. | `1` |
|
| `devSkipRepeated` | Whether to skip repeated alerts for this device. | `1` |
|
||||||
| `devLastNotification` | Timestamp of the last notification sent for this device. | `2025-03-22 12:07:26+11:00` |
|
| `devLastNotification` | Timestamp of the last notification sent for this device. | `2025-03-22 12:07:26+11:00` |
|
||||||
| `devPresentLastScan` | Whether the device was present during the last scan. | `1` |
|
| `devPresentLastScan` | Whether the device was present during the last scan. | `1` |
|
||||||
@@ -42,6 +43,12 @@
|
|||||||
| `devParentRelType` | The type of relationship between the current device and it's parent node. By default, selecting `nic` will hide it from lists. | `nic` |
|
| `devParentRelType` | The type of relationship between the current device and it's parent node. By default, selecting `nic` will hide it from lists. | `nic` |
|
||||||
| `devReqNicsOnline` | If all NICs are required to be online to mark teh current device online. | `0` |
|
| `devReqNicsOnline` | If all NICs are required to be online to mark teh current device online. | `0` |
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> `DevicesView` extends the `Devices` table with two computed fields that are never persisted:
|
||||||
|
> - `devIsSleeping` (`1` when `devCanSleep=1`, device is offline, and `devLastConnection` is within the `NTFPRCS_sleep_time` window).
|
||||||
|
> - `devFlapping` (`1` when the device has changed state more than the flap threshold times in the trailing window).
|
||||||
|
> - `devStatus` — derived string: `On-line`, `Sleeping`, `Down`, or `Off-line`.
|
||||||
|
|
||||||
|
|
||||||
To understand how values of these fields influuence application behavior, such as Notifications or Network topology, see also:
|
To understand how values of these fields influuence application behavior, such as Notifications or Network topology, see also:
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ This set of settings allows you to group Devices under different views. The Arch
|
|||||||
| <i class="fa-solid fa-plug-circle-exclamation"></i> | Online (Orange) |  | The device is online, but unstable and flapping (3 status changes in the last hour). |
|
| <i class="fa-solid fa-plug-circle-exclamation"></i> | Online (Orange) |  | The device is online, but unstable and flapping (3 status changes in the last hour). |
|
||||||
| <i class="fa-solid fa-xmark"></i> | New (Grey) |  | Same as "New (Green)" but the device is now offline. |
|
| <i class="fa-solid fa-xmark"></i> | New (Grey) |  | Same as "New (Green)" but the device is now offline. |
|
||||||
| <i class="fa-solid fa-xmark"></i> | Offline (Grey) |  | A device that was not detected online in the last scan. |
|
| <i class="fa-solid fa-xmark"></i> | Offline (Grey) |  | A device that was not detected online in the last scan. |
|
||||||
|
| <i class="fa-solid fa-moon"></i> | Sleeping (Aqua) | | A device with **Can Sleep** enabled that has gone offline within the `NTFPRCS_sleep_time` window. No down alert is fired while the device is in this state. See [Notifications](./NOTIFICATIONS.md#device-settings). |
|
||||||
| <i class="fa-solid fa-triangle-exclamation"></i> | Down (Red) |  | A device marked as "Alert Down" and offline for the duration set in `NTFPRCS_alert_down_time`.|
|
| <i class="fa-solid fa-triangle-exclamation"></i> | Down (Red) |  | A device marked as "Alert Down" and offline for the duration set in `NTFPRCS_alert_down_time`.|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,9 @@ The following device properties influence notifications. You can:
|
|||||||
|
|
||||||
1. **Alert Events** - Enables alerts of connections, disconnections, IP changes (down and down reconnected notifications are still sent even if this is disabled).
|
1. **Alert Events** - Enables alerts of connections, disconnections, IP changes (down and down reconnected notifications are still sent even if this is disabled).
|
||||||
2. **Alert Down** - Alerts when a device goes down. This setting overrides a disabled **Alert Events** setting, so you will get a notification of a device going down even if you don't have **Alert Events** ticked. Disabling this will disable down and down reconnected notifications on the device.
|
2. **Alert Down** - Alerts when a device goes down. This setting overrides a disabled **Alert Events** setting, so you will get a notification of a device going down even if you don't have **Alert Events** ticked. Disabling this will disable down and down reconnected notifications on the device.
|
||||||
3. **Skip repeated notifications**, if for example you know there is a temporary issue and want to pause the same notification for this device for a given time.
|
3. **Can Sleep** - Marks the device as sleep-capable (e.g. a battery-powered sensor that deep-sleeps between readings). When enabled, offline periods within the **Alert down after (sleep)** (`NTFPRCS_sleep_time`) global window are shown as **Sleeping** (aqua badge 🌙) instead of **Down**, and no down alert is fired during that window. Once the window expires the device falls back to normal down-alert logic. ⚠ Requires **Alert Down** to be enabled — sleeping suppresses the alert during the window only.
|
||||||
4. **Require NICs Online** - Indicates whether this device should be considered online only if all associated NICs (devices with the `nic` relationship type) are online. If disabled, the device is considered online if any NIC is online. If a NIC is online it sets the parent (this) device's status to online irrespectivelly of the detected device's status. The Relationship type is set on the childern device.
|
4. **Skip repeated notifications**, if for example you know there is a temporary issue and want to pause the same notification for this device for a given time.
|
||||||
|
5. **Require NICs Online** - Indicates whether this device should be considered online only if all associated NICs (devices with the `nic` relationship type) are online. If disabled, the device is considered online if any NIC is online. If a NIC is online it sets the parent (this) device's status to online irrespectivelly of the detected device's status. The Relationship type is set on the childern device.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> Please read through the [NTFPRCS plugin](https://github.com/netalertx/NetAlertX/blob/main/front/plugins/notification_processing/README.md) documentation to understand how device and global settings influence the notification processing.
|
> Please read through the [NTFPRCS plugin](https://github.com/netalertx/NetAlertX/blob/main/front/plugins/notification_processing/README.md) documentation to understand how device and global settings influence the notification processing.
|
||||||
@@ -44,6 +45,7 @@ In Notification Processing settings, you can specify blanket rules. These allow
|
|||||||
|
|
||||||
1. Notify on (`NTFPRCS_INCLUDED_SECTIONS`) allows you to specify which events trigger notifications. Usual setups will have `new_devices`, `down_devices`, and possibly `down_reconnected` set. Including `plugin` (dependenton the Plugin `<plugin>_WATCH` and `<plugin>_REPORT_ON` settings) and `events` (dependent on the on-device **Alert Events** setting) might be too noisy for most setups. More info in the [NTFPRCS plugin](https://github.com/netalertx/NetAlertX/blob/main/front/plugins/notification_processing/README.md) on what events these selections include.
|
1. Notify on (`NTFPRCS_INCLUDED_SECTIONS`) allows you to specify which events trigger notifications. Usual setups will have `new_devices`, `down_devices`, and possibly `down_reconnected` set. Including `plugin` (dependenton the Plugin `<plugin>_WATCH` and `<plugin>_REPORT_ON` settings) and `events` (dependent on the on-device **Alert Events** setting) might be too noisy for most setups. More info in the [NTFPRCS plugin](https://github.com/netalertx/NetAlertX/blob/main/front/plugins/notification_processing/README.md) on what events these selections include.
|
||||||
2. Alert down after (`NTFPRCS_alert_down_time`) is useful if you want to wait for some time before the system sends out a down notification for a device. This is related to the on-device **Alert down** setting and only devices with this checked will trigger a down notification.
|
2. Alert down after (`NTFPRCS_alert_down_time`) is useful if you want to wait for some time before the system sends out a down notification for a device. This is related to the on-device **Alert down** setting and only devices with this checked will trigger a down notification.
|
||||||
|
3. Alert down after (sleep) (`NTFPRCS_sleep_time`) sets the **sleep window** in minutes. If a device has **Can Sleep** enabled and goes offline, it is shown as **Sleeping** (aqua 🌙 badge) for this many minutes before down-alert logic kicks in. Default is `30` minutes. Changing this setting takes effect after saving — no restart required.
|
||||||
|
|
||||||
You can filter out unwanted notifications globally. This could be because of a misbehaving device (GoogleNest/GoogleHub (See also [ARPSAN docs and the `--exclude-broadcast` flag](https://github.com/netalertx/NetAlertX/tree/main/front/plugins/arp_scan#ip-flipping-on-google-nest-devices))) which flips between IP addresses, or because you want to ignore new device notifications of a certain pattern.
|
You can filter out unwanted notifications globally. This could be because of a misbehaving device (GoogleNest/GoogleHub (See also [ARPSAN docs and the `--exclude-broadcast` flag](https://github.com/netalertx/NetAlertX/tree/main/front/plugins/arp_scan#ip-flipping-on-google-nest-devices))) which flips between IP addresses, or because you want to ignore new device notifications of a certain pattern.
|
||||||
|
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ function getDeviceData() {
|
|||||||
},
|
},
|
||||||
// Group for event and alert settings
|
// Group for event and alert settings
|
||||||
DevDetail_EveandAl_Title: {
|
DevDetail_EveandAl_Title: {
|
||||||
data: ["devAlertEvents", "devAlertDown", "devSkipRepeated", "devReqNicsOnline", "devChildrenNicsDynamic", "devForceStatus"],
|
data: ["devAlertEvents", "devAlertDown", "devCanSleep", "devSkipRepeated", "devReqNicsOnline", "devChildrenNicsDynamic", "devForceStatus"],
|
||||||
docs: "https://docs.netalertx.com/NOTIFICATIONS",
|
docs: "https://docs.netalertx.com/NOTIFICATIONS",
|
||||||
iconClass: "fa fa-bell",
|
iconClass: "fa fa-bell",
|
||||||
inputGroupClasses: "field-group alert-group col-lg-4 col-sm-6 col-xs-12",
|
inputGroupClasses: "field-group alert-group col-lg-4 col-sm-6 col-xs-12",
|
||||||
@@ -447,6 +447,7 @@ function setDeviceData(direction = '', refreshCallback = '') {
|
|||||||
|
|
||||||
devAlertEvents: ($('#NEWDEV_devAlertEvents')[0].checked * 1),
|
devAlertEvents: ($('#NEWDEV_devAlertEvents')[0].checked * 1),
|
||||||
devAlertDown: ($('#NEWDEV_devAlertDown')[0].checked * 1),
|
devAlertDown: ($('#NEWDEV_devAlertDown')[0].checked * 1),
|
||||||
|
devCanSleep: ($('#NEWDEV_devCanSleep')[0].checked * 1),
|
||||||
devSkipRepeated: $('#NEWDEV_devSkipRepeated').val().split(' ')[0],
|
devSkipRepeated: $('#NEWDEV_devSkipRepeated').val().split(' ')[0],
|
||||||
devForceStatus: $('#NEWDEV_devForceStatus').val().replace(/'/g, ""),
|
devForceStatus: $('#NEWDEV_devForceStatus').val().replace(/'/g, ""),
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ function loadNetworkNodes() {
|
|||||||
parent.devIcon AS node_icon,
|
parent.devIcon AS node_icon,
|
||||||
parent.devAlertDown AS node_alert,
|
parent.devAlertDown AS node_alert,
|
||||||
parent.devFlapping AS node_flapping,
|
parent.devFlapping AS node_flapping,
|
||||||
|
parent.devIsSleeping AS node_sleeping,
|
||||||
COUNT(child.devMac) AS node_ports_count
|
COUNT(child.devMac) AS node_ports_count
|
||||||
FROM DevicesView AS parent
|
FROM DevicesView AS parent
|
||||||
LEFT JOIN DevicesView AS child
|
LEFT JOIN DevicesView AS child
|
||||||
@@ -34,7 +35,7 @@ function loadNetworkNodes() {
|
|||||||
WHERE parent.devType IN (${networkDeviceTypes})
|
WHERE parent.devType IN (${networkDeviceTypes})
|
||||||
AND parent.devIsArchived = 0
|
AND parent.devIsArchived = 0
|
||||||
GROUP BY parent.devMac, parent.devName, parent.devPresentLastScan,
|
GROUP BY parent.devMac, parent.devName, parent.devPresentLastScan,
|
||||||
parent.devType, parent.devParentMAC, parent.devIcon, parent.devAlertDown, parent.devFlapping
|
parent.devType, parent.devParentMAC, parent.devIcon, parent.devAlertDown, parent.devFlapping, parent.devIsSleeping
|
||||||
ORDER BY parent.devName;
|
ORDER BY parent.devName;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -146,7 +147,8 @@ function loadDeviceTable({ sql, containerSelector, tableId, wrapperHtml = null,
|
|||||||
device.devAlertDown,
|
device.devAlertDown,
|
||||||
device.devFlapping,
|
device.devFlapping,
|
||||||
device.devMac,
|
device.devMac,
|
||||||
device.devStatus
|
device.devStatus,
|
||||||
|
device.devIsSleeping || 0
|
||||||
);
|
);
|
||||||
return `<a href="${badge.url}" class="badge ${badge.cssClass}">${badge.iconHtml} ${badge.text}</a>`;
|
return `<a href="${badge.url}" class="badge ${badge.cssClass}">${badge.iconHtml} ${badge.text}</a>`;
|
||||||
}
|
}
|
||||||
@@ -204,7 +206,7 @@ function loadDeviceTable({ sql, containerSelector, tableId, wrapperHtml = null,
|
|||||||
*/
|
*/
|
||||||
function loadUnassignedDevices() {
|
function loadUnassignedDevices() {
|
||||||
const sql = `
|
const sql = `
|
||||||
SELECT devMac, devPresentLastScan, devName, devLastIP, devVendor, devAlertDown, devParentPort, devFlapping, devStatus
|
SELECT devMac, devPresentLastScan, devName, devLastIP, devVendor, devAlertDown, devParentPort, devFlapping, devIsSleeping, devStatus
|
||||||
FROM DevicesView
|
FROM DevicesView
|
||||||
WHERE (devParentMAC IS NULL OR devParentMAC IN ("", " ", "undefined", "null"))
|
WHERE (devParentMAC IS NULL OR devParentMAC IN ("", " ", "undefined", "null"))
|
||||||
AND LOWER(devMac) NOT LIKE "%internet%"
|
AND LOWER(devMac) NOT LIKE "%internet%"
|
||||||
@@ -239,10 +241,11 @@ function loadConnectedDevices(node_mac) {
|
|||||||
const normalized_mac = node_mac.toLowerCase();
|
const normalized_mac = node_mac.toLowerCase();
|
||||||
|
|
||||||
const sql = `
|
const sql = `
|
||||||
SELECT devName, devMac, devLastIP, devVendor, devPresentLastScan, devAlertDown, devParentPort, devVlan, devFlapping,
|
SELECT devName, devMac, devLastIP, devVendor, devPresentLastScan, devAlertDown, devParentPort, devVlan, devFlapping, devIsSleeping,
|
||||||
CASE
|
CASE
|
||||||
WHEN devIsNew = 1 THEN 'New'
|
WHEN devIsNew = 1 THEN 'New'
|
||||||
WHEN devPresentLastScan = 1 THEN 'On-line'
|
WHEN devPresentLastScan = 1 THEN 'On-line'
|
||||||
|
WHEN devIsSleeping = 1 THEN 'Sleeping'
|
||||||
WHEN devPresentLastScan = 0 AND devAlertDown != 0 THEN 'Down'
|
WHEN devPresentLastScan = 0 AND devAlertDown != 0 THEN 'Down'
|
||||||
WHEN devIsArchived = 1 THEN 'Archived'
|
WHEN devIsArchived = 1 THEN 'Archived'
|
||||||
WHEN devPresentLastScan = 0 THEN 'Off-line'
|
WHEN devPresentLastScan = 0 THEN 'Off-line'
|
||||||
|
|||||||
@@ -24,8 +24,9 @@ function initNetworkTopology() {
|
|||||||
LOWER(devMac) AS devMac,
|
LOWER(devMac) AS devMac,
|
||||||
LOWER(devParentMAC) AS devParentMAC,
|
LOWER(devParentMAC) AS devParentMAC,
|
||||||
CASE
|
CASE
|
||||||
WHEN devAlertDown != 0 AND devPresentLastScan = 0 THEN 'Down'
|
|
||||||
WHEN devPresentLastScan = 1 THEN 'On-line'
|
WHEN devPresentLastScan = 1 THEN 'On-line'
|
||||||
|
WHEN devIsSleeping = 1 THEN 'Sleeping'
|
||||||
|
WHEN devAlertDown != 0 AND devPresentLastScan = 0 THEN 'Down'
|
||||||
ELSE 'Off-line'
|
ELSE 'Off-line'
|
||||||
END AS devStatus,
|
END AS devStatus,
|
||||||
CASE
|
CASE
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ function renderNetworkTabs(nodes) {
|
|||||||
let html = '';
|
let html = '';
|
||||||
nodes.forEach((node, i) => {
|
nodes.forEach((node, i) => {
|
||||||
const iconClass = node.online == 1 ? "text-green" :
|
const iconClass = node.online == 1 ? "text-green" :
|
||||||
(node.node_alert == 1 ? "text-red" : "text-gray50");
|
(node.node_sleeping == 1 ? "text-aqua" :
|
||||||
|
(node.node_alert == 1 ? "text-red" : "text-gray50"));
|
||||||
|
|
||||||
const portLabel = node.node_ports_count ? ` (${node.node_ports_count})` : '';
|
const portLabel = node.node_ports_count ? ` (${node.node_ports_count})` : '';
|
||||||
const icon = atob(node.node_icon);
|
const icon = atob(node.node_icon);
|
||||||
@@ -55,7 +56,9 @@ function renderNetworkTabContent(nodes) {
|
|||||||
node.online,
|
node.online,
|
||||||
node.node_alert,
|
node.node_alert,
|
||||||
node.node_flapping,
|
node.node_flapping,
|
||||||
node.node_mac
|
node.node_mac,
|
||||||
|
'',
|
||||||
|
node.node_sleeping || 0
|
||||||
);
|
);
|
||||||
|
|
||||||
const badgeHtml = `<a href="${badge.url}" class="badge ${badge.cssClass}">${badge.iconHtml} ${badge.status}</a>`;
|
const badgeHtml = `<a href="${badge.url}" class="badge ${badge.cssClass}">${badge.iconHtml} ${badge.status}</a>`;
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ function getChildren(node, list, path, visited = [])
|
|||||||
presentLastScan: node.devPresentLastScan,
|
presentLastScan: node.devPresentLastScan,
|
||||||
flapping: node.devFlapping,
|
flapping: node.devFlapping,
|
||||||
alertDown: node.devAlertDown,
|
alertDown: node.devAlertDown,
|
||||||
|
sleeping: node.devIsSleeping || 0,
|
||||||
hasChildren: children.length > 0 || hiddenMacs.includes(node.devMac),
|
hasChildren: children.length > 0 || hiddenMacs.includes(node.devMac),
|
||||||
relType: node.devParentRelType,
|
relType: node.devParentRelType,
|
||||||
devVlan: node.devVlan,
|
devVlan: node.devVlan,
|
||||||
@@ -272,7 +273,8 @@ function initTree(myHierarchy)
|
|||||||
nodeData.data.alertDown,
|
nodeData.data.alertDown,
|
||||||
nodeData.data.flapping,
|
nodeData.data.flapping,
|
||||||
nodeData.data.mac,
|
nodeData.data.mac,
|
||||||
statusText = ''
|
'',
|
||||||
|
nodeData.data.sleeping || 0
|
||||||
);
|
);
|
||||||
|
|
||||||
return result = `<div
|
return result = `<div
|
||||||
@@ -291,6 +293,7 @@ function initTree(myHierarchy)
|
|||||||
data-firstseen="${nodeData.data.firstseen}"
|
data-firstseen="${nodeData.data.firstseen}"
|
||||||
data-relationship="${nodeData.data.relType}"
|
data-relationship="${nodeData.data.relType}"
|
||||||
data-flapping="${nodeData.data.flapping}"
|
data-flapping="${nodeData.data.flapping}"
|
||||||
|
data-sleeping="${nodeData.data.sleeping || 0}"
|
||||||
data-status="${nodeData.data.status}"
|
data-status="${nodeData.data.status}"
|
||||||
data-present="${nodeData.data.presentLastScan}"
|
data-present="${nodeData.data.presentLastScan}"
|
||||||
data-alert="${nodeData.data.alertDown}"
|
data-alert="${nodeData.data.alertDown}"
|
||||||
|
|||||||
@@ -738,7 +738,7 @@ function getColumnNameFromLangString(headStringKey) {
|
|||||||
|
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
// Generating the device status chip
|
// Generating the device status chip
|
||||||
function getStatusBadgeParts(devPresentLastScan, devAlertDown, devFlapping, devMac, statusText = '') {
|
function getStatusBadgeParts(devPresentLastScan, devAlertDown, devFlapping, devMac, statusText = '', devIsSleeping = 0) {
|
||||||
let css = 'bg-gray text-white statusUnknown';
|
let css = 'bg-gray text-white statusUnknown';
|
||||||
let icon = '<i class="fa-solid fa-question"></i>';
|
let icon = '<i class="fa-solid fa-question"></i>';
|
||||||
let status = 'unknown';
|
let status = 'unknown';
|
||||||
@@ -754,6 +754,11 @@ function getStatusBadgeParts(devPresentLastScan, devAlertDown, devFlapping, devM
|
|||||||
cssText = 'text-yellow';
|
cssText = 'text-yellow';
|
||||||
icon = '<i class="fa-solid fa-plug-circle-exclamation"></i>';
|
icon = '<i class="fa-solid fa-plug-circle-exclamation"></i>';
|
||||||
status = 'online';
|
status = 'online';
|
||||||
|
} else if (devIsSleeping == 1) {
|
||||||
|
css = 'bg-aqua text-white statusSleeping';
|
||||||
|
cssText = 'text-aqua';
|
||||||
|
icon = '<i class="fa-solid fa-moon"></i>';
|
||||||
|
status = 'sleeping';
|
||||||
} else if (devAlertDown == 1) {
|
} else if (devAlertDown == 1) {
|
||||||
css = 'bg-red text-white statusDown';
|
css = 'bg-red text-white statusDown';
|
||||||
cssText = 'text-red';
|
cssText = 'text-red';
|
||||||
@@ -929,7 +934,9 @@ function renderDeviceLink(data, container, useName = false) {
|
|||||||
device.devPresentLastScan,
|
device.devPresentLastScan,
|
||||||
device.devAlertDown,
|
device.devAlertDown,
|
||||||
device.devFlapping,
|
device.devFlapping,
|
||||||
device.devMac
|
device.devMac,
|
||||||
|
'',
|
||||||
|
device.devIsSleeping || 0
|
||||||
);
|
);
|
||||||
|
|
||||||
// badge class and hover-info class to container
|
// badge class and hover-info class to container
|
||||||
@@ -948,6 +955,7 @@ function renderDeviceLink(data, container, useName = false) {
|
|||||||
'data-flapping': device.devFlapping,
|
'data-flapping': device.devFlapping,
|
||||||
'data-present': device.devPresentLastScan,
|
'data-present': device.devPresentLastScan,
|
||||||
'data-alert': device.devAlertDown,
|
'data-alert': device.devAlertDown,
|
||||||
|
'data-sleeping': device.devIsSleeping || 0,
|
||||||
'data-icon': device.devIcon
|
'data-icon': device.devIcon
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1017,7 +1025,8 @@ function initHoverNodeInfo() {
|
|||||||
const firstseen = $el.data('firstseen') || 'Unknown';
|
const firstseen = $el.data('firstseen') || 'Unknown';
|
||||||
const relationship = $el.data('relationship') || 'Unknown';
|
const relationship = $el.data('relationship') || 'Unknown';
|
||||||
const flapping = $el.data('flapping') || 0;
|
const flapping = $el.data('flapping') || 0;
|
||||||
const badge = getStatusBadgeParts( $el.data('present'), $el.data('alert'), flapping, $el.data('mac'))
|
const sleeping = $el.data('sleeping') || 0;
|
||||||
|
const badge = getStatusBadgeParts( $el.data('present'), $el.data('alert'), flapping, $el.data('mac'), '', sleeping)
|
||||||
const status =`<span class="badge ${badge.cssClass}">${badge.iconHtml} ${badge.status}</span>`
|
const status =`<span class="badge ${badge.cssClass}">${badge.iconHtml} ${badge.status}</span>`
|
||||||
|
|
||||||
const html = `
|
const html = `
|
||||||
|
|||||||
@@ -1191,6 +1191,41 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"function": "devCanSleep",
|
||||||
|
"type": {
|
||||||
|
"dataType": "integer",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"elementType": "input",
|
||||||
|
"elementOptions": [
|
||||||
|
{
|
||||||
|
"type": "checkbox"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"transformers": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_value": 0,
|
||||||
|
"options": [],
|
||||||
|
"localized": [
|
||||||
|
"name",
|
||||||
|
"description"
|
||||||
|
],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Can Sleep"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "When enabled, the device will appear as <em>Sleeping</em> instead of <em>Down</em> or <em>Off-line</em> while it has been absent for less than the <b>Sleep Window</b> (<code>NTFPRCS_sleep_time</code>). Once the sleep window expires the device becomes subject to the normal <code>Alert Down</code> condition. Database column name: <code>devCanSleep</code>."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"function": "devSkipRepeated",
|
"function": "devSkipRepeated",
|
||||||
"type": {
|
"type": {
|
||||||
|
|||||||
@@ -105,6 +105,34 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"function": "sleep_time",
|
||||||
|
"type": {
|
||||||
|
"dataType": "integer",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"elementType": "input",
|
||||||
|
"elementOptions": [{ "type": "number" }],
|
||||||
|
"transformers": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_value": 30,
|
||||||
|
"options": [],
|
||||||
|
"localized": ["name", "description"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Sleep Window"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "How many minutes a device with <code>Can Sleep</code> enabled is shown as <em>Sleeping</em> before it becomes subject to the <code>Alert Down</code> condition. Changes take effect after saving settings."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"function": "new_dev_condition",
|
"function": "new_dev_condition",
|
||||||
"type": {
|
"type": {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ let fieldOptions = [
|
|||||||
"devName", "devMac", "devOwner", "devType", "devVendor", "devVlan", "devFavorite",
|
"devName", "devMac", "devOwner", "devType", "devVendor", "devVlan", "devFavorite",
|
||||||
"devGroup", "devComments", "devFirstConnection", "devLastConnection",
|
"devGroup", "devComments", "devFirstConnection", "devLastConnection",
|
||||||
"devLastIP", "devStaticIP", "devScan", "devLogEvents", "devAlertEvents",
|
"devLastIP", "devStaticIP", "devScan", "devLogEvents", "devAlertEvents",
|
||||||
"devAlertDown", "devSkipRepeated", "devLastNotification", "devPresentLastScan",
|
"devAlertDown", "devCanSleep", "devSkipRepeated", "devLastNotification", "devPresentLastScan",
|
||||||
"devIsNew", "devLocation", "devIsArchived", "devParentMAC", "devParentPort",
|
"devIsNew", "devLocation", "devIsArchived", "devParentMAC", "devParentPort",
|
||||||
"devIcon", "devSite", "devSSID", "devSyncHubNode", "devSourcePlugin", "devFQDN",
|
"devIcon", "devSite", "devSSID", "devSyncHubNode", "devSourcePlugin", "devFQDN",
|
||||||
"devParentRelType", "devReqNicsOnline", "devNameSource", "devVendorSource" ,
|
"devParentRelType", "devReqNicsOnline", "devNameSource", "devVendorSource" ,
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ DEFAULT_HEADER = [
|
|||||||
"devLogEvents",
|
"devLogEvents",
|
||||||
"devAlertEvents",
|
"devAlertEvents",
|
||||||
"devAlertDown",
|
"devAlertDown",
|
||||||
|
"devCanSleep",
|
||||||
"devSkipRepeated",
|
"devSkipRepeated",
|
||||||
"devLastNotification",
|
"devLastNotification",
|
||||||
"devPresentLastScan",
|
"devPresentLastScan",
|
||||||
@@ -185,6 +186,7 @@ def build_row(
|
|||||||
"devLogEvents": "1",
|
"devLogEvents": "1",
|
||||||
"devAlertEvents": "1",
|
"devAlertEvents": "1",
|
||||||
"devAlertDown": "0",
|
"devAlertDown": "0",
|
||||||
|
"devCanSleep": "0",
|
||||||
"devSkipRepeated": "0",
|
"devSkipRepeated": "0",
|
||||||
"devLastNotification": "",
|
"devLastNotification": "",
|
||||||
"devPresentLastScan": "0",
|
"devPresentLastScan": "0",
|
||||||
|
|||||||
@@ -101,6 +101,8 @@ class Device(ObjectType):
|
|||||||
devParentRelTypeSource = String(description="Source tracking for devParentRelType (USER, LOCKED, NEWDEV, or plugin prefix)")
|
devParentRelTypeSource = String(description="Source tracking for devParentRelType (USER, LOCKED, NEWDEV, or plugin prefix)")
|
||||||
devVlanSource = String(description="Source tracking for devVlan")
|
devVlanSource = String(description="Source tracking for devVlan")
|
||||||
devFlapping = Int(description="Indicates flapping device (device changing between online/offline states frequently)")
|
devFlapping = Int(description="Indicates flapping device (device changing between online/offline states frequently)")
|
||||||
|
devCanSleep = Int(description="Can this device sleep? (0 or 1). When enabled, offline periods within NTFPRCS_sleep_time are reported as Sleeping instead of Down.")
|
||||||
|
devIsSleeping = Int(description="Computed: Is device currently in a sleep window? (0 or 1)")
|
||||||
|
|
||||||
|
|
||||||
class DeviceResult(ObjectType):
|
class DeviceResult(ObjectType):
|
||||||
@@ -247,7 +249,7 @@ class Query(ObjectType):
|
|||||||
)
|
)
|
||||||
|
|
||||||
is_down = (
|
is_down = (
|
||||||
device["devPresentLastScan"] == 0 and device["devAlertDown"] and "down" in allowed_statuses
|
device["devPresentLastScan"] == 0 and device["devAlertDown"] and device.get("devIsSleeping", 0) == 0 and "down" in allowed_statuses
|
||||||
)
|
)
|
||||||
|
|
||||||
is_offline = (
|
is_offline = (
|
||||||
@@ -282,11 +284,17 @@ class Query(ObjectType):
|
|||||||
devices_data = [
|
devices_data = [
|
||||||
device for device in devices_data if device["devIsNew"] == 1 and device["devIsArchived"] == 0
|
device for device in devices_data if device["devIsNew"] == 1 and device["devIsArchived"] == 0
|
||||||
]
|
]
|
||||||
|
elif status == "sleeping":
|
||||||
|
devices_data = [
|
||||||
|
device
|
||||||
|
for device in devices_data
|
||||||
|
if device.get("devIsSleeping", 0) == 1 and device["devIsArchived"] == 0
|
||||||
|
]
|
||||||
elif status == "down":
|
elif status == "down":
|
||||||
devices_data = [
|
devices_data = [
|
||||||
device
|
device
|
||||||
for device in devices_data
|
for device in devices_data
|
||||||
if device["devPresentLastScan"] == 0 and device["devAlertDown"] and device["devIsArchived"] == 0
|
if device["devPresentLastScan"] == 0 and device["devAlertDown"] and device.get("devIsSleeping", 0) == 0 and device["devIsArchived"] == 0
|
||||||
]
|
]
|
||||||
elif status == "archived":
|
elif status == "archived":
|
||||||
devices_data = [
|
devices_data = [
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ COLUMN_NAME_PATTERN = re.compile(r"^[a-zA-Z0-9_]+$")
|
|||||||
ALLOWED_DEVICE_COLUMNS = Literal[
|
ALLOWED_DEVICE_COLUMNS = Literal[
|
||||||
"devName", "devOwner", "devType", "devVendor",
|
"devName", "devOwner", "devType", "devVendor",
|
||||||
"devGroup", "devLocation", "devComments", "devFavorite",
|
"devGroup", "devLocation", "devComments", "devFavorite",
|
||||||
"devParentMAC"
|
"devParentMAC", "devCanSleep"
|
||||||
]
|
]
|
||||||
|
|
||||||
ALLOWED_NMAP_MODES = Literal[
|
ALLOWED_NMAP_MODES = Literal[
|
||||||
@@ -204,9 +204,19 @@ class DeviceInfo(BaseModel):
|
|||||||
description="Present in last scan (0 or 1)",
|
description="Present in last scan (0 or 1)",
|
||||||
json_schema_extra={"enum": [0, 1]}
|
json_schema_extra={"enum": [0, 1]}
|
||||||
)
|
)
|
||||||
devStatus: Optional[Literal["online", "offline"]] = Field(
|
devStatus: Optional[Literal["online", "offline", "sleeping"]] = Field(
|
||||||
None,
|
None,
|
||||||
description="Online/Offline status"
|
description="Online/Offline/Sleeping status"
|
||||||
|
)
|
||||||
|
devCanSleep: Optional[int] = Field(
|
||||||
|
0,
|
||||||
|
description="Can device sleep? (0=No, 1=Yes). When enabled, offline periods within NTFPRCS_sleep_time window are shown as Sleeping.",
|
||||||
|
json_schema_extra={"enum": [0, 1]}
|
||||||
|
)
|
||||||
|
devIsSleeping: Optional[int] = Field(
|
||||||
|
0,
|
||||||
|
description="Computed: Is device currently in a sleep window? (0=No, 1=Yes)",
|
||||||
|
json_schema_extra={"enum": [0, 1]}
|
||||||
)
|
)
|
||||||
devMacSource: Optional[str] = Field(None, description="Source of devMac (USER, LOCKED, or plugin prefix)")
|
devMacSource: Optional[str] = Field(None, description="Source of devMac (USER, LOCKED, or plugin prefix)")
|
||||||
devNameSource: Optional[str] = Field(None, description="Source of devName")
|
devNameSource: Optional[str] = Field(None, description="Source of devName")
|
||||||
@@ -228,14 +238,15 @@ class DeviceSearchResponse(BaseResponse):
|
|||||||
class DeviceListRequest(BaseModel):
|
class DeviceListRequest(BaseModel):
|
||||||
"""Request for listing devices by status."""
|
"""Request for listing devices by status."""
|
||||||
status: Optional[Literal[
|
status: Optional[Literal[
|
||||||
"connected", "down", "favorites", "new", "archived", "all", "my",
|
"connected", "down", "sleeping", "favorites", "new", "archived", "all", "my",
|
||||||
"offline"
|
"offline"
|
||||||
]] = Field(
|
]] = Field(
|
||||||
None,
|
None,
|
||||||
description=(
|
description=(
|
||||||
"Filter devices by status:\n"
|
"Filter devices by status:\n"
|
||||||
"- connected: Active devices present in the last scan\n"
|
"- connected: Active devices present in the last scan\n"
|
||||||
"- down: Devices with active 'Device Down' alert\n"
|
"- down: Devices with active 'Device Down' alert (excludes sleeping)\n"
|
||||||
|
"- sleeping: Devices in a sleep window (devCanSleep=1, offline within NTFPRCS_sleep_time)\n"
|
||||||
"- favorites: Devices marked as favorite\n"
|
"- favorites: Devices marked as favorite\n"
|
||||||
"- new: Devices flagged as new\n"
|
"- new: Devices flagged as new\n"
|
||||||
"- archived: Devices moved to archive\n"
|
"- archived: Devices moved to archive\n"
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ from db.db_helper import get_table_json, json_obj
|
|||||||
from workflows.app_events import AppEvent_obj
|
from workflows.app_events import AppEvent_obj
|
||||||
from db.db_upgrade import (
|
from db.db_upgrade import (
|
||||||
ensure_column,
|
ensure_column,
|
||||||
ensure_views,
|
|
||||||
ensure_CurrentScan,
|
ensure_CurrentScan,
|
||||||
ensure_plugins_tables,
|
ensure_plugins_tables,
|
||||||
ensure_Parameters,
|
ensure_Parameters,
|
||||||
@@ -192,6 +191,8 @@ class DB:
|
|||||||
raise RuntimeError("ensure_column(devParentRelTypeSource) failed")
|
raise RuntimeError("ensure_column(devParentRelTypeSource) failed")
|
||||||
if not ensure_column(self.sql, "Devices", "devVlanSource", "TEXT"):
|
if not ensure_column(self.sql, "Devices", "devVlanSource", "TEXT"):
|
||||||
raise RuntimeError("ensure_column(devVlanSource) failed")
|
raise RuntimeError("ensure_column(devVlanSource) failed")
|
||||||
|
if not ensure_column(self.sql, "Devices", "devCanSleep", "INTEGER"):
|
||||||
|
raise RuntimeError("ensure_column(devCanSleep) failed")
|
||||||
|
|
||||||
# Settings table setup
|
# Settings table setup
|
||||||
ensure_Settings(self.sql)
|
ensure_Settings(self.sql)
|
||||||
@@ -208,8 +209,9 @@ class DB:
|
|||||||
# CurrentScan table setup
|
# CurrentScan table setup
|
||||||
ensure_CurrentScan(self.sql)
|
ensure_CurrentScan(self.sql)
|
||||||
|
|
||||||
# Views
|
# Views are created in importConfigs() after settings are committed,
|
||||||
ensure_views(self.sql)
|
# so NTFPRCS_sleep_time is available when the view is built.
|
||||||
|
# ensure_views is NOT called here.
|
||||||
|
|
||||||
# Indexes
|
# Indexes
|
||||||
ensure_Indexes(self.sql)
|
ensure_Indexes(self.sql)
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ def get_device_conditions():
|
|||||||
"connected": "WHERE devPresentLastScan=1",
|
"connected": "WHERE devPresentLastScan=1",
|
||||||
"favorites": f"WHERE {base_active} AND devFavorite=1",
|
"favorites": f"WHERE {base_active} AND devFavorite=1",
|
||||||
"new": f"WHERE {base_active} AND devIsNew=1",
|
"new": f"WHERE {base_active} AND devIsNew=1",
|
||||||
"down": f"WHERE {base_active} AND devAlertDown != 0 AND devPresentLastScan=0",
|
"sleeping": f"WHERE {base_active} AND devIsSleeping=1",
|
||||||
|
"down": f"WHERE {base_active} AND devAlertDown != 0 AND devPresentLastScan=0 AND devIsSleeping=0",
|
||||||
"offline": f"WHERE {base_active} AND devPresentLastScan=0",
|
"offline": f"WHERE {base_active} AND devPresentLastScan=0",
|
||||||
"archived": "WHERE devIsArchived=1",
|
"archived": "WHERE devIsArchived=1",
|
||||||
"network_devices": f"WHERE {base_active} AND devType IN ({network_dev_types})",
|
"network_devices": f"WHERE {base_active} AND devType IN ({network_dev_types})",
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ EXPECTED_DEVICES_COLUMNS = [
|
|||||||
"devLogEvents",
|
"devLogEvents",
|
||||||
"devAlertEvents",
|
"devAlertEvents",
|
||||||
"devAlertDown",
|
"devAlertDown",
|
||||||
|
"devCanSleep",
|
||||||
"devSkipRepeated",
|
"devSkipRepeated",
|
||||||
"devLastNotification",
|
"devLastNotification",
|
||||||
"devPresentLastScan",
|
"devPresentLastScan",
|
||||||
@@ -235,8 +236,22 @@ def ensure_views(sql) -> bool:
|
|||||||
FLAP_THRESHOLD = 3
|
FLAP_THRESHOLD = 3
|
||||||
FLAP_WINDOW_HOURS = 1
|
FLAP_WINDOW_HOURS = 1
|
||||||
|
|
||||||
|
# Read sleep window from settings; fall back to 30 min if not yet configured.
|
||||||
|
# Uses the same sql cursor (no separate connection) to avoid lock contention.
|
||||||
|
# Note: changing NTFPRCS_sleep_time requires a restart to take effect,
|
||||||
|
# same behaviour as FLAP_THRESHOLD / FLAP_WINDOW_HOURS.
|
||||||
|
try:
|
||||||
|
sql.execute("SELECT setValue FROM Settings WHERE setKey = 'NTFPRCS_sleep_time'")
|
||||||
|
_sleep_row = sql.fetchone()
|
||||||
|
SLEEP_MINUTES = int(_sleep_row[0]) if _sleep_row and _sleep_row[0] else 30
|
||||||
|
except Exception:
|
||||||
|
SLEEP_MINUTES = 30
|
||||||
|
|
||||||
sql.execute(""" DROP VIEW IF EXISTS DevicesView;""")
|
sql.execute(""" DROP VIEW IF EXISTS DevicesView;""")
|
||||||
sql.execute(f""" CREATE VIEW DevicesView AS
|
sql.execute(f""" CREATE VIEW DevicesView AS
|
||||||
|
-- CTE computes devIsSleeping and devFlapping so devStatus can
|
||||||
|
-- reference them without duplicating the sub-expressions.
|
||||||
|
WITH base AS (
|
||||||
SELECT
|
SELECT
|
||||||
rowid,
|
rowid,
|
||||||
IFNULL(devMac, '') AS devMac,
|
IFNULL(devMac, '') AS devMac,
|
||||||
@@ -259,6 +274,7 @@ def ensure_views(sql) -> bool:
|
|||||||
IFNULL(devLogEvents, '') AS devLogEvents,
|
IFNULL(devLogEvents, '') AS devLogEvents,
|
||||||
IFNULL(devAlertEvents, '') AS devAlertEvents,
|
IFNULL(devAlertEvents, '') AS devAlertEvents,
|
||||||
IFNULL(devAlertDown, '') AS devAlertDown,
|
IFNULL(devAlertDown, '') AS devAlertDown,
|
||||||
|
IFNULL(devCanSleep, 0) AS devCanSleep,
|
||||||
IFNULL(devSkipRepeated, '') AS devSkipRepeated,
|
IFNULL(devSkipRepeated, '') AS devSkipRepeated,
|
||||||
IFNULL(devLastNotification, '') AS devLastNotification,
|
IFNULL(devLastNotification, '') AS devLastNotification,
|
||||||
IFNULL(devPresentLastScan, 0) AS devPresentLastScan,
|
IFNULL(devPresentLastScan, 0) AS devPresentLastScan,
|
||||||
@@ -287,14 +303,15 @@ def ensure_views(sql) -> bool:
|
|||||||
IFNULL(devParentPortSource, '') AS devParentPortSource,
|
IFNULL(devParentPortSource, '') AS devParentPortSource,
|
||||||
IFNULL(devParentRelTypeSource, '') AS devParentRelTypeSource,
|
IFNULL(devParentRelTypeSource, '') AS devParentRelTypeSource,
|
||||||
IFNULL(devVlanSource, '') AS devVlanSource,
|
IFNULL(devVlanSource, '') AS devVlanSource,
|
||||||
|
-- devIsSleeping: opted-in, absent, and still within the sleep window
|
||||||
CASE
|
CASE
|
||||||
WHEN devIsNew = 1 THEN 'New'
|
WHEN devCanSleep = 1
|
||||||
WHEN devPresentLastScan = 1 THEN 'On-line'
|
AND devPresentLastScan = 0
|
||||||
WHEN devPresentLastScan = 0 AND devAlertDown != 0 THEN 'Down'
|
AND devLastConnection >= datetime('now', '-{SLEEP_MINUTES} minutes')
|
||||||
WHEN devIsArchived = 1 THEN 'Archived'
|
THEN 1
|
||||||
WHEN devPresentLastScan = 0 THEN 'Off-line'
|
ELSE 0
|
||||||
ELSE 'Unknown status'
|
END AS devIsSleeping,
|
||||||
END AS devStatus,
|
-- devFlapping: toggling online/offline frequently within the flap window
|
||||||
CASE
|
CASE
|
||||||
WHEN EXISTS (
|
WHEN EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
@@ -308,8 +325,20 @@ def ensure_views(sql) -> bool:
|
|||||||
THEN 1
|
THEN 1
|
||||||
ELSE 0
|
ELSE 0
|
||||||
END AS devFlapping
|
END AS devFlapping
|
||||||
|
FROM Devices
|
||||||
FROM Devices
|
)
|
||||||
|
SELECT *,
|
||||||
|
-- devStatus references devIsSleeping from the CTE (no duplication)
|
||||||
|
CASE
|
||||||
|
WHEN devIsNew = 1 THEN 'New'
|
||||||
|
WHEN devPresentLastScan = 1 THEN 'On-line'
|
||||||
|
WHEN devIsSleeping = 1 THEN 'Sleeping'
|
||||||
|
WHEN devAlertDown != 0 THEN 'Down'
|
||||||
|
WHEN devIsArchived = 1 THEN 'Archived'
|
||||||
|
WHEN devPresentLastScan = 0 THEN 'Off-line'
|
||||||
|
ELSE 'Unknown status'
|
||||||
|
END AS devStatus
|
||||||
|
FROM base
|
||||||
|
|
||||||
""")
|
""")
|
||||||
|
|
||||||
@@ -381,6 +410,10 @@ def ensure_Indexes(sql) -> bool:
|
|||||||
"idx_dev_alertdown",
|
"idx_dev_alertdown",
|
||||||
"CREATE INDEX idx_dev_alertdown ON Devices(devAlertDown)",
|
"CREATE INDEX idx_dev_alertdown ON Devices(devAlertDown)",
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"idx_dev_cansleep",
|
||||||
|
"CREATE INDEX idx_dev_cansleep ON Devices(devCanSleep)",
|
||||||
|
),
|
||||||
("idx_dev_isnew", "CREATE INDEX idx_dev_isnew ON Devices(devIsNew)"),
|
("idx_dev_isnew", "CREATE INDEX idx_dev_isnew ON Devices(devIsNew)"),
|
||||||
(
|
(
|
||||||
"idx_dev_isarchived",
|
"idx_dev_isarchived",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import uuid
|
|||||||
# Register NetAlertX libraries
|
# Register NetAlertX libraries
|
||||||
import conf
|
import conf
|
||||||
from const import fullConfPath, fullConfFolder, default_tz, applicationPath
|
from const import fullConfPath, fullConfFolder, default_tz, applicationPath
|
||||||
|
from db.db_upgrade import ensure_views
|
||||||
from helper import getBuildTimeStampAndVersion, collect_lang_strings, updateSubnets, generate_random_string
|
from helper import getBuildTimeStampAndVersion, collect_lang_strings, updateSubnets, generate_random_string
|
||||||
from utils.datetime_utils import timeNowUTC
|
from utils.datetime_utils import timeNowUTC
|
||||||
from app_state import updateState
|
from app_state import updateState
|
||||||
@@ -733,6 +734,12 @@ def importConfigs(pm, db, all_plugins):
|
|||||||
|
|
||||||
db.commitDB()
|
db.commitDB()
|
||||||
|
|
||||||
|
# Rebuild DevicesView now that settings (including NTFPRCS_sleep_time) are committed.
|
||||||
|
# This is the single call site — initDB() deliberately skips it so the view
|
||||||
|
# always gets the real user value, not an empty-Settings fallback.
|
||||||
|
ensure_views(sql)
|
||||||
|
db.commitDB()
|
||||||
|
|
||||||
# update only the settings datasource
|
# update only the settings datasource
|
||||||
update_api(db, all_plugins, True, ["settings"])
|
update_api(db, all_plugins, True, ["settings"])
|
||||||
|
|
||||||
|
|||||||
@@ -61,8 +61,8 @@ class DeviceInstance:
|
|||||||
|
|
||||||
def getDown(self):
|
def getDown(self):
|
||||||
return self._fetchall("""
|
return self._fetchall("""
|
||||||
SELECT * FROM Devices
|
SELECT * FROM DevicesView
|
||||||
WHERE devAlertDown = 1 AND devPresentLastScan = 0
|
WHERE devAlertDown = 1 AND devPresentLastScan = 0 AND devIsSleeping = 0
|
||||||
""")
|
""")
|
||||||
|
|
||||||
def getOffline(self):
|
def getOffline(self):
|
||||||
@@ -454,7 +454,9 @@ class DeviceInstance:
|
|||||||
"devPresenceHours": 0,
|
"devPresenceHours": 0,
|
||||||
"devFQDN": "",
|
"devFQDN": "",
|
||||||
"devForceStatus" : "dont_force",
|
"devForceStatus" : "dont_force",
|
||||||
"devVlan": ""
|
"devVlan": "",
|
||||||
|
"devCanSleep": 0,
|
||||||
|
"devIsSleeping": 0
|
||||||
}
|
}
|
||||||
return device_data
|
return device_data
|
||||||
|
|
||||||
@@ -467,11 +469,6 @@ class DeviceInstance:
|
|||||||
d.*,
|
d.*,
|
||||||
LOWER(d.devMac) AS devMac,
|
LOWER(d.devMac) AS devMac,
|
||||||
LOWER(d.devParentMAC) AS devParentMAC,
|
LOWER(d.devParentMAC) AS devParentMAC,
|
||||||
CASE
|
|
||||||
WHEN d.devAlertDown != 0 AND d.devPresentLastScan = 0 THEN 'Down'
|
|
||||||
WHEN d.devPresentLastScan = 1 THEN 'On-line'
|
|
||||||
ELSE 'Off-line'
|
|
||||||
END AS devStatus,
|
|
||||||
|
|
||||||
(SELECT COUNT(*) FROM Sessions
|
(SELECT COUNT(*) FROM Sessions
|
||||||
WHERE LOWER(ses_MAC) = LOWER(d.devMac) AND (
|
WHERE LOWER(ses_MAC) = LOWER(d.devMac) AND (
|
||||||
@@ -501,11 +498,10 @@ class DeviceInstance:
|
|||||||
OR ses_DateTimeDisconnection >= {period_date_sql} OR ses_StillConnected = 1)
|
OR ses_DateTimeDisconnection >= {period_date_sql} OR ses_StillConnected = 1)
|
||||||
) AS devPresenceHours
|
) AS devPresenceHours
|
||||||
|
|
||||||
FROM Devices d
|
FROM DevicesView d
|
||||||
WHERE LOWER(d.devMac) = LOWER(?) OR CAST(d.rowid AS TEXT) = ?
|
WHERE LOWER(d.devMac) = LOWER(?) OR CAST(d.rowid AS TEXT) = ?
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
conn = get_temp_db_connection()
|
conn = get_temp_db_connection()
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
cur.execute(sql, (mac, mac))
|
cur.execute(sql, (mac, mac))
|
||||||
@@ -571,7 +567,8 @@ class DeviceInstance:
|
|||||||
"devIsArchived",
|
"devIsArchived",
|
||||||
"devCustomProps",
|
"devCustomProps",
|
||||||
"devForceStatus",
|
"devForceStatus",
|
||||||
"devVlan"
|
"devVlan",
|
||||||
|
"devCanSleep"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Only mark USER for tracked fields that this method actually updates.
|
# Only mark USER for tracked fields that this method actually updates.
|
||||||
@@ -617,12 +614,12 @@ class DeviceInstance:
|
|||||||
devMac, devName, devOwner, devType, devVendor, devIcon,
|
devMac, devName, devOwner, devType, devVendor, devIcon,
|
||||||
devFavorite, devGroup, devLocation, devComments,
|
devFavorite, devGroup, devLocation, devComments,
|
||||||
devParentMAC, devParentPort, devSSID, devSite,
|
devParentMAC, devParentPort, devSSID, devSite,
|
||||||
devStaticIP, devScan, devAlertEvents, devAlertDown,
|
devStaticIP, devScan, devAlertEvents, devAlertDown, devCanSleep,
|
||||||
devParentRelType, devReqNicsOnline, devSkipRepeated,
|
devParentRelType, devReqNicsOnline, devSkipRepeated,
|
||||||
devIsNew, devIsArchived, devLastConnection,
|
devIsNew, devIsArchived, devLastConnection,
|
||||||
devFirstConnection, devLastIP, devGUID, devCustomProps,
|
devFirstConnection, devLastIP, devGUID, devCustomProps,
|
||||||
devSourcePlugin, devForceStatus, devVlan
|
devSourcePlugin, devForceStatus, devVlan
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
values = (
|
values = (
|
||||||
@@ -644,6 +641,7 @@ class DeviceInstance:
|
|||||||
data.get("devScan") or 0,
|
data.get("devScan") or 0,
|
||||||
data.get("devAlertEvents") or 0,
|
data.get("devAlertEvents") or 0,
|
||||||
data.get("devAlertDown") or 0,
|
data.get("devAlertDown") or 0,
|
||||||
|
data.get("devCanSleep") or 0,
|
||||||
data.get("devParentRelType") or "default",
|
data.get("devParentRelType") or "default",
|
||||||
data.get("devReqNicsOnline") or 0,
|
data.get("devReqNicsOnline") or 0,
|
||||||
data.get("devSkipRepeated") or 0,
|
data.get("devSkipRepeated") or 0,
|
||||||
@@ -665,7 +663,7 @@ class DeviceInstance:
|
|||||||
devName=?, devOwner=?, devType=?, devVendor=?, devIcon=?,
|
devName=?, devOwner=?, devType=?, devVendor=?, devIcon=?,
|
||||||
devFavorite=?, devGroup=?, devLocation=?, devComments=?,
|
devFavorite=?, devGroup=?, devLocation=?, devComments=?,
|
||||||
devParentMAC=?, devParentPort=?, devSSID=?, devSite=?,
|
devParentMAC=?, devParentPort=?, devSSID=?, devSite=?,
|
||||||
devStaticIP=?, devScan=?, devAlertEvents=?, devAlertDown=?,
|
devStaticIP=?, devScan=?, devAlertEvents=?, devAlertDown=?, devCanSleep=?,
|
||||||
devParentRelType=?, devReqNicsOnline=?, devSkipRepeated=?,
|
devParentRelType=?, devReqNicsOnline=?, devSkipRepeated=?,
|
||||||
devIsNew=?, devIsArchived=?, devCustomProps=?, devForceStatus=?, devVlan=?
|
devIsNew=?, devIsArchived=?, devCustomProps=?, devForceStatus=?, devVlan=?
|
||||||
WHERE devMac=?
|
WHERE devMac=?
|
||||||
@@ -688,6 +686,7 @@ class DeviceInstance:
|
|||||||
data.get("devScan") or 0,
|
data.get("devScan") or 0,
|
||||||
data.get("devAlertEvents") or 0,
|
data.get("devAlertEvents") or 0,
|
||||||
data.get("devAlertDown") or 0,
|
data.get("devAlertDown") or 0,
|
||||||
|
data.get("devCanSleep") or 0,
|
||||||
data.get("devParentRelType") or "default",
|
data.get("devParentRelType") or "default",
|
||||||
data.get("devReqNicsOnline") or 0,
|
data.get("devReqNicsOnline") or 0,
|
||||||
data.get("devSkipRepeated") or 0,
|
data.get("devSkipRepeated") or 0,
|
||||||
@@ -834,7 +833,6 @@ class DeviceInstance:
|
|||||||
conn.close()
|
conn.close()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def lockDeviceField(self, mac, field_name):
|
def lockDeviceField(self, mac, field_name):
|
||||||
"""Lock a device field so it won't be overwritten by plugins."""
|
"""Lock a device field so it won't be overwritten by plugins."""
|
||||||
if field_name not in FIELD_SOURCE_MAP:
|
if field_name not in FIELD_SOURCE_MAP:
|
||||||
|
|||||||
@@ -533,8 +533,8 @@ def print_scan_stats(db):
|
|||||||
SELECT
|
SELECT
|
||||||
(SELECT COUNT(*) FROM CurrentScan) AS devices_detected,
|
(SELECT COUNT(*) FROM CurrentScan) AS devices_detected,
|
||||||
(SELECT COUNT(*) FROM CurrentScan WHERE NOT EXISTS (SELECT 1 FROM Devices WHERE devMac = scanMac)) AS new_devices,
|
(SELECT COUNT(*) FROM CurrentScan WHERE NOT EXISTS (SELECT 1 FROM Devices WHERE devMac = scanMac)) AS new_devices,
|
||||||
(SELECT COUNT(*) FROM Devices WHERE devAlertDown != 0 AND NOT EXISTS (SELECT 1 FROM CurrentScan WHERE devMac = scanMac)) AS down_alerts,
|
(SELECT COUNT(*) FROM DevicesView WHERE devAlertDown != 0 AND devIsSleeping = 0 AND NOT EXISTS (SELECT 1 FROM CurrentScan WHERE devMac = scanMac)) AS down_alerts,
|
||||||
(SELECT COUNT(*) FROM Devices WHERE devAlertDown != 0 AND devPresentLastScan = 1 AND NOT EXISTS (SELECT 1 FROM CurrentScan WHERE devMac = scanMac)) AS new_down_alerts,
|
(SELECT COUNT(*) FROM DevicesView WHERE devAlertDown != 0 AND devIsSleeping = 0 AND devPresentLastScan = 1 AND NOT EXISTS (SELECT 1 FROM CurrentScan WHERE devMac = scanMac)) AS new_down_alerts,
|
||||||
(SELECT COUNT(*) FROM Devices WHERE devPresentLastScan = 0) AS new_connections,
|
(SELECT COUNT(*) FROM Devices WHERE devPresentLastScan = 0) AS new_connections,
|
||||||
(SELECT COUNT(*) FROM Devices WHERE devPresentLastScan = 1 AND NOT EXISTS (SELECT 1 FROM CurrentScan WHERE devMac = scanMac)) AS disconnections,
|
(SELECT COUNT(*) FROM Devices WHERE devPresentLastScan = 1 AND NOT EXISTS (SELECT 1 FROM CurrentScan WHERE devMac = scanMac)) AS disconnections,
|
||||||
(SELECT COUNT(*) FROM Devices, CurrentScan
|
(SELECT COUNT(*) FROM Devices, CurrentScan
|
||||||
|
|||||||
@@ -175,8 +175,9 @@ def insert_events(db):
|
|||||||
eve_EventType, eve_AdditionalInfo,
|
eve_EventType, eve_AdditionalInfo,
|
||||||
eve_PendingAlertEmail)
|
eve_PendingAlertEmail)
|
||||||
SELECT devMac, devLastIP, '{startTime}', 'Device Down', '', 1
|
SELECT devMac, devLastIP, '{startTime}', 'Device Down', '', 1
|
||||||
FROM Devices
|
FROM DevicesView
|
||||||
WHERE devAlertDown != 0
|
WHERE devAlertDown != 0
|
||||||
|
AND devIsSleeping = 0
|
||||||
AND devPresentLastScan = 1
|
AND devPresentLastScan = 1
|
||||||
AND NOT EXISTS (SELECT 1 FROM CurrentScan
|
AND NOT EXISTS (SELECT 1 FROM CurrentScan
|
||||||
WHERE devMac = scanMac
|
WHERE devMac = scanMac
|
||||||
@@ -242,8 +243,8 @@ def insertOnlineHistory(db):
|
|||||||
COUNT(*) AS allDevices,
|
COUNT(*) AS allDevices,
|
||||||
COALESCE(SUM(CASE WHEN devIsArchived = 1 THEN 1 ELSE 0 END), 0) AS archivedDevices,
|
COALESCE(SUM(CASE WHEN devIsArchived = 1 THEN 1 ELSE 0 END), 0) AS archivedDevices,
|
||||||
COALESCE(SUM(CASE WHEN devPresentLastScan = 1 THEN 1 ELSE 0 END), 0) AS onlineDevices,
|
COALESCE(SUM(CASE WHEN devPresentLastScan = 1 THEN 1 ELSE 0 END), 0) AS onlineDevices,
|
||||||
COALESCE(SUM(CASE WHEN devPresentLastScan = 0 AND devAlertDown = 1 THEN 1 ELSE 0 END), 0) AS downDevices
|
COALESCE(SUM(CASE WHEN devPresentLastScan = 0 AND devAlertDown = 1 AND devIsSleeping = 0 THEN 1 ELSE 0 END), 0) AS downDevices
|
||||||
FROM Devices
|
FROM DevicesView
|
||||||
"""
|
"""
|
||||||
|
|
||||||
deviceCounts = db.read(query)[
|
deviceCounts = db.read(query)[
|
||||||
|
|||||||
Reference in New Issue
Block a user