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