(emptyHopProxyFields);
const metaQuery = useQuery({
queryKey: queryKeys.managedNeMeta,
@@ -196,6 +200,27 @@ export function NePage() {
onError: (err) => showError(String(err)),
});
+ const batchHopMutation = useMutation({
+ mutationFn: () =>
+ batchApplyHopManagedNe(selected, {
+ hop_vendor: "zte",
+ hop_host: batchHop.hop_host.trim(),
+ hop_port: batchHop.hop_port,
+ hop_protocol: batchHop.hop_protocol,
+ hop_username: batchHop.hop_username.trim(),
+ hop_password: batchHop.hop_password,
+ hop_command_template: batchHop.hop_command_template.trim(),
+ hop_vrf: batchHop.hop_vrf.trim(),
+ }),
+ onSuccess: async (res) => {
+ setBatchHopOpen(false);
+ setBatchHop(emptyHopProxyFields());
+ showOk(t("managedNe.hop.batchDone", { n: res.updated }));
+ await invalidateList();
+ },
+ onError: (err) => showError(String(err)),
+ });
+
const importMutation = useMutation({
mutationFn: importManagedNe,
onSuccess: async (res) => {
@@ -318,6 +343,20 @@ export function NePage() {
>
{connectMutation.isPending ? t("managedNe.connect.running") : t("managedNe.connect.run")}
+
@@ -565,86 +604,20 @@ export function NePage() {
{t("managedNe.hop.enable")}
{form.hop_enabled ? (
-
-
-
-
-
-
-
-
-
+ setForm((prev) => ({ ...prev, ...patch }))}
+ hopPasswordRequired={!editing}
+ hopPasswordOptional={Boolean(editing)}
+ />
) : null}
@@ -658,6 +631,42 @@ export function NePage() {
) : null}
+
+ {batchHopOpen ? (
+ setBatchHopOpen(false)}>
+
e.stopPropagation()}>
+
{t("managedNe.hop.batchTitle")}
+
{t("managedNe.hop.batchHint", { n: selected.length })}
+
setBatchHop((prev) => ({ ...prev, ...patch }))} />
+
+
+
+
+
+
+ ) : null}
);
}
diff --git a/web/src/services/api.ts b/web/src/services/api.ts
index f4b3575..3f278c2 100644
--- a/web/src/services/api.ts
+++ b/web/src/services/api.ts
@@ -129,6 +129,20 @@ export const deleteManagedNe = (id: string) => apiDelete<{ ok: boolean }>(`/v1/m
export const connectTestManagedNe = (ids: string[]) =>
apiPost<{ ok: boolean; submitted: number }>("/v1/managed-ne/connect-test", { ids });
+export const batchApplyHopManagedNe = (
+ ids: string[],
+ hop: {
+ hop_host: string;
+ hop_port: number;
+ hop_protocol: string;
+ hop_username: string;
+ hop_password: string;
+ hop_command_template: string;
+ hop_vrf: string;
+ hop_vendor?: string;
+ },
+) => apiPost<{ ok: boolean; updated: number }>("/v1/managed-ne/batch-hop", { ids, hop });
+
export const managedNeImportTemplateUrl = (format: "xlsx" | "csv" = "xlsx") =>
`/v1/managed-ne/import/template?format=${format}`;