mirror of
https://github.com/hansjone/netx.git
synced 2026-10-10 13:50:44 +08:00
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:
parent
05357e971e
commit
a1acd4c2af
7 changed files with 224 additions and 38 deletions
|
|
@ -563,33 +563,48 @@ def _finish_task(task_id: str, *, error: str = "") -> None:
|
||||||
db.close()
|
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.
|
"""Enqueue a collect round; run inline when this process owns execution.
|
||||||
|
|
||||||
Dedicated worker mode: only enqueue (claim loop runs the batch).
|
Dedicated worker mode: only enqueue (claim loop runs the batch).
|
||||||
Inline / non-dedicated: enqueue then atomically promote+execute (skip if
|
Inline / non-dedicated: enqueue then atomically promote+execute (skip if
|
||||||
another worker already claimed the batch).
|
another worker already claimed the batch).
|
||||||
|
|
||||||
|
Returns the enqueue result dict (ok / queued / reason / batch_id / …).
|
||||||
"""
|
"""
|
||||||
from .claim import enqueue_collect
|
from .claim import enqueue_collect
|
||||||
|
|
||||||
result = enqueue_collect(task_id, manual=manual)
|
result = enqueue_collect(task_id, manual=manual)
|
||||||
if not result.get("queued"):
|
if not result.get("queued"):
|
||||||
return
|
return result
|
||||||
batch_id = str(result.get("batch_id") or "")
|
batch_id = str(result.get("batch_id") or "")
|
||||||
if not batch_id:
|
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
|
return
|
||||||
if _should_execute_inline():
|
if not _should_execute_inline():
|
||||||
# Atomic queued→running; if false, scheduler/worker already owns it.
|
return
|
||||||
if not _try_claim_batch_for_execute(batch_id):
|
# Atomic queued→running; if false, scheduler/worker already owns it.
|
||||||
return
|
if not _try_claim_batch_for_execute(bid):
|
||||||
execute_claimed_batch(
|
return
|
||||||
batch_id=batch_id,
|
execute_claimed_batch(
|
||||||
task_id=str(result.get("task_id") or task_id),
|
batch_id=bid,
|
||||||
source="",
|
task_id=str(task_id or ""),
|
||||||
ne_id="",
|
source="",
|
||||||
vendor="",
|
ne_id="",
|
||||||
device_type="",
|
vendor="",
|
||||||
)
|
device_type="",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _should_execute_inline() -> bool:
|
def _should_execute_inline() -> bool:
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from .db import get_db
|
from .db import get_db
|
||||||
from .biz_state import service as svc
|
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 .lldp_shared import resolve_vendor_key
|
||||||
from .models import BizStateTask
|
from .models import BizStateTask
|
||||||
|
|
||||||
|
|
@ -246,10 +246,32 @@ def api_collect_now(
|
||||||
if bool(task.collect_running):
|
if bool(task.collect_running):
|
||||||
return {"ok": True, "started": False, "reason": "already_collecting", "task_id": task_id}
|
return {"ok": True, "started": False, "reason": "already_collecting", "task_id": task_id}
|
||||||
tid = task_id
|
tid = task_id
|
||||||
# Enqueue (and run inline only when this process owns collectors).
|
# Enqueue synchronously so collect_running flips before the HTTP response
|
||||||
# Dedicated worker mode: BackgroundTasks only creates queued batch; workers claim.
|
# (schedule may stay paused; manual collect is still allowed).
|
||||||
background_tasks.add_task(lambda: dispatch_collect(tid, manual=True))
|
from .biz_state.claim import enqueue_collect
|
||||||
return {"ok": True, "started": True, "queued": True, "task_id": task_id}
|
|
||||||
|
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")
|
@router.post("/tasks/{task_id}/collect/stop")
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,7 @@ const en = {
|
||||||
colConnect: "Connect",
|
colConnect: "Connect",
|
||||||
start: "Start schedule",
|
start: "Start schedule",
|
||||||
pause: "Pause",
|
pause: "Pause",
|
||||||
statusPaused: "Paused",
|
statusPaused: "Manual only",
|
||||||
scheduleEnabled: "Enable schedule",
|
scheduleEnabled: "Enable schedule",
|
||||||
scheduleOn: "Scheduled",
|
scheduleOn: "Scheduled",
|
||||||
scheduleOff: "Manual",
|
scheduleOff: "Manual",
|
||||||
|
|
@ -204,6 +204,7 @@ const en = {
|
||||||
started: "Schedule enabled",
|
started: "Schedule enabled",
|
||||||
paused: "Schedule paused (manual only)",
|
paused: "Schedule paused (manual only)",
|
||||||
collecting: "Collecting",
|
collecting: "Collecting",
|
||||||
|
statusCollecting: "Collecting",
|
||||||
detail: "Details",
|
detail: "Details",
|
||||||
interval: "Interval",
|
interval: "Interval",
|
||||||
intervalUnitDays: "days",
|
intervalUnitDays: "days",
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,7 @@ const zh = {
|
||||||
colConnect: "连通",
|
colConnect: "连通",
|
||||||
start: "启动周期",
|
start: "启动周期",
|
||||||
pause: "暂停",
|
pause: "暂停",
|
||||||
statusPaused: "已暂停",
|
statusPaused: "仅手动",
|
||||||
scheduleEnabled: "启用周期调度",
|
scheduleEnabled: "启用周期调度",
|
||||||
scheduleOn: "周期",
|
scheduleOn: "周期",
|
||||||
scheduleOff: "手动",
|
scheduleOff: "手动",
|
||||||
|
|
@ -204,6 +204,7 @@ const zh = {
|
||||||
started: "已启用周期采集",
|
started: "已启用周期采集",
|
||||||
paused: "已暂停周期(仅手动采集)",
|
paused: "已暂停周期(仅手动采集)",
|
||||||
collecting: "采集中",
|
collecting: "采集中",
|
||||||
|
statusCollecting: "采集中",
|
||||||
detail: "详情",
|
detail: "详情",
|
||||||
interval: "采集周期",
|
interval: "采集周期",
|
||||||
intervalUnitDays: "天",
|
intervalUnitDays: "天",
|
||||||
|
|
|
||||||
|
|
@ -10072,7 +10072,40 @@ html.login-page--paused .login-page__flare {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
flex: 0 0 auto;
|
flex: 0 1 auto;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-commands-card-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
max-height: min(48vh, 520px);
|
||||||
|
overflow: auto;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-commands-card {
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.22);
|
||||||
|
background: rgba(15, 23, 42, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-commands-card__msg {
|
||||||
|
flex: 1 0 100%;
|
||||||
|
margin-top: 2px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #94a3b8;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-cmd-metric {
|
||||||
|
font-size: 11px;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bs-inline-cmd {
|
.bs-inline-cmd {
|
||||||
|
|
|
||||||
|
|
@ -467,13 +467,13 @@ export function BizStatePage() {
|
||||||
|
|
||||||
const commandsSheetColumns = useMemo<SheetCol[]>(
|
const commandsSheetColumns = useMemo<SheetCol[]>(
|
||||||
() => [
|
() => [
|
||||||
|
{ key: "_actions", header: t("bizState.colActions") },
|
||||||
{ key: "raw_command", header: t("bizState.colCommand") },
|
{ key: "raw_command", header: t("bizState.colCommand") },
|
||||||
{ key: "metric_id", header: "metric" },
|
{ key: "metric_id", header: "metric" },
|
||||||
{ key: "parse_status", header: t("bizState.colStatus") },
|
{ key: "parse_status", header: t("bizState.colStatus") },
|
||||||
{ key: "raw_line_count", header: t("bizState.colRawLines") },
|
{ key: "raw_line_count", header: t("bizState.colRawLines") },
|
||||||
{ key: "row_count", header: t("bizState.colRows") },
|
{ key: "row_count", header: t("bizState.colRows") },
|
||||||
{ key: "message", header: t("bizState.colMessage") },
|
{ key: "message", header: t("bizState.colMessage") },
|
||||||
{ key: "_actions", header: t("bizState.colActions") },
|
|
||||||
],
|
],
|
||||||
[t],
|
[t],
|
||||||
);
|
);
|
||||||
|
|
@ -823,8 +823,20 @@ export function BizStatePage() {
|
||||||
const collectNowForTask = async (id: string, fromModal = false) => {
|
const collectNowForTask = async (id: string, fromModal = false) => {
|
||||||
setTaskCollecting(id, true);
|
setTaskCollecting(id, true);
|
||||||
try {
|
try {
|
||||||
await bizStateCollectNow(id);
|
const out = await bizStateCollectNow(id);
|
||||||
showOk(t("bizState.collecting"));
|
if (out && out.started === false) {
|
||||||
|
setTaskCollecting(id, false);
|
||||||
|
const reason = String(out.reason || "");
|
||||||
|
if (reason === "already_collecting") {
|
||||||
|
setTaskCollecting(id, true);
|
||||||
|
showOk(t("bizState.collecting"));
|
||||||
|
} else {
|
||||||
|
showError(reason || t("common.opFailed"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showOk(t("bizState.collecting"));
|
||||||
|
}
|
||||||
if (fromModal && taskId === id) {
|
if (fromModal && taskId === id) {
|
||||||
setTaskTab("batches");
|
setTaskTab("batches");
|
||||||
try {
|
try {
|
||||||
|
|
@ -1295,8 +1307,8 @@ export function BizStatePage() {
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div className="pt-list-actions" style={{ flexWrap: "wrap", gap: 4 }}>
|
<div className="pt-list-actions" style={{ flexWrap: "wrap", gap: 4 }}>
|
||||||
{row.collect_running ? (
|
{row.collect_running || collectingIds[row.id] ? (
|
||||||
<NmStatusChip color="accent">{t("bizState.collecting")}</NmStatusChip>
|
<NmStatusChip color="accent">{t("bizState.statusCollecting")}</NmStatusChip>
|
||||||
) : row.status === "running" ? (
|
) : row.status === "running" ? (
|
||||||
<NmStatusChip color="success">{t("bizState.scheduleOn")}</NmStatusChip>
|
<NmStatusChip color="success">{t("bizState.scheduleOn")}</NmStatusChip>
|
||||||
) : row.status === "paused" ? (
|
) : row.status === "paused" ? (
|
||||||
|
|
@ -1500,13 +1512,21 @@ export function BizStatePage() {
|
||||||
<Modal.Header>
|
<Modal.Header>
|
||||||
<Modal.Heading>
|
<Modal.Heading>
|
||||||
{detail?.ne_name || detail?.ne_ip || t("bizState.detail")}
|
{detail?.ne_name || detail?.ne_ip || t("bizState.detail")}
|
||||||
{detail ? ` · ${detail.status}` : ""}
|
|
||||||
</Modal.Heading>
|
</Modal.Heading>
|
||||||
<Modal.CloseTrigger />
|
<Modal.CloseTrigger />
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body className="flex flex-col gap-3">
|
<Modal.Body className="flex flex-col gap-3">
|
||||||
{detail ? (
|
{detail ? (
|
||||||
<div className="config-sync-policy-row bs-schedule-row">
|
<div className="config-sync-policy-row bs-schedule-row">
|
||||||
|
{detail.collect_running || (taskId && collectingIds[taskId]) ? (
|
||||||
|
<NmStatusChip color="accent">{t("bizState.statusCollecting")}</NmStatusChip>
|
||||||
|
) : detail.status === "running" ? (
|
||||||
|
<NmStatusChip color="success">{t("bizState.scheduleOn")}</NmStatusChip>
|
||||||
|
) : detail.status === "paused" ? (
|
||||||
|
<NmStatusChip color="warning">{t("bizState.statusPaused")}</NmStatusChip>
|
||||||
|
) : (
|
||||||
|
<NmStatusChip color="default">{t("bizState.scheduleOff")}</NmStatusChip>
|
||||||
|
)}
|
||||||
<label className="config-sync-policy-check">
|
<label className="config-sync-policy-check">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
|
@ -2212,6 +2232,26 @@ export function BizStatePage() {
|
||||||
{c.raw_command || "—"}
|
{c.raw_command || "—"}
|
||||||
</code>
|
</code>
|
||||||
<div className="bs-sheet-cmd-actions">
|
<div className="bs-sheet-cmd-actions">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="secondary"
|
||||||
|
isDisabled={!c.has_raw}
|
||||||
|
onPress={() => void openRawLog(c.id)}
|
||||||
|
>
|
||||||
|
{t("bizState.viewRawLog")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
isDisabled={!c.has_raw || !batchDetail?.id}
|
||||||
|
onPress={() =>
|
||||||
|
void bizStateDownloadCommandRaw(String(batchDetail.id), c.id).catch((e) =>
|
||||||
|
showError(formatErr(e)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t("bizState.exportRawLog")}
|
||||||
|
</Button>
|
||||||
<span className="muted bs-cmd-stat">
|
<span className="muted bs-cmd-stat">
|
||||||
{t("bizState.rawLogStats", {
|
{t("bizState.rawLogStats", {
|
||||||
lines: Number(c.raw_line_count ?? 0),
|
lines: Number(c.raw_line_count ?? 0),
|
||||||
|
|
@ -2223,14 +2263,6 @@ export function BizStatePage() {
|
||||||
{c.parse_status}
|
{c.parse_status}
|
||||||
</NmStatusChip>
|
</NmStatusChip>
|
||||||
) : null}
|
) : null}
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="secondary"
|
|
||||||
isDisabled={!c.has_raw}
|
|
||||||
onPress={() => void openRawLog(c.id)}
|
|
||||||
>
|
|
||||||
{t("bizState.viewRawLog")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
@ -2238,6 +2270,64 @@ export function BizStatePage() {
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
{activeSheet.id === "commands" ? (
|
||||||
|
<div className="bs-sheet-cmd-list bs-commands-card-list">
|
||||||
|
{displayRows.map((row, i) => (
|
||||||
|
<div key={String(row.id || i)} className="bs-sheet-cmd-row bs-commands-card">
|
||||||
|
<code className="bs-sheet-cmd-code" title={cellText(row.raw_command)}>
|
||||||
|
{cellText(row.raw_command) || "—"}
|
||||||
|
</code>
|
||||||
|
<div className="bs-sheet-cmd-actions">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="secondary"
|
||||||
|
isDisabled={!row.has_raw}
|
||||||
|
onPress={() => void openRawLog(String(row.id || ""))}
|
||||||
|
>
|
||||||
|
{t("bizState.viewRawLog")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
isDisabled={!row.has_raw || !batchDetail?.id}
|
||||||
|
onPress={() =>
|
||||||
|
void bizStateDownloadCommandRaw(
|
||||||
|
String(batchDetail.id),
|
||||||
|
String(row.id || ""),
|
||||||
|
).catch((e) => showError(formatErr(e)))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t("bizState.exportRawLog")}
|
||||||
|
</Button>
|
||||||
|
<span className="muted bs-cmd-stat">
|
||||||
|
{t("bizState.rawLogStats", {
|
||||||
|
lines: Number(row.raw_line_count ?? 0),
|
||||||
|
rows: Number(row.row_count ?? 0),
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
{cellText(row.parse_status) ? (
|
||||||
|
<NmStatusChip color={jobChipColor(cellText(row.parse_status))}>
|
||||||
|
{cellText(row.parse_status)}
|
||||||
|
</NmStatusChip>
|
||||||
|
) : null}
|
||||||
|
{cellText(row.metric_id) ? (
|
||||||
|
<span className="muted bs-cmd-metric">{cellText(row.metric_id)}</span>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
{cellText(row.message) ? (
|
||||||
|
<div className="bs-commands-card__msg" title={cellText(row.message)}>
|
||||||
|
{cellText(row.message)}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{!displayRows.length ? (
|
||||||
|
<div className="pt-list-empty">{t("bizState.sheetEmpty")}</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{activeSheet.id !== "commands" ? (
|
||||||
<div className="filter-inline bs-sheet-filter">
|
<div className="filter-inline bs-sheet-filter">
|
||||||
<FieldSelect
|
<FieldSelect
|
||||||
value={sheetColumn}
|
value={sheetColumn}
|
||||||
|
|
@ -2268,7 +2358,23 @@ export function BizStatePage() {
|
||||||
{sheetLoading ? t("bizState.sheetLoading") : `${displayTotal} ${t("bizState.colRows")}`}
|
{sheetLoading ? t("bizState.sheetLoading") : `${displayTotal} ${t("bizState.colRows")}`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="filter-inline bs-sheet-filter">
|
||||||
|
<Input
|
||||||
|
value={sheetKeyword}
|
||||||
|
placeholder={t("bizState.sheetFilterPh")}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSheetKeyword(e.target.value);
|
||||||
|
setSheetPage(1);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span className="muted bs-sheet-count">
|
||||||
|
{`${displayTotal} ${t("bizState.colRows")}`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeSheet.id !== "commands" ? (
|
||||||
<div className="pt-list-table-wrap bs-sheet-table">
|
<div className="pt-list-table-wrap bs-sheet-table">
|
||||||
<table className="data-table pt-list-table">
|
<table className="data-table pt-list-table">
|
||||||
<thead>
|
<thead>
|
||||||
|
|
@ -2327,7 +2433,7 @@ export function BizStatePage() {
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
) : null}
|
) : null}
|
||||||
{sheetLoading && activeSheet.id !== "commands" && !displayRows.length ? (
|
{sheetLoading && !displayRows.length ? (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={Math.max(1, displayColumns.length)}>
|
<td colSpan={Math.max(1, displayColumns.length)}>
|
||||||
<div className="pt-list-empty muted">{t("bizState.sheetLoading")}</div>
|
<div className="pt-list-empty muted">{t("bizState.sheetLoading")}</div>
|
||||||
|
|
@ -2337,6 +2443,7 @@ export function BizStatePage() {
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<ListPager
|
<ListPager
|
||||||
page={sheetPage}
|
page={sheetPage}
|
||||||
|
|
|
||||||
|
|
@ -1824,7 +1824,14 @@ export const bizStateDeleteTask = (taskId: string) =>
|
||||||
apiDelete<{ ok: boolean }>(`/v1/biz-state/tasks/${encodeURIComponent(taskId)}`);
|
apiDelete<{ ok: boolean }>(`/v1/biz-state/tasks/${encodeURIComponent(taskId)}`);
|
||||||
|
|
||||||
export const bizStateCollectNow = (taskId: string) =>
|
export const bizStateCollectNow = (taskId: string) =>
|
||||||
apiPost<{ ok: boolean }>(`/v1/biz-state/tasks/${encodeURIComponent(taskId)}/collect`, {});
|
apiPost<{
|
||||||
|
ok: boolean;
|
||||||
|
started?: boolean;
|
||||||
|
queued?: boolean;
|
||||||
|
reason?: string;
|
||||||
|
batch_id?: string;
|
||||||
|
collect_running?: boolean;
|
||||||
|
}>(`/v1/biz-state/tasks/${encodeURIComponent(taskId)}/collect`, {});
|
||||||
|
|
||||||
export const bizStateCollectStop = (taskId: string) =>
|
export const bizStateCollectStop = (taskId: string) =>
|
||||||
apiPost<{
|
apiPost<{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue