import { useState, useEffect } from 'react'; import { Link } from 'react-router-dom'; import api from './lib/api.js'; import { formatDateTime } from './lib/datetime.js'; import { IconWorld, IconNetwork, IconServer, IconClock, IconMapPin, IconCloud, IconShield, IconAlertTriangle, IconRefresh, IconFilter, IconDownload, IconCreditCard, IconSearch } from '@tabler/icons-react'; import PageHeader from './components/PageHeader.jsx'; import Breadcrumbs from './components/Breadcrumbs.jsx'; import TopNStats from './components/TopNStats.jsx'; import TrendIndicator from './components/TrendIndicator.jsx'; import LastSaved from './components/LastSaved.jsx'; import Tooltip from './components/Tooltip.jsx'; function StatCard({ icon: Icon, color, value, title, subtitle, to, trend, previousValue }) { return (
{value}
{trend && previousValue !== undefined && ( )}
{title}
{subtitle && (
{subtitle}
)}
{to && ( )}
); } function MetricCard({ title, value, icon: Icon, color, description }) { return (
{value} {title}
{description && (
{description}
)}
); } function Dashboard() { // Глобальный поиск удалён по требованию UX const [stats, setStats] = useState({ domainsCount: null, ipRangesCount: null, asnsCount: null, serversCount: null, lastModified: null, countriesCount: null, providersCount: null, onlineServers: null, totalServers: null }); const [previousStats, setPreviousStats] = useState(null); const [raw, setRaw] = useState({ domains: [], ipRanges: [], asns: [], servers: [] }); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [lastFetchTime, setLastFetchTime] = useState(null); useEffect(() => { async function fetchStats() { setLoading(true); setError(null); try { // Загружаем данные с обработкой ошибок для каждого endpoint const results = await Promise.allSettled([ api.get('/domains-new', { params: { countOnly: true } }), api.get('/ip-ranges', { params: { countOnly: true } }), api.get('/asns', { params: { countOnly: true } }), api.get('/servers'), api.get('/servers/availability', { params: { ttlSeconds: 60 } }), api.get('/s3/last-modified') ]); // Обрабатываем результаты const domainsRes = results[0]; const ipRangesRes = results[1]; const asnsRes = results[2]; const serversRes = results[3]; const availabilityRes = results[4]; const s3Res = results[5]; // Получаем данные серверов для подсчета дополнительной статистики const domains = []; const ipRanges = []; const asns = []; const serversRaw = serversRes.status === 'fulfilled' ? serversRes.value.data : []; const servers = Array.isArray(serversRaw) ? serversRaw : []; setRaw({ domains, ipRanges, asns, servers }); const countries = new Set(servers.map(server => server.country).filter(Boolean)); const providers = new Set(servers.map(server => server.provider).filter(Boolean)); const onlineServers = availabilityRes.status === 'fulfilled' ? (availabilityRes.value.data?.online || 0) : (servers.filter(server => server.status === 'Онлайн').length); // Получаем дату последнего обновления // Новый формат: объект с ключами { domainsNew, asns, servers, filters, ipRanges } const lmRaw = s3Res.status === 'fulfilled' ? s3Res.value.data?.domainsNew?.lastModified : null; const lastModified = lmRaw ? formatDateTime(lmRaw) : formatDateTime(new Date()); const domainsCount = domainsRes.status === 'fulfilled' && typeof domainsRes.value.data?.total === 'number' ? domainsRes.value.data.total : 0; const ipRangesCount = ipRangesRes.status === 'fulfilled' && typeof ipRangesRes.value.data?.total === 'number' ? ipRangesRes.value.data.total : 0; const asnsCount = asnsRes.status === 'fulfilled' && typeof asnsRes.value.data?.total === 'number' ? asnsRes.value.data.total : 0; const newStats = { domainsCount, ipRangesCount, asnsCount, serversCount: servers.length, lastModified, countriesCount: countries.size, providersCount: providers.size, onlineServers, totalServers: servers.length }; // Сохраняем предыдущие значения перед обновлением setStats(prevStats => { if (prevStats.domainsCount !== null) { setPreviousStats(prevStats); } return newStats; }); setLastFetchTime(new Date().toISOString()); // Проверяем, есть ли ошибки const errors = results.filter(result => result.status === 'rejected'); if (errors.length > 0) { console.warn('Некоторые API недоступны:', errors.map(e => e.reason?.message)); } } catch (e) { console.error('Критическая ошибка загрузки статистики:', e); setError('Не удалось загрузить статистику'); setStats({ domainsCount: null, ipRangesCount: null, asnsCount: null, serversCount: null, lastModified: null, countriesCount: null, providersCount: null, onlineServers: null, totalServers: null }); } finally { setLoading(false); } } fetchStats(); }, []); if (error) { return (
{error}
); } return (
{/* Глобальный поиск удалён */} {/* Заголовок страницы */} )} actions={(
{lastFetchTime && ( )}
)} /> {/* Основные метрики */}
{/* Дополнительные метрики */}
{/* Top-N статистика */}

Топ статистика

{/* Убрали быстрые действия, карточки выше кликабельны */}
); } export default Dashboard;