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 @@
  • +
  • + +
  • 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 *