feat(ne): batch delete selected managed network elements

Add POST /v1/managed-ne/batch-delete and toolbar button with confirmation on NE management page.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-05-28 19:02:16 +08:00
parent d3eae3351c
commit 778442dc57
7 changed files with 75 additions and 0 deletions

View file

@ -136,6 +136,11 @@ const en = {
edit: "Edit",
delete: "Delete",
confirmDelete: "Delete this network element?",
batchDelete: "Batch delete",
batchDeleting: "Deleting…",
batchDeleteConfirm: "Delete {{n}} selected network element(s)? This cannot be undone.",
batchDeleteDone: "Deleted {{n}} network element(s)",
batchDeleteSelectRequired: "Select network elements to delete",
keywordPh: "name / IP / user / tags",
allVendors: "All vendors",
allConnectStatus: "All connect status",

View file

@ -135,6 +135,11 @@ const zh = {
edit: "编辑",
delete: "删除",
confirmDelete: "确定删除该网元?",
batchDelete: "批量删除",
batchDeleting: "删除中…",
batchDeleteConfirm: "确定删除选中的 {{n}} 台网元?此操作不可恢复。",
batchDeleteDone: "已删除 {{n}} 台网元",
batchDeleteSelectRequired: "请先勾选要删除的网元",
keywordPh: "名称 / IP / 用户名 / 标签",
allVendors: "全部厂商",
allConnectStatus: "全部连通状态",

View file

@ -793,6 +793,21 @@ pre {
gap: 8px;
}
.btn--danger {
color: #b71c1c;
border-color: #ef9a9a;
background: #ffebee;
}
.btn--danger:hover:not(:disabled) {
background: #ffcdd2;
}
.btn--danger:disabled {
opacity: 0.55;
cursor: not-allowed;
}
.table-actions {
display: flex;
gap: 8px;

View file

@ -2,6 +2,7 @@ import { useMemo, useRef, useState, type ReactNode } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
batchApplyHopManagedNe,
batchDeleteManagedNe,
connectTestManagedNe,
createManagedNe,
deleteManagedNe,
@ -203,6 +204,16 @@ export function NePage() {
onError: (err) => showError(String(err)),
});
const batchDeleteMutation = useMutation({
mutationFn: batchDeleteManagedNe,
onSuccess: async (res) => {
setSelected([]);
showOk(t("managedNe.batchDeleteDone", { n: res.deleted }));
await invalidateList();
},
onError: (err) => showError(String(err)),
});
const batchHopMutation = useMutation({
mutationFn: () =>
batchApplyHopManagedNe(selected, {
@ -361,6 +372,21 @@ export function NePage() {
>
{batchHopMutation.isPending ? t("managedNe.hop.applying") : t("managedNe.hop.batchAdd")}
</button>
<button
type="button"
className="btn btn--danger"
disabled={selected.length === 0 || batchDeleteMutation.isPending}
onClick={() => {
if (selected.length === 0) {
showError(t("managedNe.batchDeleteSelectRequired"));
return;
}
if (!window.confirm(t("managedNe.batchDeleteConfirm", { n: selected.length }))) return;
batchDeleteMutation.mutate(selected);
}}
>
{batchDeleteMutation.isPending ? t("managedNe.batchDeleting") : t("managedNe.batchDelete")}
</button>
<button type="button" onClick={() => invalidateList()}>
{t("common.refresh")}
</button>

View file

@ -126,6 +126,9 @@ export const updateManagedNe = (id: string, body: Record<string, unknown>) =>
export const deleteManagedNe = (id: string) => apiDelete<{ ok: boolean }>(`/v1/managed-ne/${id}`);
export const batchDeleteManagedNe = (ids: string[]) =>
apiPost<{ ok: boolean; deleted: number }>("/v1/managed-ne/batch-delete", { ids });
export const connectTestManagedNe = (ids: string[]) =>
apiPost<{ ok: boolean; submitted: number }>("/v1/managed-ne/connect-test", { ids });