Split ORM models into a domain package with stable re-exports.

Keep from netx_api.models import X working while grouping alarms, UME, managed NE, topology, auth, config sync, and port traffic tables.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-08-02 17:48:37 +08:00
parent 27194a3281
commit 083ff98574
10 changed files with 1119 additions and 945 deletions

View file

@ -1,945 +0,0 @@
from __future__ import annotations
from datetime import datetime
from uuid import uuid4
from sqlalchemy import BigInteger, Boolean, DateTime, Float, ForeignKey, Integer, LargeBinary, String, Text, UniqueConstraint
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.types import JSON
from .db import Base
from .timeutil import utcnow_naive
# JSONB on Postgres; plain JSON elsewhere (unit tests / sqlite).
_JsonType = JSON().with_variant(JSONB(), "postgresql")
class AlarmBatch(Base):
__tablename__ = "alarm_batches"
batch_id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
source_file: Mapped[str] = mapped_column(String(512))
parser_version: Mapped[str] = mapped_column(String(64), default="zte_alarm_monitor_v1")
dict_version: Mapped[str] = mapped_column(String(64), default="v1")
total_rows: Mapped[int] = mapped_column(Integer, default=0)
success_rows: Mapped[int] = mapped_column(Integer, default=0)
failed_rows: Mapped[int] = mapped_column(Integer, default=0)
status: Mapped[str] = mapped_column(String(32), default="done")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
alarms: Mapped[list["AlarmNorm"]] = relationship(back_populates="batch")
errors: Mapped[list["ImportErrorRow"]] = relationship(back_populates="batch")
class AlarmNorm(Base):
__tablename__ = "alarms_norm"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
batch_id: Mapped[str] = mapped_column(ForeignKey("alarm_batches.batch_id"), index=True)
row_no: Mapped[int] = mapped_column(Integer)
alarm_time: Mapped[datetime] = mapped_column(DateTime, index=True)
clear_time: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
severity_raw: Mapped[str] = mapped_column(String(64), default="")
severity_norm: Mapped[str] = mapped_column(String(32), index=True, default="unknown")
ne_name: Mapped[str] = mapped_column(String(256), default="", index=True)
ne_id: Mapped[str] = mapped_column(String(128), default="", index=True)
site_name: Mapped[str] = mapped_column(String(256), default="")
alarm_code: Mapped[str] = mapped_column(String(256), default="", index=True)
# NOTE: historically we stored `alarm_name` separately, but for ZTE Alarm Monitor exports
# "Alarm Code Name" is a single column. We keep the DB column for backward compatibility,
# but APIs/UI are unified on `alarm_code`.
alarm_name: Mapped[str] = mapped_column(String(512), default="")
description: Mapped[str] = mapped_column(Text, default="")
ack_state: Mapped[str] = mapped_column(String(64), default="")
clear_state: Mapped[str] = mapped_column(String(64), default="")
relevancy: Mapped[str] = mapped_column(String(128), default="")
l3vpn_peer_ne: Mapped[str] = mapped_column(String(256), default="")
service: Mapped[str] = mapped_column(String(256), default="")
affected_client_service_number: Mapped[int] = mapped_column(Integer, default=0)
intermittence_count: Mapped[int] = mapped_column(Integer, default=0)
me_level: 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="gateway_export_excel")
source_file: Mapped[str] = mapped_column(String(512), default="")
raw_json: Mapped[str] = mapped_column(Text, default="{}")
batch: Mapped[AlarmBatch] = relationship(back_populates="alarms")
class ImportErrorRow(Base):
__tablename__ = "import_errors"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
batch_id: Mapped[str] = mapped_column(ForeignKey("alarm_batches.batch_id"), index=True)
row_no: Mapped[int] = mapped_column(Integer)
reason: Mapped[str] = mapped_column(String(512))
raw_json: Mapped[str] = mapped_column(Text, default="{}")
batch: Mapped[AlarmBatch] = relationship(back_populates="errors")
class ImportJob(Base):
__tablename__ = "import_jobs"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
kind: Mapped[str] = mapped_column(String(32), index=True) # alarms/logs/config
file_name: Mapped[str] = mapped_column(String(512), default="")
batch_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
ok: Mapped[int] = mapped_column(Integer, default=1) # 1 ok, 0 error
summary: Mapped[str] = mapped_column(String(1024), default="")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class AiAnalyzeHistory(Base):
__tablename__ = "ai_analyze_history"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
analysis_request_id: Mapped[str] = mapped_column(String(128), default="", index=True)
batch_id: Mapped[str] = mapped_column(String(64), default="", index=True)
question: Mapped[str] = mapped_column(Text, default="")
filters_json: Mapped[str] = mapped_column(Text, default="{}")
ok: Mapped[int] = mapped_column(Integer, default=1) # 1 ok, 0 error
answer: Mapped[str] = mapped_column(Text, default="")
error: Mapped[str] = mapped_column(Text, default="")
evidence_json: Mapped[str] = mapped_column(Text, default="{}")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class UmeSyncJob(Base):
__tablename__ = "ume_sync_jobs"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
domain: Mapped[str] = mapped_column(String(32), index=True, default="inventory")
status: Mapped[str] = mapped_column(String(32), index=True, default="running")
trigger_mode: Mapped[str] = mapped_column(String(32), default="manual")
started_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
ended_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
pulled_count: Mapped[int] = mapped_column(Integer, default=0)
inserted_count: Mapped[int] = mapped_column(Integer, default=0)
updated_count: Mapped[int] = mapped_column(Integer, default=0)
error_message: Mapped[str] = mapped_column(String(1024), default="")
details_json: Mapped[str] = mapped_column(Text, default="{}")
class UmeAlarmBatch(Base):
__tablename__ = "ume_alarm_batches"
batch_id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
kind: Mapped[str] = mapped_column(String(16), index=True, default="current") # current/history
status: Mapped[str] = mapped_column(String(32), default="done")
total_rows: Mapped[int] = mapped_column(Integer, default=0)
success_rows: Mapped[int] = mapped_column(Integer, default=0)
failed_rows: Mapped[int] = mapped_column(Integer, default=0)
started_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
ended_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
error_message: Mapped[str] = mapped_column(String(1024), default="")
raw_json: Mapped[str] = mapped_column(Text, default="{}")
class UmeInventoryNE(Base):
__tablename__ = "ume_inventory_ne"
ne_id: Mapped[str] = mapped_column(String(128), primary_key=True, comment="网元uuid")
ne_name: Mapped[str] = mapped_column(String(256), default="", index=True, comment="资源名称")
user_label: Mapped[str] = mapped_column(String(256), default="", index=True, comment="用户标签")
ip_address: Mapped[str] = mapped_column(String(128), default="", index=True, comment="网元IPv4地址")
ipv6_address: Mapped[str] = mapped_column(String(128), default="", index=True, comment="IPv6地址")
ne_type: Mapped[str] = mapped_column(String(128), default="", comment="网元类型")
device_level: Mapped[str] = mapped_column(String(64), default="", comment="网元层次")
host_name: Mapped[str] = mapped_column(String(256), default="", comment="主机名称")
location: Mapped[str] = mapped_column(String(512), default="")
hardware_version: Mapped[str] = mapped_column(String(128), default="", comment="硬件版本")
loopback: Mapped[str] = mapped_column(String(128), default="", comment="业务环回IP(IPv4)")
consistent_state: Mapped[str] = mapped_column(String(64), default="", comment="数据一致性状态")
interface_version: Mapped[str] = mapped_column(String(128), default="", comment="网元接口版本号")
mac: Mapped[str] = mapped_column(String(128), default="", comment="设备机架MAC地址")
admin_status: Mapped[str] = mapped_column(String(64), default="", comment="管理状态")
address_type: Mapped[str] = mapped_column(String(64), default="", comment="管理地址类型(1:IPv4,2:IPv6)")
connection_status: Mapped[str] = mapped_column(String(64), default="", comment="连接状态")
maintain_status: Mapped[str] = mapped_column(String(64), default="", comment="工程状态")
net_mask: Mapped[str] = mapped_column(String(128), default="", comment="管理IPv4掩码")
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", comment="网元提供商")
source_type: Mapped[str] = mapped_column(String(64), default="ume_restconf")
first_seen_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
last_seen_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
raw_json: Mapped[str] = mapped_column(Text, default="{}")
class UmeAlarmCurrent(Base):
__tablename__ = "ume_alarms_current"
alarm_key: Mapped[str] = mapped_column(Text, primary_key=True)
ne_id: Mapped[str] = mapped_column(String(128), default="", index=True)
host_name: Mapped[str] = mapped_column(String(256), default="", index=True, comment="网元主机名(同步时从inventory联表写入)")
object_name: Mapped[str] = mapped_column(Text, default="", index=True)
event_type: Mapped[str] = mapped_column(Text, default="")
native_probable_cause: Mapped[str] = mapped_column(Text, default="")
perceived_severity: Mapped[str] = mapped_column(Text, default="", index=True)
is_cleared: Mapped[str] = mapped_column(Text, default="", index=True)
time_created: Mapped[str] = mapped_column(Text, default="", index=True)
root_cause_alarm_indication: Mapped[str] = mapped_column(Text, default="")
notification_id: Mapped[str] = mapped_column(String(128), default="", index=True)
first_seen_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
last_seen_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
raw_json: Mapped[str] = mapped_column(Text, default="{}")
class UmeAlarmHistory(Base):
__tablename__ = "ume_alarms_history"
alarm_key: Mapped[str] = mapped_column(Text, primary_key=True)
ne_id: Mapped[str] = mapped_column(String(128), default="", index=True)
host_name: Mapped[str] = mapped_column(String(256), default="", index=True, comment="网元主机名(同步时从inventory联表写入)")
object_name: Mapped[str] = mapped_column(Text, default="", index=True)
event_type: Mapped[str] = mapped_column(Text, default="")
native_probable_cause: Mapped[str] = mapped_column(Text, default="")
perceived_severity: Mapped[str] = mapped_column(Text, default="", index=True)
is_cleared: Mapped[str] = mapped_column(Text, default="", index=True)
time_created: Mapped[str] = mapped_column(Text, default="", index=True)
root_cause_alarm_indication: Mapped[str] = mapped_column(Text, default="")
notification_id: Mapped[str] = mapped_column(String(128), default="", index=True)
first_seen_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
last_seen_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
raw_json: Mapped[str] = mapped_column(Text, default="{}")
class UmeKeyAlertMonitorConfig(Base):
"""Global monitor options for key alert forwarding (singleton row id=1)."""
__tablename__ = "ume_key_alert_monitor_config"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
forward_on_clear: Mapped[int] = mapped_column(Integer, default=0)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class UmeKeyAlertRule(Base):
"""Key alert rule matched by UME notificationId or alarm description keyword."""
__tablename__ = "ume_key_alert_rule"
notification_id: Mapped[str] = mapped_column(String(128), primary_key=True)
match_type: Mapped[str] = mapped_column(String(32), default="notification_id", index=True)
match_value: Mapped[str] = mapped_column(String(256), default="", index=True)
enabled: Mapped[int] = mapped_column(Integer, default=1)
forward_on_clear: Mapped[int] = mapped_column(Integer, default=0)
label: Mapped[str] = mapped_column(String(256), default="")
ne_types: Mapped[str] = mapped_column(Text, default="[]")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class UmeKeyAlertForwardLog(Base):
__tablename__ = "ume_key_alert_forward_log"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
alarm_key: Mapped[str] = mapped_column(Text, index=True)
action: Mapped[str] = mapped_column(String(32), default="", index=True)
rule_key: Mapped[str] = mapped_column(String(128), default="", index=True)
notification_id: Mapped[str] = mapped_column(String(128), default="", index=True)
forwarded_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
oclaw_ok: Mapped[int] = mapped_column(Integer, default=0)
error: Mapped[str] = mapped_column(String(512), default="")
class UmeAlarmSubscription(Base):
"""Persisted UME ALARM notification subscription (manual establish/cancel)."""
__tablename__ = "ume_alarm_subscription"
cache_key: Mapped[str] = mapped_column(String(64), primary_key=True, default="default")
subscription_id: Mapped[str] = mapped_column(String(128), default="", index=True)
wss_uri: Mapped[str] = mapped_column(Text, default="")
topic: Mapped[str] = mapped_column(String(64), default="ALARM")
established_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class UmeTokenCache(Base):
__tablename__ = "ume_token_cache"
cache_key: Mapped[str] = mapped_column(String(256), primary_key=True)
token: Mapped[str] = mapped_column(Text, default="")
expires_at_epoch_s: Mapped[int] = mapped_column(Integer, default=0)
lock_owner: Mapped[str] = mapped_column(String(128), default="", index=True)
lock_expires_at_epoch_s: Mapped[int] = mapped_column(Integer, default=0, index=True)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class ManagedNE(Base):
"""Locally managed network element (SSH/Telnet), independent of UME inventory."""
__tablename__ = "managed_ne"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
name: Mapped[str] = mapped_column(String(256), default="", index=True)
vendor: Mapped[str] = mapped_column(String(64), default="Other", index=True)
device_type: Mapped[str] = mapped_column(String(128), default="")
# Not unique: WebCRT sessions may share a host IP with distinct session names.
# Inventory create/update still enforces uniqueness in ne_service.
ip_address: Mapped[str] = mapped_column(String(128), index=True)
port: Mapped[int] = mapped_column(Integer, default=22)
protocol: Mapped[str] = mapped_column(String(16), default="ssh")
username: Mapped[str] = mapped_column(String(128), default="")
password_enc: Mapped[str] = mapped_column(Text, default="")
enable_secret_enc: Mapped[str] = mapped_column(Text, default="")
connect_status: Mapped[str] = mapped_column(String(32), default="unknown", index=True)
connect_message: Mapped[str] = mapped_column(String(512), default="")
connect_detail: Mapped[str] = mapped_column(Text, default="")
connect_tested_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
site: Mapped[str] = mapped_column(String(256), default="")
tags: Mapped[str] = mapped_column(String(512), default="")
remark: Mapped[str] = mapped_column(String(1024), default="")
source: Mapped[str] = mapped_column(String(64), default="", index=True)
source_ref: Mapped[str] = mapped_column(String(128), default="", index=True)
hop_enabled: Mapped[bool] = mapped_column(default=False)
hop_vendor: Mapped[str] = mapped_column(String(32), default="zte")
hop_host: Mapped[str] = mapped_column(String(128), default="")
hop_port: Mapped[int] = mapped_column(Integer, default=22)
hop_protocol: Mapped[str] = mapped_column(String(16), default="ssh")
hop_username: Mapped[str] = mapped_column(String(128), default="")
hop_password_enc: Mapped[str] = mapped_column(Text, default="")
hop_command_template: Mapped[str] = mapped_column(Text, default="")
hop_vrf: Mapped[str] = mapped_column(String(128), default="")
hop_target_auth_mode: Mapped[str] = mapped_column(String(32), default="bastion_managed")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class CliConnectProfile(Base):
"""Reusable SSH/Telnet + hop credentials for UME lazy CLI exec."""
__tablename__ = "cli_connect_profile"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
name: Mapped[str] = mapped_column(String(256), default="", index=True)
is_default: Mapped[bool] = mapped_column(default=False, index=True)
username: Mapped[str] = mapped_column(String(128), default="")
password_enc: Mapped[str] = mapped_column(Text, default="")
port: Mapped[int] = mapped_column(Integer, default=22)
protocol: Mapped[str] = mapped_column(String(16), default="ssh")
device_type_default: Mapped[str] = mapped_column(String(128), default="zte_zxros")
vendor_default: Mapped[str] = mapped_column(String(64), default="ZTE")
ne_type_rules: Mapped[str] = mapped_column(Text, default="")
hop_enabled: Mapped[bool] = mapped_column(default=False)
hop_vendor: Mapped[str] = mapped_column(String(32), default="zte")
hop_host: Mapped[str] = mapped_column(String(128), default="")
hop_port: Mapped[int] = mapped_column(Integer, default=22)
hop_protocol: Mapped[str] = mapped_column(String(16), default="ssh")
hop_username: Mapped[str] = mapped_column(String(128), default="")
hop_password_enc: Mapped[str] = mapped_column(Text, default="")
hop_command_template: Mapped[str] = mapped_column(Text, default="")
hop_vrf: Mapped[str] = mapped_column(String(128), default="")
hop_target_auth_mode: Mapped[str] = mapped_column(String(32), default="bastion_managed")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class UmeCliOverride(Base):
"""Per-UME NE CLI overrides and connect-test cache."""
__tablename__ = "ume_cli_override"
ume_ne_id: Mapped[str] = mapped_column(String(128), primary_key=True)
profile_id: Mapped[str | None] = mapped_column(
String(64), ForeignKey("cli_connect_profile.id", ondelete="SET NULL"), nullable=True
)
username_override: Mapped[str] = mapped_column(String(128), default="")
device_type_override: Mapped[str] = mapped_column(String(128), default="")
vendor_override: Mapped[str] = mapped_column(String(64), default="")
connect_status: Mapped[str] = mapped_column(String(32), default="unknown", index=True)
connect_message: Mapped[str] = mapped_column(String(512), default="")
connect_detail: Mapped[str] = mapped_column(Text, default="")
connect_tested_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class NeCollectionJob(Base):
"""Batch CLI collection job over managed NEs."""
__tablename__ = "ne_collection_job"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
title: Mapped[str] = mapped_column(String(256), default="")
commands: Mapped[str] = mapped_column(Text, default="")
status: Mapped[str] = mapped_column(String(32), default="pending", index=True)
ne_count: Mapped[int] = mapped_column(Integer, default=0)
success_count: Mapped[int] = mapped_column(Integer, default=0)
fail_count: Mapped[int] = mapped_column(Integer, default=0)
error_message: Mapped[str] = mapped_column(String(1024), default="")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
ended_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
last_run_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, index=True)
class NeCollectionRun(Base):
"""Per-NE execution within a collection job."""
__tablename__ = "ne_collection_run"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
job_id: Mapped[str] = mapped_column(String(64), index=True)
# managed_ne.id or ume_inventory_ne.ne_id
ne_id: Mapped[str] = mapped_column(String(128), index=True)
# managed | ume
ne_source: Mapped[str] = mapped_column(String(16), default="managed", index=True)
ne_name: Mapped[str] = mapped_column(String(256), default="")
ne_ip: Mapped[str] = mapped_column(String(128), default="")
status: Mapped[str] = mapped_column(String(32), default="pending", index=True)
message: Mapped[str] = mapped_column(String(1024), default="")
output_rel_path: Mapped[str] = mapped_column(String(1024), default="")
started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
ended_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
class TopoFabricNode(Base):
"""Global topology fabric node (inventory-aligned). Target scale ~50k."""
__tablename__ = "topo_fabric_node"
__table_args__ = (
UniqueConstraint("managed_ne_id", name="uq_topo_fabric_node_managed_ne_id"),
UniqueConstraint("ume_ne_id", name="uq_topo_fabric_node_ume_ne_id"),
)
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
managed_ne_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
ume_ne_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True)
name: Mapped[str] = mapped_column(String(256), default="", index=True)
ip: Mapped[str] = mapped_column(String(128), default="", index=True)
vendor: Mapped[str] = mapped_column(String(64), default="")
device_type: Mapped[str] = mapped_column(String(64), default="")
# Classify tags (regex rules / manual). role: core|aggregation|access|unknown|""
role: Mapped[str] = mapped_column(String(32), default="", index=True)
region_folder_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
# rule | manual | ""
role_source: Mapped[str] = mapped_column(String(16), default="")
region_source: Mapped[str] = mapped_column(String(16), default="")
attrs: Mapped[dict] = mapped_column(_JsonType, default=dict)
last_seen_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, index=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class TopoClassifyRule(Base):
"""Regex rules to assign fabric role or region from NE name/IP."""
__tablename__ = "topo_classify_rule"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
# role | region
scope: Mapped[str] = mapped_column(String(16), default="role", index=True)
name: Mapped[str] = mapped_column(String(256), default="")
pattern: Mapped[str] = mapped_column(String(512), default="")
# name | ip | name_ip
match_field: Mapped[str] = mapped_column(String(32), default="name")
priority: Mapped[int] = mapped_column(Integer, default=100, index=True)
enabled: Mapped[bool] = mapped_column(Boolean, default=True, index=True)
# role: {role}; region: {folder_id} or {region_name_from_group}
payload: Mapped[dict] = mapped_column(_JsonType, default=dict)
remark: Mapped[str] = mapped_column(String(512), default="")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class TopoFabricEdge(Base):
"""Global fabric link. Target scale ~1M; layer reserved for future BGP/tunnel/l2vpn."""
__tablename__ = "topo_fabric_edge"
__table_args__ = (
UniqueConstraint(
"layer",
"a_node_id",
"b_node_id",
"a_port",
"b_port",
name="uq_topo_fabric_edge_endpoints",
),
)
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
# physical | bgp | tunnel | l2vpn (P1 writes physical only)
layer: Mapped[str] = mapped_column(String(32), default="physical", index=True)
a_node_id: Mapped[str] = mapped_column(String(64), index=True)
b_node_id: Mapped[str] = mapped_column(String(64), index=True)
a_port: Mapped[str] = mapped_column(String(128), default="")
b_port: Mapped[str] = mapped_column(String(128), default="")
# lldp | manual | stale
source: Mapped[str] = mapped_column(String(32), default="lldp", index=True)
# active | stale
status: Mapped[str] = mapped_column(String(32), default="active", index=True)
attrs: Mapped[dict] = mapped_column(_JsonType, default=dict)
discovered_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
last_seen_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, index=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
class TopoFolder(Base):
"""Grouping node for topology tree (root / region). Not a canvas."""
__tablename__ = "topo_folder"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
parent_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
# root | region
kind: Mapped[str] = mapped_column(String(32), default="region", index=True)
name: Mapped[str] = mapped_column(String(256), default="", index=True)
sort_order: Mapped[int] = mapped_column(Integer, default=0)
is_system: Mapped[bool] = mapped_column(Boolean, default=False)
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class TopoView(Base):
"""Topology canvas under a site/region (physical or custom; flat siblings)."""
__tablename__ = "topo_view"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
folder_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
# Legacy nesting column; unused (always null after vendor-model migration).
parent_view_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
# physical | custom
kind: Mapped[str] = mapped_column(String(32), default="custom", index=True)
# Optional filter preset label (legacy core|aggregation|access); not a tree level.
role: Mapped[str] = mapped_column(String(32), default="core", index=True)
name: Mapped[str] = mapped_column(String(256), default="", index=True)
remark: Mapped[str] = mapped_column(String(1024), default="")
sort_order: Mapped[int] = mapped_column(Integer, default=0)
# { layer?, status?, membership?: {...} }
filter: Mapped[dict] = mapped_column(_JsonType, default=dict)
viewport: Mapped[dict] = mapped_column(_JsonType, default=dict)
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class TopoViewNode(Base):
"""Node placement on a view; no link facts."""
__tablename__ = "topo_view_node"
__table_args__ = (UniqueConstraint("view_id", "fabric_node_id", name="uq_topo_view_node"),)
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
view_id: Mapped[str] = mapped_column(String(64), index=True)
fabric_node_id: Mapped[str] = mapped_column(String(64), index=True)
x: Mapped[float] = mapped_column(Float, default=0.0)
y: Mapped[float] = mapped_column(Float, default=0.0)
label: Mapped[str] = mapped_column(String(256), default="")
locked: Mapped[bool] = mapped_column(Boolean, default=False)
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
class TopoViewEdgeStyle(Base):
"""Optional per-view edge style override."""
__tablename__ = "topo_view_edge_style"
__table_args__ = (UniqueConstraint("view_id", "fabric_edge_id", name="uq_topo_view_edge_style"),)
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
view_id: Mapped[str] = mapped_column(String(64), index=True)
fabric_edge_id: Mapped[str] = mapped_column(String(64), index=True)
stroke_color: Mapped[str] = mapped_column(String(32), default="")
stroke_width: Mapped[int] = mapped_column(Integer, default=0)
line_style: Mapped[str] = mapped_column(String(16), default="")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
class LldpCollectPolicy(Base):
"""Singleton policy for periodic fabric LLDP collect (id=1)."""
__tablename__ = "lldp_collect_policy"
id: Mapped[int] = mapped_column(Integer, primary_key=True, default=1)
enabled: Mapped[bool] = mapped_column(Boolean, default=False)
interval_days: Mapped[int] = mapped_column(Integer, default=1) # legacy; prefer interval_hours
interval_hours: Mapped[int] = mapped_column(Integer, default=24)
concurrency: Mapped[int] = mapped_column(Integer, default=4)
scope_mode: Mapped[str] = mapped_column(String(32), default="all") # all | selected
selected_targets: Mapped[list] = mapped_column(_JsonType, default=list)
auto_add_unmatched: Mapped[bool] = mapped_column(Boolean, default=True)
# Keep N finished discover jobs (items + raw_preview); 0 = keep none finished.
history_keep: Mapped[int] = mapped_column(Integer, default=30)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
class TopoDiscoverJob(Base):
"""Async LLDP discovery job over inventory / NE ids."""
__tablename__ = "topo_discover_job"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
# all_inventory | ne_ids
scope: Mapped[str] = mapped_column(String(32), default="ne_ids", index=True)
# manual | schedule | topology (ad-hoc from canvas)
trigger_mode: Mapped[str] = mapped_column(String(32), default="manual", index=True)
ne_ids_json: Mapped[list] = mapped_column(_JsonType, default=list)
status: Mapped[str] = mapped_column(String(32), default="pending", index=True)
total: Mapped[int] = mapped_column(Integer, default=0)
done: Mapped[int] = mapped_column(Integer, default=0)
edges_added: Mapped[int] = mapped_column(Integer, default=0)
edges_updated: Mapped[int] = mapped_column(Integer, default=0)
edges_stale: Mapped[int] = mapped_column(Integer, default=0)
error: Mapped[str] = mapped_column(String(1024), default="")
started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
ended_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class TopoDiscoverJobItem(Base):
"""Per-NE result row for a discover job."""
__tablename__ = "topo_discover_job_item"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
job_id: Mapped[str] = mapped_column(String(64), index=True)
ne_id: Mapped[str] = mapped_column(String(128), default="", index=True)
ume_ne_id: Mapped[str] = mapped_column(String(128), default="")
fabric_node_id: Mapped[str] = mapped_column(String(64), default="", index=True)
ne_name: Mapped[str] = mapped_column(String(256), default="")
ne_ip: Mapped[str] = mapped_column(String(128), default="")
ok: Mapped[bool] = mapped_column(Boolean, default=False)
command: Mapped[str] = mapped_column(String(256), default="")
neighbors: Mapped[int] = mapped_column(Integer, default=0)
edges_added: Mapped[int] = mapped_column(Integer, default=0)
edges_updated: Mapped[int] = mapped_column(Integer, default=0)
unmatched_count: Mapped[int] = mapped_column(Integer, default=0)
unmatched_json: Mapped[list] = mapped_column(_JsonType, default=list)
parser_key: Mapped[str] = mapped_column(String(64), default="")
parser_stub: Mapped[bool] = mapped_column(Boolean, default=False)
error: Mapped[str] = mapped_column(String(1024), default="")
raw_preview: Mapped[str] = mapped_column(Text, default="")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
class TopoFabricStats(Base):
"""Cached fabric counters for summary (avoid COUNT on 1M edges)."""
__tablename__ = "topo_fabric_stats"
id: Mapped[str] = mapped_column(String(32), primary_key=True, default="global")
node_count: Mapped[int] = mapped_column(Integer, default=0)
edge_count: Mapped[int] = mapped_column(Integer, default=0)
edge_active: Mapped[int] = mapped_column(Integer, default=0)
edge_stale: Mapped[int] = mapped_column(Integer, default=0)
last_discover_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
class AppUser(Base):
"""Local netx application user (login account)."""
__tablename__ = "app_user"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
username: Mapped[str] = mapped_column(String(128), unique=True, index=True)
password_hash: Mapped[str] = mapped_column(String(255), default="")
role: Mapped[str] = mapped_column(String(32), default="user", index=True) # admin | user
# Optional capability override; empty => role defaults (see auth_scopes).
scopes: Mapped[list] = mapped_column(_JsonType, default=list)
is_active: Mapped[bool] = mapped_column(Boolean, default=True, index=True)
must_change_password: Mapped[bool] = mapped_column(Boolean, default=False)
created_by: Mapped[str] = mapped_column(String(64), default="")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
class AuditLog(Base):
"""Application audit trail for authenticated (and auth) actions."""
__tablename__ = "audit_log"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
ts: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
actor_user_id: Mapped[str] = mapped_column(String(64), default="", index=True)
actor_username: Mapped[str] = mapped_column(String(128), default="", index=True)
action: Mapped[str] = mapped_column(String(128), default="", index=True)
method: Mapped[str] = mapped_column(String(16), default="")
path: Mapped[str] = mapped_column(String(512), default="", index=True)
status_code: Mapped[int] = mapped_column(Integer, default=0)
client_ip: Mapped[str] = mapped_column(String(128), default="")
user_agent: Mapped[str] = mapped_column(String(512), default="")
detail: Mapped[dict] = mapped_column(_JsonType, default=dict)
class ApiToken(Base):
"""Long-lived API token (MCP/scripts); hashed at rest."""
__tablename__ = "api_token"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
name: Mapped[str] = mapped_column(String(128), default="")
token_hash: Mapped[str] = mapped_column(String(128), unique=True, index=True)
user_id: Mapped[str] = mapped_column(String(64), index=True)
# Capability subset; empty inherits owner user scopes (then intersected).
scopes: Mapped[list] = mapped_column(_JsonType, default=list)
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
expires_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, index=True)
last_used_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
revoked_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
class ConfigSyncPolicy(Base):
"""Singleton policy for periodic config sync (id=1)."""
__tablename__ = "config_sync_policy"
id: Mapped[int] = mapped_column(Integer, primary_key=True, default=1)
enabled: Mapped[bool] = mapped_column(Boolean, default=False)
interval_days: Mapped[int] = mapped_column(Integer, default=3)
concurrency: Mapped[int] = mapped_column(Integer, default=5)
scope_mode: Mapped[str] = mapped_column(String(32), default="all") # all | selected
selected_targets: Mapped[list] = mapped_column(_JsonType, default=list)
history_keep: Mapped[int] = mapped_column(Integer, default=3)
# Finished sync cycles to retain (newest kept); active cycles always kept.
cycle_keep: Mapped[int] = mapped_column(Integer, default=30)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
class ConfigSyncCycle(Base):
"""One config-sync cycle over many NEs."""
__tablename__ = "config_sync_cycle"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
trigger_mode: Mapped[str] = mapped_column(String(32), default="schedule", index=True)
status: Mapped[str] = mapped_column(String(32), default="pending", index=True)
concurrency: Mapped[int] = mapped_column(Integer, default=5)
planned_count: Mapped[int] = mapped_column(Integer, default=0)
success_count: Mapped[int] = mapped_column(Integer, default=0)
fail_count: Mapped[int] = mapped_column(Integer, default=0)
skip_count: Mapped[int] = mapped_column(Integer, default=0)
error_message: Mapped[str] = mapped_column(String(1024), default="")
started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
ended_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class ConfigSyncTask(Base):
"""Per-NE work item inside a config sync cycle."""
__tablename__ = "config_sync_task"
__table_args__ = (UniqueConstraint("cycle_id", "source", "target_id", name="uq_config_sync_task_target"),)
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
cycle_id: Mapped[str] = mapped_column(String(64), index=True)
source: Mapped[str] = mapped_column(String(32), default="managed", index=True) # managed | ume
target_id: Mapped[str] = mapped_column(String(128), index=True)
ne_name: Mapped[str] = mapped_column(String(256), default="")
ne_ip: Mapped[str] = mapped_column(String(128), default="")
vendor: Mapped[str] = mapped_column(String(64), default="")
status: Mapped[str] = mapped_column(String(32), default="pending", index=True)
message: Mapped[str] = mapped_column(String(1024), default="")
started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
ended_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
class NeConfigSnapshot(Base):
"""Latest successful config snapshot per NE (zlib-compressed)."""
__tablename__ = "ne_config_snapshot"
source: Mapped[str] = mapped_column(String(32), primary_key=True) # managed | ume
target_id: Mapped[str] = mapped_column(String(128), primary_key=True)
vendor: Mapped[str] = mapped_column(String(64), default="")
device_type: Mapped[str] = mapped_column(String(64), default="")
ne_name: Mapped[str] = mapped_column(String(256), default="", index=True)
ne_ip: Mapped[str] = mapped_column(String(128), default="", index=True)
config_zlib: Mapped[bytes] = mapped_column(LargeBinary, default=b"")
config_alt_zlib: Mapped[bytes | None] = mapped_column(LargeBinary, nullable=True)
config_sha256: Mapped[str] = mapped_column(String(64), default="")
config_alt_sha256: Mapped[str] = mapped_column(String(64), default="")
plain_size: Mapped[int] = mapped_column(Integer, default=0)
plain_alt_size: Mapped[int] = mapped_column(Integer, default=0)
zlib_size: Mapped[int] = mapped_column(Integer, default=0)
zlib_alt_size: Mapped[int] = mapped_column(Integer, default=0)
commands_json: Mapped[list] = mapped_column(_JsonType, default=list)
collected_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
last_cycle_id: Mapped[str] = mapped_column(String(64), default="")
last_task_id: Mapped[str] = mapped_column(String(64), default="")
class NeConfigHistory(Base):
"""Historical config versions when content changes."""
__tablename__ = "ne_config_history"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
source: Mapped[str] = mapped_column(String(32), index=True)
target_id: Mapped[str] = mapped_column(String(128), index=True)
vendor: Mapped[str] = mapped_column(String(64), default="")
device_type: Mapped[str] = mapped_column(String(64), default="")
ne_name: Mapped[str] = mapped_column(String(256), default="")
ne_ip: Mapped[str] = mapped_column(String(128), default="")
config_zlib: Mapped[bytes] = mapped_column(LargeBinary, default=b"")
config_alt_zlib: Mapped[bytes | None] = mapped_column(LargeBinary, nullable=True)
config_sha256: Mapped[str] = mapped_column(String(64), default="")
config_alt_sha256: Mapped[str] = mapped_column(String(64), default="")
plain_size: Mapped[int] = mapped_column(Integer, default=0)
plain_alt_size: Mapped[int] = mapped_column(Integer, default=0)
zlib_size: Mapped[int] = mapped_column(Integer, default=0)
zlib_alt_size: Mapped[int] = mapped_column(Integer, default=0)
commands_json: Mapped[list] = mapped_column(_JsonType, default=list)
collected_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
cycle_id: Mapped[str] = mapped_column(String(64), default="")
task_id: Mapped[str] = mapped_column(String(64), default="")
class PortTrafficDevice(Base):
"""Per-NE port traffic monitoring config (device-centric)."""
__tablename__ = "port_traffic_device"
__table_args__ = (
UniqueConstraint("source", "ne_id", name="uq_port_traffic_device_ne"),
)
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
source: Mapped[str] = mapped_column(String(32), default="managed", index=True)
ne_id: Mapped[str] = mapped_column(String(128), default="", index=True)
ne_name: Mapped[str] = mapped_column(String(256), default="")
ne_ip: Mapped[str] = mapped_column(String(128), default="")
vendor: Mapped[str] = mapped_column(String(64), default="")
note: Mapped[str] = mapped_column(String(256), default="") # optional remark
status: Mapped[str] = mapped_column(String(32), default="draft", index=True) # draft|running|paused|stopped
interval_sec: Mapped[int] = mapped_column(Integer, default=60)
retention_days: Mapped[int] = mapped_column(Integer, default=7)
concurrency: Mapped[int] = mapped_column(Integer, default=1)
collect_running: Mapped[bool] = mapped_column(Boolean, default=False)
last_collect_started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
last_collect_ended_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
last_error: Mapped[str] = mapped_column(String(1024), default="")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
# Back-compat alias while callers migrate.
PortTrafficTask = PortTrafficDevice
class PortTrafficSeries(Base):
"""Logical port (business link) that survives physical NE/if replacement."""
__tablename__ = "port_traffic_series"
__table_args__ = (UniqueConstraint("device_id", "title", name="uq_port_traffic_series_title"),)
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
device_id: Mapped[str] = mapped_column(String(64), default="", index=True)
title: Mapped[str] = mapped_column(String(256), default="")
status: Mapped[str] = mapped_column(String(32), default="active", index=True) # active|disabled
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
@property
def task_id(self) -> str:
return str(self.device_id or "")
@task_id.setter
def task_id(self, value: str) -> None:
self.device_id = str(value or "")
class PortTrafficTarget(Base):
"""Monitored interface under a device monitoring config."""
__tablename__ = "port_traffic_target"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
device_id: Mapped[str] = mapped_column(String(64), default="", index=True)
series_id: Mapped[str] = mapped_column(String(64), default="", index=True)
source: Mapped[str] = mapped_column(String(32), default="managed", index=True)
target_id: Mapped[str] = mapped_column(String(128), index=True) # NE id
ne_name: Mapped[str] = mapped_column(String(256), default="")
ne_ip: Mapped[str] = mapped_column(String(128), default="")
vendor: Mapped[str] = mapped_column(String(64), default="")
ifname: Mapped[str] = mapped_column(String(128), default="")
if_description: Mapped[str] = mapped_column(String(512), default="")
bw_bps: Mapped[int] = mapped_column(BigInteger, default=0)
status: Mapped[str] = mapped_column(String(32), default="active", index=True) # active|disabled|retired
last_error: Mapped[str] = mapped_column(String(1024), default="")
last_sample_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
@property
def task_id(self) -> str:
return str(self.device_id or "")
@task_id.setter
def task_id(self, value: str) -> None:
self.device_id = str(value or "")
class PortTrafficSample(Base):
"""Time-series sample for a monitored interface."""
__tablename__ = "port_traffic_sample"
__table_args__ = (UniqueConstraint("target_row_id", "ts", name="uq_port_traffic_sample_ts"),)
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
target_row_id: Mapped[str] = mapped_column(String(64), index=True)
series_id: Mapped[str] = mapped_column(String(64), default="", index=True)
ts: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
in_bps: Mapped[float] = mapped_column(Float, default=0.0)
out_bps: Mapped[float] = mapped_column(Float, default=0.0)
in_util_pct: Mapped[float] = mapped_column(Float, default=0.0)
out_util_pct: Mapped[float] = mapped_column(Float, default=0.0)
bw_bps: Mapped[int] = mapped_column(BigInteger, default=0)
rate_period_sec: Mapped[int] = mapped_column(Integer, default=0)
raw_ok: Mapped[bool] = mapped_column(Boolean, default=True)
message: Mapped[str] = mapped_column(String(512), default="")
class PortTrafficEvent(Base):
"""Collect / ops log line for a monitored device (and optional interface)."""
__tablename__ = "port_traffic_event"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
device_id: Mapped[str] = mapped_column(String(64), default="", index=True)
target_row_id: Mapped[str] = mapped_column(String(64), default="", index=True)
ifname: Mapped[str] = mapped_column(String(128), default="")
level: Mapped[str] = mapped_column(String(16), default="error", index=True) # info|warn|error
message: Mapped[str] = mapped_column(Text, default="")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class PortTrafficBoard(Base):
"""Named multi-panel traffic ops wall (cutover / duty view)."""
__tablename__ = "port_traffic_board"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
name: Mapped[str] = mapped_column(String(256), default="", index=True)
remark: Mapped[str] = mapped_column(String(1024), default="")
cols: Mapped[int] = mapped_column(Integer, default=2)
created_by: Mapped[str] = mapped_column(String(64), default="")
updated_by: Mapped[str] = mapped_column(String(64), default="")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class PortTrafficPanel(Base):
"""One chart cell on a traffic board."""
__tablename__ = "port_traffic_panel"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
board_id: Mapped[str] = mapped_column(String(64), default="", index=True)
title: Mapped[str] = mapped_column(String(256), default="")
target_id: Mapped[str] = mapped_column(String(64), default="", index=True)
range_hours: Mapped[int] = mapped_column(Integer, default=24)
baseline: Mapped[str] = mapped_column(String(16), default="off") # off|day|week|shift|custom
offset_hours: Mapped[int] = mapped_column(Integer, default=0)
ahead_hours: Mapped[int] = mapped_column(Integer, default=1) # extend chart past "now" for baseline peek
baseline_target_id: Mapped[str] = mapped_column(String(64), default="")
y_mode: Mapped[str] = mapped_column(String(16), default="auto") # auto|current|util
ord: Mapped[int] = mapped_column(Integer, default=0)
col_span: Mapped[int] = mapped_column(Integer, default=1)
row_span: Mapped[int] = mapped_column(Integer, default=1)
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)

