BE+FE: Unstable devices list (3 status changes in 1h)

Signed-off-by: jokob-sk <jokob.sk@gmail.com>
This commit is contained in:
jokob-sk
2026-02-22 23:12:46 +11:00
parent a26137800d
commit 2f1e5068e3
28 changed files with 207 additions and 87 deletions

View File

@@ -314,6 +314,9 @@
<li> <li>
<a href="devices.php#archived" onclick="forceLoadUrl('devices.php#archived')" > <?= lang("Device_Shortcut_Archived");?> </a> <a href="devices.php#archived" onclick="forceLoadUrl('devices.php#archived')" > <?= lang("Device_Shortcut_Archived");?> </a>
</li> </li>
<li>
<a href="devices.php#unstable_devices" onclick="forceLoadUrl('devices.php#unstable_devices')" > <?= lang("Device_Shortcut_Unstable");?> </a>
</li>
<li> <li>
<a href="devices.php#all_devices" onclick="forceLoadUrl('devices.php#all_devices')" > <?= lang("Gen_All_Devices");?> </a> <a href="devices.php#all_devices" onclick="forceLoadUrl('devices.php#all_devices')" > <?= lang("Gen_All_Devices");?> </a>
</li> </li>

View File

@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "المفضلة", "Device_Shortcut_Favorites": "المفضلة",
"Device_Shortcut_NewDevices": "أجهزة جديدة", "Device_Shortcut_NewDevices": "أجهزة جديدة",
"Device_Shortcut_OnlineChart": "مخطط الاتصال", "Device_Shortcut_OnlineChart": "مخطط الاتصال",
"Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "تنبيه عدم الاتصال", "Device_TableHead_AlertDown": "تنبيه عدم الاتصال",
"Device_TableHead_Connected_Devices": "الأجهزة المتصلة", "Device_TableHead_Connected_Devices": "الأجهزة المتصلة",
"Device_TableHead_CustomProps": "خصائص مخصصة", "Device_TableHead_CustomProps": "خصائص مخصصة",
@@ -789,4 +790,4 @@
"settings_system_label": "نظام", "settings_system_label": "نظام",
"settings_update_item_warning": "قم بتحديث القيمة أدناه. احرص على اتباع التنسيق السابق. <b>لم يتم إجراء التحقق.</b>", "settings_update_item_warning": "قم بتحديث القيمة أدناه. احرص على اتباع التنسيق السابق. <b>لم يتم إجراء التحقق.</b>",
"test_event_tooltip": "احفظ التغييرات أولاً قبل اختبار الإعدادات." "test_event_tooltip": "احفظ التغييرات أولاً قبل اختبار الإعدادات."
} }

View File

@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "Favorits", "Device_Shortcut_Favorites": "Favorits",
"Device_Shortcut_NewDevices": "Nous dispositius", "Device_Shortcut_NewDevices": "Nous dispositius",
"Device_Shortcut_OnlineChart": "Dispositius detectats", "Device_Shortcut_OnlineChart": "Dispositius detectats",
"Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "Cancel·lar alerta", "Device_TableHead_AlertDown": "Cancel·lar alerta",
"Device_TableHead_Connected_Devices": "Connexions", "Device_TableHead_Connected_Devices": "Connexions",
"Device_TableHead_CustomProps": "Props / Accions", "Device_TableHead_CustomProps": "Props / Accions",

View File

@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "", "Device_Shortcut_Favorites": "",
"Device_Shortcut_NewDevices": "", "Device_Shortcut_NewDevices": "",
"Device_Shortcut_OnlineChart": "", "Device_Shortcut_OnlineChart": "",
"Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "", "Device_TableHead_AlertDown": "",
"Device_TableHead_Connected_Devices": "", "Device_TableHead_Connected_Devices": "",
"Device_TableHead_CustomProps": "", "Device_TableHead_CustomProps": "",

View File

@@ -222,6 +222,7 @@
"Device_Shortcut_Favorites": "Favoriten", "Device_Shortcut_Favorites": "Favoriten",
"Device_Shortcut_NewDevices": "Neue Geräte", "Device_Shortcut_NewDevices": "Neue Geräte",
"Device_Shortcut_OnlineChart": "Gerätepräsenz im Laufe der Zeit", "Device_Shortcut_OnlineChart": "Gerätepräsenz im Laufe der Zeit",
"Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "Alarm aus", "Device_TableHead_AlertDown": "Alarm aus",
"Device_TableHead_Connected_Devices": "Verbindungen", "Device_TableHead_Connected_Devices": "Verbindungen",
"Device_TableHead_CustomProps": "Eigenschaften / Aktionen", "Device_TableHead_CustomProps": "Eigenschaften / Aktionen",

View File

@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "Favorites", "Device_Shortcut_Favorites": "Favorites",
"Device_Shortcut_NewDevices": "New devices", "Device_Shortcut_NewDevices": "New devices",
"Device_Shortcut_OnlineChart": "Device presence", "Device_Shortcut_OnlineChart": "Device presence",
"Device_Shortcut_Unstable": "Unstable",
"Device_TableHead_AlertDown": "Alert Down", "Device_TableHead_AlertDown": "Alert Down",
"Device_TableHead_Connected_Devices": "Connections", "Device_TableHead_Connected_Devices": "Connections",
"Device_TableHead_CustomProps": "Props / Actions", "Device_TableHead_CustomProps": "Props / Actions",

View File

@@ -220,6 +220,7 @@
"Device_Shortcut_Favorites": "Favorito(s)", "Device_Shortcut_Favorites": "Favorito(s)",
"Device_Shortcut_NewDevices": "Nuevos dispositivos", "Device_Shortcut_NewDevices": "Nuevos dispositivos",
"Device_Shortcut_OnlineChart": "Presencia del dispositivo a lo largo del tiempo", "Device_Shortcut_OnlineChart": "Presencia del dispositivo a lo largo del tiempo",
"Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "Alerta desactivada", "Device_TableHead_AlertDown": "Alerta desactivada",
"Device_TableHead_Connected_Devices": "Conexiones", "Device_TableHead_Connected_Devices": "Conexiones",
"Device_TableHead_CustomProps": "Propiedades / Acciones", "Device_TableHead_CustomProps": "Propiedades / Acciones",

View File

@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "", "Device_Shortcut_Favorites": "",
"Device_Shortcut_NewDevices": "", "Device_Shortcut_NewDevices": "",
"Device_Shortcut_OnlineChart": "", "Device_Shortcut_OnlineChart": "",
"Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "", "Device_TableHead_AlertDown": "",
"Device_TableHead_Connected_Devices": "", "Device_TableHead_Connected_Devices": "",
"Device_TableHead_CustomProps": "", "Device_TableHead_CustomProps": "",

View File

@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "Favoris", "Device_Shortcut_Favorites": "Favoris",
"Device_Shortcut_NewDevices": "Nouveaux appareils", "Device_Shortcut_NewDevices": "Nouveaux appareils",
"Device_Shortcut_OnlineChart": "Présence de l'appareil", "Device_Shortcut_OnlineChart": "Présence de l'appareil",
"Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "Alerter si En panne", "Device_TableHead_AlertDown": "Alerter si En panne",
"Device_TableHead_Connected_Devices": "Connexions", "Device_TableHead_Connected_Devices": "Connexions",
"Device_TableHead_CustomProps": "Champs / Actions", "Device_TableHead_CustomProps": "Champs / Actions",
@@ -789,4 +790,4 @@
"settings_system_label": "Système", "settings_system_label": "Système",
"settings_update_item_warning": "Mettre à jour la valeur ci-dessous. Veillez à bien suivre le même format qu'auparavant. <b>Il n'y a pas de pas de contrôle.</b>", "settings_update_item_warning": "Mettre à jour la valeur ci-dessous. Veillez à bien suivre le même format qu'auparavant. <b>Il n'y a pas de pas de contrôle.</b>",
"test_event_tooltip": "Enregistrer d'abord vos modifications avant de tester vôtre paramétrage." "test_event_tooltip": "Enregistrer d'abord vos modifications avant de tester vôtre paramétrage."
} }

View File

@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "Preferiti", "Device_Shortcut_Favorites": "Preferiti",
"Device_Shortcut_NewDevices": "Nuovi dispositivi", "Device_Shortcut_NewDevices": "Nuovi dispositivi",
"Device_Shortcut_OnlineChart": "Presenza dispositivo", "Device_Shortcut_OnlineChart": "Presenza dispositivo",
"Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "Avviso disconnessione", "Device_TableHead_AlertDown": "Avviso disconnessione",
"Device_TableHead_Connected_Devices": "Connessioni", "Device_TableHead_Connected_Devices": "Connessioni",
"Device_TableHead_CustomProps": "Proprietà/Azioni", "Device_TableHead_CustomProps": "Proprietà/Azioni",
@@ -789,4 +790,4 @@
"settings_system_label": "Sistema", "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>", "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." "test_event_tooltip": "Salva le modifiche prima di provare le nuove impostazioni."
} }

View File

