6bf2b3619c
此前 checks、autoApprovePlan/Exec、agentRules、budgetUsd、models 只能经 API 改,
看板无编辑口。现扩展项目「配置」面板:
- 分项检查:lint / typecheck / build 三命令(拼成 checks JSON,全空=null)
- 硬闸/自动放行:autoApprovePlan / autoApproveExec 两个勾选
- 角色模型:executor/planner/reviewer/conflict 四个输入(填模型 id 字符串,
或 {"easy":..,"hard":..} JSON;留空=默认 opus-4.8;非法 JSON 提示报错)
- 项目 Agent 规范(agentRules)textarea
- 成本预算:budgetUsd + budgetPeriod(按天/月)
toggle-config 回填全部新字段;save-config 一并 PATCH。新增 zh/en i18n 标签 +
.cfg-subhead/.cfg-check 样式。web/ 静态、无需 rebuild。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
270 lines
19 KiB
HTML
270 lines
19 KiB
HTML
<!doctype html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>MAESTRO · 任务调度台</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&family=Noto+Sans+SC:wght@400;500;700&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="style.css">
|
||
</head>
|
||
<body>
|
||
<div id="app">
|
||
|
||
<!-- ════════ 左栏:项目 ════════ -->
|
||
<aside id="sidebar">
|
||
<div class="logo">
|
||
<div class="logo-word">MAESTRO<span class="cursor">▮</span></div>
|
||
<div class="logo-sub" data-i18n="logoSub">多项目任务调度台</div>
|
||
</div>
|
||
|
||
<div class="side-head">
|
||
<span class="head-mark">▍</span><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 7a2 2 0 0 1 2-2h4l2 3h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z"/></svg> <span data-i18n="projects">项目</span>
|
||
<button class="btn btn-ghost btn-xs" data-action="open-new-project"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="12" y1="4" x2="12" y2="20" stroke="var(--icon-accent,#5fdd7d)"/><line x1="4" y1="12" x2="20" y2="12" stroke="var(--icon-accent,#5fdd7d)"/></svg> <span data-i18n="newProject">新建</span></button>
|
||
</div>
|
||
<ul id="projectList" class="project-list"></ul>
|
||
|
||
<div class="side-foot">
|
||
<span id="wsDot" class="ws-dot off"></span>
|
||
<span id="wsText" data-i18n="connecting">连接中…</span>
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- ════════ 中栏:闸 + 任务树 ════════ -->
|
||
<main id="main">
|
||
<header id="topbar">
|
||
<div>
|
||
<div id="projTitle" class="proj-title">—</div>
|
||
<div id="projMeta" class="proj-meta"></div>
|
||
</div>
|
||
<div class="spacer"></div>
|
||
<div id="badgeGroup" class="badge-group" hidden>
|
||
<span id="bdgGate" class="bdg bdg-gate" data-i18n-title="badgeGateTip" title="等待你裁决的审批闸">
|
||
<span class="bdg-ico"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3L22 21L2 21Z"/><line x1="12" y1="10" x2="12" y2="14" stroke="var(--icon-accent,#b88ef5)"/><line x1="12" y1="17.5" x2="12" y2="17.51" stroke="var(--icon-accent,#b88ef5)"/></svg></span><span class="bdg-n">0</span><span class="bdg-label" data-i18n="badgeGate">待审批</span>
|
||
</span>
|
||
<span id="bdgReady" class="bdg bdg-ready" data-i18n-title="badgeReadyTip" title="可执行(依赖已满足)+ 排队中">
|
||
<span class="bdg-ico"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 4L19 12L7 20Z" stroke="var(--icon-accent,#5fdd7d)"/></svg></span><span class="bdg-n">0</span><span class="bdg-label" data-i18n="badgeReady">待执行</span>
|
||
</span>
|
||
<span id="bdgRun" class="bdg bdg-run" data-i18n-title="badgeRunTip" title="agent 正在执行">
|
||
<span class="bdg-ico"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="8"/><circle cx="12" cy="12" r="3" fill="var(--icon-accent,#59c8d8)" stroke="none"/></svg></span><span class="bdg-n">0</span><span class="bdg-label" data-i18n="badgeRun">执行中</span>
|
||
</span>
|
||
<span id="bdgBlocked" class="bdg bdg-blocked" data-i18n-title="badgeBlockedTip" title="被依赖阻塞(依赖完成后自动放行)">
|
||
<span class="bdg-ico"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/><line x1="10" y1="10" x2="14" y2="14" stroke="var(--icon-accent,#f0b429)"/></svg></span><span class="bdg-n">0</span><span class="bdg-label" data-i18n="badgeBlocked">被阻塞</span>
|
||
</span>
|
||
<span id="bdgTotal" class="bdg bdg-total" data-i18n-title="badgeTotalTip" title="未完成任务总量(不含 done / 已拆解容器 / 取消)">
|
||
<span class="bdg-ico">Σ</span><span class="bdg-n">0</span><span class="bdg-label" data-i18n="badgeTotal">总量</span>
|
||
</span>
|
||
</div>
|
||
<div id="topActions" class="top-actions" hidden>
|
||
<button class="btn btn-xs" data-action="toggle-config" data-i18n-title="configTip" title="项目配置(并发 / 工作模式 / todo 同步)"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="3" y1="6" x2="12" y2="6"/><circle cx="15" cy="6" r="2.5" stroke="var(--icon-accent,#5fdd7d)"/><line x1="18" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="5.5" y2="12"/><circle cx="9" cy="12" r="2.5" stroke="var(--icon-accent,#5fdd7d)"/><line x1="12.5" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="12.5" y2="18"/><circle cx="16" cy="18" r="2.5" stroke="var(--icon-accent,#5fdd7d)"/><line x1="19.5" y1="18" x2="21" y2="18"/></svg> <span data-i18n="config">配置</span></button>
|
||
</div>
|
||
<button id="langBtn" class="btn btn-xs btn-lang" data-action="toggle-lang" data-i18n-title="langTip" title="切换语言 / Switch language">中文</button>
|
||
</header>
|
||
|
||
<section id="configPanel" class="panel config-panel" hidden>
|
||
<div class="form-row">
|
||
<label class="narrow"><span data-i18n="maxConcurrency">最大并发</span>
|
||
<input id="cfgConcurrency" type="number" min="1" step="1" value="1">
|
||
</label>
|
||
<label><span data-i18n="workMode">工作模式</span>
|
||
<select id="cfgAutonomy">
|
||
<option value="manual">manual · 手动</option>
|
||
<option value="auto-easy">auto-easy · 自动执行 Easy</option>
|
||
<option value="auto-approved">auto-approved · 自动执行已批准</option>
|
||
</select>
|
||
</label>
|
||
<span id="cfgCurrent" class="cfg-current"></span>
|
||
<button class="btn btn-solid" data-action="save-config" data-i18n="save">保存配置</button>
|
||
<button class="btn" data-action="toggle-config" data-i18n="collapse">收起</button>
|
||
</div>
|
||
<div class="form-row">
|
||
<label class="grow"><span data-i18n="projLogo">Logo</span>
|
||
<input id="cfgLogo" type="text" data-i18n-ph="logoPh" placeholder="图片 URL 或仓库内相对路径;留空=自动(仓库 logo→git 头像→首字母)">
|
||
</label>
|
||
</div>
|
||
<div class="form-row">
|
||
<label class="grow"><span data-i18n="verifyCmd">校验命令</span>
|
||
<input id="cfgVerifyCmd" type="text" data-i18n-ph="verifyPh" placeholder="npm test · go test ./... (可空)" autocomplete="off">
|
||
</label>
|
||
<label class="grow"><span data-i18n="model">项目 Model</span>
|
||
<input id="cfgModel" type="text" data-i18n-ph="modelPh" placeholder="claude-opus-4-5(可空,留空用全局默认)" autocomplete="off">
|
||
</label>
|
||
</div>
|
||
|
||
<div class="cfg-subhead" data-i18n="cfgChecks">分项检查(lint / typecheck / build)</div>
|
||
<div class="form-row">
|
||
<label class="grow"><span data-i18n="cfgLint">lint 命令</span>
|
||
<input id="cfgLint" type="text" data-i18n-ph="cfgCmdPh" placeholder="留空=跳过此项" autocomplete="off">
|
||
</label>
|
||
<label class="grow"><span data-i18n="cfgTypecheck">typecheck 命令</span>
|
||
<input id="cfgTypecheck" type="text" data-i18n-ph="cfgCmdPh" placeholder="留空=跳过此项" autocomplete="off">
|
||
</label>
|
||
<label class="grow"><span data-i18n="cfgBuild">build 命令</span>
|
||
<input id="cfgBuild" type="text" data-i18n-ph="cfgCmdPh" placeholder="留空=跳过此项" autocomplete="off">
|
||
</label>
|
||
</div>
|
||
|
||
<div class="cfg-subhead" data-i18n="cfgGates">硬闸 / 自动放行</div>
|
||
<div class="form-row cfg-checks-row">
|
||
<label class="cfg-check"><input id="cfgAutoPlan" type="checkbox"> <span data-i18n="cfgAutoPlan">自动放行 plan</span></label>
|
||
<label class="cfg-check"><input id="cfgAutoExec" type="checkbox"> <span data-i18n="cfgAutoExec">自动放行 exec</span></label>
|
||
</div>
|
||
|
||
<div class="cfg-subhead" data-i18n="cfgModels">角色模型(留空=默认 opus-4.8)</div>
|
||
<div class="form-row">
|
||
<label class="grow"><span data-i18n="cfgMdlExecutor">executor</span>
|
||
<input id="cfgMdlExecutor" type="text" placeholder="claude-opus-4-8" autocomplete="off">
|
||
</label>
|
||
<label class="grow"><span data-i18n="cfgMdlPlanner">planner</span>
|
||
<input id="cfgMdlPlanner" type="text" placeholder="claude-opus-4-8" autocomplete="off">
|
||
</label>
|
||
<label class="grow"><span data-i18n="cfgMdlReviewer">reviewer</span>
|
||
<input id="cfgMdlReviewer" type="text" placeholder="claude-opus-4-8" autocomplete="off">
|
||
</label>
|
||
<label class="grow"><span data-i18n="cfgMdlConflict">conflict</span>
|
||
<input id="cfgMdlConflict" type="text" placeholder="claude-opus-4-8" autocomplete="off">
|
||
</label>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<label class="grow"><span data-i18n="cfgAgentRules">项目 Agent 规范</span>
|
||
<textarea id="cfgAgentRules" rows="3" data-i18n-ph="cfgAgentRulesPh" placeholder="项目专属执行约定、风格、禁忌……(可空)" autocomplete="off"></textarea>
|
||
</label>
|
||
</div>
|
||
<div class="form-row">
|
||
<label class="narrow"><span data-i18n="cfgBudget">成本预算</span>
|
||
<input id="cfgBudget" type="number" min="0" step="0.5" data-i18n-ph="cfgBudgetPh" placeholder="USD 上限(可空=不限)" autocomplete="off">
|
||
</label>
|
||
<label class="narrow"><span> </span>
|
||
<select id="cfgBudgetPeriod">
|
||
<option value="month" data-i18n="cfgBudgetMonth">按月</option>
|
||
<option value="day" data-i18n="cfgBudgetDay">按天</option>
|
||
</select>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="form-row cfg-sync-row">
|
||
<span id="syncMeta" class="sync-meta"></span>
|
||
<button id="btnSync" class="btn btn-xs" data-action="sync-todo"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/><path d="M21 3v5h-5" stroke="var(--icon-accent,#5fdd7d)"/></svg> <span data-i18n="sync">同步 todo</span></button>
|
||
</div>
|
||
</section>
|
||
|
||
<section id="agentSection" class="agent-section">
|
||
<div class="sec-head"><span class="head-mark cyan">▍</span><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="8"/><circle cx="12" cy="12" r="3" fill="var(--icon-accent,#59c8d8)" stroke="none"/></svg> <span data-i18n="agentSection">Agent 执行</span><span id="agentTotal" class="agent-total"></span></div>
|
||
<div id="agentBody"></div>
|
||
</section>
|
||
|
||
<section id="gateSection" class="gate-section" hidden>
|
||
<div class="gate-stripe"></div>
|
||
<div class="sec-head gate-head"><span class="head-mark violet">▍</span><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3L22 21L2 21Z"/><line x1="12" y1="10" x2="12" y2="14" stroke="var(--icon-accent,#b88ef5)"/><line x1="12" y1="17.5" x2="12" y2="17.51" stroke="var(--icon-accent,#b88ef5)"/></svg> <span data-i18n="gateSection">审批闸 · 等待裁决</span></div>
|
||
<div id="gateList"></div>
|
||
</section>
|
||
|
||
<section id="taskSection">
|
||
<div class="sec-head">
|
||
<span class="head-mark">▍</span><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="4" y="4" width="16" height="16" rx="2.5"/><polyline points="8 12 11 15 16 9" stroke="var(--icon-accent,#5fdd7d)"/></svg> <span data-i18n="taskSection">任务树</span>
|
||
<button class="btn btn-ghost btn-xs" data-action="toggle-new-task"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="12" y1="4" x2="12" y2="20" stroke="var(--icon-accent,#5fdd7d)"/><line x1="4" y1="12" x2="20" y2="12" stroke="var(--icon-accent,#5fdd7d)"/></svg> <span data-i18n="newTask">新建任务</span></button>
|
||
</div>
|
||
|
||
<form id="newTaskPanel" class="panel form-panel" hidden>
|
||
<div class="form-row">
|
||
<label class="grow"><span data-i18n="titleLabel">标题</span> <span class="req">*</span>
|
||
<input name="title" type="text" data-i18n-ph="titlePlaceholder" placeholder="要做什么" autocomplete="off">
|
||
</label>
|
||
</div>
|
||
<div class="form-row">
|
||
<label><span data-i18n="complexity">复杂度</span> <span class="req">*</span>
|
||
<span class="seg" id="cplxSeg">
|
||
<input type="radio" name="complexity" value="auto" id="cx-a" checked><label for="cx-a" class="seg-a" title="由模型判断复杂度">AUTO</label>
|
||
<input type="radio" name="complexity" value="hard" id="cx-h"><label for="cx-h" class="seg-h">HARD</label>
|
||
<input type="radio" name="complexity" value="medium" id="cx-m"><label for="cx-m" class="seg-m">MEDIUM</label>
|
||
<input type="radio" name="complexity" value="easy" id="cx-e"><label for="cx-e" class="seg-e">EASY</label>
|
||
</span>
|
||
</label>
|
||
<label><span data-i18n="parentTask">父任务</span>
|
||
<select name="parentId"><option value="">(顶层)</option></select>
|
||
</label>
|
||
<label class="narrow"><span data-i18n="priority">优先级</span>
|
||
<select name="priority">
|
||
<option value="0" data-i18n="p0">P0 · 高</option>
|
||
<option value="1" selected data-i18n="p1">P1 · 中</option>
|
||
<option value="2" data-i18n="p2">P2 · 低</option>
|
||
</select>
|
||
</label>
|
||
</div>
|
||
<div class="form-row form-actions">
|
||
<button type="submit" class="btn btn-solid" data-i18n="create">创建任务</button>
|
||
<button type="button" class="btn" data-action="toggle-new-task" data-i18n="cancel">取消</button>
|
||
</div>
|
||
</form>
|
||
|
||
<div id="filterBar" class="filter-bar" hidden>
|
||
<div class="filter-row">
|
||
<span class="filter-search-wrap">
|
||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="10.5" cy="10.5" r="6.5"/><line x1="15.5" y1="15.5" x2="21" y2="21" stroke="var(--icon-accent,#5fdd7d)"/></svg>
|
||
<input id="filterKw" type="text" data-i18n-ph="searchPh" placeholder="搜索标题…" autocomplete="off">
|
||
</span>
|
||
<span id="filterCplx" class="fchips"></span>
|
||
<span class="spacer"></span>
|
||
<span id="filterCount" class="filter-count"></span>
|
||
<button class="btn btn-ghost btn-xs" id="filterClear" data-action="filter-clear" hidden><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="5" y1="5" x2="19" y2="19"/><line x1="19" y1="5" x2="5" y2="19"/></svg> <span data-i18n="clearFilter">✕ 清除筛选</span></button>
|
||
</div>
|
||
<div id="filterStatus" class="filter-row"></div>
|
||
</div>
|
||
|
||
<div id="taskTree"></div>
|
||
</section>
|
||
|
||
<!-- ════════ 指标面板(执行时长 / 成功率 / 复审 / 重试 / 额度估算) ════════ -->
|
||
<section id="metricsSection" class="metrics-section" hidden>
|
||
<div class="sec-head"><span class="head-mark cyan">▍</span><span data-i18n="metricsSection">指标 · 健康度与成本</span><span id="metricsScope" class="metrics-scope"></span></div>
|
||
<div id="metricsBody"></div>
|
||
</section>
|
||
|
||
<!-- ════════ 已归档(done / 取消,时间倒序分页) ════════ -->
|
||
<section id="archiveSection" hidden>
|
||
<div class="sec-head"><span class="head-mark">▣</span><span data-i18n="archiveSection">已归档</span> · <span id="archiveCount">0</span></div>
|
||
<div id="archiveList"></div>
|
||
<div id="archivePager" class="archive-pager"></div>
|
||
</section>
|
||
</main>
|
||
|
||
<!-- ════════ 右栏:事件流 ════════ -->
|
||
<aside id="eventPanel">
|
||
<div class="sec-head"><span class="head-mark">▍</span><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="4" y1="12" x2="19" y2="12"/><polyline points="13 6 19 12 13 18" stroke="var(--icon-accent,#59c8d8)"/></svg> <span data-i18n="events">事件流</span></div>
|
||
<ul id="eventList" class="event-list"></ul>
|
||
</aside>
|
||
</div>
|
||
|
||
<!-- 新建项目 模态 -->
|
||
<div id="modalRoot" class="modal-root" hidden>
|
||
<div class="modal-mask" data-action="close-modal"></div>
|
||
<form id="newProjectForm" class="modal panel">
|
||
<div class="sec-head"><span class="head-mark">▍</span><span data-i18n="newProject">新建项目</span></div>
|
||
<label><span data-i18n="titleLabel">名称</span> <span class="req">*</span>
|
||
<input name="name" type="text" placeholder="my-project" autocomplete="off">
|
||
</label>
|
||
<label><span>Repo path</span> <span class="req">*</span>
|
||
<input name="repoPath" type="text" placeholder="/path/to/repo" autocomplete="off">
|
||
</label>
|
||
<label><span data-i18n="branch">Default branch</span>
|
||
<input name="defaultBranch" type="text" placeholder="main">
|
||
</label>
|
||
<label><span data-i18n="verifyCmd">Verify command</span>
|
||
<input name="verifyCmd" type="text" data-i18n-ph="verifyPh" placeholder="npm test(可空)">
|
||
</label>
|
||
<div class="form-row form-actions">
|
||
<button type="submit" class="btn btn-solid" data-i18n="create">创建</button>
|
||
<button type="button" class="btn" data-action="close-modal" data-i18n="cancel">取消</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<div id="previewRoot" class="preview-root" hidden></div>
|
||
<div id="archiveModalRoot" class="preview-root" hidden></div>
|
||
<div id="toastRoot" class="toast-root"></div>
|
||
<script src="app.js"></script>
|
||
</body>
|
||
</html>
|