108
netx_api/models/__init__.py Normal file
View file

@ -0,0 +1,108 @@
"""ORM models package — domain modules with compatibility re-exports."""
from __future__ import annotations
from .alarms import (
AiAnalyzeHistory,
AlarmBatch,
AlarmNorm,
ImportErrorRow,
ImportJob,
)
from .auth import ApiToken, AppUser, AuditLog
from .config_sync import (
ConfigSyncCycle,
ConfigSyncPolicy,
ConfigSyncTask,
NeConfigHistory,
NeConfigSnapshot,
)
from .managed_ne import (
CliConnectProfile,
ManagedNE,
NeCollectionJob,
NeCollectionRun,
UmeCliOverride,
)
from .port_traffic import (
PortTrafficBoard,
PortTrafficDevice,
PortTrafficEvent,
PortTrafficPanel,
PortTrafficSample,
PortTrafficSeries,
PortTrafficTarget,
)
from .topology import (
LldpCollectPolicy,
TopoClassifyRule,
TopoDiscoverJob,
TopoDiscoverJobItem,
TopoFabricEdge,
TopoFabricNode,
TopoFabricStats,
TopoFolder,
TopoView,
TopoViewEdgeStyle,
TopoViewNode,
)
from .ume import (
UmeAlarmBatch,
UmeAlarmCurrent,
UmeAlarmHistory,
UmeAlarmSubscription,
UmeInventoryNE,
UmeKeyAlertForwardLog,
UmeKeyAlertMonitorConfig,
UmeKeyAlertRule,
UmeSyncJob,
UmeTokenCache,
)
__all__ = [
"AlarmBatch",
"AlarmNorm",
"ImportErrorRow",
"ImportJob",
"AiAnalyzeHistory",
"UmeSyncJob",
"UmeAlarmBatch",
"UmeInventoryNE",
"UmeAlarmCurrent",
"UmeAlarmHistory",
"UmeKeyAlertMonitorConfig",
"UmeKeyAlertRule",
"UmeKeyAlertForwardLog",
"UmeAlarmSubscription",
"UmeTokenCache",
"ManagedNE",
"CliConnectProfile",
"UmeCliOverride",
"NeCollectionJob",
"NeCollectionRun",
"TopoFabricNode",
"TopoClassifyRule",
"TopoFabricEdge",
"TopoFolder",
"TopoView",
"TopoViewNode",
"TopoViewEdgeStyle",
"LldpCollectPolicy",
"TopoDiscoverJob",
"TopoDiscoverJobItem",
"TopoFabricStats",
"AppUser",
"AuditLog",
"ApiToken",
"ConfigSyncPolicy",
"ConfigSyncCycle",
"ConfigSyncTask",
"NeConfigSnapshot",
"NeConfigHistory",
"PortTrafficDevice",
"PortTrafficSeries",
"PortTrafficTarget",
"PortTrafficSample",
"PortTrafficEvent",
"PortTrafficBoard",
"PortTrafficPanel",
]

