forked from Gitlink/gitlink-cli
refactor(showcase): 重构工作流展示为按工作流分块的操作面板
每个工作流独立成卡片,包含:执行(AI模式可选)/启动守护/停止/状态/日志/详情 顶部保留全局命令:列出所有工作流、生成systemd服务
This commit is contained in:
parent
fdb6e115d9
commit
cb2e401a83
|
|
@ -110,20 +110,39 @@
|
|||
.example-btn { background: none; border: 1px solid #52c41a; color: #52c41a; padding: 2px 10px; border-radius: 4px; cursor: pointer; font-size: 0.75em; margin-top: 4px; }
|
||||
.example-btn:hover { background: #52c41a; color: white; }
|
||||
|
||||
.wf-subsection { margin-bottom: 20px; }
|
||||
.wf-subsection h3 { font-size: 1.05em; margin-bottom: 12px; color: #722ed1; }
|
||||
.wf-def-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); gap: 16px; margin-bottom: 28px; }
|
||||
.wf-def-card { background: white; border-radius: 10px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); padding: 16px 18px; border-top: 4px solid #722ed1; }
|
||||
.wf-def-card .wf-top { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; }
|
||||
.wf-def-card .wf-name { font-weight: 700; font-size: 1em; font-family: monospace; color: #722ed1; }
|
||||
.wf-def-card .wf-cat { font-size: 0.7em; background: #f3e8ff; color: #722ed1; padding: 2px 8px; border-radius: 10px; }
|
||||
.wf-def-card .wf-desc { color: #666; font-size: 0.85em; margin-bottom: 8px; line-height: 1.4; }
|
||||
.wf-def-card .wf-trigger { font-size: 0.78em; color: #999; margin-bottom: 8px; }
|
||||
.wf-def-card .wf-trigger code { background: #f5f5f5; padding: 1px 6px; border-radius: 3px; font-size: 0.95em; }
|
||||
.wf-def-card .wf-steps { font-size: 0.78em; color: #888; }
|
||||
.wf-def-card .wf-steps summary { cursor: pointer; color: #722ed1; font-weight: 500; margin-bottom: 4px; }
|
||||
.wf-def-card .wf-steps ol { padding-left: 18px; line-height: 1.6; }
|
||||
.wf-commands { background: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); overflow: hidden; }
|
||||
.wf-panel { background: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); margin-bottom: 24px; overflow: hidden; }
|
||||
.wf-panel-header { padding: 16px 20px; cursor: pointer; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #f0f0f0; background: #faf6ff; }
|
||||
.wf-panel-header:hover { background: #f3e8ff; }
|
||||
.wf-panel-header .wf-name { font-weight: 700; font-size: 1.05em; font-family: 'SFMono-Regular', Consolas, monospace; color: #722ed1; }
|
||||
.wf-panel-header .wf-meta { display: flex; align-items: center; gap: 10px; }
|
||||
.wf-panel-header .wf-cat { font-size: 0.7em; background: #f3e8ff; color: #722ed1; padding: 2px 8px; border-radius: 10px; }
|
||||
.wf-panel-header .wf-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
|
||||
.wf-panel-header .wf-dot.running { background: #52c41a; box-shadow: 0 0 4px #52c41a; }
|
||||
.wf-panel-header .wf-dot.stopped { background: #d9d9d9; }
|
||||
.wf-panel-header .arrow { transition: transform 0.3s; font-size: 0.8em; color: #999; }
|
||||
.wf-panel-header .arrow.open { transform: rotate(180deg); }
|
||||
.wf-panel-body.collapsed { display: none; }
|
||||
.wf-panel-body { padding: 0; }
|
||||
.wf-panel-desc { padding: 12px 20px; color: #666; font-size: 0.9em; border-bottom: 1px solid #f0f0f0; background: #fcfcfc; }
|
||||
.wf-panel-trigger { padding: 8px 20px; font-size: 0.8em; color: #999; border-bottom: 1px solid #f0f0f0; }
|
||||
.wf-panel-trigger code { background: #f5f5f5; padding: 1px 6px; border-radius: 3px; }
|
||||
.wf-panel-steps { padding: 8px 20px; border-bottom: 1px solid #f0f0f0; }
|
||||
.wf-panel-steps summary { cursor: pointer; color: #722ed1; font-weight: 500; font-size: 0.85em; }
|
||||
.wf-panel-steps ol { padding-left: 18px; line-height: 1.6; font-size: 0.8em; color: #888; }
|
||||
.wf-actions { padding: 12px 20px; display: flex; flex-wrap: wrap; gap: 8px; align-items: flex-end; }
|
||||
.wf-actions .action-btn { color: white; border: none; padding: 6px 14px; border-radius: 6px; cursor: pointer; font-size: 0.85em; white-space: nowrap; }
|
||||
.wf-actions .action-btn:hover { opacity: 0.85; }
|
||||
.wf-actions .action-btn:disabled { background: #d9d9d9; cursor: not-allowed; }
|
||||
.wf-actions .btn-run { background: #1890ff; }
|
||||
.wf-actions .btn-start { background: #52c41a; }
|
||||
.wf-actions .btn-stop { background: #ff4d4f; }
|
||||
.wf-actions .btn-info { background: #722ed1; }
|
||||
.wf-actions .btn-status { background: #faad14; color: #333; }
|
||||
.wf-actions .btn-logs { background: #666; }
|
||||
.wf-actions .action-select { border: 1px solid #d9d9d9; border-radius: 4px; padding: 5px 8px; font-size: 0.85em; }
|
||||
.wf-global { background: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); padding: 12px 20px; margin-bottom: 24px; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
|
||||
.wf-global span { font-weight: 600; color: #722ed1; font-size: 0.95em; }
|
||||
.wf-global .action-btn { color: white; border: none; padding: 5px 12px; border-radius: 6px; cursor: pointer; font-size: 0.85em; white-space: nowrap; }
|
||||
|
||||
.placeholder-card { background: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); padding: 60px 40px; text-align: center; color: #999; }
|
||||
.placeholder-card .ph-icon { font-size: 3em; margin-bottom: 16px; }
|
||||
|
|
@ -443,78 +462,68 @@ const WORKFLOWS = [
|
|||
steps:["获取提交历史","获取 Issue/PR 贡献数据","获取成员列表","[AI] 统计贡献排行","[AI] 计算 30 天活跃度趋势","[AI] 识别新星贡献者和流失风险"] },
|
||||
];
|
||||
|
||||
const WORKFLOW_COMMANDS = [
|
||||
{ name:"list", desc:"列出所有可用工作流", detail:"按类别列出全部预置工作流,支持 --category 按类别过滤", params:[{k:"category",l:"类别过滤",v:""}] },
|
||||
{ name:"info", desc:"查看工作流详情", detail:"查看指定工作流的步骤列表、触发方式和说明", params:[{k:"name",l:"工作流名称",v:"community-ops"}] },
|
||||
{ name:"run", desc:"执行工作流", detail:"手动执行一个工作流。默认自动检测 AI Key,无 Key 时用规则引擎", params:[{k:"name",l:"工作流名称",v:"community-ops"},{k:"no-ai",l:"仅规则引擎",v:"true",bool:true},{k:"dry-run",l:"预览模式",v:"false",bool:true}] },
|
||||
{ name:"init", desc:"项目一键初始化", detail:"运行内置 project-init 工作流,检查并补齐仓库缺失项", params:[{k:"dry-run",l:"预览模式",v:"false",bool:true},{k:"no-ai",l:"仅规则引擎",v:"true",bool:true}] },
|
||||
{ name:"watch", desc:"监听变化触发工作流", detail:"轮询检测数据变化,有变更时自动触发工作流", params:[{k:"name",l:"工作流名称",v:"community-ops"},{k:"interval",l:"轮询间隔",v:"5m"}] },
|
||||
{ name:"schedule", desc:"定时执行工作流", detail:"按固定间隔周期执行工作流", params:[{k:"name",l:"工作流名称",v:"community-ops"},{k:"interval",l:"执行间隔",v:"24h"}] },
|
||||
{ name:"start", desc:"后台启动守护进程", detail:"在后台以守护进程模式运行工作流,定时巡检", params:[{k:"name",l:"工作流名称",v:"community-ops"},{k:"interval",l:"轮询间隔",v:"5m"}] },
|
||||
{ name:"stop", desc:"停止守护进程", detail:"停止指定工作流的后台守护进程", params:[{k:"name",l:"工作流名称",v:"community-ops"}] },
|
||||
{ name:"status", desc:"查看守护进程状态", detail:"查看指定工作流的后台守护进程运行状态", params:[{k:"name",l:"工作流名称",v:"community-ops"}] },
|
||||
{ name:"logs", desc:"查看守护进程日志", detail:"查看/跟踪指定工作流的后台守护进程输出日志", params:[{k:"name",l:"工作流名称",v:"community-ops"},{k:"follow",l:"持续跟踪",v:"false",bool:true}] },
|
||||
{ name:"install-systemd", desc:"生成 systemd 服务", detail:"生成 Linux systemd unit 文件,用于开机自启和系统级管理", params:[{k:"name",l:"工作流名称",v:"community-ops"},{k:"interval",l:"轮询间隔",v:"5m"}] },
|
||||
];
|
||||
|
||||
(function renderPart1() {
|
||||
const container = document.getElementById('modules');
|
||||
MODULES.forEach(mod => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'module-card';
|
||||
const cmdHTML = mod.commands.map(cmd => {
|
||||
if (cmd.section) return '<div class="cmd-section">' + cmd.section + '</div>';
|
||||
const uid = cmd.module ? mod.name + '-' + cmd.module + '-' + cmd.name : mod.name + '-' + cmd.name;
|
||||
const paramsHTML = cmd.params.map(function(p) {
|
||||
if (p.bool) return '<div class="param-group"><label>' + p.l + '</label><select id="p-' + uid + '-' + p.k + '"><option value="false"' + (p.v !== 'true' ? ' selected' : '') + '>否</option><option value="true"' + (p.v === 'true' ? ' selected' : '') + '>是</option></select></div>';
|
||||
return '<div class="param-group"><label>' + p.l + '</label><input id="p-' + uid + '-' + p.k + '" value="' + p.v + '" placeholder="' + p.l + '"></div>';
|
||||
}).join('');
|
||||
return '<div class="cmd-row"><div class="cmd-top"><span class="cmd-name">+' + cmd.name + '</span><span class="cmd-desc">' + cmd.desc + '</span><button class="cmd-toggle" onclick="toggleDetail(\'' + uid + '\')">详情</button></div><div class="cmd-detail" id="detail-' + uid + '">' + cmd.detail + '</div><div class="cmd-params" id="params-' + uid + '">' + paramsHTML + '<button class="run-btn" onclick="runCommand(\'' + mod.name + '\',\'' + cmd.name + '\',\'' + uid + '\')" id="btn-' + uid + '">▶ 运行</button></div><div class="result-panel" id="result-' + uid + '"></div></div>';
|
||||
}).join('');
|
||||
card.innerHTML = '<div class="module-header" onclick="toggleModule(this)"><h3><span style="color:' + mod.color + ';font-size:1.3em;">■</span> ' + mod.title + ' <span class="tag">' + mod.commands.length + ' 个命令</span></h3><span class="arrow">▼</span></div><div class="module-body collapsed"><p style="padding:12px 20px;color:#666;font-size:0.9em;border-bottom:1px solid #f0f0f0;background:#fafbfc;">' + mod.desc + '</p>' + (mod.repoSelector ? '<div style="padding:8px 20px;border-bottom:1px solid #f0f0f0;background:#f6ffed;display:flex;gap:10px;align-items:center;font-size:0.9em;"><label style="white-space:nowrap;color:#52c41a;">📦 仓库:</label><input id="repo-owner-' + mod.name + '" value="chroe" placeholder="owner" style="width:90px;padding:4px 8px;border:1px solid #d9d9d9;border-radius:4px;">/<input id="repo-name-' + mod.name + '" value="gitlink-cli" placeholder="repo" style="width:150px;padding:4px 8px;border:1px solid #d9d9d9;border-radius:4px;"></div>' : '') + cmdHTML + '</div>';
|
||||
container.appendChild(card);
|
||||
});
|
||||
})();
|
||||
|
||||
(function renderPart2() {
|
||||
var cats = [
|
||||
{ key:"core", label:"核心 Skills", desc:"覆盖 GitLink 平台核心操作(仓库/Issue/PR/分支/发布)", dotClass:"core" },
|
||||
{ key:"smart", label:"智能 Skills", desc:"AI 增强分析(健康度/变更日志/智能分拣/代码审查)", dotClass:"smart" },
|
||||
{ key:"aux", label:"辅助 Skills", desc:"搜索/用户/组织/CI/PM/工作流引擎/许可证合规", dotClass:"aux" },
|
||||
];
|
||||
var container = document.getElementById('skills-container');
|
||||
cats.forEach(function(cat) {
|
||||
var skills = SKILLS.filter(function(s) { return s.cat === cat.key; });
|
||||
var cardsHTML = skills.map(function(s) {
|
||||
var filesStr = s.files.join(', ');
|
||||
var exampleHTML = s.example ? '<button class="example-btn" onclick="toggleExample(\'' + s.id + '\')">📄 查看示例</button><div class="skill-example" id="ex-' + s.id + '"><div class="ex-title">' + s.example.title + '</div><div class="ex-text">' + s.example.text + '</div></div>' : '';
|
||||
return '<div class="skill-card ' + s.cat + '"><div class="skill-top"><span class="skill-name">gitlink-' + s.name + '</span><span class="skill-ver">v' + s.ver + '</span></div><div class="skill-desc">' + s.desc + '</div>' + exampleHTML + '<div class="skill-meta"><span>📁 ' + s.files.length + ' 个文件</span><span>✅ 已完成</span></div><div class="skill-files">' + filesStr + '</div></div>';
|
||||
}).join('');
|
||||
container.insertAdjacentHTML('beforeend', '<div class="skills-category"><h3><span class="dot ' + cat.dotClass + '"></span>' + cat.label + ' <span style="font-weight:400;color:#999;font-size:0.85em;">(' + skills.length + ' 个)</span></h3><p style="color:#999;font-size:0.85em;margin-bottom:12px;">' + cat.desc + '</p><div class="skill-grid">' + cardsHTML + '</div></div>');
|
||||
});
|
||||
})();
|
||||
|
||||
function toggleExample(id) { document.getElementById('ex-' + id).classList.toggle('show'); }
|
||||
const WORKFLOW_COMMANDS = []; // unused
|
||||
|
||||
(function renderPart3() {
|
||||
var container = document.getElementById('workflow-container');
|
||||
var defCardsHTML = WORKFLOWS.map(function(w) {
|
||||
var stepsHTML = w.steps.map(function(s,i) { return '<li>' + s + '</li>'; }).join('');
|
||||
var triggerLabel = w.triggerType === 'manual' ? '手动触发' : w.triggerType === 'poll' ? '事件: ' + w.triggerOn : '定时: ' + w.triggerOn;
|
||||
return '<div class="wf-def-card"><div class="wf-top"><span class="wf-name">' + w.name + '</span><span class="wf-cat">' + w.category + '</span></div><div class="wf-desc">' + w.desc + '</div><div class="wf-trigger">⚡ 触发: <code>' + w.triggerType + '</code> ' + triggerLabel + '</div><details class="wf-steps"><summary>' + w.steps.length + ' 个步骤</summary><ol>' + stepsHTML + '</ol></details></div>';
|
||||
}).join('');
|
||||
container.insertAdjacentHTML('beforeend', '<div class="wf-subsection"><h3>📋 预置工作流定义(' + WORKFLOWS.length + ' 个)</h3><div class="wf-def-grid">' + defCardsHTML + '</div></div>');
|
||||
var cmdRowsHTML = WORKFLOW_COMMANDS.map(function(cmd) {
|
||||
var uid = 'wf-' + cmd.name;
|
||||
var paramsHTML = cmd.params.map(function(p) {
|
||||
if (p.bool) return '<div class="param-group"><label>' + p.l + '</label><select id="p-' + uid + '-' + p.k + '"><option value="false"' + (p.v !== 'true' ? ' selected' : '') + '>否</option><option value="true"' + (p.v === 'true' ? ' selected' : '') + '>是</option></select></div>';
|
||||
return '<div class="param-group"><label>' + p.l + '</label><input id="p-' + uid + '-' + p.k + '" value="' + p.v + '" placeholder="' + p.l + '"></div>';
|
||||
}).join('');
|
||||
return '<div class="cmd-row"><div class="cmd-top"><span class="cmd-name" style="color:#722ed1;">+' + cmd.name + '</span><span class="cmd-desc">' + cmd.desc + '</span><button class="cmd-toggle" onclick="toggleDetail(\'' + uid + '\')">详情</button></div><div class="cmd-detail" id="detail-' + uid + '">' + cmd.detail + '</div><div class="cmd-params" id="params-' + uid + '">' + paramsHTML + '<button class="run-btn" style="background:#722ed1;" onclick="runWorkflowCommand(\'' + cmd.name + '\',\'' + uid + '\')" id="btn-' + uid + '">▶ 运行</button></div><div class="result-panel" id="result-' + uid + '"></div></div>';
|
||||
}).join('');
|
||||
container.insertAdjacentHTML('beforeend', '<div class="wf-subsection"><h3>🖥 工作流 CLI 命令(' + WORKFLOW_COMMANDS.length + ' 个)</h3><div class="wf-commands">' + cmdRowsHTML + '</div></div>');
|
||||
var container = document.getElementById("workflow-container");
|
||||
|
||||
// Global top bar
|
||||
var globalHTML = '<div class="wf-global">' +
|
||||
'<span>全局命令 </span>' +
|
||||
'<button class="action-btn btn-info" onclick="runWfGlobal(\'list\',\'wf-global-list\')" id="btn-wf-global-list">📋 列出所有工作流</button>' +
|
||||
'<button class="action-btn btn-logs" onclick="runWfGlobal(\'install-systemd\',\'wf-global-install\')" id="btn-wf-global-install">⚙ 生成 systemd 服务</button>' +
|
||||
'</div>' +
|
||||
'<div class="result-panel" id="result-wf-global-list"></div>' +
|
||||
'<div class="result-panel" id="result-wf-global-install"></div>';
|
||||
container.insertAdjacentHTML("beforeend", globalHTML);
|
||||
|
||||
// Each workflow as a panel
|
||||
WORKFLOWS.forEach(function(w) {
|
||||
var stepsHTML = w.steps.map(function(s,i) { return "<li>" + s + "</li>"; }).join("");
|
||||
var triggerLabel = w.triggerType === "manual" ? "手动触发" : w.triggerType === "poll" ? "事件触发: <code>" + w.triggerOn + "</code>" : "定时触发: <code>" + w.triggerOn + "</code>";
|
||||
var uid = "wfpanel-" + w.name;
|
||||
|
||||
var panelHTML =
|
||||
'<div class="wf-panel">' +
|
||||
'<div class="wf-panel-header" onclick="toggleWfPanel(this)">' +
|
||||
'<div class="wf-meta">' +
|
||||
'<span class="wf-dot stopped" id="dot-' + uid + '"></span>' +
|
||||
'<span class="wf-name">' + w.name + '</span>' +
|
||||
'<span class="wf-cat">' + w.category + '</span>' +
|
||||
'</div>' +
|
||||
'<span class="arrow">▼</span>' +
|
||||
'</div>' +
|
||||
'<div class="wf-panel-body collapsed">' +
|
||||
'<div class="wf-panel-desc">' + w.desc + '</div>' +
|
||||
'<div class="wf-panel-trigger">⚡ ' + triggerLabel + '</div>' +
|
||||
'<details class="wf-panel-steps"><summary>' + w.steps.length + ' 个步骤</summary><ol>' + stepsHTML + '</ol></details>' +
|
||||
'<div class="wf-actions">' +
|
||||
'<select class="action-select" id="mode-' + uid + '">' +
|
||||
'<option value="no-ai">规则引擎</option>' +
|
||||
'<option value="ai">AI 增强</option>' +
|
||||
'<option value="auto">自动检测</option>' +
|
||||
'</select>' +
|
||||
'<button class="action-btn btn-run" onclick="runWfOp(\'" + w.name + '"\',\'run\',\'" + uid + '"\')" id="btn-run-' + uid + '">▶ 执行</button>' +
|
||||
'<button class="action-btn btn-start" onclick="runWfOp(\'" + w.name + '"\',\'start\',\'" + uid + '"\')" id="btn-start-' + uid + '">▶ 启动守护</button>' +
|
||||
'<button class="action-btn btn-stop" onclick="runWfOp(\'" + w.name + '"\',\'stop\',\'" + uid + '"\')" id="btn-stop-' + uid + '">⏹ 停止</button>' +
|
||||
'<button class="action-btn btn-status" onclick="runWfOp(\'" + w.name + '"\',\'status\',\'" + uid + '"\')" id="btn-status-' + uid + '">📊 状态</button>' +
|
||||
'<button class="action-btn btn-logs" onclick="runWfOp(\'" + w.name + '"\',\'logs\',\'" + uid + '"\')" id="btn-logs-' + uid + '">📋 日志</button>' +
|
||||
'<button class="action-btn btn-info" onclick="runWfOp(\'" + w.name + '"\',\'info\',\'" + uid + '"\')" id="btn-info-' + uid + '">ℹ 详情</button>' +
|
||||
'</div>' +
|
||||
'<div class="result-panel" id="result-' + uid + '"></div>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
container.insertAdjacentHTML("beforeend", panelHTML);
|
||||
});
|
||||
})();
|
||||
|
||||
function toggleWfPanel(header) {
|
||||
var body = header.nextElementSibling;
|
||||
var arrow = header.querySelector(".arrow");
|
||||
body.classList.toggle("collapsed");
|
||||
arrow.classList.toggle("open");
|
||||
}
|
||||
|
||||
function toggleModule(header) {
|
||||
var body = header.nextElementSibling;
|
||||
var arrow = header.querySelector('.arrow');
|
||||
|
|
@ -593,42 +602,72 @@ async function runCommand(module, command, uid) {
|
|||
btn.textContent = '▶ 运行';
|
||||
}
|
||||
|
||||
async function runWorkflowCommand(command, uid) {
|
||||
async function runWfGlobal(command, uid) {
|
||||
var btn = document.getElementById('btn-' + uid);
|
||||
var panel = document.getElementById('result-' + uid);
|
||||
var cmd = WORKFLOW_COMMANDS.find(function(c) { return c.name === command; });
|
||||
if (!cmd) return;
|
||||
var args = getArgs(uid, cmd.params);
|
||||
btn.disabled = true;
|
||||
btn.textContent = '运行中...';
|
||||
var url = '/api/run?module=workflow&command=' + command + '&format=' + currentFormat;
|
||||
if (args) url += '&args=' + encodeURIComponent(args);
|
||||
try {
|
||||
var resp = await fetch(url);
|
||||
var data = await resp.json();
|
||||
var statusClass = data.ok ? 'ok' : 'err';
|
||||
var statusText = data.ok ? '成功' : '失败';
|
||||
var output, isTable = false;
|
||||
if (!data.ok && data.error) {
|
||||
output = data.error;
|
||||
} else if (data.output === null || data.output === undefined) {
|
||||
output = '(操作完成,无返回数据)';
|
||||
} else if (typeof data.output === 'string') {
|
||||
output = data.output || '(操作完成,无返回数据)';
|
||||
if (currentFormat === 'table' && /^.+\n-{2,}/.test(output)) isTable = true;
|
||||
} else {
|
||||
var s = JSON.stringify(data.output, null, 2);
|
||||
output = (s === 'null' || s === '{}' || s === '""') ? '(操作完成,无返回数据)' : s;
|
||||
}
|
||||
var bodyHTML = isTable ? '<div class="result-body" style="overflow-x:auto">' + tableToHtml(output) + '</div>' : '<div class="result-body"><pre>' + escapeHtml(output) + '</pre></div>';
|
||||
panel.innerHTML = '<div class="result-toolbar"><span class="cmd-text">$ ' + (data.command || 'workflow +' + command) + '</span><span class="status ' + statusClass + '">' + statusText + '</span><span class="close-btn" onclick="this.closest(\'.result-panel\').classList.remove(\'show\')">×</span></div>' + bodyHTML;
|
||||
var sc = data.ok ? 'ok' : 'err', st = data.ok ? '成功' : '失败';
|
||||
var out = formatOutput(data);
|
||||
panel.innerHTML = '<div class="result-toolbar"><span class="cmd-text">$ ' + (data.command || 'workflow +' + command) + '</span><span class="status ' + sc + '">' + st + '</span><span class="close-btn" onclick="this.closest(\'.result-panel\').classList.remove(\'show\')">×</span></div><div class="result-body"><pre>' + escapeHtml(out) + '</pre></div>';
|
||||
panel.classList.add('show');
|
||||
} catch (e) {
|
||||
} catch(e) {
|
||||
panel.innerHTML = '<div class="result-toolbar"><span class="cmd-text">$ workflow +' + command + '</span><span class="status err">请求失败</span><span class="close-btn" onclick="this.closest(\'.result-panel\').classList.remove(\'show\')">×</span></div><div class="result-body"><pre>' + escapeHtml(e.message) + '</pre></div>';
|
||||
panel.classList.add('show');
|
||||
}
|
||||
btn.disabled = false;
|
||||
btn.textContent = '▶ 运行';
|
||||
}
|
||||
|
||||
async function runWfOp(wfName, op, uid) {
|
||||
var btn = document.getElementById('btn-' + op + '-' + uid);
|
||||
var panel = document.getElementById('result-' + uid);
|
||||
btn.disabled = true;
|
||||
|
||||
var url = '/api/run?module=workflow&command=' + op + '&format=' + currentFormat;
|
||||
if (op === 'run') {
|
||||
var mode = document.getElementById('mode-' + uid).value;
|
||||
if (mode === 'no-ai') url += '&args=--name%20' + wfName + '%20--no-ai=true';
|
||||
else if (mode === 'ai') url += '&args=--name%20' + wfName + '%20--ai=true';
|
||||
else url += '&args=--name%20' + wfName;
|
||||
} else if (op === 'start') {
|
||||
url += '&args=--name%20' + wfName + '%20--interval%205m';
|
||||
} else {
|
||||
url += '&args=--name%20' + wfName;
|
||||
}
|
||||
|
||||
try {
|
||||
var resp = await fetch(url);
|
||||
var data = await resp.json();
|
||||
var sc = data.ok ? 'ok' : 'err', st = data.ok ? '成功' : '失败';
|
||||
var out = formatOutput(data);
|
||||
|
||||
// Update dot indicator
|
||||
if (op === 'start' && data.ok) {
|
||||
var dot = document.getElementById('dot-' + uid);
|
||||
if (dot) { dot.classList.remove('stopped'); dot.classList.add('running'); }
|
||||
} else if (op === 'stop' && data.ok) {
|
||||
var dot = document.getElementById('dot-' + uid);
|
||||
if (dot) { dot.classList.remove('running'); dot.classList.add('stopped'); }
|
||||
}
|
||||
|
||||
panel.innerHTML = '<div class="result-toolbar"><span class="cmd-text">$ ' + (data.command || 'workflow +' + op + ' --name ' + wfName) + '</span><span class="status ' + sc + '">' + st + '</span><span class="close-btn" onclick="this.closest(\'.result-panel\').classList.remove(\'show\')">×</span></div><div class="result-body"><pre>' + escapeHtml(out) + '</pre></div>';
|
||||
panel.classList.add('show');
|
||||
} catch(e) {
|
||||
panel.innerHTML = '<div class="result-toolbar"><span class="cmd-text">$ workflow +' + op + ' --name ' + wfName + '</span><span class="status err">请求失败</span><span class="close-btn" onclick="this.closest(\'.result-panel\').classList.remove(\'show\')">×</span></div><div class="result-body"><pre>' + escapeHtml(e.message) + '</pre></div>';
|
||||
panel.classList.add('show');
|
||||
}
|
||||
btn.disabled = false;
|
||||
}
|
||||
|
||||
function formatOutput(data) {
|
||||
if (!data.ok && data.error) return data.error;
|
||||
if (data.output === null || data.output === undefined) return '(操作完成,无返回数据)';
|
||||
if (typeof data.output === 'string') return data.output || '(操作完成,无返回数据)';
|
||||
var s = JSON.stringify(data.output, null, 2);
|
||||
return (s === 'null' || s === '{}' || s === '""') ? '(操作完成,无返回数据)' : s;
|
||||
}
|
||||
|
||||
function tableToHtml(text) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue