netx/netx_api/ume_inventory_router.py
oliver 6dc3946bc6 Split UME routes and managed-NE service by domain.
Keep thin facades for existing imports, tighten CSV import required columns, and restore Chinese WebCRT connection labels in status responses.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 17:03:42 +08:00

177 lines
6.2 KiB
Python

"""UME inventory NE list/detail."""
from __future__ import annotations
import logging
from datetime import datetime, timezone
from typing import Any
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import or_
from sqlalchemy.orm import Session
from .config import settings
from .db import get_db
from .key_alert_config import (
get_key_alert_monitor_config,
invalidate_key_alert_config_cache,
set_key_alert_monitor_config,
)
from .key_alert_matcher import (
invalidate_key_alert_rule_cache,
normalize_match_type,
parse_rule_ne_types_payload,
rule_match_type,
rule_match_value,
rule_ne_types,
rule_storage_key,
serialize_rule_ne_types,
)
from .models import (
UmeAlarmCurrent,
UmeAlarmHistory,
UmeInventoryNE,
UmeKeyAlertForwardLog,
UmeKeyAlertRule,
UmeSyncJob,
)
from .oclaw_alarm_forwarder import (
forwarder_status,
request_forwarder_reconnect,
)
from .ume_alarm_ws import (
cancel_alarm_subscription_manual,
clear_local_alarm_subscription_manual,
establish_alarm_subscription_manual,
get_alarms_coordination_status,
get_subscription_status,
get_ws_connection_status,
get_ws_logs,
request_ws_reconnect,
)
from .ume_support import (
UME_KNOWN_RUNTIME_TASKS,
_aggregate_rows,
_ensure_utc,
_list_runtime_tasks,
_request_force_sync_after_resume,
_runtime_pause_task,
_runtime_resume_task,
_ume_alarm_host_name,
_ume_alarm_ne_group_key,
_ume_client,
_ume_error_kind,
_clear_force_resume_hints,
)
from .ume_sync_service import sync_alarms_current, sync_alarms_history_full, sync_inventory_full
from .ume_token_store import clear_shared_token
_log = logging.getLogger("netx.ume.router")
router = APIRouter(tags=["ume"])
@router.get("/v1/ume/inventory/ne-types")
def ume_list_inventory_ne_types(
limit: int = Query(default=500, ge=1, le=2000),
db: Session = Depends(get_db),
) -> dict[str, Any]:
from sqlalchemy import func
rows = (
db.query(
UmeInventoryNE.ne_type,
func.count(UmeInventoryNE.ne_id).label("ne_count"),
)
.filter(UmeInventoryNE.ne_type != "")
.group_by(UmeInventoryNE.ne_type)
.order_by(func.count(UmeInventoryNE.ne_id).desc(), UmeInventoryNE.ne_type.asc())
.limit(limit)
.all()
)
items = [{"ne_type": str(ne_type or ""), "ne_count": int(ne_count or 0)} for ne_type, ne_count in rows if str(ne_type or "").strip()]
return {"items": items, "total": len(items)}
@router.get("/v1/ume/inventory/ne")
def ume_list_ne(
keyword: str | None = Query(default=None),
page: int = Query(default=1, ge=1),
page_size: int = Query(default=50, ge=1, le=500),
db: Session = Depends(get_db),
) -> dict[str, Any]:
stmt = db.query(UmeInventoryNE)
kw = str(keyword or "").strip()
if kw:
like = f"%{kw}%"
stmt = stmt.filter(
UmeInventoryNE.ne_id.ilike(like)
| UmeInventoryNE.ne_name.ilike(like)
| UmeInventoryNE.user_label.ilike(like)
| UmeInventoryNE.ip_address.ilike(like)
| UmeInventoryNE.host_name.ilike(like)
)
total = int(stmt.count())
rows = stmt.order_by(UmeInventoryNE.ne_id.asc()).offset((page - 1) * page_size).limit(page_size).all()
items = [
{
"ne_id": str(x.ne_id or ""),
"ne_name": str(x.ne_name or ""),
"user_label": str(x.user_label or ""),
"ip_address": str(x.ip_address or ""),
"ipv6_address": str(x.ipv6_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
]
return {"total": total, "page": page, "page_size": page_size, "items": items}
@router.get("/v1/ume/inventory/ne/{ne_id}")
def ume_get_ne(ne_id: str, db: Session = Depends(get_db)) -> dict[str, Any]:
row = db.get(UmeInventoryNE, ne_id)
if not row:
raise HTTPException(status_code=404, detail="ume_ne_not_found")
return {
"ne_id": str(row.ne_id or ""),
"ne_name": str(row.ne_name or ""),
"user_label": str(row.user_label or ""),
"ip_address": str(row.ip_address or ""),
"ipv6_address": str(row.ipv6_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(),
"last_seen_at": (_ensure_utc(row.last_seen_at) or datetime.now(timezone.utc)).isoformat(),
"raw_json": str(row.raw_json or "{}"),
}