feat(NetworkMapScheduler): add next scheduled run time to settings response and update frontend to display next run time
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m36s

This commit is contained in:
2026-02-18 14:11:27 +07:00
parent 1fda3a5f4e
commit 8cc3575902
4 changed files with 33 additions and 26 deletions
+2 -1
View File
@@ -32,7 +32,8 @@ async function getNetworkMapSchedulerSettings(req, res) {
try {
const ui = await readUiSettings();
const settings = networkMapScheduler.getSettingsFromUiSettings(ui);
return res.json(settings);
const nextRunAt = networkMapScheduler.getNextRunAt(settings);
return res.json({ ...settings, nextRunAt: nextRunAt || undefined });
} catch (e) {
console.error('[scheduler] getSettings', e);
return sendError(res, 500, 'Не удалось прочитать настройки', 'E_SCHEDULER');
+16
View File
@@ -320,6 +320,8 @@ async function writeCache(pingMap, speedMap) {
}
let schedulerTimer = null;
/** Время последнего запуска по расписанию (для расчёта следующего) */
let lastScheduledRunAt = null;
function getSettingsFromUiSettings(uiSettings) {
return {
@@ -341,6 +343,7 @@ function initNetworkMapScheduler(logger, port) {
const baseUrl = `http://127.0.0.1:${port}`;
async function tick() {
lastScheduledRunAt = Date.now();
let uiSettings;
try {
const raw = await readS3TextObject(UI_SETTINGS_KEY).catch(() => ({ body: '{}' }));
@@ -418,6 +421,18 @@ function pushLog(msg) {
logBuffer.push(msg);
}
/**
* Время следующего запуска по расписанию (timestamp или null если выключено).
* @param {object} settings - результат getSettingsFromUiSettings()
* @returns {number|null}
*/
function getNextRunAt(settings) {
if (!settings || !settings.enabled) return null;
const intervalMs = (settings.intervalMinutes || DEFAULT_INTERVAL_MINUTES) * 60 * 1000;
const from = lastScheduledRunAt || Date.now();
return from + intervalMs;
}
module.exports = {
runJob,
initNetworkMapScheduler,
@@ -427,6 +442,7 @@ module.exports = {
setLogMaxLines,
pushLog,
getSettingsFromUiSettings,
getNextRunAt,
loadUiSettings: loadUiSettings,
buildConnections,
LogBuffer,
+2 -25
View File
@@ -278,31 +278,8 @@ export default function NetworkMapDashboard() {
fetchData();
}, [fetchData]);
useEffect(() => {
if (!loading && servers.length > 0 && connections.length > 0) {
const cacheMaxAgeMs = Math.max((pingCacheSeconds || 60) * 1000, 60 * 1000);
if (cacheAppliedAtRef.current && Date.now() - cacheAppliedAtRef.current < cacheMaxAgeMs) {
return;
}
requestPings();
}
return () => {
pingAbortRef.current = true;
};
}, [loading, connections.length, pingCacheSeconds, requestPings]);
useEffect(() => {
if (!loading && connections.length > 0) {
const cacheMaxAgeMs = Math.max((pingCacheSeconds || 60) * 1000, 60 * 1000);
if (cacheAppliedAtRef.current && Date.now() - cacheAppliedAtRef.current < cacheMaxAgeMs) {
return;
}
requestSpeeds();
}
return () => {
speedAbortRef.current = true;
};
}, [loading, connections.length, pingCacheSeconds, requestSpeeds]);
// Пинг и скорость не запускаются автоматически при открытии карты (даже если кеш просрочен).
// Обновление только по кнопкам «Пинг» и «Скорость» или по планировщику.
const handleRefreshPings = () => {
requestPings();
+13
View File
@@ -23,6 +23,7 @@ export default function NetworkMapSchedulerPage() {
runSpeedTest: true,
logMaxLines: 500,
});
const [nextRunAt, setNextRunAt] = useState(null);
const [logs, setLogs] = useState([]);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
@@ -42,6 +43,7 @@ export default function NetworkMapSchedulerPage() {
runSpeedTest: data.runSpeedTest !== false,
logMaxLines: Math.max(LOG_LINES_MIN, Math.min(LOG_LINES_MAX, Number(data.logMaxLines) || 500)),
});
setNextRunAt(typeof data.nextRunAt === 'number' ? data.nextRunAt : null);
} catch (e) {
setError(e?.response?.data?.message || e?.message || 'Не удалось загрузить настройки');
}
@@ -72,6 +74,12 @@ export default function NetworkMapSchedulerPage() {
return () => clearInterval(t);
}, [fetchLogs]);
useEffect(() => {
if (!settings.enabled || !nextRunAt) return;
const t = setInterval(fetchSettings, 60 * 1000);
return () => clearInterval(t);
}, [settings.enabled, nextRunAt, fetchSettings]);
useEffect(() => {
if (autoScroll && logEndRef.current) {
logEndRef.current.scrollIntoView({ behavior: 'smooth' });
@@ -182,6 +190,11 @@ export default function NetworkMapSchedulerPage() {
<span className="form-check-label">Включить планировщик</span>
</label>
<div className="form-hint">Периодически обновлять пинг и скорость по связям из «Сетевые настройки»</div>
{settings.enabled && nextRunAt != null && (
<div className="mt-2 text-muted small">
Следующий запуск: {new Date(nextRunAt).toLocaleString('ru-RU', { dateStyle: 'short', timeStyle: 'medium' })}
</div>
)}
</div>
<div className="mb-3">