feat: Add language and theme context providers to App component, implement global search functionality in Dashboard, and enhance ASNs, Domains, and IPRanges managers with drag-and-drop import feature and confirmation modals for improved user experience
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 5m48s

This commit is contained in:
2025-08-10 23:44:17 +07:00
parent ca56b52672
commit 1fb53c3467
8 changed files with 460 additions and 25 deletions
+36 -8
View File
@@ -13,7 +13,8 @@ import {
IconRefresh,
IconFilter,
IconDownload,
IconCreditCard
IconCreditCard,
IconSearch
} from '@tabler/icons-react';
function StatCard({ icon: Icon, color, value, title, subtitle, to }) {
@@ -56,6 +57,7 @@ function MetricCard({ title, value, icon: Icon, color, description }) {
}
function Dashboard() {
const [globalQuery, setGlobalQuery] = useState('');
const [stats, setStats] = useState({
domainsCount: null,
ipRangesCount: null,
@@ -67,6 +69,7 @@ function Dashboard() {
onlineServers: null,
totalServers: null
});
const [raw, setRaw] = useState({ domains: [], ipRanges: [], asns: [], servers: [] });
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
@@ -92,7 +95,11 @@ function Dashboard() {
const s3Res = results[4];
// Получаем данные серверов для подсчета дополнительной статистики
const domains = domainsRes.status === 'fulfilled' ? domainsRes.value.data : [];
const ipRanges = ipRangesRes.status === 'fulfilled' ? ipRangesRes.value.data : [];
const asns = asnsRes.status === 'fulfilled' ? asnsRes.value.data : [];
const servers = serversRes.status === 'fulfilled' ? serversRes.value.data : [];
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 = servers.filter(server => server.status === 'Онлайн').length;
@@ -103,9 +110,9 @@ function Dashboard() {
const lastModified = lmRaw ? new Date(lmRaw).toLocaleString() : new Date().toLocaleString();
setStats({
domainsCount: domainsRes.status === 'fulfilled' ? domainsRes.value.data.length : 0,
ipRangesCount: ipRangesRes.status === 'fulfilled' ? ipRangesRes.value.data.length : 0,
asnsCount: asnsRes.status === 'fulfilled' ? asnsRes.value.data.length : 0,
domainsCount: domains.length,
ipRangesCount: ipRanges.length,
asnsCount: asns.length,
serversCount: servers.length,
lastModified,
countriesCount: countries.size,
@@ -151,6 +158,19 @@ function Dashboard() {
return (
<div>
{/* Глобальный поиск */}
<div className="card mb-3">
<div className="card-body d-flex align-items-center">
<span className="input-icon-addon me-2"><IconSearch size={18} /></span>
<input
type="text"
className="form-control"
placeholder="Глобальный поиск по доменам, ASN, IP, серверам..."
value={globalQuery}
onChange={(e) => setGlobalQuery(e.target.value)}
/>
</div>
</div>
{/* Заголовок страницы */}
<div className="page-header d-print-none mb-4">
<div className="row align-items-center">
@@ -172,7 +192,9 @@ function Dashboard() {
<StatCard
icon={IconWorld}
color="blue"
value={loading ? '...' : stats.domainsCount ?? '—'}
value={loading ? '...' : (globalQuery
? raw.domains.filter(d => `${d.domain} ${d.community || d.type || ''}`.toLowerCase().includes(globalQuery.toLowerCase())).length
: (stats.domainsCount ?? '—'))}
title="Доменов"
subtitle="Всего доменов в системе"
to="/domains"
@@ -182,7 +204,9 @@ function Dashboard() {
<StatCard
icon={IconNetwork}
color="green"
value={loading ? '...' : stats.ipRangesCount ?? '—'}
value={loading ? '...' : (globalQuery
? raw.ipRanges.filter(x => `${x.ipRange} ${x.community || ''}`.toLowerCase().includes(globalQuery.toLowerCase())).length
: (stats.ipRangesCount ?? '—'))}
title="IP-диапазонов"
subtitle="Всего IP диапазонов"
to="/ip-ranges"
@@ -192,7 +216,9 @@ function Dashboard() {
<StatCard
icon={IconNetwork}
color="purple"
value={loading ? '...' : stats.asnsCount ?? '—'}
value={loading ? '...' : (globalQuery
? raw.asns.filter(a => `${a.domain || a.asn} ${a.type || a.community || ''}`.toLowerCase().includes(globalQuery.toLowerCase())).length
: (stats.asnsCount ?? '—'))}
title="AS"
subtitle="Всего Autonomous Systems"
to="/asns"
@@ -202,7 +228,9 @@ function Dashboard() {
<StatCard
icon={IconServer}
color="orange"
value={loading ? '...' : stats.serversCount ?? '—'}
value={loading ? '...' : (globalQuery
? raw.servers.filter(s => `${s.ip} ${s.dns} ${s.country} ${s.provider}`.toLowerCase().includes(globalQuery.toLowerCase())).length
: (stats.serversCount ?? '—'))}
title="Серверов"
subtitle="Всего серверов"
to="/servers"