forked from Gitlink/gitlink-cli
356 lines
24 KiB
HTML
356 lines
24 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>gitlink-cli 功能增强展示</title>
|
||
<style>
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif; background: #f0f2f5; color: #333; }
|
||
.header { background: linear-gradient(135deg, #1a1a2e, #16213e); color: white; padding: 40px 20px; text-align: center; }
|
||
.header h1 { font-size: 2em; margin-bottom: 8px; }
|
||
.header p { color: #8892b0; font-size: 1.1em; }
|
||
.header .repo-info { margin-top: 12px; font-size: 0.9em; color: #64ffda; }
|
||
.header .repo-info code { background: rgba(255,255,255,0.1); padding: 4px 12px; border-radius: 4px; }
|
||
.stats-bar { display: flex; justify-content: center; gap: 32px; padding: 20px; background: white; border-bottom: 1px solid #e8e8e8; flex-wrap: wrap; }
|
||
.stat-item { text-align: center; }
|
||
.stat-item .num { font-size: 2em; font-weight: bold; color: #1890ff; }
|
||
.stat-item .num.green { color: #52c41a; }
|
||
.stat-item .label { color: #999; font-size: 0.85em; }
|
||
.container { max-width: 1200px; margin: 0 auto; padding: 24px; }
|
||
.module-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(550px, 1fr)); gap: 20px; }
|
||
.module-card { background: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); overflow: hidden; }
|
||
.module-header { padding: 16px 20px; cursor: pointer; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #f0f0f0; }
|
||
.module-header:hover { background: #fafafa; }
|
||
.module-header h3 { font-size: 1.1em; display: flex; align-items: center; gap: 8px; }
|
||
.module-header .tag { font-size: 0.75em; background: #e6f7ff; color: #1890ff; padding: 2px 8px; border-radius: 10px; }
|
||
.module-header .arrow { transition: transform 0.3s; font-size: 0.8em; color: #999; }
|
||
.module-header .arrow.open { transform: rotate(180deg); }
|
||
.module-body { padding: 0; }
|
||
.module-body.collapsed { display: none; }
|
||
.cmd-section { padding: 10px 20px 4px; color: #1890ff; font-weight: 600; font-size: 0.88em; background: #f0f5ff; border-bottom: 1px solid #e6f0ff; border-top: 1px solid #e6f0ff; }
|
||
.cmd-section:first-child { border-top: none; }
|
||
.cmd-row { border-bottom: 1px solid #f5f5f5; }
|
||
.cmd-row:last-child { border-bottom: none; }
|
||
.cmd-row:hover { background: #fafbfc; }
|
||
.cmd-top { display: flex; align-items: center; padding: 10px 20px; gap: 12px; }
|
||
.cmd-name { font-family: 'SFMono-Regular', Consolas, monospace; font-size: 0.9em; color: #1890ff; font-weight: 600; min-width: 70px; }
|
||
.cmd-desc { color: #666; font-size: 0.85em; flex: 1; }
|
||
.cmd-toggle { color: #999; cursor: pointer; font-size: 0.85em; border: none; background: none; }
|
||
.cmd-toggle:hover { color: #1890ff; }
|
||
.cmd-detail { padding: 0 20px 10px 90px; color: #999; font-size: 0.8em; display: none; }
|
||
.cmd-detail.show { display: block; }
|
||
.cmd-params { padding: 6px 20px 10px 90px; display: none; flex-wrap: wrap; gap: 8px; align-items: flex-end; }
|
||
.cmd-params.show { display: flex; }
|
||
.param-group { display: flex; flex-direction: column; gap: 2px; }
|
||
.param-group label { font-size: 0.75em; color: #999; }
|
||
.param-group input { border: 1px solid #d9d9d9; border-radius: 4px; padding: 4px 8px; font-size: 0.85em; width: 140px; }
|
||
.param-group input:focus { border-color: #1890ff; outline: none; box-shadow: 0 0 0 2px rgba(24,144,255,0.2); }
|
||
.run-btn { background: #1890ff; color: white; border: none; padding: 5px 14px; border-radius: 6px; cursor: pointer; font-size: 0.85em; white-space: nowrap; align-self: flex-end; }
|
||
.run-btn:hover { background: #40a9ff; }
|
||
.run-btn:disabled { background: #d9d9d9; cursor: not-allowed; }
|
||
.result-panel { background: #1e1e1e; margin: 4px 20px 14px; border-radius: 8px; overflow: hidden; display: none; }
|
||
.result-panel.show { display: block; }
|
||
.result-toolbar { background: #2d2d2d; padding: 8px 12px; display: flex; justify-content: space-between; align-items: center; }
|
||
.result-toolbar .cmd-text { color: #4ec9b0; font-family: monospace; font-size: 0.85em; }
|
||
.result-toolbar .status { font-size: 0.8em; padding: 2px 8px; border-radius: 4px; }
|
||
.result-toolbar .status.ok { background: #2ea04380; color: #4ec9b0; }
|
||
.result-toolbar .status.err { background: #da363380; color: #f85149; }
|
||
.result-toolbar .close-btn { color: #666; cursor: pointer; font-size: 1.2em; }
|
||
.result-toolbar .close-btn:hover { color: #999; }
|
||
.result-body { padding: 12px; max-height: 400px; overflow: auto; }
|
||
.result-body pre { color: #d4d4d4; font-family: 'SFMono-Regular', Consolas, monospace; font-size: 0.82em; line-height: 1.5; white-space: pre-wrap; word-break: break-all; margin: 0; }
|
||
.footer { text-align: center; color: #999; padding: 40px 20px; font-size: 0.85em; }
|
||
@media (max-width: 600px) {
|
||
.module-grid { grid-template-columns: 1fr; }
|
||
.cmd-params { padding-left: 20px; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="header">
|
||
<h1>gitlink-cli 功能增强</h1>
|
||
<p>软件演化与运维 课程实践 — 进阶任务 子任务一</p>
|
||
<div class="repo-info">演示仓库:<code>chroe/gitlink-cli</code> — 展开命令、填入参数、点击运行查看真实结果</div>
|
||
</div>
|
||
<div class="stats-bar">
|
||
<div class="stat-item"><div class="num">9</div><div class="label">新增模块</div></div>
|
||
<div class="stat-item"><div class="num green">47</div><div class="label">新增命令</div></div>
|
||
<div class="stat-item"><div class="num">7</div><div class="label">批量操作</div></div>
|
||
<div class="stat-item"><div class="num">47+</div><div class="label">单元测试</div></div>
|
||
</div>
|
||
<div class="container"><div class="module-grid" id="modules"></div></div>
|
||
<div class="footer">gitlink-cli 功能增强 — 软件演化与运维课程实践</div>
|
||
<script>
|
||
const MODULES = [
|
||
{
|
||
name: "milestone", title: "里程碑管理",
|
||
desc: "管理项目的版本规划节点,可用于跟踪项目进度和发布计划",
|
||
color: "#722ed1",
|
||
commands: [
|
||
{ name: "list", desc: "列出所有里程碑", detail: "获取项目中所有已创建的里程碑列表,包括开启和关闭的数量统计", params: [] },
|
||
{ name: "view", desc: "查看里程碑详情", detail: "查看某个里程碑的详细信息,包括关联的疑修数量和完成进度", params: [{k:"id",l:"里程碑ID",v:"2760"}] },
|
||
{ name: "create", desc: "创建里程碑", detail: "新建一个版本规划节点,可设置名称、描述、截止日期", params: [{k:"name",l:"名称",v:"v2.0"},{k:"description",l:"描述",v:"新里程碑"},{k:"due",l:"截止日期",v:"2026-07-01"}] },
|
||
{ name: "update", desc: "更新里程碑", detail: "修改里程碑的名称、描述或截止日期", params: [{k:"id",l:"里程碑ID",v:"2760"},{k:"description",l:"新描述",v:"已更新"}] },
|
||
{ name: "close", desc: "关闭里程碑", detail: "标记里程碑为已完成状态", params: [{k:"id",l:"里程碑ID",v:"2760"}] },
|
||
{ name: "delete", desc: "删除里程碑", detail: "删除指定的里程碑(不可恢复)", params: [{k:"id",l:"里程碑ID",v:"2760"}] },
|
||
]
|
||
},
|
||
{
|
||
name: "webhook", title: "Webhook 配置",
|
||
desc: "管理项目的事件通知,当仓库发生 push、PR 等事件时自动通知外部服务",
|
||
color: "#eb2f96",
|
||
commands: [
|
||
{ name: "list", desc: "列出所有 Webhook", detail: "获取项目中配置的所有事件通知地址列表", params: [] },
|
||
{ name: "view", desc: "查看 Webhook 详情", detail: "查看某个 Webhook 的详细配置信息,包括事件类型和最近触发状态", params: [{k:"id",l:"Webhook ID",v:"51106"}] },
|
||
{ name: "create", desc: "创建 Webhook", detail: "新增一个事件通知配置,指定 URL 和监听的事件类型", params: [{k:"url",l:"回调URL",v:"https://httpbin.org/post"}] },
|
||
{ name: "update", desc: "更新 Webhook", detail: "修改 Webhook 的 URL、事件类型等配置", params: [{k:"id",l:"Webhook ID",v:"51106"},{k:"url",l:"新URL",v:"https://httpbin.org/post"}] },
|
||
{ name: "test", desc: "测试 Webhook", detail: "发送一个测试事件,验证 Webhook 配置是否正确", params: [{k:"id",l:"Webhook ID",v:"51106"}] },
|
||
{ name: "delete", desc: "删除 Webhook", detail: "删除指定的事件通知配置", params: [{k:"id",l:"Webhook ID",v:"51106"}] },
|
||
]
|
||
},
|
||
{
|
||
name: "label", title: "标签管理",
|
||
desc: "管理疑修(Issue)的标签,用于分类和优先级标记",
|
||
color: "#fa8c16",
|
||
commands: [
|
||
{ name: "list", desc: "列出所有标签", detail: "获取项目中所有疑修标签,包括标签颜色和关联的疑修数量", params: [] },
|
||
{ name: "create", desc: "创建标签", detail: "新建一个疑修标签,可设置名称、颜色和描述。创建后点 +list 查看新标签的 ID", params: [{k:"name",l:"标签名",v:"test-label"},{k:"color",l:"颜色",v:"#ff6600"},{k:"description",l:"描述",v:"test"}] },
|
||
{ name: "update", desc: "更新标签", detail: "修改标签的名称、颜色或描述。先点 +list 查看 ID", params: [{k:"id",l:"标签ID",v:"328745"},{k:"description",l:"新描述",v:"updated"}] },
|
||
{ name: "delete", desc: "删除标签", detail: "删除指定的疑修标签。先点 +list 查看 ID", params: [{k:"id",l:"标签ID",v:"328745"}] },
|
||
]
|
||
},
|
||
{
|
||
name: "commit", title: "提交记录",
|
||
desc: "查看仓库的代码提交历史、变更文件和逐行追溯",
|
||
color: "#52c41a",
|
||
commands: [
|
||
{ name: "list", desc: "列出提交记录", detail: "查看仓库的所有代码提交历史,支持按分支/标签过滤和分页", params: [{k:"limit",l:"数量",v:"5"}] },
|
||
{ name: "view", desc: "查看提交详情", detail: "查看某次提交修改了哪些文件,以及每个文件的增删行数。SHA 从 +list 获取", params: [{k:"sha",l:"提交SHA",v:"a82717bbf372aee48bbd1452c3d6be9256b0febf"}] },
|
||
{ name: "diff", desc: "查看代码差异", detail: "查看某次提交的具体代码变更内容。SHA 从 +list 获取", params: [{k:"sha",l:"提交SHA",v:"a82717bbf372aee48bbd1452c3d6be9256b0febf"}] },
|
||
{ name: "blame", desc: "逐行追溯", detail: "查看文件每一行代码是谁在什么时候、通过哪次提交修改的", params: [{k:"path",l:"文件路径",v:"README.md"}] },
|
||
]
|
||
},
|
||
{
|
||
name: "wiki", title: "Wiki 页面管理",
|
||
desc: "管理项目维基页面,支持创建、查看、编辑和删除 Wiki 文档",
|
||
color: "#13c2c2",
|
||
commands: [
|
||
{ name: "list", desc: "列出 Wiki 页面", detail: "获取项目中所有 Wiki 页面列表", params: [] },
|
||
{ name: "view", desc: "查看 Wiki 页面", detail: "查看某个 Wiki 页面的详细内容", params: [{k:"name",l:"页面名称",v:"Home"}] },
|
||
{ name: "create", desc: "创建 Wiki 页面", detail: "新建一个 Wiki 页面,内容自动进行 Base64 编码", params: [{k:"name",l:"页面标题",v:"TestPage"},{k:"content",l:"页面内容",v:"Hello Wiki!"},{k:"message",l:"提交信息",v:"create test page"}] },
|
||
{ name: "update", desc: "更新 Wiki 页面", detail: "修改已有的 Wiki 页面内容,内容自动进行 Base64 编码", params: [{k:"name",l:"页面标题",v:"Home"},{k:"content",l:"新内容",v:"Updated content"},{k:"message",l:"提交信息",v:"update page"}] },
|
||
{ name: "delete", desc: "删除 Wiki 页面", detail: "删除指定的 Wiki 页面(不可恢复)", params: [{k:"name",l:"页面标题",v:"TestPage"}] },
|
||
]
|
||
},
|
||
{
|
||
name: "file", title: "文件管理",
|
||
desc: "管理仓库中的文件和目录,支持在线创建、编辑、删除和查看文件",
|
||
color: "#2f54eb",
|
||
commands: [
|
||
{ name: "list", desc: "列出仓库文件", detail: "获取仓库指定路径下的文件和目录列表,支持分支选择和关键词搜索", params: [{k:"ref",l:"分支/标签",v:"master"}] },
|
||
{ name: "tree", desc: "查看文件树", detail: "以树形结构浏览仓库目录,支持递归展开所有子目录", params: [{k:"sha",l:"分支/提交SHA",v:"master"},{k:"limit",l:"每页数量",v:"20"}] },
|
||
{ name: "get", desc: "获取文件内容", detail: "查看指定文件或目录的内容详情", params: [{k:"path",l:"文件路径",v:"README.md"},{k:"ref",l:"分支",v:"master"}] },
|
||
{ name: "create", desc: "创建文件", detail: "在仓库中新建文件,内容自动进行 Base64 编码", params: [{k:"path",l:"文件路径",v:"test/hello.txt"},{k:"content",l:"文件内容",v:"Hello World!"},{k:"message",l:"提交信息",v:"add test file"},{k:"branch",l:"目标分支",v:"master"}] },
|
||
{ name: "delete", desc: "删除文件", detail: "删除仓库中的文件,需要提供文件的 blob SHA", params: [{k:"path",l:"文件路径",v:"test/hello.txt"},{k:"sha",l:"文件SHA",v:""},{k:"message",l:"提交信息",v:"delete test file"},{k:"branch",l:"目标分支",v:"master"}] },
|
||
]
|
||
},
|
||
{
|
||
name: "member", title: "成员管理",
|
||
desc: "管理项目成员,支持添加、移除、修改角色和查看成员列表。用户ID 可用 search +users 查找",
|
||
color: "#f5222d",
|
||
commands: [
|
||
{ name: "list", desc: "列出项目成员", detail: "获取项目中所有协作者的列表,返回结果中包含每位成员的 id(数字)和 login(用户名),可按关键词搜索", params: [{k:"keyword",l:"搜索关键词",v:""}] },
|
||
{ name: "add", desc: "添加成员", detail: "邀请用户加入项目成为协作者。user-id 是数字 ID,可先点 +list 查看现有成员的 ID,或用 search +users 按用户名查找", params: [{k:"user-id",l:"用户数字ID",v:"141645"}] },
|
||
{ name: "remove", desc: "移除成员", detail: "将指定用户移出项目。先点 +list 查看成员的数字 ID", params: [{k:"user-id",l:"用户数字ID",v:"141645"}] },
|
||
{ name: "update", desc: "修改成员角色", detail: "更改成员的项目角色:Manager(管理员)、Developer(开发者)、Reporter(报告者)。先点 +list 查看成员 ID", params: [{k:"user-id",l:"用户数字ID",v:"141645"},{k:"role",l:"角色",v:"Developer"}] },
|
||
]
|
||
},
|
||
{
|
||
name: "watch", title: "仓库关注",
|
||
desc: "关注仓库以接收更新通知,支持关注/取消关注和查看关注者列表",
|
||
color: "#faad14",
|
||
commands: [
|
||
{ name: "watch", desc: "关注仓库", detail: "开始关注当前仓库,接收后续的事件通知", params: [] },
|
||
{ name: "unwatch", desc: "取消关注", detail: "停止关注当前仓库,不再接收通知", params: [] },
|
||
{ name: "watchers", desc: "查看关注者", detail: "列出关注了当前仓库的所有用户", params: [] },
|
||
]
|
||
},
|
||
{
|
||
name: "star", title: "仓库点赞",
|
||
desc: "为仓库点赞(Star),表达对项目的认可和支持",
|
||
color: "#eb2f96",
|
||
commands: [
|
||
{ name: "star", desc: "点赞仓库", detail: "为当前仓库点赞(Star)", params: [] },
|
||
{ name: "unstar", desc: "取消点赞", detail: "取消对当前仓库的点赞", params: [] },
|
||
{ name: "stars", desc: "查看点赞者", detail: "列为当前仓库点赞的所有用户", params: [] },
|
||
]
|
||
},
|
||
{
|
||
name: "issue", title: "批量 Issue 操作",
|
||
desc: "批量管理疑修(Issue):批量关闭、分配、设置标签和里程碑,支持逗号列表或 CSV 文件导入",
|
||
color: "#13c2c2",
|
||
commands: [
|
||
{ name: "batch-close", desc: "批量关闭 Issue", detail: "按编号批量关闭 Issue,支持 --numbers 逗号分隔或 --from CSV 文件。开启预览模式可先查看将执行的操作而不实际执行", params: [{k:"numbers",l:"Issue编号",v:"1,2,3"},{k:"dry-run",l:"预览模式",v:"false",bool:true}] },
|
||
{ name: "batch-assign", desc: "批量分配 Issue", detail: "将多个 Issue 批量分配给指定用户处理,用户名为 GitLink 登录名", params: [{k:"numbers",l:"Issue编号",v:"1,2,3"},{k:"user",l:"负责人用户名",v:"chroe"},{k:"dry-run",l:"预览模式",v:"false",bool:true}] },
|
||
{ name: "batch-label", desc: "批量设置标签", detail: "批量为 Issue 添加或移除标签。标签ID 先用 label +list 查看", params: [{k:"numbers",l:"Issue编号",v:"1,2,3"},{k:"add",l:"添加标签ID",v:""},{k:"remove",l:"移除标签ID",v:""},{k:"dry-run",l:"预览模式",v:"false",bool:true}] },
|
||
{ name: "batch-milestone", desc: "批量设置里程碑", detail: "批量将 Issue 关联到指定的里程碑。里程碑ID 先用 milestone +list 查看", params: [{k:"numbers",l:"Issue编号",v:"1,2,3"},{k:"milestone",l:"里程碑ID",v:"2760"},{k:"dry-run",l:"预览模式",v:"false",bool:true}] },
|
||
]
|
||
},
|
||
{
|
||
name: "repo", title: "批量仓库管理",
|
||
desc: "批量删除或 Fork 仓库,支持逗号分隔列表或 CSV 文件批量导入,建议先用预览模式确认",
|
||
color: "#722ed1",
|
||
commands: [
|
||
{ name: "batch-delete", desc: "批量删除仓库", detail: "按 owner/repo 格式批量删除仓库,支持 --repos 逗号分隔或 --from CSV 文件。⚠️ 危险操作,建议先开启预览模式确认", params: [{k:"repos",l:"仓库列表",v:"chroe/test1,chroe/test2"},{k:"dry-run",l:"预览模式",v:"true",bool:true}] },
|
||
{ name: "batch-fork", desc: "批量 Fork 仓库", detail: "批量 Fork 多个仓库到自己的账号下", params: [{k:"repos",l:"仓库列表",v:"chroe/test1,chroe/test2"},{k:"dry-run",l:"预览模式",v:"true",bool:true}] },
|
||
]
|
||
},
|
||
{
|
||
name: "org", title: "批量组织邀请",
|
||
desc: "批量邀请成员加入组织,支持逗号分隔或 CSV 文件批量导入,建议先用预览模式确认",
|
||
color: "#fa8c16",
|
||
commands: [
|
||
{ name: "batch-invite", desc: "批量邀请成员", detail: "批量邀请用户加入组织,支持设置角色(member/admin),支持 --users 逗号分隔或 --from CSV 文件", params: [{k:"id",l:"组织ID",v:"1"},{k:"users",l:"用户名列表",v:"alice,bob"},{k:"role",l:"角色",v:"member"},{k:"dry-run",l:"预览模式",v:"true",bool:true}] },
|
||
]
|
||
},
|
||
];
|
||
|
||
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 = `${mod.name}-${cmd.name}`;
|
||
const paramsHTML = cmd.params.map(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>
|
||
${cmdHTML}
|
||
</div>`;
|
||
container.appendChild(card);
|
||
});
|
||
|
||
function toggleModule(header) {
|
||
const body = header.nextElementSibling;
|
||
const arrow = header.querySelector('.arrow');
|
||
body.classList.toggle('collapsed');
|
||
arrow.classList.toggle('open');
|
||
}
|
||
|
||
function toggleDetail(uid) {
|
||
const detail = document.getElementById(`detail-${uid}`);
|
||
const params = document.getElementById(`params-${uid}`);
|
||
detail.classList.toggle('show');
|
||
params.classList.toggle('show');
|
||
}
|
||
|
||
function getArgs(uid, params) {
|
||
return params.map(p => {
|
||
const val = document.getElementById(`p-${uid}-${p.k}`).value.trim();
|
||
// Boolean flag: only pass --flag when value is "true", omit for false
|
||
if (p.bool) {
|
||
return val === 'true' ? `--${p.k}` : '';
|
||
}
|
||
if (!val) return '';
|
||
// Use = syntax for bool values (pflag requires it)
|
||
if (val === 'true' || val === 'false') {
|
||
return `--${p.k}=${val}`;
|
||
}
|
||
if (val.includes(' ')) {
|
||
return `--${p.k} "${val}"`;
|
||
}
|
||
return `--${p.k} ${val}`;
|
||
}).filter(Boolean).join(' ');
|
||
}
|
||
|
||
async function runCommand(module, command, uid) {
|
||
const btn = document.getElementById(`btn-${uid}`);
|
||
const panel = document.getElementById(`result-${uid}`);
|
||
const mod = MODULES.find(m => m.name === module);
|
||
const cmd = mod.commands.find(c => c.name === command);
|
||
const args = getArgs(uid, cmd.params);
|
||
const apiModule = cmd.module || module;
|
||
|
||
btn.disabled = true;
|
||
btn.textContent = '运行中...';
|
||
|
||
let url = `/api/run?module=${apiModule}&command=${command}`;
|
||
if (args) url += `&args=${encodeURIComponent(args)}`;
|
||
|
||
try {
|
||
const resp = await fetch(url);
|
||
const data = await resp.json();
|
||
const statusClass = data.ok ? 'ok' : 'err';
|
||
const statusText = data.ok ? '成功' : '失败';
|
||
|
||
let output;
|
||
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 || '(操作完成,无返回数据)';
|
||
} else {
|
||
const s = JSON.stringify(data.output, null, 2);
|
||
output = (s === 'null' || s === '{}' || s === '""') ? '(操作完成,无返回数据)' : s;
|
||
}
|
||
}
|
||
|
||
panel.innerHTML = `
|
||
<div class="result-toolbar">
|
||
<span class="cmd-text">$ ${data.command || module + ' +' + command}</span>
|
||
<span class="status ${statusClass}">${statusText}</span>
|
||
<span class="close-btn" onclick="panel.classList.remove('show')">×</span>
|
||
</div>
|
||
<div class="result-body"><pre>${escapeHtml(output)}</pre></div>`;
|
||
panel.classList.add('show');
|
||
} catch (e) {
|
||
panel.innerHTML = `
|
||
<div class="result-toolbar">
|
||
<span class="cmd-text">$ ${module} +${command}</span>
|
||
<span class="status err">请求失败</span>
|
||
<span class="close-btn" onclick="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 = '▶ 运行';
|
||
}
|
||
|
||
function escapeHtml(s) { return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
|
||
</script>
|
||
</body>
|
||
</html>
|