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