diff --git a/front/devices.php b/front/devices.php index a45ba884..6be7a08d 100755 --- a/front/devices.php +++ b/front/devices.php @@ -87,6 +87,8 @@

+ +
@@ -536,6 +538,95 @@ function badgeFromRowData(rowData) { ); } +// --------------------------------------------------------- +// Build the rich empty-table onboarding message (HTML). +// Used as the DataTables 'emptyTable' language option. +function buildEmptyDeviceTableMessage(nextScanLabel) { + var etaLine = nextScanLabel + ? '' + nextScanLabel + '' + : ''; + return '
' + + '
' + + '' + getString('Device_NoData_Title') + '
' + + '' + getString('Device_NoData_Scanning') + '
' + + etaLine + + '' + getString('Device_NoData_Help') + '' + + '
'; +} + +// --------------------------------------------------------- +// Compute a live countdown label from an ISO next_scan_time string. +// next_scan_time is the earliest scheduled run time across enabled device_scanner plugins, +// computed by the backend and broadcast via SSE — no guesswork needed on the frontend. +function computeNextScanLabel(nextScanTime) { + if (!nextScanTime) return getString('Device_NextScan_Imminent'); + // Append Z if no UTC offset marker present — backend may emit naive UTC ISO strings. + var isoStr = /Z$|[+-]\d{2}:?\d{2}$/.test(nextScanTime.trim()) ? nextScanTime : nextScanTime + 'Z'; + var secsLeft = Math.round((new Date(isoStr).getTime() - Date.now()) / 1000); + if (secsLeft <= 0) return getString('Device_NextScan_Imminent'); + if (secsLeft >= 60) { + var m = Math.floor(secsLeft / 60); + var s = secsLeft % 60; + return getString('Device_NextScan_In') + m + 'm ' + s + 's'; + } + return getString('Device_NextScan_In') + secsLeft + 's'; +} + +// Anchor for next scheduled scan time, ticker handle, and plugins data — module-level. +var _nextScanTimeAnchor = null; +var _scanEtaTickerId = null; +var _pluginsData = null; + +// Fetch plugins.json once on page load so we can guard ETA display to device_scanner plugins only. +$.get('php/server/query_json.php', { file: 'plugins.json', nocache: Date.now() }, function(res) { + _pluginsData = res['data'] || []; +}); + +// Returns true only when at least one device_scanner plugin is loaded and not disabled. +function hasEnabledDeviceScanners() { + if (!_pluginsData || !_pluginsData.length) return false; + return getPluginsByType(_pluginsData, 'device_scanner', true).length > 0; +} + +// --------------------------------------------------------- +// Update the title-bar ETA subtitle and the DataTables empty-state message. +// Called on every nax:scanEtaUpdate; the inner ticker keeps the title bar live between events. +function updateScanEtaDisplay(nextScanTime) { + // Prefer the backend-computed next_scan_time; keep previous anchor if not yet received. + _nextScanTimeAnchor = nextScanTime || _nextScanTimeAnchor; + + // Restart the per-second title-bar ticker + if (_scanEtaTickerId !== null) { clearInterval(_scanEtaTickerId); } + + function tickTitleBar() { + var eta = document.getElementById('nextScanEta'); + if (!eta) return; + if (!hasEnabledDeviceScanners()) { + eta.style.display = 'none'; + return; + } + eta.textContent = computeNextScanLabel(_nextScanTimeAnchor); + eta.style.display = ''; + } + + // Update DataTables empty message once per SSE event — avoids AJAX spam on server-side tables. + // Only show the next-scan ETA line when device_scanner plugins are actually enabled. + var label = hasEnabledDeviceScanners() ? computeNextScanLabel(_nextScanTimeAnchor) : ''; + if ($.fn.DataTable.isDataTable('#tableDevices')) { + var dt = $('#tableDevices').DataTable(); + dt.settings()[0].oLanguage.sEmptyTable = buildEmptyDeviceTableMessage(label); + if (dt.page.info().recordsTotal === 0) { dt.draw(false); } + } + + tickTitleBar(); + _scanEtaTickerId = setInterval(tickTitleBar, 1000); +} + +// Listen for scan ETA updates dispatched by sse_manager.js (SSE push or poll fallback) +document.addEventListener('nax:scanEtaUpdate', function(e) { + updateScanEtaDisplay(e.detail.nextScanTime); +}); + // --------------------------------------------------------- // Initializes the main devices list datatable function initializeDatatable (status) { @@ -893,7 +984,7 @@ function initializeDatatable (status) { // Processing 'processing' : true, 'language' : { - emptyTable: 'No data', + emptyTable: buildEmptyDeviceTableMessage(getString('Device_NextScan_Imminent')), "lengthMenu": "", "search": ": ", "paginate": { diff --git a/front/js/sse_manager.js b/front/js/sse_manager.js index 2c4e3d40..a61f9930 100644 --- a/front/js/sse_manager.js +++ b/front/js/sse_manager.js @@ -175,6 +175,16 @@ class NetAlertXStateManager { } } + // 5. Dispatch scan ETA update for pages that display next-scan timing + if (appState["last_scan_run"] !== undefined || appState["next_scan_time"] !== undefined) { + document.dispatchEvent(new CustomEvent('nax:scanEtaUpdate', { + detail: { + lastScanRun: appState["last_scan_run"], + nextScanTime: appState["next_scan_time"] + } + })); + } + // console.log("[NetAlertX State] UI updated via jQuery"); } catch (e) { console.error("[NetAlertX State] Failed to update state display:", e); diff --git a/front/php/templates/language/ar_ar.json b/front/php/templates/language/ar_ar.json index 56044987..3f9f80fe 100644 --- a/front/php/templates/language/ar_ar.json +++ b/front/php/templates/language/ar_ar.json @@ -203,6 +203,11 @@ "Device_MultiEdit_MassActions": "إجراءات جماعية", "Device_MultiEdit_No_Devices": "لم يتم تحديد أي أجهزة.", "Device_MultiEdit_Tooltip": "تعديل الأجهزة المحددة", + "Device_NextScan_Imminent": "", + "Device_NextScan_In": "", + "Device_NoData_Help": "", + "Device_NoData_Scanning": "", + "Device_NoData_Title": "", "Device_Save_Failed": "", "Device_Save_Unauthorized": "", "Device_Saved_Success": "", @@ -336,6 +341,7 @@ "Gen_Invalid_Value": "تم إدخال قيمة غير صالحة", "Gen_LockedDB": "قاعدة البيانات مقفلة", "Gen_NetworkMask": "قناع الشبكة", + "Gen_New": "", "Gen_Offline": "غير متصل", "Gen_Okay": "موافق", "Gen_Online": "متصل", diff --git a/front/php/templates/language/ca_ca.json b/front/php/templates/language/ca_ca.json index 501d719c..8c6dec13 100644 --- a/front/php/templates/language/ca_ca.json +++ b/front/php/templates/language/ca_ca.json @@ -203,6 +203,11 @@ "Device_MultiEdit_MassActions": "Accions massives:", "Device_MultiEdit_No_Devices": "Cap dispositiu seleccionat.", "Device_MultiEdit_Tooltip": "Atenció. Si feu clic a això s'aplicarà el valor de l'esquerra a tots els dispositius seleccionats a dalt.", + "Device_NextScan_Imminent": "", + "Device_NextScan_In": "", + "Device_NoData_Help": "", + "Device_NoData_Scanning": "", + "Device_NoData_Title": "", "Device_Save_Failed": "Problemes guardant el dispositiu", "Device_Save_Unauthorized": "Token invàlid - No autoritzat", "Device_Saved_Success": "S'ha guardat el dispositiu", @@ -336,6 +341,7 @@ "Gen_Invalid_Value": "S'ha introduït un valor incorrecte", "Gen_LockedDB": "ERROR - DB podria estar bloquejada - Fes servir F12 Eines desenvolupament -> Consola o provar-ho més tard.", "Gen_NetworkMask": "Màscara de xarxa", + "Gen_New": "", "Gen_Offline": "Fora de línia", "Gen_Okay": "Ok", "Gen_Online": "En línia", diff --git a/front/php/templates/language/cs_cz.json b/front/php/templates/language/cs_cz.json index 52d074c5..a04566d0 100644 --- a/front/php/templates/language/cs_cz.json +++ b/front/php/templates/language/cs_cz.json @@ -203,6 +203,11 @@ "Device_MultiEdit_MassActions": "", "Device_MultiEdit_No_Devices": "", "Device_MultiEdit_Tooltip": "", + "Device_NextScan_Imminent": "", + "Device_NextScan_In": "", + "Device_NoData_Help": "", + "Device_NoData_Scanning": "", + "Device_NoData_Title": "", "Device_Save_Failed": "", "Device_Save_Unauthorized": "", "Device_Saved_Success": "", @@ -336,6 +341,7 @@ "Gen_Invalid_Value": "", "Gen_LockedDB": "CHYBA - Databáze je možná zamčená - Zkontrolujte F12 -> Nástroje pro vývojáře -> Konzole. nebo to zkuste později.", "Gen_NetworkMask": "", + "Gen_New": "", "Gen_Offline": "Offline", "Gen_Okay": "Ok", "Gen_Online": "Online", diff --git a/front/php/templates/language/de_de.json b/front/php/templates/language/de_de.json index 64187af0..ae01a670 100644 --- a/front/php/templates/language/de_de.json +++ b/front/php/templates/language/de_de.json @@ -207,6 +207,11 @@ "Device_MultiEdit_MassActions": "Massen aktionen:", "Device_MultiEdit_No_Devices": "Keine Geräte ausgewählt.", "Device_MultiEdit_Tooltip": "Achtung! Beim Drücken werden alle Werte auf die oben ausgewählten Geräte übertragen.", + "Device_NextScan_Imminent": "", + "Device_NextScan_In": "", + "Device_NoData_Help": "", + "Device_NoData_Scanning": "", + "Device_NoData_Title": "", "Device_Save_Failed": "", "Device_Save_Unauthorized": "", "Device_Saved_Success": "Gerät erfolgreich gespeichert", @@ -340,6 +345,7 @@ "Gen_Invalid_Value": "", "Gen_LockedDB": "ERROR - DB eventuell gesperrt - Nutze die Konsole in den Entwickler Werkzeugen (F12) zur Überprüfung oder probiere es später erneut.", "Gen_NetworkMask": "", + "Gen_New": "", "Gen_Offline": "Offline", "Gen_Okay": "Ok", "Gen_Online": "Online", diff --git a/front/php/templates/language/en_us.json b/front/php/templates/language/en_us.json index 646d1dac..b1222f34 100755 --- a/front/php/templates/language/en_us.json +++ b/front/php/templates/language/en_us.json @@ -203,6 +203,11 @@ "Device_MultiEdit_MassActions": "Mass actions:", "Device_MultiEdit_No_Devices": "No devices selected.", "Device_MultiEdit_Tooltip": "Careful. Clicking this will apply the value on the left to all devices selected above.", + "Device_NextScan_Imminent": "imminent", + "Device_NextScan_In": "Next scan in ", + "Device_NoData_Help": "If devices don't appear after the scan, check your SCAN_SUBNETS setting and documentation.", + "Device_NoData_Scanning": "Waiting for the first scan - this may take several minutes after the initial setup.", + "Device_NoData_Title": "No devices found yet", "Device_Save_Failed": "Failed to save device", "Device_Save_Unauthorized": "Unauthorized - invalid API token", "Device_Saved_Success": "Device saved successfully", diff --git a/front/php/templates/language/es_es.json b/front/php/templates/language/es_es.json index 7d2cb9b9..464165ef 100644 --- a/front/php/templates/language/es_es.json +++ b/front/php/templates/language/es_es.json @@ -205,6 +205,11 @@ "Device_MultiEdit_MassActions": "Acciones masivas:", "Device_MultiEdit_No_Devices": "Sin dispositivo seleccionado.", "Device_MultiEdit_Tooltip": "Cuidado. Al hacer clic se aplicará el valor de la izquierda a todos los dispositivos seleccionados anteriormente.", + "Device_NextScan_Imminent": "", + "Device_NextScan_In": "", + "Device_NoData_Help": "", + "Device_NoData_Scanning": "", + "Device_NoData_Title": "", "Device_Save_Failed": "Fallo al guardar el dispositivo", "Device_Save_Unauthorized": "No autorizado - Token de API inválido", "Device_Saved_Success": "Dispositivo guardado exitósamente", @@ -338,6 +343,7 @@ "Gen_Invalid_Value": "Un valor inválido fue ingresado", "Gen_LockedDB": "Fallo - La base de datos puede estar bloqueada - Pulsa F1 -> Ajustes de desarrolladores -> Consola o prueba más tarde.", "Gen_NetworkMask": "Máscara de red", + "Gen_New": "", "Gen_Offline": "Desconectado", "Gen_Okay": "Aceptar", "Gen_Online": "En linea", diff --git a/front/php/templates/language/fa_fa.json b/front/php/templates/language/fa_fa.json index 5354403d..56394aea 100644 --- a/front/php/templates/language/fa_fa.json +++ b/front/php/templates/language/fa_fa.json @@ -203,6 +203,11 @@ "Device_MultiEdit_MassActions": "", "Device_MultiEdit_No_Devices": "", "Device_MultiEdit_Tooltip": "", + "Device_NextScan_Imminent": "", + "Device_NextScan_In": "", + "Device_NoData_Help": "", + "Device_NoData_Scanning": "", + "Device_NoData_Title": "", "Device_Save_Failed": "", "Device_Save_Unauthorized": "", "Device_Saved_Success": "", @@ -336,6 +341,7 @@ "Gen_Invalid_Value": "", "Gen_LockedDB": "", "Gen_NetworkMask": "", + "Gen_New": "", "Gen_Offline": "", "Gen_Okay": "", "Gen_Online": "", diff --git a/front/php/templates/language/fr_fr.json b/front/php/templates/language/fr_fr.json index 562af0b0..1a404705 100644 --- a/front/php/templates/language/fr_fr.json +++ b/front/php/templates/language/fr_fr.json @@ -203,6 +203,11 @@ "Device_MultiEdit_MassActions": "Actions en masse :", "Device_MultiEdit_No_Devices": "Aucun appareil sélectionné.", "Device_MultiEdit_Tooltip": "Attention. Ceci va appliquer la valeur de gauche à tous les appareils sélectionnés au-dessus.", + "Device_NextScan_Imminent": "", + "Device_NextScan_In": "", + "Device_NoData_Help": "", + "Device_NoData_Scanning": "", + "Device_NoData_Title": "", "Device_Save_Failed": "Erreur à l'enregistrement de l'appareil", "Device_Save_Unauthorized": "Non autorisé - Jeton d'API invalide", "Device_Saved_Success": "Appareil enregistré avec succès", @@ -336,6 +341,7 @@ "Gen_Invalid_Value": "Une valeur invalide a été renseignée", "Gen_LockedDB": "Erreur - La base de données est peut-être verrouillée - Vérifier avec les outils de dév via F12 -> Console ou essayer plus tard.", "Gen_NetworkMask": "Masque réseau", + "Gen_New": "", "Gen_Offline": "Hors ligne", "Gen_Okay": "OK", "Gen_Online": "En ligne", diff --git a/front/php/templates/language/it_it.json b/front/php/templates/language/it_it.json index f03913c0..a0464200 100644 --- a/front/php/templates/language/it_it.json +++ b/front/php/templates/language/it_it.json @@ -203,6 +203,11 @@ "Device_MultiEdit_MassActions": "Azioni di massa:", "Device_MultiEdit_No_Devices": "Nessun dispositivo selezionato.", "Device_MultiEdit_Tooltip": "Attento. Facendo clic verrà applicato il valore sulla sinistra a tutti i dispositivi selezionati sopra.", + "Device_NextScan_Imminent": "", + "Device_NextScan_In": "", + "Device_NoData_Help": "", + "Device_NoData_Scanning": "", + "Device_NoData_Title": "", "Device_Save_Failed": "Impossibile salvare il dispositivo", "Device_Save_Unauthorized": "Non autorizzato: token API non valido", "Device_Saved_Success": "Dispositivo salvato correttamente", @@ -336,6 +341,7 @@ "Gen_Invalid_Value": "È stato inserito un valore non valido", "Gen_LockedDB": "ERRORE: il DB potrebbe essere bloccato, controlla F12 Strumenti di sviluppo -> Console o riprova più tardi.", "Gen_NetworkMask": "Maschera di rete", + "Gen_New": "", "Gen_Offline": "Offline", "Gen_Okay": "Ok", "Gen_Online": "Online", @@ -797,4 +803,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 083ea9ea..07428b05 100644 --- a/front/php/templates/language/ja_jp.json +++ b/front/php/templates/language/ja_jp.json @@ -203,6 +203,11 @@ "Device_MultiEdit_MassActions": "大量のアクション:", "Device_MultiEdit_No_Devices": "デバイスが選択されていません。", "Device_MultiEdit_Tooltip": "注意。これをクリックすると、左側の値が上記で選択したすべてのデバイスに適用されます。", + "Device_NextScan_Imminent": "", + "Device_NextScan_In": "", + "Device_NoData_Help": "", + "Device_NoData_Scanning": "", + "Device_NoData_Title": "", "Device_Save_Failed": "デバイスの保存に失敗しました", "Device_Save_Unauthorized": "許可されていない - 無効なAPIトークン", "Device_Saved_Success": "デバイスが正常に保存されました", @@ -336,6 +341,7 @@ "Gen_Invalid_Value": "無効な値が入力されました", "Gen_LockedDB": "エラー - DBがロックされている可能性があります - F12で開発者ツール→コンソールを確認するか、後で試してください。", "Gen_NetworkMask": "ネットワークマスク", + "Gen_New": "", "Gen_Offline": "オフライン", "Gen_Okay": "Ok", "Gen_Online": "オンライン", diff --git a/front/php/templates/language/nb_no.json b/front/php/templates/language/nb_no.json index 49ff0ab5..605489b3 100644 --- a/front/php/templates/language/nb_no.json +++ b/front/php/templates/language/nb_no.json @@ -203,6 +203,11 @@ "Device_MultiEdit_MassActions": "Flerhandlinger:", "Device_MultiEdit_No_Devices": "", "Device_MultiEdit_Tooltip": "Forsiktig. Ved å klikke på denne vil verdien til venstre brukes på alle enhetene som er valgt ovenfor.", + "Device_NextScan_Imminent": "", + "Device_NextScan_In": "", + "Device_NoData_Help": "", + "Device_NoData_Scanning": "", + "Device_NoData_Title": "", "Device_Save_Failed": "", "Device_Save_Unauthorized": "", "Device_Saved_Success": "", @@ -336,6 +341,7 @@ "Gen_Invalid_Value": "", "Gen_LockedDB": "FEIL - DB kan være låst - Sjekk F12 Dev tools -> Konsoll eller prøv senere.", "Gen_NetworkMask": "", + "Gen_New": "", "Gen_Offline": "Frakoblet", "Gen_Okay": "Ok", "Gen_Online": "", diff --git a/front/php/templates/language/pl_pl.json b/front/php/templates/language/pl_pl.json index 36921670..48eef282 100644 --- a/front/php/templates/language/pl_pl.json +++ b/front/php/templates/language/pl_pl.json @@ -203,6 +203,11 @@ "Device_MultiEdit_MassActions": "Operacje zbiorcze:", "Device_MultiEdit_No_Devices": "", "Device_MultiEdit_Tooltip": "Uwaga. Kliknięcie tego spowoduje zastosowanie wartości po lewej stronie do wszystkich wybranych powyżej urządzeń.", + "Device_NextScan_Imminent": "", + "Device_NextScan_In": "", + "Device_NoData_Help": "", + "Device_NoData_Scanning": "", + "Device_NoData_Title": "", "Device_Save_Failed": "", "Device_Save_Unauthorized": "", "Device_Saved_Success": "", @@ -336,6 +341,7 @@ "Gen_Invalid_Value": "", "Gen_LockedDB": "Błąd - Baza danych może być zablokowana - Sprawdź narzędzia deweloperskie F12 -> Konsola lub spróbuj później.", "Gen_NetworkMask": "", + "Gen_New": "", "Gen_Offline": "Niedostępne", "Gen_Okay": "Ok", "Gen_Online": "Dostępne", diff --git a/front/php/templates/language/pt_br.json b/front/php/templates/language/pt_br.json index f6b44cc1..6de1d4ef 100644 --- a/front/php/templates/language/pt_br.json +++ b/front/php/templates/language/pt_br.json @@ -203,6 +203,11 @@ "Device_MultiEdit_MassActions": "Ações em massa:", "Device_MultiEdit_No_Devices": "", "Device_MultiEdit_Tooltip": "Cuidadoso. Clicar aqui aplicará o valor à esquerda a todos os dispositivos selecionados acima.", + "Device_NextScan_Imminent": "", + "Device_NextScan_In": "", + "Device_NoData_Help": "", + "Device_NoData_Scanning": "", + "Device_NoData_Title": "", "Device_Save_Failed": "", "Device_Save_Unauthorized": "", "Device_Saved_Success": "", @@ -336,6 +341,7 @@ "Gen_Invalid_Value": "", "Gen_LockedDB": "ERRO - O banco de dados pode estar bloqueado - Verifique F12 Ferramentas de desenvolvimento -> Console ou tente mais tarde.", "Gen_NetworkMask": "", + "Gen_New": "", "Gen_Offline": "Offline", "Gen_Okay": "Ok", "Gen_Online": "Online", diff --git a/front/php/templates/language/pt_pt.json b/front/php/templates/language/pt_pt.json index 88098862..b72ee269 100644 --- a/front/php/templates/language/pt_pt.json +++ b/front/php/templates/language/pt_pt.json @@ -203,6 +203,11 @@ "Device_MultiEdit_MassActions": "Ações em massa:", "Device_MultiEdit_No_Devices": "Nenhum dispositivo selecionado.", "Device_MultiEdit_Tooltip": "Cuidadoso. Clicar aqui aplicará o valor à esquerda a todos os dispositivos selecionados acima.", + "Device_NextScan_Imminent": "", + "Device_NextScan_In": "", + "Device_NoData_Help": "", + "Device_NoData_Scanning": "", + "Device_NoData_Title": "", "Device_Save_Failed": "", "Device_Save_Unauthorized": "", "Device_Saved_Success": "", @@ -336,6 +341,7 @@ "Gen_Invalid_Value": "", "Gen_LockedDB": "ERRO - A base de dados pode estar bloqueada - Verifique F12 Ferramentas de desenvolvimento -> Console ou tente mais tarde.", "Gen_NetworkMask": "Máscara de Rede", + "Gen_New": "", "Gen_Offline": "Offline", "Gen_Okay": "Ok", "Gen_Online": "Online", diff --git a/front/php/templates/language/ru_ru.json b/front/php/templates/language/ru_ru.json index ae4349c0..e19861cb 100644 --- a/front/php/templates/language/ru_ru.json +++ b/front/php/templates/language/ru_ru.json @@ -203,6 +203,11 @@ "Device_MultiEdit_MassActions": "Массовые действия:", "Device_MultiEdit_No_Devices": "Устройства не выбраны.", "Device_MultiEdit_Tooltip": "Осторожно. При нажатии на эту кнопку значение слева будет применено ко всем устройствам, выбранным выше.", + "Device_NextScan_Imminent": "", + "Device_NextScan_In": "", + "Device_NoData_Help": "", + "Device_NoData_Scanning": "", + "Device_NoData_Title": "", "Device_Save_Failed": "Не удалось сохранить устройство", "Device_Save_Unauthorized": "Не авторизован - недействительный токен API", "Device_Saved_Success": "Устройство успешно сохранено", @@ -336,6 +341,7 @@ "Gen_Invalid_Value": "Введено некорректное значение", "Gen_LockedDB": "ОШИБКА - Возможно, база данных заблокирована. Проверьте инструменты разработчика F12 -> Консоль или повторите попытку позже.", "Gen_NetworkMask": "Маска сети", + "Gen_New": "", "Gen_Offline": "Оффлайн", "Gen_Okay": "OK", "Gen_Online": "Онлайн", diff --git a/front/php/templates/language/sv_sv.json b/front/php/templates/language/sv_sv.json index 380b2520..0f1292fd 100644 --- a/front/php/templates/language/sv_sv.json +++ b/front/php/templates/language/sv_sv.json @@ -203,6 +203,11 @@ "Device_MultiEdit_MassActions": "", "Device_MultiEdit_No_Devices": "", "Device_MultiEdit_Tooltip": "", + "Device_NextScan_Imminent": "", + "Device_NextScan_In": "", + "Device_NoData_Help": "", + "Device_NoData_Scanning": "", + "Device_NoData_Title": "", "Device_Save_Failed": "", "Device_Save_Unauthorized": "", "Device_Saved_Success": "", @@ -336,6 +341,7 @@ "Gen_Invalid_Value": "", "Gen_LockedDB": "", "Gen_NetworkMask": "", + "Gen_New": "", "Gen_Offline": "", "Gen_Okay": "", "Gen_Online": "", diff --git a/front/php/templates/language/tr_tr.json b/front/php/templates/language/tr_tr.json index 489ef0d8..041891fe 100644 --- a/front/php/templates/language/tr_tr.json +++ b/front/php/templates/language/tr_tr.json @@ -203,6 +203,11 @@ "Device_MultiEdit_MassActions": "Toplu komutlar:", "Device_MultiEdit_No_Devices": "", "Device_MultiEdit_Tooltip": "Dikkat. Buna tıklamak, soldaki değeri yukarıda seçilen tüm cihazlara uygulayacaktır.", + "Device_NextScan_Imminent": "", + "Device_NextScan_In": "", + "Device_NoData_Help": "", + "Device_NoData_Scanning": "", + "Device_NoData_Title": "", "Device_Save_Failed": "", "Device_Save_Unauthorized": "", "Device_Saved_Success": "", @@ -336,6 +341,7 @@ "Gen_Invalid_Value": "", "Gen_LockedDB": "HATA - Veritabanı kilitlenmiş olabilir - F12 Geliştirici araçlarını -> Konsol kısmını kontrol edin veya daha sonra tekrar deneyin.", "Gen_NetworkMask": "", + "Gen_New": "", "Gen_Offline": "Çevrimdışı", "Gen_Okay": "Tamam", "Gen_Online": "Çevrimiçi", diff --git a/front/php/templates/language/uk_ua.json b/front/php/templates/language/uk_ua.json index 5028a912..40bdea05 100644 --- a/front/php/templates/language/uk_ua.json +++ b/front/php/templates/language/uk_ua.json @@ -203,6 +203,11 @@ "Device_MultiEdit_MassActions": "Масові акції:", "Device_MultiEdit_No_Devices": "Не вибрано жодного пристрою.", "Device_MultiEdit_Tooltip": "Обережно. Якщо натиснути це, значення зліва буде застосовано до всіх пристроїв, вибраних вище.", + "Device_NextScan_Imminent": "", + "Device_NextScan_In": "", + "Device_NoData_Help": "", + "Device_NoData_Scanning": "", + "Device_NoData_Title": "", "Device_Save_Failed": "Не вдалося зберегти пристрій", "Device_Save_Unauthorized": "Неавторизовано – недійсний токен API", "Device_Saved_Success": "Пристрій успішно збережено", @@ -336,6 +341,7 @@ "Gen_Invalid_Value": "Введено недійсне значення", "Gen_LockedDB": "ПОМИЛКА – БД може бути заблоковано – перевірте F12 Інструменти розробника -> Консоль або спробуйте пізніше.", "Gen_NetworkMask": "Маска мережі", + "Gen_New": "", "Gen_Offline": "Офлайн", "Gen_Okay": "Гаразд", "Gen_Online": "Онлайн", diff --git a/front/php/templates/language/vi_vn.json b/front/php/templates/language/vi_vn.json index 380b2520..0f1292fd 100644 --- a/front/php/templates/language/vi_vn.json +++ b/front/php/templates/language/vi_vn.json @@ -203,6 +203,11 @@ "Device_MultiEdit_MassActions": "", "Device_MultiEdit_No_Devices": "", "Device_MultiEdit_Tooltip": "", + "Device_NextScan_Imminent": "", + "Device_NextScan_In": "", + "Device_NoData_Help": "", + "Device_NoData_Scanning": "", + "Device_NoData_Title": "", "Device_Save_Failed": "", "Device_Save_Unauthorized": "", "Device_Saved_Success": "", @@ -336,6 +341,7 @@ "Gen_Invalid_Value": "", "Gen_LockedDB": "", "Gen_NetworkMask": "", + "Gen_New": "", "Gen_Offline": "", "Gen_Okay": "", "Gen_Online": "", diff --git a/front/php/templates/language/zh_cn.json b/front/php/templates/language/zh_cn.json index 9b4709f6..36fab9b8 100644 --- a/front/php/templates/language/zh_cn.json +++ b/front/php/templates/language/zh_cn.json @@ -203,6 +203,11 @@ "Device_MultiEdit_MassActions": "谨慎操作:", "Device_MultiEdit_No_Devices": "未选择设备。", "Device_MultiEdit_Tooltip": "小心。 单击此按钮会将左侧的值应用到上面选择的所有设备。", + "Device_NextScan_Imminent": "", + "Device_NextScan_In": "", + "Device_NoData_Help": "", + "Device_NoData_Scanning": "", + "Device_NoData_Title": "", "Device_Save_Failed": "保存设备失败", "Device_Save_Unauthorized": "未授权 - API 令牌无效", "Device_Saved_Success": "设备保存成功", @@ -336,6 +341,7 @@ "Gen_Invalid_Value": "输入了无效的值", "Gen_LockedDB": "错误 - DB 可能被锁定 - 检查 F12 开发工具 -> 控制台或稍后重试。", "Gen_NetworkMask": "网络掩码", + "Gen_New": "", "Gen_Offline": "离线", "Gen_Okay": "Ok", "Gen_Online": "在线", diff --git a/server/__main__.py b/server/__main__.py index e0900409..27e37395 100755 --- a/server/__main__.py +++ b/server/__main__.py @@ -124,8 +124,16 @@ def main(): # last time any scan or maintenance/upkeep was run conf.last_scan_run = loop_start_time - # Header - updateState("Process: Start") + # Compute the next scheduled run time across enabled device_scanner plugins + scanner_prefixes = {p["unique_prefix"] for p in all_plugins if p.get("plugin_type") == "device_scanner"} + scanner_next = [s.last_next_schedule for s in conf.mySchedules if s.service in scanner_prefixes] + next_scan_dt = min(scanner_next, default=None) + next_scan_time_iso = next_scan_dt.replace(microsecond=0).isoformat() if next_scan_dt else "" + + # Header (also broadcasts last_scan_run + next_scan_time to frontend via SSE / app_state.json) + updateState("Process: Start", + last_scan_run=loop_start_time.replace(microsecond=0).isoformat(), + next_scan_time=next_scan_time_iso) # Timestamp startTime = loop_start_time diff --git a/server/app_state.py b/server/app_state.py index e444cc2e..505470e3 100755 --- a/server/app_state.py +++ b/server/app_state.py @@ -43,7 +43,9 @@ class app_state_class: processScan=False, pluginsStates=None, appVersion=None, - buildTimestamp=None + buildTimestamp=None, + last_scan_run=None, + next_scan_time=None ): """ Initialize the application state, optionally overwriting previous values. @@ -89,6 +91,8 @@ class app_state_class: self.pluginsStates = previousState.get("pluginsStates", {}) self.appVersion = previousState.get("appVersion", "") self.buildTimestamp = previousState.get("buildTimestamp", "") + self.last_scan_run = previousState.get("last_scan_run", "") + self.next_scan_time = previousState.get("next_scan_time", "") else: # init first time values self.settingsSaved = 0 self.settingsImported = 0 @@ -101,6 +105,8 @@ class app_state_class: self.pluginsStates = {} self.appVersion = "" self.buildTimestamp = "" + self.last_scan_run = "" + self.next_scan_time = "" # Overwrite with provided parameters if supplied if settingsSaved is not None: @@ -133,6 +139,10 @@ class app_state_class: self.appVersion = appVersion if buildTimestamp is not None: self.buildTimestamp = buildTimestamp + if last_scan_run is not None: + self.last_scan_run = last_scan_run + if next_scan_time is not None: + self.next_scan_time = next_scan_time # check for new version every hour and if currently not running new version if self.isNewVersion is False and self.isNewVersionChecked + 3600 < int( timeNowUTC(as_string=False).timestamp() @@ -165,7 +175,9 @@ class app_state_class: self.settingsImported, timestamp=self.lastUpdated, appVersion=self.appVersion, - buildTimestamp=self.buildTimestamp + buildTimestamp=self.buildTimestamp, + last_scan_run=self.last_scan_run, + next_scan_time=self.next_scan_time ) except Exception as e: mylog("none", [f"[app_state] SSE broadcast: {e}"]) @@ -183,7 +195,9 @@ def updateState(newState = None, processScan = None, pluginsStates=None, appVersion=None, - buildTimestamp=None): + buildTimestamp=None, + last_scan_run=None, + next_scan_time=None): """ Convenience method to create or update the app state. @@ -197,6 +211,8 @@ def updateState(newState = None, pluginsStates (dict, optional): Plugin state updates. appVersion (str, optional): Application version. buildTimestamp (str, optional): Build timestamp. + last_scan_run (str, optional): ISO timestamp of last backend scan run. + next_scan_time (str, optional): ISO timestamp of next scheduled device_scanner run. Returns: app_state_class: Updated state object. @@ -210,7 +226,9 @@ def updateState(newState = None, processScan, pluginsStates, appVersion, - buildTimestamp + buildTimestamp, + last_scan_run, + next_scan_time )