feat(UME): 完善网元元数据入库并增强告警/前端展示

补齐 ume_inventory_ne 的关键元数据字段并在同步时结构化落库,支持从 alarmkey 推导 ne_id、告警查询连表补齐 user_label/ne_name,同时优化 UME 页面分页显示与网元展开详情,提升运维排障可读性。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-05-08 17:02:57 +08:00
parent 3396111f2d
commit 3ea4f08bea
7 changed files with 276 additions and 43 deletions

View file

@ -195,6 +195,31 @@ def on_startup() -> None:
conn.exec_driver_sql("ALTER TABLE alarms_norm ADD COLUMN IF NOT EXISTS me_level VARCHAR(128) DEFAULT ''")
conn.exec_driver_sql("ALTER TABLE ume_token_cache ADD COLUMN IF NOT EXISTS lock_owner VARCHAR(128) DEFAULT ''")
conn.exec_driver_sql("ALTER TABLE ume_token_cache ADD COLUMN IF NOT EXISTS lock_expires_at_epoch_s INTEGER DEFAULT 0")
conn.exec_driver_sql("ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS device_level VARCHAR(64) DEFAULT ''")
conn.exec_driver_sql("ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS host_name VARCHAR(256) DEFAULT ''")
conn.exec_driver_sql("ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS location VARCHAR(512) DEFAULT ''")
conn.exec_driver_sql(
"ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS hardware_version VARCHAR(128) DEFAULT ''"
)
conn.exec_driver_sql("ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS loopback VARCHAR(128) DEFAULT ''")
conn.exec_driver_sql(
"ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS consistent_state VARCHAR(64) DEFAULT ''"
)
conn.exec_driver_sql(
"ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS interface_version VARCHAR(128) DEFAULT ''"
)
conn.exec_driver_sql("ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS mac VARCHAR(128) DEFAULT ''")
conn.exec_driver_sql("ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS admin_status VARCHAR(64) DEFAULT ''")
conn.exec_driver_sql("ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS address_type VARCHAR(64) DEFAULT ''")
conn.exec_driver_sql(
"ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS connection_status VARCHAR(64) DEFAULT ''"
)
conn.exec_driver_sql(
"ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS maintain_status VARCHAR(64) DEFAULT ''"
)
conn.exec_driver_sql("ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS net_mask VARCHAR(128) DEFAULT ''")
conn.exec_driver_sql("ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS create_time VARCHAR(64) DEFAULT ''")
conn.exec_driver_sql("ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS creator VARCHAR(128) DEFAULT ''")
except Exception:
pass
try:
@ -379,6 +404,21 @@ def ume_list_ne(
"user_label": str(x.user_label or ""),
"ip_address": str(x.ip_address or ""),
"ne_type": str(x.ne_type or ""),
"device_level": str(x.device_level or ""),
"host_name": str(x.host_name or ""),
"location": str(x.location or ""),
"hardware_version": str(x.hardware_version or ""),
"loopback": str(x.loopback or ""),
"consistent_state": str(x.consistent_state or ""),
"interface_version": str(x.interface_version or ""),
"mac": str(x.mac or ""),
"admin_status": str(x.admin_status or ""),
"address_type": str(x.address_type or ""),
"connection_status": str(x.connection_status or ""),
"maintain_status": str(x.maintain_status or ""),
"net_mask": str(x.net_mask or ""),
"create_time": str(x.create_time or ""),
"creator": str(x.creator or ""),
"last_seen_at": (_ensure_utc(x.last_seen_at) or datetime.now(timezone.utc)).isoformat(),
}
for x in rows
@ -397,6 +437,21 @@ def ume_get_ne(ne_id: str, db: Session = Depends(get_db)) -> dict[str, Any]:
"user_label": str(row.user_label or ""),
"ip_address": str(row.ip_address or ""),
"ne_type": str(row.ne_type or ""),
"device_level": str(row.device_level or ""),
"host_name": str(row.host_name or ""),
"location": str(row.location or ""),
"hardware_version": str(row.hardware_version or ""),
"loopback": str(row.loopback or ""),
"consistent_state": str(row.consistent_state or ""),
"interface_version": str(row.interface_version or ""),
"mac": str(row.mac or ""),
"admin_status": str(row.admin_status or ""),
"address_type": str(row.address_type or ""),
"connection_status": str(row.connection_status or ""),
"maintain_status": str(row.maintain_status or ""),
"net_mask": str(row.net_mask or ""),
"create_time": str(row.create_time or ""),
"creator": str(row.creator or ""),
"vendor": str(row.vendor or ""),
"source_type": str(row.source_type or ""),
"first_seen_at": (_ensure_utc(row.first_seen_at) or datetime.now(timezone.utc)).isoformat(),
@ -415,7 +470,9 @@ def ume_list_alarms(
page_size: int = Query(default=50, ge=1, le=500),
db: Session = Depends(get_db),
) -> dict[str, Any]:
stmt = db.query(UmeAlarmCurrent)
stmt = db.query(UmeAlarmCurrent, UmeInventoryNE).outerjoin(
UmeInventoryNE, UmeAlarmCurrent.ne_id == UmeInventoryNE.ne_id
)
if severity and str(severity).strip():
stmt = stmt.filter(UmeAlarmCurrent.perceived_severity == str(severity).strip())
if is_cleared and str(is_cleared).strip():
@ -430,24 +487,27 @@ def ume_list_alarms(
| UmeAlarmCurrent.ne_name.contains(kw)
| UmeAlarmCurrent.user_label.contains(kw)
| UmeAlarmCurrent.native_probable_cause.contains(kw)
| UmeInventoryNE.ne_name.contains(kw)
| UmeInventoryNE.user_label.contains(kw)
| UmeInventoryNE.ip_address.contains(kw)
)
total = int(stmt.count())
rows = stmt.order_by(UmeAlarmCurrent.last_seen_at.desc()).offset((page - 1) * page_size).limit(page_size).all()
items = [
{
"alarm_key": str(x.alarm_key or ""),
"ne_id": str(x.ne_id or ""),
"ne_name": str(x.ne_name or ""),
"user_label": str(x.user_label or ""),
"object_name": str(x.object_name or ""),
"event_type": str(x.event_type or ""),
"native_probable_cause": str(x.native_probable_cause or ""),
"perceived_severity": str(x.perceived_severity or ""),
"is_cleared": str(x.is_cleared or ""),
"time_created": str(x.time_created or ""),
"last_seen_at": (_ensure_utc(x.last_seen_at) or datetime.now(timezone.utc)).isoformat(),
"alarm_key": str(alarm.alarm_key or ""),
"ne_id": str(alarm.ne_id or ""),
"ne_name": str(alarm.ne_name or (ne.ne_name if ne else "") or ""),
"user_label": str(alarm.user_label or (ne.user_label if ne else "") or ""),
"object_name": str(alarm.object_name or ""),
"event_type": str(alarm.event_type or ""),
"native_probable_cause": str(alarm.native_probable_cause or ""),
"perceived_severity": str(alarm.perceived_severity or ""),
"is_cleared": str(alarm.is_cleared or ""),
"time_created": str(alarm.time_created or ""),
"last_seen_at": (_ensure_utc(alarm.last_seen_at) or datetime.now(timezone.utc)).isoformat(),
}
for x in rows
for alarm, ne in rows
]
return {"total": total, "page": page, "page_size": page_size, "items": items}
@ -471,7 +531,9 @@ def ume_list_alarms_history(
page_size: int = Query(default=50, ge=1, le=500),
db: Session = Depends(get_db),
) -> dict[str, Any]:
stmt = db.query(UmeAlarmHistory)
stmt = db.query(UmeAlarmHistory, UmeInventoryNE).outerjoin(
UmeInventoryNE, UmeAlarmHistory.ne_id == UmeInventoryNE.ne_id
)
if severity and str(severity).strip():
stmt = stmt.filter(UmeAlarmHistory.perceived_severity == str(severity).strip())
if ne_id and str(ne_id).strip():
@ -484,6 +546,9 @@ def ume_list_alarms_history(
| UmeAlarmHistory.ne_name.contains(kw)
| UmeAlarmHistory.user_label.contains(kw)
| UmeAlarmHistory.native_probable_cause.contains(kw)
| UmeInventoryNE.ne_name.contains(kw)
| UmeInventoryNE.user_label.contains(kw)
| UmeInventoryNE.ip_address.contains(kw)
)
dt_from = _parse_time(time_from)
dt_to = _parse_time(time_to)
@ -495,19 +560,19 @@ def ume_list_alarms_history(
rows = stmt.order_by(UmeAlarmHistory.last_seen_at.desc()).offset((page - 1) * page_size).limit(page_size).all()
items = [
{
"alarm_key": str(x.alarm_key or ""),
"ne_id": str(x.ne_id or ""),
"ne_name": str(x.ne_name or ""),
"user_label": str(x.user_label or ""),
"object_name": str(x.object_name or ""),
"event_type": str(x.event_type or ""),
"native_probable_cause": str(x.native_probable_cause or ""),
"perceived_severity": str(x.perceived_severity or ""),
"is_cleared": str(x.is_cleared or ""),
"time_created": str(x.time_created or ""),
"last_seen_at": (_ensure_utc(x.last_seen_at) or datetime.now(timezone.utc)).isoformat(),
"alarm_key": str(alarm.alarm_key or ""),
"ne_id": str(alarm.ne_id or ""),
"ne_name": str(alarm.ne_name or (ne.ne_name if ne else "") or ""),
"user_label": str(alarm.user_label or (ne.user_label if ne else "") or ""),
"object_name": str(alarm.object_name or ""),
"event_type": str(alarm.event_type or ""),
"native_probable_cause": str(alarm.native_probable_cause or ""),
"perceived_severity": str(alarm.perceived_severity or ""),
"is_cleared": str(alarm.is_cleared or ""),
"time_created": str(alarm.time_created or ""),
"last_seen_at": (_ensure_utc(alarm.last_seen_at) or datetime.now(timezone.utc)).isoformat(),
}
for x in rows
for alarm, ne in rows
]
return {"total": total, "page": page, "page_size": page_size, "items": items}

View file

@ -432,6 +432,32 @@ def _call_tool(name: str, args: dict[str, Any]) -> dict[str, Any]:
def main() -> None:
try:
Base.metadata.create_all(bind=engine)
with engine.begin() as conn:
conn.exec_driver_sql("ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS device_level VARCHAR(64) DEFAULT ''")
conn.exec_driver_sql("ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS host_name VARCHAR(256) DEFAULT ''")
conn.exec_driver_sql("ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS location VARCHAR(512) DEFAULT ''")
conn.exec_driver_sql(
"ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS hardware_version VARCHAR(128) DEFAULT ''"
)
conn.exec_driver_sql("ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS loopback VARCHAR(128) DEFAULT ''")
conn.exec_driver_sql(
"ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS consistent_state VARCHAR(64) DEFAULT ''"
)
conn.exec_driver_sql(
"ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS interface_version VARCHAR(128) DEFAULT ''"
)
conn.exec_driver_sql("ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS mac VARCHAR(128) DEFAULT ''")
conn.exec_driver_sql("ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS admin_status VARCHAR(64) DEFAULT ''")
conn.exec_driver_sql("ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS address_type VARCHAR(64) DEFAULT ''")
conn.exec_driver_sql(
"ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS connection_status VARCHAR(64) DEFAULT ''"
)
conn.exec_driver_sql(
"ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS maintain_status VARCHAR(64) DEFAULT ''"
)
conn.exec_driver_sql("ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS net_mask VARCHAR(128) DEFAULT ''")
conn.exec_driver_sql("ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS create_time VARCHAR(64) DEFAULT ''")
conn.exec_driver_sql("ALTER TABLE ume_inventory_ne ADD COLUMN IF NOT EXISTS creator VARCHAR(128) DEFAULT ''")
except Exception:
pass
for line in sys.stdin:

View file

@ -140,6 +140,21 @@ class UmeInventoryNE(Base):
user_label: Mapped[str] = mapped_column(String(256), default="", index=True)
ip_address: Mapped[str] = mapped_column(String(128), default="", index=True)
ne_type: Mapped[str] = mapped_column(String(128), default="")
device_level: Mapped[str] = mapped_column(String(64), default="")
host_name: Mapped[str] = mapped_column(String(256), default="")
location: Mapped[str] = mapped_column(String(512), default="")
hardware_version: Mapped[str] = mapped_column(String(128), default="")
loopback: Mapped[str] = mapped_column(String(128), default="")
consistent_state: Mapped[str] = mapped_column(String(64), default="")
interface_version: Mapped[str] = mapped_column(String(128), default="")
mac: Mapped[str] = mapped_column(String(128), default="")
admin_status: Mapped[str] = mapped_column(String(64), default="")
address_type: Mapped[str] = mapped_column(String(64), default="")
connection_status: Mapped[str] = mapped_column(String(64), default="")
maintain_status: Mapped[str] = mapped_column(String(64), default="")
net_mask: Mapped[str] = mapped_column(String(128), default="")
create_time: Mapped[str] = mapped_column(String(64), default="")
creator: Mapped[str] = mapped_column(String(128), default="")
vendor: Mapped[str] = mapped_column(String(64), default="ZTE")
source_type: Mapped[str] = mapped_column(String(64), default="ume_restconf")
first_seen_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)