@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "お気に入り", "Device_Shortcut_Favorites": "お気に入り",
"Device_Shortcut_NewDevices": "新規デバイス", "Device_Shortcut_NewDevices": "新規デバイス",
"Device_Shortcut_OnlineChart": "デバイス検出", "Device_Shortcut_OnlineChart": "デバイス検出",
"Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "ダウンアラート", "Device_TableHead_AlertDown": "ダウンアラート",
"Device_TableHead_Connected_Devices": "接続", "Device_TableHead_Connected_Devices": "接続",
"Device_TableHead_CustomProps": "属性 / アクション", "Device_TableHead_CustomProps": "属性 / アクション",
@@ -789,4 +790,4 @@
"settings_system_label": "システム", "settings_system_label": "システム",
"settings_update_item_warning": "以下の値を更新してください。以前のフォーマットに従うよう注意してください。<b>検証は行われません。</b>", "settings_update_item_warning": "以下の値を更新してください。以前のフォーマットに従うよう注意してください。<b>検証は行われません。</b>",
"test_event_tooltip": "設定をテストする前に、まず変更を保存してください。" "test_event_tooltip": "設定をテストする前に、まず変更を保存してください。"
} }

View File

@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "Favoritter", "Device_Shortcut_Favorites": "Favoritter",
"Device_Shortcut_NewDevices": "Nye Enheter", "Device_Shortcut_NewDevices": "Nye Enheter",
"Device_Shortcut_OnlineChart": "Enhetens tilstedeværelse", "Device_Shortcut_OnlineChart": "Enhetens tilstedeværelse",
"Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "", "Device_TableHead_AlertDown": "",
"Device_TableHead_Connected_Devices": "Tilkoblinger", "Device_TableHead_Connected_Devices": "Tilkoblinger",
"Device_TableHead_CustomProps": "", "Device_TableHead_CustomProps": "",

View File

@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "Ulubione", "Device_Shortcut_Favorites": "Ulubione",
"Device_Shortcut_NewDevices": "Nowe urządzenia", "Device_Shortcut_NewDevices": "Nowe urządzenia",
"Device_Shortcut_OnlineChart": "Obecność urządzenia", "Device_Shortcut_OnlineChart": "Obecność urządzenia",
"Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "Alert niedostępny", "Device_TableHead_AlertDown": "Alert niedostępny",
"Device_TableHead_Connected_Devices": "Połączenia", "Device_TableHead_Connected_Devices": "Połączenia",
"Device_TableHead_CustomProps": "Właściwości / Akcje", "Device_TableHead_CustomProps": "Właściwości / Akcje",

View File

@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "Favoritos", "Device_Shortcut_Favorites": "Favoritos",
"Device_Shortcut_NewDevices": "Novos dispositivos", "Device_Shortcut_NewDevices": "Novos dispositivos",
"Device_Shortcut_OnlineChart": "Presença do dispositivo", "Device_Shortcut_OnlineChart": "Presença do dispositivo",
"Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "Alerta em baixo", "Device_TableHead_AlertDown": "Alerta em baixo",
"Device_TableHead_Connected_Devices": "Conexões", "Device_TableHead_Connected_Devices": "Conexões",
"Device_TableHead_CustomProps": "", "Device_TableHead_CustomProps": "",

View File

@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "Favoritos", "Device_Shortcut_Favorites": "Favoritos",
"Device_Shortcut_NewDevices": "Novo dispostivo", "Device_Shortcut_NewDevices": "Novo dispostivo",
"Device_Shortcut_OnlineChart": "Presença do dispositivo", "Device_Shortcut_OnlineChart": "Presença do dispositivo",
"Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "Alerta em baixo", "Device_TableHead_AlertDown": "Alerta em baixo",
"Device_TableHead_Connected_Devices": "Conexões", "Device_TableHead_Connected_Devices": "Conexões",
"Device_TableHead_CustomProps": "Propriedades / Ações", "Device_TableHead_CustomProps": "Propriedades / Ações",

View File

@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "Избранные", "Device_Shortcut_Favorites": "Избранные",
"Device_Shortcut_NewDevices": "Новые устройства", "Device_Shortcut_NewDevices": "Новые устройства",
"Device_Shortcut_OnlineChart": "Присутствие устройств", "Device_Shortcut_OnlineChart": "Присутствие устройств",
"Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "Оповещение о сост. ВЫКЛ", "Device_TableHead_AlertDown": "Оповещение о сост. ВЫКЛ",
"Device_TableHead_Connected_Devices": "Соединения", "Device_TableHead_Connected_Devices": "Соединения",
"Device_TableHead_CustomProps": "Свойства / Действия", "Device_TableHead_CustomProps": "Свойства / Действия",
@@ -789,4 +790,4 @@
"settings_system_label": "Система", "settings_system_label": "Система",
"settings_update_item_warning": "Обновить значение ниже. Будьте осторожны, следуя предыдущему формату. <b>Проверка не выполняется.</b>", "settings_update_item_warning": "Обновить значение ниже. Будьте осторожны, следуя предыдущему формату. <b>Проверка не выполняется.</b>",
"test_event_tooltip": "Сначала сохраните изменения, прежде чем проверять настройки." "test_event_tooltip": "Сначала сохраните изменения, прежде чем проверять настройки."
} }

View File

@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "", "Device_Shortcut_Favorites": "",
"Device_Shortcut_NewDevices": "", "Device_Shortcut_NewDevices": "",
"Device_Shortcut_OnlineChart": "", "Device_Shortcut_OnlineChart": "",
"Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "", "Device_TableHead_AlertDown": "",
"Device_TableHead_Connected_Devices": "", "Device_TableHead_Connected_Devices": "",
"Device_TableHead_CustomProps": "", "Device_TableHead_CustomProps": "",

View File

@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "Favoriler", "Device_Shortcut_Favorites": "Favoriler",
"Device_Shortcut_NewDevices": "Yeni Cİhazlar", "Device_Shortcut_NewDevices": "Yeni Cİhazlar",
"Device_Shortcut_OnlineChart": "Cihaz Durumu", "Device_Shortcut_OnlineChart": "Cihaz Durumu",
"Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "Çalışmama Alarmı", "Device_TableHead_AlertDown": "Çalışmama Alarmı",
"Device_TableHead_Connected_Devices": "Bağlantılar", "Device_TableHead_Connected_Devices": "Bağlantılar",
"Device_TableHead_CustomProps": "Özellikler / Eylemler", "Device_TableHead_CustomProps": "Özellikler / Eylemler",

View File

@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "Вибране", "Device_Shortcut_Favorites": "Вибране",
"Device_Shortcut_NewDevices": "Нові пристрої", "Device_Shortcut_NewDevices": "Нові пристрої",
"Device_Shortcut_OnlineChart": "Наявність пристрою", "Device_Shortcut_OnlineChart": "Наявність пристрою",
"Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "Агент Вниз", "Device_TableHead_AlertDown": "Агент Вниз",
"Device_TableHead_Connected_Devices": "Зв'язки", "Device_TableHead_Connected_Devices": "Зв'язки",
"Device_TableHead_CustomProps": "Реквізит / дії", "Device_TableHead_CustomProps": "Реквізит / дії",
@@ -789,4 +790,4 @@
"settings_system_label": "Система", "settings_system_label": "Система",
"settings_update_item_warning": "Оновіть значення нижче. Слідкуйте за попереднім форматом. <b>Перевірка не виконана.</b>", "settings_update_item_warning": "Оновіть значення нижче. Слідкуйте за попереднім форматом. <b>Перевірка не виконана.</b>",
"test_event_tooltip": "Перш ніж перевіряти налаштування, збережіть зміни." "test_event_tooltip": "Перш ніж перевіряти налаштування, збережіть зміни."
} }

View File

@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "", "Device_Shortcut_Favorites": "",
"Device_Shortcut_NewDevices": "", "Device_Shortcut_NewDevices": "",
"Device_Shortcut_OnlineChart": "", "Device_Shortcut_OnlineChart": "",
"Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "", "Device_TableHead_AlertDown": "",
"Device_TableHead_Connected_Devices": "", "Device_TableHead_Connected_Devices": "",
"Device_TableHead_CustomProps": "", "Device_TableHead_CustomProps": "",

View File

@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "收藏", "Device_Shortcut_Favorites": "收藏",
"Device_Shortcut_NewDevices": "新设备", "Device_Shortcut_NewDevices": "新设备",
"Device_Shortcut_OnlineChart": "设备统计", "Device_Shortcut_OnlineChart": "设备统计",
"Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "提醒宕机", "Device_TableHead_AlertDown": "提醒宕机",
"Device_TableHead_Connected_Devices": "链接", "Device_TableHead_Connected_Devices": "链接",
"Device_TableHead_CustomProps": "属性", "Device_TableHead_CustomProps": "属性",
@@ -789,4 +790,4 @@
"settings_system_label": "系统", "settings_system_label": "系统",
"settings_update_item_warning": "更新下面的值。请注意遵循先前的格式。<b>未执行验证。</b>", "settings_update_item_warning": "更新下面的值。请注意遵循先前的格式。<b>未执行验证。</b>",
"test_event_tooltip": "在测试设置之前,请先保存更改。" "test_event_tooltip": "在测试设置之前,请先保存更改。"
} }

