Merge branch 'develop' into pagetitles2
This commit is contained in:
commit
d604a4a5f2
29 changed files with 215 additions and 82 deletions
|
@ -2,7 +2,7 @@ import http from '@/api/http';
|
|||
import { rawDataToFileObject } from '@/api/transformers';
|
||||
|
||||
export interface FileObject {
|
||||
uuid: string;
|
||||
key: string;
|
||||
name: string;
|
||||
mode: string;
|
||||
size: number;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { Allocation } from '@/api/server/getServer';
|
||||
import { FractalResponseData } from '@/api/http';
|
||||
import { FileObject } from '@/api/server/files/loadDirectory';
|
||||
import v4 from 'uuid/v4';
|
||||
|
||||
export const rawDataToServerAllocation = (data: FractalResponseData): Allocation => ({
|
||||
id: data.attributes.id,
|
||||
|
@ -13,7 +12,7 @@ export const rawDataToServerAllocation = (data: FractalResponseData): Allocation
|
|||
});
|
||||
|
||||
export const rawDataToFileObject = (data: FractalResponseData): FileObject => ({
|
||||
uuid: v4(),
|
||||
key: `${data.attributes.is_file ? 'file' : 'dir'}_${data.attributes.name}`,
|
||||
name: data.attributes.name,
|
||||
mode: data.attributes.mode,
|
||||
size: Number(data.attributes.size),
|
||||
|
|
|
@ -6,19 +6,19 @@ export default createGlobalStyle`
|
|||
${tw`font-sans bg-neutral-800 text-neutral-200`};
|
||||
letter-spacing: 0.015em;
|
||||
}
|
||||
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
${tw`font-medium tracking-normal font-header`};
|
||||
}
|
||||
|
||||
|
||||
p {
|
||||
${tw`text-neutral-200 leading-snug font-sans`};
|
||||
}
|
||||
|
||||
|
||||
form {
|
||||
${tw`m-0`};
|
||||
}
|
||||
|
||||
|
||||
textarea, select, input, button, button:focus, button:focus-visible {
|
||||
${tw`outline-none`};
|
||||
}
|
||||
|
@ -32,4 +32,41 @@ export default createGlobalStyle`
|
|||
input[type=number] {
|
||||
-moz-appearance: textfield !important;
|
||||
}
|
||||
|
||||
/* Scroll Bar Style */
|
||||
::-webkit-scrollbar {
|
||||
background: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
border: solid 0 rgb(0 0 0 / 0%);
|
||||
border-right-width: 4px;
|
||||
border-left-width: 4px;
|
||||
-webkit-border-radius: 9px 4px;
|
||||
-webkit-box-shadow: inset 0 0 0 1px hsl(211, 10%, 53%), inset 0 0 0 4px hsl(209deg 18% 30%);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track-piece {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:horizontal {
|
||||
border-right-width: 0;
|
||||
border-left-width: 0;
|
||||
border-top-width: 4px;
|
||||
border-bottom-width: 4px;
|
||||
-webkit-border-radius: 4px 9px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
-webkit-box-shadow:
|
||||
inset 0 0 0 1px hsl(212, 92%, 43%),
|
||||
inset 0 0 0 4px hsl(212, 92%, 43%);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import * as React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import ReactGA from 'react-ga';
|
||||
import { hot } from 'react-hot-loader/root';
|
||||
import { BrowserRouter, Route, Switch } from 'react-router-dom';
|
||||
import { StoreProvider } from 'easy-peasy';
|
||||
|
@ -48,6 +49,11 @@ const App = () => {
|
|||
store.getActions().settings.setSettings(SiteConfiguration!);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
ReactGA.initialize(SiteConfiguration!.analytics);
|
||||
ReactGA.pageview(location.pathname);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<GlobalStylesheet/>
|
||||
|
|
26
resources/scripts/components/server/InstallListener.tsx
Normal file
26
resources/scripts/components/server/InstallListener.tsx
Normal file
|
@ -0,0 +1,26 @@
|
|||
import useWebsocketEvent from '@/plugins/useWebsocketEvent';
|
||||
import { ServerContext } from '@/state/server';
|
||||
import useServer from '@/plugins/useServer';
|
||||
|
||||
const InstallListener = () => {
|
||||
const server = useServer();
|
||||
const getServer = ServerContext.useStoreActions(actions => actions.server.getServer);
|
||||
const setServer = ServerContext.useStoreActions(actions => actions.server.setServer);
|
||||
|
||||
// Listen for the installation completion event and then fire off a request to fetch the updated
|
||||
// server information. This allows the server to automatically become available to the user if they
|
||||
// just sit on the page.
|
||||
useWebsocketEvent('install completed', () => {
|
||||
getServer(server.uuid).catch(error => console.error(error));
|
||||
});
|
||||
|
||||
// When we see the install started event immediately update the state to indicate such so that the
|
||||
// screens automatically update.
|
||||
useWebsocketEvent('install started', () => {
|
||||
setServer({ ...server, isInstalling: true });
|
||||
});
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default InstallListener;
|
|
@ -57,7 +57,7 @@ export default () => {
|
|||
</div>
|
||||
}
|
||||
{featureLimits.backups === 0 &&
|
||||
<p className="text-center text-sm text-neutral-400">
|
||||
<p css={tw`text-center text-sm text-neutral-400`}>
|
||||
Backups cannot be created for this server.
|
||||
</p>
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ const ModalContent = ({ ...props }: RequiredModalProps) => {
|
|||
</FormikFieldWrapper>
|
||||
</div>
|
||||
<div css={tw`flex justify-end`}>
|
||||
<Button type={'submit'}>
|
||||
<Button type={'submit'} disabled={isSubmitting}>
|
||||
Start backup
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -94,11 +94,7 @@ export default () => {
|
|||
ignored: string(),
|
||||
})}
|
||||
>
|
||||
<ModalContent
|
||||
appear
|
||||
visible={visible}
|
||||
onDismissed={() => setVisible(false)}
|
||||
/>
|
||||
<ModalContent appear visible={visible} onDismissed={() => setVisible(false)}/>
|
||||
</Formik>
|
||||
}
|
||||
<Button onClick={() => setVisible(true)}>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useRef, useState } from 'react';
|
||||
import React, { memo, useRef, useState } from 'react';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import {
|
||||
faBoxOpen,
|
||||
|
@ -29,6 +29,7 @@ import styled from 'styled-components/macro';
|
|||
import useEventListener from '@/plugins/useEventListener';
|
||||
import compressFiles from '@/api/server/files/compressFiles';
|
||||
import decompressFiles from '@/api/server/files/decompressFiles';
|
||||
import isEqual from 'react-fast-compare';
|
||||
|
||||
type ModalType = 'rename' | 'move';
|
||||
|
||||
|
@ -50,7 +51,7 @@ const Row = ({ icon, title, ...props }: RowProps) => (
|
|||
</StyledRow>
|
||||
);
|
||||
|
||||
export default ({ file }: { file: FileObject }) => {
|
||||
const FileDropdownMenu = ({ file }: { file: FileObject }) => {
|
||||
const onClickRef = useRef<DropdownMenu>(null);
|
||||
const [ showSpinner, setShowSpinner ] = useState(false);
|
||||
const [ modal, setModal ] = useState<ModalType | null>(null);
|
||||
|
@ -60,7 +61,7 @@ export default ({ file }: { file: FileObject }) => {
|
|||
const { clearAndAddHttpError, clearFlashes } = useFlash();
|
||||
const directory = ServerContext.useStoreState(state => state.files.directory);
|
||||
|
||||
useEventListener(`pterodactyl:files:ctx:${file.uuid}`, (e: CustomEvent) => {
|
||||
useEventListener(`pterodactyl:files:ctx:${file.key}`, (e: CustomEvent) => {
|
||||
if (onClickRef.current) {
|
||||
onClickRef.current.triggerMenu(e.detail);
|
||||
}
|
||||
|
@ -71,7 +72,7 @@ export default ({ file }: { file: FileObject }) => {
|
|||
|
||||
// For UI speed, immediately remove the file from the listing before calling the deletion function.
|
||||
// If the delete actually fails, we'll fetch the current directory contents again automatically.
|
||||
mutate(files => files.filter(f => f.uuid !== file.uuid), false);
|
||||
mutate(files => files.filter(f => f.key !== file.key), false);
|
||||
|
||||
deleteFiles(uuid, directory, [ file.name ]).catch(error => {
|
||||
mutate();
|
||||
|
@ -166,3 +167,5 @@ export default ({ file }: { file: FileObject }) => {
|
|||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(FileDropdownMenu, isEqual);
|
||||
|
|
|
@ -71,7 +71,7 @@ export default () => {
|
|||
}
|
||||
{
|
||||
sortFiles(files.slice(0, 250)).map(file => (
|
||||
<FileObjectRow key={file.uuid} file={file}/>
|
||||
<FileObjectRow key={file.key} file={file}/>
|
||||
))
|
||||
}
|
||||
<MassActionsBar/>
|
||||
|
|
|
@ -39,7 +39,7 @@ const FileObjectRow = ({ file }: { file: FileObject }) => {
|
|||
key={file.name}
|
||||
onContextMenu={e => {
|
||||
e.preventDefault();
|
||||
window.dispatchEvent(new CustomEvent(`pterodactyl:files:ctx:${file.uuid}`, { detail: e.clientX }));
|
||||
window.dispatchEvent(new CustomEvent(`pterodactyl:files:ctx:${file.key}`, { detail: e.clientX }));
|
||||
}}
|
||||
>
|
||||
<SelectFileCheckbox name={file.name}/>
|
||||
|
|
|
@ -6,7 +6,6 @@ import Field from '@/components/elements/Field';
|
|||
import { join } from 'path';
|
||||
import { object, string } from 'yup';
|
||||
import createDirectory from '@/api/server/files/createDirectory';
|
||||
import v4 from 'uuid/v4';
|
||||
import tw from 'twin.macro';
|
||||
import Button from '@/components/elements/Button';
|
||||
import { mutate } from 'swr';
|
||||
|
@ -24,7 +23,7 @@ const schema = object().shape({
|
|||
});
|
||||
|
||||
const generateDirectoryData = (name: string): FileObject => ({
|
||||
uuid: v4(),
|
||||
key: `dir_${name}`,
|
||||
name: name,
|
||||
mode: '0644',
|
||||
size: 0,
|
||||
|
|
|
@ -9,23 +9,22 @@ import tw from 'twin.macro';
|
|||
import Button from '@/components/elements/Button';
|
||||
import useServer from '@/plugins/useServer';
|
||||
import useFileManagerSwr from '@/plugins/useFileManagerSwr';
|
||||
import useFlash from '@/plugins/useFlash';
|
||||
import withFlash, { WithFlashProps } from '@/hoc/withFlash';
|
||||
|
||||
interface FormikValues {
|
||||
name: string;
|
||||
}
|
||||
|
||||
type Props = RequiredModalProps & { files: string[]; useMoveTerminology?: boolean };
|
||||
type OwnProps = WithFlashProps & RequiredModalProps & { files: string[]; useMoveTerminology?: boolean };
|
||||
|
||||
export default ({ files, useMoveTerminology, ...props }: Props) => {
|
||||
const RenameFileModal = ({ flash, files, useMoveTerminology, ...props }: OwnProps) => {
|
||||
const { uuid } = useServer();
|
||||
const { mutate } = useFileManagerSwr();
|
||||
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
||||
const directory = ServerContext.useStoreState(state => state.files.directory);
|
||||
const setSelectedFiles = ServerContext.useStoreActions(actions => actions.files.setSelectedFiles);
|
||||
|
||||
const submit = ({ name }: FormikValues, { setSubmitting }: FormikHelpers<FormikValues>) => {
|
||||
clearFlashes('files');
|
||||
flash.clearFlashes('files');
|
||||
|
||||
const len = name.split('/').length;
|
||||
if (files.length === 1) {
|
||||
|
@ -51,7 +50,7 @@ export default ({ files, useMoveTerminology, ...props }: Props) => {
|
|||
.catch(error => {
|
||||
mutate();
|
||||
setSubmitting(false);
|
||||
clearAndAddHttpError({ key: 'files', error });
|
||||
flash.clearAndAddHttpError({ key: 'files', error });
|
||||
})
|
||||
.then(() => props.onDismissed());
|
||||
};
|
||||
|
@ -96,3 +95,5 @@ export default ({ files, useMoveTerminology, ...props }: Props) => {
|
|||
</Formik>
|
||||
);
|
||||
};
|
||||
|
||||
export default withFlash(RenameFileModal);
|
||||
|
|
|
@ -65,7 +65,7 @@ const EditScheduleModal = ({ schedule, ...props }: Omit<Props, 'onScheduleUpdate
|
|||
/>
|
||||
</div>
|
||||
<div css={tw`mt-6 text-right`}>
|
||||
<Button type={'submit'}>
|
||||
<Button type={'submit'} disabled={isSubmitting}>
|
||||
{schedule ? 'Save changes' : 'Create schedule'}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
@ -14,7 +14,7 @@ export default ({ schedule }: { schedule: Schedule }) => (
|
|||
<p>{schedule.name}</p>
|
||||
<p css={tw`text-xs text-neutral-400`}>
|
||||
Last run
|
||||
at: {schedule.lastRunAt ? format(schedule.lastRunAt, 'MMM Do [at] h:mma') : 'never'}
|
||||
at: {schedule.lastRunAt ? format(schedule.lastRunAt, 'MMM do \'at\' h:mma') : 'never'}
|
||||
</p>
|
||||
</div>
|
||||
<div css={tw`flex items-center mx-8`}>
|
||||
|
|
|
@ -32,11 +32,16 @@ interface Values {
|
|||
}
|
||||
|
||||
const TaskDetailsForm = ({ isEditingTask }: { isEditingTask: boolean }) => {
|
||||
const { values: { action }, setFieldValue, setFieldTouched } = useFormikContext<Values>();
|
||||
const { values: { action }, initialValues, setFieldValue, setFieldTouched, isSubmitting } = useFormikContext<Values>();
|
||||
|
||||
useEffect(() => {
|
||||
setFieldValue('payload', action === 'power' ? 'start' : '');
|
||||
setFieldTouched('payload', false);
|
||||
if (action !== initialValues.action) {
|
||||
setFieldValue('payload', action === 'power' ? 'start' : '');
|
||||
setFieldTouched('payload', false);
|
||||
} else {
|
||||
setFieldValue('payload', initialValues.payload);
|
||||
setFieldTouched('payload', false);
|
||||
}
|
||||
}, [ action ]);
|
||||
|
||||
return (
|
||||
|
@ -94,7 +99,7 @@ const TaskDetailsForm = ({ isEditingTask }: { isEditingTask: boolean }) => {
|
|||
/>
|
||||
</div>
|
||||
<div css={tw`flex justify-end mt-6`}>
|
||||
<Button type={'submit'}>
|
||||
<Button type={'submit'} disabled={isSubmitting}>
|
||||
{isEditingTask ? 'Save Changes' : 'Create Task'}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
23
resources/scripts/hoc/withFlash.tsx
Normal file
23
resources/scripts/hoc/withFlash.tsx
Normal file
|
@ -0,0 +1,23 @@
|
|||
import React from 'react';
|
||||
import useFlash from '@/plugins/useFlash';
|
||||
import { Actions } from 'easy-peasy';
|
||||
import { ApplicationStore } from '@/state';
|
||||
|
||||
export interface WithFlashProps {
|
||||
flash: Actions<ApplicationStore>['flashes'];
|
||||
}
|
||||
|
||||
function withFlash<TOwnProps> (Component: React.ComponentType<TOwnProps & WithFlashProps>): React.ComponentType<TOwnProps> {
|
||||
return (props: TOwnProps) => {
|
||||
const { addError, addFlash, clearFlashes, clearAndAddHttpError } = useFlash();
|
||||
|
||||
return (
|
||||
<Component
|
||||
flash={{ addError, addFlash, clearFlashes, clearAndAddHttpError }}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default withFlash;
|
|
@ -1,9 +1,8 @@
|
|||
import { DependencyList } from 'react';
|
||||
import { ServerContext } from '@/state/server';
|
||||
import { Server } from '@/api/server/getServer';
|
||||
|
||||
const useServer = (dependencies?: DependencyList): Server => {
|
||||
return ServerContext.useStoreState(state => state.server.data!, [ dependencies ]);
|
||||
const useServer = (dependencies?: any[] | undefined): Server => {
|
||||
return ServerContext.useStoreState(state => state.server.data!, dependencies);
|
||||
};
|
||||
|
||||
export default useServer;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import ReactGA from 'react-ga';
|
||||
import { Route, RouteComponentProps, Switch } from 'react-router-dom';
|
||||
import LoginContainer from '@/components/auth/LoginContainer';
|
||||
import ForgotPasswordContainer from '@/components/auth/ForgotPasswordContainer';
|
||||
|
@ -6,17 +7,23 @@ import ResetPasswordContainer from '@/components/auth/ResetPasswordContainer';
|
|||
import LoginCheckpointContainer from '@/components/auth/LoginCheckpointContainer';
|
||||
import NotFound from '@/components/screens/NotFound';
|
||||
|
||||
export default ({ location, history, match }: RouteComponentProps) => (
|
||||
<div className={'pt-8 xl:pt-32'}>
|
||||
<Switch location={location}>
|
||||
<Route path={`${match.path}/login`} component={LoginContainer} exact/>
|
||||
<Route path={`${match.path}/login/checkpoint`} component={LoginCheckpointContainer}/>
|
||||
<Route path={`${match.path}/password`} component={ForgotPasswordContainer} exact/>
|
||||
<Route path={`${match.path}/password/reset/:token`} component={ResetPasswordContainer}/>
|
||||
<Route path={`${match.path}/checkpoint`}/>
|
||||
<Route path={'*'}>
|
||||
<NotFound onBack={() => history.push('/auth/login')}/>
|
||||
</Route>
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
export default ({ location, history, match }: RouteComponentProps) => {
|
||||
useEffect(() => {
|
||||
ReactGA.pageview(location.pathname);
|
||||
}, [ location.pathname ]);
|
||||
|
||||
return (
|
||||
<div className={'pt-8 xl:pt-32'}>
|
||||
<Switch location={location}>
|
||||
<Route path={`${match.path}/login`} component={LoginContainer} exact/>
|
||||
<Route path={`${match.path}/login/checkpoint`} component={LoginCheckpointContainer}/>
|
||||
<Route path={`${match.path}/password`} component={ForgotPasswordContainer} exact/>
|
||||
<Route path={`${match.path}/password/reset/:token`} component={ResetPasswordContainer}/>
|
||||
<Route path={`${match.path}/checkpoint`} />
|
||||
<Route path={'*'}>
|
||||
<NotFound onBack={() => history.push('/auth/login')} />
|
||||
</Route>
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import * as React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import ReactGA from 'react-ga';
|
||||
import { NavLink, Route, RouteComponentProps, Switch } from 'react-router-dom';
|
||||
import AccountOverviewContainer from '@/components/dashboard/AccountOverviewContainer';
|
||||
import NavigationBar from '@/components/NavigationBar';
|
||||
|
@ -8,24 +9,30 @@ import NotFound from '@/components/screens/NotFound';
|
|||
import TransitionRouter from '@/TransitionRouter';
|
||||
import SubNavigation from '@/components/elements/SubNavigation';
|
||||
|
||||
export default ({ location }: RouteComponentProps) => (
|
||||
<>
|
||||
<NavigationBar/>
|
||||
{location.pathname.startsWith('/account') &&
|
||||
<SubNavigation>
|
||||
<div>
|
||||
<NavLink to={'/account'} exact>Settings</NavLink>
|
||||
<NavLink to={'/account/api'}>API Credentials</NavLink>
|
||||
</div>
|
||||
</SubNavigation>
|
||||
}
|
||||
<TransitionRouter>
|
||||
<Switch location={location}>
|
||||
<Route path={'/'} component={DashboardContainer} exact/>
|
||||
<Route path={'/account'} component={AccountOverviewContainer} exact/>
|
||||
<Route path={'/account/api'} component={AccountApiContainer} exact/>
|
||||
<Route path={'*'} component={NotFound}/>
|
||||
</Switch>
|
||||
</TransitionRouter>
|
||||
</>
|
||||
);
|
||||
export default ({ location }: RouteComponentProps) => {
|
||||
useEffect(() => {
|
||||
ReactGA.pageview(location.pathname);
|
||||
}, [ location.pathname ]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<NavigationBar />
|
||||
{location.pathname.startsWith('/account') &&
|
||||
<SubNavigation>
|
||||
<div>
|
||||
<NavLink to={'/account'} exact>Settings</NavLink>
|
||||
<NavLink to={'/account/api'}>API Credentials</NavLink>
|
||||
</div>
|
||||
</SubNavigation>
|
||||
}
|
||||
<TransitionRouter>
|
||||
<Switch location={location}>
|
||||
<Route path={'/'} component={DashboardContainer} exact />
|
||||
<Route path={'/account'} component={AccountOverviewContainer} exact/>
|
||||
<Route path={'/account/api'} component={AccountApiContainer} exact/>
|
||||
<Route path={'*'} component={NotFound} />
|
||||
</Switch>
|
||||
</TransitionRouter>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import ReactGA from 'react-ga';
|
||||
import { NavLink, Route, RouteComponentProps, Switch } from 'react-router-dom';
|
||||
import NavigationBar from '@/components/NavigationBar';
|
||||
import ServerConsole from '@/components/server/ServerConsole';
|
||||
|
@ -25,6 +26,7 @@ import useServer from '@/plugins/useServer';
|
|||
import ScreenBlock from '@/components/screens/ScreenBlock';
|
||||
import SubNavigation from '@/components/elements/SubNavigation';
|
||||
import NetworkContainer from '@/components/server/network/NetworkContainer';
|
||||
import InstallListener from '@/components/server/InstallListener';
|
||||
|
||||
const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) => {
|
||||
const { rootAdmin } = useStoreState(state => state.user.data!);
|
||||
|
@ -60,6 +62,10 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>)
|
|||
};
|
||||
}, [ match.params.id ]);
|
||||
|
||||
useEffect(() => {
|
||||
ReactGA.pageview(location.pathname);
|
||||
}, [ location.pathname ]);
|
||||
|
||||
return (
|
||||
<React.Fragment key={'server-router'}>
|
||||
<NavigationBar/>
|
||||
|
@ -98,6 +104,8 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>)
|
|||
</div>
|
||||
</SubNavigation>
|
||||
</CSSTransition>
|
||||
<InstallListener/>
|
||||
<WebsocketHandler/>
|
||||
{(installing && (!rootAdmin || (rootAdmin && !location.pathname.endsWith(`/server/${server.id}`)))) ?
|
||||
<ScreenBlock
|
||||
title={'Your server is installing.'}
|
||||
|
@ -106,7 +114,6 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>)
|
|||
/>
|
||||
:
|
||||
<>
|
||||
<WebsocketHandler/>
|
||||
<TransitionRouter>
|
||||
<Switch location={location}>
|
||||
<Route path={`${match.path}`} component={ServerConsole} exact/>
|
||||
|
|
|
@ -7,6 +7,7 @@ export interface SiteSettings {
|
|||
enabled: boolean;
|
||||
siteKey: string;
|
||||
};
|
||||
analytics: string;
|
||||
}
|
||||
|
||||
export interface SettingsStore {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue