Files
biliup-next/src/biliup_next/app/dashboard.py

357 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
def render_dashboard_html() -> str:
return """<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>biliup-next Control</title>
<link rel="stylesheet" href="/assets/dashboard.css" />
</head>
<body>
<div class="app-shell">
<aside class="sidebar">
<div class="sidebar-brand">
<p class="eyebrow">Biliup Next</p>
<h1>Control</h1>
<p class="sidebar-copy">围绕任务状态、运行时健康和配置管理组织的本地控制面。</p>
</div>
<nav class="sidebar-nav">
<button class="nav-btn active" data-view="overview">Overview</button>
<button class="nav-btn" data-view="tasks">Tasks</button>
<button class="nav-btn" data-view="settings">Settings</button>
<button class="nav-btn" data-view="logs">Logs</button>
</nav>
<div class="sidebar-section">
<label class="sidebar-label">Control Token</label>
<div class="sidebar-token">
<input id="tokenInput" placeholder="optional control token" />
<button id="saveTokenBtn" class="secondary compact">保存</button>
</div>
</div>
<div class="sidebar-section">
<div class="button-stack">
<button id="refreshBtn">刷新视图</button>
<button id="runOnceBtn" class="secondary">执行一轮 Worker</button>
<button id="saveSettingsBtn" class="secondary">保存 Settings</button>
</div>
</div>
</aside>
<main class="content">
<header class="topbar">
<div>
<p class="eyebrow">Operational Workspace</p>
<h2 id="viewTitle">Overview</h2>
</div>
<div class="topbar-meta">
<div class="status-chip">API · <span id="healthValue">-</span></div>
<div class="status-chip">Doctor · <span id="doctorValue">-</span></div>
<div class="status-chip">Tasks · <span id="tasksValue">-</span></div>
</div>
</header>
<div id="banner" class="banner"></div>
<section class="view active" data-view="overview">
<div class="panel-grid two-up">
<section class="panel">
<div class="panel-head"><h3>Runtime Snapshot</h3></div>
<div class="stats">
<div class="stat-card">
<span class="stat-label">Health</span>
<strong id="overviewHealthValue" class="stat-value">-</strong>
</div>
<div class="stat-card">
<span class="stat-label">Doctor</span>
<strong id="overviewDoctorValue" class="stat-value">-</strong>
</div>
<div class="stat-card">
<span class="stat-label">Tasks</span>
<strong id="overviewTasksValue" class="stat-value">-</strong>
</div>
</div>
<div id="overviewTaskSummary" class="summary-strip" style="margin-top:14px;"></div>
</section>
<section class="panel">
<div class="panel-head"><h3>Import To Stage</h3></div>
<div class="field-grid">
<input id="stageSourcePath" placeholder="/absolute/path/to/video.mp4" />
<button id="importStageBtn" class="secondary">复制到隔离 Stage</button>
</div>
<div class="field-grid upload-grid">
<input id="stageFileInput" type="file" />
<button id="uploadStageBtn" class="secondary">上传到隔离 Stage</button>
</div>
<p class="muted-note">只会复制或上传到 `biliup-next/data/workspace/stage/`,不会移动原文件。</p>
</section>
</div>
<div class="panel-grid two-up">
<section class="panel">
<div class="panel-head"><h3>Services</h3></div>
<div id="serviceList" class="stack-list"></div>
</section>
<section class="panel">
<div class="panel-head">
<h3>Recent Actions</h3>
<button id="refreshHistoryBtn" class="secondary compact">刷新</button>
</div>
<div class="filter-grid">
<label class="checkbox-row"><input id="historyCurrentTask" type="checkbox" />仅当前任务</label>
<select id="historyStatusFilter">
<option value="">全部状态</option>
<option value="ok">ok</option>
<option value="warn">warn</option>
<option value="error">error</option>
</select>
<input id="historyActionFilter" placeholder="action_name如 worker_run_once" />
</div>
<div id="recentActionList" class="stack-list"></div>
</section>
</div>
<div class="panel-grid two-up">
<section class="panel">
<div class="panel-head">
<h3>Scheduler Queue</h3>
<button id="refreshSchedulerBtn" class="secondary compact">刷新</button>
</div>
<div id="schedulerSummary" class="summary-strip"></div>
<div id="schedulerList" class="stack-list"></div>
</section>
<section class="panel">
<div class="panel-head"><h3>Stage Scan Result</h3></div>
<div id="stageScanSummary" class="stack-list"></div>
</section>
</div>
<div class="panel-grid two-up">
<section class="panel">
<div class="panel-head"><h3>Doctor Checks</h3></div>
<div id="doctorChecks" class="stack-list"></div>
</section>
<section class="panel">
<div class="panel-head"><h3>Retry & Manual Attention</h3></div>
<div id="overviewRetrySummary" class="stack-list"></div>
</section>
</div>
<div class="panel-grid two-up">
<section class="panel">
<div class="panel-head"><h3>Modules</h3></div>
<div id="moduleList" class="stack-list"></div>
</section>
<section class="panel">
<div class="panel-head"><h3>Overview Notes</h3></div>
<div class="stack-list">
<div class="row-card">
<strong>先看 Health / Doctor</strong>
<div class="muted-note">系统级异常通常先体现在依赖检查,而不是单任务状态。</div>
</div>
<div class="row-card">
<strong>再看 Retry Summary</strong>
<div class="muted-note">优先处理已到重试时间和需要人工介入的任务。</div>
</div>
<div class="row-card">
<strong>最后看 Recent Actions</strong>
<div class="muted-note">用动作流判断最近系统是否真的在前进。</div>
</div>
</div>
</section>
</div>
</section>
<section class="view" data-view="tasks">
<div class="tasks-layout">
<section class="panel task-index-panel">
<div class="panel-head"><h3>Task Index</h3></div>
<div class="task-index-summary">
<div id="taskStatusSummary" class="summary-strip"></div>
<div class="task-pagination-toolbar">
<div id="taskPaginationSummary" class="muted-note">-</div>
<div class="button-row">
<select id="taskPageSizeSelect">
<option value="12">12 / page</option>
<option value="24" selected>24 / page</option>
<option value="48">48 / page</option>
</select>
<button id="taskPrevPageBtn" class="secondary compact">上一页</button>
<button id="taskNextPageBtn" class="secondary compact">下一页</button>
</div>
</div>
</div>
<div class="task-filters">
<input id="taskSearchInput" placeholder="搜索任务标题或 task id" />
<select id="taskStatusFilter">
<option value="">全部状态</option>
<option value="created">created</option>
<option value="transcribed">transcribed</option>
<option value="songs_detected">songs_detected</option>
<option value="split_done">split_done</option>
<option value="published">published</option>
<option value="collection_synced">collection_synced</option>
<option value="failed_retryable">failed_retryable</option>
<option value="failed_manual">failed_manual</option>
</select>
<select id="taskSortSelect">
<option value="updated_desc">最近更新</option>
<option value="updated_asc">最早更新</option>
<option value="title_asc">标题 A-Z</option>
<option value="title_desc">标题 Z-A</option>
<option value="attention_state">按关注状态</option>
<option value="status_group">按状态分组</option>
<option value="split_comment_status">按纯享评论</option>
<option value="full_comment_status">按主视频评论</option>
<option value="cleanup_state">按清理状态</option>
<option value="next_retry_asc">按下次重试</option>
</select>
<select id="taskDeliveryFilter">
<option value="">全部交付状态</option>
<option value="pending_comment">评论待完成</option>
<option value="cleanup_removed">已清理视频</option>
</select>
<select id="taskAttentionFilter">
<option value="">全部关注状态</option>
<option value="manual_now">仅看需人工</option>
<option value="retry_now">仅看到点重试</option>
<option value="waiting_retry">仅看等待重试</option>
</select>
</div>
<div id="taskListState" class="task-list-state">正在加载任务列表…</div>
<div id="taskList" class="task-table-wrap"></div>
</section>
<div class="task-workspace">
<section class="panel task-panel">
<div class="panel-head">
<h3>Task Detail</h3>
<div class="button-row">
<button id="runTaskBtn" class="secondary compact">执行当前任务</button>
<button id="retryStepBtn" class="secondary compact">重试选中 Step</button>
<button id="resetStepBtn" class="secondary compact">重置到选中 Step</button>
</div>
</div>
<div id="taskWorkspaceState" class="task-workspace-state show">选择一个任务后,这里会显示当前链路、重试状态和最近动作。</div>
<div id="taskHero" class="task-hero empty">选择一个任务后,这里会显示当前链路、重试状态和最近动作。</div>
<div id="taskRetryPanel" class="retry-banner hidden"></div>
<div class="detail-layout">
<div id="taskDetail" class="detail-grid"></div>
<div id="taskSummary" class="summary-card">暂无最近结果</div>
</div>
</section>
<section class="panel">
<div class="panel-head">
<h3>Session Workspace</h3>
<div class="button-row">
<button id="refreshSessionBtn" class="secondary compact">刷新 Session</button>
</div>
</div>
<div id="sessionWorkspaceState" class="task-workspace-state show">当前任务如果已绑定 session_key这里会显示同场片段和完整版绑定信息。</div>
<div id="sessionPanel" class="summary-card session-panel"></div>
</section>
<div class="panel-grid two-up">
<section class="panel">
<div class="panel-head"><h3>Steps</h3></div>
<div id="stepList" class="stack-list"></div>
</section>
<section class="panel">
<div class="panel-head"><h3>Artifacts</h3></div>
<div id="artifactList" class="stack-list"></div>
</section>
</div>
<div class="panel-grid two-up">
<section class="panel">
<div class="panel-head"><h3>History</h3></div>
<div id="historyList" class="stack-list"></div>
</section>
<section class="panel">
<div class="panel-head"><h3>Timeline</h3></div>
<div id="timelineList" class="timeline-list"></div>
</section>
</div>
</div>
</div>
</section>
<section class="view" data-view="settings">
<section class="panel">
<div class="panel-head"><h3>Settings</h3></div>
<div class="settings-toolbar">
<input id="settingsSearch" placeholder="过滤配置项,例如 codex / season / retry" />
<div class="button-row">
<button id="syncFormToJsonBtn" class="secondary compact">表单同步到 JSON</button>
<button id="syncJsonToFormBtn" class="secondary compact">JSON 重绘表单</button>
</div>
</div>
<div id="settingsForm" class="settings-groups"></div>
<details class="settings-advanced">
<summary>Advanced JSON Editor</summary>
<textarea id="settingsEditor" spellcheck="false"></textarea>
</details>
<p class="muted-note">敏感字段显示为 `__BILIUP_NEXT_SECRET__`。保留占位符表示不改原值,改为空字符串表示清空。</p>
</section>
</section>
<section class="view" data-view="logs">
<div class="logs-workspace">
<section class="panel logs-index-panel">
<div class="panel-head"><h3>Log Index</h3></div>
<div class="task-filters">
<input id="logSearchInput" placeholder="搜索日志文件名" />
<label class="checkbox-row"><input id="filterCurrentTask" type="checkbox" />按当前任务标题过滤</label>
<label class="checkbox-row"><input id="logAutoRefresh" type="checkbox" />自动刷新</label>
</div>
<div class="button-row" style="margin-bottom:12px;">
<button id="refreshLogBtn" class="secondary compact">刷新日志</button>
</div>
<div id="logListState" class="task-list-state show">正在加载日志索引…</div>
<div id="logList" class="task-list"></div>
</section>
<div class="log-content-stack">
<section class="panel">
<div class="panel-head"><h3>Log Content</h3></div>
<div class="filter-grid">
<input id="logLineFilter" placeholder="过滤内容关键字" />
<div class="muted-note" id="logPath">-</div>
<div class="muted-note" id="logMeta">-</div>
</div>
<pre id="logContent"></pre>
</section>
<section class="panel">
<div class="panel-head"><h3>Logs Guide</h3></div>
<div class="stack-list">
<div class="row-card">
<strong>优先看当前任务</strong>
<div class="muted-note">勾选“按当前任务标题过滤”,可快速聚焦任务链路。</div>
</div>
<div class="row-card">
<strong>先看系统,再看任务</strong>
<div class="muted-note">如果服务异常,先看 `systemd` 状态;如果单任务异常,再看 steps/history/timeline。</div>
</div>
<div class="row-card">
<strong>上传异常</strong>
<div class="muted-note">优先看 `upload.log`、任务时间线里的 publish error以及下一次重试时间。</div>
</div>
</div>
</section>
</div>
</div>
</section>
</main>
</div>
<script type="module" src="/assets/app/main.js"></script>
</body>
</html>
"""