diff --git a/doc/changes/showcase-dashboard.md b/doc/changes/showcase-dashboard.md new file mode 100644 index 0000000..94ac8d0 --- /dev/null +++ b/doc/changes/showcase-dashboard.md @@ -0,0 +1,39 @@ +# 变更说明:Showcase Dashboard 交互式展示页 + +## 概述 + +新增 GitLink CLI Showcase Dashboard —— 一个基于 Go + HTML 的 Web 展示面板,展示 gitlink-cli 的模块功能,支持在线执行命令并查看结果。 + +## 功能 + +- **12 个模块卡片**:milestone、webhook、label、commit、wiki、file、member、watch、star、issue 批量操作、repo 批量操作、org 批量操作 +- **在线执行**:每个卡片可输入参数,点击执行按钮直接调用 gitlink-cli 命令 +- **实时输出**:命令结果以 JSON/表格形式展示在页面上 +- **Docker 部署**:提供 Dockerfile,一键容器化部署 + +## 文件说明 + +| 文件 | 说明 | +|------|------| +| `showcase/main.go` | Go HTTP 服务器,提供 `/api/run` 接口执行 CLI 命令 | +| `showcase/index.html` | 前端页面,暗色主题,12 个模块卡片 | +| `showcase/Dockerfile` | 多阶段构建,基于 golang:1.26 镜像 | +| `showcase/deploy.sh` | 部署脚本 | +| `showcase/pipeline.yml` | GitLink DevOps 流水线配置 | + +## 访问方式 + +```bash +# 本地运行 +cd showcase && go run main.go +# 访问 http://localhost:9090 + +# Docker 部署 +docker build -t gitlink-cli-showcase ./showcase +docker run -p 9090:9090 -e GITLINK_TOKEN=xxx gitlink-cli-showcase +``` + +## 注意事项 + +- 需要预编译 `gitlink-cli` 二进制文件放在同目录 +- 服务器环境需配置 `GITLINK_TOKEN` 环境变量 diff --git a/showcase/Dockerfile b/showcase/Dockerfile new file mode 100644 index 0000000..8a24528 --- /dev/null +++ b/showcase/Dockerfile @@ -0,0 +1,30 @@ +# Stage 1: Build +FROM golang:1.26-alpine AS builder + +ENV GOPROXY=https://goproxy.cn,direct + +WORKDIR /build +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . + +# Build gitlink-cli binary +RUN CGO_ENABLED=0 GOOS=linux go build -o gitlink-cli . + +# Build showcase server binary +RUN CGO_ENABLED=0 GOOS=linux go build -o showcase-server ./showcase/ + +# Stage 2: Runtime +FROM alpine:latest + +RUN apk add --no-cache ca-certificates git + +WORKDIR /app + +COPY --from=builder /build/gitlink-cli . +COPY --from=builder /build/showcase-server . + +EXPOSE 9090 + +CMD ["./showcase-server"] diff --git a/showcase/deploy.sh b/showcase/deploy.sh new file mode 100644 index 0000000..a63ddb9 --- /dev/null +++ b/showcase/deploy.sh @@ -0,0 +1,14 @@ +# 展示页部署命令(在本地终端手动执行) + +# 1. 用 SCP 上传展示页到 ECS(需要手动输入密码:pd1@YwC#WRFVHkXc8nvu!4) +scp "d:/自用/self/word/大三下/软件演化/gitlink-cli/showcase/index.html" root@121.41.210.165:/opt/showcase/index.html + +# 2. SSH 到 ECS(密码:pd1@YwC#WRFVHkXc8nvu!4) +ssh root@121.41.210.165 + +# 登录后在 ECS 上执行: +mkdir -p /opt/showcase +docker run -d --name showcase -p 8080:80 -v /opt/showcase:/usr/share/nginx/html:ro --restart unless-stopped nginx:alpine + +# 3. 访问 +# 浏览器打开 http://121.41.210.165:8080 diff --git a/showcase/index.html b/showcase/index.html new file mode 100644 index 0000000..9eedabc --- /dev/null +++ b/showcase/index.html @@ -0,0 +1,417 @@ + + + + + + gitlink-cli 功能增强展示 + + + +
+

gitlink-cli 功能增强

+

软件演化与运维 课程实践 — 进阶任务 子任务一

