feat: FlowStack 发布系统首个可运行版本 (gin + React)

- 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)
This commit is contained in:
Logan.Li committed 2026-10-27 02:44:10 +08:00
commit 04d1342b27
115 files changed
+23268

No files matched your search

+87
View File
@@ -0,0 +1,87 @@
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})
}