View File

@@ -100,6 +100,7 @@ class Device(ObjectType):
devParentPortSource = String(description="Source tracking for devParentPort (USER, LOCKED, NEWDEV, or plugin prefix)") devParentPortSource = String(description="Source tracking for devParentPort (USER, LOCKED, NEWDEV, or plugin prefix)")
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 = String(description="ndicates flapping device (device changing between online/offline states frequently)")
class DeviceResult(ObjectType): class DeviceResult(ObjectType):
@@ -266,7 +267,7 @@ class Query(ObjectType):
filtered.append(device) filtered.append(device)
devices_data = filtered devices_data = filtered
# 🔻 START If you change anything here, also update get_device_condition_by_status # 🔻 START If you change anything here, also update get_device_conditions
elif status == "connected": elif status == "connected":
devices_data = [ devices_data = [
device device
@@ -323,7 +324,25 @@ class Query(ObjectType):
for device in devices_data for device in devices_data
if device["devType"] in network_dev_types and device["devPresentLastScan"] == 0 and device["devIsArchived"] == 0 if device["devType"] in network_dev_types and device["devPresentLastScan"] == 0 and device["devIsArchived"] == 0
] ]
# 🔺 END If you change anything here, also update get_device_condition_by_status elif status == "unstable_devices":
devices_data = [
device
for device in devices_data
if device["devIsArchived"] == 0 and device["devFlapping"] == 1
]
elif status == "unstable_favorites":
devices_data = [
device
for device in devices_data
if device["devIsArchived"] == 0 and device["devFavorite"] == 1 and device["devFlapping"] == 1
]
elif status == "unstable_network_devices":
devices_data = [
device
for device in devices_data
if device["devIsArchived"] == 0 and device["devType"] in network_dev_types and device["devFlapping"] == 1
]
# 🔺 END If you change anything here, also update get_device_conditions
elif status == "all_devices": elif status == "all_devices":
devices_data = devices_data # keep all devices_data = devices_data # keep all

View File

@@ -58,70 +58,13 @@ NULL_EQUIVALENTS = ["", "null", "(unknown)", "(Unknown)", "(name not found)"]
# Convert list to SQL string: wrap each value in single quotes and escape single quotes if needed # Convert list to SQL string: wrap each value in single quotes and escape single quotes if needed
NULL_EQUIVALENTS_SQL = ",".join("'" + v.replace("'", "''") + "'" for v in NULL_EQUIVALENTS) NULL_EQUIVALENTS_SQL = ",".join("'" + v.replace("'", "''") + "'" for v in NULL_EQUIVALENTS)
# =============================================================================== # ===============================================================================
# SQL queries # SQL queries
# =============================================================================== # ===============================================================================
sql_devices_all = """ sql_devices_all = """
SELECT SELECT
rowid, *
IFNULL(devMac, '') AS devMac, FROM DevicesView
IFNULL(devName, '') AS devName,
IFNULL(devOwner, '') AS devOwner,
IFNULL(devType, '') AS devType,
IFNULL(devVendor, '') AS devVendor,
IFNULL(devFavorite, '') AS devFavorite,
IFNULL(devGroup, '') AS devGroup,
IFNULL(devComments, '') AS devComments,
IFNULL(devFirstConnection, '') AS devFirstConnection,
IFNULL(devLastConnection, '') AS devLastConnection,
IFNULL(devLastIP, '') AS devLastIP,
IFNULL(devPrimaryIPv4, '') AS devPrimaryIPv4,
IFNULL(devPrimaryIPv6, '') AS devPrimaryIPv6,
IFNULL(devVlan, '') AS devVlan,
IFNULL(devForceStatus, '') AS devForceStatus,
IFNULL(devStaticIP, '') AS devStaticIP,
IFNULL(devScan, '') AS devScan,
IFNULL(devLogEvents, '') AS devLogEvents,
IFNULL(devAlertEvents, '') AS devAlertEvents,
IFNULL(devAlertDown, '') AS devAlertDown,
IFNULL(devSkipRepeated, '') AS devSkipRepeated,
IFNULL(devLastNotification, '') AS devLastNotification,
IFNULL(devPresentLastScan, 0) AS devPresentLastScan,
IFNULL(devIsNew, '') AS devIsNew,
IFNULL(devLocation, '') AS devLocation,
IFNULL(devIsArchived, '') AS devIsArchived,
IFNULL(devParentMAC, '') AS devParentMAC,
IFNULL(devParentPort, '') AS devParentPort,
IFNULL(devIcon, '') AS devIcon,
IFNULL(devGUID, '') AS devGUID,
IFNULL(devSite, '') AS devSite,
IFNULL(devSSID, '') AS devSSID,
IFNULL(devSyncHubNode, '') AS devSyncHubNode,
IFNULL(devSourcePlugin, '') AS devSourcePlugin,
IFNULL(devCustomProps, '') AS devCustomProps,
IFNULL(devFQDN, '') AS devFQDN,
IFNULL(devParentRelType, '') AS devParentRelType,
IFNULL(devReqNicsOnline, '') AS devReqNicsOnline,
IFNULL(devMacSource, '') AS devMacSource,
IFNULL(devNameSource, '') AS devNameSource,
IFNULL(devFQDNSource, '') AS devFQDNSource,
IFNULL(devLastIPSource, '') AS devLastIPSource,
IFNULL(devVendorSource, '') AS devVendorSource,
IFNULL(devSSIDSource, '') AS devSSIDSource,
IFNULL(devParentMACSource, '') AS devParentMACSource,
IFNULL(devParentPortSource, '') AS devParentPortSource,
IFNULL(devParentRelTypeSource, '') AS devParentRelTypeSource,
IFNULL(devVlanSource, '') AS devVlanSource,
CASE
WHEN devIsNew = 1 THEN 'New'
WHEN devPresentLastScan = 1 THEN 'On-line'
WHEN devPresentLastScan = 0 AND devAlertDown != 0 THEN 'Down'
WHEN devIsArchived = 1 THEN 'Archived'
WHEN devPresentLastScan = 0 THEN 'Off-line'
ELSE 'Unknown status'
END AS devStatus
FROM Devices
""" """
sql_appevents = """select * from AppEvents order by DateTimeCreated desc""" sql_appevents = """select * from AppEvents order by DateTimeCreated desc"""