View file

@ -0,0 +1,8 @@
"""Shared SQLAlchemy column types for model modules."""
from __future__ import annotations
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.types import JSON
# JSONB on Postgres; plain JSON elsewhere (unit tests / sqlite).
JsonType = JSON().with_variant(JSONB(), "postgresql")

103
netx_api/models/alarms.py Normal file
View file

@ -0,0 +1,103 @@
from __future__ import annotations
from datetime import datetime
from uuid import uuid4
from sqlalchemy import BigInteger, Boolean, DateTime, Float, ForeignKey, Integer, LargeBinary, String, Text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from ..db import Base
from ..timeutil import utcnow_naive
from ._types import JsonType as _JsonType
class AlarmBatch(Base):
__tablename__ = "alarm_batches"
batch_id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
source_file: Mapped[str] = mapped_column(String(512))
parser_version: Mapped[str] = mapped_column(String(64), default="zte_alarm_monitor_v1")
dict_version: Mapped[str] = mapped_column(String(64), default="v1")
total_rows: Mapped[int] = mapped_column(Integer, default=0)
success_rows: Mapped[int] = mapped_column(Integer, default=0)
failed_rows: Mapped[int] = mapped_column(Integer, default=0)
status: Mapped[str] = mapped_column(String(32), default="done")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
alarms: Mapped[list["AlarmNorm"]] = relationship(back_populates="batch")
errors: Mapped[list["ImportErrorRow"]] = relationship(back_populates="batch")
class AlarmNorm(Base):
__tablename__ = "alarms_norm"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
batch_id: Mapped[str] = mapped_column(ForeignKey("alarm_batches.batch_id"), index=True)
row_no: Mapped[int] = mapped_column(Integer)
alarm_time: Mapped[datetime] = mapped_column(DateTime, index=True)
clear_time: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
severity_raw: Mapped[str] = mapped_column(String(64), default="")
severity_norm: Mapped[str] = mapped_column(String(32), index=True, default="unknown")
ne_name: Mapped[str] = mapped_column(String(256), default="", index=True)
ne_id: Mapped[str] = mapped_column(String(128), default="", index=True)
site_name: Mapped[str] = mapped_column(String(256), default="")
alarm_code: Mapped[str] = mapped_column(String(256), default="", index=True)
# NOTE: historically we stored `alarm_name` separately, but for ZTE Alarm Monitor exports
# "Alarm Code Name" is a single column. We keep the DB column for backward compatibility,
# but APIs/UI are unified on `alarm_code`.
alarm_name: Mapped[str] = mapped_column(String(512), default="")
description: Mapped[str] = mapped_column(Text, default="")
ack_state: Mapped[str] = mapped_column(String(64), default="")
clear_state: Mapped[str] = mapped_column(String(64), default="")
relevancy: Mapped[str] = mapped_column(String(128), default="")
l3vpn_peer_ne: Mapped[str] = mapped_column(String(256), default="")
service: Mapped[str] = mapped_column(String(256), default="")
affected_client_service_number: Mapped[int] = mapped_column(Integer, default=0)
intermittence_count: Mapped[int] = mapped_column(Integer, default=0)
me_level: 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="gateway_export_excel")
source_file: Mapped[str] = mapped_column(String(512), default="")
raw_json: Mapped[str] = mapped_column(Text, default="{}")
batch: Mapped[AlarmBatch] = relationship(back_populates="alarms")
class ImportErrorRow(Base):
__tablename__ = "import_errors"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
batch_id: Mapped[str] = mapped_column(ForeignKey("alarm_batches.batch_id"), index=True)
row_no: Mapped[int] = mapped_column(Integer)
reason: Mapped[str] = mapped_column(String(512))
raw_json: Mapped[str] = mapped_column(Text, default="{}")
batch: Mapped[AlarmBatch] = relationship(back_populates="errors")
class ImportJob(Base):
__tablename__ = "import_jobs"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
kind: Mapped[str] = mapped_column(String(32), index=True) # alarms/logs/config
file_name: Mapped[str] = mapped_column(String(512), default="")
batch_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
ok: Mapped[int] = mapped_column(Integer, default=1) # 1 ok, 0 error
summary: Mapped[str] = mapped_column(String(1024), default="")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class AiAnalyzeHistory(Base):
__tablename__ = "ai_analyze_history"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
analysis_request_id: Mapped[str] = mapped_column(String(128), default="", index=True)
batch_id: Mapped[str] = mapped_column(String(64), default="", index=True)
question: Mapped[str] = mapped_column(Text, default="")
filters_json: Mapped[str] = mapped_column(Text, default="{}")
ok: Mapped[int] = mapped_column(Integer, default=1) # 1 ok, 0 error
answer: Mapped[str] = mapped_column(Text, default="")
error: Mapped[str] = mapped_column(Text, default="")
evidence_json: Mapped[str] = mapped_column(Text, default="{}")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)

