Built-in Groups admin screen (todo §5); /admin/groups list (search/sort/paginate) + create/delete + membership (add/remove users & nested groups), writing only to Keto — gated admin-only + CSRF-guarded like Users (Kratos read only to label pickers). A group = Keto subject set Group:<name>#members, exists while it has ≥1 member: create writes the first-member tuple, delete removes all by partial-filter. Extracted shared admin-nav.ts (Dashboard·Users·Groups); new generic rowHeader <th scope=row> data-table cell. Stability-reviewer run as a local PR: symmetric subject UUID-validation, duplicate-name rejection, malformed-%→404. 228→237 units + typecheck green; core Keto interactions boot-verified live
This commit is contained in:
23
src/admin-nav.ts
Normal file
23
src/admin-nav.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
// Shared sidebar nav for the built-in admin screens (todo §5). Both the Users and Groups
|
||||
// screens render the same admin section (Dashboard · Users · Groups), with `current` set on the
|
||||
// active item. Extracted so the two screens can't drift. The global config-driven menu wiring
|
||||
// (an admin section gated per user) is the separate §5 menu item; this is the local in-screen nav.
|
||||
|
||||
import { type MenuConfig } from "./menu-config.ts";
|
||||
import { composeNav, type NavNode } from "./nav.ts";
|
||||
|
||||
export const ADMIN_PERMISSION = "admin"; // role token gating every admin screen
|
||||
export const ADMIN_USERS_BASE = "/admin/users";
|
||||
export const ADMIN_GROUPS_BASE = "/admin/groups";
|
||||
|
||||
type AdminScreen = "dashboard" | "groups" | "users";
|
||||
|
||||
export function adminNav(roles: string[], menu: MenuConfig, current: AdminScreen): NavNode[] {
|
||||
const gated = (id: AdminScreen, href: string, icon: string, label: string): NavNode =>
|
||||
({ ...(current === id ? { current: true } : {}), href, icon, id, label, permission: ADMIN_PERMISSION });
|
||||
return composeNav([[
|
||||
{ href: "/", icon: "i-grid", id: "dashboard", label: "Dashboard" },
|
||||
gated("users", ADMIN_USERS_BASE, "i-users", "Users"),
|
||||
gated("groups", ADMIN_GROUPS_BASE, "i-layers", "Groups"),
|
||||
]], menu.override, roles);
|
||||
}
|
||||
Reference in New Issue
Block a user