Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commita23996b

Browse files
committed
feat: initial commit custom roles
1 parent7a4737c commita23996b

File tree

4 files changed

+337
-0
lines changed

4 files changed

+337
-0
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
importGroupAddfrom"@mui/icons-material/GroupAddOutlined";
2+
importButtonfrom"@mui/material/Button";
3+
import{typeFC,useEffect}from"react";
4+
import{Helmet}from"react-helmet-async";
5+
import{useQuery}from"react-query";
6+
import{
7+
Navigate,
8+
LinkasRouterLink,
9+
useLocation,
10+
useParams,
11+
}from"react-router-dom";
12+
import{getErrorMessage}from"api/errors";
13+
import{organizationRoles}from"api/queries/roles";
14+
importtype{Organization}from"api/typesGenerated";
15+
import{displayError}from"components/GlobalSnackbar/utils";
16+
import{PageHeader,PageHeaderTitle}from"components/PageHeader/PageHeader";
17+
import{useAuthenticated}from"contexts/auth/RequireAuth";
18+
import{useDashboard}from"modules/dashboard/useDashboard";
19+
import{useFeatureVisibility}from"modules/dashboard/useFeatureVisibility";
20+
import{pageTitle}from"utils/page";
21+
importCustomRolesPageViewfrom"./CustomRolesPageView";
22+
23+
exportconstCustomRolesPage:FC=()=>{
24+
const{ permissions}=useAuthenticated();
25+
const{createGroup:canCreateGroup}=permissions;
26+
const{
27+
multiple_organizations:organizationsEnabled,
28+
template_rbac:isTemplateRBACEnabled,
29+
}=useFeatureVisibility();
30+
const{ experiments}=useDashboard();
31+
constlocation=useLocation();
32+
const{ organization="default"}=useParams()as{organization:string};
33+
constorganizationRolesQuery=useQuery(organizationRoles(organization));
34+
35+
useEffect(()=>{
36+
if(organizationRolesQuery.error){
37+
displayError(
38+
getErrorMessage(
39+
organizationRolesQuery.error,
40+
"Error loading custom roles.",
41+
),
42+
);
43+
}
44+
},[organizationRolesQuery.error]);
45+
46+
// if (
47+
// organizationsEnabled &&
48+
// experiments.includes("multi-organization") &&
49+
// location.pathname === "/deployment/groups"
50+
// ) {
51+
// const defaultName =
52+
// getOrganizationNameByDefault(organizations) ?? "default";
53+
// return <Navigate to={`/organizations/${defaultName}/groups`} replace />;
54+
// }
55+
56+
return(
57+
<>
58+
<Helmet>
59+
<title>{pageTitle("Groups")}</title>
60+
</Helmet>
61+
62+
<PageHeader
63+
actions={
64+
<>
65+
{canCreateGroup&&isTemplateRBACEnabled&&(
66+
<Button
67+
component={RouterLink}
68+
startIcon={<GroupAdd/>}
69+
to="create"
70+
>
71+
Create custom role
72+
</Button>
73+
)}
74+
</>
75+
}
76+
>
77+
<PageHeaderTitle>Custom Roles</PageHeaderTitle>
78+
</PageHeader>
79+
80+
<CustomRolesPageView
81+
roles={organizationRolesQuery.data}
82+
canCreateGroup={canCreateGroup}
83+
isTemplateRBACEnabled={isTemplateRBACEnabled}
84+
/>
85+
</>
86+
);
87+
};
88+
89+
exportdefaultCustomRolesPage;
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
import{css}from"@emotion/css";
2+
importtype{Interpolation,Theme}from"@emotion/react";
3+
importKeyboardArrowRightfrom"@mui/icons-material/KeyboardArrowRight";
4+
importPersonAddfrom"@mui/icons-material/PersonAdd";
5+
import{LoadingButton}from"@mui/lab";
6+
import{Table,TableBody,TableContainer,TextField}from"@mui/material";
7+
importAutocomplete,{createFilterOptions}from"@mui/material/Autocomplete";
8+
importAvatarGroupfrom"@mui/material/AvatarGroup";
9+
importSkeletonfrom"@mui/material/Skeleton";
10+
importTableCellfrom"@mui/material/TableCell";
11+
importTableRowfrom"@mui/material/TableRow";
12+
import{useState,typeFC}from"react";
13+
import{LinkasRouterLink,useNavigate}from"react-router-dom";
14+
import{RBACResourceActions}from"api/rbacresources_gen";
15+
importtype{Group,Role}from"api/typesGenerated";
16+
import{AvatarData}from"components/AvatarData/AvatarData";
17+
import{AvatarDataSkeleton}from"components/AvatarData/AvatarDataSkeleton";
18+
import{ChooseOne,Cond}from"components/Conditionals/ChooseOne";
19+
import{EmptyState}from"components/EmptyState/EmptyState";
20+
import{GroupAvatar}from"components/GroupAvatar/GroupAvatar";
21+
import{Paywall}from"components/Paywall/Paywall";
22+
import{Stack}from"components/Stack/Stack";
23+
import{
24+
TableLoader,
25+
TableLoaderSkeleton,
26+
TableRowSkeleton,
27+
}from"components/TableLoader/TableLoader";
28+
import{UserAvatar}from"components/UserAvatar/UserAvatar";
29+
import{permissionsToCheck}from"contexts/auth/permissions";
30+
import{useClickableTableRow}from"hooks";
31+
import{docs}from"utils/docs";
32+
33+
exporttypeCustomRolesPageViewProps={
34+
roles:Role[]|undefined;
35+
canCreateGroup:boolean;
36+
isTemplateRBACEnabled:boolean;
37+
};
38+
39+
constfilter=createFilterOptions<Role>();
40+
41+
exportconstCustomRolesPageView:FC<CustomRolesPageViewProps>=({
42+
roles,
43+
canCreateGroup,
44+
isTemplateRBACEnabled,
45+
})=>{
46+
constisLoading=Boolean(roles===undefined);
47+
constisEmpty=Boolean(roles&&roles.length===0);
48+
const[selectedRole,setSelectedRole]=useState<Role|null>(null);
49+
console.log({ selectedRole});
50+
51+
return(
52+
<>
53+
<ChooseOne>
54+
<Condcondition={!isTemplateRBACEnabled}>
55+
<Paywall
56+
message="Custom Roles"
57+
description="Organize users into groups with restricted access to templates. You need an Enterprise license to use this feature."
58+
documentationLink={docs("/admin/groups")}
59+
/>
60+
</Cond>
61+
<Cond>
62+
<Stack
63+
direction="row"
64+
alignItems="center"
65+
spacing={1}
66+
css={styles.rolesDropdown}
67+
>
68+
<Autocomplete
69+
value={selectedRole}
70+
onChange={(_,newValue)=>{
71+
console.log("onChange: ",newValue);
72+
if(typeofnewValue==="string"){
73+
console.log("0");
74+
setSelectedRole({
75+
name:newValue,
76+
display_name:newValue,
77+
site_permissions:[],
78+
organization_permissions:[],
79+
user_permissions:[],
80+
});
81+
}elseif(newValue&&newValue.display_name){
82+
console.log("1");
83+
// Create a new value from the user input
84+
// setSelectedRole({ ...newValue, display_name: newValue.name });
85+
setSelectedRole(newValue);
86+
}else{
87+
console.log("2");
88+
setSelectedRole(newValue);
89+
}
90+
}}
91+
isOptionEqualToValue={(option:Role,value:Role)=>
92+
option.name===value.name
93+
}
94+
filterOptions={(options,params)=>{
95+
constfiltered=filter(options,params);
96+
97+
const{ inputValue}=params;
98+
// Suggest the creation of a new value
99+
constisExisting=options.some(
100+
(option)=>inputValue===option.display_name,
101+
);
102+
if(inputValue!==""&&!isExisting){
103+
filtered.push({
104+
name:inputValue,
105+
display_name:`Add${inputValue}`,
106+
site_permissions:[],
107+
organization_permissions:[],
108+
user_permissions:[],
109+
});
110+
}
111+
112+
returnfiltered;
113+
}}
114+
selectOnFocus
115+
clearOnBlur
116+
handleHomeEndKeys
117+
id="custom-role"
118+
options={roles||[]}
119+
getOptionLabel={(option)=>{
120+
// console.log("getOptionLabel: ", option);
121+
// Value selected with enter, right from the input
122+
if(typeofoption==="string"){
123+
returnoption;
124+
}
125+
// Add "xxx" option created dynamically
126+
if(option.name){
127+
returnoption.name;
128+
}
129+
// Regular option
130+
returnoption.display_name;
131+
}}
132+
renderOption={(props,option)=>{
133+
const{ key, ...optionProps}=props;
134+
return(
135+
<likey={key}{...optionProps}>
136+
{option.display_name}
137+
</li>
138+
);
139+
}}
140+
sx={{width:300}}
141+
renderInput={(params)=>(
142+
<TextField
143+
{...params}
144+
label="Display Name"
145+
InputLabelProps={{
146+
shrink:true,
147+
}}
148+
/>
149+
)}
150+
/>
151+
152+
<LoadingButton
153+
loadingPosition="start"
154+
// disabled={!selectedUser}
155+
type="submit"
156+
startIcon={<PersonAdd/>}
157+
loading={isLoading}
158+
>
159+
Save Custom Role
160+
</LoadingButton>
161+
</Stack>
162+
163+
<TableContainer>
164+
<Table>
165+
<TableBody>
166+
<ChooseOne>
167+
<Condcondition={isLoading}>
168+
<TableLoader/>
169+
</Cond>
170+
171+
<Condcondition={isEmpty}>
172+
<TableRow>
173+
<TableCellcolSpan={999}>
174+
<EmptyState
175+
message="No custom roles yet"
176+
description={
177+
canCreateGroup
178+
?"Create your first custom role"
179+
:"You don't have permission to create a custom role"
180+
}
181+
/>
182+
</TableCell>
183+
</TableRow>
184+
</Cond>
185+
186+
<Cond>
187+
{Object.entries(RBACResourceActions).map(([key,value])=>{
188+
return(
189+
<TableRowkey={key}>
190+
<TableCell>
191+
<likey={key}css={styles.checkBoxes}>
192+
<inputtype="checkbox"/>{key}
193+
<ulcss={styles.checkBoxes}>
194+
{Object.entries(value).map(([key,value])=>{
195+
return(
196+
<likey={key}>
197+
<spancss={styles.actionText}>
198+
<inputtype="checkbox"/>{key}
199+
</span>{" "}
200+
-{" "}
201+
<spancss={styles.actionDescription}>
202+
{value}
203+
</span>
204+
</li>
205+
);
206+
})}
207+
</ul>
208+
</li>
209+
</TableCell>
210+
</TableRow>
211+
);
212+
})}
213+
</Cond>
214+
</ChooseOne>
215+
</TableBody>
216+
</Table>
217+
</TableContainer>
218+
</Cond>
219+
</ChooseOne>
220+
</>
221+
);
222+
};
223+
224+
conststyles={
225+
rolesDropdown:{
226+
marginBottom:20,
227+
},
228+
checkBoxes:{
229+
margin:0,
230+
listStyleType:"none",
231+
},
232+
actionText:(theme)=>({
233+
color:theme.palette.text.primary,
234+
}),
235+
actionDescription:(theme)=>({
236+
color:theme.palette.text.secondary,
237+
}),
238+
}satisfiesRecord<string,Interpolation<Theme>>;
239+
240+
exportdefaultCustomRolesPageView;

