mirror of
https://github.com/hansjone/netx.git
synced 2026-10-08 23:33:21 +08:00
Topology MCP adds pull/compact/bundle/suggest-hubs with a no-template skill path; API gains fabric level and NE collection policy; web list pages get paging and denser collect/network workflows. Co-authored-by: Cursor <cursoragent@cursor.com>
114 lines
4.1 KiB
Python
114 lines
4.1 KiB
Python
"""Topology classify rule CRUD."""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
from uuid import uuid4
|
|
|
|
from fastapi import HTTPException
|
|
from sqlalchemy.orm import Session
|
|
|
|
from .models import TopoClassifyRule, TopoFolder
|
|
from .topology_classify_common import (
|
|
_MATCH_FIELDS,
|
|
_MAX_PATTERN_LEN,
|
|
_SCOPES,
|
|
_compile_pattern,
|
|
_normalize_scope,
|
|
_rule_out,
|
|
_utcnow,
|
|
_validate_payload,
|
|
)
|
|
from .topology_schemas import ClassifyRuleCreate, ClassifyRuleOut, ClassifyRuleUpdate
|
|
|
|
def list_rules(db: Session, *, scope: str = "") -> list[ClassifyRuleOut]:
|
|
q = db.query(TopoClassifyRule)
|
|
scope_f = str(scope or "").strip().lower()
|
|
if scope_f:
|
|
scope_n = _normalize_scope(scope_f)
|
|
if scope_n == "level":
|
|
q = q.filter(TopoClassifyRule.scope.in_(("level", "role")))
|
|
else:
|
|
q = q.filter(TopoClassifyRule.scope == scope_n)
|
|
rows = q.order_by(
|
|
TopoClassifyRule.scope.asc(),
|
|
TopoClassifyRule.priority.asc(),
|
|
TopoClassifyRule.name.asc(),
|
|
).all()
|
|
return [_rule_out(r) for r in rows]
|
|
|
|
|
|
def create_rule(db: Session, body: ClassifyRuleCreate) -> ClassifyRuleOut:
|
|
scope_raw = str(body.scope or "").strip().lower()
|
|
if scope_raw not in _SCOPES:
|
|
raise HTTPException(status_code=400, detail="scope_invalid")
|
|
scope = _normalize_scope(scope_raw)
|
|
match_field = str(body.match_field or "name").strip().lower()
|
|
if match_field not in _MATCH_FIELDS:
|
|
raise HTTPException(status_code=400, detail="match_field_invalid")
|
|
_compile_pattern(body.pattern)
|
|
payload = _validate_payload(scope, dict(body.payload or {}))
|
|
if scope == "region" and payload.get("folder_id"):
|
|
folder = db.get(TopoFolder, payload["folder_id"])
|
|
if folder is None or str(folder.kind or "") != "region":
|
|
raise HTTPException(status_code=400, detail="folder_not_found")
|
|
now = _utcnow()
|
|
row = TopoClassifyRule(
|
|
id=uuid4().hex,
|
|
scope=scope,
|
|
name=str(body.name or "").strip()[:256] or f"{scope}-rule",
|
|
pattern=str(body.pattern or "").strip()[:_MAX_PATTERN_LEN],
|
|
match_field=match_field,
|
|
priority=int(body.priority if body.priority is not None else 100),
|
|
enabled=bool(body.enabled if body.enabled is not None else True),
|
|
payload=payload,
|
|
remark=str(body.remark or "")[:512],
|
|
created_at=now,
|
|
updated_at=now,
|
|
)
|
|
db.add(row)
|
|
db.commit()
|
|
db.refresh(row)
|
|
return _rule_out(row)
|
|
|
|
|
|
def update_rule(db: Session, rule_id: str, body: ClassifyRuleUpdate) -> ClassifyRuleOut:
|
|
row = db.get(TopoClassifyRule, rule_id)
|
|
if row is None:
|
|
raise HTTPException(status_code=404, detail="rule_not_found")
|
|
if body.name is not None:
|
|
row.name = str(body.name or "").strip()[:256]
|
|
if body.pattern is not None:
|
|
_compile_pattern(body.pattern)
|
|
row.pattern = str(body.pattern or "").strip()[:_MAX_PATTERN_LEN]
|
|
if body.match_field is not None:
|
|
mf = str(body.match_field or "name").strip().lower()
|
|
if mf not in _MATCH_FIELDS:
|
|
raise HTTPException(status_code=400, detail="match_field_invalid")
|
|
row.match_field = mf
|
|
if body.priority is not None:
|
|
row.priority = int(body.priority)
|
|
if body.enabled is not None:
|
|
row.enabled = bool(body.enabled)
|
|
if body.remark is not None:
|
|
row.remark = str(body.remark or "")[:512]
|
|
if body.payload is not None:
|
|
row.payload = _validate_payload(str(row.scope or "role"), dict(body.payload or {}))
|
|
if str(row.scope) == "region" and row.payload.get("folder_id"):
|
|
folder = db.get(TopoFolder, row.payload["folder_id"])
|
|
if folder is None or str(folder.kind or "") != "region":
|
|
raise HTTPException(status_code=400, detail="folder_not_found")
|
|
row.updated_at = _utcnow()
|
|
db.commit()
|
|
db.refresh(row)
|
|
return _rule_out(row)
|
|
|
|
|
|
def delete_rule(db: Session, rule_id: str) -> dict[str, Any]:
|
|
row = db.get(TopoClassifyRule, rule_id)
|
|
if row is None:
|
|
raise HTTPException(status_code=404, detail="rule_not_found")
|
|
db.delete(row)
|
|
db.commit()
|
|
return {"ok": True, "id": rule_id, "deleted": True}
|
|
|
|
|