重构页面目录与 RBAC:扁平配置入口并对齐管理/运营页映射。
下线运营总览幽灵页,补 ExpandKeysForScope / PlatformToMonitor,同步导航与角色配置 UI。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -29,6 +29,7 @@ public class RbacController : ControllerBase
|
||||
new("ops.car.gohome", "车辆 · 回库"),
|
||||
new("ops.car.resetSession", "车辆 · 重置会话"),
|
||||
new("ops.car.manualCharge", "车辆 · 手动充电"),
|
||||
new("ops.car.execute", "车辆 · 地图监控动作(按管理端配置)"),
|
||||
new("ops.task.pause", "任务 · 暂停"),
|
||||
new("ops.task.cancel", "任务 · 取消"),
|
||||
new("ops.task.reassign", "任务 · 改派"),
|
||||
@@ -61,18 +62,35 @@ public class RbacController : ControllerBase
|
||||
|
||||
/// <summary>权限「字典」:页面清单 + 可选操作码 + 可选控件 + scope 选项。前端角色编辑器据此渲染勾选项。</summary>
|
||||
[HttpGet("catalog")]
|
||||
public IActionResult Catalog() => Ok(new
|
||||
public IActionResult Catalog()
|
||||
{
|
||||
pages = PageCatalog.All,
|
||||
ops = KnownOps,
|
||||
widgets = KnownWidgets,
|
||||
scopes = new[]
|
||||
var actorPages = ActorPlatformPages();
|
||||
var grantablePlatform = PageCatalog.GrantablePlatformPages(actorPages).OrderBy(x => x).ToList();
|
||||
var grantableMonitor = PageCatalog.GrantableMonitorPages(actorPages).OrderBy(x => x).ToList();
|
||||
return Ok(new
|
||||
{
|
||||
new { value = PageCatalog.ScopePlatform, label = "管理端 (Platform)" },
|
||||
new { value = PageCatalog.ScopeMonitor, label = "运营端 (RCSMonitor)" },
|
||||
new { value = PageCatalog.Wildcard, label = "通用 (全部域)" },
|
||||
}
|
||||
});
|
||||
pages = PageCatalog.All,
|
||||
ops = KnownOps,
|
||||
widgets = KnownWidgets,
|
||||
scopes = new[]
|
||||
{
|
||||
new { value = PageCatalog.ScopePlatform, label = "管理权限 (Platform)" },
|
||||
new { value = PageCatalog.ScopeMonitor, label = "运营权限 (RCSMonitor)" },
|
||||
new { value = PageCatalog.Wildcard, label = "通用 (全部域)" },
|
||||
},
|
||||
// 当前登录管理员可勾选的页面(运营端由管理端已有页映射而来)。
|
||||
grantablePages = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
[PageCatalog.ScopePlatform] = grantablePlatform,
|
||||
[PageCatalog.ScopeMonitor] = grantableMonitor,
|
||||
[PageCatalog.Wildcard] = grantablePlatform.Concat(grantableMonitor).Distinct(StringComparer.OrdinalIgnoreCase).OrderBy(x => x).ToList(),
|
||||
},
|
||||
platformToMonitor = PageCatalog.PlatformToMonitor
|
||||
.Select(kv => new { platform = kv.Key, monitor = kv.Value })
|
||||
.ToList(),
|
||||
monitorOnlyPages = PageCatalog.MonitorOnlyPages,
|
||||
});
|
||||
}
|
||||
|
||||
// ───────────────────────── 角色 ─────────────────────────
|
||||
|
||||
@@ -80,10 +98,12 @@ public class RbacController : ControllerBase
|
||||
public IActionResult ListRoles() => Ok(_store.ListRoles());
|
||||
|
||||
[HttpPost("roles")]
|
||||
public IActionResult CreateRole([FromBody] SaveRoleRequest req) => Guard(() => Ok(_store.CreateRole(req)));
|
||||
public IActionResult CreateRole([FromBody] SaveRoleRequest req) => Guard(() =>
|
||||
Ok(_store.CreateRole(ClampPages(req))));
|
||||
|
||||
[HttpPut("roles/{id}")]
|
||||
public IActionResult UpdateRole(string id, [FromBody] SaveRoleRequest req) => Guard(() => Ok(_store.UpdateRole(id, req)));
|
||||
public IActionResult UpdateRole(string id, [FromBody] SaveRoleRequest req) => Guard(() =>
|
||||
Ok(_store.UpdateRole(id, ClampPages(req))));
|
||||
|
||||
[HttpDelete("roles/{id}")]
|
||||
public IActionResult DeleteRole(string id) => Guard(() =>
|
||||
@@ -136,4 +156,43 @@ public class RbacController : ControllerBase
|
||||
|
||||
private string? CurrentUserId() =>
|
||||
User.FindFirstValue("sub") ?? User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
|
||||
/// <summary>当前登录管理员在管理端的有效页面集合。</summary>
|
||||
private List<string> ActorPlatformPages()
|
||||
{
|
||||
var id = CurrentUserId();
|
||||
if (string.IsNullOrEmpty(id)) return new();
|
||||
var user = _store.FindUserById(id);
|
||||
if (user is null) return new();
|
||||
return _store.ComputeEffective(user, PageCatalog.ScopePlatform).Pages;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存角色时按「当前管理员可授页面」裁剪:运营端页只能选自管理端已有页的映射 + 运营专属页。
|
||||
/// 通配 <c>*</c> 仅当可授集合已覆盖该域全部页面时才保留。
|
||||
/// </summary>
|
||||
private SaveRoleRequest ClampPages(SaveRoleRequest req)
|
||||
{
|
||||
var scope = string.IsNullOrWhiteSpace(req.Scope) ? PageCatalog.ScopePlatform : req.Scope.Trim();
|
||||
var grantable = PageCatalog.GrantablePagesForRoleScope(scope, ActorPlatformPages());
|
||||
var pages = req.Pages ?? new List<string>();
|
||||
if (pages.Contains(PageCatalog.Wildcard, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
var scopeKeys = scope == PageCatalog.Wildcard
|
||||
? PageCatalog.All.Select(p => p.Key).ToList()
|
||||
: PageCatalog.KeysForScope(scope).ToList();
|
||||
if (scopeKeys.All(k => grantable.Contains(k)))
|
||||
return req with { Pages = new List<string> { PageCatalog.Wildcard } };
|
||||
return req with
|
||||
{
|
||||
Pages = scopeKeys.Where(k => grantable.Contains(k)).Distinct(StringComparer.OrdinalIgnoreCase).ToList()
|
||||
};
|
||||
}
|
||||
var clamped = pages
|
||||
.Select(PageCatalog.NormalizeKey)
|
||||
.Where(p => p == PageCatalog.Wildcard || (PageCatalog.IsValidKey(p) && grantable.Contains(p)))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
return req with { Pages = clamped };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user