mirror of https://github.com/yb/uptime-status
feat: add ping functionality and update config for server monitoring
parent
935fa4970c
commit
c27aed85e4
|
@ -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: [
|
||||
{
|
||||
|
|
50
src/app.scss
50
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Спиннер
|
||||
|
|
|
@ -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: 'Текущая задержка',
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -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`,
|
||||
}
|
||||
}
|
|
@ -27,9 +27,9 @@ const AppContent = () => {
|
|||
</div>
|
||||
|
||||
<div id="uptime" className="row g-3 mb-5">
|
||||
{apikeys.map((key) => (
|
||||
{apikeys.map((key, index) => (
|
||||
<div key={key} className="col-12">
|
||||
<UptimeRobot apikey={key} />
|
||||
<UptimeRobot apikey={key} pingUrl={window.Config?.PingUrls?.[index]} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import ReactTooltip from 'react-tooltip'
|
||||
import { getCountryCodeFromServerName, getCountryName } from '../common/country-flags'
|
||||
import { formatDuration, formatNumber } from '../common/helper'
|
||||
import { formatPing, measurePing } from '../common/ping'
|
||||
import { GetMonitors } from '../common/uptimerobot'
|
||||
import { useLanguage } from '../contexts/LanguageContext'
|
||||
import Link from './link'
|
||||
|
||||
const UptimeRobot = ({ apikey }) => {
|
||||
const UptimeRobot = ({ apikey, pingUrl }) => {
|
||||
const { t } = useLanguage()
|
||||
|
||||
const status = {
|
||||
|
@ -18,6 +19,25 @@ const UptimeRobot = ({ apikey }) => {
|
|||
const { CountDays, ShowLink } = window.Config
|
||||
const [monitors, setMonitors] = useState(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [pingResult, setPingResult] = useState(null)
|
||||
const [pingLoading, setPingLoading] = useState(false)
|
||||
const pingIntervalRef = useRef(null)
|
||||
|
||||
// Функция для обновления пинга
|
||||
const updatePing = async () => {
|
||||
if (!pingUrl || !window.Config?.PingSettings?.enabled) return
|
||||
|
||||
setPingLoading(true)
|
||||
try {
|
||||
const result = await measurePing(pingUrl, window.Config.PingSettings.attempts || 3)
|
||||
setPingResult(result)
|
||||
} catch (error) {
|
||||
console.error('Ошибка измерения пинга:', error)
|
||||
setPingResult(null)
|
||||
} finally {
|
||||
setPingLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// Debouncing - задержка 500мс перед запросом
|
||||
|
@ -32,6 +52,24 @@ const UptimeRobot = ({ apikey }) => {
|
|||
return () => clearTimeout(timeoutId)
|
||||
}, [apikey, CountDays])
|
||||
|
||||
// Эффект для пинга
|
||||
useEffect(() => {
|
||||
if (pingUrl && window.Config?.PingSettings?.enabled) {
|
||||
// Сразу измеряем пинг
|
||||
updatePing()
|
||||
|
||||
// Устанавливаем интервал
|
||||
const interval = window.Config.PingSettings.interval || 15000
|
||||
pingIntervalRef.current = setInterval(updatePing, interval)
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (pingIntervalRef.current) {
|
||||
clearInterval(pingIntervalRef.current)
|
||||
}
|
||||
}
|
||||
}, [pingUrl])
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="my-block-big">
|
||||
|
@ -229,6 +267,29 @@ const UptimeRobot = ({ apikey }) => {
|
|||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Показываем пинг в блоке статистики, если URL настроен */}
|
||||
{pingUrl && (
|
||||
<div className="col-md-6 ping-stats-col">
|
||||
<div className="stats-item ping-stats-item">
|
||||
<span className="my-text-content">{t('pingStatus')}</span>
|
||||
{(() => {
|
||||
const formatted = formatPing(pingResult)
|
||||
const tooltipText = pingResult ? formatted.details : 'Измерение пинга...'
|
||||
return (
|
||||
<span
|
||||
className={`my-text-heading fw-semibold ping-stats-value ${formatted.class} ${
|
||||
pingLoading ? 'ping-loading' : ''
|
||||
}`}
|
||||
data-tip={tooltipText}
|
||||
>
|
||||
📡 {pingLoading ? '...' : formatted.text}
|
||||
</span>
|
||||
)
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ReactTooltip className="tooltip" place="top" type="dark" effect="solid" multiline={true} delayShow={200} delayHide={100} />
|
||||
|
|
Loading…
Reference in New Issue