Files
EvoBGP/apps/web/src/routes/_auth/modules/$moduleId.tsx
T
Denozordec 144d342c16
CI / changes (push) Successful in 11s
CI / commitlint (push) Has been skipped
CI / openapi (push) Has been skipped
CI / web (push) Successful in 1m3s
CI / go (push) Has been skipped
CI / bird2 (push) Has been skipped
CI / release (push) Successful in 4m4s
fix(modules): enhance ModuleDetailComponent with additional queries and UI updates
Updated the ModuleDetailComponent to include new queries for communities and DoH profiles, improving data handling. Added a module type alert function for better user guidance and refined the UI to display module type in a more user-friendly manner. Removed unused components and streamlined the refresh functionality for better performance.
2026-07-03 16:50:21 +07:00

148 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { createFileRoute, Link } from '@tanstack/react-router'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { ArrowLeft, Info, RefreshCw } from 'lucide-react'
import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert'
import { Button } from '@evobgp/ui/components/button'
import { PageHeader } from '@/components/page-header'
import { QueryState } from '@/components/query-state'
import { TableSkeleton } from '@/components/skeletons'
import { StatusBadge } from '@/components/status-badge'
import { ModuleEntriesSection } from '@/components/modules/module-entries-section'
import { ModuleKpiCards } from '@/components/modules/module-kpi-cards'
import { moduleTypeRu } from '@/lib/ui-labels'
import {
directoriesCommunitiesQueryOptions,
directoriesDohQueryOptions,
} from '@/queries/directories'
import { moduleDetailQueryOptions, moduleEntriesQueryOptions, modulesKeys } from '@/queries/modules'
import type { AsEntry, ModuleRow } from '@/types/api'
export const Route = createFileRoute('/_auth/modules/$moduleId')({
component: ModuleDetailComponent,
})
function moduleTypeAlert(type: ModuleRow['type']): string {
switch (type) {
case 'AS_PREFIXES':
return 'Модуль AS получает префиксы через RIPEstat по указанным ASN. После refresh счётчики префиксов обновляются в таблице записей.'
case 'CDN_CIDRS':
return 'Модуль CDN скачивает списки CIDR по URL (plaintext или JSON). Используйте предпросмотр при добавлении источника.'
case 'DOMAINS':
return 'Модуль доменов резолвит FQDN через DoH-профили и конвертирует IP в префиксы. Политика и профили настраиваются в редактировании модуля.'
case 'IP_RANGES':
return 'Модуль IP-диапазонов использует статические CIDR без внешнего refresh (сервер может вернуть 204). Записи участвуют в агрегации напрямую.'
}
}
function ModuleDetailComponent() {
const { moduleId } = Route.useParams()
const queryClient = useQueryClient()
const detail = useQuery(moduleDetailQueryOptions(moduleId))
const mod = detail.data
const communitiesQ = useQuery(directoriesCommunitiesQueryOptions())
const dohQ = useQuery(directoriesDohQueryOptions())
const entriesQuery = useQuery({
...moduleEntriesQueryOptions(moduleId, mod?.type ?? 'DOMAINS'),
enabled: !!mod,
})
const asEntriesQ = useQuery({
...moduleEntriesQueryOptions(moduleId, 'AS_PREFIXES'),
enabled: mod?.type === 'AS_PREFIXES',
select: (data) => data.items as unknown as AsEntry[],
})
async function refreshAll() {
await Promise.all([detail.refetch(), entriesQuery.refetch(), communitiesQ.refetch(), dohQ.refetch()])
}
async function onEntriesChanged() {
await entriesQuery.refetch()
if (mod?.type === 'AS_PREFIXES') {
await queryClient.invalidateQueries({ queryKey: modulesKeys.asEntries(moduleId) })
}
await detail.refetch()
}
const communities = communitiesQ.data?.items ?? []
const dohProfiles = dohQ.data?.items ?? []
const asEntries = mod?.type === 'AS_PREFIXES' ? (asEntriesQ.data ?? []) : []
return (
<div className="flex flex-col gap-6">
<PageHeader
title={mod?.name ?? moduleId}
description={mod ? `Тип: ${moduleTypeRu(mod.type)} (${mod.type})` : 'Загрузка модуля…'}
actions={
<>
<Button variant="outline" size="sm" render={<Link to="/modules" />}>
<ArrowLeft />
К списку
</Button>
<Button
variant="outline"
size="sm"
onClick={() => void refreshAll()}
disabled={detail.isFetching}
>
<RefreshCw className={detail.isFetching ? 'animate-spin' : ''} />
Обновить
</Button>
</>
}
/>
<QueryState
data={mod}
isLoading={detail.isLoading}
isError={detail.isError}
error={detail.error}
skeleton={<TableSkeleton rows={4} cols={2} />}
onRetry={() => detail.refetch()}
>
{(m) => (
<div className="flex flex-col gap-6">
<div className="flex flex-wrap items-center gap-2">
{m.enabled ? (
<StatusBadge status="active" label="включён" />
) : (
<StatusBadge status="paused" label="выключен" />
)}
</div>
<Alert>
<Info />
<AlertTitle>О модуле</AlertTitle>
<AlertDescription>{moduleTypeAlert(m.type)}</AlertDescription>
</Alert>
<ModuleKpiCards
mod={m}
communities={communities}
dohProfiles={dohProfiles}
asEntries={asEntries}
loading={detail.isLoading || communitiesQ.isLoading}
/>
<ModuleEntriesSection
moduleId={moduleId}
mod={m}
items={entriesQuery.data?.items ?? []}
communities={communities}
isLoading={entriesQuery.isLoading}
isError={entriesQuery.isError}
error={entriesQuery.error}
onRetry={() => entriesQuery.refetch()}
onChanged={onEntriesChanged}
/>
</div>
)}
</QueryState>
</div>
)
}