Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
632adaa63f |
@@ -12,14 +12,6 @@ import {
|
|||||||
AlertDescription,
|
AlertDescription,
|
||||||
AlertTitle,
|
AlertTitle,
|
||||||
} from '@/components/reui/alert'
|
} from '@/components/reui/alert'
|
||||||
import {
|
|
||||||
Frame,
|
|
||||||
FrameDescription,
|
|
||||||
FrameFooter,
|
|
||||||
FrameHeader,
|
|
||||||
FramePanel,
|
|
||||||
FrameTitle,
|
|
||||||
} from '@/components/reui/frame'
|
|
||||||
import { SelectMenu } from '@/components/select-field'
|
import { SelectMenu } from '@/components/select-field'
|
||||||
import { ApiError, apiMutate } from '@/lib/api-client'
|
import { ApiError, apiMutate } from '@/lib/api-client'
|
||||||
import type {
|
import type {
|
||||||
@@ -30,9 +22,8 @@ import type {
|
|||||||
} from '@/types/api'
|
} from '@/types/api'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lookup wizard step 3 — choose module + community and create entry.
|
* Lookup wizard step 3 — module + community (bare content for single Frame).
|
||||||
* @see https://reui.io/preview/base/wizard-2
|
* @see https://reui.io/preview/base/wizard-2
|
||||||
* @see https://reui.io/docs/components/base/frame
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function hostPrefixFromIp(ip: string): string {
|
function hostPrefixFromIp(ip: string): string {
|
||||||
@@ -153,14 +144,14 @@ export function LookupAddStep({
|
|||||||
: hostPrefixFromIp(data.normalized)
|
: hostPrefixFromIp(data.normalized)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Frame spacing="sm" className="w-full">
|
<div className="flex flex-col gap-6">
|
||||||
<FrameHeader>
|
<div className="flex flex-col gap-1">
|
||||||
<FrameTitle>Добавить в списки</FrameTitle>
|
<h3 className="text-sm font-semibold">Добавить в списки</h3>
|
||||||
<FrameDescription>
|
<p className="text-muted-foreground text-sm">
|
||||||
«{valueLabel}» отсутствует в списках. Выберите модуль и community.
|
«{valueLabel}» отсутствует в списках. Выберите модуль и community.
|
||||||
</FrameDescription>
|
</p>
|
||||||
</FrameHeader>
|
</div>
|
||||||
<FramePanel className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<Field>
|
<Field>
|
||||||
<FieldLabel htmlFor="lookup-add-module">Модуль ({wantedType})</FieldLabel>
|
<FieldLabel htmlFor="lookup-add-module">Модуль ({wantedType})</FieldLabel>
|
||||||
<SelectMenu
|
<SelectMenu
|
||||||
@@ -181,8 +172,8 @@ export function LookupAddStep({
|
|||||||
communities={communities}
|
communities={communities}
|
||||||
nullable={data.query_kind === 'domain'}
|
nullable={data.query_kind === 'domain'}
|
||||||
/>
|
/>
|
||||||
</FramePanel>
|
</div>
|
||||||
<FrameFooter className="flex flex-wrap items-center justify-end gap-2">
|
<div className="flex flex-wrap items-center justify-end gap-2 border-t pt-4">
|
||||||
<Button type="button" variant="outline" onClick={onCancel}>
|
<Button type="button" variant="outline" onClick={onCancel}>
|
||||||
Назад
|
Назад
|
||||||
</Button>
|
</Button>
|
||||||
@@ -193,7 +184,7 @@ export function LookupAddStep({
|
|||||||
>
|
>
|
||||||
Добавить
|
Добавить
|
||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
</FrameFooter>
|
</div>
|
||||||
</Frame>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { useNavigate } from '@tanstack/react-router'
|
|||||||
|
|
||||||
import { CategoryBadge } from '@/components/category-badge'
|
import { CategoryBadge } from '@/components/category-badge'
|
||||||
import { DataGridPrimaryCell } from '@/components/data-grid-cell'
|
import { DataGridPrimaryCell } from '@/components/data-grid-cell'
|
||||||
import { FrameDataGrid } from '@/components/reui-kit'
|
|
||||||
import { DataGridSection } from '@/components/data-grid-shell'
|
import { DataGridSection } from '@/components/data-grid-shell'
|
||||||
import { Badge } from '@/components/reui/badge'
|
import { Badge } from '@/components/reui/badge'
|
||||||
import { DataGridColumnHeader } from '@/components/reui/data-grid/data-grid-column-header'
|
import { DataGridColumnHeader } from '@/components/reui/data-grid/data-grid-column-header'
|
||||||
@@ -12,7 +11,8 @@ import { useClientDataGrid } from '@/hooks/use-client-data-grid'
|
|||||||
import type { LookupMatch } from '@/types/api'
|
import type { LookupMatch } from '@/types/api'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lookup matches grid — data-grid-filtering-2 pattern.
|
* Lookup matches grid — bare section for wizard-2 single Frame.
|
||||||
|
* @see https://reui.io/preview/base/wizard-2
|
||||||
* @see https://reui.io/preview/base/data-grid-filtering-2
|
* @see https://reui.io/preview/base/data-grid-filtering-2
|
||||||
* @see https://reui.io/docs/components/base/badge
|
* @see https://reui.io/docs/components/base/badge
|
||||||
*/
|
*/
|
||||||
@@ -113,10 +113,13 @@ export function LookupMatchesGrid({
|
|||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FrameDataGrid
|
<div className="flex flex-col gap-3">
|
||||||
title="Совпадения"
|
<div className="flex flex-col gap-px">
|
||||||
description="Entries и snapshots · клик по строке открывает модуль"
|
<h3 className="text-sm font-semibold">Совпадения</h3>
|
||||||
>
|
<p className="text-muted-foreground text-sm">
|
||||||
|
Entries и snapshots · клик по строке открывает модуль
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<DataGridSection
|
<DataGridSection
|
||||||
table={table}
|
table={table}
|
||||||
recordCount={filteredCount}
|
recordCount={filteredCount}
|
||||||
@@ -129,6 +132,6 @@ export function LookupMatchesGrid({
|
|||||||
void navigate({ to: '/modules/$moduleId', params: { moduleId: row.module_id } })
|
void navigate({ to: '/modules/$moduleId', params: { moduleId: row.module_id } })
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</FrameDataGrid>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,17 +8,10 @@ import {
|
|||||||
InputGroupAddon,
|
InputGroupAddon,
|
||||||
InputGroupInput,
|
InputGroupInput,
|
||||||
} from '@evobgp/ui/components/input-group'
|
} from '@evobgp/ui/components/input-group'
|
||||||
import {
|
|
||||||
Frame,
|
|
||||||
FrameDescription,
|
|
||||||
FrameHeader,
|
|
||||||
FramePanel,
|
|
||||||
FrameTitle,
|
|
||||||
} from '@/components/reui/frame'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lookup search form — Frame + InputGroup (form-7 pattern).
|
* Lookup search fields — bare form for wizard-2 single-Frame surface.
|
||||||
* @see https://reui.io/preview/base/form-7
|
* @see https://reui.io/preview/base/wizard-2
|
||||||
* @see https://reui.io/docs/components/base/frame
|
* @see https://reui.io/docs/components/base/frame
|
||||||
*/
|
*/
|
||||||
export function LookupSearchForm({
|
export function LookupSearchForm({
|
||||||
@@ -40,37 +33,27 @@ export function LookupSearchForm({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Frame spacing="sm" className="w-full">
|
<form onSubmit={handleSubmit} className="flex flex-col gap-4 sm:flex-row sm:items-end">
|
||||||
<FrameHeader>
|
<Field className="min-w-0 flex-1">
|
||||||
<FrameTitle>Проверка списка</FrameTitle>
|
<FieldLabel htmlFor="lookup-q">IP, CIDR или домен</FieldLabel>
|
||||||
<FrameDescription>
|
<InputGroup>
|
||||||
IP, CIDR или FQDN — поиск в entries и материализованных snapshots с community.
|
<InputGroupAddon align="inline-start">
|
||||||
</FrameDescription>
|
<Search aria-hidden />
|
||||||
</FrameHeader>
|
</InputGroupAddon>
|
||||||
<FramePanel>
|
<InputGroupInput
|
||||||
<form onSubmit={handleSubmit} className="flex flex-col gap-4 sm:flex-row sm:items-end">
|
id="lookup-q"
|
||||||
<Field className="min-w-0 flex-1">
|
name="q"
|
||||||
<FieldLabel htmlFor="lookup-q">IP, CIDR или домен</FieldLabel>
|
value={value}
|
||||||
<InputGroup>
|
onChange={(e) => setValue(e.target.value)}
|
||||||
<InputGroupAddon align="inline-start">
|
placeholder="8.8.8.8, 203.0.113.0/24 или example.com"
|
||||||
<Search aria-hidden />
|
autoComplete="off"
|
||||||
</InputGroupAddon>
|
autoFocus
|
||||||
<InputGroupInput
|
/>
|
||||||
id="lookup-q"
|
</InputGroup>
|
||||||
name="q"
|
</Field>
|
||||||
value={value}
|
<Button type="submit" disabled={isPending || !value.trim()}>
|
||||||
onChange={(e) => setValue(e.target.value)}
|
Проверить
|
||||||
placeholder="8.8.8.8, 203.0.113.0/24 или example.com"
|
</Button>
|
||||||
autoComplete="off"
|
</form>
|
||||||
autoFocus
|
|
||||||
/>
|
|
||||||
</InputGroup>
|
|
||||||
</Field>
|
|
||||||
<Button type="submit" disabled={isPending || !value.trim()}>
|
|
||||||
Проверить
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
</FramePanel>
|
|
||||||
</Frame>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState, type ReactNode } from 'react'
|
||||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { Check, Plus, Search, ShieldAlert } from 'lucide-react'
|
import { Check, Plus, Search, ShieldAlert } from 'lucide-react'
|
||||||
|
|
||||||
@@ -29,7 +29,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
Frame,
|
Frame,
|
||||||
FrameDescription,
|
FrameDescription,
|
||||||
FrameFooter,
|
|
||||||
FrameHeader,
|
FrameHeader,
|
||||||
FramePanel,
|
FramePanel,
|
||||||
FrameTitle,
|
FrameTitle,
|
||||||
@@ -42,8 +41,7 @@ import { lookupKeys, lookupQueryOptions } from '@/queries/lookup'
|
|||||||
import { modulesListQueryOptions } from '@/queries/modules'
|
import { modulesListQueryOptions } from '@/queries/modules'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lookup membership wizard — check → result → optional add.
|
* Lookup membership wizard — one Frame (wizard-2), all content inside.
|
||||||
* 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/docs/components/base/frame
|
* @see https://reui.io/docs/components/base/frame
|
||||||
@@ -55,6 +53,14 @@ const STEP_RESULT = 2
|
|||||||
const STEP_ADD = 3
|
const STEP_ADD = 3
|
||||||
const STEP_DONE = 4
|
const STEP_DONE = 4
|
||||||
|
|
||||||
|
function WizardActions({ children }: { children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-wrap items-center justify-end gap-2 border-t pt-4">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function LookupWizard({
|
export function LookupWizard({
|
||||||
q,
|
q,
|
||||||
onSubmitQuery,
|
onSubmitQuery,
|
||||||
@@ -109,13 +115,14 @@ export function LookupWizard({
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4 md:gap-6">
|
<div className="flex flex-col gap-4 md:gap-6">
|
||||||
<Frame spacing="sm" className="w-full">
|
<Frame spacing="sm" className="w-full">
|
||||||
<FrameHeader>
|
<FramePanel className="flex flex-col gap-6">
|
||||||
<FrameTitle>Мастер проверки</FrameTitle>
|
<FrameHeader className="px-0 py-0">
|
||||||
<FrameDescription>
|
<FrameTitle>Мастер проверки</FrameTitle>
|
||||||
Проверка IP / CIDR / домена в списках, затем при необходимости — добавление.
|
<FrameDescription>
|
||||||
</FrameDescription>
|
Проверка IP / CIDR / домена в списках, затем при необходимости — добавление.
|
||||||
</FrameHeader>
|
</FrameDescription>
|
||||||
<FramePanel>
|
</FrameHeader>
|
||||||
|
|
||||||
<Stepper
|
<Stepper
|
||||||
value={step}
|
value={step}
|
||||||
onValueChange={setStep}
|
onValueChange={setStep}
|
||||||
@@ -184,21 +191,21 @@ export function LookupWizard({
|
|||||||
|
|
||||||
<StepperPanel>
|
<StepperPanel>
|
||||||
<StepperContent value={STEP_QUERY}>
|
<StepperContent value={STEP_QUERY}>
|
||||||
<LookupSearchForm
|
<div className="flex flex-col gap-6">
|
||||||
key={`form-${trimmed}`}
|
<LookupSearchForm
|
||||||
initialQuery={trimmed}
|
key={`form-${trimmed}`}
|
||||||
isPending={lookupQ.isFetching}
|
initialQuery={trimmed}
|
||||||
onSubmit={onSubmitQuery}
|
isPending={lookupQ.isFetching}
|
||||||
/>
|
onSubmit={onSubmitQuery}
|
||||||
{!trimmed ? (
|
/>
|
||||||
<div className="mt-4">
|
{!trimmed ? (
|
||||||
<EmptyState
|
<EmptyState
|
||||||
icon={<Search className="size-8" />}
|
icon={<Search className="size-8" />}
|
||||||
title="Введите IP, CIDR или домен"
|
title="Введите IP, CIDR или домен"
|
||||||
description="Например 8.8.8.8, 203.0.113.0/24 или example.com — проверка по entries и snapshots."
|
description="Например 8.8.8.8, 203.0.113.0/24 или example.com — проверка по entries и snapshots."
|
||||||
/>
|
/>
|
||||||
</div>
|
) : null}
|
||||||
) : null}
|
</div>
|
||||||
</StepperContent>
|
</StepperContent>
|
||||||
|
|
||||||
<StepperContent value={STEP_RESULT}>
|
<StepperContent value={STEP_RESULT}>
|
||||||
@@ -240,17 +247,15 @@ export function LookupWizard({
|
|||||||
items={result.matches}
|
items={result.matches}
|
||||||
isLoading={lookupQ.isFetching}
|
isLoading={lookupQ.isFetching}
|
||||||
/>
|
/>
|
||||||
<Frame spacing="sm" className="w-full">
|
<WizardActions>
|
||||||
<FrameFooter className="flex flex-wrap items-center justify-end gap-2">
|
<Button
|
||||||
<Button
|
type="button"
|
||||||
type="button"
|
variant="outline"
|
||||||
variant="outline"
|
onClick={resetToQuery}
|
||||||
onClick={resetToQuery}
|
>
|
||||||
>
|
Новая проверка
|
||||||
Новая проверка
|
</Button>
|
||||||
</Button>
|
</WizardActions>
|
||||||
</FrameFooter>
|
|
||||||
</Frame>
|
|
||||||
</>
|
</>
|
||||||
) : canWrite ? (
|
) : canWrite ? (
|
||||||
<>
|
<>
|
||||||
@@ -261,20 +266,18 @@ export function LookupWizard({
|
|||||||
«{result.normalized}» отсутствует. Добавить запись?
|
«{result.normalized}» отсутствует. Добавить запись?
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
<Frame spacing="sm" className="w-full">
|
<WizardActions>
|
||||||
<FrameFooter className="flex flex-wrap items-center justify-end gap-2">
|
<Button
|
||||||
<Button
|
type="button"
|
||||||
type="button"
|
variant="outline"
|
||||||
variant="outline"
|
onClick={resetToQuery}
|
||||||
onClick={resetToQuery}
|
>
|
||||||
>
|
Новая проверка
|
||||||
Новая проверка
|
</Button>
|
||||||
</Button>
|
<Button type="button" onClick={() => goToAdd()}>
|
||||||
<Button type="button" onClick={() => goToAdd()}>
|
Добавить
|
||||||
Добавить
|
</Button>
|
||||||
</Button>
|
</WizardActions>
|
||||||
</FrameFooter>
|
|
||||||
</Frame>
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@@ -286,17 +289,15 @@ export function LookupWizard({
|
|||||||
права добавлять записи (нужно bgp:modules:write).
|
права добавлять записи (нужно bgp:modules:write).
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
<Frame spacing="sm" className="w-full">
|
<WizardActions>
|
||||||
<FrameFooter className="flex flex-wrap items-center justify-end gap-2">
|
<Button
|
||||||
<Button
|
type="button"
|
||||||
type="button"
|
variant="outline"
|
||||||
variant="outline"
|
onClick={resetToQuery}
|
||||||
onClick={resetToQuery}
|
>
|
||||||
>
|
Новая проверка
|
||||||
Новая проверка
|
</Button>
|
||||||
</Button>
|
</WizardActions>
|
||||||
</FrameFooter>
|
|
||||||
</Frame>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -343,20 +344,14 @@ export function LookupWizard({
|
|||||||
isLoading={lookupQ.isFetching}
|
isLoading={lookupQ.isFetching}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<Frame spacing="sm" className="w-full">
|
<WizardActions>
|
||||||
<FrameFooter className="flex flex-wrap items-center justify-end gap-2">
|
<Button type="button" variant="outline" onClick={resetToQuery}>
|
||||||
<Button
|
Новая проверка
|
||||||
type="button"
|
</Button>
|
||||||
variant="outline"
|
<Button type="button" onClick={() => setStep(STEP_RESULT)}>
|
||||||
onClick={resetToQuery}
|
К результату
|
||||||
>
|
</Button>
|
||||||
Новая проверка
|
</WizardActions>
|
||||||
</Button>
|
|
||||||
<Button type="button" onClick={() => setStep(STEP_RESULT)}>
|
|
||||||
К результату
|
|
||||||
</Button>
|
|
||||||
</FrameFooter>
|
|
||||||
</Frame>
|
|
||||||
</div>
|
</div>
|
||||||
</StepperContent>
|
</StepperContent>
|
||||||
</StepperPanel>
|
</StepperPanel>
|
||||||
|
|||||||
Reference in New Issue
Block a user