Merge pull request #1044 from bbassie/feature/role-permission-sections

Add a permission group list to the role page
This commit is contained in:
Cooldude2606 authored and GitHub committed 2026-10-05 01:30:20 +01:00
commit d89a6e33f3
1 file changed
+67 -15
+67 -15
View File
@@ -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, Grid, 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 (
<div style={{ border: "1px solid #424242", padding: 12, borderRadius: 6 }}>
<div id={`permission-group-${groupName}`} style={{
border: "1px solid #424242", padding: 12, borderRadius: 6, scrollMarginTop: 80,
}}>
<Space style={{ width: "100%", justifyContent: "space-between" }}>
<strong>{formatGroupTitle(groupName)}</strong>
<Space>
@@ -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 (
<div
role="button"
onClick={scrollToGroup}
style={{ display: "flex", alignItems: "center", gap: 8, padding: "4px 8px", cursor: "pointer" }}
>
<span style={{
width: 8,
height: 8,
borderRadius: "50%",
flexShrink: 0,
background: granted ? "#1677ff" : "#595959",
}} />
{formatGroupTitle(groupName)}
</div>
);
}
/** The role page view */
export default function RoleViewPage() {
const params = useParams();
@@ -149,6 +175,9 @@ export default function RoleViewPage() {
const [permissionState, setPermissionState] = useState<Record<string, boolean>>({});
const [baselineState, setBaselineState] = useState<Record<string, boolean>>({});
const [search, setSearch] = useState("");
// Hidden below lg, where the site sider leaves too little room; undefined until measured
const screens = Grid.useBreakpoint();
const showGroupList = screens.lg !== false;
const allPermissions = useMemo(() => [...lib.permissions.values()], []);
const canUpdate = Boolean(account.hasPermission("core.role.update"));
@@ -209,6 +238,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 +334,39 @@ export default function RoleViewPage() {
<Input.Search placeholder="Search permissions" allowClear
onChange={e => setSearch(e.target.value)} style={{ marginBottom: 16 }} />
<Space direction="vertical" style={{ width: "100%" }}>
{sortedGroups.map(([groupName, permissions]) => (
<PermissionGroup
key={groupName}
groupName={groupName}
permissions={permissions}
permissionState={permissionState}
baselineState={baselineState}
setPermissionState={setPermissionState}
canUpdate={canUpdate}
/>
))}
</Space>
<Row gutter={16} wrap={false}>
{showGroupList && <Col flex="200px">
{/* Clear of the fixed page header */}
<div style={{
position: "sticky", top: 80, maxHeight: "calc(100vh - 96px)", overflowY: "auto",
// Keep the last groups reachable above the unsaved changes bar
paddingBottom: edited ? 76 : 0,
}}>
{sortedGroups.map(([groupName]) => (
<PermissionGroupNav
key={groupName}
groupName={groupName}
granted={grantedGroups.has(groupName)}
/>
))}
</div>
</Col>}
<Col flex="auto" style={{ minWidth: 0 }}>
<Space direction="vertical" style={{ width: "100%" }}>
{sortedGroups.map(([groupName, permissions]) => (
<PermissionGroup
key={groupName}
groupName={groupName}
permissions={permissions}
permissionState={permissionState}
baselineState={baselineState}
setPermissionState={setPermissionState}
canUpdate={canUpdate}
/>
))}
</Space>
</Col>
</Row>
<PluginExtra component="RoleViewPage" role={role} search={search}/>
</PageLayout>