diff --git a/admin.html b/admin.html index 5f8be70..cd2bc20 100644 --- a/admin.html +++ b/admin.html @@ -11,6 +11,7 @@ WOV diff --git a/assets/app.js b/assets/app.js index 4ebce40..c86e776 100644 --- a/assets/app.js +++ b/assets/app.js @@ -78,15 +78,30 @@ function escapeHtml(value) { } function badge(status) { + const value = String(status).toLowerCase(); const className = - status === "ready" || status === "completed" || status === "ok" + value === "ready" || value === "completed" || value === "ok" ? "ok" - : status === "stopped" || status === "error" + : value === "stopped" || value === "error" || value === "failed" ? "error" : "warn"; return `${escapeHtml(status)}`; } +function formatTime(value) { + if (!value) return ""; + const date = new Date(value); + return Number.isNaN(date.getTime()) ? value : date.toLocaleString(); +} + +function formatElapsed(value) { + if (!value) return ""; + const seconds = Math.floor((Date.now() - new Date(value).getTime()) / 1000); + if (Number.isNaN(seconds) || seconds < 0) return ""; + if (seconds < 60) return `${seconds} 秒`; + return `${Math.floor(seconds / 60)} 分钟`; +} + async function loadHealth() { try { await api("/health"); @@ -235,6 +250,51 @@ async function loadWorkflows() { : '暂无工作流'; } +async function loadRuns() { + const tbody = document.getElementById("runList"); + if (!tbody) return; + const runs = await api("/api/runs"); + tbody.innerHTML = runs.length + ? runs + .map((run) => { + const percent = Math.round((run.progress || 0) * 100); + const error = run.error || ""; + const statusHtml = + run.status === "RUNNING" + ? `${badge(run.status)} 已运行 ${formatElapsed(run.updated_at)}` + : run.status === "QUEUED" + ? `${badge(run.status)} 排队中` + : badge(run.status); + const actions = + run.status === "FAILED" + ? `` + : ""; + return ` + + ${escapeHtml(run.id)} + ${escapeHtml(run.workflow_id)} + ${statusHtml} + ${escapeHtml(run.current_node_id || "-")} + ${percent}% + ${escapeHtml(formatTime(run.created_at))} + ${error ? escapeHtml(error.slice(0, 60)) : "-"} + ${actions} + `; + }) + .join("") + : '暂无任务'; +} + +async function retryRun(runId) { + try { + const result = await api(`/api/runs/${runId}/retry`, { method: "POST" }); + alert(`任务 ${result.id} 已重新排队`); + await loadRuns(); + } catch (error) { + alert(`重试失败:${error.message}`); + } +} + async function createWorkflow() { const workflowId = document.getElementById("workflowId").value.trim(); const name = document.getElementById("workflowName").value.trim(); @@ -306,7 +366,7 @@ async function uploadVideo() { } async function pollRun(runId, progress, downloads) { - for (let i = 0; i < 600; i += 1) { + for (let i = 0; i < 3600; i += 1) { const run = await api(`/api/runs/${runId}`); const percent = Math.round((run.progress || 0) * 100); progress.textContent = `状态:${run.status} | 当前节点:${run.current_node_id || "-"} | 进度:${percent}%`; @@ -354,6 +414,11 @@ document.addEventListener("click", (event) => { const publishWorkflowButton = event.target.closest("[data-publish-workflow]"); if (publishWorkflowButton) { publishWorkflow(publishWorkflowButton.dataset.publishWorkflow); + return; + } + const retryButton = event.target.closest("[data-retry-run]"); + if (retryButton) { + retryRun(retryButton.dataset.retryRun); } }); @@ -406,4 +471,8 @@ document.addEventListener("DOMContentLoaded", async () => { if (document.getElementById("workflowList")) { await loadWorkflows(); } + if (document.getElementById("runList")) { + await loadRuns(); + setInterval(loadRuns, 5000); + } }); diff --git a/index.html b/index.html index d3213be..20d90f6 100644 --- a/index.html +++ b/index.html @@ -11,6 +11,7 @@ WOV diff --git a/tasks.html b/tasks.html new file mode 100644 index 0000000..fc7a549 --- /dev/null +++ b/tasks.html @@ -0,0 +1,47 @@ + + + + + + WOV 任务管理 + + + +
+ WOV + +
+ +
+

任务管理

+

查看所有任务的执行状态;失败的任务可以重新排队执行。

+ +
+
+ + + + + + + + + + + + + + +
任务 ID工作流状态当前节点进度创建时间错误信息操作
+
+
+
+ + + + diff --git a/workflow.html b/workflow.html index 04ea238..3992a05 100644 --- a/workflow.html +++ b/workflow.html @@ -11,6 +11,7 @@ WOV