Skip to content
This repository was archived by the owner on May 13, 2025. It is now read-only.

Commit 40755d9

Browse files
[feat] add role based feature view for users (#118)
1 parent 5301ce3 commit 40755d9

File tree

12 files changed

+316
-157
lines changed

12 files changed

+316
-157
lines changed

src/@types/parseable/api/query.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export type LogsQuery = {
22
streamName: string;
33
startTime: Date;
44
endTime: Date;
5+
access:string[]|null;
56
};
67

78
export enum SortOrder {

src/components/Navbar/index.tsx

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,14 @@ import { LOGIN_ROUTE, USERS_MANAGEMENT_ROUTE } from '@/constants/routes';
2828
import { useDeleteLogStream } from '@/hooks/useDeleteLogStream';
2929
import InfoModal from './infoModal';
3030
import { useGetUserRole } from '@/hooks/useGetUserRoles';
31+
import { getStreamsSepcificAccess, getUserSepcificStreams } from './rolesHandler';
32+
import { LogStreamData } from '@/@types/parseable/api/stream';
3133

3234
const links = [
33-
{ icon: IconZoomCode, label: 'Query', pathname: '/query' },
34-
{ icon: IconTableShortcut, label: 'Logs', pathname: '/logs' },
35-
{ icon: IconReportAnalytics, label: 'Stats', pathname: '/stats' },
36-
{ icon: IconSettings, label: 'Config', pathname: '/config' },
35+
{ icon: IconZoomCode, label: 'Query', pathname: '/query', requiredAccess: ["Query","GetSchema"] },
36+
{ icon: IconTableShortcut, label: 'Logs', pathname: '/logs', requiredAccess: ["Query","GetSchema"]},
37+
{ icon: IconReportAnalytics, label: 'Stats', pathname: '/stats', requiredAccess: ["GetStats"]},
38+
{ icon: IconSettings, label: 'Config', pathname: '/config', requiredAccess: ["PutAlert"]},
3739
];
3840

3941
type NavbarProps = Omit<MantineNavbarProps, 'children'>;
@@ -55,7 +57,8 @@ const Navbar: FC<NavbarProps> = (props) => {
5557
const [searchValue, setSearchValue] = useMountedState('');
5658
const [currentPage, setCurrentPage] = useMountedState('/query');
5759
const [deleteStream, setDeleteStream] = useMountedState('');
58-
const [isAdmin, setIsAdmin] = useMountedState(false);
60+
const [userSepecficStreams, setUserSepecficStreams] =useMountedState<LogStreamData | null>(null );
61+
const [userSepecficAccess, setUserSepecficAccess] =useMountedState<string[] | null>(null);
5962

6063
const [disableLink, setDisableLink] = useMountedState(false);
6164
const [isSubNavbarOpen, setIsSubNavbarOpen] = useMountedState(false);
@@ -64,7 +67,6 @@ const Navbar: FC<NavbarProps> = (props) => {
6467

6568
const { data: streams, error, getData, resetData: resetStreamArray } = useGetLogStreamList();
6669
const { data: deleteData, deleteLogStreamFun } = useDeleteLogStream();
67-
6870
useEffect(() => {
6971
const listener = subNavbarTogle.subscribe(setIsSubNavbarOpen);
7072
return () => {
@@ -91,16 +93,16 @@ const Navbar: FC<NavbarProps> = (props) => {
9193
if (location.pathname.split('/')[2]) {
9294
setCurrentPage(`/${location.pathname.split('/')[2]}`);
9395
}
94-
if (streams && streams.length === 0) {
96+
if (userSepecficStreams && userSepecficStreams.length === 0) {
9597
setActiveStream('');
9698
setSearchValue('');
9799
setDisableLink(true);
98100
navigate(`/`);
99101
} else if (streamName) {
100-
if (streamName === deleteStream && streams) {
102+
if (streamName === deleteStream && userSepecficStreams) {
101103
setDeleteStream('');
102-
handleChange(streams[0].name);
103-
} else if (streams && !streams.find((stream) => stream.name === streamName)) {
104+
handleChange(userSepecficStreams[0].name);
105+
} else if (userSepecficStreams && !userSepecficStreams.find((stream : any) => stream.name === streamName)) {
104106
notifications.show({
105107
id: 'error-data',
106108
color: 'red',
@@ -109,18 +111,18 @@ const Navbar: FC<NavbarProps> = (props) => {
109111
icon: <IconFileAlert size="1rem" />,
110112
autoClose: 5000,
111113
});
112-
handleChange(streams[0].name);
113-
} else if (streams?.find((stream) => stream.name === streamName)) {
114+
handleChange(userSepecficStreams[0].name);
115+
} else if (userSepecficStreams?.find((stream:any) => stream.name === streamName)) {
114116
handleChange(streamName);
115117
}
116-
} else if (streams && Boolean(streams.length)) {
118+
} else if (userSepecficStreams && Boolean(userSepecficStreams.length)) {
117119
if (location.pathname === USERS_MANAGEMENT_ROUTE) {
118-
handleChangeWithoutRiderection(streams[0].name, location.pathname);
120+
handleChangeWithoutRiderection(userSepecficStreams[0].name, location.pathname);
119121
} else {
120-
handleChange(streams[0].name);
122+
handleChange(userSepecficStreams[0].name);
121123
}
122124
}
123-
}, [streams]);
125+
}, [userSepecficStreams]);
124126

125127
const handleChange = (value: string,page: string = currentPage ) => {
126128
handleChangeWithoutRiderection(value,page);
@@ -131,11 +133,12 @@ const Navbar: FC<NavbarProps> = (props) => {
131133
setSearchValue(value);
132134
setCurrentPage(page);
133135
const now = dayjs();
134-
136+
setUserSepecficAccess(getStreamsSepcificAccess(roles, value));
135137
subLogQuery.set((state) => {
136138
state.streamName = value || '';
137139
state.startTime = now.subtract(DEFAULT_FIXED_DURATIONS.milliseconds, 'milliseconds').toDate();
138140
state.endTime = now.toDate();
141+
state.access= getStreamsSepcificAccess(roles, value);
139142
});
140143
subLogSelectedTimeRange.set((state) => {
141144
state.state = 'fixed';
@@ -167,7 +170,7 @@ const Navbar: FC<NavbarProps> = (props) => {
167170
}, [deleteData]);
168171

169172
//isAdmin
170-
const { data: adminData, getRoles, resetData } = useGetUserRole();
173+
const { data: roles, getRoles, resetData } = useGetUserRole();
171174
useEffect(() => {
172175
if (username) {
173176
getRoles(username);
@@ -178,10 +181,13 @@ const Navbar: FC<NavbarProps> = (props) => {
178181
}, [username]);
179182

180183
useEffect(() => {
181-
if (adminData) {
182-
setIsAdmin(true);
184+
if(streams && streams.length > 0 && roles && roles.length > 0){
185+
const userStreams = getUserSepcificStreams(roles,streams as any);
186+
setUserSepecficStreams(userStreams as any);
183187
}
184-
}, [adminData]);
188+
189+
}, [roles, streams]);
190+
185191

186192
const { classes } = useNavbarStyles();
187193
const {
@@ -210,12 +216,15 @@ const Navbar: FC<NavbarProps> = (props) => {
210216
onSearchChange={(value) => setSearchValue(value)}
211217
onDropdownClose={() => setSearchValue(activeStream)}
212218
onDropdownOpen={() => setSearchValue('')}
213-
data={streams?.map((stream) => ({ value: stream.name, label: stream.name })) ?? []}
219+
data={userSepecficStreams?.map((stream:any) => ({ value: stream.name, label: stream.name })) ?? []}
214220
searchable
215221
required
216222
className={selectStreambtn}
217223
/>
218224
{links.map((link) => {
225+
if (link.requiredAccess && !userSepecficAccess?.some((access:string) => link.requiredAccess.includes(access))) {
226+
return null;
227+
}
219228
return (
220229
<NavLink
221230
label={link.label}
@@ -230,14 +239,17 @@ const Navbar: FC<NavbarProps> = (props) => {
230239
/>
231240
);
232241
})}
233-
<NavLink
242+
{ !userSepecficAccess?.some((access:string) => ["DeleteStream"].includes(access)) ? null :
243+
<NavLink
234244
label={'Delete'}
235245
icon={<IconTrash size="1.3rem" stroke={1.2} />}
236246
sx={{ paddingLeft: 53 }}
237247
onClick={openDelete}
238248
className={linkBtn}
239249
disabled={disableLink}
240250
/>
251+
}
252+
241253
{error && <div>{error}</div>}
242254
{error && (
243255
<NavLink
@@ -248,7 +260,7 @@ const Navbar: FC<NavbarProps> = (props) => {
248260
sx={{ paddingLeft: 0 }}
249261
/>
250262
)}
251-
{isAdmin && (
263+
{ !userSepecficAccess?.some((access:string) => ["ListUser"].includes(access)) ? null :
252264
<NavLink
253265
pt={24}
254266
className={(currentPage === USERS_MANAGEMENT_ROUTE && userManagementBtnActive) || userManagementBtn}
@@ -259,7 +271,7 @@ const Navbar: FC<NavbarProps> = (props) => {
259271
setCurrentPage(USERS_MANAGEMENT_ROUTE);
260272
}}
261273
/>
262-
)}
274+
}
263275
</MantineNavbar.Section>
264276
<MantineNavbar.Section className={lowerContainer}>
265277
<NavLink label={username} icon={<IconUser size="1.3rem" stroke={1.3} />} className={userBtn} component="a" />

src/components/Navbar/rolesHandler.ts

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
2+
// [
3+
// {
4+
// "privilege": "writer",
5+
// "resource": {
6+
// "stream": "backend"
7+
// }
8+
// },
9+
// {
10+
// "privilege": "writer",
11+
// "resource": {
12+
// "stream": "frontend"
13+
// }
14+
// },
15+
16+
// {
17+
// "privilege": "reader",
18+
// "resource": {
19+
// "stream": "backend"
20+
// }
21+
// },
22+
23+
// ]
24+
// [
25+
// {
26+
// "privilege": "admin"
27+
// }
28+
// ]
29+
30+
// [
31+
// {
32+
// "privilege": "editor",
33+
// }
34+
// ]
35+
// Writer: Ingest, Query, ListStream, GetSchema, GetStats, GetRetention, PutAlert, GetAlert.
36+
// Reader: Query, ListStream, GetSchema, GetStats, GetRetention, GetAlert.
37+
// admin :Ingest,
38+
// Query,
39+
// CreateStream,
40+
// ListStream,
41+
// GetSchema,
42+
// GetStats,
43+
// DeleteStream,
44+
// GetRetention,
45+
// PutRetention,
46+
// PutAlert,
47+
// GetAlert,
48+
// PutUser,
49+
// ListUser,
50+
// DeleteUser,
51+
// PutRoles,
52+
// GetRole,
53+
54+
55+
const adminAccess =[ "Ingest", "Query", "CreateStream", "ListStream", "GetSchema", "GetStats", "DeleteStream", "GetRetention", "PutRetention", "PutAlert", "GetAlert", "PutUser", "ListUser", "DeleteUser", "PutRoles", "GetRole"]
56+
const editorAccess = ["Ingest", "Query", "CreateStream", "ListStream", "GetSchema", "GetStats", "GetRetention", "PutRetention", "PutAlert", "GetAlert"]
57+
const writerAccess = ["Ingest", "Query", "ListStream", "GetSchema", "GetStats", "GetRetention", "PutAlert", "GetAlert"]
58+
const readerAccess = ["Query", "ListStream", "GetSchema", "GetStats", "GetRetention", "GetAlert"]
59+
60+
61+
const getStreamsSepcificAccess = (roles: object[], stream: string ) => {
62+
let access : string[] = [];
63+
roles.forEach((role: any) => {
64+
if(role.privilege === "admin"){
65+
adminAccess.forEach((adminAction: string) => {
66+
if(!access.includes(adminAction)){
67+
access.push(adminAction);
68+
}
69+
}
70+
)
71+
}
72+
if(role.privilege === "editor"){
73+
editorAccess.forEach((editorAction: string) => {
74+
if(!access.includes(editorAction)){
75+
access.push(editorAction);
76+
}
77+
}
78+
)
79+
}
80+
if(role.privilege === "writer" && role.resource.stream === stream){
81+
writerAccess.forEach((writerAction: string) => {
82+
if(!access.includes(writerAction)){
83+
access.push(writerAction);
84+
}
85+
}
86+
)
87+
88+
}
89+
if(role.privilege === "reader" && role.resource.stream === stream){
90+
readerAccess.forEach((readerAction: string) => {
91+
if(!access.includes(readerAction)){
92+
access.push(readerAction);
93+
}
94+
}
95+
)
96+
97+
}
98+
});
99+
100+
return access;
101+
}
102+
103+
const getUserSepcificStreams = (roles: object[], streams: any[]) => {
104+
let userStreams: any[] = [];
105+
roles.forEach((role: any) => {
106+
if(role.privilege === "admin" || role.privilege === "editor" || role.resource.stream === "*"){
107+
userStreams = streams;
108+
return userStreams;
109+
}
110+
if(streams.find((stream: any) => stream.name === role.resource.stream)){
111+
if(!userStreams.find((stream: any) => stream.name === role.resource.stream)){
112+
userStreams.push({name: role.resource.stream})
113+
}
114+
}
115+
});
116+
117+
return userStreams;
118+
}
119+
120+
export {getStreamsSepcificAccess, getUserSepcificStreams};

src/hooks/usePutUserRole.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export const usePutUserRole = () => {
5757
id: 'load-data',
5858
color: 'red',
5959
title: 'Error Occured',
60-
message: 'Error Occured while Creating User',
60+
message: 'Error Occured while Updating User Roles',
6161
icon: <IconFileAlert size="1rem" />,
6262
autoClose: 2000,
6363
});

src/layouts/MainLayout/Context.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ const MainLayoutPageProvider: FC<HeaderProviderProps> = ({ children }) => {
6969
startTime: now.subtract(DEFAULT_FIXED_DURATIONS.milliseconds, 'milliseconds').toDate(),
7070
endTime: now.toDate(),
7171
streamName: '',
72+
access:null
7273
});
7374
const subLogSearch = useSubscribeState<LogsSearch>({
7475
search: '',

0 commit comments

Comments
 (0)