diff --git a/backend/server.js b/backend/server.js index d64024c..b1be61b 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1397,36 +1397,59 @@ app.get('/api/auto-urls', async (req, res) => { }); // --- Servers availability check --- -// Simple TCP connect check to 80 then 443 with short timeouts +// Optimized TCP check: parallelize ports and hosts, cap per-host time, add in-memory TTL cache function tcpCheck(host, port, timeoutMs) { return new Promise((resolve) => { const socket = new net.Socket(); let settled = false; - const onOk = () => { if (!settled) { settled = true; try{ socket.destroy(); }catch{} resolve(true); } }; - const onFail = () => { if (!settled) { settled = true; try{ socket.destroy(); }catch{} resolve(false); } }; - socket.setTimeout(timeoutMs); - socket.once('error', onFail); - socket.once('timeout', onFail); - socket.connect(port, host, onOk); + const settle = (ok) => { if (!settled) { settled = true; try { socket.destroy(); } catch {} resolve(ok); } }; + socket.setTimeout(timeoutMs, () => settle(false)); + socket.once('error', () => settle(false)); + socket.connect(port, host, () => settle(true)); }); } -async function checkOneServer(srv, timeoutMs = 1500) { - const hostCandidates = []; - if (srv.ip) hostCandidates.push(srv.ip); - if (srv.dns) hostCandidates.push(srv.dns); - for (const host of hostCandidates) { - // try 80 then 443 - const ok80 = await tcpCheck(host, 80, timeoutMs); - if (ok80) return true; - const ok443 = await tcpCheck(host, 443, timeoutMs); - if (ok443) return true; - } - return false; +function anyTrue(promises) { + return new Promise((resolve) => { + if (!Array.isArray(promises) || promises.length === 0) return resolve(false); + let remaining = promises.length; + let resolved = false; + for (const p of promises) { + Promise.resolve(p).then((v) => { + if (v && !resolved) { resolved = true; resolve(true); } + }).finally(() => { + remaining -= 1; + if (remaining === 0 && !resolved) resolve(false); + }); + } + }); } +async function checkOneServerFast(srv, perSocketTimeoutMs = 800, perServerBudgetMs = 1000) { + const hosts = []; + if (srv.ip) hosts.push(String(srv.ip)); + if (srv.dns) hosts.push(String(srv.dns)); + const tryOneHost = (host) => anyTrue([ + // try common ports simultaneously + tcpCheck(host, 443, perSocketTimeoutMs), + tcpCheck(host, 80, perSocketTimeoutMs), + ]); + const run = anyTrue(hosts.map((h) => tryOneHost(h))); + // Per-server overall budget + const timeout = new Promise((resolve) => setTimeout(() => resolve(false), perServerBudgetMs)); + return Promise.race([run, timeout]); +} + +const availabilityCache = { at: 0, data: null }; + app.get('/api/servers/availability', async (req, res) => { try { + const ttlSeconds = Math.max(0, Math.min(300, Number(req.query.ttlSeconds) || 30)); + const now = Date.now(); + if (availabilityCache.data && (now - availabilityCache.at) < ttlSeconds * 1000) { + return res.json({ ...availabilityCache.data, cached: true }); + } + const data = await s3.getObject({ Bucket: BUCKET_NAME, Key: 'servers.json' }).promise(); let servers = []; try { @@ -1435,10 +1458,14 @@ app.get('/api/servers/availability', async (req, res) => { } catch { servers = []; } - const checks = await Promise.allSettled(servers.map(s => checkOneServer(s))); + + const checks = await Promise.allSettled(servers.map((s) => checkOneServerFast(s))); const statuses = servers.map((s, i) => ({ ip: s.ip, dns: s.dns, online: checks[i].status === 'fulfilled' ? Boolean(checks[i].value) : false })); - const online = statuses.filter(x => x.online).length; - res.json({ online, total: servers.length, statuses }); + const online = statuses.filter((x) => x.online).length; + const payload = { online, total: servers.length, statuses }; + availabilityCache.at = Date.now(); + availabilityCache.data = payload; + res.json(payload); } catch (e) { console.error('availability error', e); res.status(500).json({ online: 0, total: 0, statuses: [] }); diff --git a/frontend/src/Dashboard.jsx b/frontend/src/Dashboard.jsx index aa9a707..8ef283b 100644 --- a/frontend/src/Dashboard.jsx +++ b/frontend/src/Dashboard.jsx @@ -80,11 +80,11 @@ function Dashboard() { try { // Загружаем данные с обработкой ошибок для каждого endpoint const results = await Promise.allSettled([ - axios.get('/api/domains-new'), - axios.get('/api/ip-ranges'), - axios.get('/api/asns'), + axios.get('/api/domains-new', { params: { offset: 0, limit: 1 } }), + axios.get('/api/ip-ranges', { params: { offset: 0, limit: 1 } }), + axios.get('/api/asns', { params: { offset: 0, limit: 1 } }), axios.get('/api/servers'), - axios.get('/api/servers/availability'), + axios.get('/api/servers/availability', { params: { ttlSeconds: 60 } }), axios.get('/api/s3/last-modified') ]); @@ -97,9 +97,15 @@ function Dashboard() { const s3Res = results[5]; // Получаем данные серверов для подсчета дополнительной статистики - const domains = domainsRes.status === 'fulfilled' ? domainsRes.value.data : []; - const ipRanges = ipRangesRes.status === 'fulfilled' ? ipRangesRes.value.data : []; - const asns = asnsRes.status === 'fulfilled' ? asnsRes.value.data : []; + const domains = domainsRes.status === 'fulfilled' + ? (Array.isArray(domainsRes.value.data?.items) ? domainsRes.value.data.items : domainsRes.value.data) + : []; + const ipRanges = ipRangesRes.status === 'fulfilled' + ? (Array.isArray(ipRangesRes.value.data?.items) ? ipRangesRes.value.data.items : ipRangesRes.value.data) + : []; + const asns = asnsRes.status === 'fulfilled' + ? (Array.isArray(asnsRes.value.data?.items) ? asnsRes.value.data.items : asnsRes.value.data) + : []; const servers = serversRes.status === 'fulfilled' ? serversRes.value.data : []; setRaw({ domains, ipRanges, asns, servers }); const countries = new Set(servers.map(server => server.country).filter(Boolean)); @@ -111,10 +117,20 @@ function Dashboard() { const lmRaw = s3Res.status === 'fulfilled' ? s3Res.value.data?.domainsNew?.lastModified : null; const lastModified = lmRaw ? new Date(lmRaw).toLocaleString() : new Date().toLocaleString(); + const domainsCount = domainsRes.status === 'fulfilled' && typeof domainsRes.value.data?.total === 'number' + ? domainsRes.value.data.total + : domains.length; + const ipRangesCount = ipRangesRes.status === 'fulfilled' && typeof ipRangesRes.value.data?.total === 'number' + ? ipRangesRes.value.data.total + : ipRanges.length; + const asnsCount = asnsRes.status === 'fulfilled' && typeof asnsRes.value.data?.total === 'number' + ? asnsRes.value.data.total + : asns.length; + setStats({ - domainsCount: domains.length, - ipRangesCount: ipRanges.length, - asnsCount: asns.length, + domainsCount, + ipRangesCount, + asnsCount, serversCount: servers.length, lastModified, countriesCount: countries.size,