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
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.
148 lines
5.6 KiB
TypeScript
148 lines
5.6 KiB
TypeScript
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>
|
||
)
|
||
}
|