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]) => ( + + ))} + + +