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
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m36s
This commit is contained in:
@@ -32,7 +32,8 @@ async function getNetworkMapSchedulerSettings(req, res) {
|
|||||||
try {
|
try {
|
||||||
const ui = await readUiSettings();
|
const ui = await readUiSettings();
|
||||||
const settings = networkMapScheduler.getSettingsFromUiSettings(ui);
|
const settings = networkMapScheduler.getSettingsFromUiSettings(ui);
|
||||||
return res.json(settings);
|
const nextRunAt = networkMapScheduler.getNextRunAt(settings);
|
||||||
|
return res.json({ ...settings, nextRunAt: nextRunAt || undefined });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[scheduler] getSettings', e);
|
console.error('[scheduler] getSettings', e);
|
||||||
return sendError(res, 500, 'Не удалось прочитать настройки', 'E_SCHEDULER');
|
return sendError(res, 500, 'Не удалось прочитать настройки', 'E_SCHEDULER');
|
||||||
|
|||||||
@@ -320,6 +320,8 @@ async function writeCache(pingMap, speedMap) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let schedulerTimer = null;
|
let schedulerTimer = null;
|
||||||
|
/** Время последнего запуска по расписанию (для расчёта следующего) */
|
||||||
|
let lastScheduledRunAt = null;
|
||||||
|
|
||||||
function getSettingsFromUiSettings(uiSettings) {
|
function getSettingsFromUiSettings(uiSettings) {
|
||||||
return {
|
return {
|
||||||
@@ -341,6 +343,7 @@ function initNetworkMapScheduler(logger, port) {
|
|||||||
const baseUrl = `http://127.0.0.1:${port}`;
|
const baseUrl = `http://127.0.0.1:${port}`;
|
||||||
|
|
||||||
async function tick() {
|
async function tick() {
|
||||||
|
lastScheduledRunAt = Date.now();
|
||||||
let uiSettings;
|
let uiSettings;
|
||||||
try {
|
try {
|
||||||
const raw = await readS3TextObject(UI_SETTINGS_KEY).catch(() => ({ body: '{}' }));
|
const raw = await readS3TextObject(UI_SETTINGS_KEY).catch(() => ({ body: '{}' }));
|
||||||
@@ -418,6 +421,18 @@ function pushLog(msg) {
|
|||||||
logBuffer.push(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 = {
|
module.exports = {
|
||||||
runJob,
|
runJob,
|
||||||
initNetworkMapScheduler,
|
initNetworkMapScheduler,
|
||||||
@@ -427,6 +442,7 @@ module.exports = {
|
|||||||
setLogMaxLines,
|
setLogMaxLines,
|
||||||
pushLog,
|
pushLog,
|
||||||
getSettingsFromUiSettings,
|
getSettingsFromUiSettings,
|
||||||
|
getNextRunAt,
|
||||||
loadUiSettings: loadUiSettings,
|
loadUiSettings: loadUiSettings,
|
||||||
buildConnections,
|
buildConnections,
|
||||||
LogBuffer,
|
LogBuffer,
|
||||||
|
|||||||
@@ -278,31 +278,8 @@ export default function NetworkMapDashboard() {
|
|||||||
fetchData();
|
fetchData();
|
||||||
}, [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 = () => {
|
const handleRefreshPings = () => {
|
||||||
requestPings();
|
requestPings();
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export default function NetworkMapSchedulerPage() {
|
|||||||
runSpeedTest: true,
|
runSpeedTest: true,
|
||||||
logMaxLines: 500,
|
logMaxLines: 500,
|
||||||
});
|
});
|
||||||
|
const [nextRunAt, setNextRunAt] = useState(null);
|
||||||
const [logs, setLogs] = useState([]);
|
const [logs, setLogs] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
@@ -42,6 +43,7 @@ export default function NetworkMapSchedulerPage() {
|
|||||||
runSpeedTest: data.runSpeedTest !== false,
|
runSpeedTest: data.runSpeedTest !== false,
|
||||||
logMaxLines: Math.max(LOG_LINES_MIN, Math.min(LOG_LINES_MAX, Number(data.logMaxLines) || 500)),
|
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) {
|
} catch (e) {
|
||||||
setError(e?.response?.data?.message || e?.message || 'Не удалось загрузить настройки');
|
setError(e?.response?.data?.message || e?.message || 'Не удалось загрузить настройки');
|
||||||
}
|
}
|
||||||
@@ -72,6 +74,12 @@ export default function NetworkMapSchedulerPage() {
|
|||||||
return () => clearInterval(t);
|
return () => clearInterval(t);
|
||||||
}, [fetchLogs]);
|
}, [fetchLogs]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!settings.enabled || !nextRunAt) return;
|
||||||
|
const t = setInterval(fetchSettings, 60 * 1000);
|
||||||
|
return () => clearInterval(t);
|
||||||
|
}, [settings.enabled, nextRunAt, fetchSettings]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (autoScroll && logEndRef.current) {
|
if (autoScroll && logEndRef.current) {
|
||||||
logEndRef.current.scrollIntoView({ behavior: 'smooth' });
|
logEndRef.current.scrollIntoView({ behavior: 'smooth' });
|
||||||
@@ -182,6 +190,11 @@ export default function NetworkMapSchedulerPage() {
|
|||||||
<span className="form-check-label">Включить планировщик</span>
|
<span className="form-check-label">Включить планировщик</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="form-hint">Периодически обновлять пинг и скорость по связям из «Сетевые настройки»</div>
|
<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>
|
||||||
|
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
|
|||||||
Reference in New Issue
Block a user