(() => { 'use strict'; const $ = (selector) => document.querySelector(selector); const openButton = $('#gitea-workflow-open'); const panel = $('#gitea-workflow-panel'); const root = $('#gitea-workflow-root'); const status = $('#gitea-workflow-status'); const refreshButton = $('#gitea-workflow-refresh'); const closeButton = $('#gitea-workflow-close'); if (!openButton || !panel || !root) return; const SYSTEM_AUDIT_ACTOR = 'system'; const SYSTEM_AUDIT_REASON = 'cc-web 管理页自动操作'; let overview = null; let loading = false; function escapeHtml(value) { return String(value == null ? '' : value) .replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>') .replaceAll('"', '"').replaceAll("'", '''); } function token() { return localStorage.getItem('cc-web-token') || ''; } function api(path, init = {}) { const headers = { ...(init.body ? { 'Content-Type': 'application/json' } : {}), ...(init.headers || {}) }; if (token()) headers.Authorization = `Bearer ${token()}`; return fetch(path, { ...init, headers }).then(async (response) => { const data = await response.json().catch(() => ({})); if (!response.ok) throw Object.assign(new Error(data.message || data.code || `HTTP ${response.status}`), { response, data }); return data; }); } function stateLabel(task) { return task.stateLabel || ({ queued: '排队中', preparing: '准备中', running: '运行中', waiting_user: '等待用户', verifying_reply: '核验回帖', retry_wait: '等待重试', blocked_workspace: '工作区阻塞', aborting: '中止中', succeeded: '已完成', succeeded_with_rest_fallback: '已完成(REST 兜底)', failed: '失败', failed_reply: '回帖失败', cancelled: '已取消', aborted: '已中止' }[task.state] || task.state); } function date(value) { if (!value) return '—'; const parsed = new Date(value); return Number.isNaN(parsed.getTime()) ? escapeHtml(value) : escapeHtml(parsed.toLocaleString()); } function actionLabel(value) { const labels = { 'admin.pause': '暂停工作流', 'admin.resume': '恢复工作流', 'admin.enable': '启用仓库', 'admin.disable': '停用仓库', 'admin.cancel': '取消排队', 'admin.abort': '中止 Turn', 'admin.configuration_updated': '更新配置', }; return labels[value] || String(value || '工作流操作').replace(/^admin\./, ''); } function systemAuditFields(requestId = '') { const payload = { actor: SYSTEM_AUDIT_ACTOR, reason: SYSTEM_AUDIT_REASON, }; if (requestId) payload.requestId = requestId; return payload; } function actionButtonLabel(paused) { return paused ? '恢复全局' : '暂停全局'; } function render() { const data = overview || { summary: {}, control: {}, repositories: [], tasks: [], audits: [], logs: [] }; const control = data.control || {}; const summary = data.summary || {}; const paused = !!control.globalPaused; const repositories = Array.isArray(data.repositories) ? data.repositories : []; const tasks = Array.isArray(data.tasks) ? data.tasks : []; const audits = Array.isArray(data.audits) ? data.audits : []; const logs = Array.isArray(data.logs) ? data.logs : []; const settings = data.settings || {}; root.innerHTML = `
${paused ? '全局已暂停' : '工作流运行中'}
${paused ? '工作流已暂停' : '暂停只阻止新任务领取,运行中任务不受影响'} · ${date(data.generatedAt)}
${!data.available ? '
核心 Workflow 服务尚未挂接:当前页面只展示已持久化的镜像状态,控制操作会安全返回 503。
' : ''}

Gitea 连接与工作区配置

运行中${Number(summary.running || 0)}
队列${Number(summary.queued || 0)}
仓库${Number(summary.repositories || 0)}
脏目录 / 阻塞${Number(summary.blocked || 0)}

仓库与工作区

${repositories.length ? repositories.map((repo) => ``).join('') : ''}
仓库状态工作区脏目录原因队列操作
${escapeHtml(repo.owner ? `${repo.owner}/${repo.name}` : repo.repoKey)}
${escapeHtml(repo.repoKey)}
${repo.enabled ? '启用' : '停用'}${escapeHtml(repo.workspacePath || '—')}${repo.dirty ? escapeHtml(repo.dirtyReason || '检测到未提交修改或冲突') : '—'}${Number(repo.queuedCount || 0)}
暂无仓库记录

任务队列与 Turn

${tasks.length ? tasks.map((task) => ``).join('') : ''}
任务 / 资源状态Turn脏目录原因更新时间操作
${escapeHtml(task.taskId || '—')}
${escapeHtml(task.resourceKey || task.repoKey || '—')}
${escapeHtml(stateLabel(task))}${task.errorMessage ? `
${escapeHtml(task.errorMessage)}` : ''}
${escapeHtml(task.turnId || '—')}
${escapeHtml(task.turnState || '—')}
${escapeHtml(task.dirtyReason || '—')}${date(task.updatedAt || task.turnUpdatedAt || task.createdAt)}${task.state === 'queued' || task.state === 'waiting_user' ? `` : ''}${task.state === 'running' || task.state === 'preparing' || task.state === 'aborting' ? `` : ''}
暂无任务记录

运行日志

${logs.length ? logs.slice().reverse().map((entry) => `
${date(entry.timestamp)}${escapeHtml(entry.level)}${escapeHtml(entry.message || entry.event || '—')}
`).join('') : '
暂无日志
'}

操作记录

${audits.length ? audits.slice().reverse().map((entry) => ``).join('') : ''}
时间动作任务 / 仓库
${date(entry.timestamp)}${escapeHtml(actionLabel(entry.action))}${escapeHtml(entry.taskId || entry.repoKey || '—')}
暂无记录
`; root.querySelectorAll('[data-action], [data-repo-action], [data-task-action], [data-config-save]').forEach((button) => button.addEventListener('click', button.dataset.configSave !== undefined ? saveConfig : onAction)); } async function load() { if (loading) return; loading = true; status.textContent = '正在刷新工作流状态…'; try { const [nextOverview, config] = await Promise.all([api('/api/gitea-workflow/overview'), api('/api/gitea-workflow/config')]); overview = { ...nextOverview, settings: config.settings || {} }; render(); status.textContent = overview.available ? '状态已更新' : '核心服务未挂接,显示镜像状态'; } catch (error) { root.innerHTML = `
加载失败:${escapeHtml(error.message)}
`; status.textContent = '加载失败'; } finally { loading = false; } } async function saveConfig(event) { const button = event.currentTarget; button.disabled = true; try { await api('/api/gitea-workflow/config', { method: 'PUT', body: JSON.stringify({ ...systemAuditFields(), host: $('#gitea-config-host')?.value || '', botLogin: $('#gitea-config-bot-login')?.value || '', workspaceRoot: $('#gitea-config-workspace')?.value || '', defaultBranch: $('#gitea-config-branch')?.value || '', botToken: $('#gitea-config-bot-token')?.value || '', }) }); await load(); } catch (error) { window.alert(`配置保存失败:${error.message}`); } finally { button.disabled = false; } } async function onAction(event) { const button = event.currentTarget; button.disabled = true; try { let path; let method = 'POST'; if (button.dataset.action) path = `/api/gitea-workflow/control/${button.dataset.action}`; if (button.dataset.repoAction) path = `/api/gitea-workflow/repos/${encodeURIComponent(button.dataset.repoKey)}/${button.dataset.repoAction}`; if (button.dataset.taskAction) path = `/api/gitea-workflow/tasks/${encodeURIComponent(button.dataset.taskId)}/${button.dataset.taskAction}`; await api(path, { method, body: JSON.stringify({ ...systemAuditFields(window.crypto?.randomUUID?.() || `${Date.now()}-${Math.random()}`), }) }); await load(); } catch (error) { window.alert(`操作未执行:${error.message}`); button.disabled = false; } } function open() { panel.hidden = false; panel.setAttribute('aria-hidden', 'false'); openButton.setAttribute('aria-expanded', 'true'); load(); } function close() { panel.hidden = true; panel.setAttribute('aria-hidden', 'true'); openButton.setAttribute('aria-expanded', 'false'); } openButton.addEventListener('click', open); closeButton?.addEventListener('click', close); refreshButton?.addEventListener('click', load); })();