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;