diff --git a/apps/web/src/queries/firewall.ts b/apps/web/src/queries/firewall.ts index 14d697e..52e42f6 100644 --- a/apps/web/src/queries/firewall.ts +++ b/apps/web/src/queries/firewall.ts @@ -60,16 +60,16 @@ export function useApproveFirewallClient() { }) } -export function useRevokeFirewallClient() { +export function useDeleteFirewallClient() { const qc = useQueryClient() return useMutation({ mutationFn: (id: string) => - apiJSON<{ status: string }>(`/v1/firewall/clients/${id}/revoke`, { method: 'POST' }), + apiJSON(`/v1/firewall/clients/${id}`, { method: 'DELETE' }), onSuccess: () => { - toast.success('Клиент отключён') + toast.success('Клиент удалён') void qc.invalidateQueries({ queryKey: firewallKeys.clients() }) }, - onError: (e) => toast.error(e instanceof Error ? e.message : 'Не удалось отклонить'), + onError: (e) => toast.error(e instanceof Error ? e.message : 'Не удалось удалить'), }) } diff --git a/apps/web/src/routes/_auth/firewall.tsx b/apps/web/src/routes/_auth/firewall.tsx index 0fee8ff..4c255ba 100644 --- a/apps/web/src/routes/_auth/firewall.tsx +++ b/apps/web/src/routes/_auth/firewall.tsx @@ -31,8 +31,8 @@ import { firewallRulesQueryOptions, useApproveFirewallClient, useCreateFirewallRule, + useDeleteFirewallClient, useDeleteFirewallRule, - useRevokeFirewallClient, } from '@/queries/firewall' import type { BgpCommunity, FirewallClient } from '@/types/api' @@ -56,7 +56,7 @@ function FirewallPage() { const clientsQ = useQuery(firewallClientsQueryOptions()) const rulesQ = useQuery(firewallRulesQueryOptions('tenant')) const approve = useApproveFirewallClient() - const revoke = useRevokeFirewallClient() + const deleteClient = useDeleteFirewallClient() const createRule = useCreateFirewallRule() const deleteRule = useDeleteFirewallRule() @@ -82,8 +82,13 @@ function FirewallPage() { const communities = communitiesQ.data?.items ?? [] - const clients = clientsQ.data?.items ?? [] - const pending = clients.filter((c) => c.status === 'pending') + const { activeClients, pending } = useMemo(() => { + const all = clientsQ.data?.items ?? [] + return { + activeClients: all.filter((c) => c.status !== 'revoked'), + pending: all.filter((c) => c.status === 'pending'), + } + }, [clientsQ.data?.items]) const rules = rulesQ.data?.items ?? [] const installCmd = useMemo(() => { @@ -190,18 +195,18 @@ function FirewallPage() { - Клиенты ({clients.length}) + Клиенты ({activeClients.length}) Правила ({rules.length}) Запросы ({pending.length}) approve.mutate(id)} - onReject={(id) => revoke.mutate(id)} + onReject={(id) => deleteClient.mutate(id)} approvePending={approve.isPending} - rejectPending={revoke.isPending} + rejectPending={deleteClient.isPending} /> @@ -263,9 +268,9 @@ function FirewallPage() { approve.mutate(id)} - onReject={(id) => revoke.mutate(id)} + onReject={(id) => deleteClient.mutate(id)} approvePending={approve.isPending} - rejectPending={revoke.isPending} + rejectPending={deleteClient.isPending} emptyTitle="Нет pending-запросов" /> @@ -342,7 +347,7 @@ function ClientsTable({ } title="Отклонить запрос?" - description={`${c.name}${c.hostname ? ` (${c.hostname})` : ''} — токен перестанет работать.`} + description={`${c.name}${c.hostname ? ` (${c.hostname})` : ''} — запись будет удалена, токен перестанет работать.`} confirmLabel="Отклонить" destructive onConfirm={() => onReject(c.id)} @@ -358,12 +363,12 @@ function ClientsTable({ className="text-destructive" disabled={rejectPending} > - Отозвать + Удалить } - title="Отозвать клиент?" - description={`${c.name} — blocklist перестанет отдаваться, токен будет недействителен.`} - confirmLabel="Отозвать" + title="Удалить клиент?" + description={`${c.name} — запись будет удалена, blocklist и токен перестанут работать.`} + confirmLabel="Удалить" destructive onConfirm={() => onReject(c.id)} /> diff --git a/internal/httpapi/routes_firewall_test.go b/internal/httpapi/routes_firewall_test.go index b68d6b2..f2ec0f3 100644 --- a/internal/httpapi/routes_firewall_test.go +++ b/internal/httpapi/routes_firewall_test.go @@ -2,6 +2,7 @@ package httpapi import ( "encoding/json" + "errors" "io" "net/http" "net/http/httptest" @@ -205,7 +206,7 @@ func TestFirewallInstallContext(t *testing.T) { } } -func TestFirewallRevokePendingClient(t *testing.T) { +func TestFirewallDeletePendingClient(t *testing.T) { srv, err := New(Options{SeedDemo: true, BundleSeedHex: testBundleSeed}) if err != nil { t.Fatal(err) @@ -241,24 +242,24 @@ func TestFirewallRevokePendingClient(t *testing.T) { t.Fatal("missing client_id") } - reqRevoke, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/firewall/clients/"+clientID+"/revoke", nil) - reqRevoke.Header.Set("Authorization", "Bearer opkey") - respRevoke, err := client.Do(reqRevoke) + reqDelete, _ := http.NewRequest(http.MethodDelete, ts.URL+"/v1/firewall/clients/"+clientID, nil) + reqDelete.Header.Set("Authorization", "Bearer opkey") + respDelete, err := client.Do(reqDelete) if err != nil { t.Fatal(err) } - defer func() { _ = respRevoke.Body.Close() }() - if respRevoke.StatusCode != http.StatusOK { - b, _ := io.ReadAll(respRevoke.Body) - t.Fatalf("revoke status=%d body=%s", respRevoke.StatusCode, b) + defer func() { _ = respDelete.Body.Close() }() + if respDelete.StatusCode != http.StatusNoContent { + b, _ := io.ReadAll(respDelete.Body) + t.Fatalf("delete status=%d body=%s", respDelete.StatusCode, b) } - got, err := srv.Store().GetFirewallClient(tenant, clientID) - if err != nil { - t.Fatal(err) + _, err = srv.Store().GetFirewallClient(tenant, clientID) + if err == nil { + t.Fatal("client should be deleted") } - if got.Status != "revoked" { - t.Fatalf("status=%q want revoked", got.Status) + if !errors.Is(err, store.ErrNotFound) { + t.Fatalf("delete err=%v", err) } reqBlock, _ := http.NewRequest(http.MethodGet, ts.URL+"/v1/firewall/blocklist", nil) @@ -268,8 +269,8 @@ func TestFirewallRevokePendingClient(t *testing.T) { t.Fatal(err) } defer func() { _ = respBlock.Body.Close() }() - if respBlock.StatusCode != http.StatusForbidden { - t.Fatalf("revoked blocklist want 403 got %d", respBlock.StatusCode) + if respBlock.StatusCode != http.StatusUnauthorized { + t.Fatalf("deleted blocklist want 401 got %d", respBlock.StatusCode) } }