- Go gin 后端:流水线/节点/连线/运行/日志/GitOps/插件 REST API, SQLite(GORM) 自动建表 + seed,模拟执行引擎 + SSE 实时日志推送, 前端产物 go:embed 嵌入单二进制,一个端口同时服务 API 与页面 - React 19 前端:画布式流水线编辑器(React Flow + Tekton 任务库)、 GitOps 部署、批量发布、发布记录等 14 个模块页面, tRPC 全部替换为 REST(TanStack Query) - 首次启动生成随机密码管理员账号(日志 + data/initial-admin-password.txt) - run.sh 一键构建启动(默认端口 9527,支持 start/stop/restart/build/dev)
88 lines
3.5 KiB
Go
88 lines
3.5 KiB
Go
package handler
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"flowstack/internal/database"
|
|
"flowstack/internal/model"
|
|
)
|
|
|
|
// availablePlugins 可安装插件市场(演示数据)
|
|
var availablePlugins = []gin.H{
|
|
{"pluginId": "gitops-sync", "name": "GitOps Sync", "description": "Argo CD integration", "version": "1.3.0", "author": "OpsX Team", "category": "部署", "installs": 3420},
|
|
{"pluginId": "tekton-tasks", "name": "Tekton Tasks", "description": "Cloud-native CI tasks", "version": "2.1.0", "author": "CloudNative", "category": "构建", "installs": 5180},
|
|
{"pluginId": "dark-theme", "name": "Dark Theme", "description": "Dark UI theme", "version": "1.1.0", "author": "DesignLab", "category": "主题", "installs": 8920},
|
|
{"pluginId": "sonarqube", "name": "SonarQube Scanner", "description": "Code quality scan", "version": "3.2.0", "author": "CodeQuality", "category": "扫描", "installs": 2150},
|
|
{"pluginId": "vault-secrets", "name": "Vault Secrets", "description": "HashiCorp Vault integration", "version": "1.6.0", "author": "SecurityTeam", "category": "安全", "installs": 980},
|
|
{"pluginId": "matrix-build", "name": "Matrix Build", "description": "Multi-dimension parallel build", "version": "2.0.0", "author": "BuildMaster", "category": "构建", "installs": 1670},
|
|
{"pluginId": "slack-notify", "name": "Slack Notify", "description": "Slack notifications", "version": "1.3.0", "author": "NotifyHub", "category": "通知", "installs": 2890},
|
|
}
|
|
|
|
// ListPlugins GET /api/plugins
|
|
func ListPlugins(c *gin.Context) {
|
|
var list []model.Plugin
|
|
database.DB.Order("created_at DESC").Find(&list)
|
|
c.JSON(http.StatusOK, list)
|
|
}
|
|
|
|
// AvailablePlugins GET /api/plugins/available
|
|
func AvailablePlugins(c *gin.Context) {
|
|
c.JSON(http.StatusOK, availablePlugins)
|
|
}
|
|
|
|
// InstallPlugin POST /api/plugins/install
|
|
func InstallPlugin(c *gin.Context) {
|
|
var body struct {
|
|
PluginID string `json:"pluginId" binding:"required"`
|
|
Name string `json:"name" binding:"required"`
|
|
Description string `json:"description"`
|
|
Version string `json:"version" binding:"required"`
|
|
Author string `json:"author"`
|
|
Category string `json:"category"`
|
|
EntryPath string `json:"entryPath"`
|
|
}
|
|
if err := c.ShouldBindJSON(&body); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if body.Category == "" {
|
|
body.Category = "general"
|
|
}
|
|
p := model.Plugin{
|
|
PluginID: body.PluginID, Name: body.Name, Description: body.Description,
|
|
Version: body.Version, Author: body.Author, Category: body.Category, EntryPath: body.EntryPath,
|
|
Installed: true, Enabled: true, Config: map[string]any{},
|
|
}
|
|
if err := database.DB.Create(&p).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, p)
|
|
}
|
|
|
|
// TogglePlugin POST /api/plugins/:id/toggle {enabled}
|
|
func TogglePlugin(c *gin.Context) {
|
|
var p model.Plugin
|
|
if err := database.DB.First(&p, c.Param("id")).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "plugin not found"})
|
|
return
|
|
}
|
|
var body struct {
|
|
Enabled bool `json:"enabled"`
|
|
}
|
|
if err := c.ShouldBindJSON(&body); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
database.DB.Model(&p).Update("enabled", body.Enabled)
|
|
c.JSON(http.StatusOK, gin.H{"success": true, "enabled": body.Enabled})
|
|
}
|
|
|
|
// UninstallPlugin DELETE /api/plugins/:id
|
|
func UninstallPlugin(c *gin.Context) {
|
|
database.DB.Delete(&model.Plugin{}, c.Param("id"))
|
|
c.JSON(http.StatusOK, gin.H{"success": true})
|
|
}
|