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
+107
-68
@@ -59,101 +59,140 @@ func main() {
|
||||
{
|
||||
api.POST("/auth/login", handler.Login)
|
||||
api.POST("/auth/logout", func(c *gin.Context) { c.JSON(200, gin.H{"success": true}) })
|
||||
api.GET("/me", handler.Me)
|
||||
|
||||
// 用户管理(管理员)
|
||||
// 用户管理(列表登录即可读——权限成员选择器需要;增删改仅管理员)
|
||||
api.GET("/users", handler.ListUsers)
|
||||
users := api.Group("/users", handler.AdminOnly())
|
||||
{
|
||||
users.GET("", handler.ListUsers)
|
||||
users.POST("", handler.CreateUser)
|
||||
users.PUT("/:id", handler.UpdateUser)
|
||||
users.DELETE("/:id", handler.DeleteUser)
|
||||
users.POST("/:id/reset-password", handler.ResetPassword)
|
||||
users.POST("/ldap-import", handler.LdapImport)
|
||||
}
|
||||
// 流水线权限角色(管理员;Jenkins Item-role 正则风格)
|
||||
pipelineRoles := api.Group("/pipeline-roles", handler.AdminOnly())
|
||||
{
|
||||
pipelineRoles.GET("", handler.ListPipelineRoles)
|
||||
pipelineRoles.POST("", handler.CreatePipelineRole)
|
||||
pipelineRoles.PUT("/:id", handler.UpdatePipelineRole)
|
||||
pipelineRoles.DELETE("/:id", handler.DeletePipelineRole)
|
||||
}
|
||||
api.POST("/ldap/users", handler.AdminOnly(), handler.LdapListUsers)
|
||||
api.POST("/settings/ldap/test", handler.AdminOnly(), handler.TestLDAP)
|
||||
api.GET("/docs", handler.GetDocs(docsFS))
|
||||
|
||||
api.GET("/summary", handler.GetSummary)
|
||||
api.GET("/settings", handler.GetSettings)
|
||||
api.PUT("/settings", handler.UpdateSettings)
|
||||
api.POST("/settings/test-llm", handler.TestLLM)
|
||||
api.POST("/settings/test-lark", handler.TestLark)
|
||||
api.GET("/settings", handler.AdminOnly(), handler.GetSettings)
|
||||
api.PUT("/settings", handler.AdminOnly(), handler.UpdateSettings)
|
||||
api.POST("/settings/test-llm", handler.AdminOnly(), handler.TestLLM)
|
||||
api.POST("/settings/test-lark", handler.AdminOnly(), handler.TestLark)
|
||||
|
||||
api.POST("/ai/chat", handler.AiChat)
|
||||
api.POST("/ai/chat/stream", handler.AiChatStream)
|
||||
api.POST("/ai/release", handler.AiRelease(hub))
|
||||
api.POST("/batch/parse", handler.BatchParse)
|
||||
|
||||
// 流水线:查看 viewer / 编辑配置 owner / 触发构建 operator;新建与删除仅管理员
|
||||
api.GET("/pipelines", handler.ListPipelines)
|
||||
api.POST("/pipelines", handler.CreatePipeline)
|
||||
api.GET("/pipelines/:id", handler.GetPipelineDetail)
|
||||
api.PUT("/pipelines/:id", handler.UpdatePipeline)
|
||||
api.DELETE("/pipelines/:id", handler.DeletePipeline)
|
||||
api.PUT("/pipelines/:id/nodes", handler.SaveNodes)
|
||||
api.PUT("/pipelines/:id/edges", handler.SaveEdges)
|
||||
api.POST("/pipelines/:id/runs", handler.CreateRun(hub))
|
||||
api.POST("/pipelines", handler.AdminOnly(), handler.CreatePipeline)
|
||||
api.GET("/pipelines/:id", handler.RequirePipelineAccess(handler.LevelRank(handler.LevelViewer)), handler.GetPipelineDetail)
|
||||
api.PUT("/pipelines/:id", handler.RequirePipelineAccess(handler.LevelRank(handler.LevelOwner)), handler.UpdatePipeline)
|
||||
api.DELETE("/pipelines/:id", handler.AdminOnly(), handler.DeletePipeline)
|
||||
api.PUT("/pipelines/:id/nodes", handler.RequirePipelineAccess(handler.LevelRank(handler.LevelOwner)), handler.SaveNodes)
|
||||
api.PUT("/pipelines/:id/edges", handler.RequirePipelineAccess(handler.LevelRank(handler.LevelOwner)), handler.SaveEdges)
|
||||
api.POST("/pipelines/:id/runs", handler.RequirePipelineAccess(handler.LevelRank(handler.LevelOperator)), handler.CreateRun(hub))
|
||||
api.POST("/hooks/pipeline/:id", handler.InboundPipelineHook(hub))
|
||||
|
||||
// 单流水线成员:查看列表 viewer,增删 owner
|
||||
api.GET("/pipelines/:id/members", handler.RequirePipelineAccess(handler.LevelRank(handler.LevelViewer)), handler.ListPipelineMembers)
|
||||
api.POST("/pipelines/:id/members", handler.RequirePipelineAccess(handler.LevelRank(handler.LevelOwner)), handler.AddPipelineMember)
|
||||
api.DELETE("/pipelines/:id/members/:mid", handler.RequirePipelineAccess(handler.LevelRank(handler.LevelOwner)), handler.DeletePipelineMember)
|
||||
|
||||
api.GET("/runs", handler.ListRuns)
|
||||
api.GET("/runs/:runId", handler.GetRun)
|
||||
api.GET("/runs/:runId/logs/stream", handler.StreamLogs(hub))
|
||||
api.POST("/runs/:runId/retry", handler.RetryRun(hub))
|
||||
api.GET("/runs/:runId", handler.RequireRunAccess(handler.LevelRank(handler.LevelViewer)), handler.GetRun)
|
||||
api.GET("/runs/:runId/logs/stream", handler.RequireRunAccess(handler.LevelRank(handler.LevelViewer)), handler.StreamLogs(hub))
|
||||
api.POST("/runs/:runId/retry", handler.RequireRunAccess(handler.LevelRank(handler.LevelOperator)), handler.RetryRun(hub))
|
||||
|
||||
api.GET("/gitops/apps", handler.ListGitOpsApps)
|
||||
api.GET("/gitops/argo/apps", handler.ArgoList)
|
||||
api.POST("/gitops/argo/sync", handler.ArgoSync)
|
||||
api.GET("/gitops/argo/:appName/tree", handler.ArgoResourceTree)
|
||||
api.GET("/gitops/argo/:appName/diff", handler.ArgoDiff)
|
||||
// GitOps 部署(全局基础设施操作,未按流水线隔离)→ 管理员
|
||||
gitops := api.Group("/gitops", handler.AdminOnly())
|
||||
{
|
||||
gitops.GET("/apps", handler.ListGitOpsApps)
|
||||
gitops.GET("/argo/apps", handler.ArgoList)
|
||||
gitops.POST("/argo/sync", handler.ArgoSync)
|
||||
gitops.GET("/argo/:appName/tree", handler.ArgoResourceTree)
|
||||
gitops.GET("/argo/:appName/diff", handler.ArgoDiff)
|
||||
}
|
||||
|
||||
api.GET("/plugins", handler.ListPlugins)
|
||||
api.GET("/plugins/available", handler.AvailablePlugins)
|
||||
api.POST("/plugins/install", handler.InstallPlugin)
|
||||
api.POST("/plugins/:id/toggle", handler.TogglePlugin)
|
||||
api.DELETE("/plugins/:id", handler.UninstallPlugin)
|
||||
// 插件市场 → 管理员
|
||||
plugins := api.Group("/plugins", handler.AdminOnly())
|
||||
{
|
||||
plugins.GET("", handler.ListPlugins)
|
||||
plugins.GET("/available", handler.AvailablePlugins)
|
||||
plugins.POST("/install", handler.InstallPlugin)
|
||||
plugins.POST("/:id/toggle", handler.TogglePlugin)
|
||||
plugins.DELETE("/:id", handler.UninstallPlugin)
|
||||
}
|
||||
|
||||
api.GET("/hosts", handler.ListHosts)
|
||||
api.POST("/hosts", handler.CreateHost)
|
||||
api.PUT("/hosts/:id", handler.UpdateHost)
|
||||
api.DELETE("/hosts/:id", handler.DeleteHost)
|
||||
api.POST("/hosts/:id/test", handler.TestHost)
|
||||
|
||||
api.GET("/clusters", handler.ListClusters)
|
||||
api.POST("/clusters", handler.CreateCluster)
|
||||
api.PUT("/clusters/:id", handler.UpdateCluster)
|
||||
api.DELETE("/clusters/:id", handler.DeleteCluster)
|
||||
api.POST("/clusters/:id/test", handler.TestCluster)
|
||||
|
||||
api.GET("/credentials", handler.ListCredentials)
|
||||
api.POST("/credentials", handler.CreateCredential)
|
||||
api.PUT("/credentials/:id", handler.UpdateCredential)
|
||||
api.DELETE("/credentials/:id", handler.DeleteCredential)
|
||||
|
||||
api.GET("/build-nodes", handler.ListBuildNodes)
|
||||
api.POST("/build-nodes", handler.CreateBuildNode)
|
||||
api.PUT("/build-nodes/:id", handler.UpdateBuildNode)
|
||||
api.DELETE("/build-nodes/:id", handler.DeleteBuildNode)
|
||||
api.POST("/build-nodes/:id/test", handler.TestBuildNode)
|
||||
api.POST("/build-nodes/:id/disk-test", handler.TestBuildNodeDisk)
|
||||
|
||||
api.GET("/tools", handler.ListTools)
|
||||
api.PUT("/tools/:id", handler.UpdateTool)
|
||||
api.POST("/tools/:id/check", handler.CheckTool)
|
||||
api.POST("/tools/:id/install", handler.InstallTool)
|
||||
|
||||
api.GET("/webhooks", handler.ListWebhooks)
|
||||
api.POST("/webhooks", handler.CreateWebhook)
|
||||
api.PUT("/webhooks/:id", handler.UpdateWebhook)
|
||||
api.DELETE("/webhooks/:id", handler.DeleteWebhook)
|
||||
api.POST("/webhooks/:id/test", handler.TestWebhook)
|
||||
|
||||
api.GET("/jenkins-servers", handler.ListJenkinsServers)
|
||||
api.POST("/jenkins-servers", handler.CreateJenkinsServer)
|
||||
api.PUT("/jenkins-servers/:id", handler.UpdateJenkinsServer)
|
||||
api.DELETE("/jenkins-servers/:id", handler.DeleteJenkinsServer)
|
||||
api.POST("/jenkins-servers/:id/test", handler.TestJenkinsServer)
|
||||
api.GET("/jenkins-servers/:id/jobs", handler.ListJenkinsJobs)
|
||||
api.POST("/jenkins-servers/:id/import", handler.ImportJenkinsJobs)
|
||||
// 资源(主机/集群/凭证/构建节点/工具/Webhook)与 Jenkins 集成 → 管理员
|
||||
hosts := api.Group("/hosts", handler.AdminOnly())
|
||||
{
|
||||
hosts.GET("", handler.ListHosts)
|
||||
hosts.POST("", handler.CreateHost)
|
||||
hosts.PUT("/:id", handler.UpdateHost)
|
||||
hosts.DELETE("/:id", handler.DeleteHost)
|
||||
hosts.POST("/:id/test", handler.TestHost)
|
||||
}
|
||||
clusters := api.Group("/clusters", handler.AdminOnly())
|
||||
{
|
||||
clusters.GET("", handler.ListClusters)
|
||||
clusters.POST("", handler.CreateCluster)
|
||||
clusters.PUT("/:id", handler.UpdateCluster)
|
||||
clusters.DELETE("/:id", handler.DeleteCluster)
|
||||
clusters.POST("/:id/test", handler.TestCluster)
|
||||
}
|
||||
credentials := api.Group("/credentials", handler.AdminOnly())
|
||||
{
|
||||
credentials.GET("", handler.ListCredentials)
|
||||
credentials.POST("", handler.CreateCredential)
|
||||
credentials.PUT("/:id", handler.UpdateCredential)
|
||||
credentials.DELETE("/:id", handler.DeleteCredential)
|
||||
}
|
||||
buildNodes := api.Group("/build-nodes", handler.AdminOnly())
|
||||
{
|
||||
buildNodes.GET("", handler.ListBuildNodes)
|
||||
buildNodes.POST("", handler.CreateBuildNode)
|
||||
buildNodes.PUT("/:id", handler.UpdateBuildNode)
|
||||
buildNodes.DELETE("/:id", handler.DeleteBuildNode)
|
||||
buildNodes.POST("/:id/test", handler.TestBuildNode)
|
||||
buildNodes.POST("/:id/disk-test", handler.TestBuildNodeDisk)
|
||||
}
|
||||
tools := api.Group("/tools", handler.AdminOnly())
|
||||
{
|
||||
tools.GET("", handler.ListTools)
|
||||
tools.PUT("/:id", handler.UpdateTool)
|
||||
tools.POST("/:id/check", handler.CheckTool)
|
||||
tools.POST("/:id/install", handler.InstallTool)
|
||||
}
|
||||
webhooks := api.Group("/webhooks", handler.AdminOnly())
|
||||
{
|
||||
webhooks.GET("", handler.ListWebhooks)
|
||||
webhooks.POST("", handler.CreateWebhook)
|
||||
webhooks.PUT("/:id", handler.UpdateWebhook)
|
||||
webhooks.DELETE("/:id", handler.DeleteWebhook)
|
||||
webhooks.POST("/:id/test", handler.TestWebhook)
|
||||
}
|
||||
jenkins := api.Group("/jenkins-servers", handler.AdminOnly())
|
||||
{
|
||||
jenkins.GET("", handler.ListJenkinsServers)
|
||||
jenkins.POST("", handler.CreateJenkinsServer)
|
||||
jenkins.PUT("/:id", handler.UpdateJenkinsServer)
|
||||
jenkins.DELETE("/:id", handler.DeleteJenkinsServer)
|
||||
jenkins.POST("/:id/test", handler.TestJenkinsServer)
|
||||
jenkins.GET("/:id/jobs", handler.ListJenkinsJobs)
|
||||
jenkins.POST("/:id/import", handler.ImportJenkinsJobs)
|
||||
}
|
||||
}
|
||||
|
||||
distFS, _ := fs.Sub(staticFS, "static")
|
||||
|
||||
Reference in new issue
Block a user