forked from Gitlink/gitlink-cli
278 lines
15 KiB
HTML
278 lines
15 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-row { display: flex; align-items: stretch; border-bottom: 1px solid #f5f5f5; }
|
||
.cmd-row:last-child { border-bottom: none; }
|
||
.cmd-row:hover { background: #fafbfc; }
|
||
|
||
.cmd-info { flex: 1; padding: 12px 20px; }
|
||
.cmd-name { font-family: 'SFMono-Regular', Consolas, monospace; font-size: 0.9em; color: #1890ff; font-weight: 600; }
|
||
.cmd-desc { color: #666; font-size: 0.85em; margin-top: 4px; line-height: 1.5; }
|
||
.cmd-example { color: #999; font-size: 0.8em; margin-top: 4px; font-family: monospace; }
|
||
|
||
.cmd-action { display: flex; align-items: center; padding: 12px 20px; }
|
||
.run-btn { background: #1890ff; color: white; border: none; padding: 6px 16px; border-radius: 6px; cursor: pointer; font-size: 0.85em; transition: all 0.2s; white-space: nowrap; }
|
||
.run-btn:hover { background: #40a9ff; }
|
||
.run-btn:disabled { background: #d9d9d9; cursor: not-allowed; color: #999; }
|
||
.run-btn.loading { background: #1890ff; opacity: 0.7; }
|
||
.run-btn.auth-needed { background: #faad14; color: #333; }
|
||
|
||
.result-panel { background: #1e1e1e; margin: 0 20px 16px; 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; line-height: 1; }
|
||
.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; }
|
||
.stats-bar { gap: 16px; }
|
||
.cmd-row { flex-direction: column; }
|
||
.cmd-action { padding: 0 20px 12px; }
|
||
}
|
||
</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">4</div>
|
||
<div class="label">新增模块</div>
|
||
</div>
|
||
<div class="stat-item">
|
||
<div class="num green">20</div>
|
||
<div class="label">新增命令</div>
|
||
</div>
|
||
<div class="stat-item">
|
||
<div class="num">36+</div>
|
||
<div class="label">单元测试</div>
|
||
</div>
|
||
<div class="stat-item">
|
||
<div class="num">41</div>
|
||
<div class="label">总提交数</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="container">
|
||
<div class="module-grid" id="modules"></div>
|
||
</div>
|
||
|
||
<div class="footer">
|
||
gitlink-cli 功能增强 — 软件演化与运维课程实践 — 点击任意命令按钮查看真实 API 返回结果
|
||
</div>
|
||
|
||
<script>
|
||
const MODULES = [
|
||
{
|
||
name: "milestone",
|
||
title: "里程碑管理",
|
||
desc: "管理项目的版本规划节点,可用于跟踪项目进度和发布计划",
|
||
color: "#722ed1",
|
||
commands: [
|
||
{ name: "list", desc: "列出所有里程碑", detail: "获取项目中所有已创建的里程碑列表,包括开启和关闭的数量统计", example: "gitlink-cli milestone +list --owner chroe --repo gitlink-cli" },
|
||
{ name: "view", desc: "查看里程碑详情", detail: "查看某个里程碑的详细信息,包括关联的疑修数量和完成进度", example: "gitlink-cli milestone +view --id 1", needAuth: true, args: "--id 1" },
|
||
{ name: "create", desc: "创建里程碑", detail: "新建一个版本规划节点,可设置名称、描述、截止日期", example: "gitlink-cli milestone +create --name v2.0 --due 2026-06-30", needAuth: true },
|
||
{ name: "update", desc: "更新里程碑", detail: "修改里程碑的名称、描述或截止日期", example: "gitlink-cli milestone +update --id 1 --name v2.1", needAuth: true },
|
||
{ name: "close", desc: "关闭里程碑", detail: "标记里程碑为已完成状态", example: "gitlink-cli milestone +close --id 1", needAuth: true },
|
||
{ name: "delete", desc: "删除里程碑", detail: "删除指定的里程碑(不可恢复)", example: "gitlink-cli milestone +delete --id 1", needAuth: true },
|
||
]
|
||
},
|
||
{
|
||
name: "webhook",
|
||
title: "Webhook 配置",
|
||
desc: "管理项目的事件通知,当仓库发生 push、PR 等事件时自动通知外部服务",
|
||
color: "#eb2f96",
|
||
commands: [
|
||
{ name: "list", desc: "列出所有 Webhook", detail: "获取项目中配置的所有事件通知地址列表", example: "gitlink-cli webhook +list --owner chroe --repo gitlink-cli", needAuth: true },
|
||
{ name: "create", desc: "创建 Webhook", detail: "新增一个事件通知配置,指定 URL 和监听的事件类型", example: "gitlink-cli webhook +create --url https://example.com/hook", needAuth: true },
|
||
{ name: "view", desc: "查看 Webhook 详情", detail: "查看某个 Webhook 的详细配置信息,包括事件类型和最近触发状态", example: "gitlink-cli webhook +view --id 1", needAuth: true },
|
||
{ name: "update", desc: "更新 Webhook", detail: "修改 Webhook 的 URL、事件类型等配置", example: "gitlink-cli webhook +update --id 1 --url https://new.url", needAuth: true },
|
||
{ name: "delete", desc: "删除 Webhook", detail: "删除指定的事件通知配置", example: "gitlink-cli webhook +delete --id 1", needAuth: true },
|
||
{ name: "test", desc: "测试 Webhook", detail: "发送一个测试事件,验证 Webhook 配置是否正确", example: "gitlink-cli webhook +test --id 1", needAuth: true },
|
||
]
|
||
},
|
||
{
|
||
name: "label",
|
||
title: "标签管理",
|
||
desc: "管理疑修(Issue)的标签,用于分类和优先级标记",
|
||
color: "#fa8c16",
|
||
commands: [
|
||
{ name: "list", desc: "列出所有标签", detail: "获取项目中所有疑修标签,包括标签颜色和关联的疑修数量", example: "gitlink-cli label +list --owner chroe --repo gitlink-cli" },
|
||
{ name: "create", desc: "创建标签", detail: "新建一个疑修标签,可设置名称、颜色和描述", example: "gitlink-cli label +create --name bug --color #ff0000", needAuth: true },
|
||
{ name: "update", desc: "更新标签", detail: "修改标签的名称、颜色或描述", example: "gitlink-cli label +update --id 1 --name new-name", needAuth: true },
|
||
{ name: "delete", desc: "删除标签", detail: "删除指定的疑修标签", example: "gitlink-cli label +delete --id 1", needAuth: true },
|
||
]
|
||
},
|
||
{
|
||
name: "commit",
|
||
title: "提交记录",
|
||
desc: "查看仓库的代码提交历史、变更文件和逐行追溯",
|
||
color: "#52c41a",
|
||
commands: [
|
||
{ name: "list", desc: "列出提交记录", detail: "查看仓库的所有代码提交历史,支持按分支/标签过滤和分页", example: "gitlink-cli commit +list --owner chroe --repo gitlink-cli --limit 5", args: "--limit 5" },
|
||
{ name: "view", desc: "查看提交详情", detail: "查看某次提交修改了哪些文件,以及每个文件的增删行数", example: "gitlink-cli commit +view --sha 6042a7e", args: "--sha 6042a7ea937dae5314c6356a34655be026a7b753" },
|
||
{ name: "diff", desc: "查看代码差异", detail: "查看某次提交的具体代码变更内容(增删的具体代码行)", example: "gitlink-cli commit +diff --sha 6042a7e", args: "--sha 6042a7ea937dae5314c6356a34655be026a7b753" },
|
||
{ name: "blame", desc: "逐行追溯", detail: "查看文件每一行代码是谁在什么时候、通过哪次提交修改的", example: "gitlink-cli commit +blame --path README.md", args: "--path README.md" },
|
||
]
|
||
},
|
||
];
|
||
|
||
const container = document.getElementById('modules');
|
||
|
||
MODULES.forEach(mod => {
|
||
const card = document.createElement('div');
|
||
card.className = 'module-card';
|
||
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.commands.map(cmd => `
|
||
<div class="cmd-row" id="${mod.name}-${cmd.name}">
|
||
<div class="cmd-info">
|
||
<div class="cmd-name">+${cmd.name}</div>
|
||
<div class="cmd-desc">${cmd.desc} — ${cmd.detail}</div>
|
||
<div class="cmd-example">${cmd.example}</div>
|
||
</div>
|
||
<div class="cmd-action">
|
||
<button class="run-btn ${cmd.needAuth ? 'auth-needed' : ''}" onclick="runCommand('${mod.name}', '${cmd.name}', ${cmd.args ? "'" + cmd.args + "'" : 'null'}, ${!!cmd.needAuth})" id="btn-${mod.name}-${cmd.name}">
|
||
${cmd.needAuth ? '需认证' : '▶ 运行'}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="result-panel" id="result-${mod.name}-${cmd.name}"></div>
|
||
`).join('')}
|
||
</div>
|
||
`;
|
||
container.appendChild(card);
|
||
});
|
||
|
||
// Auto expand first module
|
||
document.querySelector('.module-body').classList.remove('collapsed');
|
||
document.querySelector('.arrow').classList.add('open');
|
||
|
||
function toggleModule(header) {
|
||
const body = header.nextElementSibling;
|
||
const arrow = header.querySelector('.arrow');
|
||
body.classList.toggle('collapsed');
|
||
arrow.classList.toggle('open');
|
||
}
|
||
|
||
async function runCommand(module, command, extraArgs, needAuth) {
|
||
const btn = document.getElementById(`btn-${module}-${command}`);
|
||
const panel = document.getElementById(`result-${module}-${command}`);
|
||
|
||
if (btn.disabled) return;
|
||
btn.disabled = true;
|
||
btn.textContent = '运行中...';
|
||
btn.classList.add('loading');
|
||
|
||
let url = `/api/run?module=${module}&command=${command}`;
|
||
if (extraArgs) url += `&args=${encodeURIComponent(extraArgs)}`;
|
||
|
||
try {
|
||
const resp = await fetch(url);
|
||
const data = await resp.json();
|
||
|
||
const statusClass = data.ok ? 'ok' : 'err';
|
||
const statusText = data.ok ? '成功' : '失败';
|
||
const output = typeof data.output === 'string'
|
||
? data.output
|
||
: JSON.stringify(data.output, null, 2);
|
||
|
||
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="closeResult('${module}','${command}')">×</span>
|
||
</div>
|
||
<div class="result-body">
|
||
<pre>${escapeHtml(output || data.error || '无输出')}</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="closeResult('${module}','${command}')">×</span>
|
||
</div>
|
||
<div class="result-body">
|
||
<pre>${escapeHtml(e.message)}</pre>
|
||
</div>
|
||
`;
|
||
panel.classList.add('show');
|
||
}
|
||
|
||
btn.disabled = false;
|
||
btn.textContent = needAuth ? '需认证' : '▶ 运行';
|
||
btn.classList.remove('loading');
|
||
}
|
||
|
||
function closeResult(module, command) {
|
||
document.getElementById(`result-${module}-${command}`).classList.remove('show');
|
||
}
|
||
|
||
function escapeHtml(str) {
|
||
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||
}
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|