import { useMemo, useState } from 'react' import { CheckIcon, Network, SearchIcon, XIcon } from 'lucide-react' import { Button } from '@evobgp/ui/components/button' import { Input } from '@evobgp/ui/components/input' import { Label } from '@evobgp/ui/components/label' import { ConfirmDialog } from '@/components/confirm-dialog' import { DataGridMonoCell, DataGridNameCell } from '@/components/data-grid-cell' import { FormDrawer } from '@/components/form-drawer' import { LoadingButton } from '@/components/loading-button' import { SelectField } from '@/components/select-field' import { StatusBadge } from '@/components/status-badge' import { DataGridColumnHeader } from '@/components/reui/data-grid/data-grid-column-header' import type { FilterField, FilterQuery } from '@/components/reui/filters/filters-types' import { ResourcePage, createTextFilterQuery, type DataGridColumnDef, } from '@/components/reui-kit' import { bgpSessionStateRu } from '@/lib/ui-labels' import { useApproveDiscoveredPeerMutation, useRejectDiscoveredPeerMutation, } from '@/queries/network' import type { PeerDiscoveryRow, SpeakerRow } from '@/types/api' /** * Pending BGP peer discoveries — approve / reject. * @see https://reui.io/preview/base/data-grid-filtering-2 * @see https://reui.io/preview/base/components/c-empty-1 */ function speakerLabel(s: SpeakerRow): string { if (s.role === 'master') { const host = s.agent_domain ?? s.endpoint return host ? `Плоскость · ${host}` : 'Плоскость управления' } return s.agent_domain ?? s.endpoint ?? `${s.id.slice(0, 8)}…` } const filterFields: FilterField[] = [ { id: 'neighbor', label: 'Сосед', icon: , type: 'text', placeholder: 'IP или ID соседа…', }, ] interface NetworkDiscoveredPeersCardProps { items: PeerDiscoveryRow[] speakers: SpeakerRow[] isLoading: boolean isError: boolean error: unknown onRetry: () => void } export function NetworkDiscoveredPeersCard({ items, speakers, isLoading, isError, error, onRetry, }: NetworkDiscoveredPeersCardProps) { const approveMutation = useApproveDiscoveredPeerMutation() const rejectMutation = useRejectDiscoveredPeerMutation() const [filterQuery, setFilterQuery] = useState(() => createTextFilterQuery('neighbor'), ) const [approveTarget, setApproveTarget] = useState(null) const [rejectTarget, setRejectTarget] = useState(null) const [name, setName] = useState('') const [speakerId, setSpeakerId] = useState(null) const speakerItems = useMemo( () => [ { value: '', label: 'Авто (с ноды обнаружения)' }, ...speakers.map((s) => ({ value: s.id, label: speakerLabel(s) })), ], [speakers], ) const columns = useMemo[]>( () => [ { id: 'neighbor_id', accessorFn: (row) => row.neighbor_id || row.neighbor, header: ({ column }) => ( ), cell: ({ row }) => ( ), meta: { headerTitle: 'ID соседа' }, }, { accessorKey: 'remote_asn', header: ({ column }) => , cell: ({ row }) => ( {row.original.remote_asn || '—'} ), meta: { headerTitle: 'ASN' }, }, { accessorKey: 'session_state', header: ({ column }) => ( ), cell: ({ row }) => ( ), meta: { headerTitle: 'Состояние' }, }, { id: 'actions', enableSorting: false, header: () => Действия, cell: ({ row }) => (
), }, ], [], ) function getFilterFieldValue(item: PeerDiscoveryRow, field: string): unknown { if (field === 'neighbor') { return `${item.neighbor_id ?? ''} ${item.neighbor}` } return undefined } async function confirmApprove() { if (!approveTarget) return await approveMutation.mutateAsync({ id: approveTarget.id, body: { name: name.trim() || undefined, bgp_speaker_id: speakerId || null, }, }) setApproveTarget(null) } return ( <> setFilterQuery(createTextFilterQuery('neighbor'))} getFilterFieldValue={getFilterFieldValue} columns={columns} data={items} getRowId={(row) => row.id} isLoading={isLoading} isError={isError} error={error instanceof Error ? error : null} onRetry={onRetry} pinLastColumn emptyState={{ title: 'Нет ожидающих пиров', description: 'Включите peer discovery в параметрах BIRD и задайте CIDR-диапазоны. Новые сессии появятся здесь.', }} /> { if (!open) setApproveTarget(null) }} title="Одобрить пира" description={ approveTarget ? `ID соседа ${approveTarget.neighbor_id || '—'} · ${approveTarget.neighbor} AS${approveTarget.remote_asn ?? '?'}` : undefined } className="sm:max-w-sm" footer={ <> void confirmApprove()} > Одобрить } >
setName(e.target.value)} placeholder="client-edge-1" />
setSpeakerId(v || null)} placeholder="Авто" />
{ if (!open) setRejectTarget(null) }} title="Отклонить пира?" description={ rejectTarget ? `${rejectTarget.neighbor_id || rejectTarget.neighbor} больше не будет появляться в списке.` : undefined } confirmLabel="Отклонить" destructive confirmLoading={rejectMutation.isPending} onConfirm={() => { if (!rejectTarget) return void rejectMutation.mutateAsync(rejectTarget.id).then(() => setRejectTarget(null)) }} /> ) }