mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 04:20:45 +08:00
Add explicit Huawei hop system-view option and startup column safety-net.
Some hops only accept stelnet in system-view; make it a saved yes/no setting (default off) instead of auto-retry, and always ALTER the new column on API boot when Alembic skips legacy DDL. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
0a550abcb0
commit
b36174bfa4
20 changed files with 242 additions and 10 deletions
|
|
@ -22,6 +22,7 @@ export type HopProxyFieldsState = {
|
|||
hop_command_template: string;
|
||||
hop_vrf: string;
|
||||
hop_target_auth_mode: HopTargetAuthMode;
|
||||
hop_enter_system_view: boolean;
|
||||
};
|
||||
|
||||
export const emptyHopProxyFields = (): HopProxyFieldsState => ({
|
||||
|
|
@ -34,6 +35,7 @@ export const emptyHopProxyFields = (): HopProxyFieldsState => ({
|
|||
hop_command_template: defaultHopTemplate("zte", "ssh", ""),
|
||||
hop_vrf: "",
|
||||
hop_target_auth_mode: "bastion_managed",
|
||||
hop_enter_system_view: false,
|
||||
});
|
||||
|
||||
function FormLabel({ children, required }: { children: ReactNode; required?: boolean }) {
|
||||
|
|
@ -224,6 +226,19 @@ export function HopProxyFields({
|
|||
}}
|
||||
/>
|
||||
</label>
|
||||
{huawei ? (
|
||||
<label className="form-grid__full">
|
||||
<FormLabel>{t("managedNe.hop.enterSystemView")}</FormLabel>
|
||||
<select
|
||||
value={value.hop_enter_system_view ? "yes" : "no"}
|
||||
onChange={(e) => set({ hop_enter_system_view: e.target.value === "yes" })}
|
||||
>
|
||||
<option value="no">{t("managedNe.hop.enterSystemViewNo")}</option>
|
||||
<option value="yes">{t("managedNe.hop.enterSystemViewYes")}</option>
|
||||
</select>
|
||||
<span className="form-field-hint">{t("managedNe.hop.enterSystemViewHint")}</span>
|
||||
</label>
|
||||
) : null}
|
||||
<label className="form-grid__full">
|
||||
<FormLabel>{t("managedNe.hop.commandTemplate")}</FormLabel>
|
||||
<input
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ function profileToForm(row: CliConnectProfileItem): ProfileForm {
|
|||
hop_command_template: row.hop_command_template ?? "",
|
||||
hop_vrf: row.hop_vrf ?? "",
|
||||
hop_target_auth_mode: (row.hop_target_auth_mode || "bastion_managed") as HopProxyFieldsState["hop_target_auth_mode"],
|
||||
hop_enter_system_view: Boolean(row.hop_enter_system_view),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -153,6 +154,7 @@ export function UmeCliConnectPanel({ enabled = true, embedded = false }: { enabl
|
|||
hop_command_template: form.hop.hop_command_template,
|
||||
hop_vrf: form.hop.hop_vrf,
|
||||
hop_target_auth_mode: form.hop.hop_target_auth_mode,
|
||||
hop_enter_system_view: form.hop.hop_enter_system_view,
|
||||
};
|
||||
if (form.id) {
|
||||
return apiPatch<CliConnectProfileItem>(`/v1/cli/profiles/${form.id}`, body);
|
||||
|
|
|
|||
|
|
@ -931,6 +931,11 @@ const en = {
|
|||
vrf: "Mgmt VRF (optional)",
|
||||
vpnInstance: "VPN-Instance (optional)",
|
||||
vrfCisco: "VRF (optional, e.g. MGMT)",
|
||||
enterSystemView: "Huawei system-view before jump",
|
||||
enterSystemViewNo: "Stay in user-view (default)",
|
||||
enterSystemViewYes: "Enter system-view first",
|
||||
enterSystemViewHint:
|
||||
"Some Huawei hops only accept stelnet/telnet in system-view. Choose explicitly; NetX will not auto-retry.",
|
||||
commandTemplate: "Jump command template",
|
||||
templateHint:
|
||||
"ZTE CLI: telnet {target_ip}, telnet {target_ip} vrf {vrf}, ssh {target_ip}, ssh {target_ip} vrf {vrf}. Auto-suggested from jump protocol/VRF; same when left blank. Target credentials via secondary auth prompts.",
|
||||
|
|
|
|||
|
|
@ -923,6 +923,11 @@ const zh = {
|
|||
vrf: "管理 VRF(可选)",
|
||||
vpnInstance: "VPN-Instance(可选)",
|
||||
vrfCisco: "VRF(可选,如 MGMT)",
|
||||
enterSystemView: "跳登前是否进入系统视图",
|
||||
enterSystemViewNo: "不进入(用户视图,默认)",
|
||||
enterSystemViewYes: "进入系统视图(system-view)",
|
||||
enterSystemViewHint:
|
||||
"部分华为跳板仅在系统视图下支持 stelnet/telnet。需自行选择;不会因失败自动重试。",
|
||||
commandTemplate: "跳登命令模板",
|
||||
templateHint:
|
||||
"ZTE 常用:telnet {target_ip}、telnet {target_ip} vrf {vrf}、ssh {target_ip}、ssh {target_ip} vrf {vrf}。按跳板协议与 VRF 自动推荐;留空时后端同样规则。目标账号密码由二次认证提示输入。",
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ type FormState = {
|
|||
hop_command_template: string;
|
||||
hop_vrf: string;
|
||||
hop_target_auth_mode: "bastion_managed" | "manual";
|
||||
hop_enter_system_view: boolean;
|
||||
};
|
||||
|
||||
type AccountState = {
|
||||
|
|
@ -98,6 +99,7 @@ const emptyForm = (): FormState => ({
|
|||
hop_command_template: defaultHopTemplate("zte", "ssh", ""),
|
||||
hop_vrf: "",
|
||||
hop_target_auth_mode: "bastion_managed",
|
||||
hop_enter_system_view: false,
|
||||
});
|
||||
|
||||
const emptyAccount = (): AccountState => ({
|
||||
|
|
@ -250,6 +252,7 @@ export function NePage() {
|
|||
hop_command_template: form.hop_command_template,
|
||||
hop_vrf: form.hop_vrf,
|
||||
hop_target_auth_mode: form.hop_target_auth_mode,
|
||||
hop_enter_system_view: form.hop_enter_system_view,
|
||||
...(form.password ? { password: form.password } : {}),
|
||||
...(form.hop_password ? { hop_password: form.hop_password } : {}),
|
||||
};
|
||||
|
|
@ -315,6 +318,7 @@ export function NePage() {
|
|||
hop_command_template: batchHop.hop_command_template.trim(),
|
||||
hop_vrf: batchHop.hop_vrf.trim(),
|
||||
hop_target_auth_mode: batchHop.hop_target_auth_mode,
|
||||
hop_enter_system_view: batchHop.hop_enter_system_view,
|
||||
}),
|
||||
onSuccess: async (res) => {
|
||||
setBatchHopOpen(false);
|
||||
|
|
@ -415,6 +419,7 @@ export function NePage() {
|
|||
hop_command_template: bulkHop.hop_command_template.trim(),
|
||||
hop_vrf: bulkHop.hop_vrf.trim(),
|
||||
hop_target_auth_mode: bulkHop.hop_target_auth_mode,
|
||||
hop_enter_system_view: bulkHop.hop_enter_system_view,
|
||||
});
|
||||
return { type: "proxy" as const, n: res.updated };
|
||||
},
|
||||
|
|
@ -485,6 +490,7 @@ export function NePage() {
|
|||
hop_vrf: row.hop_vrf,
|
||||
hop_target_auth_mode:
|
||||
row.hop_target_auth_mode === "manual" ? "manual" : "bastion_managed",
|
||||
hop_enter_system_view: Boolean(row.hop_enter_system_view),
|
||||
});
|
||||
setModalOpen(true);
|
||||
};
|
||||
|
|
@ -1129,6 +1135,7 @@ export function NePage() {
|
|||
hop_command_template: form.hop_command_template,
|
||||
hop_vrf: form.hop_vrf,
|
||||
hop_target_auth_mode: form.hop_target_auth_mode,
|
||||
hop_enter_system_view: form.hop_enter_system_view,
|
||||
}}
|
||||
onChange={(patch) => setForm((prev) => ({ ...prev, ...patch }))}
|
||||
hopPasswordRequired={!editing}
|
||||
|
|
|
|||
|
|
@ -555,6 +555,7 @@ export const batchApplyHopManagedNe = (
|
|||
hop_vrf: string;
|
||||
hop_vendor?: string;
|
||||
hop_target_auth_mode?: string;
|
||||
hop_enter_system_view?: boolean;
|
||||
},
|
||||
) => apiPost<{ ok: boolean; updated: number }>("/v1/managed-ne/batch-hop", { ids, hop });
|
||||
|
||||
|
|
|
|||
|
|
@ -214,6 +214,7 @@ export type ManagedNeItem = {
|
|||
hop_command_template: string;
|
||||
hop_vrf: string;
|
||||
hop_target_auth_mode: string;
|
||||
hop_enter_system_view?: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
|
@ -255,6 +256,7 @@ export type CliConnectProfileItem = {
|
|||
hop_command_template: string;
|
||||
hop_vrf: string;
|
||||
hop_target_auth_mode: string;
|
||||
hop_enter_system_view?: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -128,9 +128,16 @@ export function patchHopVendorChange(
|
|||
hop_command_template: string;
|
||||
hop_port?: number;
|
||||
hop_target_auth_mode?: HopTargetAuthMode;
|
||||
hop_enter_system_view?: boolean;
|
||||
} {
|
||||
if (vendor === "linux") {
|
||||
return { hop_vendor: "linux", hop_protocol: "ssh", hop_vrf: "", hop_command_template: "" };
|
||||
return {
|
||||
hop_vendor: "linux",
|
||||
hop_protocol: "ssh",
|
||||
hop_vrf: "",
|
||||
hop_command_template: "",
|
||||
hop_enter_system_view: false,
|
||||
};
|
||||
}
|
||||
if (vendor === "bastion") {
|
||||
return {
|
||||
|
|
@ -140,6 +147,7 @@ export function patchHopVendorChange(
|
|||
hop_vrf: "",
|
||||
hop_command_template: bastionHopTemplate(),
|
||||
hop_target_auth_mode: "bastion_managed" as HopTargetAuthMode,
|
||||
hop_enter_system_view: false,
|
||||
};
|
||||
}
|
||||
const protocol = prev.hop_protocol || "ssh";
|
||||
|
|
@ -149,6 +157,8 @@ export function patchHopVendorChange(
|
|||
hop_protocol: protocol,
|
||||
hop_vrf: vrf,
|
||||
hop_command_template: defaultHopTemplate(vendor, protocol, vrf),
|
||||
// Explicit Huawei option only; reset when leaving/entering other CLI hop vendors.
|
||||
hop_enter_system_view: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue