feat(firewall): rename revoke function to delete and update related UI components

Refactored the revoke functionality for firewall clients to be more accurately represented as a delete operation. Updated the corresponding API call to use the DELETE method and modified the UI components to reflect this change, including confirmation dialogs and success messages. Adjusted tests to ensure the new delete functionality works as intended.
This commit is contained in:
Denozordec
2026-07-09 00:27:50 +07:00
parent e51999c908
commit 4a4c11c6bf
3 changed files with 40 additions and 34 deletions
+4 -4
View File
@@ -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<void>(`/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 : 'Не удалось удалить'),
})
}
+20 -15
View File
@@ -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() {
<Tabs defaultValue="clients">
<TabsList>
<TabsTrigger value="clients">Клиенты ({clients.length})</TabsTrigger>
<TabsTrigger value="clients">Клиенты ({activeClients.length})</TabsTrigger>
<TabsTrigger value="rules">Правила ({rules.length})</TabsTrigger>
<TabsTrigger value="requests">Запросы ({pending.length})</TabsTrigger>
</TabsList>
<TabsContent value="clients" className="mt-4">
<ClientsTable
clients={clients}
clients={activeClients}
onApprove={(id) => approve.mutate(id)}
onReject={(id) => revoke.mutate(id)}
onReject={(id) => deleteClient.mutate(id)}
approvePending={approve.isPending}
rejectPending={revoke.isPending}
rejectPending={deleteClient.isPending}
/>
</TabsContent>
@@ -263,9 +268,9 @@ function FirewallPage() {
<ClientsTable
clients={pending}
onApprove={(id) => approve.mutate(id)}
onReject={(id) => revoke.mutate(id)}
onReject={(id) => deleteClient.mutate(id)}
approvePending={approve.isPending}
rejectPending={revoke.isPending}
rejectPending={deleteClient.isPending}
emptyTitle="Нет pending-запросов"
/>
</TabsContent>
@@ -342,7 +347,7 @@ function ClientsTable({
</Button>
}
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}
>
Отозвать
Удалить
</Button>
}
title="Отозвать клиент?"
description={`${c.name} — blocklist перестанет отдаваться, токен будет недействителен.`}
confirmLabel="Отозвать"
title="Удалить клиент?"
description={`${c.name} запись будет удалена, blocklist и токен перестанут работать.`}
confirmLabel="Удалить"
destructive
onConfirm={() => onReject(c.id)}
/>
+16 -15
View File
@@ -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)
}
}