From 48cc381afd4c29f4e0d4e31e34979f91eba99c60 Mon Sep 17 00:00:00 2001 From: oliver Date: Tue, 4 Aug 2026 14:27:46 +0800 Subject: [PATCH] Allow promoting LLDP/WebCRT placeholder NEs to real device types. Accept generic/linux on update, expose them in the device-type meta list, and auto-pick zte_zxros when vendor changes from a placeholder type. Co-authored-by: Cursor --- netx_api/managed_ne_router.py | 5 ++++- netx_api/ne_service_crud.py | 11 ++++++++--- web/src/pages/NePage.tsx | 24 ++++++++++++++++++++++-- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/netx_api/managed_ne_router.py b/netx_api/managed_ne_router.py index dd8b899..406fbe9 100644 --- a/netx_api/managed_ne_router.py +++ b/netx_api/managed_ne_router.py @@ -59,7 +59,10 @@ def api_list_managed_ne( @router.get("/meta/device-types") def api_device_types(): - return {"device_types": list(SUPPORTED_DEVICE_TYPES), "vendors": list(SUPPORTED_VENDORS)} + # Include generic/linux so LLDP/WebCRT placeholders can be edited without a bogus select value. + from .device_types import WEBCRT_DEVICE_TYPES + + return {"device_types": list(WEBCRT_DEVICE_TYPES), "vendors": list(SUPPORTED_VENDORS)} @router.get("/meta/credentials-configured") diff --git a/netx_api/ne_service_crud.py b/netx_api/ne_service_crud.py index 6af493f..d90e95c 100644 --- a/netx_api/ne_service_crud.py +++ b/netx_api/ne_service_crud.py @@ -8,7 +8,7 @@ from fastapi import HTTPException from sqlalchemy import or_ from sqlalchemy.orm import Session -from .device_types import LLDP_DISCOVERED_NE_SOURCE, SUPPORTED_DEVICE_TYPES, SUPPORTED_VENDORS +from .device_types import LLDP_DISCOVERED_NE_SOURCE, SUPPORTED_DEVICE_TYPES, SUPPORTED_VENDORS, WEBCRT_DEVICE_TYPES from .models import ManagedNE from .ne_crypto import encrypt_secret from .ne_hop_templates import default_bastion_username_template, default_hop_command_template @@ -33,6 +33,10 @@ from .ne_service_common import ( row_to_out, ) +# Inventory create stays strict; updates must also accept WebCRT/LLDP placeholder types +# (generic/linux) so operators can open the form and promote them to zte_zxros etc. +_ALLOWED_UPDATE_DEVICE_TYPES = frozenset(WEBCRT_DEVICE_TYPES) + def list_managed_ne( db: Session, *, @@ -135,9 +139,10 @@ def update_managed_ne(db: Session, ne_id: str, body: ManagedNeUpdate) -> Managed raise HTTPException(status_code=400, detail="ip_address_exists") row.ip_address = ip if "device_type" in data: - if data["device_type"] not in SUPPORTED_DEVICE_TYPES: + dt = str(data["device_type"] or "").strip() + if dt not in _ALLOWED_UPDATE_DEVICE_TYPES: raise HTTPException(status_code=400, detail="unsupported_device_type") - row.device_type = data["device_type"] + row.device_type = dt if "vendor" in data: v = str(data["vendor"] or "").strip() row.vendor = v if v in SUPPORTED_VENDORS else "Other" diff --git a/web/src/pages/NePage.tsx b/web/src/pages/NePage.tsx index 1f75f7b..91f4867 100644 --- a/web/src/pages/NePage.tsx +++ b/web/src/pages/NePage.tsx @@ -410,7 +410,12 @@ export function NePage() { }); const vendors = metaQuery.data?.vendors ?? []; - const deviceTypes = metaQuery.data?.device_types ?? []; + const deviceTypes = useMemo(() => { + const base = metaQuery.data?.device_types ?? []; + const cur = String(form.device_type || "").trim(); + if (cur && !base.includes(cur)) return [cur, ...base]; + return base; + }, [metaQuery.data?.device_types, form.device_type]); const credsOk = metaQuery.data?.credentials_configured ?? false; const allSelected = useMemo(() => { @@ -921,7 +926,22 @@ export function NePage() {