Merge branch 'develop' into feature/server-mounts
This commit is contained in:
commit
29876e023b
166 changed files with 5482 additions and 4130 deletions
|
@ -1,9 +1,7 @@
|
|||
import http from '@/api/http';
|
||||
|
||||
export default (code: string): Promise<void> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
http.post('/api/client/account/two-factor', { code })
|
||||
.then(() => resolve())
|
||||
.catch(reject);
|
||||
});
|
||||
export default async (code: string): Promise<string[]> => {
|
||||
const { data } = await http.post('/api/client/account/two-factor', { code });
|
||||
|
||||
return data.attributes.tokens;
|
||||
};
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import http from '@/api/http';
|
||||
import { LoginResponse } from '@/api/auth/login';
|
||||
|
||||
export default (token: string, code: string): Promise<LoginResponse> => {
|
||||
export default (token: string, code: string, recoveryToken?: string): Promise<LoginResponse> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
http.post('/auth/login/checkpoint', {
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
confirmation_token: token,
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
authentication_code: code,
|
||||
recovery_token: (recoveryToken && recoveryToken.length > 0) ? recoveryToken : undefined,
|
||||
/* eslint-enable @typescript-eslint/camelcase */
|
||||
})
|
||||
.then(response => resolve({
|
||||
complete: response.data.data.complete,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { Link, RouteComponentProps } from 'react-router-dom';
|
||||
import loginCheckpoint from '@/api/auth/loginCheckpoint';
|
||||
import { httpErrorToHuman } from '@/api/http';
|
||||
|
@ -14,6 +14,7 @@ import Field from '@/components/elements/Field';
|
|||
|
||||
interface Values {
|
||||
code: string;
|
||||
recoveryCode: '',
|
||||
}
|
||||
|
||||
type OwnProps = RouteComponentProps<{}, StaticContext, { token?: string }>
|
||||
|
@ -24,7 +25,8 @@ type Props = OwnProps & {
|
|||
}
|
||||
|
||||
const LoginCheckpointContainer = () => {
|
||||
const { isSubmitting } = useFormikContext<Values>();
|
||||
const { isSubmitting, setFieldValue } = useFormikContext<Values>();
|
||||
const [ isMissingDevice, setIsMissingDevice ] = useState(false);
|
||||
|
||||
return (
|
||||
<LoginFormContainer
|
||||
|
@ -34,10 +36,14 @@ const LoginCheckpointContainer = () => {
|
|||
<div className={'mt-6'}>
|
||||
<Field
|
||||
light={true}
|
||||
name={'code'}
|
||||
title={'Authentication Code'}
|
||||
description={'Enter the two-factor token generated by your device.'}
|
||||
type={'number'}
|
||||
name={isMissingDevice ? 'recoveryCode' : 'code'}
|
||||
title={isMissingDevice ? 'Recovery Code' : 'Authentication Code'}
|
||||
description={
|
||||
isMissingDevice
|
||||
? 'Enter one of the recovery codes generated when you setup 2-Factor authentication on this account in order to continue.'
|
||||
: 'Enter the two-factor token generated by your device.'
|
||||
}
|
||||
type={isMissingDevice ? 'text' : 'number'}
|
||||
autoFocus={true}
|
||||
/>
|
||||
</div>
|
||||
|
@ -54,6 +60,18 @@ const LoginCheckpointContainer = () => {
|
|||
}
|
||||
</button>
|
||||
</div>
|
||||
<div className={'mt-6 text-center'}>
|
||||
<span
|
||||
onClick={() => {
|
||||
setFieldValue('code', '');
|
||||
setFieldValue('recoveryCode', '');
|
||||
setIsMissingDevice(s => !s);
|
||||
}}
|
||||
className={'cursor-pointer text-xs text-neutral-500 tracking-wide uppercase no-underline hover:text-neutral-700'}
|
||||
>
|
||||
{!isMissingDevice ? 'I\'ve Lost My Device' : 'I Have My Device'}
|
||||
</span>
|
||||
</div>
|
||||
<div className={'mt-6 text-center'}>
|
||||
<Link
|
||||
to={'/auth/login'}
|
||||
|
@ -67,10 +85,9 @@ const LoginCheckpointContainer = () => {
|
|||
};
|
||||
|
||||
const EnhancedForm = withFormik<Props, Values>({
|
||||
handleSubmit: ({ code }, { setSubmitting, props: { addError, clearFlashes, location } }) => {
|
||||
handleSubmit: ({ code, recoveryCode }, { setSubmitting, props: { addError, clearFlashes, location } }) => {
|
||||
clearFlashes();
|
||||
console.log(location.state.token, code);
|
||||
loginCheckpoint(location.state?.token || '', code)
|
||||
loginCheckpoint(location.state?.token || '', code, recoveryCode)
|
||||
.then(response => {
|
||||
if (response.complete) {
|
||||
// @ts-ignore
|
||||
|
@ -89,11 +106,7 @@ const EnhancedForm = withFormik<Props, Values>({
|
|||
|
||||
mapPropsToValues: () => ({
|
||||
code: '',
|
||||
}),
|
||||
|
||||
validationSchema: object().shape({
|
||||
code: string().required('An authentication code must be provided.')
|
||||
.length(6, 'Authentication code must be 6 digits in length.'),
|
||||
recoveryCode: '',
|
||||
}),
|
||||
})(LoginCheckpointContainer);
|
||||
|
||||
|
|
|
@ -52,6 +52,8 @@ export default ({ server, className }: { server: Server; className: string | und
|
|||
alarms.memory = isAlarmState(stats.memoryUsageInBytes, server.limits.memory);
|
||||
alarms.disk = server.limits.disk === 0 ? false : isAlarmState(stats.diskUsageInBytes, server.limits.disk);
|
||||
}
|
||||
const disklimit = server.limits.disk != 0 ? bytesToHuman(server.limits.disk * 1000 * 1000) : "Unlimited";
|
||||
const memorylimit = server.limits.memory != 0 ? bytesToHuman(server.limits.memory * 1000 * 1000) : "Unlimited";
|
||||
|
||||
return (
|
||||
<Link to={`/server/${server.id}`} className={`grey-row-box cursor-pointer ${className}`}>
|
||||
|
@ -127,7 +129,7 @@ export default ({ server, className }: { server: Server; className: string | und
|
|||
{bytesToHuman(stats.memoryUsageInBytes)}
|
||||
</p>
|
||||
</div>
|
||||
<p className={'text-xs text-neutral-600 text-center mt-1'}>of {bytesToHuman(server.limits.memory * 1000 * 1000)}</p>
|
||||
<p className={'text-xs text-neutral-600 text-center mt-1'}>of {memorylimit}</p>
|
||||
</div>
|
||||
<div className={'flex-1 ml-4'}>
|
||||
<div className={'flex justify-center'}>
|
||||
|
@ -147,9 +149,7 @@ export default ({ server, className }: { server: Server; className: string | und
|
|||
{bytesToHuman(stats.diskUsageInBytes)}
|
||||
</p>
|
||||
</div>
|
||||
<p className={'text-xs text-neutral-600 text-center mt-1'}>
|
||||
of {bytesToHuman(server.limits.disk * 1000 * 1000)}
|
||||
</p>
|
||||
<p className={'text-xs text-neutral-600 text-center mt-1'}>of {disklimit}</p>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
}
|
||||
|
|
|
@ -2,21 +2,22 @@ import React, { useEffect, useState } from 'react';
|
|||
import Modal, { RequiredModalProps } from '@/components/elements/Modal';
|
||||
import { Form, Formik, FormikHelpers } from 'formik';
|
||||
import { object, string } from 'yup';
|
||||
import Field from '@/components/elements/Field';
|
||||
import getTwoFactorTokenUrl from '@/api/account/getTwoFactorTokenUrl';
|
||||
import enableAccountTwoFactor from '@/api/account/enableAccountTwoFactor';
|
||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||
import { Actions, useStoreActions } from 'easy-peasy';
|
||||
import { ApplicationStore } from '@/state';
|
||||
import { httpErrorToHuman } from '@/api/http';
|
||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||
import Field from '@/components/elements/Field';
|
||||
|
||||
interface Values {
|
||||
code: string;
|
||||
}
|
||||
|
||||
export default ({ ...props }: RequiredModalProps) => {
|
||||
export default ({ onDismissed, ...props }: RequiredModalProps) => {
|
||||
const [ token, setToken ] = useState('');
|
||||
const [ loading, setLoading ] = useState(true);
|
||||
const [ recoveryTokens, setRecoveryTokens ] = useState<string[]>([]);
|
||||
|
||||
const updateUserData = useStoreActions((actions: Actions<ApplicationStore>) => actions.user.updateUserData);
|
||||
const { addError, clearFlashes } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
|
||||
|
@ -27,22 +28,30 @@ export default ({ ...props }: RequiredModalProps) => {
|
|||
.then(setToken)
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
addError({ message: httpErrorToHuman(error), key: 'account:two-factor' });
|
||||
});
|
||||
}, []);
|
||||
|
||||
const submit = ({ code }: Values, { setSubmitting }: FormikHelpers<Values>) => {
|
||||
clearFlashes('account:two-factor');
|
||||
enableAccountTwoFactor(code)
|
||||
.then(() => {
|
||||
updateUserData({ useTotp: true });
|
||||
props.onDismissed();
|
||||
.then(tokens => {
|
||||
setRecoveryTokens(tokens);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
|
||||
addError({ message: httpErrorToHuman(error), key: 'account:two-factor' });
|
||||
setSubmitting(false);
|
||||
});
|
||||
})
|
||||
.then(() => setSubmitting(false));
|
||||
};
|
||||
|
||||
const dismiss = () => {
|
||||
if (recoveryTokens.length > 0) {
|
||||
updateUserData({ useTotp: true });
|
||||
}
|
||||
|
||||
onDismissed();
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -58,47 +67,73 @@ export default ({ ...props }: RequiredModalProps) => {
|
|||
{({ isSubmitting, isValid }) => (
|
||||
<Modal
|
||||
{...props}
|
||||
onDismissed={dismiss}
|
||||
dismissable={!isSubmitting}
|
||||
showSpinnerOverlay={loading || isSubmitting}
|
||||
closeOnEscape={!recoveryTokens}
|
||||
closeOnBackground={!recoveryTokens}
|
||||
>
|
||||
<Form className={'mb-0'}>
|
||||
<FlashMessageRender className={'mb-6'} byKey={'account:two-factor'}/>
|
||||
<div className={'flex flex-wrap'}>
|
||||
<div className={'w-full md:flex-1'}>
|
||||
<div className={'w-32 h-32 md:w-64 md:h-64 bg-neutral-600 p-2 rounded mx-auto'}>
|
||||
{!token || !token.length ?
|
||||
<img
|
||||
src={'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='}
|
||||
className={'w-64 h-64 rounded'}
|
||||
{recoveryTokens.length > 0 ?
|
||||
<>
|
||||
<h2 className={'mb-4'}>Two-factor authentication enabled</h2>
|
||||
<p className={'text-neutral-300'}>
|
||||
Two-factor authentication has been enabled on your account. Should you loose access to
|
||||
this device you'll need to use on of the codes displayed below in order to access your
|
||||
account.
|
||||
</p>
|
||||
<p className={'text-neutral-300 mt-4'}>
|
||||
<strong>These codes will not be displayed again.</strong> Please take note of them now
|
||||
by storing them in a secure repository such as a password manager.
|
||||
</p>
|
||||
<pre className={'mt-4 rounded font-mono bg-neutral-900 p-4'}>
|
||||
{recoveryTokens.map(token => <code key={token} className={'block mb-1'}>{token}</code>)}
|
||||
</pre>
|
||||
<div className={'text-right'}>
|
||||
<button className={'mt-6 btn btn-lg btn-primary'} onClick={dismiss}>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
:
|
||||
<Form className={'mb-0'}>
|
||||
<FlashMessageRender className={'mb-6'} byKey={'account:two-factor'}/>
|
||||
<div className={'flex flex-wrap'}>
|
||||
<div className={'w-full md:flex-1'}>
|
||||
<div className={'w-32 h-32 md:w-64 md:h-64 bg-neutral-600 p-2 rounded mx-auto'}>
|
||||
{!token || !token.length ?
|
||||
<img
|
||||
src={'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='}
|
||||
className={'w-64 h-64 rounded'}
|
||||
/>
|
||||
:
|
||||
<img
|
||||
src={`https://api.qrserver.com/v1/create-qr-code/?size=500x500&data=${token}`}
|
||||
onLoad={() => setLoading(false)}
|
||||
className={'w-full h-full shadow-none rounded-0'}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className={'w-full mt-6 md:mt-0 md:flex-1 md:flex md:flex-col'}>
|
||||
<div className={'flex-1'}>
|
||||
<Field
|
||||
id={'code'}
|
||||
name={'code'}
|
||||
type={'text'}
|
||||
title={'Code From Authenticator'}
|
||||
description={'Enter the code from your authenticator device after scanning the QR image.'}
|
||||
autoFocus={!loading}
|
||||
/>
|
||||
:
|
||||
<img
|
||||
src={`https://api.qrserver.com/v1/create-qr-code/?size=500x500&data=${token}`}
|
||||
onLoad={() => setLoading(false)}
|
||||
className={'w-full h-full shadow-none rounded-0'}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
<div className={'mt-6 md:mt-0 text-right'}>
|
||||
<button className={'btn btn-primary btn-sm'} disabled={!isValid}>
|
||||
Setup
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={'w-full mt-6 md:mt-0 md:flex-1 md:flex md:flex-col'}>
|
||||
<div className={'flex-1'}>
|
||||
<Field
|
||||
id={'code'}
|
||||
name={'code'}
|
||||
type={'text'}
|
||||
title={'Code From Authenticator'}
|
||||
description={'Enter the code from your authenticator device after scanning the QR image.'}
|
||||
autoFocus={!loading}
|
||||
/>
|
||||
</div>
|
||||
<div className={'mt-6 md:mt-0 text-right'}>
|
||||
<button className={'btn btn-primary btn-sm'} disabled={!isValid}>
|
||||
Setup
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</Form>
|
||||
}
|
||||
</Modal>
|
||||
)}
|
||||
</Formik>
|
||||
|
|
|
@ -81,6 +81,9 @@ export default () => {
|
|||
};
|
||||
}, [ instance, connected ]);
|
||||
|
||||
const disklimit = server.limits.disk != 0 ? bytesToHuman(server.limits.disk * 1000 * 1000) : "Unlimited";
|
||||
const memorylimit = server.limits.memory != 0 ? bytesToHuman(server.limits.memory * 1000 * 1000) : "Unlimited";
|
||||
|
||||
return (
|
||||
<PageContentBlock className={'flex'}>
|
||||
<div className={'w-1/4'}>
|
||||
|
@ -112,8 +115,8 @@ export default () => {
|
|||
className={'mr-1'}
|
||||
/>
|
||||
{bytesToHuman(memory)}
|
||||
<span className={'text-neutral-500'}> / {bytesToHuman(server.limits.memory * 1000 * 1000)}</span>
|
||||
</p>
|
||||
<span className={'text-neutral-500'}> / {memorylimit}</span>
|
||||
</p>
|
||||
<p className={'text-xs mt-2'}>
|
||||
<FontAwesomeIcon
|
||||
icon={faHdd}
|
||||
|
@ -121,7 +124,7 @@ export default () => {
|
|||
className={'mr-1'}
|
||||
/>
|
||||
{bytesToHuman(disk)}
|
||||
<span className={'text-neutral-500'}> / {bytesToHuman(server.limits.disk * 1000 * 1000)}</span>
|
||||
<span className={'text-neutral-500'}> / {disklimit}</span>
|
||||
</p>
|
||||
</TitledGreyBox>
|
||||
{!server.isInstalling ?
|
||||
|
|
|
@ -12,7 +12,7 @@ import { ServerContext } from '@/state/server';
|
|||
import PageContentBlock from '@/components/elements/PageContentBlock';
|
||||
|
||||
export default () => {
|
||||
const { uuid } = useServer();
|
||||
const { uuid, featureLimits } = useServer();
|
||||
const { addError, clearFlashes } = useFlash();
|
||||
const [ loading, setLoading ] = useState(true);
|
||||
|
||||
|
@ -50,10 +50,22 @@ export default () => {
|
|||
/>)}
|
||||
</div>
|
||||
}
|
||||
{featureLimits.backups === 0 &&
|
||||
<p className="text-center text-sm text-neutral-400">
|
||||
Backups cannot be created for this server.
|
||||
</p>
|
||||
}
|
||||
<Can action={'backup.create'}>
|
||||
{(featureLimits.backups > 0 && backups.length > 0) &&
|
||||
<p className="text-center text-xs text-neutral-400 mt-2">
|
||||
{backups.length} of {featureLimits.backups} backups have been created for this server.
|
||||
</p>
|
||||
}
|
||||
{featureLimits.backups > 0 && featureLimits.backups !== backups.length &&
|
||||
<div className={'mt-6 flex justify-end'}>
|
||||
<CreateBackupButton/>
|
||||
</div>
|
||||
}
|
||||
</Can>
|
||||
</PageContentBlock>
|
||||
);
|
||||
|
|
|
@ -59,7 +59,12 @@ export default () => {
|
|||
</p>
|
||||
}
|
||||
<Can action={'database.create'}>
|
||||
{featureLimits.databases > 0 &&
|
||||
{(featureLimits.databases > 0 && databases.length > 0) &&
|
||||
<p className="text-center text-xs text-neutral-400 mt-2">
|
||||
{databases.length} of {featureLimits.databases} databases have been allocated to this server.
|
||||
</p>
|
||||
}
|
||||
{featureLimits.databases > 0 && featureLimits.databases !== databases.length &&
|
||||
<div className={'mt-6 flex justify-end'}>
|
||||
<CreateDatabaseButton/>
|
||||
</div>
|
||||
|
|
|
@ -110,7 +110,7 @@ export default () => {
|
|||
fetchContent={value => {
|
||||
fetchFileContent = value;
|
||||
}}
|
||||
onContentSaved={() => null}
|
||||
onContentSaved={() => save()}
|
||||
/>
|
||||
</div>
|
||||
<div className={'flex justify-end mt-4'}>
|
||||
|
|
|
@ -25,10 +25,10 @@ export default ({ withinFileEditor, isNewFile }: Props) => {
|
|||
.filter(directory => !!directory)
|
||||
.map((directory, index, dirs) => {
|
||||
if (!withinFileEditor && index === dirs.length - 1) {
|
||||
return { name: directory };
|
||||
return { name: decodeURIComponent(directory) };
|
||||
}
|
||||
|
||||
return { name: directory, path: `/${dirs.slice(0, index + 1).join('/')}` };
|
||||
return { name: decodeURIComponent(directory), path: `/${dirs.slice(0, index + 1).join('/')}` };
|
||||
});
|
||||
|
||||
return (
|
||||
|
@ -57,7 +57,7 @@ export default ({ withinFileEditor, isNewFile }: Props) => {
|
|||
}
|
||||
{file &&
|
||||
<React.Fragment>
|
||||
<span className={'px-1 text-neutral-300'}>{file}</span>
|
||||
<span className={'px-1 text-neutral-300'}>{decodeURIComponent(file)}</span>
|
||||
</React.Fragment>
|
||||
}
|
||||
</div>
|
||||
|
|
|
@ -70,7 +70,12 @@ export default () => {
|
|||
/>
|
||||
<p className={'text-xs mt-2 text-neutral-400'}>
|
||||
<span className={'text-neutral-200'}>This directory will be created as</span>
|
||||
/home/container/<span className={'text-cyan-200'}>{join(directory, values.directoryName).replace(/^(\.\.\/|\/)+/, '')}</span>
|
||||
/home/container/
|
||||
<span className={'text-cyan-200'}>
|
||||
{decodeURIComponent(
|
||||
join(directory, values.directoryName).replace(/^(\.\.\/|\/)+/, ''),
|
||||
)}
|
||||
</span>
|
||||
</p>
|
||||
<div className={'flex justify-end'}>
|
||||
<button className={'btn btn-sm btn-primary mt-8'}>
|
||||
|
|
|
@ -14,12 +14,26 @@ import Can from '@/components/elements/Can';
|
|||
import useServer from '@/plugins/useServer';
|
||||
import useFlash from '@/plugins/useFlash';
|
||||
import { ServerContext } from '@/state/server';
|
||||
import { faFileArchive } from '@fortawesome/free-solid-svg-icons/faFileArchive';
|
||||
|
||||
interface Props {
|
||||
schedule: Schedule;
|
||||
task: Task;
|
||||
}
|
||||
|
||||
const getActionDetails = (action: string): [ string, any ] => {
|
||||
switch (action) {
|
||||
case 'command':
|
||||
return ['Send Command', faCode];
|
||||
case 'power':
|
||||
return ['Send Power Action', faToggleOn];
|
||||
case 'backup':
|
||||
return ['Create Backup', faFileArchive];
|
||||
default:
|
||||
return ['Unknown Action', faCode];
|
||||
}
|
||||
};
|
||||
|
||||
export default ({ schedule, task }: Props) => {
|
||||
const { uuid } = useServer();
|
||||
const { clearFlashes, addError } = useFlash();
|
||||
|
@ -43,6 +57,8 @@ export default ({ schedule, task }: Props) => {
|
|||
});
|
||||
};
|
||||
|
||||
const [ title, icon ] = getActionDetails(task.action);
|
||||
|
||||
return (
|
||||
<div className={'flex items-center bg-neutral-700 border border-neutral-600 mb-2 px-6 py-4 rounded'}>
|
||||
<SpinnerOverlay visible={isLoading} fixed={true} size={'large'}/>
|
||||
|
@ -56,14 +72,19 @@ export default ({ schedule, task }: Props) => {
|
|||
onDismissed={() => setVisible(false)}
|
||||
onConfirmed={() => onConfirmDeletion()}
|
||||
/>
|
||||
<FontAwesomeIcon icon={task.action === 'command' ? faCode : faToggleOn} className={'text-lg text-white'}/>
|
||||
<FontAwesomeIcon icon={icon} className={'text-lg text-white'}/>
|
||||
<div className={'flex-1'}>
|
||||
<p className={'ml-6 text-neutral-300 mb-2 uppercase text-xs'}>
|
||||
{task.action === 'command' ? 'Send command' : 'Send power action'}
|
||||
<p className={'ml-6 text-neutral-300 uppercase text-xs'}>
|
||||
{title}
|
||||
</p>
|
||||
<code className={'ml-6 font-mono bg-neutral-800 rounded py-1 px-2 text-sm'}>
|
||||
{task.payload}
|
||||
</code>
|
||||
{task.payload &&
|
||||
<div className={'ml-6 mt-2'}>
|
||||
{task.action === 'backup' && <p className={'text-xs uppercase text-neutral-400 mb-1'}>Ignoring files & folders:</p>}
|
||||
<div className={'font-mono bg-neutral-800 rounded py-1 px-2 text-sm w-auto whitespace-pre inline-block'}>
|
||||
{task.payload}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
{task.sequenceId > 1 &&
|
||||
<div className={'mr-6'}>
|
||||
|
|
|
@ -71,7 +71,10 @@ const TaskDetailsForm = ({ isEditingTask }: { isEditingTask: boolean }) => {
|
|||
:
|
||||
<div>
|
||||
<label className={'input-dark-label'}>Ignored Files</label>
|
||||
<FormikFieldWrapper name={'payload'}>
|
||||
<FormikFieldWrapper
|
||||
name={'payload'}
|
||||
description={'Optional. Include the files and folders to be excluded in this backup. By default, the contents of your .pteroignore file will be used.'}
|
||||
>
|
||||
<FormikField as={'textarea'} name={'payload'} className={'input-dark h-32'}/>
|
||||
</FormikFieldWrapper>
|
||||
</div>
|
||||
|
|
|
@ -48,7 +48,7 @@ const files: ServerFileStore = {
|
|||
}),
|
||||
|
||||
setDirectory: action((state, payload) => {
|
||||
state.directory = cleanDirectoryPath(payload)
|
||||
state.directory = cleanDirectoryPath(payload);
|
||||
}),
|
||||
};
|
||||
|
||||
|
|
|
@ -123,6 +123,7 @@ select.input:not(.appearance-none) {
|
|||
select.input-dark:not(.appearance-none) {
|
||||
@apply .bg-neutral-600 .border-neutral-500 .text-neutral-200;
|
||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='%23C3D1DF' d='M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z'/%3e%3c/svg%3e ");
|
||||
background-color: hsl(220deg 21% 16%);
|
||||
|
||||
&:hover:not(:disabled), &:focus {
|
||||
@apply .border-neutral-400;
|
||||
|
|
|
@ -27,3 +27,39 @@ code.clean {
|
|||
@apply .mt-4;
|
||||
}
|
||||
}
|
||||
|
||||
::-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;
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@
|
|||
swal({
|
||||
type: 'success',
|
||||
title: 'Token created.',
|
||||
text: '<p>To auto-configure your node run the following command:<br /><small><pre>cd /etc/pterodactyl && ./wings configure --panel-url {{ config('app.url') }} --token ' + data.token + ' --node ' + data.node + '{{ config('app.debug') ? ' --allow-insecure' : '' }}</pre></small></p>',
|
||||
text: '<p>To auto-configure your node run the following command:<br /><small><pre>cd /etc/pterodactyl && sudo ./wings configure --panel-url {{ config('app.url') }} --token ' + data.token + ' --node ' + data.node + '{{ config('app.debug') ? ' --allow-insecure' : '' }}</pre></small></p>',
|
||||
html: true
|
||||
})
|
||||
}).fail(function () {
|
||||
|
|
|
@ -176,6 +176,8 @@
|
|||
<input type="text" id="pMemory" name="memory" class="form-control" value="{{ old('memory') }}" />
|
||||
<span class="input-group-addon">MB</span>
|
||||
</div>
|
||||
|
||||
<p class="text-muted small">The maximum amount of memory allowed for this container. Setting this to <code>0</code> will allow unlimited memory in a container.</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-xs-6">
|
||||
|
@ -185,21 +187,18 @@
|
|||
<input type="text" id="pSwap" name="swap" class="form-control" value="{{ old('swap', 0) }}" />
|
||||
<span class="input-group-addon">MB</span>
|
||||
</div>
|
||||
<p class="text-muted small">Setting this to <code>0</code> will disable swap space on this server. Setting to <code>-1</code> will allow unlimited swap.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box-footer no-border no-pad-top no-pad-bottom">
|
||||
<p class="text-muted small">If you do not want to assign swap space to a server, simply put <code>0</code> for the value, or <code>-1</code> to allow unlimited swap space. If you want to disable memory limiting on a server, simply enter <code>0</code> into the memory field.<p>
|
||||
</div>
|
||||
|
||||
<div class="box-body row">
|
||||
<div class="form-group col-xs-6">
|
||||
<label for="pDisk">Disk Space</label>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" id="pDisk" name="disk" class="form-control" value="{{ old('disk') }}" />
|
||||
<span class="input-group-addon">MB</span>
|
||||
</div>
|
||||
<p class="text-muted small">This server will not be allowed to boot if it is using more than this amount of space. If a server goes over this limit while running it will be safely stopped and locked until enough space is available. Set to <code>0</code> to allow unlimited disk usage.</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-xs-6">
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
<input type="text" name="disk" class="form-control" value="{{ old('disk', $server->disk) }}"/>
|
||||
<span class="input-group-addon">MB</span>
|
||||
</div>
|
||||
<p class="text-muted small">This server will not be allowed to boot if it is using more than this amount of space. If a server goes over this limit while running it will be safely stopped and locked until enough space is available.</p>
|
||||
<p class="text-muted small">This server will not be allowed to boot if it is using more than this amount of space. If a server goes over this limit while running it will be safely stopped and locked until enough space is available. Set to <code>0</code> to allow unlimited disk usage.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="io" class="control-label">Block IO Proportion</label>
|
||||
|
|
|
@ -97,7 +97,13 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>Disk Space</td>
|
||||
<td><code>{{ $server->disk }}MB</code></td>
|
||||
<td>
|
||||
@if($server->disk === 0)
|
||||
<code>Unlimited</code>
|
||||
@else
|
||||
<code>{{ $server->disk }}MB</code>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Block IO Weight</td>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue