Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc80858952 |
@@ -1,62 +0,0 @@
|
|||||||
import { Globe, Layers, ListChecks, Radar } from 'lucide-react'
|
|
||||||
|
|
||||||
import { KpiStatGrid, type KpiStatItem } from '@/components/reui-kit'
|
|
||||||
import type { LookupResponse } from '@/types/api'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lookup summary KPI — stats-12 via KpiStatGrid.
|
|
||||||
* @see https://reui.io/preview/base/stats-12
|
|
||||||
*/
|
|
||||||
export function LookupSummaryKpi({ data }: { data: LookupResponse }) {
|
|
||||||
const entryCount = data.matches.filter((m) => m.layer === 'entry').length
|
|
||||||
const snapshotCount = data.matches.filter((m) => m.layer === 'snapshot').length
|
|
||||||
const resolvedCount = data.resolved_ips?.length ?? 0
|
|
||||||
|
|
||||||
const items: KpiStatItem[] = [
|
|
||||||
{
|
|
||||||
id: 'matched',
|
|
||||||
label: 'Результат',
|
|
||||||
value: data.matched ? 'Найдено' : 'Не найдено',
|
|
||||||
hint: data.normalized,
|
|
||||||
icon: <Radar aria-hidden />,
|
|
||||||
iconClassName: data.matched
|
|
||||||
? 'bg-success text-success-foreground [&_svg]:text-success-foreground'
|
|
||||||
: 'bg-muted text-muted-foreground [&_svg]:text-muted-foreground',
|
|
||||||
variant: data.matched ? 'default' : 'warning',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'entry',
|
|
||||||
label: 'Слой entry',
|
|
||||||
value: entryCount,
|
|
||||||
hint: 'сырые списки',
|
|
||||||
icon: <ListChecks aria-hidden />,
|
|
||||||
iconClassName: 'bg-info text-info-foreground [&_svg]:text-info-foreground',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'snapshot',
|
|
||||||
label: 'Слой snapshot',
|
|
||||||
value: snapshotCount,
|
|
||||||
hint: 'материализация',
|
|
||||||
icon: <Layers aria-hidden />,
|
|
||||||
iconClassName: 'bg-focus text-focus-foreground [&_svg]:text-focus-foreground',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
if (data.query_kind === 'domain') {
|
|
||||||
items.push({
|
|
||||||
id: 'resolved',
|
|
||||||
label: 'DNS IP',
|
|
||||||
value: resolvedCount,
|
|
||||||
hint: resolvedCount > 0 ? data.resolved_ips?.slice(0, 3).join(', ') : 'нет A/AAAA',
|
|
||||||
icon: <Globe aria-hidden />,
|
|
||||||
iconClassName: 'bg-primary text-primary-foreground [&_svg]:text-primary-foreground',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<KpiStatGrid
|
|
||||||
items={items}
|
|
||||||
aria-label={`Запрос: ${data.query_kind} · ${data.query}`}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -7,12 +7,10 @@ import { Button } from '@evobgp/ui/components/button'
|
|||||||
import { LookupAddStep } from '@/components/lookup/lookup-add-step'
|
import { LookupAddStep } from '@/components/lookup/lookup-add-step'
|
||||||
import { LookupMatchesGrid } from '@/components/lookup/lookup-matches-grid'
|
import { LookupMatchesGrid } from '@/components/lookup/lookup-matches-grid'
|
||||||
import { LookupSearchForm } from '@/components/lookup/lookup-search-form'
|
import { LookupSearchForm } from '@/components/lookup/lookup-search-form'
|
||||||
import { LookupSummaryKpi } from '@/components/lookup/lookup-summary-kpi'
|
|
||||||
import { EmptyState } from '@/components/empty-state'
|
import { EmptyState } from '@/components/empty-state'
|
||||||
import { QueryState } from '@/components/query-state'
|
import { QueryState } from '@/components/query-state'
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
AlertAction,
|
|
||||||
AlertDescription,
|
AlertDescription,
|
||||||
AlertTitle,
|
AlertTitle,
|
||||||
} from '@/components/reui/alert'
|
} from '@/components/reui/alert'
|
||||||
@@ -31,11 +29,12 @@ import {
|
|||||||
import {
|
import {
|
||||||
Frame,
|
Frame,
|
||||||
FrameDescription,
|
FrameDescription,
|
||||||
|
FrameFooter,
|
||||||
FrameHeader,
|
FrameHeader,
|
||||||
FramePanel,
|
FramePanel,
|
||||||
FrameTitle,
|
FrameTitle,
|
||||||
} from '@/components/reui/frame'
|
} from '@/components/reui/frame'
|
||||||
import { SectionCardsSkeleton, TableSkeleton } from '@/components/skeletons'
|
import { TableSkeleton } from '@/components/skeletons'
|
||||||
import { sessionCanWriteModules } from '@/lib/auth'
|
import { sessionCanWriteModules } from '@/lib/auth'
|
||||||
import { authSessionQueryOptions } from '@/queries/auth'
|
import { authSessionQueryOptions } from '@/queries/auth'
|
||||||
import { directoriesCommunitiesQueryOptions } from '@/queries/directories'
|
import { directoriesCommunitiesQueryOptions } from '@/queries/directories'
|
||||||
@@ -44,10 +43,10 @@ import { modulesListQueryOptions } from '@/queries/modules'
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Lookup membership wizard — check → result → optional add.
|
* Lookup membership wizard — check → result → optional add.
|
||||||
* DNA: wizard-2 · surface frame · stepper.
|
* DNA: wizard-2 · surface frame · stepper · FrameFooter CTAs.
|
||||||
* @see https://reui.io/preview/base/wizard-2
|
* @see https://reui.io/preview/base/wizard-2
|
||||||
* @see https://reui.io/docs/components/base/stepper
|
* @see https://reui.io/docs/components/base/stepper
|
||||||
* @see https://reui.io/preview/base/stats-12
|
* @see https://reui.io/docs/components/base/frame
|
||||||
* @see https://reui.io/preview/base/data-grid-filtering-2
|
* @see https://reui.io/preview/base/data-grid-filtering-2
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -216,12 +215,7 @@ export function LookupWizard({
|
|||||||
isError={lookupQ.isError}
|
isError={lookupQ.isError}
|
||||||
error={lookupQ.error}
|
error={lookupQ.error}
|
||||||
onRetry={() => void lookupQ.refetch()}
|
onRetry={() => void lookupQ.refetch()}
|
||||||
skeleton={
|
skeleton={<TableSkeleton rows={5} />}
|
||||||
<div className="flex flex-col gap-4 md:gap-6">
|
|
||||||
<SectionCardsSkeleton />
|
|
||||||
<TableSkeleton rows={5} />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{(result) => (
|
{(result) => (
|
||||||
<div className="flex flex-col gap-4 md:gap-6">
|
<div className="flex flex-col gap-4 md:gap-6">
|
||||||
@@ -231,7 +225,6 @@ export function LookupWizard({
|
|||||||
isPending={lookupQ.isFetching}
|
isPending={lookupQ.isFetching}
|
||||||
onSubmit={onSubmitQuery}
|
onSubmit={onSubmitQuery}
|
||||||
/>
|
/>
|
||||||
<LookupSummaryKpi data={result} />
|
|
||||||
|
|
||||||
{result.matched ? (
|
{result.matched ? (
|
||||||
<>
|
<>
|
||||||
@@ -242,66 +235,69 @@ export function LookupWizard({
|
|||||||
«{result.normalized}» найден в entries и/или snapshots
|
«{result.normalized}» найден в entries и/или snapshots
|
||||||
({result.match_count} совпад.).
|
({result.match_count} совпад.).
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
<AlertAction>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
size="xs"
|
|
||||||
onClick={resetToQuery}
|
|
||||||
>
|
|
||||||
Новая проверка
|
|
||||||
</Button>
|
|
||||||
</AlertAction>
|
|
||||||
</Alert>
|
</Alert>
|
||||||
<LookupMatchesGrid
|
<LookupMatchesGrid
|
||||||
items={result.matches}
|
items={result.matches}
|
||||||
isLoading={lookupQ.isFetching}
|
isLoading={lookupQ.isFetching}
|
||||||
/>
|
/>
|
||||||
|
<Frame spacing="sm" className="w-full">
|
||||||
|
<FrameFooter className="flex flex-wrap items-center justify-end gap-2">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
onClick={resetToQuery}
|
||||||
|
>
|
||||||
|
Новая проверка
|
||||||
|
</Button>
|
||||||
|
</FrameFooter>
|
||||||
|
</Frame>
|
||||||
</>
|
</>
|
||||||
) : canWrite ? (
|
) : canWrite ? (
|
||||||
<Alert variant="info">
|
<>
|
||||||
<Plus className="size-4" />
|
<Alert variant="info">
|
||||||
<AlertTitle>Не найдено в списках</AlertTitle>
|
<Plus className="size-4" />
|
||||||
<AlertDescription>
|
<AlertTitle>Не найдено в списках</AlertTitle>
|
||||||
«{result.normalized}» отсутствует. Добавить запись?
|
<AlertDescription>
|
||||||
</AlertDescription>
|
«{result.normalized}» отсутствует. Добавить запись?
|
||||||
<AlertAction>
|
</AlertDescription>
|
||||||
<Button
|
</Alert>
|
||||||
type="button"
|
<Frame spacing="sm" className="w-full">
|
||||||
size="xs"
|
<FrameFooter className="flex flex-wrap items-center justify-end gap-2">
|
||||||
onClick={() => void goToAdd()}
|
<Button
|
||||||
>
|
type="button"
|
||||||
Добавить
|
variant="outline"
|
||||||
</Button>
|
onClick={resetToQuery}
|
||||||
<Button
|
>
|
||||||
type="button"
|
Новая проверка
|
||||||
variant="outline"
|
</Button>
|
||||||
size="xs"
|
<Button type="button" onClick={() => goToAdd()}>
|
||||||
onClick={resetToQuery}
|
Добавить
|
||||||
>
|
</Button>
|
||||||
Новая проверка
|
</FrameFooter>
|
||||||
</Button>
|
</Frame>
|
||||||
</AlertAction>
|
</>
|
||||||
</Alert>
|
|
||||||
) : (
|
) : (
|
||||||
<Alert variant="warning">
|
<>
|
||||||
<ShieldAlert className="size-4" />
|
<Alert variant="warning">
|
||||||
<AlertTitle>Не найдено</AlertTitle>
|
<ShieldAlert className="size-4" />
|
||||||
<AlertDescription>
|
<AlertTitle>Не найдено</AlertTitle>
|
||||||
«{result.normalized}» отсутствует в списках. У вас нет
|
<AlertDescription>
|
||||||
права добавлять записи (нужно bgp:modules:write).
|
«{result.normalized}» отсутствует в списках. У вас нет
|
||||||
</AlertDescription>
|
права добавлять записи (нужно bgp:modules:write).
|
||||||
<AlertAction>
|
</AlertDescription>
|
||||||
<Button
|
</Alert>
|
||||||
type="button"
|
<Frame spacing="sm" className="w-full">
|
||||||
variant="outline"
|
<FrameFooter className="flex flex-wrap items-center justify-end gap-2">
|
||||||
size="xs"
|
<Button
|
||||||
onClick={resetToQuery}
|
type="button"
|
||||||
>
|
variant="outline"
|
||||||
Новая проверка
|
onClick={resetToQuery}
|
||||||
</Button>
|
>
|
||||||
</AlertAction>
|
Новая проверка
|
||||||
</Alert>
|
</Button>
|
||||||
|
</FrameFooter>
|
||||||
|
</Frame>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -340,35 +336,27 @@ export function LookupWizard({
|
|||||||
Повторная проверка обновлена. Можно посмотреть совпадения или
|
Повторная проверка обновлена. Можно посмотреть совпадения или
|
||||||
начать новый запрос.
|
начать новый запрос.
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
<AlertAction>
|
</Alert>
|
||||||
<Button
|
{lookupQ.data?.matched ? (
|
||||||
type="button"
|
<LookupMatchesGrid
|
||||||
size="xs"
|
items={lookupQ.data.matches}
|
||||||
onClick={() => setStep(STEP_RESULT)}
|
isLoading={lookupQ.isFetching}
|
||||||
>
|
/>
|
||||||
К результату
|
) : null}
|
||||||
</Button>
|
<Frame spacing="sm" className="w-full">
|
||||||
|
<FrameFooter className="flex flex-wrap items-center justify-end gap-2">
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="xs"
|
|
||||||
onClick={resetToQuery}
|
onClick={resetToQuery}
|
||||||
>
|
>
|
||||||
Новая проверка
|
Новая проверка
|
||||||
</Button>
|
</Button>
|
||||||
</AlertAction>
|
<Button type="button" onClick={() => setStep(STEP_RESULT)}>
|
||||||
</Alert>
|
К результату
|
||||||
{lookupQ.data ? (
|
</Button>
|
||||||
<>
|
</FrameFooter>
|
||||||
<LookupSummaryKpi data={lookupQ.data} />
|
</Frame>
|
||||||
{lookupQ.data.matched ? (
|
|
||||||
<LookupMatchesGrid
|
|
||||||
items={lookupQ.data.matches}
|
|
||||||
isLoading={lookupQ.isFetching}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
</StepperContent>
|
</StepperContent>
|
||||||
</StepperPanel>
|
</StepperPanel>
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ export type BgpCommunityPatch = Partial<BgpCommunityCreate>
|
|||||||
export type CommunitiesResponse = Page<BgpCommunity>
|
export type CommunitiesResponse = Page<BgpCommunity>
|
||||||
|
|
||||||
// ---- Lookup (GET /v1/lookup) ----
|
// ---- Lookup (GET /v1/lookup) ----
|
||||||
/** @see https://reui.io/preview/base/stats-12 — KPI summary on /lookup */
|
/** @see https://reui.io/preview/base/wizard-2 — lookup membership wizard */
|
||||||
export type LookupQueryKind = 'ip' | 'domain' | 'cidr'
|
export type LookupQueryKind = 'ip' | 'domain' | 'cidr'
|
||||||
export type LookupLayer = 'entry' | 'snapshot'
|
export type LookupLayer = 'entry' | 'snapshot'
|
||||||
export type LookupMatchKind = 'ip_range' | 'domain' | 'prefix'
|
export type LookupMatchKind = 'ip_range' | 'domain' | 'prefix'
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user