From 04e97cc67e4dff43772f92c28f985fe4df19c9f4 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 12 Jun 2022 11:36:55 -0400 Subject: [PATCH] Make it easier for plugins to extend the navigation and add routes --- .../components/elements/PermissionRoute.tsx | 30 ++++ .../server/ConflictStateRenderer.tsx | 33 ++++ .../server/files/FileEditContainer.tsx | 7 +- resources/scripts/routers/ServerRouter.tsx | 143 ++++-------------- resources/scripts/routers/routes.ts | 101 +++++++++++++ 5 files changed, 198 insertions(+), 116 deletions(-) create mode 100644 resources/scripts/components/elements/PermissionRoute.tsx create mode 100644 resources/scripts/components/server/ConflictStateRenderer.tsx create mode 100644 resources/scripts/routers/routes.ts diff --git a/resources/scripts/components/elements/PermissionRoute.tsx b/resources/scripts/components/elements/PermissionRoute.tsx new file mode 100644 index 000000000..d00bc9711 --- /dev/null +++ b/resources/scripts/components/elements/PermissionRoute.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Route } from 'react-router-dom'; +import { RouteProps } from 'react-router'; +import Can from '@/components/elements/Can'; +import { ServerError } from '@/components/elements/ScreenBlock'; + +interface Props extends Omit { + path: string; + permission: string | string[] | null; +} + +export default ({ permission, children, ...props }: Props) => ( + + {!permission ? + children + : + + } + > + {children} + + } + +); diff --git a/resources/scripts/components/server/ConflictStateRenderer.tsx b/resources/scripts/components/server/ConflictStateRenderer.tsx new file mode 100644 index 000000000..c08c594a4 --- /dev/null +++ b/resources/scripts/components/server/ConflictStateRenderer.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { ServerContext } from '@/state/server'; +import ScreenBlock from '@/components/elements/ScreenBlock'; +import ServerInstallSvg from '@/assets/images/server_installing.svg'; +import ServerErrorSvg from '@/assets/images/server_error.svg'; +import ServerRestoreSvg from '@/assets/images/server_restore.svg'; + +export default () => { + const status = ServerContext.useStoreState(state => state.server.data?.status || null); + const isTransferring = ServerContext.useStoreState(state => state.server.data?.isTransferring || false); + + return ( + status === 'installing' || status === 'install_failed' ? + + : + status === 'suspended' ? + + : + + ); +}; diff --git a/resources/scripts/components/server/files/FileEditContainer.tsx b/resources/scripts/components/server/files/FileEditContainer.tsx index 8a7feb9a3..f5444fe24 100644 --- a/resources/scripts/components/server/files/FileEditContainer.tsx +++ b/resources/scripts/components/server/files/FileEditContainer.tsx @@ -1,4 +1,4 @@ -import React, { lazy, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import getFileContents from '@/api/server/files/getFileContents'; import { httpErrorToHuman } from '@/api/http'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; @@ -19,8 +19,7 @@ import { ServerContext } from '@/state/server'; import ErrorBoundary from '@/components/elements/ErrorBoundary'; import { encodePathSegments, hashToPath } from '@/helpers'; import { dirname } from 'path'; - -const LazyCodemirrorEditor = lazy(() => import(/* webpackChunkName: "editor" */'@/components/elements/CodemirrorEditor')); +import CodemirrorEditor from '@/components/elements/CodemirrorEditor'; export default () => { const [ error, setError ] = useState(''); @@ -116,7 +115,7 @@ export default () => { />
- { - const status = ServerContext.useStoreState(state => state.server.data?.status || null); - const isTransferring = ServerContext.useStoreState(state => state.server.data?.isTransferring || false); - - return ( - status === 'installing' || status === 'install_failed' ? - - : - status === 'suspended' ? - - : - - ); -}; +import ConflictStateRenderer from '@/components/server/ConflictStateRenderer'; +import PermissionRoute from '@/components/elements/PermissionRoute'; +import routes from '@/routers/routes'; export default () => { const match = useRouteMatch<{ id: string }>(); @@ -74,6 +35,10 @@ export default () => { const getServer = ServerContext.useStoreActions(actions => actions.server.getServer); const clearServerState = ServerContext.useStoreActions(actions => actions.clearServerState); + const to = (value: string, url = false) => { + return `${(url ? match.url : match.path).replace(/\/*$/, '')}/${value.replace(/^\/+/, '')}`; + }; + useEffect(() => () => { clearServerState(); }, []); @@ -105,35 +70,23 @@ export default () => {
- Console - - File Manager - - - Databases - - - Schedules - - - Users - - - Backups - - - Network - - - Startup - - - Settings - + {routes.server.filter(route => !!route.name).map((route) => ( + route.permission ? + + + {route.name} + + + : + + {route.name} + + ))} {rootAdmin && - - - + // eslint-disable-next-line react/jsx-no-target-blank + + + }
@@ -147,47 +100,13 @@ export default () => { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + {routes.server.map(({ path, permission, component: Component }) => ( + + + + + + ))} diff --git a/resources/scripts/routers/routes.ts b/resources/scripts/routers/routes.ts new file mode 100644 index 000000000..f03774ae7 --- /dev/null +++ b/resources/scripts/routers/routes.ts @@ -0,0 +1,101 @@ +import React, { lazy } from 'react'; +import ServerConsole from '@/components/server/ServerConsole'; +import DatabasesContainer from '@/components/server/databases/DatabasesContainer'; +import ScheduleContainer from '@/components/server/schedules/ScheduleContainer'; +import UsersContainer from '@/components/server/users/UsersContainer'; +import BackupContainer from '@/components/server/backups/BackupContainer'; +import NetworkContainer from '@/components/server/network/NetworkContainer'; +import StartupContainer from '@/components/server/startup/StartupContainer'; + +const FileManagerContainer = lazy(() => import('@/components/server/files/FileManagerContainer')); +const FileEditContainer = lazy(() => import('@/components/server/files/FileEditContainer')); +const ScheduleEditContainer = lazy(() => import('@/components/server/schedules/ScheduleEditContainer')); +const SettingsContainer = lazy(() => import('@/components/server/settings/SettingsContainer')); + +interface ServerRouteDefinition { + path: string; + permission: string | string[] | null; + // If undefined is passed this route is still rendered into the router itself + // but no navigation link is displayed in the sub-navigation menu. + name: string | undefined; + component: React.ComponentType; + // The default for "exact" is assumed to be "true" unless you explicitly + // pass it as false. + exact?: boolean; +} + +interface Routes { + server: ServerRouteDefinition[]; +} + +export default { + server: [ + { + path: '/', + permission: null, + name: 'Console', + component: ServerConsole, + exact: true, + }, + { + path: '/files', + permission: 'file.*', + name: 'Files', + component: FileManagerContainer, + }, + { + path: '/files/:action(edit|new)', + permission: 'file.*', + name: undefined, + component: FileEditContainer, + }, + { + path: '/databases', + permission: 'database.*', + name: 'Databases', + component: DatabasesContainer, + }, + { + path: '/schedules', + permission: 'schedule.*', + name: 'Schedules', + component: ScheduleContainer, + }, + { + path: '/schedules/:id', + permission: 'schedule.*', + name: undefined, + component: ScheduleEditContainer, + }, + { + path: '/users', + permission: 'user.*', + name: 'Users', + component: UsersContainer, + }, + { + path: '/backups', + permission: 'backup.*', + name: 'Backups', + component: BackupContainer, + }, + { + path: '/network', + permission: 'allocation.*', + name: 'Network', + component: NetworkContainer, + }, + { + path: '/startup', + permission: 'startup.*', + name: 'Startup', + component: StartupContainer, + }, + { + path: '/settings', + permission: [ 'settings.*', 'file.sftp' ], + name: 'Settings', + component: SettingsContainer, + }, + ], +} as Routes;