feat(web): add delete functionality to agent components
Build and Push EvoFirewall Docker Image / build-and-push (push) Successful in 1m59s
Build and Push EvoFirewall Docker Image / create-release (push) Skipped

- Introduced delete functionality across multiple agent components, including AgentCard, AgentCardsGrid, AgentDetailSheet, and AgentDetailView, allowing users to remove agents directly from the UI.
- Integrated a confirmation dialog to prevent accidental deletions, enhancing user experience and safety.
- Updated relevant props and handlers to manage delete actions consistently across components.

These changes improve the overall management of agents, providing users with the ability to easily delete agents while ensuring confirmation for critical actions.
This commit is contained in:
Denozordec
2026-07-25 16:59:05 +07:00
parent 837b29735f
commit fb95ef22b3
6 changed files with 186 additions and 86 deletions
@@ -47,6 +47,7 @@ import {
computeFleetCounts,
fleetKpiCards,
} from '@/components/agents/agents-fleet-kpis'
import { ConfirmDialog } from '@/components/confirm-dialog'
import { agentsQueryOptions } from '@/queries'
import { apiFetch } from '@/lib/api'
import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard'
@@ -105,6 +106,7 @@ function AgentsPage() {
const agentsQ = useQuery(agentsQueryOptions())
const { copyToClipboard } = useCopyToClipboard()
const [createOpen, setCreateOpen] = useState(false)
const [deleteAgentId, setDeleteAgentId] = useState<string | null>(null)
const [filters, setFilters] = useState<Filter[]>([])
const [searchQuery, setSearchQuery] = useState('')
const [activeTab, setActiveTab] = useState('all')
@@ -155,6 +157,21 @@ function AgentsPage() {
onError: (e: Error) => toast.error(e.message),
})
const removeAgent = useMutation({
mutationFn: (id: string) =>
apiFetch(`/api/v1/agents/${id}`, { method: 'DELETE' }),
onSuccess: (_data, id) => {
toast.success('Агент удалён')
setDeleteAgentId(null)
if (detailAgentId === id) {
setSearch({ agent: '' })
}
void qc.invalidateQueries({ queryKey: ['agents'] })
void qc.invalidateQueries({ queryKey: ['dashboard'] })
},
onError: (e: Error) => toast.error(e.message),
})
const items = agentsQ.data?.items ?? []
const counts = useMemo(() => computeFleetCounts(items), [items])
const pendingIds = useMemo(
@@ -330,6 +347,15 @@ function AgentsPage() {
[setSearch],
)
const handleDeleteAgent = useCallback((id: string) => {
setDeleteAgentId(id)
}, [])
const deleteTargetName = useMemo(
() => items.find((a) => a.id === deleteAgentId)?.name,
[items, deleteAgentId],
)
const handleClearFilters = useCallback(() => {
setFilters([])
setSearchQuery('')
@@ -454,6 +480,7 @@ function AgentsPage() {
onApprove={(id) => approve.mutate(id)}
approvePending={approve.isPending}
onCopyInstall={handleCopyInstall}
onDelete={handleDeleteAgent}
/>
) : agentsQ.isError ? (
<Alert variant="destructive">
@@ -533,6 +560,7 @@ function AgentsPage() {
agents={filteredItems}
selectedId={detailOpen ? detailAgentId : null}
onSelect={handleSelectAgent}
onDelete={handleDeleteAgent}
isLoading={agentsQ.isLoading}
emptyTitle={
items.length === 0
@@ -559,6 +587,24 @@ function AgentsPage() {
onOpenChange={(open) => {
if (!open) setSearch({ agent: '' })
}}
onDelete={handleDeleteAgent}
/>
<ConfirmDialog
open={deleteAgentId !== null}
onOpenChange={(open) => {
if (!open) setDeleteAgentId(null)
}}
title="Удалить агента?"
description={
deleteTargetName
? `Агент «${deleteTargetName}» будет удалён вместе с overrides, install-ссылками и статистикой. Это действие нельзя отменить.`
: 'Агент будет удалён вместе с overrides, install-ссылками и статистикой. Это действие нельзя отменить.'
}
onConfirm={() => {
if (deleteAgentId) removeAgent.mutate(deleteAgentId)
}}
disabled={removeAgent.isPending}
/>
</PageShell>
)