mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 02:00:46 +08:00
Add collect dashboard KPIs and create-job modal.
Align collection tasks with LLDP/config-sync chrome, and refresh active-task overview when jobs change. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
0100fb8430
commit
6642f4b8b5
11 changed files with 502 additions and 249 deletions
|
|
@ -8,6 +8,7 @@ from .collection_service import (
|
|||
build_collection_job_zip,
|
||||
create_collection,
|
||||
delete_collection_job,
|
||||
get_collection_dashboard,
|
||||
get_collection_job,
|
||||
list_collection_jobs,
|
||||
list_collection_runs,
|
||||
|
|
@ -36,6 +37,11 @@ def api_eligible_ne(
|
|||
return list_eligible_ne(db, page=page, page_size=page_size, keyword=keyword)
|
||||
|
||||
|
||||
@router.get("/dashboard")
|
||||
def api_collection_dashboard(db: Session = Depends(get_db)):
|
||||
return get_collection_dashboard(db).model_dump()
|
||||
|
||||
|
||||
@router.post("")
|
||||
def api_create_collection(body: CollectionJobCreate, db: Session = Depends(get_db)):
|
||||
return create_collection(db, body).model_dump()
|
||||
|
|
|
|||
|
|
@ -41,3 +41,23 @@ class CollectionJobOut(BaseModel):
|
|||
started_at: datetime | None
|
||||
ended_at: datetime | None
|
||||
last_run_at: datetime | None = None
|
||||
|
||||
|
||||
class CollectionJobSummary(BaseModel):
|
||||
id: str
|
||||
title: str
|
||||
status: str
|
||||
ne_count: int
|
||||
success_count: int
|
||||
fail_count: int
|
||||
created_at: datetime | None = None
|
||||
started_at: datetime | None = None
|
||||
ended_at: datetime | None = None
|
||||
last_run_at: datetime | None = None
|
||||
|
||||
|
||||
class CollectionDashboardOut(BaseModel):
|
||||
job_count: int = 0
|
||||
active_count: int = 0
|
||||
running_job: CollectionJobSummary | None = None
|
||||
last_job: CollectionJobSummary | None = None
|
||||
|
|
|
|||
|
|
@ -20,7 +20,13 @@ from .collection_job_state import (
|
|||
sync_job_progress,
|
||||
_sync_job_counts,
|
||||
)
|
||||
from .collection_schemas import CollectionJobCreate, CollectionJobOut, CollectionRunOut
|
||||
from .collection_schemas import (
|
||||
CollectionDashboardOut,
|
||||
CollectionJobCreate,
|
||||
CollectionJobOut,
|
||||
CollectionJobSummary,
|
||||
CollectionRunOut,
|
||||
)
|
||||
from .ne_collection_paths import clear_run_output_files, collection_data_root
|
||||
|
||||
_log = logging.getLogger("netx.collection")
|
||||
|
|
@ -94,6 +100,76 @@ def job_to_out(row: NeCollectionJob, *, output_count: int | None = None) -> Coll
|
|||
)
|
||||
|
||||
|
||||
def job_to_summary(row: NeCollectionJob | None) -> CollectionJobSummary | None:
|
||||
if row is None:
|
||||
return None
|
||||
return CollectionJobSummary(
|
||||
id=str(row.id),
|
||||
title=str(row.title or "").strip() or str(row.id)[:8],
|
||||
status=str(row.status or "pending"),
|
||||
ne_count=int(row.ne_count or 0),
|
||||
success_count=int(row.success_count or 0),
|
||||
fail_count=int(row.fail_count or 0),
|
||||
created_at=row.created_at,
|
||||
started_at=row.started_at,
|
||||
ended_at=row.ended_at,
|
||||
last_run_at=row.last_run_at,
|
||||
)
|
||||
|
||||
|
||||
def has_active_collection_job(db: Session) -> NeCollectionJob | None:
|
||||
return (
|
||||
db.query(NeCollectionJob)
|
||||
.filter(NeCollectionJob.status.in_(("pending", "running", "paused")))
|
||||
.order_by(NeCollectionJob.created_at.desc())
|
||||
.first()
|
||||
)
|
||||
|
||||
|
||||
def last_finished_collection_job(db: Session) -> NeCollectionJob | None:
|
||||
return (
|
||||
db.query(NeCollectionJob)
|
||||
.filter(NeCollectionJob.status.in_(("done", "failed")))
|
||||
.order_by(NeCollectionJob.created_at.desc())
|
||||
.first()
|
||||
)
|
||||
|
||||
|
||||
def get_collection_dashboard(db: Session) -> CollectionDashboardOut:
|
||||
job_count = int(db.query(NeCollectionJob).count() or 0)
|
||||
active_count = int(
|
||||
db.query(NeCollectionJob)
|
||||
.filter(NeCollectionJob.status.in_(("pending", "running", "paused")))
|
||||
.count()
|
||||
or 0
|
||||
)
|
||||
running = has_active_collection_job(db)
|
||||
if running is not None:
|
||||
jid = str(running.id)
|
||||
if str(running.status or "") not in ("done", "failed", "paused"):
|
||||
reconcile_stale_collection_job(db, jid)
|
||||
db.refresh(running)
|
||||
if str(running.status or "") == "running":
|
||||
sync_job_progress(db, jid)
|
||||
finalize_collection_job(db, jid)
|
||||
db.refresh(running)
|
||||
if str(running.status or "") not in ("pending", "running", "paused"):
|
||||
running = has_active_collection_job(db)
|
||||
active_count = int(
|
||||
db.query(NeCollectionJob)
|
||||
.filter(NeCollectionJob.status.in_(("pending", "running", "paused")))
|
||||
.count()
|
||||
or 0
|
||||
)
|
||||
last = last_finished_collection_job(db)
|
||||
return CollectionDashboardOut(
|
||||
job_count=job_count,
|
||||
active_count=active_count,
|
||||
running_job=job_to_summary(running),
|
||||
last_job=job_to_summary(last),
|
||||
)
|
||||
|
||||
|
||||
def run_to_out(row: NeCollectionRun) -> CollectionRunOut:
|
||||
rel = str(row.output_rel_path or "").strip()
|
||||
return CollectionRunOut(
|
||||
|
|
|
|||
|
|
@ -185,6 +185,7 @@ def _collection_items(db: Session, actors: dict[str, str]) -> list[dict[str, Any
|
|||
ok = int(row.success_count or 0)
|
||||
fail = int(row.fail_count or 0)
|
||||
total = int(row.ne_count or 0)
|
||||
done = ok + fail
|
||||
items.append(
|
||||
_item(
|
||||
kind="ne_collect",
|
||||
|
|
@ -195,7 +196,7 @@ def _collection_items(db: Session, actors: dict[str, str]) -> list[dict[str, Any
|
|||
actor=actor,
|
||||
started_at=row.started_at or row.created_at,
|
||||
updated_at=row.last_run_at or row.ended_at or row.started_at or row.created_at,
|
||||
progress=f"{ok}/{fail}/{total}",
|
||||
progress=f"{done}/{total}",
|
||||
inflight=int(running),
|
||||
detail=str(row.error_message or "")[:240],
|
||||
href="/network/tasks/collect",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue