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:
1 parent
7090c8efc2
commit
9103b13510
27 files changed
+1690
-190
No files matched your search
@@ -29,7 +29,12 @@ func CreateRun(hub *Hub) gin.HandlerFunc {
|
||||
}
|
||||
_ = c.ShouldBindJSON(&body)
|
||||
|
||||
run, err := triggerPipelineRun(hub, p, body.Branch, body.Env, body.TriggerBy, body.TriggerType)
|
||||
// 触发人以会话身份为准(服务端可信,覆盖前端传入)
|
||||
triggerBy := c.GetString("username")
|
||||
if triggerBy == "" {
|
||||
triggerBy = body.TriggerBy
|
||||
}
|
||||
run, err := triggerPipelineRun(hub, p, body.Branch, body.Env, triggerBy, body.TriggerType)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
@@ -222,9 +227,15 @@ func stageScript(st *model.RunStage) []string {
|
||||
}
|
||||
}
|
||||
|
||||
// ListRuns GET /api/runs?pipelineId=&status=
|
||||
// ListRuns GET /api/runs?pipelineId=&status= —— 按流水线可见性过滤,附 canRetry
|
||||
func ListRuns(c *gin.Context) {
|
||||
username, isAdmin := currentUser(c)
|
||||
ids, filter := AccessiblePipelineIDs(username, isAdmin)
|
||||
|
||||
q := database.DB.Model(&model.PipelineRun{}).Order("started_at DESC").Limit(100)
|
||||
if filter {
|
||||
q = q.Where("pipeline_id IN ?", ids)
|
||||
}
|
||||
if v := c.Query("pipelineId"); v != "" {
|
||||
q = q.Where("pipeline_id = ?", v)
|
||||
}
|
||||
@@ -233,7 +244,25 @@ func ListRuns(c *gin.Context) {
|
||||
}
|
||||
var runs []model.PipelineRun
|
||||
q.Find(&runs)
|
||||
c.JSON(http.StatusOK, runs)
|
||||
|
||||
type runWithPerm struct {
|
||||
model.PipelineRun
|
||||
CanRetry bool `json:"canRetry"`
|
||||
}
|
||||
levelCache := map[uint]int{}
|
||||
out := make([]runWithPerm, 0, len(runs))
|
||||
for _, r := range runs {
|
||||
lv, ok := levelCache[r.PipelineID]
|
||||
if !ok {
|
||||
var p model.Pipeline
|
||||
if database.DB.First(&p, r.PipelineID).Error == nil {
|
||||
lv = pipelineAccessLevel(username, isAdmin, &p)
|
||||
}
|
||||
levelCache[r.PipelineID] = lv
|
||||
}
|
||||
out = append(out, runWithPerm{PipelineRun: r, CanRetry: lv >= LevelRank(LevelOperator)})
|
||||
}
|
||||
c.JSON(http.StatusOK, out)
|
||||
}
|
||||
|
||||
// GetRun GET /api/runs/:runId
|
||||
@@ -316,7 +345,7 @@ func RetryRun(hub *Hub) gin.HandlerFunc {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "pipeline not found"})
|
||||
return
|
||||
}
|
||||
run, err := triggerPipelineRun(hub, p, prev.Branch, prev.Env, "admin", "manual")
|
||||
run, err := triggerPipelineRun(hub, p, prev.Branch, prev.Env, c.GetString("username"), "manual")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
|
||||
Reference in new issue
Block a user