feat(UptimeMonitor): add uptime check endpoint and frontend settings for monitoring server availability
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m34s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m34s
This commit is contained in:
@@ -244,6 +244,115 @@ async function testMikrotikConnection(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/uptime/check
|
||||
* Проверка доступности одного сервера (jumphost/home). Тип проверки берётся из ui-settings.uptimeMonitorCheckType.
|
||||
* Body: { serverId }
|
||||
* Returns: { ok: boolean, ms?: number }
|
||||
*/
|
||||
async function uptimeCheck(req, res) {
|
||||
const t0 = Date.now();
|
||||
try {
|
||||
const { serverId } = req.body || {};
|
||||
if (!serverId) {
|
||||
return sendError(res, 400, 'serverId is required', 'E_BAD_REQUEST');
|
||||
}
|
||||
const uiSettings = await loadUiSettings();
|
||||
const checkType = String(uiSettings.uptimeMonitorCheckType || 'http').toLowerCase();
|
||||
const effectiveType =
|
||||
checkType === 'internal-ping' || checkType === 'external-ping' ? checkType : 'http';
|
||||
|
||||
const servers = await readServersFromS3();
|
||||
const server = servers.find((s) => (s.id || s.dns || s.ip) === serverId);
|
||||
if (!server || !['jumphost', 'home'].includes(String(server.type || '').toLowerCase())) {
|
||||
return sendError(res, 400, 'Jumphost или Home сервер не найден', 'E_NOT_FOUND');
|
||||
}
|
||||
|
||||
let ok = false;
|
||||
let ms = null;
|
||||
|
||||
if (effectiveType === 'http') {
|
||||
const creds = getMikrotikCredentials(server);
|
||||
if (!creds) {
|
||||
return sendError(res, 400, 'MikroTik credentials not configured', 'E_CREDENTIALS');
|
||||
}
|
||||
const client = createRosClient(creds);
|
||||
await client.print('system/resource');
|
||||
ok = true;
|
||||
ms = Date.now() - t0;
|
||||
return res.json({ ok, ms });
|
||||
}
|
||||
|
||||
if (effectiveType === 'internal-ping') {
|
||||
const config = await loadNetworkConfig();
|
||||
const tunnelInterfaces = Array.isArray(config.tunnelInterfaces) ? config.tunnelInterfaces : [];
|
||||
const iface = tunnelInterfaces.find(
|
||||
(i) =>
|
||||
(i.serverId && (i.serverId === serverId || i.serverId === server.ip || i.serverId === server.dns)) ||
|
||||
(i.serverId2 && (i.serverId2 === serverId || i.serverId2 === server.ip || i.serverId2 === server.dns))
|
||||
);
|
||||
if (!iface || (!iface.remoteIp && !iface.localIp)) {
|
||||
ms = Date.now() - t0;
|
||||
return res.json({ ok: false, ms, error: 'Нет туннеля с внутренним адресом для этого сервера' });
|
||||
}
|
||||
const target = iface.serverId === serverId || iface.serverId === server.ip || iface.serverId === server.dns
|
||||
? iface.remoteIp
|
||||
: iface.localIp;
|
||||
const gatewayIp = target;
|
||||
if (!target) {
|
||||
ms = Date.now() - t0;
|
||||
return res.json({ ok: false, ms, error: 'Нет целевого адреса для пинга' });
|
||||
}
|
||||
try {
|
||||
const result = await runPingViaRouter(serverId, gatewayIp, target, 3);
|
||||
ms = Date.now() - t0;
|
||||
ok = typeof result.avgMs === 'number';
|
||||
return res.json({ ok, ms: ok ? result.avgMs : ms });
|
||||
} catch (err) {
|
||||
ms = Date.now() - t0;
|
||||
return res.json({ ok: false, ms, error: err?.message || String(err) });
|
||||
}
|
||||
}
|
||||
|
||||
if (effectiveType === 'external-ping') {
|
||||
const others = servers.filter(
|
||||
(s) =>
|
||||
['jumphost', 'home'].includes(String(s.type || '').toLowerCase()) &&
|
||||
(s.id || s.dns || s.ip) !== serverId &&
|
||||
s.ip !== server.ip
|
||||
);
|
||||
const sourceServer = others[0];
|
||||
if (!sourceServer) {
|
||||
ms = Date.now() - t0;
|
||||
return res.json({ ok: false, ms, error: 'Нет другого jumphost для внешнего пинга' });
|
||||
}
|
||||
const targetIp = server.ip || server.extIp;
|
||||
if (!targetIp) {
|
||||
ms = Date.now() - t0;
|
||||
return res.json({ ok: false, ms, error: 'У сервера нет внешнего IP' });
|
||||
}
|
||||
const sourceId = sourceServer.id || sourceServer.dns || sourceServer.ip;
|
||||
try {
|
||||
const result = await runPingViaRouter(sourceId, null, targetIp, 3);
|
||||
ms = Date.now() - t0;
|
||||
ok = typeof result.avgMs === 'number';
|
||||
return res.json({ ok, ms: ok ? result.avgMs : ms });
|
||||
} catch (err) {
|
||||
ms = Date.now() - t0;
|
||||
return res.json({ ok: false, ms, error: err?.message || String(err) });
|
||||
}
|
||||
}
|
||||
|
||||
ms = Date.now() - t0;
|
||||
return res.json({ ok: false, ms });
|
||||
} catch (error) {
|
||||
const ms = Date.now() - t0;
|
||||
const msg = error?.message || String(error);
|
||||
console.error('uptimeCheck:', error);
|
||||
return res.status(502).json({ ok: false, ms, error: msg });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/mikrotik/apply
|
||||
* Body: { serverId, type?: 'interfaces'|'recursive'|'all', dryRun?: boolean }
|
||||
@@ -1344,4 +1453,5 @@ module.exports = {
|
||||
loadNetworkConfig,
|
||||
getAddressLists,
|
||||
applyAddressListSummary,
|
||||
uptimeCheck,
|
||||
};
|
||||
|
||||
@@ -470,6 +470,9 @@ app.post('/api/mikrotik/run-script', mikrotikConfigRoutes.runScript);
|
||||
app.get('/api/mikrotik/address-lists', mikrotikConfigRoutes.getAddressLists);
|
||||
app.post('/api/mikrotik/address-lists/apply-summary', writeLimiter, mikrotikConfigRoutes.applyAddressListSummary);
|
||||
|
||||
// === UPTIME MONITOR (проверка доступности: http / internal-ping / external-ping из настроек) ===
|
||||
app.post('/api/uptime/check', writeLimiter, mikrotikConfigRoutes.uptimeCheck);
|
||||
|
||||
// === TRAFFIC STATS (MikroTik interfaces by jumphost) ===
|
||||
app.get('/api/traffic/interface-stats', trafficRoutes.getInterfaceStats);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user