feat(ne): connect detail, Huawei/Cisco hop, Cisco hostname probe

Persist full connect test logs (connect_detail) with NE UI detail modal.
Add Huawei/Cisco jump CLI templates and generic CLI hop session path.
Probe Cisco hostname via show configuration | include hostname (60s timeout).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-05-28 21:50:41 +08:00
parent 778442dc57
commit dc17f9d15a
15 changed files with 457 additions and 63 deletions

View file

@ -1,26 +1,43 @@
import { isAutoHopTemplate, zteHopTemplate } from "./zteHop";
/** Jump-host (hop) templates per vendor. */
export type HopVendor = "zte" | "linux";
import { ciscoHopTemplate, huaweiHopTemplate, isAutoHopTemplate, zteHopTemplate } from "./zteHop";
export const HOP_VENDORS: HopVendor[] = ["zte", "linux"];
export type HopVendor = "zte" | "huawei" | "cisco" | "linux";
export const HOP_VENDORS: HopVendor[] = ["zte", "huawei", "cisco", "linux"];
export function isLinuxHopVendor(vendor: string): boolean {
return String(vendor || "").toLowerCase() === "linux";
}
export function isCliHopVendor(vendor: string): boolean {
const v = String(vendor || "").toLowerCase();
return v === "zte" || v === "huawei" || v === "cisco";
}
export function defaultHopTemplate(vendor: string, protocol: string, vrf: string): string {
const v = String(vendor || "zte").toLowerCase();
if (v === "huawei") return huaweiHopTemplate(protocol, vrf);
if (v === "cisco") return ciscoHopTemplate(protocol, vrf);
if (v === "linux") return "";
return zteHopTemplate(protocol, vrf);
}
export function patchHopVendorChange(
vendor: HopVendor,
prev: { hop_protocol: string; hop_vrf: string; hop_command_template: string },
prev: { hop_protocol: string; hop_vrf: string; hop_command_template: string; hop_vendor?: string },
): { hop_vendor: HopVendor; hop_protocol: string; hop_vrf: string; hop_command_template: string } {
if (vendor === "linux") {
return { hop_vendor: "linux", hop_protocol: "ssh", hop_vrf: "", hop_command_template: "" };
}
const protocol = prev.hop_protocol || "ssh";
const vrf = prev.hop_vrf || "";
return {
hop_vendor: "zte",
hop_protocol: prev.hop_protocol || "ssh",
hop_vrf: prev.hop_vrf,
hop_command_template: zteHopTemplate(prev.hop_protocol || "ssh", prev.hop_vrf),
hop_vendor: vendor,
hop_protocol: protocol,
hop_vrf: vrf,
hop_command_template: defaultHopTemplate(vendor, protocol, vrf),
};
}
export { isAutoHopTemplate, zteHopTemplate };
export { ciscoHopTemplate, huaweiHopTemplate, isAutoHopTemplate, zteHopTemplate };

View file

@ -1,4 +1,4 @@
/** ZTE device CLI jump commands: ssh/telnet <ip> [vrf <name>]. */
/** Vendor CLI jump command templates (placeholders). */
const LEGACY_HOP_TEMPLATES = new Set([
"ssh {target_user}@{target_ip}",
@ -12,8 +12,35 @@ export function zteHopTemplate(protocol: string, vrf: string): string {
return v ? `${cmd} {target_ip} vrf {vrf}` : `${cmd} {target_ip}`;
}
export function isAutoHopTemplate(template: string, protocol: string, vrf: string): boolean {
/** Cisco: ssh -vrf VRF IP; telnet IP [/vrf VRF]. */
export function ciscoHopTemplate(protocol: string, vrf: string): string {
const v = String(vrf || "").trim();
if (String(protocol || "ssh").toLowerCase() === "telnet") {
return v ? `telnet {target_ip} /vrf {vrf}` : `telnet {target_ip}`;
}
return v ? `ssh -vrf {vrf} {target_ip}` : `ssh {target_ip}`;
}
/** Huawei: telnet [vpn-instance VRF] IP; SSH uses stelnet. */
export function huaweiHopTemplate(protocol: string, vrf: string): string {
const v = String(vrf || "").trim();
if (String(protocol || "ssh").toLowerCase() === "telnet") {
return v ? `telnet vpn-instance {vrf} {target_ip}` : `telnet {target_ip}`;
}
return v ? `stelnet {target_ip} -vpn-instance {vrf}` : `stelnet {target_ip}`;
}
export function isAutoHopTemplate(
template: string,
vendor: string,
protocol: string,
vrf: string,
): boolean {
const t = String(template || "").trim();
if (!t || LEGACY_HOP_TEMPLATES.has(t)) return true;
const v = String(vendor || "zte").toLowerCase();
if (v === "huawei") return t === huaweiHopTemplate(protocol, vrf);
if (v === "cisco") return t === ciscoHopTemplate(protocol, vrf);
if (v === "linux") return t === "";
return t === zteHopTemplate(protocol, vrf);
}