63
netx_api/models/auth.py Normal file
View file

@ -0,0 +1,63 @@
from __future__ import annotations
from datetime import datetime
from uuid import uuid4
from sqlalchemy import BigInteger, Boolean, DateTime, Float, ForeignKey, Integer, LargeBinary, String, Text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from ..db import Base
from ..timeutil import utcnow_naive
from ._types import JsonType as _JsonType
class AppUser(Base):
"""Local netx application user (login account)."""
__tablename__ = "app_user"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
username: Mapped[str] = mapped_column(String(128), unique=True, index=True)
password_hash: Mapped[str] = mapped_column(String(255), default="")
role: Mapped[str] = mapped_column(String(32), default="user", index=True) # admin | user
# Optional capability override; empty => role defaults (see auth_scopes).
scopes: Mapped[list] = mapped_column(_JsonType, default=list)
is_active: Mapped[bool] = mapped_column(Boolean, default=True, index=True)
must_change_password: Mapped[bool] = mapped_column(Boolean, default=False)
created_by: Mapped[str] = mapped_column(String(64), default="")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
class AuditLog(Base):
"""Application audit trail for authenticated (and auth) actions."""
__tablename__ = "audit_log"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
ts: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
actor_user_id: Mapped[str] = mapped_column(String(64), default="", index=True)
actor_username: Mapped[str] = mapped_column(String(128), default="", index=True)
action: Mapped[str] = mapped_column(String(128), default="", index=True)
method: Mapped[str] = mapped_column(String(16), default="")
path: Mapped[str] = mapped_column(String(512), default="", index=True)
status_code: Mapped[int] = mapped_column(Integer, default=0)
client_ip: Mapped[str] = mapped_column(String(128), default="")
user_agent: Mapped[str] = mapped_column(String(512), default="")
detail: Mapped[dict] = mapped_column(_JsonType, default=dict)
class ApiToken(Base):
"""Long-lived API token (MCP/scripts); hashed at rest."""
__tablename__ = "api_token"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
name: Mapped[str] = mapped_column(String(128), default="")
token_hash: Mapped[str] = mapped_column(String(128), unique=True, index=True)
user_id: Mapped[str] = mapped_column(String(64), index=True)
# Capability subset; empty inherits owner user scopes (then intersected).
scopes: Mapped[list] = mapped_column(_JsonType, default=list)
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
expires_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, index=True)
last_used_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
revoked_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)

View file

@ -0,0 +1,117 @@
from __future__ import annotations
from datetime import datetime
from uuid import uuid4
from sqlalchemy import BigInteger, Boolean, DateTime, Float, ForeignKey, Integer, LargeBinary, String, Text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from ..db import Base
from ..timeutil import utcnow_naive
from ._types import JsonType as _JsonType
class ConfigSyncPolicy(Base):
"""Singleton policy for periodic config sync (id=1)."""
__tablename__ = "config_sync_policy"
id: Mapped[int] = mapped_column(Integer, primary_key=True, default=1)
enabled: Mapped[bool] = mapped_column(Boolean, default=False)
interval_days: Mapped[int] = mapped_column(Integer, default=3)
concurrency: Mapped[int] = mapped_column(Integer, default=5)
scope_mode: Mapped[str] = mapped_column(String(32), default="all") # all | selected
selected_targets: Mapped[list] = mapped_column(_JsonType, default=list)
history_keep: Mapped[int] = mapped_column(Integer, default=3)
# Finished sync cycles to retain (newest kept); active cycles always kept.
cycle_keep: Mapped[int] = mapped_column(Integer, default=30)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
class ConfigSyncCycle(Base):
"""One config-sync cycle over many NEs."""
__tablename__ = "config_sync_cycle"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
trigger_mode: Mapped[str] = mapped_column(String(32), default="schedule", index=True)
status: Mapped[str] = mapped_column(String(32), default="pending", index=True)
concurrency: Mapped[int] = mapped_column(Integer, default=5)
planned_count: Mapped[int] = mapped_column(Integer, default=0)
success_count: Mapped[int] = mapped_column(Integer, default=0)
fail_count: Mapped[int] = mapped_column(Integer, default=0)
skip_count: Mapped[int] = mapped_column(Integer, default=0)
error_message: Mapped[str] = mapped_column(String(1024), default="")
started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
ended_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class ConfigSyncTask(Base):
"""Per-NE work item inside a config sync cycle."""
__tablename__ = "config_sync_task"
__table_args__ = (UniqueConstraint("cycle_id", "source", "target_id", name="uq_config_sync_task_target"),)
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
cycle_id: Mapped[str] = mapped_column(String(64), index=True)
source: Mapped[str] = mapped_column(String(32), default="managed", index=True) # managed | ume
target_id: Mapped[str] = mapped_column(String(128), index=True)
ne_name: Mapped[str] = mapped_column(String(256), default="")
ne_ip: Mapped[str] = mapped_column(String(128), default="")
vendor: Mapped[str] = mapped_column(String(64), default="")
status: Mapped[str] = mapped_column(String(32), default="pending", index=True)
message: Mapped[str] = mapped_column(String(1024), default="")
started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
ended_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
class NeConfigSnapshot(Base):
"""Latest successful config snapshot per NE (zlib-compressed)."""
__tablename__ = "ne_config_snapshot"
source: Mapped[str] = mapped_column(String(32), primary_key=True) # managed | ume
target_id: Mapped[str] = mapped_column(String(128), primary_key=True)
vendor: Mapped[str] = mapped_column(String(64), default="")
device_type: Mapped[str] = mapped_column(String(64), default="")
ne_name: Mapped[str] = mapped_column(String(256), default="", index=True)
ne_ip: Mapped[str] = mapped_column(String(128), default="", index=True)
config_zlib: Mapped[bytes] = mapped_column(LargeBinary, default=b"")
config_alt_zlib: Mapped[bytes | None] = mapped_column(LargeBinary, nullable=True)
config_sha256: Mapped[str] = mapped_column(String(64), default="")
config_alt_sha256: Mapped[str] = mapped_column(String(64), default="")
plain_size: Mapped[int] = mapped_column(Integer, default=0)
plain_alt_size: Mapped[int] = mapped_column(Integer, default=0)
zlib_size: Mapped[int] = mapped_column(Integer, default=0)
zlib_alt_size: Mapped[int] = mapped_column(Integer, default=0)
commands_json: Mapped[list] = mapped_column(_JsonType, default=list)
collected_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
last_cycle_id: Mapped[str] = mapped_column(String(64), default="")
last_task_id: Mapped[str] = mapped_column(String(64), default="")
class NeConfigHistory(Base):
"""Historical config versions when content changes."""
__tablename__ = "ne_config_history"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
source: Mapped[str] = mapped_column(String(32), index=True)
target_id: Mapped[str] = mapped_column(String(128), index=True)
vendor: Mapped[str] = mapped_column(String(64), default="")
device_type: Mapped[str] = mapped_column(String(64), default="")
ne_name: Mapped[str] = mapped_column(String(256), default="")
ne_ip: Mapped[str] = mapped_column(String(128), default="")
config_zlib: Mapped[bytes] = mapped_column(LargeBinary, default=b"")
config_alt_zlib: Mapped[bytes | None] = mapped_column(LargeBinary, nullable=True)
config_sha256: Mapped[str] = mapped_column(String(64), default="")
config_alt_sha256: Mapped[str] = mapped_column(String(64), default="")
plain_size: Mapped[int] = mapped_column(Integer, default=0)
plain_alt_size: Mapped[int] = mapped_column(Integer, default=0)
zlib_size: Mapped[int] = mapped_column(Integer, default=0)
zlib_alt_size: Mapped[int] = mapped_column(Integer, default=0)
commands_json: Mapped[list] = mapped_column(_JsonType, default=list)
collected_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
cycle_id: Mapped[str] = mapped_column(String(64), default="")
task_id: Mapped[str] = mapped_column(String(64), default="")

View file

@ -0,0 +1,138 @@
from __future__ import annotations
from datetime import datetime
from uuid import uuid4
from sqlalchemy import BigInteger, Boolean, DateTime, Float, ForeignKey, Integer, LargeBinary, String, Text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from ..db import Base
from ..timeutil import utcnow_naive
from ._types import JsonType as _JsonType
class ManagedNE(Base):
"""Locally managed network element (SSH/Telnet), independent of UME inventory."""
__tablename__ = "managed_ne"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
name: Mapped[str] = mapped_column(String(256), default="", index=True)
vendor: Mapped[str] = mapped_column(String(64), default="Other", index=True)
device_type: Mapped[str] = mapped_column(String(128), default="")
# Not unique: WebCRT sessions may share a host IP with distinct session names.
# Inventory create/update still enforces uniqueness in ne_service.
ip_address: Mapped[str] = mapped_column(String(128), index=True)
port: Mapped[int] = mapped_column(Integer, default=22)
protocol: Mapped[str] = mapped_column(String(16), default="ssh")
username: Mapped[str] = mapped_column(String(128), default="")
password_enc: Mapped[str] = mapped_column(Text, default="")
enable_secret_enc: Mapped[str] = mapped_column(Text, default="")
connect_status: Mapped[str] = mapped_column(String(32), default="unknown", index=True)
connect_message: Mapped[str] = mapped_column(String(512), default="")
connect_detail: Mapped[str] = mapped_column(Text, default="")
connect_tested_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
site: Mapped[str] = mapped_column(String(256), default="")
tags: Mapped[str] = mapped_column(String(512), default="")
remark: Mapped[str] = mapped_column(String(1024), default="")
source: Mapped[str] = mapped_column(String(64), default="", index=True)
source_ref: Mapped[str] = mapped_column(String(128), default="", index=True)
hop_enabled: Mapped[bool] = mapped_column(default=False)
hop_vendor: Mapped[str] = mapped_column(String(32), default="zte")
hop_host: Mapped[str] = mapped_column(String(128), default="")
hop_port: Mapped[int] = mapped_column(Integer, default=22)
hop_protocol: Mapped[str] = mapped_column(String(16), default="ssh")
hop_username: Mapped[str] = mapped_column(String(128), default="")
hop_password_enc: Mapped[str] = mapped_column(Text, default="")
hop_command_template: Mapped[str] = mapped_column(Text, default="")
hop_vrf: Mapped[str] = mapped_column(String(128), default="")
hop_target_auth_mode: Mapped[str] = mapped_column(String(32), default="bastion_managed")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class CliConnectProfile(Base):
"""Reusable SSH/Telnet + hop credentials for UME lazy CLI exec."""
__tablename__ = "cli_connect_profile"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
name: Mapped[str] = mapped_column(String(256), default="", index=True)
is_default: Mapped[bool] = mapped_column(default=False, index=True)
username: Mapped[str] = mapped_column(String(128), default="")
password_enc: Mapped[str] = mapped_column(Text, default="")
port: Mapped[int] = mapped_column(Integer, default=22)
protocol: Mapped[str] = mapped_column(String(16), default="ssh")
device_type_default: Mapped[str] = mapped_column(String(128), default="zte_zxros")
vendor_default: Mapped[str] = mapped_column(String(64), default="ZTE")
ne_type_rules: Mapped[str] = mapped_column(Text, default="")
hop_enabled: Mapped[bool] = mapped_column(default=False)
hop_vendor: Mapped[str] = mapped_column(String(32), default="zte")
hop_host: Mapped[str] = mapped_column(String(128), default="")
hop_port: Mapped[int] = mapped_column(Integer, default=22)
hop_protocol: Mapped[str] = mapped_column(String(16), default="ssh")
hop_username: Mapped[str] = mapped_column(String(128), default="")
hop_password_enc: Mapped[str] = mapped_column(Text, default="")
hop_command_template: Mapped[str] = mapped_column(Text, default="")
hop_vrf: Mapped[str] = mapped_column(String(128), default="")
hop_target_auth_mode: Mapped[str] = mapped_column(String(32), default="bastion_managed")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class UmeCliOverride(Base):
"""Per-UME NE CLI overrides and connect-test cache."""
__tablename__ = "ume_cli_override"
ume_ne_id: Mapped[str] = mapped_column(String(128), primary_key=True)
profile_id: Mapped[str | None] = mapped_column(
String(64), ForeignKey("cli_connect_profile.id", ondelete="SET NULL"), nullable=True
)
username_override: Mapped[str] = mapped_column(String(128), default="")
device_type_override: Mapped[str] = mapped_column(String(128), default="")
vendor_override: Mapped[str] = mapped_column(String(64), default="")
connect_status: Mapped[str] = mapped_column(String(32), default="unknown", index=True)
connect_message: Mapped[str] = mapped_column(String(512), default="")
connect_detail: Mapped[str] = mapped_column(Text, default="")
connect_tested_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class NeCollectionJob(Base):
"""Batch CLI collection job over managed NEs."""
__tablename__ = "ne_collection_job"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
title: Mapped[str] = mapped_column(String(256), default="")
commands: Mapped[str] = mapped_column(Text, default="")
status: Mapped[str] = mapped_column(String(32), default="pending", index=True)
ne_count: Mapped[int] = mapped_column(Integer, default=0)
success_count: Mapped[int] = mapped_column(Integer, default=0)
fail_count: Mapped[int] = mapped_column(Integer, default=0)
error_message: Mapped[str] = mapped_column(String(1024), default="")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
ended_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
last_run_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, index=True)
class NeCollectionRun(Base):
"""Per-NE execution within a collection job."""
__tablename__ = "ne_collection_run"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
job_id: Mapped[str] = mapped_column(String(64), index=True)
# managed_ne.id or ume_inventory_ne.ne_id
ne_id: Mapped[str] = mapped_column(String(128), index=True)
# managed | ume
ne_source: Mapped[str] = mapped_column(String(16), default="managed", index=True)
ne_name: Mapped[str] = mapped_column(String(256), default="")
ne_ip: Mapped[str] = mapped_column(String(128), default="")
status: Mapped[str] = mapped_column(String(32), default="pending", index=True)
message: Mapped[str] = mapped_column(String(1024), default="")
output_rel_path: Mapped[str] = mapped_column(String(1024), default="")
started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
ended_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)

View file

