From d23b5b7cb0870d319aa694940298e54f9b54bacc Mon Sep 17 00:00:00 2001 From: oliver Date: Fri, 5 Jun 2026 15:16:12 +0800 Subject: [PATCH] revert(managed-ne): keep bulk import NE-only Remove hop columns from the import template and stop applying hop settings during import; use Batch add proxy instead. Co-authored-by: Cursor --- netx_api/ne_service.py | 74 ++-------------------------------------- tests/test_managed_ne.py | 17 --------- web/src/i18n/en.ts | 3 +- web/src/i18n/zh.ts | 3 +- 4 files changed, 4 insertions(+), 93 deletions(-) diff --git a/netx_api/ne_service.py b/netx_api/ne_service.py index 8c8ba07..c117ba4 100644 --- a/netx_api/ne_service.py +++ b/netx_api/ne_service.py @@ -32,17 +32,6 @@ IMPORT_COLUMNS = ( "vendor", ) -OPTIONAL_IMPORT_HOP_COLUMNS = ( - "hop_enabled", - "hop_vendor", - "hop_host", - "hop_port", - "hop_username", - "hop_password", - "hop_target_auth_mode", - "hop_command_template", -) - def _now() -> datetime: return datetime.utcnow() @@ -98,44 +87,6 @@ def _import_cell_str(value: Any) -> str: return "" if text.lower() == "nan" else text -def _apply_import_hop(row: ManagedNE, data: Any) -> str | None: - """Apply optional hop columns from an import row. Returns failure reason or None.""" - if "hop_enabled" not in data.index: - return None - if not _parse_import_bool(data.get("hop_enabled")): - row.hop_enabled = False - return None - hop_host = _import_cell_str(data.get("hop_host", "")) - hop_user = _import_cell_str(data.get("hop_username", "")) - hop_pass = _import_cell_str(data.get("hop_password", "")) - if not hop_host or not hop_user or not hop_pass: - return "hop_fields_incomplete" - hop_vendor = _normalize_hop_vendor(_import_cell_str(data.get("hop_vendor", "")) or "zte") - hop_port_raw = data.get("hop_port", 22) - try: - hop_port = int(hop_port_raw) - except (TypeError, ValueError): - hop_port = 22 - template = _import_cell_str(data.get("hop_command_template", "")) - if hop_vendor == "bastion" and not template: - template = default_bastion_username_template() - elif hop_vendor not in ("linux", "bastion") and not template: - template = default_hop_command_template(hop_vendor, "ssh", "") - row.hop_enabled = True - row.hop_vendor = hop_vendor - row.hop_host = hop_host - row.hop_port = hop_port - row.hop_protocol = "ssh" - row.hop_username = hop_user - row.hop_password_enc = encrypt_secret(hop_pass) - row.hop_command_template = template - row.hop_vrf = "" - row.hop_target_auth_mode = _normalize_hop_target_auth_mode( - _import_cell_str(data.get("hop_target_auth_mode", "")) or "bastion_managed" - ) - return None - - def _apply_hop_create(row: ManagedNE, body: ManagedNeCreate) -> None: row.hop_enabled = bool(body.hop_enabled) row.hop_vendor = _normalize_hop_vendor(body.hop_vendor) @@ -433,14 +384,6 @@ def build_managed_ne_import_template(fmt: str = "xlsx") -> tuple[str, bytes, str "protocol": "ssh", "name": "Core-SW1", "vendor": "Cisco", - "hop_enabled": "", - "hop_vendor": "", - "hop_host": "", - "hop_port": "", - "hop_username": "", - "hop_password": "", - "hop_target_auth_mode": "", - "hop_command_template": "", }, { "device_type": "zte_zxros", @@ -449,20 +392,11 @@ def build_managed_ne_import_template(fmt: str = "xlsx") -> tuple[str, bytes, str "password": "", "port": 22, "protocol": "ssh", - "name": "PE-via-bastion", + "name": "PE-01", "vendor": "ZTE", - "hop_enabled": "true", - "hop_vendor": "bastion", - "hop_host": "1.1.1.1", - "hop_port": 22, - "hop_username": "bastion-user", - "hop_password": "vault_password", - "hop_target_auth_mode": "bastion_managed", - "hop_command_template": "", }, ] - all_columns = list(IMPORT_COLUMNS) + list(OPTIONAL_IMPORT_HOP_COLUMNS) - df = pd.DataFrame(rows, columns=all_columns) + df = pd.DataFrame(rows, columns=list(IMPORT_COLUMNS)) buf = BytesIO() kind = str(fmt or "xlsx").strip().lower() if kind == "csv": @@ -544,10 +478,6 @@ def import_managed_ne(db: Session, content: bytes, filename: str) -> ImportResul existing.protocol = protocol existing.username = username existing.password_enc = encrypt_secret(password) if password else "" - hop_err = _apply_import_hop(existing, row) - if hop_err: - failed.append(ImportFailure(row=row_no, reason=hop_err)) - continue existing.updated_at = now except CredentialCryptoError as exc: failed.append(ImportFailure(row=row_no, reason=str(exc))) diff --git a/tests/test_managed_ne.py b/tests/test_managed_ne.py index 31ddf65..0684f1f 100644 --- a/tests/test_managed_ne.py +++ b/tests/test_managed_ne.py @@ -190,23 +190,6 @@ class ManagedNeServiceImportTests(unittest.TestCase): self.assertEqual(row.username, "target-user") self.assertEqual(row.password_enc, "") - def test_csv_import_with_bastion_hop(self): - csv = ( - "device_type,ip,username,password,port,protocol,name,vendor," - "hop_enabled,hop_vendor,hop_host,hop_port,hop_username,hop_password,hop_target_auth_mode\n" - "zte_zxros,2.2.2.2,target-user,,22,ssh,NE-C,ZTE," - "true,bastion,1.1.1.1,22,bastion-user,vault-pass,bastion_managed\n" - ).encode("utf-8") - result = import_managed_ne(self.db, csv, "devices.csv") - self.assertEqual(result.inserted, 1) - self.assertEqual(len(result.failed), 0) - row = self.db.query(ManagedNE).filter(ManagedNE.ip_address == "2.2.2.2").one() - self.assertTrue(row.hop_enabled) - self.assertEqual(row.hop_vendor, "bastion") - self.assertEqual(row.hop_host, "1.1.1.1") - self.assertEqual(row.hop_username, "bastion-user") - self.assertEqual(decrypt_secret(row.hop_password_enc), "vault-pass") - class ManagedNeCreateOptionalPasswordTests(unittest.TestCase): def setUp(self): diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 67b7f2d..bc77758 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -173,8 +173,7 @@ const en = { "[Bulk import]\n" + "· Required columns: device_type, ip, username, port, protocol, name, vendor. Download the template first.\n" + "· password may be empty (required for direct login; optional for bastion-managed or batch proxy later).\n" + - "· Optional hop columns: hop_enabled, hop_vendor, hop_host, hop_port, hop_username, hop_password, hop_target_auth_mode, hop_command_template.\n" + - "· Or import NEs first, select rows, then use Batch add proxy.\n\n" + + "· Recommended flow: import NEs first, select rows, then use Batch add proxy.\n\n" + "[Jump / bastion]\n" + "· Bastion SSH username template: {hop_user}@{target_user}@{target_ip}@{hop_host}; target account = NE Username.\n" + "· Bastion-managed: set Jump password (Vault); target password optional. Manual mode needs target password.\n" + diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 2d6eb05..b595a08 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -171,8 +171,7 @@ const zh = { "【批量导入】\n" + "· 必填列:device_type、ip、username、port、protocol、name、vendor;可先下载模板。\n" + "· password 可留空(直连需填;堡垒机托管或后续批量添加代理时可空)。\n" + - "· 可选跳板列:hop_enabled、hop_vendor、hop_host、hop_port、hop_username、hop_password、hop_target_auth_mode、hop_command_template。\n" + - "· 也可先导入网元,勾选后点「批量添加代理」统一配置跳板。\n\n" + + "· 推荐流程:先导入网元 → 勾选网元 → 点「批量添加代理」统一配置跳板/堡垒机。\n\n" + "【跳板 / 堡垒机】\n" + "· 堡垒机 SSH 用户名模板:{hop_user}@{target_user}@{target_ip}@{hop_host};目标账号填网元「用户名」。\n" + "· 堡垒机托管时填「跳板密码」(Vault 密码),目标密码可留空;手动模式需填目标密码。\n" +