feat(UptimeMonitor): implement uptime cache retrieval and update logic for improved monitoring performance
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m36s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m36s
This commit is contained in:
@@ -86,6 +86,7 @@ export default function SettingsPage() {
|
||||
const [trafficInterfacesError, setTrafficInterfacesError] = useState('');
|
||||
const [uptimeMonitorIntervalSeconds, setUptimeMonitorIntervalSeconds] = useState('120');
|
||||
const [uptimeMonitorCheckType, setUptimeMonitorCheckType] = useState('http');
|
||||
const [uptimeMonitorCacheSeconds, setUptimeMonitorCacheSeconds] = useState('120');
|
||||
const [serversList, setServersList] = useState([]);
|
||||
const [sidebarSearch, setSidebarSearch] = useState('');
|
||||
const [activeSection, setActiveSection] = useState(() => {
|
||||
@@ -260,6 +261,9 @@ export default function SettingsPage() {
|
||||
setUptimeMonitorCheckType(
|
||||
checkType === 'internal-ping' || checkType === 'external-ping' ? checkType : 'http'
|
||||
);
|
||||
setUptimeMonitorCacheSeconds(
|
||||
data?.uptimeMonitorCacheSeconds != null ? String(data.uptimeMonitorCacheSeconds) : '120'
|
||||
);
|
||||
const raw = data?.trafficInterfaces;
|
||||
setTrafficInterfacesSelected(
|
||||
Array.isArray(raw)
|
||||
@@ -370,6 +374,10 @@ export default function SettingsPage() {
|
||||
uptimeMonitorCheckType === 'internal-ping' || uptimeMonitorCheckType === 'external-ping'
|
||||
? uptimeMonitorCheckType
|
||||
: 'http',
|
||||
uptimeMonitorCacheSeconds: Math.max(
|
||||
0,
|
||||
parseInt(uptimeMonitorCacheSeconds, 10) || 120
|
||||
),
|
||||
trafficInterfaces: Array.isArray(trafficInterfacesSelected)
|
||||
? trafficInterfacesSelected.map((p) => ({
|
||||
serverKey: p.serverKey,
|
||||
@@ -762,6 +770,20 @@ export default function SettingsPage() {
|
||||
HTTP: подключение к RouterOS API. Внутренний: пинг через туннель (как на карте сети). Внешний: пинг внешнего IP с другого jumphost.
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<FormField
|
||||
label="Кеш результатов (сек)"
|
||||
name="uptimeMonitorCacheSeconds"
|
||||
type="number"
|
||||
value={uptimeMonitorCacheSeconds}
|
||||
onChange={setUptimeMonitorCacheSeconds}
|
||||
placeholder="120"
|
||||
helpText="При заходе на страницу показываются последние проверки, если кеш младше этого срока (0 — не показывать кеш)."
|
||||
disabled={saving}
|
||||
min={0}
|
||||
max={86400}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -23,7 +23,7 @@ import PageHeader from './components/PageHeader.jsx';
|
||||
import { formatRelative } from './lib/datetime.js';
|
||||
|
||||
const DEFAULT_CHECK_INTERVAL_SEC = 120;
|
||||
const DELAY_BETWEEN_CHECKS_MS = 1500;
|
||||
const PARALLEL_CHECKS = 4; // количество одновременных проверок
|
||||
const HISTORY_MAX = 500;
|
||||
const STORAGE_KEY = 'uptime-monitor-history';
|
||||
|
||||
@@ -90,8 +90,8 @@ export default function UptimeMonitorPage() {
|
||||
const [historyMap, setHistoryMap] = useState(loadHistoryFromStorage);
|
||||
const [selectedServerId, setSelectedServerId] = useState(null);
|
||||
const [checkIntervalSeconds, setCheckIntervalSeconds] = useState(DEFAULT_CHECK_INTERVAL_SEC);
|
||||
/** serverId, для которого сейчас идёт проверка — показываем «Проверка…» в строке */
|
||||
const [currentCheckingServerId, setCurrentCheckingServerId] = useState(null);
|
||||
/** serverId, по которым сейчас идёт проверка — показываем «Проверка…» в строке (до PARALLEL_CHECKS одновременно) */
|
||||
const [currentCheckingServerIds, setCurrentCheckingServerIds] = useState([]);
|
||||
const intervalRef = useRef(null);
|
||||
const checkingRef = useRef(false);
|
||||
|
||||
@@ -128,44 +128,66 @@ export default function UptimeMonitorPage() {
|
||||
fetchServers();
|
||||
}, [fetchServers]);
|
||||
|
||||
// При заходе на страницу подставляем кеш — сразу видны последние проверки
|
||||
useEffect(() => {
|
||||
if (jumphosts.length === 0) return;
|
||||
api.get('/uptime/cache')
|
||||
.then(({ data }) => {
|
||||
if (data?.results && typeof data.results === 'object' && Object.keys(data.results).length > 0) {
|
||||
setStatusMap((prev) => ({ ...prev, ...data.results }));
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}, [jumphosts]);
|
||||
|
||||
const checkIntervalMs = checkIntervalSeconds * 1000;
|
||||
|
||||
/** Параллельный запуск проверок (до PARALLEL_CHECKS одновременно), результат по мере готовности */
|
||||
const runChecks = useCallback(async () => {
|
||||
if (jumphosts.length === 0) return;
|
||||
if (checkingRef.current) return;
|
||||
checkingRef.current = true;
|
||||
setChecking(true);
|
||||
for (const server of jumphosts) {
|
||||
const serverId = server.id || server.dns || server.ip;
|
||||
if (!serverId) continue;
|
||||
setCurrentCheckingServerId(serverId);
|
||||
const t0 = Date.now();
|
||||
let ok = false;
|
||||
let ms = null;
|
||||
try {
|
||||
const { data: checkData } = await api.post('/uptime/check', { serverId });
|
||||
ok = checkData?.ok === true;
|
||||
ms = typeof checkData?.ms === 'number' ? checkData.ms : Date.now() - t0;
|
||||
} catch (_) {
|
||||
ms = Date.now() - t0;
|
||||
setCurrentCheckingServerIds([]);
|
||||
let index = 0;
|
||||
const getNext = () => {
|
||||
const i = index++;
|
||||
return i < jumphosts.length ? jumphosts[i] : null;
|
||||
};
|
||||
const checkOne = async () => {
|
||||
let server;
|
||||
while ((server = getNext()) != null) {
|
||||
const serverId = server.id || server.dns || server.ip;
|
||||
if (!serverId) continue;
|
||||
setCurrentCheckingServerIds((prev) => [...prev, serverId]);
|
||||
const t0 = Date.now();
|
||||
let ok = false;
|
||||
let ms = null;
|
||||
try {
|
||||
const { data: checkData } = await api.post('/uptime/check', { serverId });
|
||||
ok = checkData?.ok === true;
|
||||
ms = typeof checkData?.ms === 'number' ? checkData.ms : Date.now() - t0;
|
||||
} catch (_) {
|
||||
ms = Date.now() - t0;
|
||||
}
|
||||
const lastCheckTs = Date.now();
|
||||
const entry = { ts: lastCheckTs, ok, ms };
|
||||
setStatusMap((prev) => ({ ...prev, [serverId]: { ok, lastCheckTs } }));
|
||||
setHistoryMap((prev) => {
|
||||
const list = Array.isArray(prev[serverId]) ? prev[serverId] : [];
|
||||
const merged = [...list, entry].slice(-HISTORY_MAX);
|
||||
return { ...prev, [serverId]: merged };
|
||||
});
|
||||
setCurrentCheckingServerIds((prev) => prev.filter((id) => id !== serverId));
|
||||
}
|
||||
const lastCheckTs = Date.now();
|
||||
const entry = { ts: lastCheckTs, ok, ms };
|
||||
// Обновляем UI сразу после каждой проверки — результат виден по мере прохождения тестов
|
||||
setStatusMap((prev) => ({ ...prev, [serverId]: { ok, lastCheckTs } }));
|
||||
setHistoryMap((prev) => {
|
||||
const list = Array.isArray(prev[serverId]) ? prev[serverId] : [];
|
||||
const merged = [...list, entry].slice(-HISTORY_MAX);
|
||||
return { ...prev, [serverId]: merged };
|
||||
});
|
||||
setCurrentCheckingServerId(null);
|
||||
await new Promise((r) => setTimeout(r, DELAY_BETWEEN_CHECKS_MS));
|
||||
}
|
||||
setCurrentCheckingServerId(null);
|
||||
};
|
||||
await Promise.all(Array.from({ length: PARALLEL_CHECKS }, checkOne));
|
||||
setCurrentCheckingServerIds([]);
|
||||
checkingRef.current = false;
|
||||
setChecking(false);
|
||||
}, [jumphosts]);
|
||||
|
||||
// Автопроверка по интервалу в фоне (параллельно с другими проверками)
|
||||
useEffect(() => {
|
||||
if (jumphosts.length === 0) return;
|
||||
runChecks();
|
||||
@@ -278,28 +300,28 @@ export default function UptimeMonitorPage() {
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
{currentCheckingServerId === id && (
|
||||
{currentCheckingServerIds.includes(id) && (
|
||||
<span className="badge bg-blue-lt text-blue">
|
||||
<span className="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true" />
|
||||
Проверка…
|
||||
</span>
|
||||
)}
|
||||
{currentCheckingServerId !== id && status == null && (
|
||||
{!currentCheckingServerIds.includes(id) && status == null && (
|
||||
<span className="badge bg-secondary">—</span>
|
||||
)}
|
||||
{currentCheckingServerId !== id && status?.ok === true && (
|
||||
{!currentCheckingServerIds.includes(id) && status?.ok === true && (
|
||||
<span className="badge bg-success-lt text-success">
|
||||
<IconCircleCheck size={14} /> Доступен
|
||||
</span>
|
||||
)}
|
||||
{currentCheckingServerId !== id && status?.ok === false && (
|
||||
{!currentCheckingServerIds.includes(id) && status?.ok === false && (
|
||||
<span className="badge bg-danger-lt text-danger">
|
||||
<IconCircleX size={14} /> Недоступен
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="text-muted">
|
||||
{currentCheckingServerId === id
|
||||
{currentCheckingServerIds.includes(id)
|
||||
? '…'
|
||||
: status?.lastCheckTs
|
||||
? formatRelative(status.lastCheckTs)
|
||||
|
||||
Reference in New Issue
Block a user