‎site/src/pages/ManagementSettingsPage/Sidebar.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,9 @@ export const OrganizationSettingsNavigation: FC<
155155
<SidebarNavSubItemhref={urlForSubpage(organization.name,"groups")}>
156156
Groups
157157
</SidebarNavSubItem>
158+
<SidebarNavSubItemhref={urlForSubpage(organization.name,"roles")}>
159+
Roles
160+
</SidebarNavSubItem>
158161
{/* For now redirect to the site-wide audit page with the organization
159162
pre-filled into the filter. Based on user feedback we might want
160163
to serve a copy of the audit page or even delete this link. */}

‎site/src/router.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,10 @@ const OrganizationGroupSettingsPage = lazy(
242242
constOrganizationMembersPage=lazy(
243243
()=>import("./pages/ManagementSettingsPage/OrganizationMembersPage"),
244244
);
245+
constOrganizationCustomRolesPage=lazy(
246+
()=>
247+
import("./pages/ManagementSettingsPage/CustomRolesPage/CustomRolesPage"),
248+
);
245249
constTemplateEmbedPage=lazy(
246250
()=>import("./pages/TemplatePage/TemplateEmbedPage/TemplateEmbedPage"),
247251
);
@@ -372,6 +376,7 @@ export const router = createBrowserRouter(
372376
<Routeindexelement={<OrganizationSettingsPage/>}/>
373377
<Routepath="members"element={<OrganizationMembersPage/>}/>
374378
{groupsRouter()}
379+
<Routepath="roles"element={<OrganizationCustomRolesPage/>}/>
375380
<Routepath="auditing"element={<></>}/>
376381
</Route>
377382
</Route>

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp