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:
@@ -5,8 +5,8 @@
|
||||
caption?, selectable?, actions? sr-only caption; toggle the check / kebab columns
|
||||
columns: { label, sortable?, sort?: "asc"|"desc", href?, className? }[]
|
||||
rows: { name?, cells: Cell[], actions?: Action[] }[]
|
||||
Cell ∈ string | { text, className? } | { user:{name,initials} } | { badge:{tone,label} } | { html, className? }
|
||||
user cells render as <th scope="row"> — they identify the row (the row header).
|
||||
Cell ∈ string | { text, className? } | { user:{name,initials} } | { rowHeader:{text,href?} } | { badge:{tone,label} } | { html, className? }
|
||||
user + rowHeader cells render as <th scope="row"> — they identify the row (the row header).
|
||||
Action = { label, icon?, href?, danger?, separatorBefore? }
|
||||
%><%
|
||||
const caption = locals.caption;
|
||||
@@ -48,6 +48,8 @@
|
||||
<td><%= cell %></td>
|
||||
<% } else if (cell.user) { -%>
|
||||
<th scope="row"><span class="cell-user"><span class="avatar" aria-hidden="true"><%= cell.user.initials %></span><span class="cell-strong"><%= cell.user.name %></span></span></th>
|
||||
<% } else if (cell.rowHeader) { -%>
|
||||
<th scope="row"><% if (cell.rowHeader.href) { %><a class="cell-strong" href="<%= cell.rowHeader.href %>"><%= cell.rowHeader.text %></a><% } else { %><span class="cell-strong"><%= cell.rowHeader.text %></span><% } %></th>
|
||||
<% } else if (cell.badge) { -%>
|
||||
<td><span class="badge <%= cell.badge.tone %>"><span class="dot"></span><%= cell.badge.label %></span></td>
|
||||
<% } else if (cell.html != null) { -%>
|
||||
|
||||
Reference in New Issue
Block a user