From 2b272d230c17fd9a4c5dcfdaf6544a699dc6397d Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Thu, 1 Oct 2026 19:56:33 +0000 Subject: [PATCH] Add a permission group list to the role page A sticky list of permission groups sits left of the permissions. Clicking an entry scrolls to that group, and its dot is blue when any permission in the group is granted, grey otherwise. Same layout as the ExpCluster scenario features page. Co-Authored-By: Claude Opus 5.5 --- .../web_ui/src/components/RoleViewPage.tsx | 75 +++++++++++++++---- 1 file changed, 60 insertions(+), 15 deletions(-) diff --git a/packages/web_ui/src/components/RoleViewPage.tsx b/packages/web_ui/src/components/RoleViewPage.tsx index ddd18535..ce6fa2f1 100644 --- a/packages/web_ui/src/components/RoleViewPage.tsx +++ b/packages/web_ui/src/components/RoleViewPage.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useContext, useState, useMemo } from "react"; import { useNavigate, useParams } from "react-router-dom"; -import { Button, Checkbox, Input, Popconfirm, Space, Spin, Tooltip } from "antd"; +import { Button, Checkbox, Col, Input, Popconfirm, Row, Space, Spin, Tooltip } from "antd"; import { ExclamationCircleOutlined, StarOutlined, DeleteOutlined} from "@ant-design/icons"; import * as lib from "@clusterio/lib"; @@ -109,7 +109,9 @@ function PermissionGroup({ } return ( -
+
{formatGroupTitle(groupName)} @@ -134,6 +136,30 @@ function PermissionGroup({ ); } +/** An entry in the group list; the dot is blue when any permission in the group is granted */ +function PermissionGroupNav({ groupName, granted }: { groupName: string; granted: boolean }) { + function scrollToGroup() { + document.getElementById(`permission-group-${groupName}`)?.scrollIntoView({ behavior: "smooth" }); + } + + return ( +
+ + {formatGroupTitle(groupName)} +
+ ); +} + /** The role page view */ export default function RoleViewPage() { const params = useParams(); @@ -209,6 +235,9 @@ export default function RoleViewPage() { permissions.sort((a, b) => a.title.localeCompare(b.title)); } + // Groups with at least one granted permission, including those hidden by the search + const grantedGroups = new Set(allPermissions.filter(p => permissionState[p.name]).map(p => getGroupName(p.name))); + // Check if any changes have been made to the permissions or role metadata const permissionChanged = Object.keys(permissionState).some(k => permissionState[k] !== baselineState[k]); const metaChanged = name !== role.name || description !== role.description; @@ -302,19 +331,35 @@ export default function RoleViewPage() { setSearch(e.target.value)} style={{ marginBottom: 16 }} /> - - {sortedGroups.map(([groupName, permissions]) => ( - - ))} - + + + {/* Clear of the fixed page header */} +
+ {sortedGroups.map(([groupName]) => ( + + ))} +
+ + + + {sortedGroups.map(([groupName, permissions]) => ( + + ))} + + +