View File

@@ -14,22 +14,28 @@ from const import NULL_EQUIVALENTS_SQL # noqa: E402 [flake8 lint suppression]
def get_device_conditions(): def get_device_conditions():
network_dev_types = ",".join("'" + v.replace("'", "''") + "'" for v in get_setting_value("NETWORK_DEVICE_TYPES")) network_dev_types = ",".join("'" + v.replace("'", "''") + "'" for v in get_setting_value("NETWORK_DEVICE_TYPES"))
# DO NOT CHANGE ORDER # Base archived condition
base_active = "devIsArchived=0"
# DO NOT CHANGE ORDER - if you add or change something update graphql endpoint as well
conditions = { conditions = {
"all": "WHERE devIsArchived=0", "all": f"WHERE {base_active}",
"my": "WHERE devIsArchived=0", "my": f"WHERE {base_active}",
"connected": "WHERE devPresentLastScan=1", "connected": "WHERE devPresentLastScan=1",
"favorites": "WHERE devIsArchived=0 AND devFavorite=1", "favorites": f"WHERE {base_active} AND devFavorite=1",
"new": "WHERE devIsArchived=0 AND devIsNew=1", "new": f"WHERE {base_active} AND devIsNew=1",
"down": "WHERE devIsArchived=0 AND devAlertDown != 0 AND devPresentLastScan=0", "down": f"WHERE {base_active} AND devAlertDown != 0 AND devPresentLastScan=0",
"offline": "WHERE devIsArchived=0 AND devPresentLastScan=0", "offline": f"WHERE {base_active} AND devPresentLastScan=0",
"archived": "WHERE devIsArchived=1", "archived": "WHERE devIsArchived=1",
"network_devices": f"WHERE devIsArchived=0 AND devType in ({network_dev_types})", "network_devices": f"WHERE {base_active} AND devType IN ({network_dev_types})",
"network_devices_down": f"WHERE devIsArchived=0 AND devType in ({network_dev_types}) AND devPresentLastScan=0", "network_devices_down": f"WHERE {base_active} AND devType IN ({network_dev_types}) AND devPresentLastScan=0",
"unknown": f"WHERE devIsArchived=0 AND devName in ({NULL_EQUIVALENTS_SQL})", "unknown": f"WHERE {base_active} AND devName IN ({NULL_EQUIVALENTS_SQL})",
"known": f"WHERE devIsArchived=0 AND devName not in ({NULL_EQUIVALENTS_SQL})", "known": f"WHERE {base_active} AND devName NOT IN ({NULL_EQUIVALENTS_SQL})",
"favorites_offline": "WHERE devIsArchived=0 AND devFavorite=1 AND devPresentLastScan=0", "favorites_offline": f"WHERE {base_active} AND devFavorite=1 AND devPresentLastScan=0",
"new_online": "WHERE devIsArchived=0 AND devIsNew=1 AND devPresentLastScan=0", "new_online": f"WHERE {base_active} AND devIsNew=1 AND devPresentLastScan=0",
"unstable_devices": f"WHERE {base_active} AND devFlapping=1",
"unstable_favorites": f"WHERE {base_active} AND devFavorite=1 AND devFlapping=1",
"unstable_network_devices": f"WHERE {base_active} AND devType IN ({network_dev_types}) AND devFlapping=1",
} }
return conditions return conditions

View File

@@ -232,6 +232,87 @@ def ensure_views(sql) -> bool:
""") """)
FLAP_THRESHOLD = 3
FLAP_WINDOW_HOURS = 1
sql.execute(""" DROP VIEW IF EXISTS DevicesView;""")
sql.execute(f""" CREATE VIEW DevicesView AS
SELECT
rowid,
IFNULL(devMac, '') AS devMac,
IFNULL(devName, '') AS devName,
IFNULL(devOwner, '') AS devOwner,
IFNULL(devType, '') AS devType,
IFNULL(devVendor, '') AS devVendor,
IFNULL(devFavorite, '') AS devFavorite,
IFNULL(devGroup, '') AS devGroup,
IFNULL(devComments, '') AS devComments,
IFNULL(devFirstConnection, '') AS devFirstConnection,
IFNULL(devLastConnection, '') AS devLastConnection,
IFNULL(devLastIP, '') AS devLastIP,
IFNULL(devPrimaryIPv4, '') AS devPrimaryIPv4,
IFNULL(devPrimaryIPv6, '') AS devPrimaryIPv6,
IFNULL(devVlan, '') AS devVlan,
IFNULL(devForceStatus, '') AS devForceStatus,
IFNULL(devStaticIP, '') AS devStaticIP,
IFNULL(devScan, '') AS devScan,
IFNULL(devLogEvents, '') AS devLogEvents,
IFNULL(devAlertEvents, '') AS devAlertEvents,
IFNULL(devAlertDown, '') AS devAlertDown,
IFNULL(devSkipRepeated, '') AS devSkipRepeated,
IFNULL(devLastNotification, '') AS devLastNotification,
IFNULL(devPresentLastScan, 0) AS devPresentLastScan,
IFNULL(devIsNew, '') AS devIsNew,
IFNULL(devLocation, '') AS devLocation,
IFNULL(devIsArchived, '') AS devIsArchived,
IFNULL(devParentMAC, '') AS devParentMAC,
IFNULL(devParentPort, '') AS devParentPort,
IFNULL(devIcon, '') AS devIcon,
IFNULL(devGUID, '') AS devGUID,
IFNULL(devSite, '') AS devSite,
IFNULL(devSSID, '') AS devSSID,
IFNULL(devSyncHubNode, '') AS devSyncHubNode,
IFNULL(devSourcePlugin, '') AS devSourcePlugin,
IFNULL(devCustomProps, '') AS devCustomProps,
IFNULL(devFQDN, '') AS devFQDN,
IFNULL(devParentRelType, '') AS devParentRelType,
IFNULL(devReqNicsOnline, '') AS devReqNicsOnline,
IFNULL(devMacSource, '') AS devMacSource,
IFNULL(devNameSource, '') AS devNameSource,
IFNULL(devFQDNSource, '') AS devFQDNSource,
IFNULL(devLastIPSource, '') AS devLastIPSource,
IFNULL(devVendorSource, '') AS devVendorSource,
IFNULL(devSSIDSource, '') AS devSSIDSource,
IFNULL(devParentMACSource, '') AS devParentMACSource,
IFNULL(devParentPortSource, '') AS devParentPortSource,
IFNULL(devParentRelTypeSource, '') AS devParentRelTypeSource,
IFNULL(devVlanSource, '') AS devVlanSource,
CASE
WHEN devIsNew = 1 THEN 'New'
WHEN devPresentLastScan = 1 THEN 'On-line'
WHEN devPresentLastScan = 0 AND devAlertDown != 0 THEN 'Down'
WHEN devIsArchived = 1 THEN 'Archived'
WHEN devPresentLastScan = 0 THEN 'Off-line'
ELSE 'Unknown status'
END AS devStatus,
CASE
WHEN EXISTS (
SELECT 1
FROM Events e
WHERE e.eve_MAC = Devices.devMac
AND e.eve_EventType IN ('Connected','Disconnected','Device Down','Down Reconnected')
AND e.eve_DateTime >= datetime('now', '-{FLAP_WINDOW_HOURS} hours')
GROUP BY e.eve_MAC
HAVING COUNT(*) >= {FLAP_THRESHOLD}
)
THEN 1
ELSE 0
END AS devFlapping
FROM Devices
""")
return True return True

View File

@@ -338,7 +338,7 @@ class DeviceInstance:
for key, condition in conditions.items(): for key, condition in conditions.items():
# Make sure the alias is SQL-safe (no spaces or special chars) # Make sure the alias is SQL-safe (no spaces or special chars)
alias = key.replace(" ", "_").lower() alias = key.replace(" ", "_").lower()
sub_queries.append(f'(SELECT COUNT(*) FROM Devices {condition}) AS "{alias}"') sub_queries.append(f'(SELECT COUNT(*) FROM DevicesView {condition}) AS "{alias}"')
# Join all sub-selects with commas # Join all sub-selects with commas
query = "SELECT\n " + ",\n ".join(sub_queries) query = "SELECT\n " + ",\n ".join(sub_queries)
@@ -360,7 +360,7 @@ class DeviceInstance:
for key, condition in conditions.items(): for key, condition in conditions.items():
# Make sure the alias is SQL-safe (no spaces or special chars) # Make sure the alias is SQL-safe (no spaces or special chars)
alias = key.replace(" ", "_").lower() alias = key.replace(" ", "_").lower()
sub_queries.append(f'(SELECT COUNT(*) FROM Devices {condition}) AS "{alias}"') sub_queries.append(f'(SELECT COUNT(*) FROM DevicesView {condition}) AS "{alias}"')
# Join all sub-selects with commas # Join all sub-selects with commas
query = "SELECT\n " + ",\n ".join(sub_queries) query = "SELECT\n " + ",\n ".join(sub_queries)
@@ -381,7 +381,8 @@ class DeviceInstance:
# Build condition for SQL # Build condition for SQL
condition = get_device_condition_by_status(status) if status else "" condition = get_device_condition_by_status(status) if status else ""
query = f"SELECT * FROM Devices {condition}" # Only DevicesView has devFlapping
query = f"SELECT * FROM DevicesView {condition}"
sql.execute(query) sql.execute(query)
table_data = [] table_data = []

View File

@@ -218,3 +218,50 @@ class EventInstance:
# Return as list # Return as list
return [row[0], row[1], row[2], row[3], row[4], row[5]] return [row[0], row[1], row[2], row[3], row[4], row[5]]
def get_unstable_devices(self, hours: int = 1, threshold: int = 3, macs_only: bool = True):
"""
Return unstable devices based on flap detection.
A device is considered unstable if it has >= threshold events within the last `hours`.
Events considered:
- Connected
- Disconnected
- Device Down
- Down Reconnected
Args:
hours (int): Time window in hours (default: 1)
threshold (int): Minimum number of events to be considered unstable (default: 3)
macs_only (bool): If True, return only MAC addresses (set). Otherwise return full rows.
Returns:
set[str] OR list[dict]
"""
if hours <= 0 or threshold <= 0:
mylog("warn", f"[Events] get_unstable_devices invalid params: hours={hours}, threshold={threshold}")
return set() if macs_only else []
conn = self._conn()
sql = """
SELECT eve_MAC, COUNT(*) as event_count
FROM Events
WHERE eve_EventType IN ('Connected','Disconnected','Device Down','Down Reconnected')
AND eve_DateTime >= datetime('now', ?)
GROUP BY eve_MAC
HAVING COUNT(*) >= ?
"""
# SQLite expects "-1 hours" format
window = f"-{hours} hours"
rows = conn.execute(sql, (window, threshold)).fetchall()
conn.close()
if macs_only:
return {row["eve_MAC"] for row in rows}
return [dict(row) for row in rows]

View File

@@ -49,7 +49,7 @@ class PluginObjectInstance:
"SELECT * FROM Plugins_Objects WHERE Plugin = ?", (plugin,) "SELECT * FROM Plugins_Objects WHERE Plugin = ?", (plugin,)
) )
def getLastNCreatedPerPLugin(self, plugin, entries=1): def getLastNCreatedPerPlugin(self, plugin, entries=1):
return self._fetchall( return self._fetchall(
""" """
SELECT * SELECT *