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

+107 -68
View File
@@ -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")