@ -0,0 +1,163 @@
from __future__ import annotations
from datetime import datetime
from uuid import uuid4
from sqlalchemy import BigInteger, Boolean, DateTime, Float, ForeignKey, Integer, LargeBinary, String, Text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from ..db import Base
from ..timeutil import utcnow_naive
from ._types import JsonType as _JsonType
class PortTrafficDevice(Base):
"""Per-NE port traffic monitoring config (device-centric)."""
__tablename__ = "port_traffic_device"
__table_args__ = (
UniqueConstraint("source", "ne_id", name="uq_port_traffic_device_ne"),
)
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
source: Mapped[str] = mapped_column(String(32), default="managed", index=True)
ne_id: Mapped[str] = mapped_column(String(128), default="", index=True)
ne_name: Mapped[str] = mapped_column(String(256), default="")
ne_ip: Mapped[str] = mapped_column(String(128), default="")
vendor: Mapped[str] = mapped_column(String(64), default="")
note: Mapped[str] = mapped_column(String(256), default="") # optional remark
status: Mapped[str] = mapped_column(String(32), default="draft", index=True) # draft|running|paused|stopped
interval_sec: Mapped[int] = mapped_column(Integer, default=60)
retention_days: Mapped[int] = mapped_column(Integer, default=7)
concurrency: Mapped[int] = mapped_column(Integer, default=1)
collect_running: Mapped[bool] = mapped_column(Boolean, default=False)
last_collect_started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
last_collect_ended_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
last_error: Mapped[str] = mapped_column(String(1024), default="")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
# Back-compat alias while callers migrate.
PortTrafficTask = PortTrafficDevice
class PortTrafficSeries(Base):
"""Logical port (business link) that survives physical NE/if replacement."""
__tablename__ = "port_traffic_series"
__table_args__ = (UniqueConstraint("device_id", "title", name="uq_port_traffic_series_title"),)
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
device_id: Mapped[str] = mapped_column(String(64), default="", index=True)
title: Mapped[str] = mapped_column(String(256), default="")
status: Mapped[str] = mapped_column(String(32), default="active", index=True) # active|disabled
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
@property
def task_id(self) -> str:
return str(self.device_id or "")
@task_id.setter
def task_id(self, value: str) -> None:
self.device_id = str(value or "")
class PortTrafficTarget(Base):
"""Monitored interface under a device monitoring config."""
__tablename__ = "port_traffic_target"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
device_id: Mapped[str] = mapped_column(String(64), default="", index=True)
series_id: Mapped[str] = mapped_column(String(64), default="", index=True)
source: Mapped[str] = mapped_column(String(32), default="managed", index=True)
target_id: Mapped[str] = mapped_column(String(128), index=True) # NE id
ne_name: Mapped[str] = mapped_column(String(256), default="")
ne_ip: Mapped[str] = mapped_column(String(128), default="")
vendor: Mapped[str] = mapped_column(String(64), default="")
ifname: Mapped[str] = mapped_column(String(128), default="")
if_description: Mapped[str] = mapped_column(String(512), default="")
bw_bps: Mapped[int] = mapped_column(BigInteger, default=0)
status: Mapped[str] = mapped_column(String(32), default="active", index=True) # active|disabled|retired
last_error: Mapped[str] = mapped_column(String(1024), default="")
last_sample_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
@property
def task_id(self) -> str:
return str(self.device_id or "")
@task_id.setter
def task_id(self, value: str) -> None:
self.device_id = str(value or "")
class PortTrafficSample(Base):
"""Time-series sample for a monitored interface."""
__tablename__ = "port_traffic_sample"
__table_args__ = (UniqueConstraint("target_row_id", "ts", name="uq_port_traffic_sample_ts"),)
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
target_row_id: Mapped[str] = mapped_column(String(64), index=True)
series_id: Mapped[str] = mapped_column(String(64), default="", index=True)
ts: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
in_bps: Mapped[float] = mapped_column(Float, default=0.0)
out_bps: Mapped[float] = mapped_column(Float, default=0.0)
in_util_pct: Mapped[float] = mapped_column(Float, default=0.0)
out_util_pct: Mapped[float] = mapped_column(Float, default=0.0)
bw_bps: Mapped[int] = mapped_column(BigInteger, default=0)
rate_period_sec: Mapped[int] = mapped_column(Integer, default=0)
raw_ok: Mapped[bool] = mapped_column(Boolean, default=True)
message: Mapped[str] = mapped_column(String(512), default="")
class PortTrafficEvent(Base):
"""Collect / ops log line for a monitored device (and optional interface)."""
__tablename__ = "port_traffic_event"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
device_id: Mapped[str] = mapped_column(String(64), default="", index=True)
target_row_id: Mapped[str] = mapped_column(String(64), default="", index=True)
ifname: Mapped[str] = mapped_column(String(128), default="")
level: Mapped[str] = mapped_column(String(16), default="error", index=True) # info|warn|error
message: Mapped[str] = mapped_column(Text, default="")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class PortTrafficBoard(Base):
"""Named multi-panel traffic ops wall (cutover / duty view)."""
__tablename__ = "port_traffic_board"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
name: Mapped[str] = mapped_column(String(256), default="", index=True)
remark: Mapped[str] = mapped_column(String(1024), default="")
cols: Mapped[int] = mapped_column(Integer, default=2)
created_by: Mapped[str] = mapped_column(String(64), default="")
updated_by: Mapped[str] = mapped_column(String(64), default="")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class PortTrafficPanel(Base):
"""One chart cell on a traffic board."""
__tablename__ = "port_traffic_panel"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
board_id: Mapped[str] = mapped_column(String(64), default="", index=True)
title: Mapped[str] = mapped_column(String(256), default="")
target_id: Mapped[str] = mapped_column(String(64), default="", index=True)
range_hours: Mapped[int] = mapped_column(Integer, default=24)
baseline: Mapped[str] = mapped_column(String(16), default="off") # off|day|week|shift|custom
offset_hours: Mapped[int] = mapped_column(Integer, default=0)
ahead_hours: Mapped[int] = mapped_column(Integer, default=1) # extend chart past "now" for baseline peek
baseline_target_id: Mapped[str] = mapped_column(String(64), default="")
y_mode: Mapped[str] = mapped_column(String(16), default="auto") # auto|current|util
ord: Mapped[int] = mapped_column(Integer, default=0)
col_span: Mapped[int] = mapped_column(Integer, default=1)
row_span: Mapped[int] = mapped_column(Integer, default=1)
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)

246
netx_api/models/topology.py Normal file
View file

@ -0,0 +1,246 @@
from __future__ import annotations
from datetime import datetime
from uuid import uuid4
from sqlalchemy import BigInteger, Boolean, DateTime, Float, ForeignKey, Integer, LargeBinary, String, Text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from ..db import Base
from ..timeutil import utcnow_naive
from ._types import JsonType as _JsonType
class TopoFabricNode(Base):
"""Global topology fabric node (inventory-aligned). Target scale ~50k."""
__tablename__ = "topo_fabric_node"
__table_args__ = (
UniqueConstraint("managed_ne_id", name="uq_topo_fabric_node_managed_ne_id"),
UniqueConstraint("ume_ne_id", name="uq_topo_fabric_node_ume_ne_id"),
)
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
managed_ne_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
ume_ne_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True)
name: Mapped[str] = mapped_column(String(256), default="", index=True)
ip: Mapped[str] = mapped_column(String(128), default="", index=True)
vendor: Mapped[str] = mapped_column(String(64), default="")
device_type: Mapped[str] = mapped_column(String(64), default="")
# Classify tags (regex rules / manual). role: core|aggregation|access|unknown|""
role: Mapped[str] = mapped_column(String(32), default="", index=True)
region_folder_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
# rule | manual | ""
role_source: Mapped[str] = mapped_column(String(16), default="")
region_source: Mapped[str] = mapped_column(String(16), default="")
attrs: Mapped[dict] = mapped_column(_JsonType, default=dict)
last_seen_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, index=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class TopoClassifyRule(Base):
"""Regex rules to assign fabric role or region from NE name/IP."""
__tablename__ = "topo_classify_rule"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
# role | region
scope: Mapped[str] = mapped_column(String(16), default="role", index=True)
name: Mapped[str] = mapped_column(String(256), default="")
pattern: Mapped[str] = mapped_column(String(512), default="")
# name | ip | name_ip
match_field: Mapped[str] = mapped_column(String(32), default="name")
priority: Mapped[int] = mapped_column(Integer, default=100, index=True)
enabled: Mapped[bool] = mapped_column(Boolean, default=True, index=True)
# role: {role}; region: {folder_id} or {region_name_from_group}
payload: Mapped[dict] = mapped_column(_JsonType, default=dict)
remark: Mapped[str] = mapped_column(String(512), default="")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class TopoFabricEdge(Base):
"""Global fabric link. Target scale ~1M; layer reserved for future BGP/tunnel/l2vpn."""
__tablename__ = "topo_fabric_edge"
__table_args__ = (
UniqueConstraint(
"layer",
"a_node_id",
"b_node_id",
"a_port",
"b_port",
name="uq_topo_fabric_edge_endpoints",
),
)
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
# physical | bgp | tunnel | l2vpn (P1 writes physical only)
layer: Mapped[str] = mapped_column(String(32), default="physical", index=True)
a_node_id: Mapped[str] = mapped_column(String(64), index=True)
b_node_id: Mapped[str] = mapped_column(String(64), index=True)
a_port: Mapped[str] = mapped_column(String(128), default="")
b_port: Mapped[str] = mapped_column(String(128), default="")
# lldp | manual | stale
source: Mapped[str] = mapped_column(String(32), default="lldp", index=True)
# active | stale
status: Mapped[str] = mapped_column(String(32), default="active", index=True)
attrs: Mapped[dict] = mapped_column(_JsonType, default=dict)
discovered_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
last_seen_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, index=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
class TopoFolder(Base):
"""Grouping node for topology tree (root / region). Not a canvas."""
__tablename__ = "topo_folder"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
parent_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
# root | region
kind: Mapped[str] = mapped_column(String(32), default="region", index=True)
name: Mapped[str] = mapped_column(String(256), default="", index=True)
sort_order: Mapped[int] = mapped_column(Integer, default=0)
is_system: Mapped[bool] = mapped_column(Boolean, default=False)
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class TopoView(Base):
"""Topology canvas under a site/region (physical or custom; flat siblings)."""
__tablename__ = "topo_view"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
folder_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
# Legacy nesting column; unused (always null after vendor-model migration).
parent_view_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
# physical | custom
kind: Mapped[str] = mapped_column(String(32), default="custom", index=True)
# Optional filter preset label (legacy core|aggregation|access); not a tree level.
role: Mapped[str] = mapped_column(String(32), default="core", index=True)
name: Mapped[str] = mapped_column(String(256), default="", index=True)
remark: Mapped[str] = mapped_column(String(1024), default="")
sort_order: Mapped[int] = mapped_column(Integer, default=0)
# { layer?, status?, membership?: {...} }
filter: Mapped[dict] = mapped_column(_JsonType, default=dict)
viewport: Mapped[dict] = mapped_column(_JsonType, default=dict)
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class TopoViewNode(Base):
"""Node placement on a view; no link facts."""
__tablename__ = "topo_view_node"
__table_args__ = (UniqueConstraint("view_id", "fabric_node_id", name="uq_topo_view_node"),)
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
view_id: Mapped[str] = mapped_column(String(64), index=True)
fabric_node_id: Mapped[str] = mapped_column(String(64), index=True)
x: Mapped[float] = mapped_column(Float, default=0.0)
y: Mapped[float] = mapped_column(Float, default=0.0)
label: Mapped[str] = mapped_column(String(256), default="")
locked: Mapped[bool] = mapped_column(Boolean, default=False)
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
class TopoViewEdgeStyle(Base):
"""Optional per-view edge style override."""
__tablename__ = "topo_view_edge_style"
__table_args__ = (UniqueConstraint("view_id", "fabric_edge_id", name="uq_topo_view_edge_style"),)
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
view_id: Mapped[str] = mapped_column(String(64), index=True)
fabric_edge_id: Mapped[str] = mapped_column(String(64), index=True)
stroke_color: Mapped[str] = mapped_column(String(32), default="")
stroke_width: Mapped[int] = mapped_column(Integer, default=0)
line_style: Mapped[str] = mapped_column(String(16), default="")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
class LldpCollectPolicy(Base):
"""Singleton policy for periodic fabric LLDP collect (id=1)."""
__tablename__ = "lldp_collect_policy"
id: Mapped[int] = mapped_column(Integer, primary_key=True, default=1)
enabled: Mapped[bool] = mapped_column(Boolean, default=False)
interval_days: Mapped[int] = mapped_column(Integer, default=1) # legacy; prefer interval_hours
interval_hours: Mapped[int] = mapped_column(Integer, default=24)
concurrency: Mapped[int] = mapped_column(Integer, default=4)
scope_mode: Mapped[str] = mapped_column(String(32), default="all") # all | selected
selected_targets: Mapped[list] = mapped_column(_JsonType, default=list)
auto_add_unmatched: Mapped[bool] = mapped_column(Boolean, default=True)
# Keep N finished discover jobs (items + raw_preview); 0 = keep none finished.
history_keep: Mapped[int] = mapped_column(Integer, default=30)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
class TopoDiscoverJob(Base):
"""Async LLDP discovery job over inventory / NE ids."""
__tablename__ = "topo_discover_job"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
# all_inventory | ne_ids
scope: Mapped[str] = mapped_column(String(32), default="ne_ids", index=True)
# manual | schedule | topology (ad-hoc from canvas)
trigger_mode: Mapped[str] = mapped_column(String(32), default="manual", index=True)
ne_ids_json: Mapped[list] = mapped_column(_JsonType, default=list)
status: Mapped[str] = mapped_column(String(32), default="pending", index=True)
total: Mapped[int] = mapped_column(Integer, default=0)
done: Mapped[int] = mapped_column(Integer, default=0)
edges_added: Mapped[int] = mapped_column(Integer, default=0)
edges_updated: Mapped[int] = mapped_column(Integer, default=0)
edges_stale: Mapped[int] = mapped_column(Integer, default=0)
error: Mapped[str] = mapped_column(String(1024), default="")
started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
ended_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class TopoDiscoverJobItem(Base):
"""Per-NE result row for a discover job."""
__tablename__ = "topo_discover_job_item"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
job_id: Mapped[str] = mapped_column(String(64), index=True)
ne_id: Mapped[str] = mapped_column(String(128), default="", index=True)
ume_ne_id: Mapped[str] = mapped_column(String(128), default="")
fabric_node_id: Mapped[str] = mapped_column(String(64), default="", index=True)
ne_name: Mapped[str] = mapped_column(String(256), default="")
ne_ip: Mapped[str] = mapped_column(String(128), default="")
ok: Mapped[bool] = mapped_column(Boolean, default=False)
command: Mapped[str] = mapped_column(String(256), default="")
neighbors: Mapped[int] = mapped_column(Integer, default=0)
edges_added: Mapped[int] = mapped_column(Integer, default=0)
edges_updated: Mapped[int] = mapped_column(Integer, default=0)
unmatched_count: Mapped[int] = mapped_column(Integer, default=0)
unmatched_json: Mapped[list] = mapped_column(_JsonType, default=list)
parser_key: Mapped[str] = mapped_column(String(64), default="")
parser_stub: Mapped[bool] = mapped_column(Boolean, default=False)
error: Mapped[str] = mapped_column(String(1024), default="")
raw_preview: Mapped[str] = mapped_column(Text, default="")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
class TopoFabricStats(Base):
"""Cached fabric counters for summary (avoid COUNT on 1M edges)."""
__tablename__ = "topo_fabric_stats"
id: Mapped[str] = mapped_column(String(32), primary_key=True, default="global")
node_count: Mapped[int] = mapped_column(Integer, default=0)
edge_count: Mapped[int] = mapped_column(Integer, default=0)
edge_active: Mapped[int] = mapped_column(Integer, default=0)
edge_stale: Mapped[int] = mapped_column(Integer, default=0)
last_discover_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)

