Files
BlueArchiveToolkit/web/index.html
T
nyaKazuha 4ed81f0030
bat-rust / Build and test Rust (push) Canceled after 0s
bat-rust / Build and test Go API (push) Canceled after 0s
feat(bat-api): 实现内嵌 dashboard
Closes #46
2026-08-31 23:17:23 +08:00

422 lines
17 KiB
HTML

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>bat-api Dashboard - BlueArchiveToolkit</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="shell">
<aside class="sidebar">
<div class="brand">
<div class="brand-mark" aria-hidden="true">BA</div>
<div>
<div class="brand-title">bat-api Dashboard</div>
<div class="brand-subtitle">BlueArchiveToolkit</div>
</div>
</div>
<nav class="nav" aria-label="dashboard navigation">
<button class="nav-item is-active" type="button" data-view="dashboard">总览</button>
<button class="nav-item" type="button" data-view="resources">资源</button>
<button class="nav-item" type="button" data-view="schedules">调度</button>
<button class="nav-item" type="button" data-view="translation">翻译</button>
<button class="nav-item" type="button" data-view="tasks">任务</button>
<button class="nav-item" type="button" data-view="logs">日志</button>
<button class="nav-item" type="button" data-view="settings">设置</button>
</nav>
<div class="connection-box">
<span class="state-dot" id="connectionDot"></span>
<div>
<div class="connection-title" id="connectionTitle">未连接</div>
<div class="connection-subtitle" id="connectionSubtitle">等待刷新</div>
</div>
</div>
</aside>
<main class="main">
<header class="topbar">
<div>
<h1 id="viewTitle">总览</h1>
<p id="viewSubtitle">读取 bat-api 与 Rust daemon 的当前状态。</p>
</div>
<div class="topbar-actions">
<label class="inline-check">
<input id="autoRefreshInput" type="checkbox" checked>
自动刷新
</label>
<button class="btn btn-primary" id="refreshAllBtn" type="button">刷新</button>
</div>
</header>
<section class="view is-active" id="view-dashboard" data-title="总览" data-subtitle="读取 bat-api 与 Rust daemon 的当前状态。">
<div class="status-grid">
<div class="metric">
<div class="metric-label">bat-api</div>
<div class="metric-value" id="metricHealth">unknown</div>
<div class="metric-note" id="metricHealthNote">-</div>
</div>
<div class="metric">
<div class="metric-label">Rust RPC</div>
<div class="metric-value" id="metricRpc">unknown</div>
<div class="metric-note" id="metricRpcNote">-</div>
</div>
<div class="metric">
<div class="metric-label">资源完整度</div>
<div class="metric-value" id="metricResources">-</div>
<div class="metric-note" id="metricResourcesNote">-</div>
</div>
<div class="metric">
<div class="metric-label">汉化状态</div>
<div class="metric-value" id="metricLocalized">unknown</div>
<div class="metric-note" id="metricLocalizedNote">需要 admin token</div>
</div>
</div>
<div class="grid two-col">
<section class="panel">
<div class="panel-head">
<div>
<h2>当前 release</h2>
<p id="releaseSummary">尚未读取</p>
</div>
</div>
<dl class="kv-list" id="releaseDetails"></dl>
</section>
<section class="panel">
<div class="panel-head">
<div>
<h2>最近任务</h2>
<p id="taskSummary">需要 admin token</p>
</div>
<button class="btn btn-small" type="button" data-jump="tasks">打开任务</button>
</div>
<div class="list" id="dashboardTaskList"></div>
</section>
</div>
<section class="panel">
<div class="panel-head">
<div>
<h2>资源样本</h2>
<p id="resourceSampleSummary">读取 /v1/resources</p>
</div>
<button class="btn btn-small" type="button" data-jump="resources">查看资源</button>
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>路径</th>
<th>大小</th>
<th>状态</th>
<th>BLAKE3</th>
</tr>
</thead>
<tbody id="dashboardResourceRows"></tbody>
</table>
</div>
</section>
</section>
<section class="view" id="view-resources" data-title="资源" data-subtitle="查看当前发布 release 的资源清单,并触发同步、校验或修复。">
<section class="panel">
<div class="panel-head split">
<div>
<h2>资源清单</h2>
<p id="resourceListSummary">-</p>
</div>
<div class="row-actions">
<button class="btn" type="button" data-control="sync">同步</button>
<button class="btn" type="button" data-control="verify">校验</button>
<button class="btn btn-danger" type="button" data-control="repair">修复</button>
</div>
</div>
<div class="filter-row">
<input id="resourceFilterInput" type="search" placeholder="过滤已加载路径">
<select id="resourceLimitInput">
<option value="20">20</option>
<option value="50" selected>50</option>
<option value="100">100</option>
<option value="200">200</option>
</select>
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>路径</th>
<th>大小</th>
<th>状态</th>
<th>BLAKE3</th>
</tr>
</thead>
<tbody id="resourceRows"></tbody>
</table>
</div>
<div class="pager">
<span id="resourcePagerText">-</span>
<div>
<button class="btn btn-small" id="resourcePrevBtn" type="button">上一页</button>
<button class="btn btn-small" id="resourceNextBtn" type="button">下一页</button>
</div>
</div>
</section>
</section>
<section class="view" id="view-schedules" data-title="调度" data-subtitle="管理 Rust 持久化 schedule,覆盖资源拉取、解析、翻译和发布任务。">
<div class="grid two-col">
<section class="panel">
<div class="panel-head">
<div>
<h2>计划列表</h2>
<p id="scheduleSummary">需要 admin token</p>
</div>
<button class="btn btn-small" id="scheduleRefreshBtn" type="button">刷新</button>
</div>
<div class="filter-row">
<input id="scheduleIdFilter" type="search" placeholder="id">
<input id="scheduleGroupFilter" type="search" placeholder="group: res / parse / i18n">
<select id="scheduleEnabledFilter">
<option value="">全部</option>
<option value="true">启用</option>
<option value="false">停用</option>
</select>
</div>
<div class="list" id="scheduleList"></div>
</section>
<section class="panel">
<div class="panel-head">
<div>
<h2>新增或更新</h2>
<p>schedule.add / schedule.update</p>
</div>
</div>
<form class="form" id="scheduleForm">
<label>ID <input id="scheduleIdInput" autocomplete="off"></label>
<label>Group <input id="scheduleGroupInput" placeholder="res"></label>
<label>Command <input id="scheduleActionInput" placeholder="res sync"></label>
<label>Args <textarea id="scheduleArgsInput" rows="3" placeholder="每行一个参数"></textarea></label>
<div class="form-grid">
<label>Delay seconds <input id="scheduleDelayInput" type="number" min="0"></label>
<label>Every seconds <input id="scheduleEveryInput" type="number" min="1"></label>
<label>Count <input id="scheduleCountInput" type="number" min="1"></label>
<label>Next run unix <input id="scheduleNextRunInput" type="number" min="0"></label>
</div>
<label class="inline-check"><input id="scheduleEnabledInput" type="checkbox" checked>启用</label>
<div class="row-actions">
<button class="btn btn-primary" id="scheduleAddBtn" type="button">新增</button>
<button class="btn" id="scheduleUpdateBtn" type="button">更新</button>
</div>
</form>
</section>
</div>
</section>
<section class="view" id="view-translation" data-title="翻译" data-subtitle="查看翻译任务、触发 provider worker、编辑人工校对结果并发布汉化资源。">
<div class="grid two-col">
<section class="panel">
<div class="panel-head split">
<div>
<h2>翻译任务</h2>
<p id="translationTaskSummary">需要 admin token</p>
</div>
<button class="btn btn-small" id="translationRefreshBtn" type="button">刷新</button>
</div>
<div class="filter-row">
<select id="translationStatusFilter">
<option value="">全部状态</option>
<option value="queued">queued</option>
<option value="running">running</option>
<option value="failed">failed</option>
<option value="completed">completed</option>
<option value="skipped">skipped</option>
</select>
<input id="translationDestinationFilter" type="search" placeholder="destination">
<input id="translationTaskFilter" type="search" placeholder="task_id">
</div>
<div class="table-wrap compact">
<table>
<thead>
<tr>
<th>任务</th>
<th>资源</th>
<th>状态</th>
<th>结果</th>
</tr>
</thead>
<tbody id="translationTaskRows"></tbody>
</table>
</div>
</section>
<section class="panel">
<div class="panel-head">
<div>
<h2>Provider worker</h2>
<p>translation.worker.run</p>
</div>
</div>
<form class="form">
<div class="form-grid">
<label>Provider
<select id="workerProviderInput">
<option value="mock">mock</option>
<option value="crowdin">crowdin</option>
</select>
</label>
<label>Concurrency <input id="workerConcurrencyInput" type="number" min="1" max="256" value="8"></label>
<label>Max attempts <input id="workerMaxAttemptsInput" type="number" min="1" value="3"></label>
<label>Lease seconds <input id="workerLeaseInput" type="number" min="1" value="300"></label>
<label>Backoff seconds <input id="workerBackoffInput" type="number" min="0" value="5"></label>
<label>Max tasks <input id="workerMaxTasksInput" type="number" min="1"></label>
</div>
<label>Worker ID <input id="workerIdInput" placeholder="dashboard-worker"></label>
<label>Fixture path <input id="workerFixtureInput" placeholder="/tmp/mock-provider.json"></label>
<button class="btn btn-primary" id="workerRunBtn" type="button">启动 worker</button>
</form>
</section>
</div>
<section class="panel">
<div class="panel-head split">
<div>
<h2>人工校对</h2>
<p id="translationDetailSummary">选择一个翻译任务</p>
</div>
<div class="row-actions">
<button class="btn" id="proofreadBtn" type="button">标记人工校对中</button>
<button class="btn btn-primary" id="manualSaveBtn" type="button">保存校对结果</button>
</div>
</div>
<div class="form manual-meta">
<label>Provider run ID <input id="manualProviderRunInput" placeholder="留空时由 Rust 生成"></label>
</div>
<div class="unit-editor" id="unitEditor"></div>
</section>
<div class="grid two-col">
<section class="panel">
<div class="panel-head">
<div>
<h2>汉化发布</h2>
<p id="localizedSummary">需要 admin token</p>
</div>
</div>
<div class="form">
<label>Localized release ID <input id="localizedReleaseInput" placeholder="可选"></label>
<label class="inline-check"><input id="localizedForceInput" type="checkbox">强制发布</label>
<div class="row-actions">
<button class="btn btn-primary" id="localizedPublishBtn" type="button">从 worker 结果发布</button>
<button class="btn btn-danger" id="localizedRollbackBtn" type="button">回滚</button>
</div>
</div>
</section>
<section class="panel">
<div class="panel-head">
<div>
<h2>交接视图</h2>
<p id="handoffSummary">translation.handoff</p>
</div>
</div>
<dl class="kv-list" id="handoffDetails"></dl>
</section>
</div>
</section>
<section class="view" id="view-tasks" data-title="任务" data-subtitle="轮询 Rust daemon 持久化任务,查看状态、日志并发起取消。">
<section class="panel">
<div class="panel-head split">
<div>
<h2>任务列表</h2>
<p id="daemonTaskSummary">需要 admin token</p>
</div>
<button class="btn btn-small" id="daemonTaskRefreshBtn" type="button">刷新</button>
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Task ID</th>
<th>Kind</th>
<th>Status</th>
<th>Stage</th>
<th>Updated</th>
</tr>
</thead>
<tbody id="daemonTaskRows"></tbody>
</table>
</div>
</section>
<section class="panel">
<div class="panel-head split">
<div>
<h2>任务详情</h2>
<p id="daemonTaskDetailSummary">选择一个任务</p>
</div>
<button class="btn btn-danger" id="daemonTaskCancelBtn" type="button">取消任务</button>
</div>
<dl class="kv-list" id="daemonTaskDetails"></dl>
<pre class="log-box" id="daemonTaskLogBox"></pre>
</section>
</section>
<section class="view" id="view-logs" data-title="日志" data-subtitle="读取 Rust daemon 诊断和最近日志。">
<div class="grid two-col">
<section class="panel">
<div class="panel-head split">
<div>
<h2>诊断</h2>
<p id="doctorSummary">需要 admin token</p>
</div>
<button class="btn btn-small" id="doctorRefreshBtn" type="button">刷新</button>
</div>
<div class="list" id="doctorChecks"></div>
</section>
<section class="panel">
<div class="panel-head split">
<div>
<h2>Daemon log</h2>
<p id="logSummary">需要 admin token</p>
</div>
<label class="inline-label">Tail <input id="logTailInput" type="number" min="1" max="2000" value="200"></label>
</div>
<pre class="log-box" id="daemonLogBox"></pre>
</section>
</div>
</section>
<section class="view" id="view-settings" data-title="设置" data-subtitle="配置 dashboard 调用 bat-api 的地址和 admin token。">
<section class="panel settings-panel">
<div class="panel-head">
<div>
<h2>连接</h2>
<p>默认使用当前页面同源 bat-api。</p>
</div>
</div>
<form class="form">
<label>Base URL <input id="baseUrlInput" autocomplete="off"></label>
<label>Admin token <input id="tokenInput" type="password" autocomplete="off"></label>
<label class="inline-check"><input id="rememberTokenInput" type="checkbox">在本浏览器保存 token</label>
<div class="row-actions">
<button class="btn btn-primary" id="saveSettingsBtn" type="button">保存并刷新</button>
<button class="btn" id="forgetSettingsBtn" type="button">清除</button>
</div>
</form>
</section>
</section>
</main>
</div>
<div class="toast-region" id="toastRegion" aria-live="polite"></div>
<script src="app.js"></script>
</body>
</html>