diff --git a/front/php/templates/header.php b/front/php/templates/header.php
index c7d15f0e..e32b0efb 100755
--- a/front/php/templates/header.php
+++ b/front/php/templates/header.php
@@ -314,6 +314,9 @@
= lang("Device_Shortcut_Archived");?>
+
+ = lang("Device_Shortcut_Unstable");?>
+
= lang("Gen_All_Devices");?>
diff --git a/front/php/templates/language/ar_ar.json b/front/php/templates/language/ar_ar.json
index 3076815e..1391389a 100644
--- a/front/php/templates/language/ar_ar.json
+++ b/front/php/templates/language/ar_ar.json
@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "المفضلة",
"Device_Shortcut_NewDevices": "أجهزة جديدة",
"Device_Shortcut_OnlineChart": "مخطط الاتصال",
+ "Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "تنبيه عدم الاتصال",
"Device_TableHead_Connected_Devices": "الأجهزة المتصلة",
"Device_TableHead_CustomProps": "خصائص مخصصة",
@@ -789,4 +790,4 @@
"settings_system_label": "نظام",
"settings_update_item_warning": "قم بتحديث القيمة أدناه. احرص على اتباع التنسيق السابق. لم يتم إجراء التحقق.",
"test_event_tooltip": "احفظ التغييرات أولاً قبل اختبار الإعدادات."
-}
+}
\ No newline at end of file
diff --git a/front/php/templates/language/ca_ca.json b/front/php/templates/language/ca_ca.json
index 943f239e..2e26de41 100644
--- a/front/php/templates/language/ca_ca.json
+++ b/front/php/templates/language/ca_ca.json
@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "Favorits",
"Device_Shortcut_NewDevices": "Nous dispositius",
"Device_Shortcut_OnlineChart": "Dispositius detectats",
+ "Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "Cancel·lar alerta",
"Device_TableHead_Connected_Devices": "Connexions",
"Device_TableHead_CustomProps": "Props / Accions",
diff --git a/front/php/templates/language/cs_cz.json b/front/php/templates/language/cs_cz.json
index 61324a97..06dd9a15 100644
--- a/front/php/templates/language/cs_cz.json
+++ b/front/php/templates/language/cs_cz.json
@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "",
"Device_Shortcut_NewDevices": "",
"Device_Shortcut_OnlineChart": "",
+ "Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "",
"Device_TableHead_Connected_Devices": "",
"Device_TableHead_CustomProps": "",
diff --git a/front/php/templates/language/de_de.json b/front/php/templates/language/de_de.json
index 57f55df0..19e1d12f 100644
--- a/front/php/templates/language/de_de.json
+++ b/front/php/templates/language/de_de.json
@@ -222,6 +222,7 @@
"Device_Shortcut_Favorites": "Favoriten",
"Device_Shortcut_NewDevices": "Neue Geräte",
"Device_Shortcut_OnlineChart": "Gerätepräsenz im Laufe der Zeit",
+ "Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "Alarm aus",
"Device_TableHead_Connected_Devices": "Verbindungen",
"Device_TableHead_CustomProps": "Eigenschaften / Aktionen",
diff --git a/front/php/templates/language/en_us.json b/front/php/templates/language/en_us.json
index a52725e2..8129a5fc 100755
--- a/front/php/templates/language/en_us.json
+++ b/front/php/templates/language/en_us.json
@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "Favorites",
"Device_Shortcut_NewDevices": "New devices",
"Device_Shortcut_OnlineChart": "Device presence",
+ "Device_Shortcut_Unstable": "Unstable",
"Device_TableHead_AlertDown": "Alert Down",
"Device_TableHead_Connected_Devices": "Connections",
"Device_TableHead_CustomProps": "Props / Actions",
diff --git a/front/php/templates/language/es_es.json b/front/php/templates/language/es_es.json
index aff4832c..aa4a4e39 100644
--- a/front/php/templates/language/es_es.json
+++ b/front/php/templates/language/es_es.json
@@ -220,6 +220,7 @@
"Device_Shortcut_Favorites": "Favorito(s)",
"Device_Shortcut_NewDevices": "Nuevos dispositivos",
"Device_Shortcut_OnlineChart": "Presencia del dispositivo a lo largo del tiempo",
+ "Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "Alerta desactivada",
"Device_TableHead_Connected_Devices": "Conexiones",
"Device_TableHead_CustomProps": "Propiedades / Acciones",
diff --git a/front/php/templates/language/fa_fa.json b/front/php/templates/language/fa_fa.json
index 772ef8df..e9e9fc84 100644
--- a/front/php/templates/language/fa_fa.json
+++ b/front/php/templates/language/fa_fa.json
@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "",
"Device_Shortcut_NewDevices": "",
"Device_Shortcut_OnlineChart": "",
+ "Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "",
"Device_TableHead_Connected_Devices": "",
"Device_TableHead_CustomProps": "",
diff --git a/front/php/templates/language/fr_fr.json b/front/php/templates/language/fr_fr.json
index 66ea2d84..9c10212c 100644
--- a/front/php/templates/language/fr_fr.json
+++ b/front/php/templates/language/fr_fr.json
@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "Favoris",
"Device_Shortcut_NewDevices": "Nouveaux appareils",
"Device_Shortcut_OnlineChart": "Présence de l'appareil",
+ "Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "Alerter si En panne",
"Device_TableHead_Connected_Devices": "Connexions",
"Device_TableHead_CustomProps": "Champs / Actions",
@@ -789,4 +790,4 @@
"settings_system_label": "Système",
"settings_update_item_warning": "Mettre à jour la valeur ci-dessous. Veillez à bien suivre le même format qu'auparavant. Il n'y a pas de pas de contrôle.",
"test_event_tooltip": "Enregistrer d'abord vos modifications avant de tester vôtre paramétrage."
-}
+}
\ No newline at end of file
diff --git a/front/php/templates/language/it_it.json b/front/php/templates/language/it_it.json
index b88e6e4e..ee981731 100644
--- a/front/php/templates/language/it_it.json
+++ b/front/php/templates/language/it_it.json
@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "Preferiti",
"Device_Shortcut_NewDevices": "Nuovi dispositivi",
"Device_Shortcut_OnlineChart": "Presenza dispositivo",
+ "Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "Avviso disconnessione",
"Device_TableHead_Connected_Devices": "Connessioni",
"Device_TableHead_CustomProps": "Proprietà/Azioni",
@@ -789,4 +790,4 @@
"settings_system_label": "Sistema",
"settings_update_item_warning": "Aggiorna il valore qui sotto. Fai attenzione a seguire il formato precedente. La convalida non viene eseguita.",
"test_event_tooltip": "Salva le modifiche prima di provare le nuove impostazioni."
-}
+}
\ No newline at end of file
diff --git a/front/php/templates/language/ja_jp.json b/front/php/templates/language/ja_jp.json
index a4aa0b1f..7262ca5a 100644
--- a/front/php/templates/language/ja_jp.json
+++ b/front/php/templates/language/ja_jp.json
@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "お気に入り",
"Device_Shortcut_NewDevices": "新規デバイス",
"Device_Shortcut_OnlineChart": "デバイス検出",
+ "Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "ダウンアラート",
"Device_TableHead_Connected_Devices": "接続",
"Device_TableHead_CustomProps": "属性 / アクション",
@@ -789,4 +790,4 @@
"settings_system_label": "システム",
"settings_update_item_warning": "以下の値を更新してください。以前のフォーマットに従うよう注意してください。検証は行われません。",
"test_event_tooltip": "設定をテストする前に、まず変更を保存してください。"
-}
+}
\ No newline at end of file
diff --git a/front/php/templates/language/nb_no.json b/front/php/templates/language/nb_no.json
index e86b9200..21b66914 100644
--- a/front/php/templates/language/nb_no.json
+++ b/front/php/templates/language/nb_no.json
@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "Favoritter",
"Device_Shortcut_NewDevices": "Nye Enheter",
"Device_Shortcut_OnlineChart": "Enhetens tilstedeværelse",
+ "Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "",
"Device_TableHead_Connected_Devices": "Tilkoblinger",
"Device_TableHead_CustomProps": "",
diff --git a/front/php/templates/language/pl_pl.json b/front/php/templates/language/pl_pl.json
index 877376bd..a208d6a2 100644
--- a/front/php/templates/language/pl_pl.json
+++ b/front/php/templates/language/pl_pl.json
@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "Ulubione",
"Device_Shortcut_NewDevices": "Nowe urządzenia",
"Device_Shortcut_OnlineChart": "Obecność urządzenia",
+ "Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "Alert niedostępny",
"Device_TableHead_Connected_Devices": "Połączenia",
"Device_TableHead_CustomProps": "Właściwości / Akcje",
diff --git a/front/php/templates/language/pt_br.json b/front/php/templates/language/pt_br.json
index 5adbb5cd..a6ccad5b 100644
--- a/front/php/templates/language/pt_br.json
+++ b/front/php/templates/language/pt_br.json
@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "Favoritos",
"Device_Shortcut_NewDevices": "Novos dispositivos",
"Device_Shortcut_OnlineChart": "Presença do dispositivo",
+ "Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "Alerta em baixo",
"Device_TableHead_Connected_Devices": "Conexões",
"Device_TableHead_CustomProps": "",
diff --git a/front/php/templates/language/pt_pt.json b/front/php/templates/language/pt_pt.json
index c44b0545..d311d2c9 100644
--- a/front/php/templates/language/pt_pt.json
+++ b/front/php/templates/language/pt_pt.json
@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "Favoritos",
"Device_Shortcut_NewDevices": "Novo dispostivo",
"Device_Shortcut_OnlineChart": "Presença do dispositivo",
+ "Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "Alerta em baixo",
"Device_TableHead_Connected_Devices": "Conexões",
"Device_TableHead_CustomProps": "Propriedades / Ações",
diff --git a/front/php/templates/language/ru_ru.json b/front/php/templates/language/ru_ru.json
index 838e487f..67034818 100644
--- a/front/php/templates/language/ru_ru.json
+++ b/front/php/templates/language/ru_ru.json
@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "Избранные",
"Device_Shortcut_NewDevices": "Новые устройства",
"Device_Shortcut_OnlineChart": "Присутствие устройств",
+ "Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "Оповещение о сост. ВЫКЛ",
"Device_TableHead_Connected_Devices": "Соединения",
"Device_TableHead_CustomProps": "Свойства / Действия",
@@ -789,4 +790,4 @@
"settings_system_label": "Система",
"settings_update_item_warning": "Обновить значение ниже. Будьте осторожны, следуя предыдущему формату. Проверка не выполняется.",
"test_event_tooltip": "Сначала сохраните изменения, прежде чем проверять настройки."
-}
+}
\ No newline at end of file
diff --git a/front/php/templates/language/sv_sv.json b/front/php/templates/language/sv_sv.json
index 74024c1d..f38ce99e 100644
--- a/front/php/templates/language/sv_sv.json
+++ b/front/php/templates/language/sv_sv.json
@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "",
"Device_Shortcut_NewDevices": "",
"Device_Shortcut_OnlineChart": "",
+ "Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "",
"Device_TableHead_Connected_Devices": "",
"Device_TableHead_CustomProps": "",
diff --git a/front/php/templates/language/tr_tr.json b/front/php/templates/language/tr_tr.json
index 3d54e46c..8f50a7fc 100644
--- a/front/php/templates/language/tr_tr.json
+++ b/front/php/templates/language/tr_tr.json
@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "Favoriler",
"Device_Shortcut_NewDevices": "Yeni Cİhazlar",
"Device_Shortcut_OnlineChart": "Cihaz Durumu",
+ "Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "Çalışmama Alarmı",
"Device_TableHead_Connected_Devices": "Bağlantılar",
"Device_TableHead_CustomProps": "Özellikler / Eylemler",
diff --git a/front/php/templates/language/uk_ua.json b/front/php/templates/language/uk_ua.json
index fffbddc8..7fe0124e 100644
--- a/front/php/templates/language/uk_ua.json
+++ b/front/php/templates/language/uk_ua.json
@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "Вибране",
"Device_Shortcut_NewDevices": "Нові пристрої",
"Device_Shortcut_OnlineChart": "Наявність пристрою",
+ "Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "Агент Вниз",
"Device_TableHead_Connected_Devices": "Зв'язки",
"Device_TableHead_CustomProps": "Реквізит / дії",
@@ -789,4 +790,4 @@
"settings_system_label": "Система",
"settings_update_item_warning": "Оновіть значення нижче. Слідкуйте за попереднім форматом. Перевірка не виконана.",
"test_event_tooltip": "Перш ніж перевіряти налаштування, збережіть зміни."
-}
+}
\ No newline at end of file
diff --git a/front/php/templates/language/vi_vn.json b/front/php/templates/language/vi_vn.json
index 74024c1d..f38ce99e 100644
--- a/front/php/templates/language/vi_vn.json
+++ b/front/php/templates/language/vi_vn.json
@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "",
"Device_Shortcut_NewDevices": "",
"Device_Shortcut_OnlineChart": "",
+ "Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "",
"Device_TableHead_Connected_Devices": "",
"Device_TableHead_CustomProps": "",
diff --git a/front/php/templates/language/zh_cn.json b/front/php/templates/language/zh_cn.json
index 4d3b2683..7ff54902 100644
--- a/front/php/templates/language/zh_cn.json
+++ b/front/php/templates/language/zh_cn.json
@@ -218,6 +218,7 @@
"Device_Shortcut_Favorites": "收藏",
"Device_Shortcut_NewDevices": "新设备",
"Device_Shortcut_OnlineChart": "设备统计",
+ "Device_Shortcut_Unstable": "",
"Device_TableHead_AlertDown": "提醒宕机",
"Device_TableHead_Connected_Devices": "链接",
"Device_TableHead_CustomProps": "属性",
@@ -789,4 +790,4 @@
"settings_system_label": "系统",
"settings_update_item_warning": "更新下面的值。请注意遵循先前的格式。未执行验证。",
"test_event_tooltip": "在测试设置之前,请先保存更改。"
-}
+}
\ No newline at end of file
diff --git a/server/api_server/graphql_endpoint.py b/server/api_server/graphql_endpoint.py
index c7edb4fb..eab23791 100755
--- a/server/api_server/graphql_endpoint.py
+++ b/server/api_server/graphql_endpoint.py
@@ -100,6 +100,7 @@ class Device(ObjectType):
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)")
devVlanSource = String(description="Source tracking for devVlan")
+ devFlapping = String(description="ndicates flapping device (device changing between online/offline states frequently)")
class DeviceResult(ObjectType):
@@ -266,7 +267,7 @@ class Query(ObjectType):
filtered.append(device)
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":
devices_data = [
device
@@ -323,7 +324,25 @@ class Query(ObjectType):
for device in devices_data
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":
devices_data = devices_data # keep all
diff --git a/server/const.py b/server/const.py
index 0ea4ca13..f8ea4782 100755
--- a/server/const.py
+++ b/server/const.py
@@ -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
NULL_EQUIVALENTS_SQL = ",".join("'" + v.replace("'", "''") + "'" for v in NULL_EQUIVALENTS)
-
# ===============================================================================
# SQL queries
# ===============================================================================
-sql_devices_all = """
- 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
- FROM Devices
+sql_devices_all = """
+ SELECT
+ *
+ FROM DevicesView
"""
sql_appevents = """select * from AppEvents order by DateTimeCreated desc"""
diff --git a/server/db/db_helper.py b/server/db/db_helper.py
index 8ceb534b..54ae5a59 100755
--- a/server/db/db_helper.py
+++ b/server/db/db_helper.py
@@ -14,22 +14,28 @@ from const import NULL_EQUIVALENTS_SQL # noqa: E402 [flake8 lint suppression]
def get_device_conditions():
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 = {
- "all": "WHERE devIsArchived=0",
- "my": "WHERE devIsArchived=0",
+ "all": f"WHERE {base_active}",
+ "my": f"WHERE {base_active}",
"connected": "WHERE devPresentLastScan=1",
- "favorites": "WHERE devIsArchived=0 AND devFavorite=1",
- "new": "WHERE devIsArchived=0 AND devIsNew=1",
- "down": "WHERE devIsArchived=0 AND devAlertDown != 0 AND devPresentLastScan=0",
- "offline": "WHERE devIsArchived=0 AND devPresentLastScan=0",
+ "favorites": f"WHERE {base_active} AND devFavorite=1",
+ "new": f"WHERE {base_active} AND devIsNew=1",
+ "down": f"WHERE {base_active} AND devAlertDown != 0 AND devPresentLastScan=0",
+ "offline": f"WHERE {base_active} AND devPresentLastScan=0",
"archived": "WHERE devIsArchived=1",
- "network_devices": f"WHERE devIsArchived=0 AND devType in ({network_dev_types})",
- "network_devices_down": f"WHERE devIsArchived=0 AND devType in ({network_dev_types}) AND devPresentLastScan=0",
- "unknown": f"WHERE devIsArchived=0 AND devName in ({NULL_EQUIVALENTS_SQL})",
- "known": f"WHERE devIsArchived=0 AND devName not in ({NULL_EQUIVALENTS_SQL})",
- "favorites_offline": "WHERE devIsArchived=0 AND devFavorite=1 AND devPresentLastScan=0",
- "new_online": "WHERE devIsArchived=0 AND devIsNew=1 AND devPresentLastScan=0",
+ "network_devices": f"WHERE {base_active} AND devType IN ({network_dev_types})",
+ "network_devices_down": f"WHERE {base_active} AND devType IN ({network_dev_types}) AND devPresentLastScan=0",
+ "unknown": f"WHERE {base_active} AND devName IN ({NULL_EQUIVALENTS_SQL})",
+ "known": f"WHERE {base_active} AND devName NOT IN ({NULL_EQUIVALENTS_SQL})",
+ "favorites_offline": f"WHERE {base_active} AND devFavorite=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
diff --git a/server/db/db_upgrade.py b/server/db/db_upgrade.py
index f2f640ac..d3f18de2 100755
--- a/server/db/db_upgrade.py
+++ b/server/db/db_upgrade.py
@@ -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
diff --git a/server/models/device_instance.py b/server/models/device_instance.py
index a2f90bd3..d51fdf0a 100755
--- a/server/models/device_instance.py
+++ b/server/models/device_instance.py
@@ -338,7 +338,7 @@ class DeviceInstance:
for key, condition in conditions.items():
# Make sure the alias is SQL-safe (no spaces or special chars)
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
query = "SELECT\n " + ",\n ".join(sub_queries)
@@ -360,7 +360,7 @@ class DeviceInstance:
for key, condition in conditions.items():
# Make sure the alias is SQL-safe (no spaces or special chars)
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
query = "SELECT\n " + ",\n ".join(sub_queries)
@@ -381,7 +381,8 @@ class DeviceInstance:
# Build condition for SQL
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)
table_data = []
diff --git a/server/models/event_instance.py b/server/models/event_instance.py
index d0dfba84..0b166ad6 100644
--- a/server/models/event_instance.py
+++ b/server/models/event_instance.py
@@ -218,3 +218,50 @@ class EventInstance:
# Return as list
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]
diff --git a/server/models/plugin_object_instance.py b/server/models/plugin_object_instance.py
index 1f13d69c..30b2bf45 100755
--- a/server/models/plugin_object_instance.py
+++ b/server/models/plugin_object_instance.py
@@ -49,7 +49,7 @@ class PluginObjectInstance:
"SELECT * FROM Plugins_Objects WHERE Plugin = ?", (plugin,)
)
- def getLastNCreatedPerPLugin(self, plugin, entries=1):
+ def getLastNCreatedPerPlugin(self, plugin, entries=1):
return self._fetchall(
"""
SELECT *