Skip to content

Commit

Permalink
Add user management page (#113)
Browse files Browse the repository at this point in the history
  • Loading branch information
kartik-gupta-ij authored Aug 9, 2023
1 parent 54b734c commit dc6eaf2
Show file tree
Hide file tree
Showing 22 changed files with 1,418 additions and 33 deletions.
10 changes: 9 additions & 1 deletion src/api/constants.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
const API_V1 = 'api/v1';

export const HEALTH_LIVENESS_URL = `${API_V1}/liveness`;

// Streams Management
export const LOG_STREAM_LIST_URL = `${API_V1}/logstream`;
export const LOG_STREAMS_SCHEMA_URL = (streamName: string) => `${LOG_STREAM_LIST_URL}/${streamName}/schema`;
export const LOG_QUERY_URL = `${API_V1}/query`;
export const LOG_STREAMS_ALERTS_URL = (streamName: string) => `${LOG_STREAM_LIST_URL}/${streamName}/alert`;
export const LOG_STREAMS_RETRNTION_URL = (streamName: string) => `${LOG_STREAM_LIST_URL}/${streamName}/retention`;
export const LOG_STREAMS_STATS_URL = (streamName: string) => `${LOG_STREAM_LIST_URL}/${streamName}/stats`;
export const DELETE_STREAMS_URL = (streamName: string) => `${LOG_STREAM_LIST_URL}/${streamName}`;

// About Parsable Instance
export const ABOUT_URL = `${API_V1}/about`;

// Users Management
export const USERS_LIST_URL = `${API_V1}/user`;
export const USER_URL = (username: string) => `${USERS_LIST_URL}/${username}`;
export const USER_ROLES_URL = (username: string) => `${USER_URL(username)}/role`;
22 changes: 22 additions & 0 deletions src/api/users.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Axios } from "./axios";
import { USERS_LIST_URL, USER_ROLES_URL, USER_URL } from "./constants";

export const getUsers = () => {
return Axios().get(USERS_LIST_URL);
}

export const putUser = (username: string, roles?: object[]) => {
return Axios().put(USER_URL(username), roles );
}

export const deleteUser = (username: string) => {
return Axios().delete(USER_URL(username));
}

export const putUserRoles = (username: string, roles: object[]) => {
return Axios().put(USER_ROLES_URL(username), roles);
}

export const getUserRoles = (username: string) => {
return Axios().get(USER_ROLES_URL(username));
}
34 changes: 34 additions & 0 deletions src/components/Header/CreateUser.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Button, Tooltip, px } from '@mantine/core';
import { IconUserPlus } from '@tabler/icons-react';
import type { FC } from 'react';
import { useLogQueryStyles } from './styles';
import { useHeaderContext } from '@/layouts/MainLayout/Context';

const CreateUser: FC = () => {
const { classes } = useLogQueryStyles();
const { refreshNowBtn } = classes;
const {
state: { subCreateUserModalTogle }
} = useHeaderContext();


const handleOpen = () => {
subCreateUserModalTogle.set(true);
};


return (
<Tooltip
label={'Create New User'}
sx={{ color: 'white', backgroundColor: 'black' }}
withArrow
onClick={handleOpen}
position="left">
<Button variant="default" className={refreshNowBtn} aria-label="create user">
<IconUserPlus size={px('1.2rem')} stroke={1.5} />
</Button>
</Tooltip>
);
};

export default CreateUser;
21 changes: 21 additions & 0 deletions src/components/Header/ReloadUser.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Button, Tooltip, px } from '@mantine/core';
import { IconReload } from '@tabler/icons-react';
import type { FC } from 'react';
import { useLogQueryStyles } from './styles';
import { useNavigate } from 'react-router-dom';

const ReloadUser: FC = () => {
const navigate = useNavigate();
const { classes } = useLogQueryStyles();
const { refreshNowBtn } = classes;

return (
<Tooltip label="Refresh" sx={{ color: 'white', backgroundColor: 'black' }} withArrow position="left">
<Button className={refreshNowBtn} onClick={()=>navigate(0)}>
<IconReload size={px('1.2rem')} stroke={1.5} />
</Button>
</Tooltip>
);
};

export default ReloadUser;
25 changes: 25 additions & 0 deletions src/components/Header/SubHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import RefreshNow from './RefreshNow';
import Search from './Search';
import TimeRange from './TimeRange';
import { useLogQueryStyles } from './styles';
import ReloadUser from './ReloadUser';
import DocsUser from './UserDocs';
import CreateUser from './CreateUser';

export const StatsHeader: FC = () => {
const { classes } = useLogQueryStyles();
Expand Down Expand Up @@ -88,3 +91,25 @@ export const ConfigHeader: FC = () => {
</Box>
);
};

