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:
@@ -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 : 'Не удалось удалить'),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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)}
|
||||
/>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user