+
演示仓库:chroe/gitlink-cli — 展开命令、填入参数、点击运行查看真实结果
+
+
+
8
新增模块
+
47
新增命令
+
8
批量操作
+
47+
单元测试
+
+
JSON
+
输出格式 ▾
+
+
+
+ + + + diff --git a/showcase/main.go b/showcase/main.go new file mode 100644 index 0000000..1d13340 --- /dev/null +++ b/showcase/main.go @@ -0,0 +1,149 @@ +package main + +import ( + _ "embed" + "encoding/json" + "fmt" + "log" + "net/http" + "os" + "os/exec" + "path/filepath" + "strings" +) + +//go:embed index.html +var indexHTML []byte + +type RunResult struct { + OK bool `json:"ok"` + Command string `json:"command"` + Output interface{} `json:"output"` + Error string `json:"error,omitempty"` +} + +func main() { + port := os.Getenv("PORT") + if port == "" { + port = "9090" + } + cliBin := findCLIBinary() + + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.Write(indexHTML) + }) + + http.HandleFunc("/api/run", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + + module := r.URL.Query().Get("module") + command := r.URL.Query().Get("command") + owner := r.URL.Query().Get("owner") + repo := r.URL.Query().Get("repo") + format := r.URL.Query().Get("format") + extraArgs := r.URL.Query().Get("args") + + if module == "" || command == "" { + json.NewEncoder(w).Encode(RunResult{Error: "missing module or command"}) + return + } + if owner == "" { + owner = "chroe" + } + if repo == "" { + if module == "wiki" { + repo = "gitlink_help_center" + } else { + repo = "gitlink-cli" + } + } + if format == "" { + format = "json" + } + + args := []string{module, "+" + command, "--owner", owner, "--repo", repo, "--format", format} + if extraArgs != "" { + args = append(args, parseShellArgs(extraArgs)...) + } + + cmdStr := "gitlink-cli " + strings.Join(args, " ") + log.Printf("Running: %s", cmdStr) + + cmd := exec.Command(cliBin, args...) + output, err := cmd.CombinedOutput() + + result := RunResult{ + Command: cmdStr, + } + + if err != nil { + result.Error = strings.TrimSpace(string(output)) + result.Output = nil + } else { + result.OK = true + var parsed interface{} + if json.Unmarshal(output, &parsed) == nil { + result.Output = parsed + } else { + result.Output = strings.TrimSpace(string(output)) + } + } + + json.NewEncoder(w).Encode(result) + }) + + fmt.Printf("Showcase Dashboard running at http://localhost:%s\n", port) + log.Fatal(http.ListenAndServe(":"+port, nil)) +} + +// parseShellArgs splits a shell-style argument string, respecting quoted values. +// e.g. `--content "Hello Wiki!" --message "create page"` -> ["--content", "Hello Wiki!", "--message", "create page"] +func parseShellArgs(s string) []string { + var args []string + var current strings.Builder + inQuote := false + + for i := 0; i < len(s); i++ { + ch := s[i] + if ch == '"' { + inQuote = !inQuote + continue + } + if ch == ' ' && !inQuote { + if current.Len() > 0 { + args = append(args, current.String()) + current.Reset() + } + continue + } + current.WriteByte(ch) + } + if current.Len() > 0 { + args = append(args, current.String()) + } + return args +} + +func findCLIBinary() string { + exe, _ := os.Executable() + exeDir := filepath.Dir(exe) + + candidates := []string{ + filepath.Join(exeDir, "gitlink-cli.exe"), + filepath.Join(exeDir, "gitlink-cli"), + filepath.Join(exeDir, "..", "gitlink-cli.exe"), + filepath.Join(exeDir, "..", "gitlink-cli"), + "./gitlink-cli.exe", + "./gitlink-cli", + "../gitlink-cli.exe", + "../gitlink-cli", + } + for _, c := range candidates { + if _, err := os.Stat(c); err == nil { + abs, _ := filepath.Abs(c) + return abs + } + } + return "gitlink-cli" +} diff --git a/showcase/pipeline.yml b/showcase/pipeline.yml new file mode 100644 index 0000000..cfb3330 --- /dev/null +++ b/showcase/pipeline.yml @@ -0,0 +1,31 @@ +version: 2 +name: 构建部署Showcase +description: "代码提交自动触发:在服务器上拉取代码、构建Docker镜像并部署" +global: + concurrent: 1 +trigger: + webhook: gitlink@1.0.0 + event: + - ref: push + ruleset-operator: AND +workflow: + - ref: start + name: 开始 + task: start + - ref: ssh_cmd_0 + name: SSH部署到服务器 + task: ssh_cmd@1.1.1 + input: + ssh_pass: ((deploy_server.server_password)) + ssh_ip: '"118.31.4.168"' + ssh_port: '"22"' + ssh_user: '"root"' + ssh_cmd: >- + "mkdir -p /opt/gitlink-cli && cd /opt/gitlink-cli && (git clone https://gitlink.org.cn/chroe/gitlink-cli.git . || git pull origin master) && docker build -f showcase/Dockerfile -t gitlink-cli-showcase . && docker stop gitlink-cli-showcase || true && docker rm gitlink-cli-showcase || true && docker run -d -p 9090:9090 --name gitlink-cli-showcase --restart unless-stopped gitlink-cli-showcase" + needs: + - start + - ref: end + name: 结束 + task: end + needs: + - ssh_cmd_0