View file

@ -39,7 +39,7 @@ def _pick(d: dict[str, Any], *keys: str) -> Any:
def _alarm_key(alarm: dict[str, Any]) -> str:
key = _s(_pick(alarm, "alarmKey", "alarm-key", "id"))
key = _s(_pick(alarm, "alarmKey", "alarm-key","alarmkey","id"))
if key:
return key
parts = [
@ -52,6 +52,25 @@ def _alarm_key(alarm: dict[str, Any]) -> str:
return merged or f"fallback-{datetime.utcnow().timestamp()}"
def _derive_ne_id_from_alarm(alarm: dict[str, Any]) -> str:
ne_id = _s(_pick(alarm, "ne-id", "neId", "ne_id"))
if ne_id:
return ne_id
alarm_key = _s(_pick(alarm, "alarmKey", "alarm-key", "alarmkey"))
if not alarm_key:
return ""
# Common UME formats observed:
# 1) "<net_id>#<suffix>"
# 2) "<net_id>, <x>, <y>"
if "#" in alarm_key:
return _s(alarm_key.split("#", 1)[0])
if "," in alarm_key:
return _s(alarm_key.split(",", 1)[0])
return ""
def _build_sync_job(domain: str, trigger_mode: str) -> UmeSyncJob:
return UmeSyncJob(
domain=domain,
@ -88,6 +107,22 @@ def sync_inventory_full(db: Session, client: UMEClient, *, trigger_mode: str = "
existing.user_label = _s(_pick(row, "user-label", "user_label"))
existing.ip_address = _s(_pick(row, "ip-Address", "ip-address", "ip"))
existing.ne_type = _s(_pick(row, "type", "ne-type"))
existing.device_level = _s(_pick(row, "device-level"))
existing.host_name = _s(_pick(row, "host-name"))
existing.location = _s(_pick(row, "location"))
existing.hardware_version = _s(_pick(row, "hardware-version"))
existing.loopback = _s(_pick(row, "loopback"))
existing.consistent_state = _s(_pick(row, "consistent-state"))
existing.interface_version = _s(_pick(row, "interface-version"))
existing.mac = _s(_pick(row, "mac"))
existing.admin_status = _s(_pick(row, "admin-status"))
existing.address_type = _s(_pick(row, "address-type"))
existing.connection_status = _s(_pick(row, "connection-status"))
existing.maintain_status = _s(_pick(row, "maintain-status"))
existing.net_mask = _s(_pick(row, "net-mask"))
existing.create_time = _s(_pick(row, "create-time"))
existing.creator = _s(_pick(row, "creator"))
existing.vendor = _s(_pick(row, "vendor-name")) or "ZTE"
existing.last_seen_at = now
existing.raw_json = json.dumps(row, ensure_ascii=False, default=str)
@ -192,7 +227,7 @@ def _sync_alarms_common(
inserted += 1
else:
updated += 1
existing.ne_id = _s(_pick(alarm, "ne-id", "neId", "ne_id"))
existing.ne_id = _derive_ne_id_from_alarm(alarm)
existing.ne_name = _s(_pick(alarm, "ne-name", "neName", "ne_name"))
existing.user_label = _s(_pick(alarm, "user-label", "userLabel", "user_label"))
existing.object_name = _s(_pick(alarm, "objectName", "object-name"))