diff --git a/public/config.js b/public/config.js index aa58fa3..eb1dbfe 100644 --- a/public/config.js +++ b/public/config.js @@ -12,12 +12,28 @@ window.Config = { // 'm800679644-4ee3480057a34ce157103cba', // Read-Only ключ ], + // URL для проверки пинга (по порядку соответствуют API ключам) + PingUrls: [ + 'http://itachi.nj0.ru', // Для первого API ключа + 'http://hidan.nj0.ru', // Для второго API ключа + 'http://yugito.nj0.ru', // Для третьего API ключа + 'http://lando.nj0.ru', // Для четвертого API ключа + ], + // Количество дней в логах CountDays: 20, // Показывать ли ссылки на проверяемые сайты ShowLink: false, + // Настройки пинга + PingSettings: { + enabled: true, + timeout: 3000, + attempts: 3, + interval: 15000, // Обновление каждые 15 сек + }, + // Меню навигации Navi: [ { diff --git a/src/app.scss b/src/app.scss index 2029a80..9d5ae72 100644 --- a/src/app.scss +++ b/src/app.scss @@ -484,12 +484,62 @@ padding: 0.5rem 0; } +// Пинг в блоке статистики +.ping-stats-item { + display: flex; + flex-direction: column; + gap: 0.25rem; + padding: 0.5rem 0; +} + +.ping-stats-value { + font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace; + cursor: pointer; + transition: all 0.2s ease; +} + +.ping-stats-value:hover { + transform: scale(1.05); +} + +.ping-stats-value.ping-excellent { + color: #22c55e; +} + +.ping-stats-value.ping-good { + color: #16a34a; +} + +.ping-stats-value.ping-ok { + color: #f59e0b; +} + +.ping-stats-value.ping-poor { + color: #ef4444; +} + +.ping-stats-value.ping-fail { + color: #dc2626; +} + +.ping-stats-value.ping-loading { + color: var(--my-text-content-color); + font-style: italic; +} + @media (min-width: 768px) { .stats-item { flex-direction: row; justify-content: space-between; align-items: center; } + + // На ПК ping-статистика тоже должна быть горизонтальной + .ping-stats-item { + flex-direction: row; + justify-content: space-between; + align-items: center; + } } // Спиннер diff --git a/src/common/i18n.js b/src/common/i18n.js index e17c178..05b90d7 100644 --- a/src/common/i18n.js +++ b/src/common/i18n.js @@ -44,6 +44,9 @@ export const MESSAGES = { updateDescription: 'A new version of the application is available. Update now to get the latest improvements.', updateNow: 'Update Now', later: 'Later', + + // Пинг + pingStatus: 'Current delay', }, ru: { // Общие @@ -89,6 +92,9 @@ export const MESSAGES = { updateDescription: 'Доступна новая версия приложения. Обновите сейчас, чтобы получить последние улучшения.', updateNow: 'Обновить сейчас', later: 'Позже', + + // Пинг + pingStatus: 'Текущая задержка', }, } diff --git a/src/common/ping.js b/src/common/ping.js new file mode 100644 index 0000000..b7c6b51 --- /dev/null +++ b/src/common/ping.js @@ -0,0 +1,102 @@ +// Измерение пинга серверов +const PING_TIMEOUT = 3000 + +// Пинг с замером времени +export const measurePing = async (url, attempts = 3) => { + const times = [] + + for (let i = 0; i < attempts; i++) { + const controller = new AbortController() + const timeout = setTimeout(() => controller.abort(), PING_TIMEOUT) + const start = performance.now() + + try { + await fetch(url, { + method: 'HEAD', + mode: 'no-cors', + cache: 'no-store', + signal: controller.signal, + }) + const time = performance.now() - start + + if (i > 0) times.push(Math.round(time)) // пропускаем первый + } catch { + // Игнорируем ошибки + } + + clearTimeout(timeout) + } + + if (times.length === 0) return null + + const min = Math.min(...times) + const max = Math.max(...times) + const avg = Math.round(times.reduce((a, b) => a + b, 0) / times.length) + + return { avg, min, max, times } +} + +// Проверка пинга для списка серверов +export const checkServersLatency = async (servers) => { + const results = {} + + for (const server of servers) { + try { + const result = await measurePing(server.url) + results[server.name] = result + } catch (error) { + console.error(`Ошибка пинга для ${server.name}:`, error) + results[server.name] = null + } + } + + return results +} + +// Парсинг имени сервера для извлечения флага и типа +export const parseServerName = (name) => { + let cleanName = name + let countryCode = null + let serverType = null + + // Извлекаем тип сервера (CDN/DED/API) + const typeMatch = name.match(/\[(CDN|DED|API)\]/i) + if (typeMatch) { + serverType = typeMatch[1].toLowerCase() + cleanName = cleanName.replace(typeMatch[0], '').trim() + } + + // Извлекаем код страны + const flagMatch = name.match(/\[([A-Z]{2})\]/) + if (flagMatch) { + countryCode = flagMatch[1].toLowerCase() + cleanName = cleanName.replace(flagMatch[0], '').trim() + } + + return { + cleanName, + countryCode, + serverType, + originalName: name, + } +} + +// Цветовая классификация пинга +export const getPingClass = (ping) => { + if (!ping) return 'ping-fail' + if (ping.avg <= 50) return 'ping-excellent' + if (ping.avg <= 100) return 'ping-good' + if (ping.avg <= 200) return 'ping-ok' + return 'ping-poor' +} + +// Форматирование отображения пинга +export const formatPing = (ping) => { + if (!ping) return { text: 'timeout', class: 'ping-fail' } + + return { + text: `${ping.avg}ms`, + class: getPingClass(ping), + details: `AVG: ${ping.avg}ms\nMin/Max: ${ping.min}/${ping.max}ms\nDetails: ${ping.times.join('ms, ')}ms`, + } +} diff --git a/src/components/app.js b/src/components/app.js index 67fc43a..c5d50fc 100644 --- a/src/components/app.js +++ b/src/components/app.js @@ -27,9 +27,9 @@ const AppContent = () => {