Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94c7867c5a | ||
|
|
0a028bedcc |
@@ -13,3 +13,9 @@
|
||||
- 页面不直接暴露 API 调用细节给普通用户。
|
||||
- 前端只通过 `/api/...` 调用后端,不内联业务逻辑。
|
||||
- 后续迁移到 React 时,页面行为应与当前静态页面保持一致。
|
||||
|
||||
## 代码注释规范
|
||||
|
||||
- 本仓库所有源码(HTML、JavaScript、CSS 等支持注释的文件)必须配有详细中文注释,说明页面职责、函数作用与关键交互逻辑,确保后续维护人员可以快速理解代码工作原理。
|
||||
- 新增或修改代码时,必须同步补充或更新对应注释;不得删除已有注释。
|
||||
- 每个 HTML 页面需要在文件头说明页面用途;公共脚本需要说明各函数的职责。
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<!doctype html>
|
||||
<!-- WOV 管理后台:节点注册、节点列表、手动调用与实例管理。 -->
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
@@ -11,6 +12,7 @@
|
||||
<a class="brand" href="/">WOV</a>
|
||||
<nav>
|
||||
<a href="/">应用中心</a>
|
||||
<a href="/tasks.html">任务管理</a>
|
||||
<a href="/admin.html">管理后台</a>
|
||||
<a href="/workflow.html">工作流</a>
|
||||
</nav>
|
||||
@@ -19,6 +21,7 @@
|
||||
<main class="container">
|
||||
<h1>节点管理</h1>
|
||||
|
||||
<!-- 注册节点:粘贴 node.manifest.json,由后端校验后写入注册表。 -->
|
||||
<section class="panel">
|
||||
<h2>注册节点</h2>
|
||||
<label for="manifestJson">节点 Manifest JSON</label>
|
||||
@@ -29,6 +32,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 已注册节点:展示节点能力与版本,可删除节点。 -->
|
||||
<section class="panel">
|
||||
<h2>已注册节点</h2>
|
||||
<div class="table-wrap">
|
||||
@@ -47,6 +51,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 调用节点:管理后台手动触发一次节点调用,用于联调。 -->
|
||||
<section class="panel">
|
||||
<h2>调用节点</h2>
|
||||
<div class="form-grid">
|
||||
@@ -67,6 +72,7 @@
|
||||
<pre id="invokeResult" class="result">等待调用...</pre>
|
||||
</section>
|
||||
|
||||
<!-- 节点实例:查看 NodeManager 启动的进程状态,可手动停止。 -->
|
||||
<section class="panel">
|
||||
<h2>节点实例</h2>
|
||||
<div class="table-wrap">
|
||||
|
||||
+105
-3
@@ -1,3 +1,6 @@
|
||||
// WOV 静态前端公共脚本:所有页面共用的 API 封装、渲染函数与事件绑定。
|
||||
|
||||
// Echo 演示节点的预置 manifest,便于管理后台一键填入。
|
||||
const ECHO_MANIFEST = {
|
||||
id: "echo",
|
||||
name: "Echo Node",
|
||||
@@ -14,6 +17,7 @@ const ECHO_MANIFEST = {
|
||||
keep_warm: false,
|
||||
};
|
||||
|
||||
// 演示“视频字幕生成”工作流的 DAG 定义,预填在工作流编排页。
|
||||
const DEMO_WORKFLOW = {
|
||||
name: "视频字幕生成",
|
||||
version: 1,
|
||||
@@ -55,6 +59,7 @@ const DEMO_WORKFLOW = {
|
||||
},
|
||||
};
|
||||
|
||||
// 统一封装 fetch:自动携带 JSON 头、解析响应并在失败时抛出可读错误。
|
||||
async function api(path, options = {}) {
|
||||
const response = await fetch(path, {
|
||||
headers: { "Content-Type": "application/json", ...(options.headers || {}) },
|
||||
@@ -62,12 +67,14 @@ async function api(path, options = {}) {
|
||||
});
|
||||
const data = await response.json().catch(() => null);
|
||||
if (!response.ok) {
|
||||
// FastAPI 的校验错误 detail 可能是数组,统一序列化为字符串展示。
|
||||
const detail = data && data.detail ? JSON.stringify(data.detail) : response.statusText;
|
||||
throw new Error(`${response.status} ${detail}`);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
// 转义用户可控文本,防止 XSS 注入到表格或状态 HTML 中。
|
||||
function escapeHtml(value) {
|
||||
return String(value)
|
||||
.replaceAll("&", "&")
|
||||
@@ -77,16 +84,36 @@ function escapeHtml(value) {
|
||||
.replaceAll("'", "'");
|
||||
}
|
||||
|
||||
// 根据状态生成带语义颜色的徽章 HTML。
|
||||
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 `<span class="badge ${className}">${escapeHtml(status)}</span>`;
|
||||
}
|
||||
|
||||
// 把 ISO 时间格式化为本地时间;非法值原样返回。
|
||||
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");
|
||||
@@ -98,6 +125,7 @@ async function loadHealth() {
|
||||
}
|
||||
}
|
||||
|
||||
// 加载已注册节点并渲染节点表格与调用下拉框。
|
||||
async function loadNodes() {
|
||||
const nodes = await api("/api/admin/nodes");
|
||||
const tbody = document.getElementById("nodeList");
|
||||
@@ -121,6 +149,7 @@ async function loadNodes() {
|
||||
.join("");
|
||||
}
|
||||
|
||||
// 加载节点实例并渲染实例表格。
|
||||
async function loadInstances() {
|
||||
const instances = await api("/api/admin/node-instances");
|
||||
const tbody = document.getElementById("instanceList");
|
||||
@@ -141,10 +170,12 @@ async function loadInstances() {
|
||||
: '<tr><td colspan="6">暂无实例</td></tr>';
|
||||
}
|
||||
|
||||
// 管理后台页面同时刷新节点与实例两个列表。
|
||||
async function refresh() {
|
||||
await Promise.all([loadNodes(), loadInstances()]);
|
||||
}
|
||||
|
||||
// 注册节点:解析文本域中的 manifest JSON 并提交到后端。
|
||||
async function registerNode() {
|
||||
const raw = document.getElementById("manifestJson").value.trim();
|
||||
try {
|
||||
@@ -157,6 +188,7 @@ async function registerNode() {
|
||||
}
|
||||
}
|
||||
|
||||
// 手动调用节点:读取表单参数并展示返回结果。
|
||||
async function invokeNode() {
|
||||
const nodeId = document.getElementById("invokeNodeId").value;
|
||||
const runId = document.getElementById("invokeRunId").value.trim() || "run_web";
|
||||
@@ -175,6 +207,7 @@ async function invokeNode() {
|
||||
}
|
||||
}
|
||||
|
||||
// 删除节点前不做二次确认,由点击按钮触发。
|
||||
async function deleteNode(nodeId) {
|
||||
try {
|
||||
await api(`/api/admin/nodes/${nodeId}`, { method: "DELETE" });
|
||||
@@ -184,6 +217,7 @@ async function deleteNode(nodeId) {
|
||||
}
|
||||
}
|
||||
|
||||
// 请求后端停止指定节点实例。
|
||||
async function stopInstance(instanceId) {
|
||||
try {
|
||||
await api(`/api/admin/node-instances/${instanceId}/stop`, { method: "POST" });
|
||||
@@ -193,6 +227,7 @@ async function stopInstance(instanceId) {
|
||||
}
|
||||
}
|
||||
|
||||
// 应用中心的 Echo 演示:直接调用 echo 节点并展示结果。
|
||||
async function runEcho() {
|
||||
const text = document.getElementById("echoText").value;
|
||||
const resultBox = document.getElementById("echoResult");
|
||||
@@ -212,6 +247,7 @@ async function runEcho() {
|
||||
}
|
||||
}
|
||||
|
||||
// 加载工作流列表并渲染发布状态与操作按钮。
|
||||
async function loadWorkflows() {
|
||||
const workflows = await api("/api/admin/workflows");
|
||||
const tbody = document.getElementById("workflowList");
|
||||
@@ -235,6 +271,55 @@ async function loadWorkflows() {
|
||||
: '<tr><td colspan="5">暂无工作流</td></tr>';
|
||||
}
|
||||
|
||||
// 加载最近任务并渲染任务表格,失败任务提供重试按钮。
|
||||
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 || "";
|
||||
// RUNNING/QUEUED 附加耗时或排队提示,其余状态只显示徽章。
|
||||
const statusHtml =
|
||||
run.status === "RUNNING"
|
||||
? `${badge(run.status)} <span class="muted">已运行 ${formatElapsed(run.updated_at)}</span>`
|
||||
: run.status === "QUEUED"
|
||||
? `${badge(run.status)} <span class="muted">排队中</span>`
|
||||
: badge(run.status);
|
||||
const actions =
|
||||
run.status === "FAILED"
|
||||
? `<button class="danger" data-retry-run="${escapeHtml(run.id)}">重试</button>`
|
||||
: "";
|
||||
return `
|
||||
<tr>
|
||||
<td>${escapeHtml(run.id)}</td>
|
||||
<td>${escapeHtml(run.workflow_id)}</td>
|
||||
<td>${statusHtml}</td>
|
||||
<td>${escapeHtml(run.current_node_id || "-")}</td>
|
||||
<td>${percent}%</td>
|
||||
<td>${escapeHtml(formatTime(run.created_at))}</td>
|
||||
<td title="${escapeHtml(error)}">${error ? escapeHtml(error.slice(0, 60)) : "-"}</td>
|
||||
<td>${actions}</td>
|
||||
</tr>`;
|
||||
})
|
||||
.join("")
|
||||
: '<tr><td colspan="8">暂无任务</td></tr>';
|
||||
}
|
||||
|
||||
// 请求后端重试失败任务,成功后刷新列表。
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 创建或更新工作流:解析 DAG JSON 后提交,随后刷新列表。
|
||||
async function createWorkflow() {
|
||||
const workflowId = document.getElementById("workflowId").value.trim();
|
||||
const name = document.getElementById("workflowName").value.trim();
|
||||
@@ -263,6 +348,7 @@ async function createWorkflow() {
|
||||
}
|
||||
}
|
||||
|
||||
// 发布指定工作流,使其出现在用户应用中心。
|
||||
async function publishWorkflow(workflowId) {
|
||||
try {
|
||||
await api(`/api/admin/workflows/${workflowId}/publish`, { method: "POST" });
|
||||
@@ -272,6 +358,7 @@ async function publishWorkflow(workflowId) {
|
||||
}
|
||||
}
|
||||
|
||||
// 删除指定工作流。
|
||||
async function deleteWorkflow(workflowId) {
|
||||
try {
|
||||
await api(`/api/admin/workflows/${workflowId}`, { method: "DELETE" });
|
||||
@@ -281,6 +368,7 @@ async function deleteWorkflow(workflowId) {
|
||||
}
|
||||
}
|
||||
|
||||
// 上传视频并轮询任务进度,完成后渲染下载链接。
|
||||
async function uploadVideo() {
|
||||
const fileInput = document.getElementById("videoFile");
|
||||
const progress = document.getElementById("runProgress");
|
||||
@@ -305,8 +393,9 @@ async function uploadVideo() {
|
||||
}
|
||||
}
|
||||
|
||||
// 每秒轮询一次任务状态,最多 3600 次(约 1 小时),超时后提示查看接口。
|
||||
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}%`;
|
||||
@@ -323,6 +412,7 @@ async function pollRun(runId, progress, downloads) {
|
||||
progress.textContent = "轮询超时,请到 /api/runs 查看任务状态";
|
||||
}
|
||||
|
||||
// 只展示用户关心的最终产物(中文 SRT 与 ASS)下载入口。
|
||||
function renderArtifacts(runId, artifacts, container) {
|
||||
container.innerHTML = artifacts
|
||||
.filter((item) => ["cn_srt", "ass"].includes(item.name))
|
||||
@@ -335,6 +425,7 @@ function renderArtifacts(runId, artifacts, container) {
|
||||
.join("");
|
||||
}
|
||||
|
||||
// 全局点击委托:按按钮上的 data-* 属性分发到对应操作。
|
||||
document.addEventListener("click", (event) => {
|
||||
const deleteNodeButton = event.target.closest("[data-delete-node]");
|
||||
if (deleteNodeButton) {
|
||||
@@ -354,9 +445,15 @@ 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);
|
||||
}
|
||||
});
|
||||
|
||||
// 页面初始化:预填 manifest/DAG、绑定按钮事件并加载对应页面数据。
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
const manifestBox = document.getElementById("manifestJson");
|
||||
if (manifestBox) {
|
||||
@@ -406,4 +503,9 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
if (document.getElementById("workflowList")) {
|
||||
await loadWorkflows();
|
||||
}
|
||||
// 任务管理页每 5 秒自动刷新一次状态。
|
||||
if (document.getElementById("runList")) {
|
||||
await loadRuns();
|
||||
setInterval(loadRuns, 5000);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/* WOV 静态前端全局样式:定义色彩变量、布局与通用组件样式。 */
|
||||
|
||||
/* 设计令牌:集中管理配色,后续换肤只需修改变量。 */
|
||||
:root {
|
||||
--bg: #f5f7fa;
|
||||
--surface: #ffffff;
|
||||
@@ -10,10 +13,12 @@
|
||||
--radius: 8px;
|
||||
}
|
||||
|
||||
/* 全局盒模型与基础排版重置。 */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 页面主体:浅灰背景与默认文字颜色。 */
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "Segoe UI", "Microsoft YaHei", sans-serif;
|
||||
@@ -21,6 +26,7 @@ body {
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
/* 顶部导航栏:品牌标题与页面导航。 */
|
||||
.topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -30,6 +36,7 @@ body {
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
/* 品牌标题样式。 */
|
||||
.brand {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
@@ -37,11 +44,13 @@ body {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* 导航链接横向排列。 */
|
||||
nav {
|
||||
display: flex;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
/* 导航链接默认使用弱化色,悬停时高亮。 */
|
||||
nav a {
|
||||
color: var(--muted);
|
||||
text-decoration: none;
|
||||
@@ -52,12 +61,14 @@ nav a:hover {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
/* 内容容器:限制最大宽度并居中。 */
|
||||
.container {
|
||||
max-width: 1080px;
|
||||
margin: 0 auto;
|
||||
padding: 28px 20px 60px;
|
||||
}
|
||||
|
||||
/* 一级标题与二级标题的字号控制。 */
|
||||
h1 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 28px;
|
||||
@@ -68,10 +79,12 @@ h2 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/* 弱化文字:用于说明、时间等次要信息。 */
|
||||
.muted {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
/* 应用中心卡片网格:自适应列数的最小宽度布局。 */
|
||||
.app-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
@@ -79,6 +92,7 @@ h2 {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
/* 卡片与面板共用白底、边框和圆角外观。 */
|
||||
.card,
|
||||
.panel {
|
||||
background: var(--surface);
|
||||
@@ -87,14 +101,17 @@ h2 {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 面板之间保持纵向间距。 */
|
||||
.panel {
|
||||
margin-top: 22px;
|
||||
}
|
||||
|
||||
/* 禁用态样式:降低透明度表达不可用。 */
|
||||
.disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* 表单标签:块级显示并加粗。 */
|
||||
label {
|
||||
display: block;
|
||||
margin: 10px 0 6px;
|
||||
@@ -103,6 +120,7 @@ label {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
/* 输入控件统一样式,宽度撑满容器。 */
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
@@ -114,11 +132,13 @@ textarea {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* 文本域使用等宽字体并允许纵向拉伸。 */
|
||||
textarea {
|
||||
font-family: Consolas, monospace;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/* 按钮基础样式。 */
|
||||
button {
|
||||
margin-top: 12px;
|
||||
padding: 9px 16px;
|
||||
@@ -131,21 +151,25 @@ button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 按钮悬停强调边框与文字颜色。 */
|
||||
button:hover {
|
||||
border-color: var(--primary);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
/* 主按钮:填充品牌色。 */
|
||||
button.primary {
|
||||
background: var(--primary);
|
||||
border-color: var(--primary);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 幽灵按钮:透明背景,用于次要操作。 */
|
||||
button.ghost {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* 操作区与表单网格:弹性换行排列。 */
|
||||
.actions,
|
||||
.form-grid {
|
||||
display: flex;
|
||||
@@ -153,25 +177,30 @@ button.ghost {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* 表单网格:默认两列,宽字段占整行。 */
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
/* 宽字段跨满整行。 */
|
||||
.form-grid .wide {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
/* 表格容器:窄屏时允许横向滚动。 */
|
||||
.table-wrap {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* 表格基础样式。 */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 表头与单元格的间距、对齐与分隔线。 */
|
||||
th,
|
||||
td {
|
||||
padding: 10px;
|
||||
@@ -180,12 +209,14 @@ td {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 表头使用弱化色与小字号。 */
|
||||
th {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* 结果块:深色等宽字体,适合展示 JSON 或日志。 */
|
||||
.result {
|
||||
margin: 16px 0 0;
|
||||
padding: 14px;
|
||||
@@ -199,6 +230,7 @@ th {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
/* 下载区:横向排列的下载链接。 */
|
||||
.downloads {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
@@ -206,6 +238,7 @@ th {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
/* 下载链接:描边按钮式链接。 */
|
||||
.download-link {
|
||||
display: inline-block;
|
||||
padding: 8px 14px;
|
||||
@@ -216,10 +249,12 @@ th {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* 下载链接悬停时轻微填充背景。 */
|
||||
.download-link:hover {
|
||||
background: #e8f1f9;
|
||||
}
|
||||
|
||||
/* 状态徽章基础样式。 */
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 3px 8px;
|
||||
@@ -228,21 +263,25 @@ th {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* 成功徽章:绿色背景。 */
|
||||
.badge.ok {
|
||||
background: #e2f4ea;
|
||||
color: var(--ok);
|
||||
}
|
||||
|
||||
/* 警告徽章:黄色背景。 */
|
||||
.badge.warn {
|
||||
background: #fff2d9;
|
||||
color: #8a5a00;
|
||||
}
|
||||
|
||||
/* 错误徽章:红色背景。 */
|
||||
.badge.error {
|
||||
background: #fde8e6;
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
/* 危险操作按钮文字颜色。 */
|
||||
.danger {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<!doctype html>
|
||||
<!-- WOV 应用中心:用户端首页,提供 Echo 演示与视频字幕生成两个入口。 -->
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
@@ -11,16 +12,19 @@
|
||||
<a class="brand" href="/">WOV</a>
|
||||
<nav>
|
||||
<a href="/">应用中心</a>
|
||||
<a href="/tasks.html">任务管理</a>
|
||||
<a href="/admin.html">管理后台</a>
|
||||
<a href="/workflow.html">工作流</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<!-- 用户端只展示应用卡片,不暴露底层工作流与节点细节。 -->
|
||||
<h1>WOV 应用中心</h1>
|
||||
<p class="muted">选择一个已发布的应用,上传或输入内容后由后台自动执行。</p>
|
||||
|
||||
<div class="app-grid">
|
||||
<!-- Echo 演示:验证前端到后端再到节点进程的完整调用链路。 -->
|
||||
<article class="card">
|
||||
<h2>Echo 演示</h2>
|
||||
<p>调用已注册的 echo 节点,验证前端到后端再到节点进程的完整链路。</p>
|
||||
@@ -30,6 +34,7 @@
|
||||
<pre id="echoResult" class="result">等待运行...</pre>
|
||||
</article>
|
||||
|
||||
<!-- 视频字幕生成:上传视频后自动执行提音、转写、翻译和 ASS 生成。 -->
|
||||
<article class="card">
|
||||
<h2>视频字幕生成</h2>
|
||||
<p>上传视频后自动执行提音、转写、翻译和 VR 双眼 ASS 生成。</p>
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
<!doctype html>
|
||||
<!-- WOV 任务管理页:查看所有任务状态,失败任务可重新排队。 -->
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>WOV 任务管理</title>
|
||||
<link rel="stylesheet" href="/assets/styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<header class="topbar">
|
||||
<a class="brand" href="/">WOV</a>
|
||||
<nav>
|
||||
<a href="/">应用中心</a>
|
||||
<a href="/tasks.html">任务管理</a>
|
||||
<a href="/admin.html">管理后台</a>
|
||||
<a href="/workflow.html">工作流</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<h1>任务管理</h1>
|
||||
<p class="muted">查看所有任务的执行状态;失败的任务可以重新排队执行。</p>
|
||||
|
||||
<!-- 任务列表由 assets/app.js 的 loadRuns 定期刷新填充。 -->
|
||||
<section class="panel">
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>任务 ID</th>
|
||||
<th>工作流</th>
|
||||
<th>状态</th>
|
||||
<th>当前节点</th>
|
||||
<th>进度</th>
|
||||
<th>创建时间</th>
|
||||
<th>错误信息</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="runList"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="/assets/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,4 +1,5 @@
|
||||
<!doctype html>
|
||||
<!-- WOV 工作流编排页:以 DAG JSON 创建/更新工作流并发布。 -->
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
@@ -11,6 +12,7 @@
|
||||
<a class="brand" href="/">WOV</a>
|
||||
<nav>
|
||||
<a href="/">应用中心</a>
|
||||
<a href="/tasks.html">任务管理</a>
|
||||
<a href="/admin.html">管理后台</a>
|
||||
<a href="/workflow.html">工作流</a>
|
||||
</nav>
|
||||
@@ -19,6 +21,7 @@
|
||||
<main class="container">
|
||||
<h1>工作流编排</h1>
|
||||
|
||||
<!-- 工作流定义:输入概要信息与 DAG JSON,创建或追加新版本。 -->
|
||||
<section class="panel">
|
||||
<h2>工作流定义</h2>
|
||||
<label for="workflowId">工作流 ID</label>
|
||||
@@ -35,6 +38,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 已发布工作流:展示发布状态,支持发布与删除操作。 -->
|
||||
<section class="panel">
|
||||
<h2>已发布工作流</h2>
|
||||
<div class="table-wrap">
|
||||
|
||||
Reference in New Issue
Block a user