mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: use READ_API_TOKEN permission (#906)
* refactor: extract AdminAlert component * refactor: split ApiTokenPage from ApiTokenList * refactor: display AdminMenu based on path instead of permissions * feat: use the new READ_API_TOKEN permission
This commit is contained in:
		
							parent
							
								
									49a63173f8
								
							
						
					
					
						commit
						f6e42f99f9
					
				| @ -1,16 +1,19 @@ | ||||
| import { ApiTokenList } from '../apiToken/ApiTokenList/ApiTokenList'; | ||||
| import AdminMenu from '../menu/AdminMenu'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import AccessContext from 'contexts/AccessContext'; | ||||
| import { useContext } from 'react'; | ||||
| import { ApiTokenPage } from 'component/admin/apiToken/ApiTokenPage/ApiTokenPage'; | ||||
| import { useLocation } from 'react-router-dom'; | ||||
| 
 | ||||
| const ApiPage = () => { | ||||
|     const { isAdmin } = useContext(AccessContext); | ||||
|     const { pathname } = useLocation(); | ||||
|     const showAdminMenu = pathname.includes('/admin/'); | ||||
| 
 | ||||
|     return ( | ||||
|         <div> | ||||
|             <ConditionallyRender condition={isAdmin} show={<AdminMenu />} /> | ||||
|             <ApiTokenList /> | ||||
|             <ConditionallyRender | ||||
|                 condition={showAdminMenu} | ||||
|                 show={<AdminMenu />} | ||||
|             /> | ||||
|             <ApiTokenPage /> | ||||
|         </div> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -13,10 +13,6 @@ export const useStyles = makeStyles(theme => ({ | ||||
|             justifyContent: 'center', | ||||
|         }, | ||||
|     }, | ||||
|     apiError: { | ||||
|         maxWidth: '400px', | ||||
|         marginBottom: '1rem', | ||||
|     }, | ||||
|     center: { | ||||
|         textAlign: 'center', | ||||
|     }, | ||||
| @ -25,9 +21,6 @@ export const useStyles = makeStyles(theme => ({ | ||||
|         display: 'flex-inline', | ||||
|         flexWrap: 'nowrap', | ||||
|     }, | ||||
|     infoBoxContainer: { | ||||
|         marginBottom: 40, | ||||
|     }, | ||||
|     hideSM: { | ||||
|         [theme.breakpoints.down('sm')]: { | ||||
|             display: 'none', | ||||
|  | ||||
| @ -1,7 +1,5 @@ | ||||
| import { Fragment, useContext, useState } from 'react'; | ||||
| import { useHistory } from 'react-router-dom'; | ||||
| import { useContext, useState } from 'react'; | ||||
| import { | ||||
|     Button, | ||||
|     IconButton, | ||||
|     Table, | ||||
|     TableBody, | ||||
| @ -17,19 +15,12 @@ import useApiTokens from 'hooks/api/getters/useApiTokens/useApiTokens'; | ||||
| import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import useApiTokensApi from 'hooks/api/actions/useApiTokensApi/useApiTokensApi'; | ||||
| import ApiError from 'component/common/ApiError/ApiError'; | ||||
| import PageContent from 'component/common/PageContent'; | ||||
| import HeaderTitle from 'component/common/HeaderTitle'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { | ||||
|     CREATE_API_TOKEN, | ||||
|     DELETE_API_TOKEN, | ||||
| } from 'component/providers/AccessProvider/permissions'; | ||||
| import { DELETE_API_TOKEN } from 'component/providers/AccessProvider/permissions'; | ||||
| import { useStyles } from './ApiTokenList.styles'; | ||||
| import Secret from './secret'; | ||||
| import { Delete, FileCopy } from '@material-ui/icons'; | ||||
| import Dialogue from 'component/common/Dialogue'; | ||||
| import { CREATE_API_TOKEN_BUTTON } from 'utils/testIds'; | ||||
| import { Alert } from '@material-ui/lab'; | ||||
| import copy from 'copy-to-clipboard'; | ||||
| import { useLocationSettings } from 'hooks/useLocationSettings'; | ||||
| import { formatDateYMD } from 'utils/formatDate'; | ||||
| @ -56,16 +47,9 @@ export const ApiTokenList = () => { | ||||
|     const { tokens, loading, refetch, error } = useApiTokens(); | ||||
|     const { deleteToken } = useApiTokensApi(); | ||||
|     const ref = useLoading(loading); | ||||
|     const history = useHistory(); | ||||
| 
 | ||||
|     const renderError = () => { | ||||
|         return ( | ||||
|             <ApiError | ||||
|                 onClick={refetch} | ||||
|                 // className={styles.apiError}
 | ||||
|                 text="Error fetching api tokens" | ||||
|             /> | ||||
|         ); | ||||
|         return <ApiError onClick={refetch} text="Error fetching api tokens" />; | ||||
|     }; | ||||
| 
 | ||||
|     const copyToken = (value: string) => { | ||||
| @ -236,87 +220,37 @@ export const ApiTokenList = () => { | ||||
| 
 | ||||
|     return ( | ||||
|         <div ref={ref}> | ||||
|             <PageContent | ||||
|                 headerContent={ | ||||
|                     <HeaderTitle | ||||
|                         title="API Access" | ||||
|                         actions={ | ||||
|                             <ConditionallyRender | ||||
|                                 condition={hasAccess(CREATE_API_TOKEN)} | ||||
|                                 show={ | ||||
|                                     <Button | ||||
|                                         variant="contained" | ||||
|                                         color="primary" | ||||
|                                         onClick={() => | ||||
|                                             history.push( | ||||
|                                                 '/admin/api/create-token' | ||||
|                                             ) | ||||
|                                         } | ||||
|                                         data-testid={CREATE_API_TOKEN_BUTTON} | ||||
|                                     > | ||||
|                                         New API token | ||||
|                                     </Button> | ||||
|                                 } | ||||
|                             /> | ||||
|                         } | ||||
|                     /> | ||||
|                 } | ||||
|             <ConditionallyRender condition={error} show={renderError()} /> | ||||
|             <div className={styles.container}> | ||||
|                 <ConditionallyRender | ||||
|                     condition={tokens.length < 1 && !loading} | ||||
|                     show={<div>No API tokens available.</div>} | ||||
|                     elseShow={renderApiTokens(tokens)} | ||||
|                 /> | ||||
|             </div> | ||||
|             <Dialogue | ||||
|                 open={showDelete} | ||||
|                 onClick={onDeleteToken} | ||||
|                 onClose={() => { | ||||
|                     setShowDelete(false); | ||||
|                     setDeleteToken(undefined); | ||||
|                 }} | ||||
|                 title="Confirm deletion" | ||||
|             > | ||||
|                 <Alert severity="info" className={styles.infoBoxContainer}> | ||||
|                     <p> | ||||
|                         Read the{' '} | ||||
|                         <a | ||||
|                             href="https://docs.getunleash.io/docs" | ||||
|                             target="_blank" | ||||
|                             rel="noreferrer" | ||||
|                         > | ||||
|                             Getting started guide | ||||
|                         </a>{' '} | ||||
|                         to learn how to connect to the Unleash API from your | ||||
|                         application or programmatically. Please note it can take | ||||
|                         up to 1 minute before a new API key is activated. | ||||
|                     </p> | ||||
|                 <div> | ||||
|                     Are you sure you want to delete the following API token? | ||||
|                     <br /> | ||||
|                     <strong>API URL: </strong>{' '} | ||||
|                     <pre style={{ display: 'inline' }}> | ||||
|                         {uiConfig.unleashUrl}/api/ | ||||
|                     </pre> | ||||
|                 </Alert> | ||||
| 
 | ||||
|                 <ConditionallyRender condition={error} show={renderError()} /> | ||||
|                 <div className={styles.container}> | ||||
|                     <ConditionallyRender | ||||
|                         condition={tokens.length < 1 && !loading} | ||||
|                         show={<div>No API tokens available.</div>} | ||||
|                         elseShow={renderApiTokens(tokens)} | ||||
|                     /> | ||||
|                     <ul> | ||||
|                         <li> | ||||
|                             <strong>username</strong>:{' '} | ||||
|                             <code>{delToken?.username}</code> | ||||
|                         </li> | ||||
|                         <li> | ||||
|                             <strong>type</strong>: <code>{delToken?.type}</code> | ||||
|                         </li> | ||||
|                     </ul> | ||||
|                 </div> | ||||
| 
 | ||||
|                 <Dialogue | ||||
|                     open={showDelete} | ||||
|                     onClick={onDeleteToken} | ||||
|                     onClose={() => { | ||||
|                         setShowDelete(false); | ||||
|                         setDeleteToken(undefined); | ||||
|                     }} | ||||
|                     title="Confirm deletion" | ||||
|                 > | ||||
|                     <div> | ||||
|                         Are you sure you want to delete the following API token? | ||||
|                         <br /> | ||||
|                         <ul> | ||||
|                             <li> | ||||
|                                 <strong>username</strong>:{' '} | ||||
|                                 <code>{delToken?.username}</code> | ||||
|                             </li> | ||||
|                             <li> | ||||
|                                 <strong>type</strong>:{' '} | ||||
|                                 <code>{delToken?.type}</code> | ||||
|                             </li> | ||||
|                         </ul> | ||||
|                     </div> | ||||
|                 </Dialogue> | ||||
|             </PageContent> | ||||
|             </Dialogue> | ||||
|         </div> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -0,0 +1,7 @@ | ||||
| import { makeStyles } from '@material-ui/core/styles'; | ||||
| 
 | ||||
| export const useStyles = makeStyles(theme => ({ | ||||
|     infoBoxContainer: { | ||||
|         marginBottom: 40, | ||||
|     }, | ||||
| })); | ||||
| @ -0,0 +1,77 @@ | ||||
| import { useContext } from 'react'; | ||||
| import { useHistory } from 'react-router-dom'; | ||||
| import { Button } from '@material-ui/core'; | ||||
| import AccessContext from 'contexts/AccessContext'; | ||||
| import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import PageContent from 'component/common/PageContent'; | ||||
| import HeaderTitle from 'component/common/HeaderTitle'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { | ||||
|     CREATE_API_TOKEN, | ||||
|     READ_API_TOKEN, | ||||
| } from 'component/providers/AccessProvider/permissions'; | ||||
| import { useStyles } from './ApiTokenPage.styles'; | ||||
| import { CREATE_API_TOKEN_BUTTON } from 'utils/testIds'; | ||||
| import { Alert } from '@material-ui/lab'; | ||||
| import { ApiTokenList } from 'component/admin/apiToken/ApiTokenList/ApiTokenList'; | ||||
| import { AdminAlert } from 'component/common/AdminAlert/AdminAlert'; | ||||
| 
 | ||||
| export const ApiTokenPage = () => { | ||||
|     const styles = useStyles(); | ||||
|     const { hasAccess } = useContext(AccessContext); | ||||
|     const { uiConfig } = useUiConfig(); | ||||
|     const history = useHistory(); | ||||
| 
 | ||||
|     return ( | ||||
|         <PageContent | ||||
|             headerContent={ | ||||
|                 <HeaderTitle | ||||
|                     title="API Access" | ||||
|                     actions={ | ||||
|                         <ConditionallyRender | ||||
|                             condition={hasAccess(CREATE_API_TOKEN)} | ||||
|                             show={ | ||||
|                                 <Button | ||||
|                                     variant="contained" | ||||
|                                     color="primary" | ||||
|                                     onClick={() => | ||||
|                                         history.push('/admin/api/create-token') | ||||
|                                     } | ||||
|                                     data-testid={CREATE_API_TOKEN_BUTTON} | ||||
|                                 > | ||||
|                                     New API token | ||||
|                                 </Button> | ||||
|                             } | ||||
|                         /> | ||||
|                     } | ||||
|                 /> | ||||
|             } | ||||
|         > | ||||
|             <Alert severity="info" className={styles.infoBoxContainer}> | ||||
|                 <p> | ||||
|                     Read the{' '} | ||||
|                     <a | ||||
|                         href="https://docs.getunleash.io/docs" | ||||
|                         target="_blank" | ||||
|                         rel="noreferrer" | ||||
|                     > | ||||
|                         Getting started guide | ||||
|                     </a>{' '} | ||||
|                     to learn how to connect to the Unleash API from your | ||||
|                     application or programmatically. Please note it can take up | ||||
|                     to 1 minute before a new API key is activated. | ||||
|                 </p> | ||||
|                 <br /> | ||||
|                 <strong>API URL: </strong>{' '} | ||||
|                 <pre style={{ display: 'inline' }}> | ||||
|                     {uiConfig.unleashUrl}/api/ | ||||
|                 </pre> | ||||
|             </Alert> | ||||
|             <ConditionallyRender | ||||
|                 condition={hasAccess(READ_API_TOKEN)} | ||||
|                 show={() => <ApiTokenList />} | ||||
|                 elseShow={() => <AdminAlert />} | ||||
|             /> | ||||
|         </PageContent> | ||||
|     ); | ||||
| }; | ||||
| @ -1,5 +1,4 @@ | ||||
| import { Button } from '@material-ui/core'; | ||||
| import { Alert } from '@material-ui/lab'; | ||||
| import { useContext } from 'react'; | ||||
| import { useHistory } from 'react-router-dom'; | ||||
| import AccessContext from 'contexts/AccessContext'; | ||||
| @ -10,6 +9,7 @@ import { ADMIN } from 'component/providers/AccessProvider/permissions'; | ||||
| import AdminMenu from 'component/admin/menu/AdminMenu'; | ||||
| import { useStyles } from './ProjectRoles.styles'; | ||||
| import ProjectRoleList from './ProjectRoleList/ProjectRoleList'; | ||||
| import { AdminAlert } from 'component/common/AdminAlert/AdminAlert'; | ||||
| 
 | ||||
| const ProjectRoles = () => { | ||||
|     const { hasAccess } = useContext(AccessContext); | ||||
| @ -53,11 +53,7 @@ const ProjectRoles = () => { | ||||
|                 <ConditionallyRender | ||||
|                     condition={hasAccess(ADMIN)} | ||||
|                     show={<ProjectRoleList />} | ||||
|                     elseShow={ | ||||
|                         <Alert severity="error"> | ||||
|                             You need instance admin to access this section. | ||||
|                         </Alert> | ||||
|                     } | ||||
|                     elseShow={<AdminAlert />} | ||||
|                 /> | ||||
|             </PageContent> | ||||
|         </div> | ||||
|  | ||||
| @ -5,12 +5,12 @@ import PageContent from 'component/common/PageContent/PageContent'; | ||||
| import AccessContext from 'contexts/AccessContext'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ADMIN } from 'component/providers/AccessProvider/permissions'; | ||||
| import { Alert } from '@material-ui/lab'; | ||||
| import HeaderTitle from 'component/common/HeaderTitle'; | ||||
| import { TableActions } from 'component/common/Table/TableActions/TableActions'; | ||||
| import { Button } from '@material-ui/core'; | ||||
| import { useStyles } from './UserAdmin.styles'; | ||||
| import { useHistory } from 'react-router-dom'; | ||||
| import { AdminAlert } from 'component/common/AdminAlert/AdminAlert'; | ||||
| 
 | ||||
| const UsersAdmin = () => { | ||||
|     const [search, setSearch] = useState(''); | ||||
| @ -63,11 +63,7 @@ const UsersAdmin = () => { | ||||
|                 <ConditionallyRender | ||||
|                     condition={hasAccess(ADMIN)} | ||||
|                     show={<UsersList search={search} />} | ||||
|                     elseShow={ | ||||
|                         <Alert severity="error"> | ||||
|                             You need instance admin to access this section. | ||||
|                         </Alert> | ||||
|                     } | ||||
|                     elseShow={<AdminAlert />} | ||||
|                 /> | ||||
|             </PageContent> | ||||
|         </div> | ||||
|  | ||||
							
								
								
									
										9
									
								
								frontend/src/component/common/AdminAlert/AdminAlert.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								frontend/src/component/common/AdminAlert/AdminAlert.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | ||||
| import { Alert } from '@material-ui/lab'; | ||||
| 
 | ||||
| export const AdminAlert = () => { | ||||
|     return ( | ||||
|         <Alert severity="error"> | ||||
|             You need instance admin to access this section. | ||||
|         </Alert> | ||||
|     ); | ||||
| }; | ||||
| @ -1,9 +1,9 @@ | ||||
| import { Alert } from '@material-ui/lab'; | ||||
| import React, { useContext } from 'react'; | ||||
| import { ADMIN } from 'component/providers/AccessProvider/permissions'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import AccessContext from 'contexts/AccessContext'; | ||||
| import { EventHistory } from '../EventHistory/EventHistory'; | ||||
| import { AdminAlert } from 'component/common/AdminAlert/AdminAlert'; | ||||
| 
 | ||||
| export const EventHistoryPage = () => { | ||||
|     const { hasAccess } = useContext(AccessContext); | ||||
| @ -12,11 +12,7 @@ export const EventHistoryPage = () => { | ||||
|         <ConditionallyRender | ||||
|             condition={hasAccess(ADMIN)} | ||||
|             show={<EventHistory />} | ||||
|             elseShow={ | ||||
|                 <Alert severity="error"> | ||||
|                     You need instance admin to access this section. | ||||
|                 </Alert> | ||||
|             } | ||||
|             elseShow={<AdminAlert />} | ||||
|         /> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -18,6 +18,7 @@ export const UPDATE_ADDON = 'UPDATE_ADDON'; | ||||
| export const DELETE_ADDON = 'DELETE_ADDON'; | ||||
| export const CREATE_API_TOKEN = 'CREATE_API_TOKEN'; | ||||
| export const DELETE_API_TOKEN = 'DELETE_API_TOKEN'; | ||||
| export const READ_API_TOKEN = 'READ_API_TOKEN'; | ||||
| export const DELETE_ENVIRONMENT = 'DELETE_ENVIRONMENT'; | ||||
| export const UPDATE_ENVIRONMENT = 'UPDATE_ENVIRONMENT'; | ||||
| export const CREATE_FEATURE_STRATEGY = 'CREATE_FEATURE_STRATEGY'; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user