Fix biz_state status while collecting and improve raw log export UX.

Show collecting over paused schedule, enqueue sync so collect_running flips immediately, and expose export on metric sheets with a card layout for Commands.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-23 11:19:01 +08:00
parent 05357e971e
commit a1acd4c2af
7 changed files with 224 additions and 38 deletions

View file

@ -563,33 +563,48 @@ def _finish_task(task_id: str, *, error: str = "") -> None:
db.close()
def dispatch_collect(task_id: str, *, manual: bool = False) -> None:
def dispatch_collect(task_id: str, *, manual: bool = False) -> dict[str, Any]:
"""Enqueue a collect round; run inline when this process owns execution.
Dedicated worker mode: only enqueue (claim loop runs the batch).
Inline / non-dedicated: enqueue then atomically promote+execute (skip if
another worker already claimed the batch).
Returns the enqueue result dict (ok / queued / reason / batch_id / …).
"""
from .claim import enqueue_collect
result = enqueue_collect(task_id, manual=manual)
if not result.get("queued"):
return
return result
batch_id = str(result.get("batch_id") or "")
if not batch_id:
return result
execute_enqueued_batch(
batch_id=batch_id,
task_id=str(result.get("task_id") or task_id),
)
return result
def execute_enqueued_batch(*, batch_id: str, task_id: str) -> None:
"""Promote+run a queued batch when this process owns inline execution."""
bid = str(batch_id or "").strip()
if not bid:
return
if _should_execute_inline():
# Atomic queued→running; if false, scheduler/worker already owns it.
if not _try_claim_batch_for_execute(batch_id):
return
execute_claimed_batch(
batch_id=batch_id,
task_id=str(result.get("task_id") or task_id),
source="",
ne_id="",
vendor="",
device_type="",
)
if not _should_execute_inline():
return
# Atomic queued→running; if false, scheduler/worker already owns it.
if not _try_claim_batch_for_execute(bid):
return
execute_claimed_batch(
batch_id=bid,
task_id=str(task_id or ""),
source="",
ne_id="",
vendor="",
device_type="",
)
def _should_execute_inline() -> bool:

View file

@ -11,7 +11,7 @@ from sqlalchemy.orm import Session
from .db import get_db
from .biz_state import service as svc
from .biz_state.collect_runner import dispatch_collect
from .biz_state.collect_runner import execute_enqueued_batch
from .lldp_shared import resolve_vendor_key
from .models import BizStateTask
@ -246,10 +246,32 @@ def api_collect_now(
if bool(task.collect_running):
return {"ok": True, "started": False, "reason": "already_collecting", "task_id": task_id}
tid = task_id
# Enqueue (and run inline only when this process owns collectors).
# Dedicated worker mode: BackgroundTasks only creates queued batch; workers claim.
background_tasks.add_task(lambda: dispatch_collect(tid, manual=True))
return {"ok": True, "started": True, "queued": True, "task_id": task_id}
# Enqueue synchronously so collect_running flips before the HTTP response
# (schedule may stay paused; manual collect is still allowed).
from .biz_state.claim import enqueue_collect
result = enqueue_collect(tid, manual=True)
if not result.get("queued"):
return {
"ok": bool(result.get("ok", False)),
"started": False,
"queued": False,
"reason": result.get("reason") or "enqueue_failed",
"task_id": tid,
}
batch_id = str(result.get("batch_id") or "")
if batch_id:
background_tasks.add_task(
lambda bid=batch_id, t=tid: execute_enqueued_batch(batch_id=bid, task_id=t)
)
return {
"ok": True,
"started": True,
"queued": True,
"batch_id": batch_id,
"task_id": tid,
"collect_running": True,
}
@router.post("/tasks/{task_id}/collect/stop")