feat(PingServices): add endpoint for retrieving ping services list and update dashboard to display dynamic service configurations
Publish Fast Tabler Docker image / build-and-push-fast (push) Failing after 1m12s

This commit is contained in:
2026-02-17 00:15:32 +07:00
parent 17ab2a30ec
commit 89a86f7c72
7 changed files with 661 additions and 35 deletions
+45 -17
View File
@@ -331,14 +331,29 @@ function measureTcpRtt(host, port = 443, timeoutMs = 6000) {
});
}
/** Список целей для пинга с главной: id, имя, хост, порт */
const PING_SERVICES = [
{ id: 'google', name: 'Google', host: '8.8.8.8', port: 443 },
{ id: 'cloudflare', name: 'Cloudflare', host: '1.1.1.1', port: 443 },
{ id: 'yandex', name: 'Yandex', host: 'ya.ru', port: 443 },
{ id: 'instagram', name: 'Instagram', host: 'instagram.com', port: 443 },
/** Список целей для пинга по умолчанию (id, name, host, port, icon, color) */
const PING_SERVICES_DEFAULT = [
{ id: 'google', name: 'Google', host: '8.8.8.8', port: 443, icon: 'IconBrandGoogle', color: 'blue' },
{ id: 'cloudflare', name: 'Cloudflare', host: '1.1.1.1', port: 443, icon: 'IconBrandCloudflare', color: 'orange' },
{ id: 'yandex', name: 'Yandex', host: 'ya.ru', port: 443, icon: 'IconBrandYandex', color: 'red' },
{ id: 'instagram', name: 'Instagram', host: 'instagram.com', port: 443, icon: 'IconBrandInstagram', color: 'pink' },
];
function getPingServicesListFromSettings(uiSettings) {
const raw = uiSettings.pingServicesList;
if (Array.isArray(raw) && raw.length > 0) {
return raw.map((s) => ({
id: String(s?.id ?? '').trim() || `svc-${Math.random().toString(36).slice(2, 9)}`,
name: String(s?.name ?? '').trim() || 'Сервис',
host: String(s?.host ?? '').trim() || '0.0.0.0',
port: Math.max(1, parseInt(s?.port, 10) || 443),
icon: typeof s?.icon === 'string' ? s.icon : 'IconWorld',
color: typeof s?.color === 'string' ? s.color : 'primary',
})).filter((s) => s.host !== '0.0.0.0');
}
return PING_SERVICES_DEFAULT;
}
/** Загрузить UI-настройки из S3 (для pingServicesSource и др.) */
async function loadUiSettingsSync() {
try {
@@ -353,16 +368,26 @@ async function loadUiSettingsSync() {
const PING_SERVICES_CACHE_KEY_PREFIX = 'ping-services/cache_';
// GET /api/ping-services — RTT до Google, Cloudflare, Yandex, Instagram (веб или через RouterOS по настройке)
async function getPingServices(req, res) {
const fallbackPayload = {
google: { id: 'google', name: 'Google', host: '8.8.8.8', ms: null },
cloudflare: { id: 'cloudflare', name: 'Cloudflare', host: '1.1.1.1', ms: null },
yandex: { id: 'yandex', name: 'Yandex', host: 'ya.ru', ms: null },
instagram: { id: 'instagram', name: 'Instagram', host: 'instagram.com', ms: null },
};
// GET /api/ping-services-list — список сервисов для пинга из настроек (для дашборда и редактора)
async function getPingServicesList(req, res) {
try {
const uiSettings = await loadUiSettingsSync();
const list = getPingServicesListFromSettings(uiSettings);
return res.json({ list });
} catch (e) {
console.error('ping-services-list error', e);
return res.json({ list: PING_SERVICES_DEFAULT });
}
}
// GET /api/ping-services — RTT до сервисов из списка (веб или через RouterOS по настройке)
async function getPingServices(req, res) {
try {
const uiSettings = await loadUiSettingsSync();
const servicesList = getPingServicesListFromSettings(uiSettings);
const fallbackPayload = {};
servicesList.forEach((s) => { fallbackPayload[s.id] = { id: s.id, name: s.name, host: s.host, ms: null }; });
const viaRouter = String(uiSettings.pingServicesSource || 'web').toLowerCase() === 'router';
const cacheSeconds = Math.max(0, parseInt(uiSettings.pingServicesCacheSeconds, 10) || 0);
@@ -414,7 +439,7 @@ async function getPingServices(req, res) {
return res.json(fallbackPayload);
}
const results = await Promise.all(
PING_SERVICES.map(async (svc) => {
servicesList.map(async (svc) => {
try {
const result = await runPingViaRouter(serverId, gatewayIpResolved || null, svc.host, 3);
const ms = typeof result.avgMs === 'number' ? Math.round(result.avgMs) : null;
@@ -434,7 +459,7 @@ async function getPingServices(req, res) {
}
const results = await Promise.all(
PING_SERVICES.map(async (svc) => {
servicesList.map(async (svc) => {
const ms = await measureTcpRtt(svc.host, svc.port, 6000);
return { id: svc.id, name: svc.name, host: svc.host, ms };
})
@@ -447,7 +472,9 @@ async function getPingServices(req, res) {
res.json(byId);
} catch (e) {
console.error('ping-services error', e);
res.status(500).json(fallbackPayload);
const fallback = {};
PING_SERVICES_DEFAULT.forEach((s) => { fallback[s.id] = { id: s.id, name: s.name, host: s.host, ms: null }; });
res.status(500).json(fallback);
}
}
@@ -666,6 +693,7 @@ module.exports = {
getWsUrl,
getUiSettings,
postUiSettings,
getPingServicesList,
getPingServices,
};
+1
View File
@@ -439,6 +439,7 @@ app.post('/api/update-bgp/background', bgpUpdateLimiter, miscRoutes.updateBgpBac
app.get('/api/ws/url', miscRoutes.getWsUrl);
app.get('/api/ui-settings', miscRoutes.getUiSettings);
app.post('/api/ui-settings', miscRoutes.postUiSettings);
app.get('/api/ping-services-list', miscRoutes.getPingServicesList);
app.get('/api/ping-services', miscRoutes.getPingServices);
// === IPSEC PASSWORDS ===