export const UsersManagementHeader: FC = () => {
const { classes } = useLogQueryStyles();
const { container, innerContainer } = classes;

return (
<Box className={container}>
<Box>
<Box className={innerContainer}>
<HeaderBreadcrumbs crumbs={['User Management']} />
</Box>
</Box>
<Box>
<Box className={innerContainer}>
<ReloadUser />
<DocsUser />
<CreateUser />
</Box>
</Box>
</Box>
);
};
24 changes: 24 additions & 0 deletions src/components/Header/UserDocs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Button, Tooltip, px } from '@mantine/core';
import { IconBook2 } from '@tabler/icons-react';
import type { FC } from 'react';
import { useLogQueryStyles } from './styles';

const DocsUser: FC = () => {
const { classes } = useLogQueryStyles();
const { refreshNowBtn } = classes;

return (
<Tooltip label={'Docs'} sx={{ color: 'white', backgroundColor: 'black' }} withArrow position="left">
<Button
variant="default"
className={refreshNowBtn}
onClick={() => {
window.open('https://www.parseable.io/docs/rbac', '_blank');
}}>
<IconBook2 size={px('1.2rem')} stroke={1.5} />
</Button>
</Tooltip>
);
};

export default DocsUser;
5 changes: 3 additions & 2 deletions src/components/Header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import type { HeaderProps as MantineHeaderProps } from '@mantine/core';
import { FC } from 'react';
import { Route, Routes } from 'react-router-dom';
import HeaderLayout from './Layout';
import { ConfigHeader, LogsHeader, QueryHeader, StatsHeader } from './SubHeader';
import { CONFIG_ROUTE, LOGS_ROUTE, QUERY_ROUTE, STATS_ROUTE } from '@/constants/routes';
import { ConfigHeader, LogsHeader, QueryHeader, StatsHeader, UsersManagementHeader } from './SubHeader';
import { CONFIG_ROUTE, LOGS_ROUTE, QUERY_ROUTE, STATS_ROUTE, USERS_MANAGEMENT_ROUTE } from '@/constants/routes';

type HeaderProps = Omit<MantineHeaderProps, 'children' | 'height' | 'className'>;

