feat: 流水线三级权限体系(手动多选+通配符授权)+ 菜单分级 + SSE 认证修复

权限模型(借鉴 Jenkins Role-Strategy 与云效,默认拒绝):
- 三级权限:查看(流水线+成功/失败日志)/ 发布(+构建、批量发布、重试)/ 管理(+配置、成员);admin 全局豁免
- 权限组(管理员配置):默认手动多选流水线(免正则,操作友好);高级模式仅 * 通配符(cex-*)
- 单流水线成员直授权作为例外补充;AI 批量发布逐 job 校验,越权明确拒绝

后端:
- 新增 PipelineRole/PipelineMember 表与 /api/pipeline-roles、/api/pipelines/:id/members CRUD
- RequirePipelineAccess/RequireRunAccess 中间件挂全流水线与运行路由;列表按可见性过滤并附 accessLevel/canRetry
- /api/me 会话接口;logs/stream SSE 支持 ?token= 认证(原先完全免鉴权)并校验查看权限
- 凭证/主机/集群/构建节点/工具/Webhook/Jenkins/GitOps/插件/设置全部 AdminOnly;触发人以会话身份落库

前端:
- useSession 会话 hook;Sidebar 菜单分级(资源/集成/系统管理 admin-only);admin 路由守卫
- 按钮级权限:执行/配置/删除/保存/重试按等级显隐;新页「流水线权限」与流水线成员弹窗(纯点选)
- 登录页记住密码;GUIDE/API 文档补权限章节
This commit is contained in:
Logan.Li committed 2026-10-27 17:34:41 +08:00
1 parent 7090c8efc2
commit 9103b13510
27 files changed
+1690 -190

No files matched your search

+14 -4
View File
@@ -14,10 +14,17 @@ import (
"flowstack/internal/model"
)
// ListPipelines GET /api/pipelines —— 附带每条流水线最近一次运行信息
// ListPipelines GET /api/pipelines —— 附带每条流水线最近一次运行信息与当前用户权限等级
func ListPipelines(c *gin.Context) {
username, isAdmin := currentUser(c)
ids, filter := AccessiblePipelineIDs(username, isAdmin)
q := database.DB.Order("updated_at DESC")
if filter {
q = q.Where("id IN ?", ids)
}
var list []model.Pipeline
database.DB.Order("updated_at DESC").Find(&list)
q.Find(&list)
// 每流水线最近一次 run(一次查询取全表后内存分组,数据量小;jianmu 用反规范化表,此处无需)
var runs []model.PipelineRun
@@ -31,11 +38,12 @@ func ListPipelines(c *gin.Context) {
type pipelineWithLastRun struct {
model.Pipeline
LastRun *model.PipelineRun `json:"lastRun"`
LastRun *model.PipelineRun `json:"lastRun"`
AccessLevel int `json:"accessLevel"` // 1查看 2执行 3管理 4管理员
}
result := make([]pipelineWithLastRun, 0, len(list))
for _, p := range list {
item := pipelineWithLastRun{Pipeline: p}
item := pipelineWithLastRun{Pipeline: p, AccessLevel: pipelineAccessLevel(username, isAdmin, &p)}
if r, ok := lastByPipeline[p.ID]; ok {
item.LastRun = &r
}
@@ -57,8 +65,10 @@ func GetPipelineDetail(c *gin.Context) {
database.DB.Where("pipeline_id = ?", p.ID).Find(&edges)
var lastRun model.PipelineRun
hasRun := database.DB.Where("pipeline_id = ?", p.ID).Order("started_at DESC").First(&lastRun).Error == nil
username, isAdmin := currentUser(c)
c.JSON(http.StatusOK, gin.H{
"pipeline": p, "nodes": nodes, "edges": edges,
"accessLevel": pipelineAccessLevel(username, isAdmin, &p),
"lastRun": func() *model.PipelineRun {
if hasRun {
return &lastRun