173
netx_api/models/ume.py Normal file
View file

@ -0,0 +1,173 @@
from __future__ import annotations
from datetime import datetime
from uuid import uuid4
from sqlalchemy import BigInteger, Boolean, DateTime, Float, ForeignKey, Integer, LargeBinary, String, Text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from ..db import Base
from ..timeutil import utcnow_naive
from ._types import JsonType as _JsonType
class UmeSyncJob(Base):
__tablename__ = "ume_sync_jobs"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
domain: Mapped[str] = mapped_column(String(32), index=True, default="inventory")
status: Mapped[str] = mapped_column(String(32), index=True, default="running")
trigger_mode: Mapped[str] = mapped_column(String(32), default="manual")
started_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
ended_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
pulled_count: Mapped[int] = mapped_column(Integer, default=0)
inserted_count: Mapped[int] = mapped_column(Integer, default=0)
updated_count: Mapped[int] = mapped_column(Integer, default=0)
error_message: Mapped[str] = mapped_column(String(1024), default="")
details_json: Mapped[str] = mapped_column(Text, default="{}")
class UmeAlarmBatch(Base):
__tablename__ = "ume_alarm_batches"
batch_id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
kind: Mapped[str] = mapped_column(String(16), index=True, default="current") # current/history
status: Mapped[str] = mapped_column(String(32), default="done")
total_rows: Mapped[int] = mapped_column(Integer, default=0)
success_rows: Mapped[int] = mapped_column(Integer, default=0)
failed_rows: Mapped[int] = mapped_column(Integer, default=0)
started_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
ended_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
error_message: Mapped[str] = mapped_column(String(1024), default="")
raw_json: Mapped[str] = mapped_column(Text, default="{}")
class UmeInventoryNE(Base):
__tablename__ = "ume_inventory_ne"
ne_id: Mapped[str] = mapped_column(String(128), primary_key=True, comment="网元uuid")
ne_name: Mapped[str] = mapped_column(String(256), default="", index=True, comment="资源名称")
user_label: Mapped[str] = mapped_column(String(256), default="", index=True, comment="用户标签")
ip_address: Mapped[str] = mapped_column(String(128), default="", index=True, comment="网元IPv4地址")
ipv6_address: Mapped[str] = mapped_column(String(128), default="", index=True, comment="IPv6地址")
ne_type: Mapped[str] = mapped_column(String(128), default="", comment="网元类型")
device_level: Mapped[str] = mapped_column(String(64), default="", comment="网元层次")
host_name: Mapped[str] = mapped_column(String(256), default="", comment="主机名称")
location: Mapped[str] = mapped_column(String(512), default="")
hardware_version: Mapped[str] = mapped_column(String(128), default="", comment="硬件版本")
loopback: Mapped[str] = mapped_column(String(128), default="", comment="业务环回IP(IPv4)")
consistent_state: Mapped[str] = mapped_column(String(64), default="", comment="数据一致性状态")
interface_version: Mapped[str] = mapped_column(String(128), default="", comment="网元接口版本号")
mac: Mapped[str] = mapped_column(String(128), default="", comment="设备机架MAC地址")
admin_status: Mapped[str] = mapped_column(String(64), default="", comment="管理状态")
address_type: Mapped[str] = mapped_column(String(64), default="", comment="管理地址类型(1:IPv4,2:IPv6)")
connection_status: Mapped[str] = mapped_column(String(64), default="", comment="连接状态")
maintain_status: Mapped[str] = mapped_column(String(64), default="", comment="工程状态")
net_mask: Mapped[str] = mapped_column(String(128), default="", comment="管理IPv4掩码")
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", comment="网元提供商")
source_type: Mapped[str] = mapped_column(String(64), default="ume_restconf")
first_seen_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
last_seen_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
raw_json: Mapped[str] = mapped_column(Text, default="{}")
class UmeAlarmCurrent(Base):
__tablename__ = "ume_alarms_current"
alarm_key: Mapped[str] = mapped_column(Text, primary_key=True)
ne_id: Mapped[str] = mapped_column(String(128), default="", index=True)
host_name: Mapped[str] = mapped_column(String(256), default="", index=True, comment="网元主机名(同步时从inventory联表写入)")
object_name: Mapped[str] = mapped_column(Text, default="", index=True)
event_type: Mapped[str] = mapped_column(Text, default="")
native_probable_cause: Mapped[str] = mapped_column(Text, default="")
perceived_severity: Mapped[str] = mapped_column(Text, default="", index=True)
is_cleared: Mapped[str] = mapped_column(Text, default="", index=True)
time_created: Mapped[str] = mapped_column(Text, default="", index=True)
root_cause_alarm_indication: Mapped[str] = mapped_column(Text, default="")
notification_id: Mapped[str] = mapped_column(String(128), default="", index=True)
first_seen_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
last_seen_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
raw_json: Mapped[str] = mapped_column(Text, default="{}")
class UmeAlarmHistory(Base):
__tablename__ = "ume_alarms_history"
alarm_key: Mapped[str] = mapped_column(Text, primary_key=True)
ne_id: Mapped[str] = mapped_column(String(128), default="", index=True)
host_name: Mapped[str] = mapped_column(String(256), default="", index=True, comment="网元主机名(同步时从inventory联表写入)")
object_name: Mapped[str] = mapped_column(Text, default="", index=True)
event_type: Mapped[str] = mapped_column(Text, default="")
native_probable_cause: Mapped[str] = mapped_column(Text, default="")
perceived_severity: Mapped[str] = mapped_column(Text, default="", index=True)
is_cleared: Mapped[str] = mapped_column(Text, default="", index=True)
time_created: Mapped[str] = mapped_column(Text, default="", index=True)
root_cause_alarm_indication: Mapped[str] = mapped_column(Text, default="")
notification_id: Mapped[str] = mapped_column(String(128), default="", index=True)
first_seen_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
last_seen_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
raw_json: Mapped[str] = mapped_column(Text, default="{}")
class UmeKeyAlertMonitorConfig(Base):
"""Global monitor options for key alert forwarding (singleton row id=1)."""
__tablename__ = "ume_key_alert_monitor_config"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
forward_on_clear: Mapped[int] = mapped_column(Integer, default=0)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class UmeKeyAlertRule(Base):
"""Key alert rule matched by UME notificationId or alarm description keyword."""
__tablename__ = "ume_key_alert_rule"
notification_id: Mapped[str] = mapped_column(String(128), primary_key=True)
match_type: Mapped[str] = mapped_column(String(32), default="notification_id", index=True)
match_value: Mapped[str] = mapped_column(String(256), default="", index=True)
enabled: Mapped[int] = mapped_column(Integer, default=1)
forward_on_clear: Mapped[int] = mapped_column(Integer, default=0)
label: Mapped[str] = mapped_column(String(256), default="")
ne_types: Mapped[str] = mapped_column(Text, default="[]")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class UmeKeyAlertForwardLog(Base):
__tablename__ = "ume_key_alert_forward_log"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
alarm_key: Mapped[str] = mapped_column(Text, index=True)
action: Mapped[str] = mapped_column(String(32), default="", index=True)
rule_key: Mapped[str] = mapped_column(String(128), default="", index=True)
notification_id: Mapped[str] = mapped_column(String(128), default="", index=True)
forwarded_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
oclaw_ok: Mapped[int] = mapped_column(Integer, default=0)
error: Mapped[str] = mapped_column(String(512), default="")
class UmeAlarmSubscription(Base):
"""Persisted UME ALARM notification subscription (manual establish/cancel)."""
__tablename__ = "ume_alarm_subscription"
cache_key: Mapped[str] = mapped_column(String(64), primary_key=True, default="default")
subscription_id: Mapped[str] = mapped_column(String(128), default="", index=True)
wss_uri: Mapped[str] = mapped_column(Text, default="")
topic: Mapped[str] = mapped_column(String(64), default="ALARM")
established_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)
class UmeTokenCache(Base):
__tablename__ = "ume_token_cache"
cache_key: Mapped[str] = mapped_column(String(256), primary_key=True)
token: Mapped[str] = mapped_column(Text, default="")
expires_at_epoch_s: Mapped[int] = mapped_column(Integer, default=0)
lock_owner: Mapped[str] = mapped_column(String(128), default="", index=True)
lock_expires_at_epoch_s: Mapped[int] = mapped_column(Integer, default=0, index=True)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True)