diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 5ad60fd..35c5ac0 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -56,6 +56,7 @@ const en = { filterKeyword: "Filter", filterKeywordPh: "Name / IP / vendor / device type", add: "Add", + addBatch: "Add selected ({{count}})", added: "Added", create: "Create job", creating: "Creating…", diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 94bc2f7..ec08d50 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -56,6 +56,7 @@ const zh = { filterKeyword: "筛选", filterKeywordPh: "名称 / IP / 厂商 / 设备类型", add: "添加", + addBatch: "批量添加({{count}})", added: "已添加", create: "创建任务", creating: "创建中…", diff --git a/web/src/pages/CollectPage.tsx b/web/src/pages/CollectPage.tsx index ea18732..b616f92 100644 --- a/web/src/pages/CollectPage.tsx +++ b/web/src/pages/CollectPage.tsx @@ -31,6 +31,7 @@ export function CollectPage() { const [commands, setCommands] = useState(""); const [title, setTitle] = useState(""); const [selectedMap, setSelectedMap] = useState>({}); + const [pickSelectedIds, setPickSelectedIds] = useState([]); const [neKeyword, setNeKeyword] = useState(""); const [nePage, setNePage] = useState(1); const [jobPage, setJobPage] = useState(1); @@ -150,6 +151,17 @@ export function CollectPage() { setSelectedMap((prev) => (prev[row.id] ? prev : { ...prev, [row.id]: row })); }; + const addBatchNe = (rows: EligibleNeItem[]) => { + if (rows.length === 0) return; + setSelectedMap((prev) => { + const next = { ...prev }; + for (const row of rows) { + if (!next[row.id]) next[row.id] = row; + } + return next; + }); + }; + const removeNe = (id: string) => { setSelectedMap((prev) => { const next = { ...prev }; @@ -161,6 +173,9 @@ export function CollectPage() { const clearSelected = () => setSelectedMap({}); const eligibleItems = eligibleQuery.data?.items ?? []; + const selectablePickItems = eligibleItems.filter((row) => !selectedMap[row.id]); + const allPickSelected = selectablePickItems.length > 0 && selectablePickItems.every((row) => pickSelectedIds.includes(row.id)); + const batchPickCount = pickSelectedIds.length; const neTotal = eligibleQuery.data?.total ?? 0; const nePages = pageCount(neTotal, ELIGIBLE_PAGE_SIZE); @@ -246,9 +261,23 @@ export function CollectPage() {

{t("collect.create.pickTitle")}

{t("collect.create.pickHint")}

- +
+ + +
@@ -271,6 +301,20 @@ export function CollectPage() { + @@ -283,6 +327,19 @@ export function CollectPage() { const picked = Boolean(selectedMap[row.id]); return ( + @@ -294,7 +351,10 @@ export function CollectPage() { type="button" className="link-btn" disabled={picked} - onClick={() => addNe(row)} + onClick={() => { + addNe(row); + setPickSelectedIds((prev) => prev.filter((id) => id !== row.id)); + }} > {picked ? t("collect.create.added") : t("collect.create.add")} @@ -311,10 +371,24 @@ export function CollectPage() { {t("common.pagerMeta", { total: neTotal, page: nePage, pages: nePages })}
- -
+ { + if (e.target.checked) { + setPickSelectedIds(selectablePickItems.map((row) => row.id)); + return; + } + setPickSelectedIds([]); + }} + aria-label="pick all" + /> + {t("managedNe.col.name")} {t("managedNe.col.vendor")} {t("managedNe.col.ip")}
+ { + if (picked) return; + setPickSelectedIds((prev) => + e.target.checked ? [...new Set([...prev, row.id])] : prev.filter((id) => id !== row.id), + ); + }} + /> + {row.name || row.ip_address} {row.vendor} {row.ip_address}