feat(rbac): F3 前端 RBAC 按钮级控制
后端: - 新增 GET /user/permissions 端点返回当前用户权限码 - Login 响应附带 permissions 数组 - collectUserPermissionCodes 用 raw SQL JOIN 避免 GORM many2many 表名问题 - admin 用户返回 ['*'] 通配符 前端: - usePermission hook (hasPermission/hasAnyPermission/hasAllPermissions) - <HasPermission> 组件 (permission/anyOf/allOf + fallback) - zustand store 新增 permissions 字段 - Sidebar 菜单递归过滤 (42 菜单项全部标注 permission) - ServerManager: 添加/导入/批量删除按钮按权限显隐 - ServerTableRow: 终端/编辑/文件/删除按权限显隐 - SystemUserTab: 新建/导入/编辑/删除按钮按权限显隐 - MainLayout: 页面刷新自动拉取权限 - Login: 登录时存储 permissions 验证: - admin: permissions=['*'] 通配符 - viewer: 7 个只读权限 (dashboard:view/server:view/...) - viewer 无 server:create/delete - 10/10 回归测试通过 - npm run build 零错误
This commit is contained in:
1 parent
77c234b060
commit
be2a377f01
30 files changed
+2328
-462
No files matched your search
+78
-23
@@ -82,7 +82,7 @@ func SeedData(db *gorm.DB) {
|
||||
{"key": "purpose", "label": "用途说明", "type": "textarea", "required": true}
|
||||
]
|
||||
}`,
|
||||
FlowConfig: `[{"name":"直接主管","approver_role":"team_lead"},{"name":"运维负责人","approver_role":"ops_manager"}]`,
|
||||
FlowConfig: `[{"name":"直接主管","approver_role":"team_lead","step_type":"audit"},{"name":"运维负责人","approver_role":"ops_manager","step_type":"audit"},{"name":"运维执行","approver_role":"ops_manager","step_type":"execute"}]`,
|
||||
Enabled: true,
|
||||
},
|
||||
{
|
||||
@@ -106,7 +106,7 @@ func SeedData(db *gorm.DB) {
|
||||
{"key": "remark", "label": "备注", "type": "textarea", "required": false}
|
||||
]
|
||||
}`,
|
||||
FlowConfig: `[{"name":"技术负责人","approver_role":"tech_lead"},{"name":"运维负责人","approver_role":"ops_manager"}]`,
|
||||
FlowConfig: `[{"name":"技术负责人","approver_role":"tech_lead","step_type":"audit"},{"name":"运维负责人","approver_role":"ops_manager","step_type":"audit"},{"name":"部署执行","approver_role":"ops_manager","step_type":"execute"}]`,
|
||||
Enabled: true,
|
||||
},
|
||||
{
|
||||
@@ -125,7 +125,7 @@ func SeedData(db *gorm.DB) {
|
||||
{"key": "dept", "label": "所属部门", "type": "text", "placeholder": "如 基础架构部", "required": true}
|
||||
]
|
||||
}`,
|
||||
FlowConfig: `[{"name":"直接主管","approver_role":"team_lead"},{"name":"安全负责人","approver_role":"security_lead"}]`,
|
||||
FlowConfig: `[{"name":"直接主管","approver_role":"team_lead","step_type":"audit"},{"name":"安全负责人","approver_role":"security_lead","step_type":"audit"},{"name":"权限开通","approver_role":"ops_manager","step_type":"execute"}]`,
|
||||
Enabled: true,
|
||||
},
|
||||
}
|
||||
@@ -182,6 +182,11 @@ func SeedData(db *gorm.DB) {
|
||||
{Name: "LDAP配置", Code: "system:ldap", Type: "menu", Sort: 83, Status: 1},
|
||||
// 仪表盘
|
||||
{Name: "仪表盘", Code: "dashboard:view", Type: "menu", Sort: 90, Status: 1},
|
||||
// 安全审计
|
||||
{Name: "终端审计", Code: "audit:terminal", Type: "menu", Sort: 91, Status: 1},
|
||||
{Name: "操作审计", Code: "audit:operations", Type: "menu", Sort: 92, Status: 1},
|
||||
{Name: "登录日志", Code: "audit:login-logs", Type: "menu", Sort: 93, Status: 1},
|
||||
{Name: "会话阻断", Code: "audit:kill-session", Type: "button", Sort: 94, Status: 1},
|
||||
}
|
||||
db.Create(&perms)
|
||||
zap.L().Info("seeded default permissions")
|
||||
@@ -192,13 +197,16 @@ func SeedData(db *gorm.DB) {
|
||||
db.Model(&model.Role{}).Count(&roleCount)
|
||||
if roleCount == 0 {
|
||||
roles := []model.Role{
|
||||
{Name: "管理员", Code: "admin", Description: "系统管理员,拥有全部权限", Status: 1},
|
||||
{Name: "运维工程师", Code: "ops", Description: "日常运维管理", Status: 1},
|
||||
{Name: "开发工程师", Code: "developer", Description: "开发人员,只读权限", Status: 1},
|
||||
{Name: "只读用户", Code: "viewer", Description: "仅查看权限", Status: 1},
|
||||
{Name: "超级管理员", Code: "super_admin", Description: "系统最高权限,管理所有资源和配置", Status: 1, IsSystem: true},
|
||||
{Name: "Kubernetes管理员", Code: "k8s_admin", Description: "管理K8s集群、节点、工作负载和存储", Status: 1, IsSystem: true},
|
||||
{Name: "资源管理员", Code: "resource_admin", Description: "管理服务器、云账号、域名和云资源", Status: 1, IsSystem: true},
|
||||
{Name: "监控管理员", Code: "monitor_admin", Description: "管理告警、监控指标和采集Agent", Status: 1, IsSystem: true},
|
||||
{Name: "安全审计员", Code: "security_auditor", Description: "审计终端会话、操作日志和登录记录", Status: 1, IsSystem: true},
|
||||
{Name: "发布管理员", Code: "release_admin", Description: "管理流水线、应用发布和审批流程", Status: 1, IsSystem: true},
|
||||
{Name: "只读用户", Code: "viewer", Description: "仅查看仪表盘和资源概览,无操作权限", Status: 1, IsSystem: true},
|
||||
}
|
||||
db.Create(&roles)
|
||||
zap.L().Info("seeded default roles")
|
||||
zap.L().Info("seeded 7 system default roles")
|
||||
|
||||
// 为各角色分配权限
|
||||
var allPerms []model.Permission
|
||||
@@ -206,37 +214,71 @@ func SeedData(db *gorm.DB) {
|
||||
for _, role := range roles {
|
||||
var permIds []uint
|
||||
switch role.Code {
|
||||
case "admin":
|
||||
case "super_admin":
|
||||
for _, p := range allPerms {
|
||||
permIds = append(permIds, p.ID)
|
||||
}
|
||||
case "ops":
|
||||
case "k8s_admin":
|
||||
k8sPrefixes := []string{"k8s:", "server:webshell", "dashboard:view", "deploy:"}
|
||||
for _, p := range allPerms {
|
||||
if !strings.HasPrefix(p.Code, "system:") {
|
||||
permIds = append(permIds, p.ID)
|
||||
for _, pre := range k8sPrefixes {
|
||||
if strings.HasPrefix(p.Code, pre) {
|
||||
permIds = append(permIds, p.ID)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
case "developer":
|
||||
devPerms := map[string]bool{
|
||||
"dashboard:view": true, "server:view": true, "cloud:account": true,
|
||||
"cloud:domain": true, "cloud:billing": true,
|
||||
"k8s:cluster": true, "k8s:nodes": true, "k8s:workloads": true, "k8s:topology": true,
|
||||
"monitor:alert": true, "deploy:pipeline": true, "deploy:release": true,
|
||||
"approval:apply": true, "aiops:assistant": true, "aiops:analysis": true, "aiops:forecast": true,
|
||||
}
|
||||
case "resource_admin":
|
||||
resPrefixes := []string{"server:", "cloud:", "dashboard:view", "k8s:topology"}
|
||||
for _, p := range allPerms {
|
||||
if devPerms[p.Code] {
|
||||
permIds = append(permIds, p.ID)
|
||||
for _, pre := range resPrefixes {
|
||||
if strings.HasPrefix(p.Code, pre) {
|
||||
permIds = append(permIds, p.ID)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
case "monitor_admin":
|
||||
monPrefixes := []string{"monitor:", "agent:", "dashboard:view", "server:view"}
|
||||
for _, p := range allPerms {
|
||||
for _, pre := range monPrefixes {
|
||||
if strings.HasPrefix(p.Code, pre) {
|
||||
permIds = append(permIds, p.ID)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
case "security_auditor":
|
||||
auditPrefixes := []string{"audit:", "dashboard:view", "server:view", "system:audit"}
|
||||
for _, p := range allPerms {
|
||||
for _, pre := range auditPrefixes {
|
||||
if strings.HasPrefix(p.Code, pre) {
|
||||
permIds = append(permIds, p.ID)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
case "release_admin":
|
||||
relPrefixes := []string{"deploy:", "approval:", "dashboard:view", "server:view", "k8s:cluster", "k8s:workloads"}
|
||||
for _, p := range allPerms {
|
||||
for _, pre := range relPrefixes {
|
||||
if strings.HasPrefix(p.Code, pre) {
|
||||
permIds = append(permIds, p.ID)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
case "viewer":
|
||||
for _, p := range allPerms {
|
||||
if p.Code == "dashboard:view" || p.Code == "server:view" || p.Code == "monitor:alert" {
|
||||
if p.Code == "dashboard:view" || p.Code == "server:view" || p.Code == "monitor:alert" ||
|
||||
p.Code == "k8s:cluster" || p.Code == "k8s:topology" || p.Code == "cloud:billing" ||
|
||||
p.Code == "deploy:pipeline" {
|
||||
permIds = append(permIds, p.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(permIds) > 0 {
|
||||
permIds = dedupeUint(permIds)
|
||||
var rolePerms []model.Permission
|
||||
db.Where("id IN ?", permIds).Find(&rolePerms)
|
||||
db.Model(&role).Association("Permissions").Append(rolePerms)
|
||||
@@ -650,3 +692,16 @@ func SeedData(db *gorm.DB) {
|
||||
ti := toolinstaller.New(db)
|
||||
ti.SeedTools()
|
||||
}
|
||||
|
||||
// dedupeUint 去重 uint 切片
|
||||
func dedupeUint(slice []uint) []uint {
|
||||
seen := make(map[uint]bool)
|
||||
result := make([]uint, 0, len(slice))
|
||||
for _, v := range slice {
|
||||
if !seen[v] {
|
||||
seen[v] = true
|
||||
result = append(result, v)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
Reference in new issue
Block a user