mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 03:10:46 +08:00
Fix port traffic wall chart refresh and first-sample visibility.
Trigger collect on start, normalize sample time bounds, and keep the uPlot series updating live. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
efe8f58cd2
commit
eea4043848
9 changed files with 240 additions and 54 deletions
|
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||
|
||||
from datetime import datetime
|
||||
|
||||
from fastapi import APIRouter, Depends, Query, Request
|
||||
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query, Request
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from .auth_service import write_audit
|
||||
|
|
@ -56,10 +56,15 @@ def api_list_tasks(
|
|||
@router.post("/tasks")
|
||||
def api_create_task(
|
||||
body: PortTrafficTaskCreate,
|
||||
background_tasks: BackgroundTasks,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
out = create_task(db, body)
|
||||
if body.start_now and out.status == "running":
|
||||
from .port_traffic_runner import dispatch_collect
|
||||
|
||||
background_tasks.add_task(dispatch_collect, out.id)
|
||||
uid, uname = _actor(request)
|
||||
write_audit(
|
||||
db,
|
||||
|
|
@ -119,8 +124,16 @@ def api_delete_task(task_id: str, request: Request, db: Session = Depends(get_db
|
|||
|
||||
|
||||
@router.post("/tasks/{task_id}/start")
|
||||
def api_start_task(task_id: str, request: Request, db: Session = Depends(get_db)):
|
||||
def api_start_task(
|
||||
task_id: str,
|
||||
background_tasks: BackgroundTasks,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
out = set_task_status(db, task_id, "running")
|
||||
from .port_traffic_runner import dispatch_collect
|
||||
|
||||
background_tasks.add_task(dispatch_collect, task_id)
|
||||
uid, uname = _actor(request)
|
||||
write_audit(
|
||||
db,
|
||||
|
|
@ -135,6 +148,46 @@ def api_start_task(task_id: str, request: Request, db: Session = Depends(get_db)
|
|||
return out.model_dump()
|
||||
|
||||
|
||||
@router.post("/tasks/{task_id}/collect-now")
|
||||
def api_collect_now(
|
||||
task_id: str,
|
||||
background_tasks: BackgroundTasks,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
task = get_task(db, task_id)
|
||||
if task.status not in ("running", "paused", "draft", "stopped"):
|
||||
raise HTTPException(status_code=400, detail="invalid_status")
|
||||
# Force due: clear last end so claim accepts, ensure running for this round.
|
||||
from .models import PortTrafficTask
|
||||
from .port_traffic_runner import dispatch_collect
|
||||
|
||||
row = db.get(PortTrafficTask, task_id)
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="task_not_found")
|
||||
if bool(row.collect_running):
|
||||
return {"ok": True, "started": False, "reason": "already_collecting", **task.model_dump()}
|
||||
if str(row.status) != "running":
|
||||
row.status = "running"
|
||||
row.last_collect_ended_at = None
|
||||
row.updated_at = datetime.utcnow()
|
||||
db.commit()
|
||||
background_tasks.add_task(dispatch_collect, task_id)
|
||||
uid, uname = _actor(request)
|
||||
write_audit(
|
||||
db,
|
||||
action="port_traffic.task.collect_now",
|
||||
actor_user_id=uid,
|
||||
actor_username=uname,
|
||||
method="POST",
|
||||
path=f"/v1/port-traffic/tasks/{task_id}/collect-now",
|
||||
status_code=200,
|
||||
detail={"id": task_id},
|
||||
)
|
||||
out = get_task(db, task_id)
|
||||
return {"ok": True, "started": True, **out.model_dump()}
|
||||
|
||||
|
||||
@router.post("/tasks/{task_id}/pause")
|
||||
def api_pause_task(task_id: str, request: Request, db: Session = Depends(get_db)):
|
||||
out = set_task_status(db, task_id, "paused")
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Any
|
||||
from uuid import uuid4
|
||||
|
||||
|
|
@ -204,11 +204,10 @@ def set_task_status(db: Session, task_id: str, status: str) -> PortTrafficTaskOu
|
|||
)
|
||||
if active <= 0:
|
||||
raise HTTPException(status_code=400, detail="no_active_targets")
|
||||
# Allow scheduler/collect-now to fire immediately after start/resume.
|
||||
task.last_collect_ended_at = None
|
||||
task.status = status
|
||||
task.updated_at = _utcnow()
|
||||
if status in ("stopped", "paused"):
|
||||
# leave collect_running for runner to finish; recovery clears stuck flags
|
||||
pass
|
||||
db.commit()
|
||||
db.refresh(task)
|
||||
return _task_out(db, task)
|
||||
|
|
@ -308,6 +307,15 @@ def discover_ports(db: Session, body: DiscoverPortsRequest) -> DiscoverPortsResp
|
|||
)
|
||||
|
||||
|
||||
def _as_naive_utc(value: datetime | None) -> datetime | None:
|
||||
"""Normalize query bounds to naive UTC (DB columns are naive utcnow)."""
|
||||
if value is None:
|
||||
return None
|
||||
if value.tzinfo is None:
|
||||
return value
|
||||
return value.astimezone(timezone.utc).replace(tzinfo=None)
|
||||
|
||||
|
||||
def get_samples(
|
||||
db: Session,
|
||||
*,
|
||||
|
|
@ -319,10 +327,10 @@ def get_samples(
|
|||
if not target:
|
||||
raise HTTPException(status_code=404, detail="target_not_found")
|
||||
now = _utcnow()
|
||||
if to_ts is None:
|
||||
to_ts = now
|
||||
if from_ts is None:
|
||||
from_ts = to_ts - timedelta(hours=1)
|
||||
to_ts = _as_naive_utc(to_ts) or now
|
||||
from_ts = _as_naive_utc(from_ts) or (to_ts - timedelta(hours=1))
|
||||
# Slight skew so just-written samples are not clipped by client clock.
|
||||
to_ts = to_ts + timedelta(seconds=5)
|
||||
rows = (
|
||||
db.query(PortTrafficSample)
|
||||
.filter(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue