ui(admin): implement egg about

This commit is contained in:
Matthew Penner 2021-09-17 15:40:17 -06:00
parent 8d0dd42475
commit 66443dd5d3
No known key found for this signature in database
GPG key ID: 030E4AB751DC756F
7 changed files with 258 additions and 46 deletions

View file

@ -41,8 +41,8 @@ export interface Egg {
description: string | null;
features: string[] | null;
dockerImages: string[];
configFiles: string | null;
configStartup: string | null;
configFiles: Record<string, any> | null;
configStartup: Record<string, any> | null;
configStop: string | null;
configFrom: number | null;
startup: string;

View file

@ -104,24 +104,21 @@ const EditInformationContainer = () => {
<AdminBox title={'Edit Nest'} css={tw`flex-1 self-start w-full relative mb-8 lg:mb-0 mr-0 lg:mr-4`}>
<SpinnerOverlay visible={isSubmitting}/>
<Form css={tw`mb-0`}>
<div>
<Field
id={'name'}
name={'name'}
label={'Name'}
type={'text'}
/>
</div>
<Form>
<Field
id={'name'}
name={'name'}
label={'Name'}
type={'text'}
css={tw`mb-6`}
/>
<div css={tw`mt-6`}>
<Field
id={'description'}
name={'description'}
label={'Description'}
type={'text'}
/>
</div>
<Field
id={'description'}
name={'description'}
label={'Description'}
type={'text'}
/>
<div css={tw`w-full flex flex-row items-center mt-6`}>
<div css={tw`flex`}>

View file

@ -0,0 +1,58 @@
import deleteEgg from '@/api/admin/eggs/deleteEgg';
import React, { useState } from 'react';
import { Actions, useStoreActions } from 'easy-peasy';
import { ApplicationStore } from '@/state';
import tw from 'twin.macro';
import Button from '@/components/elements/Button';
import ConfirmationModal from '@/components/elements/ConfirmationModal';
interface Props {
eggId: number;
onDeleted: () => void;
}
export default ({ eggId, onDeleted }: Props) => {
const [ visible, setVisible ] = useState(false);
const [ loading, setLoading ] = useState(false);
const { clearFlashes, clearAndAddHttpError } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
const onDelete = () => {
setLoading(true);
clearFlashes('egg');
deleteEgg(eggId)
.then(() => {
setLoading(false);
onDeleted();
})
.catch(error => {
console.error(error);
clearAndAddHttpError({ key: 'egg', error });
setLoading(false);
setVisible(false);
});
};
return (
<>
<ConfirmationModal
visible={visible}
title={'Delete egg?'}
buttonText={'Yes, delete egg'}
onConfirmed={onDelete}
showSpinnerOverlay={loading}
onModalDismissed={() => setVisible(false)}
>
Are you sure you want to delete this egg? You may only delete an egg with no servers using it.
</ConfirmationModal>
<Button type={'button'} size={'xsmall'} color={'red'} onClick={() => setVisible(true)}>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" css={tw`h-5 w-5`}>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</Button>
</>
);
};

View file

@ -93,11 +93,11 @@ const EggRouter = () => {
<Switch location={location}>
<Route path={`${match.path}`} exact>
<EggSettingsContainer/>
<EggSettingsContainer egg={egg}/>
</Route>
<Route path={`${match.path}/variables`} exact>
<EggVariablesContainer/>
<EggVariablesContainer egg={egg}/>
</Route>
<Route path={`${match.path}/install`} exact>

View file

@ -1,19 +1,182 @@
import Editor from '@/components/elements/Editor';
import Field from '@/components/elements/Field';
import Input from '@/components/elements/Input';
import Label from '@/components/elements/Label';
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
import useFlash from '@/plugins/useFlash';
import { jsonLanguage } from '@codemirror/lang-json';
import { faEgg, faTerminal } from '@fortawesome/free-solid-svg-icons';
import React from 'react';
import AdminBox from '@/components/admin/AdminBox';
import { Context } from '@/components/admin/nests/eggs/EggRouter';
import { Egg } from '@/api/admin/eggs/getEgg';
import tw from 'twin.macro';
import { object } from 'yup';
import { Form, Formik, useFormikContext } from 'formik';
export default () => {
const egg = Context.useStoreState(state => state.egg);
if (egg === undefined) {
return (
<></>
);
}
function EggInformationContainer () {
const { isSubmitting } = useFormikContext();
return (
<AdminBox title={'Egg Information'}>
<AdminBox icon={faEgg} title={'Egg Information'} css={tw`relative`}>
<SpinnerOverlay visible={isSubmitting}/>
<Field
id={'name'}
name={'name'}
label={'Name'}
type={'text'}
css={tw`mb-6`}
/>
<Field
id={'description'}
name={'description'}
label={'Description'}
type={'text'}
css={tw`mb-2`}
/>
</AdminBox>
);
};
}
function EggDetailsContainer ({ egg }: { egg: Egg }) {
return (
<AdminBox icon={faEgg} title={'Egg Details'} css={tw`relative`}>
<div css={tw`mb-6`}>
<Label>UUID</Label>
<Input
id={'uuid'}
name={'uuid'}
type={'text'}
value={egg.uuid}
readOnly
/>
</div>
<div css={tw`mb-2`}>
<Label>Author</Label>
<Input
id={'author'}
name={'author'}
type={'text'}
value={egg.author}
readOnly
/>
</div>
</AdminBox>
);
}
function EggStartupContainer ({ className }: { className?: string }) {
const { isSubmitting } = useFormikContext();
return (
<AdminBox icon={faTerminal} title={'Startup Command'} css={tw`relative`} className={className}>
<SpinnerOverlay visible={isSubmitting}/>
<Field
id={'startup'}
name={'startup'}
label={'Startup Command'}
type={'text'}
css={tw`mb-1`}
/>
</AdminBox>
);
}
function EggImageContainer () {
const { isSubmitting } = useFormikContext();
return (
<AdminBox icon={undefined} title={'Image'} css={tw`relative`}>
<SpinnerOverlay visible={isSubmitting}/>
</AdminBox>
);
}
function EggStopContainer () {
const { isSubmitting } = useFormikContext();
return (
<AdminBox icon={undefined} title={'Stop'} css={tw`relative`}>
<SpinnerOverlay visible={isSubmitting}/>
<Field
id={'stopCommand'}
name={'stopCommand'}
label={'Stop Command'}
type={'text'}
css={tw`mb-1`}
/>
</AdminBox>
);
}
function EggProcessContainer ({ egg }: { egg: Egg }) {
const { isSubmitting } = useFormikContext();
return (
<AdminBox title={'Process Configuration'} css={tw`relative mb-16`}>
<SpinnerOverlay visible={isSubmitting}/>
<div css={tw`mb-6`}>
<Label>Startup Configuration</Label>
<Editor
mode={jsonLanguage}
initialContent={JSON.stringify(egg.configStartup, null, '\t') || ''}
overrides={tw`h-32 rounded`}
/>
</div>
<div css={tw`mb-1`}>
<Label>Configuration Files</Label>
<Editor
mode={jsonLanguage}
initialContent={JSON.stringify(egg.configFiles, null, '\t') || ''}
overrides={tw`h-48 rounded`}
/>
</div>
</AdminBox>
);
}
export default function EggSettingsContainer ({ egg }: { egg: Egg }) {
const { clearFlashes } = useFlash();
const submit = () => {
clearFlashes('egg');
};
return (
<Formik
onSubmit={submit}
initialValues={{
name: egg.name,
description: egg.description || '',
startup: egg.startup,
stopCommand: egg.configStop,
}}
validationSchema={object().shape({
})}
>
<Form>
<div css={tw`grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 mb-6`}>
<EggInformationContainer/>
<EggDetailsContainer egg={egg}/>
</div>
<EggStartupContainer css={tw`mb-6`}/>
<div css={tw`grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 mb-6`}>
<EggImageContainer/>
<EggStopContainer/>
</div>
<EggProcessContainer egg={egg}/>
</Form>
</Formik>
);
}

View file

@ -1,19 +1,11 @@
import React from 'react';
import AdminBox from '@/components/admin/AdminBox';
import { Context } from '@/components/admin/nests/eggs/EggRouter';
export default () => {
const egg = Context.useStoreState(state => state.egg);
if (egg === undefined) {
return (
<></>
);
}
import { Egg } from '@/api/admin/eggs/getEgg';
export default ({ egg }: { egg: Egg }) => {
return (
<AdminBox title={'Variables'}>
{egg.name}
</AdminBox>
);
};

View file

@ -6,7 +6,7 @@ import { foldGutter, foldKeymap } from '@codemirror/fold';
import { lineNumbers, highlightActiveLineGutter } from '@codemirror/gutter';
import { defaultHighlightStyle } from '@codemirror/highlight';
import { history, historyKeymap } from '@codemirror/history';
import { indentOnInput, LanguageSupport, LRLanguage } from '@codemirror/language';
import { indentOnInput, LanguageSupport, LRLanguage, indentUnit } from '@codemirror/language';
import { lintKeymap } from '@codemirror/lint';
import { bracketMatching } from '@codemirror/matchbrackets';
import { rectangularSelection } from '@codemirror/rectangular-selection';
@ -167,12 +167,13 @@ const defaultExtensions: Extension = [
...lintKeymap,
indentWithTab,
]),
EditorState.tabSize.of(4),
// This is gonna piss people off, but that isn't my problem.
indentUnit.of('\t'),
];
const EditorContainer = styled.div<{ overrides?: TwStyle }>`
min-height: 12rem;
//min-height: 12rem;
${tw`relative`};
& > div {
@ -207,6 +208,7 @@ export default ({ className, style, overrides, initialContent, extensions, mode,
extensions: [
...defaultExtensions,
...(extensions !== undefined ? extensions : []),
languageConfig.of(mode !== undefined ? modeToExtension(mode) : findLanguageExtensionByMode(findModeByFilename(filename || ''))),
keybinds.of([]),
],