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 AdminMenu from '../menu/AdminMenu'; | ||||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||||
| import AccessContext from 'contexts/AccessContext'; | import { ApiTokenPage } from 'component/admin/apiToken/ApiTokenPage/ApiTokenPage'; | ||||||
| import { useContext } from 'react'; | import { useLocation } from 'react-router-dom'; | ||||||
| 
 | 
 | ||||||
| const ApiPage = () => { | const ApiPage = () => { | ||||||
|     const { isAdmin } = useContext(AccessContext); |     const { pathname } = useLocation(); | ||||||
|  |     const showAdminMenu = pathname.includes('/admin/'); | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|         <div> |         <div> | ||||||
|             <ConditionallyRender condition={isAdmin} show={<AdminMenu />} /> |             <ConditionallyRender | ||||||
|             <ApiTokenList /> |                 condition={showAdminMenu} | ||||||
|  |                 show={<AdminMenu />} | ||||||
|  |             /> | ||||||
|  |             <ApiTokenPage /> | ||||||
|         </div> |         </div> | ||||||
|     ); |     ); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -13,10 +13,6 @@ export const useStyles = makeStyles(theme => ({ | |||||||
|             justifyContent: 'center', |             justifyContent: 'center', | ||||||
|         }, |         }, | ||||||
|     }, |     }, | ||||||
|     apiError: { |  | ||||||
|         maxWidth: '400px', |  | ||||||
|         marginBottom: '1rem', |  | ||||||
|     }, |  | ||||||
|     center: { |     center: { | ||||||
|         textAlign: 'center', |         textAlign: 'center', | ||||||
|     }, |     }, | ||||||
| @ -25,9 +21,6 @@ export const useStyles = makeStyles(theme => ({ | |||||||
|         display: 'flex-inline', |         display: 'flex-inline', | ||||||
|         flexWrap: 'nowrap', |         flexWrap: 'nowrap', | ||||||
|     }, |     }, | ||||||
|     infoBoxContainer: { |  | ||||||
|         marginBottom: 40, |  | ||||||
|     }, |  | ||||||
|     hideSM: { |     hideSM: { | ||||||
|         [theme.breakpoints.down('sm')]: { |         [theme.breakpoints.down('sm')]: { | ||||||
|             display: 'none', |             display: 'none', | ||||||
|  | |||||||
| @ -1,7 +1,5 @@ | |||||||
| import { Fragment, useContext, useState } from 'react'; | import { useContext, useState } from 'react'; | ||||||
| import { useHistory } from 'react-router-dom'; |  | ||||||
| import { | import { | ||||||
|     Button, |  | ||||||
|     IconButton, |     IconButton, | ||||||
|     Table, |     Table, | ||||||
|     TableBody, |     TableBody, | ||||||
| @ -17,19 +15,12 @@ import useApiTokens from 'hooks/api/getters/useApiTokens/useApiTokens'; | |||||||
| import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | ||||||
| import useApiTokensApi from 'hooks/api/actions/useApiTokensApi/useApiTokensApi'; | import useApiTokensApi from 'hooks/api/actions/useApiTokensApi/useApiTokensApi'; | ||||||
| import ApiError from 'component/common/ApiError/ApiError'; | 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 ConditionallyRender from 'component/common/ConditionallyRender'; | ||||||
| import { | import { DELETE_API_TOKEN } from 'component/providers/AccessProvider/permissions'; | ||||||
|     CREATE_API_TOKEN, |  | ||||||
|     DELETE_API_TOKEN, |  | ||||||
| } from 'component/providers/AccessProvider/permissions'; |  | ||||||
| import { useStyles } from './ApiTokenList.styles'; | import { useStyles } from './ApiTokenList.styles'; | ||||||
| import Secret from './secret'; | import Secret from './secret'; | ||||||
| import { Delete, FileCopy } from '@material-ui/icons'; | import { Delete, FileCopy } from '@material-ui/icons'; | ||||||
| import Dialogue from 'component/common/Dialogue'; | 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 copy from 'copy-to-clipboard'; | ||||||
| import { useLocationSettings } from 'hooks/useLocationSettings'; | import { useLocationSettings } from 'hooks/useLocationSettings'; | ||||||
| import { formatDateYMD } from 'utils/formatDate'; | import { formatDateYMD } from 'utils/formatDate'; | ||||||
| @ -56,16 +47,9 @@ export const ApiTokenList = () => { | |||||||
|     const { tokens, loading, refetch, error } = useApiTokens(); |     const { tokens, loading, refetch, error } = useApiTokens(); | ||||||
|     const { deleteToken } = useApiTokensApi(); |     const { deleteToken } = useApiTokensApi(); | ||||||
|     const ref = useLoading(loading); |     const ref = useLoading(loading); | ||||||
|     const history = useHistory(); |  | ||||||
| 
 | 
 | ||||||
|     const renderError = () => { |     const renderError = () => { | ||||||
|         return ( |         return <ApiError onClick={refetch} text="Error fetching api tokens" />; | ||||||
|             <ApiError |  | ||||||
|                 onClick={refetch} |  | ||||||
|                 // className={styles.apiError}
 |  | ||||||
|                 text="Error fetching api tokens" |  | ||||||
|             /> |  | ||||||
|         ); |  | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     const copyToken = (value: string) => { |     const copyToken = (value: string) => { | ||||||
| @ -236,53 +220,6 @@ export const ApiTokenList = () => { | |||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|         <div ref={ref}> |         <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> |  | ||||||
|                                 } |  | ||||||
|                             /> |  | ||||||
|                         } |  | ||||||
|                     /> |  | ||||||
|                 } |  | ||||||
|             > |  | ||||||
|                 <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={error} show={renderError()} /> |             <ConditionallyRender condition={error} show={renderError()} /> | ||||||
|             <div className={styles.container}> |             <div className={styles.container}> | ||||||
|                 <ConditionallyRender |                 <ConditionallyRender | ||||||
| @ -291,7 +228,6 @@ export const ApiTokenList = () => { | |||||||
|                     elseShow={renderApiTokens(tokens)} |                     elseShow={renderApiTokens(tokens)} | ||||||
|                 /> |                 /> | ||||||
|             </div> |             </div> | ||||||
| 
 |  | ||||||
|             <Dialogue |             <Dialogue | ||||||
|                 open={showDelete} |                 open={showDelete} | ||||||
|                 onClick={onDeleteToken} |                 onClick={onDeleteToken} | ||||||
| @ -310,13 +246,11 @@ export const ApiTokenList = () => { | |||||||
|                             <code>{delToken?.username}</code> |                             <code>{delToken?.username}</code> | ||||||
|                         </li> |                         </li> | ||||||
|                         <li> |                         <li> | ||||||
|                                 <strong>type</strong>:{' '} |                             <strong>type</strong>: <code>{delToken?.type}</code> | ||||||
|                                 <code>{delToken?.type}</code> |  | ||||||
|                         </li> |                         </li> | ||||||
|                     </ul> |                     </ul> | ||||||
|                 </div> |                 </div> | ||||||
|             </Dialogue> |             </Dialogue> | ||||||
|             </PageContent> |  | ||||||
|         </div> |         </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 { Button } from '@material-ui/core'; | ||||||
| import { Alert } from '@material-ui/lab'; |  | ||||||
| import { useContext } from 'react'; | import { useContext } from 'react'; | ||||||
| import { useHistory } from 'react-router-dom'; | import { useHistory } from 'react-router-dom'; | ||||||
| import AccessContext from 'contexts/AccessContext'; | import AccessContext from 'contexts/AccessContext'; | ||||||
| @ -10,6 +9,7 @@ import { ADMIN } from 'component/providers/AccessProvider/permissions'; | |||||||
| import AdminMenu from 'component/admin/menu/AdminMenu'; | import AdminMenu from 'component/admin/menu/AdminMenu'; | ||||||
| import { useStyles } from './ProjectRoles.styles'; | import { useStyles } from './ProjectRoles.styles'; | ||||||
| import ProjectRoleList from './ProjectRoleList/ProjectRoleList'; | import ProjectRoleList from './ProjectRoleList/ProjectRoleList'; | ||||||
|  | import { AdminAlert } from 'component/common/AdminAlert/AdminAlert'; | ||||||
| 
 | 
 | ||||||
| const ProjectRoles = () => { | const ProjectRoles = () => { | ||||||
|     const { hasAccess } = useContext(AccessContext); |     const { hasAccess } = useContext(AccessContext); | ||||||
| @ -53,11 +53,7 @@ const ProjectRoles = () => { | |||||||
|                 <ConditionallyRender |                 <ConditionallyRender | ||||||
|                     condition={hasAccess(ADMIN)} |                     condition={hasAccess(ADMIN)} | ||||||
|                     show={<ProjectRoleList />} |                     show={<ProjectRoleList />} | ||||||
|                     elseShow={ |                     elseShow={<AdminAlert />} | ||||||
|                         <Alert severity="error"> |  | ||||||
|                             You need instance admin to access this section. |  | ||||||
|                         </Alert> |  | ||||||
|                     } |  | ||||||
|                 /> |                 /> | ||||||
|             </PageContent> |             </PageContent> | ||||||
|         </div> |         </div> | ||||||
|  | |||||||
| @ -5,12 +5,12 @@ import PageContent from 'component/common/PageContent/PageContent'; | |||||||
| import AccessContext from 'contexts/AccessContext'; | import AccessContext from 'contexts/AccessContext'; | ||||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||||
| import { ADMIN } from 'component/providers/AccessProvider/permissions'; | import { ADMIN } from 'component/providers/AccessProvider/permissions'; | ||||||
| import { Alert } from '@material-ui/lab'; |  | ||||||
| import HeaderTitle from 'component/common/HeaderTitle'; | import HeaderTitle from 'component/common/HeaderTitle'; | ||||||
| import { TableActions } from 'component/common/Table/TableActions/TableActions'; | import { TableActions } from 'component/common/Table/TableActions/TableActions'; | ||||||
| import { Button } from '@material-ui/core'; | import { Button } from '@material-ui/core'; | ||||||
| import { useStyles } from './UserAdmin.styles'; | import { useStyles } from './UserAdmin.styles'; | ||||||
| import { useHistory } from 'react-router-dom'; | import { useHistory } from 'react-router-dom'; | ||||||
|  | import { AdminAlert } from 'component/common/AdminAlert/AdminAlert'; | ||||||
| 
 | 
 | ||||||
| const UsersAdmin = () => { | const UsersAdmin = () => { | ||||||
|     const [search, setSearch] = useState(''); |     const [search, setSearch] = useState(''); | ||||||
| @ -63,11 +63,7 @@ const UsersAdmin = () => { | |||||||
|                 <ConditionallyRender |                 <ConditionallyRender | ||||||
|                     condition={hasAccess(ADMIN)} |                     condition={hasAccess(ADMIN)} | ||||||
|                     show={<UsersList search={search} />} |                     show={<UsersList search={search} />} | ||||||
|                     elseShow={ |                     elseShow={<AdminAlert />} | ||||||
|                         <Alert severity="error"> |  | ||||||
|                             You need instance admin to access this section. |  | ||||||
|                         </Alert> |  | ||||||
|                     } |  | ||||||
|                 /> |                 /> | ||||||
|             </PageContent> |             </PageContent> | ||||||
|         </div> |         </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 React, { useContext } from 'react'; | ||||||
| import { ADMIN } from 'component/providers/AccessProvider/permissions'; | import { ADMIN } from 'component/providers/AccessProvider/permissions'; | ||||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||||
| import AccessContext from 'contexts/AccessContext'; | import AccessContext from 'contexts/AccessContext'; | ||||||
| import { EventHistory } from '../EventHistory/EventHistory'; | import { EventHistory } from '../EventHistory/EventHistory'; | ||||||
|  | import { AdminAlert } from 'component/common/AdminAlert/AdminAlert'; | ||||||
| 
 | 
 | ||||||
| export const EventHistoryPage = () => { | export const EventHistoryPage = () => { | ||||||
|     const { hasAccess } = useContext(AccessContext); |     const { hasAccess } = useContext(AccessContext); | ||||||
| @ -12,11 +12,7 @@ export const EventHistoryPage = () => { | |||||||
|         <ConditionallyRender |         <ConditionallyRender | ||||||
|             condition={hasAccess(ADMIN)} |             condition={hasAccess(ADMIN)} | ||||||
|             show={<EventHistory />} |             show={<EventHistory />} | ||||||
|             elseShow={ |             elseShow={<AdminAlert />} | ||||||
|                 <Alert severity="error"> |  | ||||||
|                     You need instance admin to access this section. |  | ||||||
|                 </Alert> |  | ||||||
|             } |  | ||||||
|         /> |         /> | ||||||
|     ); |     ); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -18,6 +18,7 @@ export const UPDATE_ADDON = 'UPDATE_ADDON'; | |||||||
| export const DELETE_ADDON = 'DELETE_ADDON'; | export const DELETE_ADDON = 'DELETE_ADDON'; | ||||||
| export const CREATE_API_TOKEN = 'CREATE_API_TOKEN'; | export const CREATE_API_TOKEN = 'CREATE_API_TOKEN'; | ||||||
| export const DELETE_API_TOKEN = 'DELETE_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 DELETE_ENVIRONMENT = 'DELETE_ENVIRONMENT'; | ||||||
| export const UPDATE_ENVIRONMENT = 'UPDATE_ENVIRONMENT'; | export const UPDATE_ENVIRONMENT = 'UPDATE_ENVIRONMENT'; | ||||||
| export const CREATE_FEATURE_STRATEGY = 'CREATE_FEATURE_STRATEGY'; | export const CREATE_FEATURE_STRATEGY = 'CREATE_FEATURE_STRATEGY'; | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user