mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 02:00:46 +08:00
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:
parent
3396111f2d
commit
3ea4f08bea
7 changed files with 276 additions and 43 deletions
117
netx_api/main.py
117
netx_api/main.py
|
|
@ -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 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_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_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:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
|
|
@ -379,6 +404,21 @@ def ume_list_ne(
|
||||||
"user_label": str(x.user_label or ""),
|
"user_label": str(x.user_label or ""),
|
||||||
"ip_address": str(x.ip_address or ""),
|
"ip_address": str(x.ip_address or ""),
|
||||||
"ne_type": str(x.ne_type 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(),
|
"last_seen_at": (_ensure_utc(x.last_seen_at) or datetime.now(timezone.utc)).isoformat(),
|
||||||
}
|
}
|
||||||
for x in rows
|
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 ""),
|
"user_label": str(row.user_label or ""),
|
||||||
"ip_address": str(row.ip_address or ""),
|
"ip_address": str(row.ip_address or ""),
|
||||||
"ne_type": str(row.ne_type 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 ""),
|
"vendor": str(row.vendor or ""),
|
||||||
"source_type": str(row.source_type or ""),
|
"source_type": str(row.source_type or ""),
|
||||||
"first_seen_at": (_ensure_utc(row.first_seen_at) or datetime.now(timezone.utc)).isoformat(),
|
"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),
|
page_size: int = Query(default=50, ge=1, le=500),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
) -> dict[str, Any]:
|
) -> 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():
|
if severity and str(severity).strip():
|
||||||
stmt = stmt.filter(UmeAlarmCurrent.perceived_severity == str(severity).strip())
|
stmt = stmt.filter(UmeAlarmCurrent.perceived_severity == str(severity).strip())
|
||||||
if is_cleared and str(is_cleared).strip():
|
if is_cleared and str(is_cleared).strip():
|
||||||
|
|
@ -430,24 +487,27 @@ def ume_list_alarms(
|
||||||
| UmeAlarmCurrent.ne_name.contains(kw)
|
| UmeAlarmCurrent.ne_name.contains(kw)
|
||||||
| UmeAlarmCurrent.user_label.contains(kw)
|
| UmeAlarmCurrent.user_label.contains(kw)
|
||||||
| UmeAlarmCurrent.native_probable_cause.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())
|
total = int(stmt.count())
|
||||||
rows = stmt.order_by(UmeAlarmCurrent.last_seen_at.desc()).offset((page - 1) * page_size).limit(page_size).all()
|
rows = stmt.order_by(UmeAlarmCurrent.last_seen_at.desc()).offset((page - 1) * page_size).limit(page_size).all()
|
||||||
items = [
|
items = [
|
||||||
{
|
{
|
||||||
"alarm_key": str(x.alarm_key or ""),
|
"alarm_key": str(alarm.alarm_key or ""),
|
||||||
"ne_id": str(x.ne_id or ""),
|
"ne_id": str(alarm.ne_id or ""),
|
||||||
"ne_name": str(x.ne_name or ""),
|
"ne_name": str(alarm.ne_name or (ne.ne_name if ne else "") or ""),
|
||||||
"user_label": str(x.user_label or ""),
|
"user_label": str(alarm.user_label or (ne.user_label if ne else "") or ""),
|
||||||
"object_name": str(x.object_name or ""),
|
"object_name": str(alarm.object_name or ""),
|
||||||
"event_type": str(x.event_type or ""),
|
"event_type": str(alarm.event_type or ""),
|
||||||
"native_probable_cause": str(x.native_probable_cause or ""),
|
"native_probable_cause": str(alarm.native_probable_cause or ""),
|
||||||
"perceived_severity": str(x.perceived_severity or ""),
|
"perceived_severity": str(alarm.perceived_severity or ""),
|
||||||
"is_cleared": str(x.is_cleared or ""),
|
"is_cleared": str(alarm.is_cleared or ""),
|
||||||
"time_created": str(x.time_created or ""),
|
"time_created": str(alarm.time_created or ""),
|
||||||
"last_seen_at": (_ensure_utc(x.last_seen_at) or datetime.now(timezone.utc)).isoformat(),
|
"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}
|
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),
|
page_size: int = Query(default=50, ge=1, le=500),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
) -> dict[str, Any]:
|
) -> 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():
|
if severity and str(severity).strip():
|
||||||
stmt = stmt.filter(UmeAlarmHistory.perceived_severity == str(severity).strip())
|
stmt = stmt.filter(UmeAlarmHistory.perceived_severity == str(severity).strip())
|
||||||
if ne_id and str(ne_id).strip():
|
if ne_id and str(ne_id).strip():
|
||||||
|
|
@ -484,6 +546,9 @@ def ume_list_alarms_history(
|
||||||
| UmeAlarmHistory.ne_name.contains(kw)
|
| UmeAlarmHistory.ne_name.contains(kw)
|
||||||
| UmeAlarmHistory.user_label.contains(kw)
|
| UmeAlarmHistory.user_label.contains(kw)
|
||||||
| UmeAlarmHistory.native_probable_cause.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_from = _parse_time(time_from)
|
||||||
dt_to = _parse_time(time_to)
|
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()
|
rows = stmt.order_by(UmeAlarmHistory.last_seen_at.desc()).offset((page - 1) * page_size).limit(page_size).all()
|
||||||
items = [
|
items = [
|
||||||
{
|
{
|
||||||
"alarm_key": str(x.alarm_key or ""),
|
"alarm_key": str(alarm.alarm_key or ""),
|
||||||
"ne_id": str(x.ne_id or ""),
|
"ne_id": str(alarm.ne_id or ""),
|
||||||
"ne_name": str(x.ne_name or ""),
|
"ne_name": str(alarm.ne_name or (ne.ne_name if ne else "") or ""),
|
||||||
"user_label": str(x.user_label or ""),
|
"user_label": str(alarm.user_label or (ne.user_label if ne else "") or ""),
|
||||||
"object_name": str(x.object_name or ""),
|
"object_name": str(alarm.object_name or ""),
|
||||||
"event_type": str(x.event_type or ""),
|
"event_type": str(alarm.event_type or ""),
|
||||||
"native_probable_cause": str(x.native_probable_cause or ""),
|
"native_probable_cause": str(alarm.native_probable_cause or ""),
|
||||||
"perceived_severity": str(x.perceived_severity or ""),
|
"perceived_severity": str(alarm.perceived_severity or ""),
|
||||||
"is_cleared": str(x.is_cleared or ""),
|
"is_cleared": str(alarm.is_cleared or ""),
|
||||||
"time_created": str(x.time_created or ""),
|
"time_created": str(alarm.time_created or ""),
|
||||||
"last_seen_at": (_ensure_utc(x.last_seen_at) or datetime.now(timezone.utc)).isoformat(),
|
"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}
|
return {"total": total, "page": page, "page_size": page_size, "items": items}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -432,6 +432,32 @@ def _call_tool(name: str, args: dict[str, Any]) -> dict[str, Any]:
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
try:
|
try:
|
||||||
Base.metadata.create_all(bind=engine)
|
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:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
for line in sys.stdin:
|
for line in sys.stdin:
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,21 @@ class UmeInventoryNE(Base):
|
||||||
user_label: Mapped[str] = mapped_column(String(256), default="", index=True)
|
user_label: Mapped[str] = mapped_column(String(256), default="", index=True)
|
||||||
ip_address: Mapped[str] = mapped_column(String(128), default="", index=True)
|
ip_address: Mapped[str] = mapped_column(String(128), default="", index=True)
|
||||||
ne_type: Mapped[str] = mapped_column(String(128), default="")
|
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")
|
vendor: Mapped[str] = mapped_column(String(64), default="ZTE")
|
||||||
source_type: Mapped[str] = mapped_column(String(64), default="ume_restconf")
|
source_type: Mapped[str] = mapped_column(String(64), default="ume_restconf")
|
||||||
first_seen_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
first_seen_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ def _pick(d: dict[str, Any], *keys: str) -> Any:
|
||||||
|
|
||||||
|
|
||||||
def _alarm_key(alarm: dict[str, Any]) -> str:
|
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:
|
if key:
|
||||||
return key
|
return key
|
||||||
parts = [
|
parts = [
|
||||||
|
|
@ -52,6 +52,25 @@ def _alarm_key(alarm: dict[str, Any]) -> str:
|
||||||
return merged or f"fallback-{datetime.utcnow().timestamp()}"
|
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:
|
def _build_sync_job(domain: str, trigger_mode: str) -> UmeSyncJob:
|
||||||
return UmeSyncJob(
|
return UmeSyncJob(
|
||||||
domain=domain,
|
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.user_label = _s(_pick(row, "user-label", "user_label"))
|
||||||
existing.ip_address = _s(_pick(row, "ip-Address", "ip-address", "ip"))
|
existing.ip_address = _s(_pick(row, "ip-Address", "ip-address", "ip"))
|
||||||
existing.ne_type = _s(_pick(row, "type", "ne-type"))
|
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.last_seen_at = now
|
||||||
existing.raw_json = json.dumps(row, ensure_ascii=False, default=str)
|
existing.raw_json = json.dumps(row, ensure_ascii=False, default=str)
|
||||||
|
|
||||||
|
|
@ -192,7 +227,7 @@ def _sync_alarms_common(
|
||||||
inserted += 1
|
inserted += 1
|
||||||
else:
|
else:
|
||||||
updated += 1
|
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.ne_name = _s(_pick(alarm, "ne-name", "neName", "ne_name"))
|
||||||
existing.user_label = _s(_pick(alarm, "user-label", "userLabel", "user_label"))
|
existing.user_label = _s(_pick(alarm, "user-label", "userLabel", "user_label"))
|
||||||
existing.object_name = _s(_pick(alarm, "objectName", "object-name"))
|
existing.object_name = _s(_pick(alarm, "objectName", "object-name"))
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ from sqlalchemy.orm import sessionmaker
|
||||||
from netx_api.db import Base
|
from netx_api.db import Base
|
||||||
from netx_api.models import UmeAlarmCurrent, UmeInventoryNE
|
from netx_api.models import UmeAlarmCurrent, UmeInventoryNE
|
||||||
from netx_api.ume_client import UMEClient
|
from netx_api.ume_client import UMEClient
|
||||||
from netx_api.ume_sync_service import sync_alarms_current, sync_inventory_full
|
from netx_api.ume_sync_service import _derive_ne_id_from_alarm, sync_alarms_current, sync_inventory_full
|
||||||
|
|
||||||
|
|
||||||
class _FakeResponse:
|
class _FakeResponse:
|
||||||
|
|
@ -139,7 +139,17 @@ class UmeSyncServiceTests(unittest.TestCase):
|
||||||
class _C:
|
class _C:
|
||||||
def get_network_elements(self):
|
def get_network_elements(self):
|
||||||
rows = [
|
rows = [
|
||||||
{"ne-id": "NE-1", "name": "ne1", "user-label": "网元1", "ip-Address": "10.0.0.1", "type": "A"},
|
{
|
||||||
|
"ne-id": "NE-1",
|
||||||
|
"name": "ne1",
|
||||||
|
"user-label": "网元1",
|
||||||
|
"ip-Address": "10.0.0.1",
|
||||||
|
"type": "A",
|
||||||
|
"device-level": "Access",
|
||||||
|
"host-name": "host-1",
|
||||||
|
"hardware-version": "V1",
|
||||||
|
"vendor-name": "ZTE",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
return rows, None
|
return rows, None
|
||||||
|
|
||||||
|
|
@ -154,6 +164,9 @@ class UmeSyncServiceTests(unittest.TestCase):
|
||||||
ne = self.db.get(UmeInventoryNE, "NE-1")
|
ne = self.db.get(UmeInventoryNE, "NE-1")
|
||||||
self.assertIsNotNone(ne)
|
self.assertIsNotNone(ne)
|
||||||
self.assertEqual(ne.user_label, "网元1")
|
self.assertEqual(ne.user_label, "网元1")
|
||||||
|
self.assertEqual(ne.device_level, "Access")
|
||||||
|
self.assertEqual(ne.host_name, "host-1")
|
||||||
|
self.assertEqual(ne.hardware_version, "V1")
|
||||||
|
|
||||||
def test_sync_current_alarms_upsert(self):
|
def test_sync_current_alarms_upsert(self):
|
||||||
class _C:
|
class _C:
|
||||||
|
|
@ -250,6 +263,19 @@ class UmeSyncServiceTests(unittest.TestCase):
|
||||||
self.assertEqual(job.pulled_count, 1)
|
self.assertEqual(job.pulled_count, 1)
|
||||||
self.assertIsNotNone(self.db.get(UmeAlarmCurrent, "AK-1"))
|
self.assertIsNotNone(self.db.get(UmeAlarmCurrent, "AK-1"))
|
||||||
|
|
||||||
|
def test_derive_ne_id_from_alarmkey_formats(self):
|
||||||
|
alarm_hash = {"alarmkey": "00ceb960-1b62-478e-8303-0935ffea1d28#99010"}
|
||||||
|
alarm_csv = {"alarmkey": "00ceb960-1b62-478e-8303-0935ffea1d28, 4237, 79"}
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
_derive_ne_id_from_alarm(alarm_hash),
|
||||||
|
"00ceb960-1b62-478e-8303-0935ffea1d28",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
_derive_ne_id_from_alarm(alarm_csv),
|
||||||
|
"00ceb960-1b62-478e-8303-0935ffea1d28",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState } from "react";
|
import { Fragment, useState } from "react";
|
||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import {
|
import {
|
||||||
apiPost,
|
apiPost,
|
||||||
|
|
@ -23,6 +23,7 @@ export function UmePage() {
|
||||||
const [neKeyword, setNeKeyword] = useState("");
|
const [neKeyword, setNeKeyword] = useState("");
|
||||||
const [nePage, setNePage] = useState(1);
|
const [nePage, setNePage] = useState(1);
|
||||||
const [nePageSize, setNePageSize] = useState(50);
|
const [nePageSize, setNePageSize] = useState(50);
|
||||||
|
const [expandedNeId, setExpandedNeId] = useState("");
|
||||||
|
|
||||||
const [curSeverity, setCurSeverity] = useState("");
|
const [curSeverity, setCurSeverity] = useState("");
|
||||||
const [curCleared, setCurCleared] = useState("");
|
const [curCleared, setCurCleared] = useState("");
|
||||||
|
|
@ -265,19 +266,55 @@ export function UmePage() {
|
||||||
<th>ne_name</th>
|
<th>ne_name</th>
|
||||||
<th>ip</th>
|
<th>ip</th>
|
||||||
<th>type</th>
|
<th>type</th>
|
||||||
|
<th>device_level</th>
|
||||||
|
<th>host_name</th>
|
||||||
|
<th>hw_ver</th>
|
||||||
<th>last_seen</th>
|
<th>last_seen</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{(neQuery.data?.items || []).map((x) => (
|
{(neQuery.data?.items || []).map((x) => (
|
||||||
<tr key={x.ne_id}>
|
<Fragment key={x.ne_id}>
|
||||||
<td>{x.ne_id}</td>
|
<tr>
|
||||||
<td>{x.user_label}</td>
|
<td>
|
||||||
<td>{x.ne_name}</td>
|
<button
|
||||||
<td>{x.ip_address}</td>
|
className="link-btn"
|
||||||
<td>{x.ne_type}</td>
|
onClick={() => setExpandedNeId(expandedNeId === x.ne_id ? "" : x.ne_id)}
|
||||||
<td>{x.last_seen_at ? formatSystemTime(x.last_seen_at) : "-"}</td>
|
title={expandedNeId === x.ne_id ? "收起详情" : "展开详情"}
|
||||||
</tr>
|
>
|
||||||
|
{x.ne_id}
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td>{x.user_label}</td>
|
||||||
|
<td>{x.ne_name}</td>
|
||||||
|
<td>{x.ip_address}</td>
|
||||||
|
<td>{x.ne_type}</td>
|
||||||
|
<td>{x.device_level || "-"}</td>
|
||||||
|
<td>{x.host_name || "-"}</td>
|
||||||
|
<td>{x.hardware_version || "-"}</td>
|
||||||
|
<td>{x.last_seen_at ? formatSystemTime(x.last_seen_at) : "-"}</td>
|
||||||
|
</tr>
|
||||||
|
{expandedNeId === x.ne_id ? (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={9}>
|
||||||
|
<div style={{ fontSize: 12, display: "grid", gridTemplateColumns: "repeat(3, minmax(180px, 1fr))", gap: 8 }}>
|
||||||
|
<div>consistent_state: {x.consistent_state || "-"}</div>
|
||||||
|
<div>admin_status: {x.admin_status || "-"}</div>
|
||||||
|
<div>connection_status: {x.connection_status || "-"}</div>
|
||||||
|
<div>maintain_status: {x.maintain_status || "-"}</div>
|
||||||
|
<div>address_type: {x.address_type || "-"}</div>
|
||||||
|
<div>location: {x.location || "-"}</div>
|
||||||
|
<div>loopback: {x.loopback || "-"}</div>
|
||||||
|
<div>net_mask: {x.net_mask || "-"}</div>
|
||||||
|
<div>mac: {x.mac || "-"}</div>
|
||||||
|
<div>interface_version: {x.interface_version || "-"}</div>
|
||||||
|
<div>create_time: {x.create_time || "-"}</div>
|
||||||
|
<div>creator: {x.creator || "-"}</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
) : null}
|
||||||
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
@ -368,12 +405,19 @@ export function UmePage() {
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div className="pager">
|
<div className="pager">
|
||||||
<div className="pager__meta">共 {currentQuery.data?.total || 0} 条 · 第 {curPage} 页</div>
|
<div className="pager__meta">
|
||||||
|
共 {currentQuery.data?.total || 0} 条 · 第 {curPage}/
|
||||||
|
{Math.max(1, Math.ceil(Math.max(0, Number(currentQuery.data?.total || 0)) / Math.max(1, curPageSize)))} 页
|
||||||
|
</div>
|
||||||
<div className="pager__controls">
|
<div className="pager__controls">
|
||||||
<button className="pager__btn" onClick={() => setCurPage(Math.max(1, curPage - 1))} disabled={curPage <= 1}>
|
<button className="pager__btn" onClick={() => setCurPage(Math.max(1, curPage - 1))} disabled={curPage <= 1}>
|
||||||
上一页
|
上一页
|
||||||
</button>
|
</button>
|
||||||
<button className="pager__btn" onClick={() => setCurPage(curPage + 1)} disabled={(currentQuery.data?.items || []).length < curPageSize}>
|
<button
|
||||||
|
className="pager__btn"
|
||||||
|
onClick={() => setCurPage(curPage + 1)}
|
||||||
|
disabled={curPage >= Math.max(1, Math.ceil(Math.max(0, Number(currentQuery.data?.total || 0)) / Math.max(1, curPageSize)))}
|
||||||
|
>
|
||||||
下一页
|
下一页
|
||||||
</button>
|
</button>
|
||||||
<select
|
<select
|
||||||
|
|
@ -457,12 +501,19 @@ export function UmePage() {
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div className="pager">
|
<div className="pager">
|
||||||
<div className="pager__meta">共 {historyQuery.data?.total || 0} 条 · 第 {hisPage} 页</div>
|
<div className="pager__meta">
|
||||||
|
共 {historyQuery.data?.total || 0} 条 · 第 {hisPage}/
|
||||||
|
{Math.max(1, Math.ceil(Math.max(0, Number(historyQuery.data?.total || 0)) / Math.max(1, hisPageSize)))} 页
|
||||||
|
</div>
|
||||||
<div className="pager__controls">
|
<div className="pager__controls">
|
||||||
<button className="pager__btn" onClick={() => setHisPage(Math.max(1, hisPage - 1))} disabled={hisPage <= 1}>
|
<button className="pager__btn" onClick={() => setHisPage(Math.max(1, hisPage - 1))} disabled={hisPage <= 1}>
|
||||||
上一页
|
上一页
|
||||||
</button>
|
</button>
|
||||||
<button className="pager__btn" onClick={() => setHisPage(hisPage + 1)} disabled={(historyQuery.data?.items || []).length < hisPageSize}>
|
<button
|
||||||
|
className="pager__btn"
|
||||||
|
onClick={() => setHisPage(hisPage + 1)}
|
||||||
|
disabled={hisPage >= Math.max(1, Math.ceil(Math.max(0, Number(historyQuery.data?.total || 0)) / Math.max(1, hisPageSize)))}
|
||||||
|
>
|
||||||
下一页
|
下一页
|
||||||
</button>
|
</button>
|
||||||
<select
|
<select
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,21 @@ export type UmeNeItem = {
|
||||||
user_label: string;
|
user_label: string;
|
||||||
ip_address: string;
|
ip_address: string;
|
||||||
ne_type: string;
|
ne_type: string;
|
||||||
|
device_level?: string;
|
||||||
|
host_name?: string;
|
||||||
|
location?: string;
|
||||||
|
hardware_version?: string;
|
||||||
|
loopback?: string;
|
||||||
|
consistent_state?: string;
|
||||||
|
interface_version?: string;
|
||||||
|
mac?: string;
|
||||||
|
admin_status?: string;
|
||||||
|
address_type?: string;
|
||||||
|
connection_status?: string;
|
||||||
|
maintain_status?: string;
|
||||||
|
net_mask?: string;
|
||||||
|
create_time?: string;
|
||||||
|
creator?: string;
|
||||||
last_seen_at?: string;
|
last_seen_at?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue