mirror of
				https://github.com/strapi/strapi.git
				synced 2025-10-31 18:08:11 +00:00 
			
		
		
		
	add frontend views (#11267)
This commit is contained in:
		
							parent
							
								
									6e5be7ac49
								
							
						
					
					
						commit
						39b76f140a
					
				| @ -0,0 +1,14 @@ | |||||||
|  | import styled from 'styled-components'; | ||||||
|  | import { FieldAction } from '@strapi/parts/Field'; | ||||||
|  | 
 | ||||||
|  | const FieldActionWrapper = styled(FieldAction)` | ||||||
|  |   svg { | ||||||
|  |     height: 1rem; | ||||||
|  |     width: 1rem; | ||||||
|  |     path { | ||||||
|  |       fill: ${({ theme }) => theme.colors.neutral600}; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | `;
 | ||||||
|  | 
 | ||||||
|  | export default FieldActionWrapper; | ||||||
| @ -25,7 +25,9 @@ export default { | |||||||
|       }, |       }, | ||||||
|       permissions: pluginPermissions.main, |       permissions: pluginPermissions.main, | ||||||
|       Component: async () => { |       Component: async () => { | ||||||
|         const component = await import(/* webpackChunkName: "documentation-page" */ './pages/App'); |         const component = await import( | ||||||
|  |           /* webpackChunkName: "documentation-page" */ './pages/PluginPage' | ||||||
|  |         ); | ||||||
| 
 | 
 | ||||||
|         return component; |         return component; | ||||||
|       }, |       }, | ||||||
| @ -41,7 +43,24 @@ export default { | |||||||
|       pluginLogo, |       pluginLogo, | ||||||
|     }); |     }); | ||||||
|   }, |   }, | ||||||
|   bootstrap() {}, |   bootstrap(app) { | ||||||
|  |     app.addSettingsLink('global', { | ||||||
|  |       intlLabel: { | ||||||
|  |         id: `${pluginId}.plugin.name`, | ||||||
|  |         defaultMessage: 'Documentation', | ||||||
|  |       }, | ||||||
|  |       id: 'documentation', | ||||||
|  |       to: `/settings/${pluginId}`, | ||||||
|  |       Component: async () => { | ||||||
|  |         const component = await import( | ||||||
|  |           /* webpackChunkName: "documentation-settings" */ './pages/SettingsPage' | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         return component; | ||||||
|  |       }, | ||||||
|  |       permissions: pluginPermissions.main, | ||||||
|  |     }); | ||||||
|  |   }, | ||||||
|   async registerTrads({ locales }) { |   async registerTrads({ locales }) { | ||||||
|     const importedTrads = await Promise.all( |     const importedTrads = await Promise.all( | ||||||
|       locales.map(locale => { |       locales.map(locale => { | ||||||
|  | |||||||
| @ -1,52 +0,0 @@ | |||||||
| /** |  | ||||||
|  * |  | ||||||
|  * This component is the skeleton around the actual pages, and should only |  | ||||||
|  * contain code that should be seen on all pages. (e.g. navigation bar) |  | ||||||
|  * |  | ||||||
|  */ |  | ||||||
| 
 |  | ||||||
| import React from 'react'; |  | ||||||
| import { useIntl } from 'react-intl'; |  | ||||||
| import { CheckPagePermissions, NoContent } from '@strapi/helper-plugin'; |  | ||||||
| import { Layout, HeaderLayout, ContentLayout } from '@strapi/parts/Layout'; |  | ||||||
| import { Main } from '@strapi/parts/Main'; |  | ||||||
| import pluginPermissions from '../../permissions'; |  | ||||||
| import { getTrad } from '../../utils'; |  | ||||||
| // import HomePage from '../HomePage';
 |  | ||||||
| 
 |  | ||||||
| const ComingSoon = () => { |  | ||||||
|   const { formatMessage } = useIntl(); |  | ||||||
| 
 |  | ||||||
|   return ( |  | ||||||
|     <Layout> |  | ||||||
|       <Main> |  | ||||||
|         <HeaderLayout |  | ||||||
|           title={formatMessage({ |  | ||||||
|             id: getTrad('plugin.name'), |  | ||||||
|             defaultMessage: 'Documentation', |  | ||||||
|           })} |  | ||||||
|         /> |  | ||||||
|         <ContentLayout> |  | ||||||
|           <NoContent |  | ||||||
|             content={{ |  | ||||||
|               id: getTrad('coming.soon'), |  | ||||||
|               defaultMessage: |  | ||||||
|                 'This content is currently under construction and will be back in a few weeks!', |  | ||||||
|             }} |  | ||||||
|           /> |  | ||||||
|         </ContentLayout> |  | ||||||
|       </Main> |  | ||||||
|     </Layout> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| function App() { |  | ||||||
|   return ( |  | ||||||
|     <CheckPagePermissions permissions={pluginPermissions.main}> |  | ||||||
|       {/* <HomePage /> */} |  | ||||||
|       <ComingSoon /> |  | ||||||
|     </CheckPagePermissions> |  | ||||||
|   ); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export default App; |  | ||||||
| @ -90,8 +90,6 @@ const HomePage = () => { | |||||||
|     setVersionToDelete(null); |     setVersionToDelete(null); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   console.log(data); |  | ||||||
| 
 |  | ||||||
|   if (isLoading) { |   if (isLoading) { | ||||||
|     return <LoadingIndicatorPage />; |     return <LoadingIndicatorPage />; | ||||||
|   } |   } | ||||||
|  | |||||||
							
								
								
									
										193
									
								
								packages/plugins/documentation/admin/src/pages/PluginPage/index.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										193
									
								
								packages/plugins/documentation/admin/src/pages/PluginPage/index.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,193 @@ | |||||||
|  | /** | ||||||
|  |  * | ||||||
|  |  * This component is the skeleton around the actual pages, and should only | ||||||
|  |  * contain code that should be seen on all pages. (e.g. navigation bar) | ||||||
|  |  * | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | import React, { useState } from 'react'; | ||||||
|  | import { useIntl } from 'react-intl'; | ||||||
|  | import { | ||||||
|  |   CheckPermissions, | ||||||
|  |   ConfirmDialog, | ||||||
|  |   LoadingIndicatorPage, | ||||||
|  |   stopPropagation, | ||||||
|  |   EmptyStateLayout, | ||||||
|  | } from '@strapi/helper-plugin'; | ||||||
|  | import { Button } from '@strapi/parts/Button'; | ||||||
|  | import { Layout, HeaderLayout, ContentLayout } from '@strapi/parts/Layout'; | ||||||
|  | import { Main } from '@strapi/parts/Main'; | ||||||
|  | import { IconButton } from '@strapi/parts/IconButton'; | ||||||
|  | import { Text, TableLabel } from '@strapi/parts/Text'; | ||||||
|  | import { Row } from '@strapi/parts/Row'; | ||||||
|  | import { Table, Tr, Thead, Th, Tbody, Td } from '@strapi/parts/Table'; | ||||||
|  | 
 | ||||||
|  | import DeleteIcon from '@strapi/icons/DeleteIcon'; | ||||||
|  | import Show from '@strapi/icons/Show'; | ||||||
|  | import Reload from '@strapi/icons/Reload'; | ||||||
|  | 
 | ||||||
|  | import permissions from '../../permissions'; | ||||||
|  | import { getTrad } from '../../utils'; | ||||||
|  | import openWithNewTab from '../../utils/openWithNewTab'; | ||||||
|  | import useReactQuery from '../utils/useReactQuery'; | ||||||
|  | 
 | ||||||
|  | const PluginPage = () => { | ||||||
|  |   const { formatMessage } = useIntl(); | ||||||
|  |   const { data, isLoading, deleteMutation, regenerateDocMutation } = useReactQuery(); | ||||||
|  |   const [showConfirmDelete, setShowConfirmDelete] = useState(false); | ||||||
|  |   const [isConfirmButtonLoading, setIsConfirmButtonLoading] = useState(false); | ||||||
|  |   const [versionToDelete, setVersionToDelete] = useState(); | ||||||
|  | 
 | ||||||
|  |   const colCount = 4; | ||||||
|  |   const rowCount = (data?.docVersions?.length || 0) + 1; | ||||||
|  | 
 | ||||||
|  |   const openDocVersion = () => { | ||||||
|  |     const slash = data?.prefix.startsWith('/') ? '' : '/'; | ||||||
|  |     openWithNewTab(`${slash}${data?.prefix}/v${data?.currentVersion}`); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const handleRegenerateDoc = version => { | ||||||
|  |     regenerateDocMutation.mutate({ version, prefix: data?.prefix }); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const handleShowConfirmDelete = () => { | ||||||
|  |     setShowConfirmDelete(!showConfirmDelete); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const handleConfirmDelete = async () => { | ||||||
|  |     setIsConfirmButtonLoading(true); | ||||||
|  |     await deleteMutation.mutateAsync({ prefix: data?.prefix, version: versionToDelete }); | ||||||
|  |     setShowConfirmDelete(!showConfirmDelete); | ||||||
|  |     setIsConfirmButtonLoading(false); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const handleClickDelete = version => { | ||||||
|  |     setVersionToDelete(version); | ||||||
|  |     setShowConfirmDelete(!showConfirmDelete); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <Layout> | ||||||
|  |       <Main> | ||||||
|  |         <HeaderLayout | ||||||
|  |           title={formatMessage({ | ||||||
|  |             id: getTrad('plugin.name'), | ||||||
|  |             defaultMessage: 'Documentation', | ||||||
|  |           })} | ||||||
|  |           subtitle={formatMessage({ | ||||||
|  |             id: getTrad('pages.PluginPage.header.description'), | ||||||
|  |             defaultMessage: 'Configure the documentation plugin', | ||||||
|  |           })} | ||||||
|  |           primaryAction={ | ||||||
|  |             //  eslint-disable-next-line
 | ||||||
|  |             <CheckPermissions permissions={permissions.open}> | ||||||
|  |               <Button onClick={openDocVersion} startIcon={<Show />}> | ||||||
|  |                 {formatMessage({ | ||||||
|  |                   id: getTrad('pages.PluginPage.Button.open'), | ||||||
|  |                   defaultMessage: 'Open Documentation', | ||||||
|  |                 })} | ||||||
|  |               </Button> | ||||||
|  |             </CheckPermissions> | ||||||
|  |           } | ||||||
|  |         /> | ||||||
|  |         <ContentLayout> | ||||||
|  |           {isLoading && <LoadingIndicatorPage>Plugin is loading</LoadingIndicatorPage>} | ||||||
|  |           {data?.docVersions.length ? ( | ||||||
|  |             <Table colCount={colCount} rowCount={rowCount}> | ||||||
|  |               <Thead> | ||||||
|  |                 <Tr> | ||||||
|  |                   <Th> | ||||||
|  |                     <TableLabel textColor="neutral600"> | ||||||
|  |                       {formatMessage({ | ||||||
|  |                         id: getTrad('pages.PluginPage.table.version'), | ||||||
|  |                         defaultMessage: 'Version', | ||||||
|  |                       })} | ||||||
|  |                     </TableLabel> | ||||||
|  |                   </Th> | ||||||
|  |                   <Th> | ||||||
|  |                     <TableLabel textColor="neutral600"> | ||||||
|  |                       {formatMessage({ | ||||||
|  |                         id: getTrad('pages.PluginPage.table.generated'), | ||||||
|  |                         defaultMessage: 'Last Generated', | ||||||
|  |                       })} | ||||||
|  |                     </TableLabel> | ||||||
|  |                   </Th> | ||||||
|  |                 </Tr> | ||||||
|  |               </Thead> | ||||||
|  |               <Tbody> | ||||||
|  |                 {data.docVersions | ||||||
|  |                   .sort((a, b) => (a.generatedDate < b.generatedDate ? 1 : -1)) | ||||||
|  |                   .map(doc => ( | ||||||
|  |                     <Tr key={doc.version}> | ||||||
|  |                       <Td width="50%"> | ||||||
|  |                         <Text>{doc.version}</Text> | ||||||
|  |                       </Td> | ||||||
|  |                       <Td width="50%"> | ||||||
|  |                         <Text>{doc.generatedDate}</Text> | ||||||
|  |                       </Td> | ||||||
|  |                       <Td> | ||||||
|  |                         <Row justifyContent="end" {...stopPropagation}> | ||||||
|  |                           <IconButton | ||||||
|  |                             onClick={openDocVersion} | ||||||
|  |                             noBorder | ||||||
|  |                             icon={<Show />} | ||||||
|  |                             label={formatMessage( | ||||||
|  |                               { | ||||||
|  |                                 id: getTrad('pages.PluginPage.table.icon.show'), | ||||||
|  |                                 defaultMessage: 'Open {target}', | ||||||
|  |                               }, | ||||||
|  |                               { target: `${doc.version}` } | ||||||
|  |                             )} | ||||||
|  |                           /> | ||||||
|  |                           <CheckPermissions permissions={permissions.regenerate}> | ||||||
|  |                             <IconButton | ||||||
|  |                               onClick={() => handleRegenerateDoc(doc.version)} | ||||||
|  |                               noBorder | ||||||
|  |                               icon={<Reload />} | ||||||
|  |                               label={formatMessage( | ||||||
|  |                                 { | ||||||
|  |                                   id: getTrad('pages.PluginPage.table.icon.regenerate'), | ||||||
|  |                                   defaultMessage: 'Regnerate {target}', | ||||||
|  |                                 }, | ||||||
|  |                                 { target: `${doc.version}` } | ||||||
|  |                               )} | ||||||
|  |                             /> | ||||||
|  |                           </CheckPermissions> | ||||||
|  |                           <CheckPermissions permissions={permissions.update}> | ||||||
|  |                             {doc.version !== data.currentVersion && ( | ||||||
|  |                               <IconButton | ||||||
|  |                                 onClick={() => handleClickDelete(doc.version)} | ||||||
|  |                                 noBorder | ||||||
|  |                                 icon={<DeleteIcon />} | ||||||
|  |                                 label={formatMessage( | ||||||
|  |                                   { | ||||||
|  |                                     id: getTrad('pages.PluginPage.table.icon.delete'), | ||||||
|  |                                     defaultMessage: 'Delete {target}', | ||||||
|  |                                   }, | ||||||
|  |                                   { target: `${doc.version}` } | ||||||
|  |                                 )} | ||||||
|  |                               /> | ||||||
|  |                             )} | ||||||
|  |                           </CheckPermissions> | ||||||
|  |                         </Row> | ||||||
|  |                       </Td> | ||||||
|  |                     </Tr> | ||||||
|  |                   ))} | ||||||
|  |               </Tbody> | ||||||
|  |             </Table> | ||||||
|  |           ) : ( | ||||||
|  |             <EmptyStateLayout /> | ||||||
|  |           )} | ||||||
|  |         </ContentLayout> | ||||||
|  |         <ConfirmDialog | ||||||
|  |           isConfirmButtonLoading={isConfirmButtonLoading} | ||||||
|  |           onConfirm={handleConfirmDelete} | ||||||
|  |           onToggleDialog={handleShowConfirmDelete} | ||||||
|  |           isOpen={showConfirmDelete} | ||||||
|  |         /> | ||||||
|  |       </Main> | ||||||
|  |     </Layout> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default PluginPage; | ||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -0,0 +1,23 @@ | |||||||
|  | import { setupServer } from 'msw/node'; | ||||||
|  | import { rest } from 'msw'; | ||||||
|  | 
 | ||||||
|  | const handlers = [ | ||||||
|  |   rest.get('*/getInfos', (req, res, ctx) => { | ||||||
|  |     return res( | ||||||
|  |       ctx.delay(1000), | ||||||
|  |       ctx.status(200), | ||||||
|  |       ctx.json({ | ||||||
|  |         currentVersion: '1.0.0', | ||||||
|  |         docVersions: [ | ||||||
|  |           { version: '1.0.0', generatedDoc: '10/05/2021 2:52:44 PM' }, | ||||||
|  |           { version: '1.2.0', generatedDoc: '11/05/2021 3:00:00 PM' }, | ||||||
|  |         ], | ||||||
|  |         prefix: '/documentation', | ||||||
|  |       }) | ||||||
|  |     ); | ||||||
|  |   }), | ||||||
|  | ]; | ||||||
|  | 
 | ||||||
|  | const server = setupServer(...handlers); | ||||||
|  | 
 | ||||||
|  | export default server; | ||||||
| @ -0,0 +1,169 @@ | |||||||
|  | import React, { useState } from 'react'; | ||||||
|  | import { useIntl } from 'react-intl'; | ||||||
|  | import { Formik } from 'formik'; | ||||||
|  | import { CheckPermissions, Form, LoadingIndicatorPage } from '@strapi/helper-plugin'; | ||||||
|  | 
 | ||||||
|  | // Strapi Parts
 | ||||||
|  | import { ContentLayout, HeaderLayout } from '@strapi/parts/Layout'; | ||||||
|  | import { Main } from '@strapi/parts/Main'; | ||||||
|  | import { Button } from '@strapi/parts/Button'; | ||||||
|  | import { Box } from '@strapi/parts/Box'; | ||||||
|  | import { Stack } from '@strapi/parts/Stack'; | ||||||
|  | import { H3 } from '@strapi/parts/Text'; | ||||||
|  | import { ToggleInput } from '@strapi/parts/ToggleInput'; | ||||||
|  | import { TextInput } from '@strapi/parts/TextInput'; | ||||||
|  | import { Grid, GridItem } from '@strapi/parts/Grid'; | ||||||
|  | 
 | ||||||
|  | // Strapi Icons
 | ||||||
|  | import Show from '@strapi/icons/Show'; | ||||||
|  | import Hide from '@strapi/icons/Hide'; | ||||||
|  | import Check from '@strapi/icons/Check'; | ||||||
|  | 
 | ||||||
|  | import permissions from '../../permissions'; | ||||||
|  | import { getTrad } from '../../utils'; | ||||||
|  | import useReactQuery from '../utils/useReactQuery'; | ||||||
|  | import FieldActionWrapper from '../../components/FieldActionWrapper'; | ||||||
|  | import schema from '../utils/schema'; | ||||||
|  | 
 | ||||||
|  | const SettingsPage = () => { | ||||||
|  |   const { formatMessage } = useIntl(); | ||||||
|  |   const { submitMutation, data, isLoading } = useReactQuery(); | ||||||
|  |   const [passwordShown, setPasswordShown] = useState(false); | ||||||
|  | 
 | ||||||
|  |   const handleUpdateSettingsSubmit = body => { | ||||||
|  |     submitMutation.mutate({ | ||||||
|  |       prefix: data?.prefix, | ||||||
|  |       body, | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <Main> | ||||||
|  |       {isLoading ? ( | ||||||
|  |         <LoadingIndicatorPage>Plugin settings are loading</LoadingIndicatorPage> | ||||||
|  |       ) : ( | ||||||
|  |         <Formik | ||||||
|  |           initialValues={{ | ||||||
|  |             restrictedAccess: data?.documentationAccess.restrictedAccess || false, | ||||||
|  |             password: data?.documentationAccess.password, | ||||||
|  |           }} | ||||||
|  |           onSubmit={handleUpdateSettingsSubmit} | ||||||
|  |           validationSchema={schema} | ||||||
|  |         > | ||||||
|  |           {({ handleSubmit, values, handleChange, errors }) => { | ||||||
|  |             return ( | ||||||
|  |               <Form noValidate onSubmit={handleSubmit}> | ||||||
|  |                 <HeaderLayout | ||||||
|  |                   title={formatMessage({ | ||||||
|  |                     id: getTrad('plugin.name'), | ||||||
|  |                     defaultMessage: 'Documentation', | ||||||
|  |                   })} | ||||||
|  |                   subtitle={formatMessage({ | ||||||
|  |                     id: getTrad('pages.SettingsPage.header.description'), | ||||||
|  |                     defaultMessage: 'Configure the documentation plugin', | ||||||
|  |                   })} | ||||||
|  |                   primaryAction={ | ||||||
|  |                     //  eslint-disable-next-line
 | ||||||
|  |                     <CheckPermissions permissions={permissions.update}> | ||||||
|  |                       <Button type="submit" startIcon={<Check />}> | ||||||
|  |                         {formatMessage({ | ||||||
|  |                           id: getTrad('pages.SettingsPage.Button.save'), | ||||||
|  |                           defaultMessage: 'Save', | ||||||
|  |                         })} | ||||||
|  |                       </Button> | ||||||
|  |                     </CheckPermissions> | ||||||
|  |                   } | ||||||
|  |                 /> | ||||||
|  |                 <ContentLayout> | ||||||
|  |                   <Box | ||||||
|  |                     background="neutral0" | ||||||
|  |                     hasRadius | ||||||
|  |                     shadow="filterShadow" | ||||||
|  |                     paddingTop={6} | ||||||
|  |                     paddingBottom={6} | ||||||
|  |                     paddingLeft={7} | ||||||
|  |                     paddingRight={7} | ||||||
|  |                   > | ||||||
|  |                     <Stack size={4}> | ||||||
|  |                       <H3 as="h2"> | ||||||
|  |                         {formatMessage({ | ||||||
|  |                           id: getTrad('pages.SettingsPage.title'), | ||||||
|  |                           defaultMessage: 'Settings', | ||||||
|  |                         })} | ||||||
|  |                       </H3> | ||||||
|  |                       <Grid gap={4}> | ||||||
|  |                         <GridItem col={6} s={12}> | ||||||
|  |                           <ToggleInput | ||||||
|  |                             name="restrictedAccess" | ||||||
|  |                             label={formatMessage({ | ||||||
|  |                               id: getTrad('pages.SettingsPage.toggle.label'), | ||||||
|  |                               defaultMessage: 'Restricted Access', | ||||||
|  |                             })} | ||||||
|  |                             hint={formatMessage({ | ||||||
|  |                               id: getTrad('pages.SettingsPage.toggle.hint'), | ||||||
|  |                               defaultMessage: 'Make the documentation endpoint private', | ||||||
|  |                             })} | ||||||
|  |                             checked={values.restrictedAccess} | ||||||
|  |                             onChange={handleChange} | ||||||
|  |                             onLabel="On" | ||||||
|  |                             offLabel="Off" | ||||||
|  |                           /> | ||||||
|  |                         </GridItem> | ||||||
|  |                         {values.restrictedAccess && ( | ||||||
|  |                           <GridItem col={6} s={12}> | ||||||
|  |                             <TextInput | ||||||
|  |                               label={formatMessage({ | ||||||
|  |                                 id: getTrad('pages.SettingsPage.password.label'), | ||||||
|  |                                 defaultMessage: 'Password', | ||||||
|  |                               })} | ||||||
|  |                               name="password" | ||||||
|  |                               type={passwordShown ? 'text' : 'password'} | ||||||
|  |                               value={values.password} | ||||||
|  |                               onChange={handleChange} | ||||||
|  |                               error={ | ||||||
|  |                                 errors.password | ||||||
|  |                                   ? formatMessage({ | ||||||
|  |                                       id: errors.password, | ||||||
|  |                                       defaultMessage: 'Invalid value', | ||||||
|  |                                     }) | ||||||
|  |                                   : null | ||||||
|  |                               } | ||||||
|  |                               endAction={ | ||||||
|  |                                 // eslint-disable-next-line
 | ||||||
|  |                                 <FieldActionWrapper | ||||||
|  |                                   onClick={e => { | ||||||
|  |                                     e.stopPropagation(); | ||||||
|  |                                     setPasswordShown(prev => !prev); | ||||||
|  |                                   }} | ||||||
|  |                                   label={formatMessage( | ||||||
|  |                                     passwordShown | ||||||
|  |                                       ? { | ||||||
|  |                                           id: 'Auth.form.password.show-password', | ||||||
|  |                                           defaultMessage: 'Show password', | ||||||
|  |                                         } | ||||||
|  |                                       : { | ||||||
|  |                                           id: 'Auth.form.password.hide-password', | ||||||
|  |                                           defaultMessage: 'Hide password', | ||||||
|  |                                         } | ||||||
|  |                                   )} | ||||||
|  |                                 > | ||||||
|  |                                   {passwordShown ? <Show /> : <Hide />} | ||||||
|  |                                 </FieldActionWrapper> | ||||||
|  |                               } | ||||||
|  |                             /> | ||||||
|  |                           </GridItem> | ||||||
|  |                         )} | ||||||
|  |                       </Grid> | ||||||
|  |                     </Stack> | ||||||
|  |                   </Box> | ||||||
|  |                 </ContentLayout> | ||||||
|  |               </Form> | ||||||
|  |             ); | ||||||
|  |           }} | ||||||
|  |         </Formik> | ||||||
|  |       )} | ||||||
|  |     </Main> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default SettingsPage; | ||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -0,0 +1,18 @@ | |||||||
|  | import { setupServer } from 'msw/node'; | ||||||
|  | import { rest } from 'msw'; | ||||||
|  | 
 | ||||||
|  | const handlers = [ | ||||||
|  |   rest.get('*/getInfos', (req, res, ctx) => { | ||||||
|  |     return res( | ||||||
|  |       ctx.delay(1000), | ||||||
|  |       ctx.status(200), | ||||||
|  |       ctx.json({ | ||||||
|  |         documentationAccess: { restrictedAccess: false, password: '' }, | ||||||
|  |       }) | ||||||
|  |     ); | ||||||
|  |   }), | ||||||
|  | ]; | ||||||
|  | 
 | ||||||
|  | const server = setupServer(...handlers); | ||||||
|  | 
 | ||||||
|  | export default server; | ||||||
							
								
								
									
										31
									
								
								packages/plugins/documentation/admin/src/pages/utils/api.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								packages/plugins/documentation/admin/src/pages/utils/api.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | |||||||
|  | import { request } from '@strapi/helper-plugin'; | ||||||
|  | import pluginId from '../../pluginId'; | ||||||
|  | 
 | ||||||
|  | const deleteDoc = ({ prefix, version }) => { | ||||||
|  |   return request(`${prefix}/deleteDoc/${version}`, { method: 'DELETE' }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const fetchDocumentationVersions = async toggleNotification => { | ||||||
|  |   try { | ||||||
|  |     const data = await request(`/${pluginId}/getInfos`, { method: 'GET' }); | ||||||
|  | 
 | ||||||
|  |     return data; | ||||||
|  |   } catch (err) { | ||||||
|  |     toggleNotification({ | ||||||
|  |       type: 'warning', | ||||||
|  |       message: { id: 'notification.error' }, | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     // FIXME
 | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const regenerateDoc = ({ prefix, version }) => { | ||||||
|  |   return request(`${prefix}/regenerateDoc`, { method: 'POST', body: { version } }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const updateSettings = ({ prefix, body }) => | ||||||
|  |   request(`${prefix}/updateSettings`, { method: 'PUT', body }); | ||||||
|  | 
 | ||||||
|  | export { deleteDoc, fetchDocumentationVersions, regenerateDoc, updateSettings }; | ||||||
| @ -0,0 +1,11 @@ | |||||||
|  | import { translatedErrors } from '@strapi/helper-plugin'; | ||||||
|  | import * as yup from 'yup'; | ||||||
|  | 
 | ||||||
|  | const schema = yup.object().shape({ | ||||||
|  |   restrictedAccess: yup.boolean(), | ||||||
|  |   password: yup.string().when('restrictedAccess', (value, initSchema) => { | ||||||
|  |     return value ? initSchema.required(translatedErrors.required) : initSchema; | ||||||
|  |   }), | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | export default schema; | ||||||
| @ -0,0 +1,46 @@ | |||||||
|  | import { useQuery, useMutation, useQueryClient } from 'react-query'; | ||||||
|  | import { useNotification } from '@strapi/helper-plugin'; | ||||||
|  | import { fetchDocumentationVersions, deleteDoc, regenerateDoc, updateSettings } from './api'; | ||||||
|  | import getTrad from '../../utils/getTrad'; | ||||||
|  | 
 | ||||||
|  | const useReactQuery = () => { | ||||||
|  |   const queryClient = useQueryClient(); | ||||||
|  |   const toggleNotification = useNotification(); | ||||||
|  |   const { isLoading, data } = useQuery('get-documentation', () => | ||||||
|  |     fetchDocumentationVersions(toggleNotification) | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   const handleError = err => { | ||||||
|  |     toggleNotification({ | ||||||
|  |       type: 'warning', | ||||||
|  |       message: err.response.payload.message, | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const handleSuccess = (type, tradId) => { | ||||||
|  |     queryClient.invalidateQueries('get-documentation'); | ||||||
|  |     toggleNotification({ | ||||||
|  |       type, | ||||||
|  |       message: { id: getTrad(tradId) }, | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const deleteMutation = useMutation(deleteDoc, { | ||||||
|  |     onSuccess: () => handleSuccess('info', 'notification.delete.success'), | ||||||
|  |     onError: error => handleError(error), | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   const submitMutation = useMutation(updateSettings, { | ||||||
|  |     onSuccess: () => handleSuccess('success', 'notification.update.success'), | ||||||
|  |     onError: handleError, | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   const regenerateDocMutation = useMutation(regenerateDoc, { | ||||||
|  |     onSuccess: () => handleSuccess('info', 'notification.generate.success'), | ||||||
|  |     onError: error => handleError(error), | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   return { data, isLoading, deleteMutation, submitMutation, regenerateDocMutation }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default useReactQuery; | ||||||
| @ -1,12 +1,9 @@ | |||||||
| { | { | ||||||
|   "coming-soon": "This content is currently under construction and will be back in a few weeks!", |   "coming-soon": "This content is currently under construction and will be back in a few weeks!", | ||||||
|   "components.Row.generatedDate": "Last generation", |  | ||||||
|   "components.Row.open": "Open", |   "components.Row.open": "Open", | ||||||
|   "components.Row.regenerate": "Regenerate", |   "components.Row.regenerate": "Regenerate", | ||||||
|   "containers.HomePage.Block.title": "Versions", |   "containers.HomePage.Block.title": "Versions", | ||||||
|   "containers.HomePage.Button.open": "Open the documentation", |  | ||||||
|   "containers.HomePage.Button.update": "Update", |   "containers.HomePage.Button.update": "Update", | ||||||
|   "containers.HomePage.PluginHeader.description": "Configure the documentation plugin", |  | ||||||
|   "containers.HomePage.PluginHeader.title": "Documentation - Settings", |   "containers.HomePage.PluginHeader.title": "Documentation - Settings", | ||||||
|   "containers.HomePage.PopUpWarning.confirm": "I understand", |   "containers.HomePage.PopUpWarning.confirm": "I understand", | ||||||
|   "containers.HomePage.PopUpWarning.message": "Are you sure you want to delete this version?", |   "containers.HomePage.PopUpWarning.message": "Are you sure you want to delete this version?", | ||||||
| @ -26,6 +23,19 @@ | |||||||
|   "notification.delete.success": "Doc deleted", |   "notification.delete.success": "Doc deleted", | ||||||
|   "notification.generate.success": "Doc generated", |   "notification.generate.success": "Doc generated", | ||||||
|   "notification.update.success": "Settings updated successfully", |   "notification.update.success": "Settings updated successfully", | ||||||
|  |   "pages.PluginPage.Button.open": "Open documentation", | ||||||
|  |   "pages.PluginPage.header.description": "Configure the documentation plugin", | ||||||
|  |   "pages.PluginPage.table.generated": "Last generated", | ||||||
|  |   "pages.PluginPage.table.icon.delete": "Delete {target}", | ||||||
|  |   "pages.PluginPage.table.icon.regnerate": "Regenerate {target}", | ||||||
|  |   "pages.PluginPage.table.icon.show": "Open {target}", | ||||||
|  |   "pages.PluginPage.table.version": "Version", | ||||||
|  |   "pages.SettingPage.title": "Settings", | ||||||
|  |   "pages.SettingsPage.Button.description": "Configure the documentation plugin", | ||||||
|  |   "pages.SettingsPage.header.save": "Save", | ||||||
|  |   "pages.SettingsPage.password.label": "Password", | ||||||
|  |   "pages.SettingsPage.toggle.label": "Restricted Access", | ||||||
|  |   "pages.SettingsPage.toggle.hint": "Make the documentation endpoint private", | ||||||
|   "plugin.description.long": "Create an OpenAPI Document and visualize your API with SWAGGER UI.", |   "plugin.description.long": "Create an OpenAPI Document and visualize your API with SWAGGER UI.", | ||||||
|   "plugin.description.short": "Create an OpenAPI Document and visualize your API with SWAGGER UI.", |   "plugin.description.short": "Create an OpenAPI Document and visualize your API with SWAGGER UI.", | ||||||
|   "plugin.name": "Documentation" |   "plugin.name": "Documentation" | ||||||
|  | |||||||
| @ -20,6 +20,8 @@ | |||||||
|     "lodash": "4.17.21", |     "lodash": "4.17.21", | ||||||
|     "moment": "^2.29.1", |     "moment": "^2.29.1", | ||||||
|     "path-to-regexp": "6.2.0", |     "path-to-regexp": "6.2.0", | ||||||
|  |     "pluralize": "8.0.0", | ||||||
|  |     "koa-session": "6.2.0", | ||||||
|     "react": "^17.0.2", |     "react": "^17.0.2", | ||||||
|     "react-copy-to-clipboard": "^5.0.3", |     "react-copy-to-clipboard": "^5.0.3", | ||||||
|     "react-dom": "^17.0.2", |     "react-dom": "^17.0.2", | ||||||
|  | |||||||
| @ -1,8 +1,9 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
| 
 | 
 | ||||||
| const defaultConfig = require('./default-config'); | const defaultConfig = require('./default-config'); | ||||||
|  | const sessionConfig = require('./session-config'); | ||||||
| 
 | 
 | ||||||
| module.exports = { | module.exports = { | ||||||
|   default: defaultConfig, |   default: { ...defaultConfig, ...sessionConfig }, | ||||||
|   validator() {}, |   validator() {}, | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -0,0 +1,19 @@ | |||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | module.exports = { | ||||||
|  |   session: { | ||||||
|  |     client: 'cookie', | ||||||
|  |     key: 'strapi.sid', | ||||||
|  |     prefix: 'strapi:sess:', | ||||||
|  |     ttl: 864000000, | ||||||
|  |     rolling: false, | ||||||
|  |     secretKeys: ['mySecretKey1', 'mySecretKey2'], | ||||||
|  |     cookie: { | ||||||
|  |       path: '/', | ||||||
|  |       httpOnly: true, | ||||||
|  |       maxAge: 864000000, | ||||||
|  |       rewrite: true, | ||||||
|  |       signed: false, | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | }; | ||||||
| @ -42,7 +42,11 @@ module.exports = { | |||||||
|       const version = |       const version = | ||||||
|         major && minor && patch |         major && minor && patch | ||||||
|           ? `${major}.${minor}.${patch}` |           ? `${major}.${minor}.${patch}` | ||||||
|           : strapi.plugins.documentation.config.info.version; |           : strapi | ||||||
|  |               .plugin('documentation') | ||||||
|  |               .service('documentation') | ||||||
|  |               .getDocumentationVersion(); | ||||||
|  | 
 | ||||||
|       const openAPISpecsPath = path.join( |       const openAPISpecsPath = path.join( | ||||||
|         strapi.config.appPath, |         strapi.config.appPath, | ||||||
|         'src', |         'src', | ||||||
|  | |||||||
| @ -1,39 +1,15 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
| 
 | 
 | ||||||
| const path = require('path'); | const path = require('path'); | ||||||
| const _ = require('lodash'); |  | ||||||
| const koaStatic = require('koa-static'); | const koaStatic = require('koa-static'); | ||||||
| 
 | const session = require('koa-session'); | ||||||
| const initialRoutes = []; | const swaggerUi = require('swagger-ui-dist'); | ||||||
| 
 | 
 | ||||||
| // TODO: delete when refactoring documentation plugin for v4
 | // TODO: delete when refactoring documentation plugin for v4
 | ||||||
| module.exports = async ({ strapi }) => { | module.exports = async ({ strapi }) => { | ||||||
|   // strapi.config.middleware.load.before.push('documentation');
 |   const sessionConfig = strapi.config.get('plugin.documentation').session; | ||||||
| 
 |   strapi.server.app.keys = sessionConfig.secretKeys; | ||||||
|   initialRoutes.push(..._.cloneDeep(strapi.plugins.documentation.routes)); |   strapi.server.app.use(session(sessionConfig, strapi.server.app)); | ||||||
| 
 |  | ||||||
|   const swaggerUi = require('swagger-ui-dist'); |  | ||||||
| 
 |  | ||||||
|   // Find the plugins routes.
 |  | ||||||
|   strapi.plugins.documentation.routes = strapi.plugins.documentation.routes.map((route, index) => { |  | ||||||
|     if (route.handler === 'Documentation.getInfos') { |  | ||||||
|       return route; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (route.handler === 'Documentation.index' || route.path === '/login') { |  | ||||||
|       route.config.policies = initialRoutes[index].config.policies; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Set prefix to empty to be able to customise it.
 |  | ||||||
|     if (strapi.config.has('plugins.documentation.x-strapi-config.path')) { |  | ||||||
|       route.config.prefix = ''; |  | ||||||
|       route.path = `/${strapi.config.get('plugin.documentation.x-strapi-config').path}${ |  | ||||||
|         route.path |  | ||||||
|       }`.replace('//', '/');
 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return route; |  | ||||||
|   }); |  | ||||||
| 
 | 
 | ||||||
|   strapi.server.routes([ |   strapi.server.routes([ | ||||||
|     { |     { | ||||||
| @ -43,7 +19,7 @@ module.exports = async ({ strapi }) => { | |||||||
|         ctx.url = path.basename(ctx.url); |         ctx.url = path.basename(ctx.url); | ||||||
| 
 | 
 | ||||||
|         return koaStatic(swaggerUi.getAbsoluteFSPath(), { |         return koaStatic(swaggerUi.getAbsoluteFSPath(), { | ||||||
|           maxage: 6000, |           maxage: sessionConfig.cookie.maxAge, | ||||||
|           defer: true, |           defer: true, | ||||||
|         })(ctx, next); |         })(ctx, next); | ||||||
|       }, |       }, | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
|  | const restrictAccess = require('../middlewares/restrict-access'); | ||||||
| 
 | 
 | ||||||
| module.exports = [ | module.exports = [ | ||||||
|   { |   { | ||||||
| @ -7,10 +8,15 @@ module.exports = [ | |||||||
|     handler: 'documentation.index', |     handler: 'documentation.index', | ||||||
|     config: { |     config: { | ||||||
|       auth: false, |       auth: false, | ||||||
|       // middlewares: [restrictAccess],
 |       middlewares: [restrictAccess], | ||||||
|       // policies: [
 |       policies: [ | ||||||
|       //   { name: 'admin::hasPermissions', options: { actions: ['plugin::documentation.read'] } },
 |         { | ||||||
|       // ],
 |           name: 'admin::hasPermissions', | ||||||
|  |           config: { | ||||||
|  |             actions: ['plugin::documentation.read'], | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |       ], | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
| @ -19,10 +25,15 @@ module.exports = [ | |||||||
|     handler: 'documentation.index', |     handler: 'documentation.index', | ||||||
|     config: { |     config: { | ||||||
|       auth: false, |       auth: false, | ||||||
|       // middlewares: [restrictAccess],
 |       middlewares: [restrictAccess], | ||||||
|       // policies: [
 |       policies: [ | ||||||
|       //   { name: 'admin::hasPermissions', options: { actions: ['plugin::documentation.read'] } },
 |         { | ||||||
|       // ],
 |           name: 'admin::hasPermissions', | ||||||
|  |           config: { | ||||||
|  |             actions: ['plugin::documentation.read'], | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |       ], | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
| @ -30,8 +41,14 @@ module.exports = [ | |||||||
|     path: '/login', |     path: '/login', | ||||||
|     handler: 'documentation.loginView', |     handler: 'documentation.loginView', | ||||||
|     config: { |     config: { | ||||||
|  |       auth: false, | ||||||
|       policies: [ |       policies: [ | ||||||
|         { name: 'admin::hasPermissions', config: { actions: ['plugin::documentation.read'] } }, |         { | ||||||
|  |           name: 'admin::hasPermissions', | ||||||
|  |           config: { | ||||||
|  |             actions: ['plugin::documentation.read'], | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|       ], |       ], | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
| @ -40,6 +57,7 @@ module.exports = [ | |||||||
|     path: '/login', |     path: '/login', | ||||||
|     handler: 'documentation.login', |     handler: 'documentation.login', | ||||||
|     config: { |     config: { | ||||||
|  |       auth: false, | ||||||
|       policies: [ |       policies: [ | ||||||
|         { name: 'admin::hasPermissions', config: { actions: ['plugin::documentation.read'] } }, |         { name: 'admin::hasPermissions', config: { actions: ['plugin::documentation.read'] } }, | ||||||
|       ], |       ], | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 markkaylor
						markkaylor