gitlink-cli/showcase/index.html

278 lines
15 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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;">&#9632;</span>
${mod.title}
<span class="tag">${mod.commands.length} 个命令</span>
</h3>
<span class="arrow">&#9660;</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 ? '需认证' : '&#9654; 运行'}
</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}')">&times;</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}')">&times;</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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
</script>
</body>
</html>