Add next scan ETA display and update app state with scan timing information

This commit is contained in:
Jokob @NetAlertX
2026-03-03 04:07:22 +00:00
parent 95f411d92a
commit ab74307ed1
24 changed files with 254 additions and 8 deletions

View File

@@ -87,6 +87,8 @@
<div class="box-header">
<div class=" col-sm-8 ">
<h3 id="tableDevicesTitle" class="box-title text-gray "></h3>
<!-- Next scan ETA — populated by sse_manager.js via nax:scanEtaUpdate -->
<small id="nextScanEta" class="text-muted" style="display:none;margin-left:8px;font-weight:normal;font-size:0.75em;"></small>
</div>
<div class="dummyDevice col-sm-4 ">
<span id="multiEditPlc">
@@ -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
? '<small class="text-muted" style="margin-top:6px;display:block;">' + nextScanLabel + '</small>'
: '';
return '<div class="text-center" style="padding:20px;">' +
'<i class="fa fa-search fa-2x text-muted" style="margin-bottom:10px;"></i><br>' +
'<strong>' + getString('Device_NoData_Title') + '</strong><br>' +
'<span class="text-muted">' + getString('Device_NoData_Scanning') + '</span><br>' +
etaLine +
'<small style="margin-top:6px;display:block;">' + getString('Device_NoData_Help') + '</small>' +
'</div>';
}
// ---------------------------------------------------------
// 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": "<?= lang('Device_Tablelenght');?>",
"search": "<?= lang('Device_Searchbox');?>: ",
"paginate": {

View File

@@ -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);

View File

@@ -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": "متصل",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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 <a href=\"https://docs.netalertx.com/SUBNETS\" target=\"_blank\">documentation</a>.",
"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",

View File

@@ -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",

View File

@@ -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": "",

View File

@@ -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",

View File

@@ -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. <b>La convalida non viene eseguita.</b>",
"test_event_tooltip": "Salva le modifiche prima di provare le nuove impostazioni."
}
}

View File

@@ -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": "オンライン",

View File

@@ -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": "",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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": "Онлайн",

View File

@@ -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": "",

View File

@@ -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",

View File

@@ -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": "Онлайн",

View File

@@ -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": "",

View File

@@ -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": "在线",

View File

@@ -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

View File

@@ -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
)