netx/netx_api/managed_ne_router.py
oliver 778442dc57 feat(ne): batch delete selected managed network elements
Add POST /v1/managed-ne/batch-delete and toolbar button with confirmation on NE management page.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-28 19:02:16 +08:00

116 lines
3.8 KiB
Python

from __future__ import annotations
from fastapi import APIRouter, Depends, File, HTTPException, Query, UploadFile
from fastapi.responses import Response
from sqlalchemy.orm import Session
from .db import get_db
from .device_types import SUPPORTED_DEVICE_TYPES, SUPPORTED_VENDORS
from .ne_connect import schedule_connect_tests
from .ne_crypto import credentials_configured
from .ne_schemas import BatchHopApplyRequest, ConnectTestRequest, ManagedNeCreate, ManagedNeUpdate
from .ne_service import (
batch_apply_hop_proxy,
build_managed_ne_import_template,
create_managed_ne,
batch_delete_managed_ne,
delete_managed_ne,
get_managed_ne,
import_managed_ne,
list_managed_ne,
update_managed_ne,
)
from .models import ManagedNE
router = APIRouter(prefix="/v1/managed-ne", tags=["managed-ne"])
@router.get("")
def api_list_managed_ne(
keyword: str | None = Query(default=None),
vendor: str | None = Query(default=None),
connect_status: str | None = Query(default=None),
page: int = Query(default=1, ge=1),
page_size: int = Query(default=50, ge=1, le=500),
db: Session = Depends(get_db),
):
return list_managed_ne(
db,
keyword=keyword,
vendor=vendor,
connect_status=connect_status,
page=page,
page_size=page_size,
)
@router.get("/meta/device-types")
def api_device_types():
return {"device_types": list(SUPPORTED_DEVICE_TYPES), "vendors": list(SUPPORTED_VENDORS)}
@router.get("/meta/credentials-configured")
def api_credentials_configured():
return {"configured": credentials_configured()}
@router.post("")
def api_create_managed_ne(body: ManagedNeCreate, db: Session = Depends(get_db)):
return create_managed_ne(db, body).model_dump()
@router.get("/{ne_id}")
def api_get_managed_ne(ne_id: str, db: Session = Depends(get_db)):
return get_managed_ne(db, ne_id).model_dump()
@router.patch("/{ne_id}")
def api_update_managed_ne(ne_id: str, body: ManagedNeUpdate, db: Session = Depends(get_db)):
return update_managed_ne(db, ne_id, body).model_dump()
@router.delete("/{ne_id}")
def api_delete_managed_ne(ne_id: str, db: Session = Depends(get_db)):
return delete_managed_ne(db, ne_id)
@router.get("/import/template")
def api_managed_ne_import_template(format: str = Query(default="xlsx")):
filename, payload, media_type = build_managed_ne_import_template(format)
return Response(
content=payload,
media_type=media_type,
headers={"content-disposition": f'attachment; filename="{filename}"'},
)
@router.post("/import")
async def api_import_managed_ne(file: UploadFile = File(...), db: Session = Depends(get_db)):
content = await file.read()
if not content:
raise HTTPException(status_code=400, detail="empty_file")
return import_managed_ne(db, content, file.filename or "import.xlsx").model_dump()
@router.post("/batch-hop")
def api_batch_apply_hop(body: BatchHopApplyRequest, db: Session = Depends(get_db)):
return batch_apply_hop_proxy(db, body.ids, body.hop)
@router.post("/batch-delete")
def api_batch_delete_managed_ne(body: ConnectTestRequest, db: Session = Depends(get_db)):
return batch_delete_managed_ne(db, body.ids)
@router.post("/connect-test")
def api_connect_test(body: ConnectTestRequest, db: Session = Depends(get_db)):
ids = [str(x).strip() for x in body.ids if str(x).strip()]
if not ids:
raise HTTPException(status_code=400, detail="ids_required")
rows = db.query(ManagedNE).filter(ManagedNE.id.in_(ids)).all()
found_ids = {str(r.id) for r in rows}
missing = [x for x in ids if x not in found_ids]
if missing:
raise HTTPException(status_code=404, detail=f"managed_ne_not_found: {','.join(missing[:5])}")
submitted = schedule_connect_tests(ids)
return {"ok": True, "submitted": submitted}