Expand All @@ -15,6 +15,7 @@ const Header: FC<HeaderProps> = (props) => {
<Route path={QUERY_ROUTE} element={<QueryHeader />} />
<Route path={STATS_ROUTE} element={<StatsHeader />} />
<Route path={CONFIG_ROUTE} element={<ConfigHeader />} />
<Route path={USERS_MANAGEMENT_ROUTE} element={<UsersManagementHeader />} />
</Route>
</Routes>
);
Expand Down
82 changes: 55 additions & 27 deletions src/components/Navbar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
import type { NavbarProps as MantineNavbarProps } from '@mantine/core';
import {
Navbar as MantineNavbar,
NavLink,
Select,
Box,
Modal,
Text,
Button,
TextInput,
} from '@mantine/core';
import { Navbar as MantineNavbar, NavLink, Select, Box, Modal, Text, Button, TextInput } from '@mantine/core';
import {
IconZoomCode,
IconReportAnalytics,
Expand All @@ -21,6 +12,7 @@ import {
IconSettings,
IconTrash,
IconInfoCircle,
IconUserCog,
} from '@tabler/icons-react';
import { FC, useEffect } from 'react';
import { useNavbarStyles } from './styles';
Expand All @@ -32,9 +24,10 @@ import { DEFAULT_FIXED_DURATIONS, useHeaderContext } from '@/layouts/MainLayout/
import useMountedState from '@/hooks/useMountedState';
import dayjs from 'dayjs';
import { useDisclosure, useLocalStorage } from '@mantine/hooks';
import { LOGIN_ROUTE } from '@/constants/routes';
import { LOGIN_ROUTE, USERS_MANAGEMENT_ROUTE } from '@/constants/routes';
import { useDeleteLogStream } from '@/hooks/useDeleteLogStream';
import InfoModal from './infoModal';
import { useGetUserRole } from '@/hooks/useGetUserRoles';

const links = [
{ icon: IconZoomCode, label: 'Query', pathname: '/query' },
Expand All @@ -49,12 +42,11 @@ const Navbar: FC<NavbarProps> = (props) => {
const navigate = useNavigate();
const { streamName } = useParams();
const location = useLocation();

const [username] = useLocalStorage({ key: 'username', getInitialValueInEffect: false });
const [, , removeCredentials] = useLocalStorage({ key: 'credentials' });
const [, , removeUsername] = useLocalStorage({ key: 'username' });



const {
state: { subNavbarTogle },
} = useHeaderContext();
Expand All @@ -63,6 +55,7 @@ const Navbar: FC<NavbarProps> = (props) => {
const [searchValue, setSearchValue] = useMountedState('');
const [currentPage, setCurrentPage] = useMountedState('/query');
const [deleteStream, setDeleteStream] = useMountedState('');
const [isAdmin, setIsAdmin] = useMountedState(false);

const [disableLink, setDisableLink] = useMountedState(false);
const [isSubNavbarOpen, setIsSubNavbarOpen] = useMountedState(false);
Expand Down Expand Up @@ -103,8 +96,7 @@ const Navbar: FC<NavbarProps> = (props) => {
setSearchValue('');
setDisableLink(true);
navigate(`/`);
}
else if (streamName) {
} else if (streamName) {
if (streamName === deleteStream && streams) {
setDeleteStream('');
handleChange(streams[0].name);
Expand All @@ -122,14 +114,24 @@ const Navbar: FC<NavbarProps> = (props) => {
handleChange(streamName);
}
} else if (streams && Boolean(streams.length)) {
handleChange(streams[0].name);
if (location.pathname === USERS_MANAGEMENT_ROUTE) {
handleChangeWithoutRiderection(streams[0].name, location.pathname);
} else {
handleChange(streams[0].name);
}
}
}, [streams]);

const handleChange = (value: string) => {
handleChangeWithoutRiderection(value);
navigate(`/${value}${currentPage}`);
};
const handleChangeWithoutRiderection = (value: string, page: string = currentPage) => {
setActiveStream(value);
setSearchValue(value);
setCurrentPage(page);
const now = dayjs();

subLogQuery.set((state) => {
state.streamName = value || '';
state.startTime = now.subtract(DEFAULT_FIXED_DURATIONS.milliseconds, 'milliseconds').toDate();
Expand All @@ -138,16 +140,14 @@ const Navbar: FC<NavbarProps> = (props) => {
subLogSelectedTimeRange.set((state) => {
state.state = 'fixed';
state.value = DEFAULT_FIXED_DURATIONS.name;
});
});
subLogSearch.set((state) => {
state.search = '';
state.filters = {};
});
subRefreshInterval.set(null);
setDisableLink(false);
navigate(`/${value}${currentPage}`);
};

const handleCloseDelete = () => {
closeDelete();
setDeleteStream('');
Expand All @@ -166,6 +166,22 @@ const Navbar: FC<NavbarProps> = (props) => {
}
}, [deleteData]);

//isAdmin
const { data: adminData, getRoles, resetData } = useGetUserRole();
useEffect(() => {
if (username) {
getRoles(username);
}
return () => {
resetData();
};
}, [username]);

useEffect(() => {
if (adminData) {
setIsAdmin(true);
}
}, [adminData]);

const { classes } = useNavbarStyles();
const {
Expand All @@ -177,10 +193,13 @@ const Navbar: FC<NavbarProps> = (props) => {
lowerContainer,
actionBtn,
userBtn,
userManagementBtn,
userManagementBtnActive,
} = classes;
return (
<MantineNavbar {...props} withBorder zIndex={1} hiddenBreakpoint={window.outerWidth + 20} hidden={isSubNavbarOpen}>
<MantineNavbar.Section grow className={container}>

<NavLink label="Log Streams" icon={<IconBinaryTree2 size="1.5rem" stroke={1.3} />} className={streamsBtn} />
<Select
placeholder="Pick one"
Expand Down Expand Up @@ -208,8 +227,7 @@ const Navbar: FC<NavbarProps> = (props) => {
setCurrentPage(link.pathname);
}}
key={link.label}

className={(currentPage === link.pathname && linkBtnActive ) || linkBtn}
className={(currentPage === link.pathname && linkBtnActive) || linkBtn}
/>
);
})}
Expand All @@ -231,6 +249,18 @@ const Navbar: FC<NavbarProps> = (props) => {
sx={{ paddingLeft: 0 }}
/>
)}
{isAdmin && (
<NavLink
pt={24}
className={(currentPage === USERS_MANAGEMENT_ROUTE && userManagementBtnActive) || userManagementBtn}
label="Users"
icon={<IconUserCog size="1.5rem" stroke={1.3} />}
onClick={() => {
navigate(`/users`);
setCurrentPage(USERS_MANAGEMENT_ROUTE);
}}
/>
)}
</MantineNavbar.Section>
<MantineNavbar.Section className={lowerContainer}>
<NavLink label={username} icon={<IconUser size="1.3rem" stroke={1.3} />} className={userBtn} component="a" />
Expand All @@ -256,15 +286,15 @@ const Navbar: FC<NavbarProps> = (props) => {
onChange={(e) => {
setDeleteStream(e.target.value);
}}
placeholder={`Type the name of the stream to confirm. i.e: ${streamName}`}
placeholder={`Type the name of the stream to confirm. i.e: ${activeStream}`}
/>

<Box mt={10} display="flex" sx={{ justifyContent: 'end' }}>
<Button
variant="filled"
color="red"
sx={{ margin: '12px' }}
disabled={deleteStream === streamName ? false : true}
disabled={deleteStream === activeStream ? false : true}
onClick={handleDelete}>
Delete
</Button>
Expand All @@ -273,11 +303,9 @@ const Navbar: FC<NavbarProps> = (props) => {
</Button>
</Box>
</Modal>
<InfoModal opened={opened} close={close}/>
<InfoModal opened={opened} close={close} />
</MantineNavbar>
);
};



export default Navbar;
Loading

0 comments on commit dc6eaf2

Please sign in to comment.