Skip to content

Commit ecac9fa

Browse files
committed
[frontend] RBAC creation of roles screen
1 parent 9ed8937 commit ecac9fa

File tree

5 files changed

+168
-95
lines changed

5 files changed

+168
-95
lines changed

openbas-front/src/admin/components/settings/groups/GroupManageRoles.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { SecurityOutlined } from '@mui/icons-material';
22
import { Box, Button, Checkbox, Divider } from '@mui/material';
3+
import { useTheme } from '@mui/material/styles';
34
import { type FC, useEffect, useState } from 'react';
45

56
import { fetchRoles } from '../../../../actions/roles/roles-actions';
67
import Drawer from '../../../../components/common/Drawer';
78
import { useFormatter } from '../../../../components/i18n';
89
import type { Role } from '../../../../utils/api-types';
910
import { useAppDispatch } from '../../../../utils/hooks';
10-
import {useTheme} from "@mui/material/styles";
1111

1212
interface GroupManageRolesProps {
1313
initialState: string[];

openbas-front/src/admin/components/settings/roles/Roles.tsx

Lines changed: 11 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import { HelpOutlineOutlined, SecurityOutlined } from '@mui/icons-material';
2-
import { List, ListItem, ListItemIcon, ListItemText } from '@mui/material';
3-
import { useMemo, useState } from 'react';
1+
import { useState } from 'react';
42
import { useSearchParams } from 'react-router';
53
import { makeStyles } from 'tss-react/mui';
64

@@ -9,15 +7,13 @@ import Breadcrumbs from '../../../../components/Breadcrumbs';
97
import { initSorting } from '../../../../components/common/queryable/Page';
108
import PaginationComponentV2 from '../../../../components/common/queryable/pagination/PaginationComponentV2';
119
import { buildSearchPagination } from '../../../../components/common/queryable/QueryableUtils';
12-
import SortHeadersComponentV2 from '../../../../components/common/queryable/sort/SortHeadersComponentV2';
13-
import useBodyItemsStyles from '../../../../components/common/queryable/style/style';
1410
import { useQueryableWithLocalStorage } from '../../../../components/common/queryable/useQueryableWithLocalStorage';
1511
import { useFormatter } from '../../../../components/i18n';
16-
import PaginatedListLoader from '../../../../components/PaginatedListLoader';
1712
import type { Role, SearchPaginationInput } from '../../../../utils/api-types';
1813
import SecurityMenu from '../SecurityMenu';
1914
import CreateRole from './CreateRole';
2015
import RolePopover from './RolePopover';
16+
import RolesList from './RolesList';
2117

2218
const useStyles = makeStyles()(() => ({
2319
itemHead: { textTransform: 'uppercase' },
@@ -26,37 +22,9 @@ const useStyles = makeStyles()(() => ({
2622
bodyItems: { flexGrow: 1 },
2723
}));
2824

29-
const inlineStyles = {
30-
role_name: { width: '50%' },
31-
role_creation_date: { width: '25%' },
32-
role_modification_date: { width: '25%' },
33-
};
34-
3525
const Roles = () => {
3626
const { classes } = useStyles();
3727
const { t } = useFormatter();
38-
39-
const bodyItemsStyles = useBodyItemsStyles();
40-
41-
// Headers
42-
const headers = useMemo(() => [
43-
{
44-
field: 'role_name',
45-
label: t('Name'),
46-
isSortable: true,
47-
},
48-
{
49-
field: 'role_creation_date',
50-
label: t('Platform creation date'),
51-
isSortable: true,
52-
},
53-
{
54-
field: 'role_modification_date',
55-
label: t('Modification date'),
56-
isSortable: true,
57-
},
58-
], []);
59-
6028
// Query param
6129
const [searchParams] = useSearchParams();
6230
const [search] = searchParams.getAll('search');
@@ -69,6 +37,14 @@ const Roles = () => {
6937
textSearch: search,
7038
}));
7139

40+
const secondaryAction = (role: Role) => (
41+
<RolePopover
42+
onDelete={result => setRoles(roles.filter(r => (r.role_id !== result)))}
43+
onUpdate={result => setRoles(roles.map(r => (r.role_id !== result.role_id ? r : result)))}
44+
role={{ ...role }}
45+
/>
46+
);
47+
7248
const searchRolesToLoad = (input: SearchPaginationInput) => {
7349
setLoading(true);
7450
return searchRoles(input).finally(() => {
@@ -95,66 +71,7 @@ const Roles = () => {
9571
disablePagination
9672
disableFilters
9773
/>
98-
<List>
99-
<ListItem
100-
classes={{ root: classes.itemHead }}
101-
divider={false}
102-
style={{ paddingTop: 0 }}
103-
secondaryAction={<>&nbsp;</>}
104-
>
105-
<ListItemIcon />
106-
<ListItemText
107-
primary={(
108-
<SortHeadersComponentV2
109-
headers={headers}
110-
inlineStylesHeaders={inlineStyles}
111-
sortHelpers={queryableHelpers.sortHelpers}
112-
/>
113-
)}
114-
/>
115-
</ListItem>
116-
{loading
117-
? <PaginatedListLoader Icon={HelpOutlineOutlined} headers={headers} headerStyles={inlineStyles} />
118-
: (
119-
<div>
120-
{roles.map((role: Role) => (
121-
<ListItem
122-
key={role.role_id}
123-
classes={{ root: classes.item }}
124-
divider={true}
125-
>
126-
<ListItemIcon>
127-
<SecurityOutlined sx={{ color: '#96F426' }} />
128-
</ListItemIcon>
129-
<ListItemText primary={(
130-
<div style={bodyItemsStyles.bodyItems}>
131-
{Object.keys(inlineStyles).map(key => (
132-
<div
133-
key={key}
134-
style={{
135-
...bodyItemsStyles.bodyItem,
136-
...inlineStyles[key as keyof typeof inlineStyles],
137-
}}
138-
>
139-
{role[key as keyof typeof role]}
140-
</div>
141-
))}
142-
143-
</div>
144-
145-
)}
146-
/>
147-
<RolePopover
148-
onDelete={result => setRoles(roles.filter(r => (r.role_id !== result)))}
149-
onUpdate={result => setRoles(roles.map(r => (r.role_id !== result.role_id ? r : result)))}
150-
role={{ ...role }}
151-
/>
152-
</ListItem>
153-
),
154-
)}
155-
</div>
156-
)}
157-
</List>
74+
<RolesList roles={roles} queryableHelpers={queryableHelpers} loading={loading} secondaryAction={secondaryAction} />
15875
<CreateRole onCreate={(result: Role) => setRoles([...roles, result])} />
15976

16077
</div>
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { HelpOutlineOutlined, SecurityOutlined } from '@mui/icons-material';
2+
import { List, ListItem, ListItemIcon, ListItemText } from '@mui/material';
3+
import { type CSSProperties, type FC, type ReactNode } from 'react';
4+
import { useMemo } from 'react';
5+
import { makeStyles } from 'tss-react/mui';
6+
7+
import type { QueryableHelpers } from '../../../../components/common/queryable/QueryableHelpers';
8+
import SortHeadersComponentV2 from '../../../../components/common/queryable/sort/SortHeadersComponentV2';
9+
import useBodyItemsStyles from '../../../../components/common/queryable/style/style';
10+
import { useFormatter } from '../../../../components/i18n';
11+
import PaginatedListLoader from '../../../../components/PaginatedListLoader';
12+
import { type Role } from '../../../../utils/api-types';
13+
14+
interface RolesListProps {
15+
roles: Role[];
16+
queryableHelpers: QueryableHelpers;
17+
loading: boolean;
18+
secondaryAction?: (role: Role) => ReactNode;
19+
}
20+
21+
const useStyles = makeStyles()(() => ({
22+
itemHead: { textTransform: 'uppercase' },
23+
item: { height: 50 },
24+
container: { display: 'flex' },
25+
bodyItems: { flexGrow: 1 },
26+
}));
27+
28+
const inlineStyles: Record<string, CSSProperties> = {
29+
role_name: { width: '50%' },
30+
role_created_at: { width: '25%' },
31+
role_updated_at: { width: '25%' },
32+
};
33+
34+
const RolesList: FC<RolesListProps> = ({
35+
roles,
36+
queryableHelpers,
37+
loading,
38+
secondaryAction,
39+
}) => {
40+
const { t, nsdt } = useFormatter();
41+
const { classes } = useStyles();
42+
const bodyItemsStyles = useBodyItemsStyles();
43+
44+
const headers = useMemo(() => [
45+
{
46+
field: 'role_name',
47+
label: t('Name'),
48+
isSortable: true,
49+
value: (role: Role) => role.role_name,
50+
},
51+
{
52+
field: 'role_created_at',
53+
label: t('Platform creation date'),
54+
isSortable: true,
55+
value: (role: Role) => {
56+
if (!role.role_created_at) {
57+
return '-';
58+
}
59+
return <>{(nsdt(role.role_created_at))}</>;
60+
},
61+
},
62+
{
63+
field: 'role_updated_at',
64+
label: t('Modification date'),
65+
isSortable: true,
66+
value: (role: Role) => {
67+
if (!role.role_updated_at) {
68+
return '-';
69+
}
70+
return <>{(nsdt(role.role_updated_at))}</>;
71+
},
72+
},
73+
], []);
74+
return (
75+
<List>
76+
<ListItem
77+
classes={{ root: classes.itemHead }}
78+
divider={false}
79+
style={{ paddingTop: 0 }}
80+
secondaryAction={<>&nbsp;</>}
81+
>
82+
<ListItemIcon />
83+
<ListItemText
84+
primary={(
85+
<SortHeadersComponentV2
86+
headers={headers}
87+
inlineStylesHeaders={inlineStyles}
88+
sortHelpers={queryableHelpers.sortHelpers}
89+
/>
90+
)}
91+
/>
92+
</ListItem>
93+
{loading
94+
? <PaginatedListLoader Icon={HelpOutlineOutlined} headers={headers} headerStyles={inlineStyles} />
95+
: (
96+
<div>
97+
{roles.map((role: Role) => (
98+
<ListItem
99+
key={role.role_id}
100+
classes={{ root: classes.item }}
101+
divider
102+
secondaryAction={secondaryAction && secondaryAction(role)}
103+
104+
>
105+
<ListItemIcon>
106+
<SecurityOutlined sx={{ color: '#96F426' }} />
107+
</ListItemIcon>
108+
<ListItemText primary={(
109+
<div style={bodyItemsStyles.bodyItems}>
110+
{headers.map(header => (
111+
<div
112+
key={header.field}
113+
style={{
114+
...bodyItemsStyles.bodyItem,
115+
...inlineStyles[header.field],
116+
}}
117+
>
118+
{header.value && header.value(role)}
119+
</div>
120+
))}
121+
122+
</div>
123+
124+
)}
125+
/>
126+
</ListItem>
127+
),
128+
)}
129+
</div>
130+
)}
131+
</List>
132+
);
133+
};
134+
135+
export default RolesList;

openbas-front/src/utils/api-types.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5805,6 +5805,8 @@ export interface WidgetLayout {
58055805
export interface Role {
58065806
role_id: string;
58075807
role_name: string;
5808+
role_created_at: string;
5809+
role_updated_at: string;
58085810
role_description?: string;
58095811
role_capabilities: string[];
58105812
}

openbas-model/src/main/java/io/openbas/database/model/Role.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
package io.openbas.database.model;
22

3+
import static java.time.Instant.now;
4+
35
import com.fasterxml.jackson.annotation.JsonProperty;
46
import io.openbas.annotation.Queryable;
57
import io.openbas.database.audit.ModelBaseListener;
8+
import io.swagger.v3.oas.annotations.media.Schema;
69
import jakarta.persistence.*;
710
import jakarta.validation.constraints.NotBlank;
11+
import jakarta.validation.constraints.NotNull;
12+
import java.time.Instant;
813
import java.util.HashSet;
914
import java.util.Set;
1015
import lombok.Data;
@@ -36,4 +41,18 @@ public class Role implements Base {
3641
@Enumerated(EnumType.STRING)
3742
@Column(name = "capability")
3843
private Set<Capability> capabilities = new HashSet<>();
44+
45+
@Queryable(sortable = true)
46+
@Column(name = "role_created_at")
47+
@JsonProperty("role_created_at")
48+
@NotNull
49+
@Schema(description = "Creation date of the role", accessMode = Schema.AccessMode.READ_ONLY)
50+
private Instant createdAt = now();
51+
52+
@Queryable(sortable = true)
53+
@Column(name = "role_updated_at")
54+
@JsonProperty("role_updated_at")
55+
@NotNull
56+
@Schema(description = "Update date of the role", accessMode = Schema.AccessMode.READ_ONLY)
57+
private Instant updatedAt = now();
3958
}

0 commit comments

Comments
 (0)