import { useState } from 'react' import { Trash2 } from 'lucide-react' import { toast } from 'sonner' import { Controller, useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' import { parseListEntry } from '@evofw/shared' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { agentOverridesQueryOptions, agentsQueryOptions, } from '@/queries' import { apiFetch } from '@/lib/api' import { Badge } from '@/components/reui/badge' import { LoadingButton } from '@/components/loading-button' import { Button } from '@evofw/ui/components/button' import { Field, FieldError, FieldLabel } from '@evofw/ui/components/field' import { Input } from '@evofw/ui/components/input' import { ScrollArea } from '@evofw/ui/components/scroll-area' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@evofw/ui/components/select' import { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, } from '@evofw/ui/components/sheet' /** * Agent settings sheets — override IP + clone sets. * Preview: https://reui.io/preview/base/sheet-8 · sheet-1 * Docs: https://ui.shadcn.com/docs/components/base/sheet */ type OverrideSheetProps = { agentId: string open: boolean onOpenChange: (open: boolean) => void } const OVERRIDE_ACTION_ITEMS = [ { value: 'deny', label: 'deny — блокировать' }, { value: 'allow', label: 'allow — пропускать' }, ] as const const overrideSchema = z.object({ cidr: z .string() .trim() .min(1, 'Укажите CIDR или IP') .refine((v) => { try { const kind = parseListEntry(v).kind return kind === 'cidr' || kind === 'ip' } catch { return false } }, 'Ожидается IP или CIDR, например 1.2.3.4/32'), action: z.enum(['deny', 'allow']), }) type OverrideValues = z.infer export function AgentOverrideSheet({ agentId, open, onOpenChange, }: OverrideSheetProps) { const qc = useQueryClient() const overridesQ = useQuery({ ...agentOverridesQueryOptions(agentId), enabled: open, }) const form = useForm({ resolver: zodResolver(overrideSchema), defaultValues: { cidr: '', action: 'deny' }, mode: 'onBlur', }) const add = useMutation({ mutationFn: (values: OverrideValues) => apiFetch(`/api/v1/agents/${agentId}/overrides`, { method: 'POST', body: JSON.stringify(values), }), onSuccess: () => { toast.success('Override добавлен — подхватится на следующей итерации sync') form.reset({ cidr: '', action: 'deny' }) void qc.invalidateQueries({ queryKey: ['agents', agentId, 'overrides'] }) void qc.invalidateQueries({ queryKey: ['agents', agentId] }) }, onError: (e: Error) => toast.error(e.message), }) const remove = useMutation({ mutationFn: (overrideId: string) => apiFetch(`/api/v1/agents/${agentId}/overrides/${overrideId}`, { method: 'DELETE', }), onSuccess: () => { toast.success('Override удалён') void qc.invalidateQueries({ queryKey: ['agents', agentId, 'overrides'] }) void qc.invalidateQueries({ queryKey: ['agents', agentId] }) }, onError: (e: Error) => toast.error(e.message), }) const items = overridesQ.data?.items ?? [] return ( IP override Мгновенные allow/deny поверх политики. Обновятся на агенте на следующей итерации sync (~1 мин).
add.mutateAsync(values))} className="grid gap-3" > CIDR / IP Действие ( )} /> Добавить

Активные{' '} {items.length}

{items.length === 0 ? (

Пока нет overrides

) : (
    {items.map((o) => (
  • {o.cidr} {o.action}
  • ))}
)}
) } type CloneSheetProps = { agentId: string open: boolean onOpenChange: (open: boolean) => void } export function AgentCloneSetsSheet({ agentId, open, onOpenChange, }: CloneSheetProps) { const qc = useQueryClient() const agentsQ = useQuery({ ...agentsQueryOptions(), enabled: open, }) const [cloneFrom, setCloneFrom] = useState(null) const clone = useMutation({ mutationFn: () => apiFetch(`/api/v1/agents/${agentId}/clone-from/${cloneFrom}`, { method: 'POST', body: JSON.stringify({ include_overrides: true }), }), onSuccess: () => { toast.success('Наборы скопированы') setCloneFrom(null) onOpenChange(false) void qc.invalidateQueries({ queryKey: ['agents', agentId] }) void qc.invalidateQueries({ queryKey: ['policy-sets'] }) }, onError: (e: Error) => toast.error(e.message), }) const sources = (agentsQ.data?.items ?? []).filter((x) => x.id !== agentId) return ( Копировать наборы Копирует назначения наборов и overrides с другого агента.
Источник
) }