ui(admin): add startup and file configuration editing for eggs

This commit is contained in:
Matthew Penner 2021-09-18 11:45:32 -06:00
parent 7d1cb2971f
commit b125830859
No known key found for this signature in database
GPG key ID: 030E4AB751DC756F
7 changed files with 112 additions and 40 deletions

View file

@ -40,6 +40,8 @@
"@codemirror/theme-one-dark": "^0.19.0",
"@codemirror/view": "^0.19.4",
"@fortawesome/fontawesome-svg-core": "^1.2.36",
"@fortawesome/free-brands-svg-icons": "^5.15.4",
"@fortawesome/free-regular-svg-icons": "^5.15.4",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/react-fontawesome": "^0.1.15",
"@hot-loader/react-dom": "^16.14.0",

View file

@ -1,7 +1,9 @@
import http from '@/api/http';
import { Egg, rawDataToEgg } from '@/api/admin/eggs/getEgg';
export default (id: number, egg: Partial<Egg>): Promise<Egg> => {
type Egg2 = Omit<Omit<Partial<Egg>, 'configFiles'>, 'configStartup'> & { configFiles?: string, configStartup?: string };
export default (id: number, egg: Partial<Egg2>): Promise<Egg> => {
return new Promise((resolve, reject) => {
http.patch(
`/api/application/eggs/${id}`,

View file

@ -18,7 +18,7 @@ export default ({ className }: { className?: string }) => {
const match = useRouteMatch<{ nestId: string }>();
const { mutate } = getEggs(Number(match.params.nestId));
let fetchFileContent: null | (() => Promise<string>) = null;
let fetchFileContent: (() => Promise<string>) | null = null;
const submit = async () => {
clearFlashes('egg:import');

View file

@ -21,7 +21,7 @@ interface Values {
export default function EggInstallContainer ({ egg }: { egg: Egg }) {
const { clearFlashes, clearAndAddHttpError } = useFlash();
let fetchFileContent: null | (() => Promise<string>) = null;
let fetchFileContent: (() => Promise<string>) | null = null;
const submit = async (values: Values, { setSubmitting }: FormikHelpers<Values>) => {
if (fetchFileContent === null) {

View file

@ -8,8 +8,9 @@ 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 { faDocker } from '@fortawesome/free-brands-svg-icons';
import { faEgg, faFireAlt, faMicrochip, faTerminal } from '@fortawesome/free-solid-svg-icons';
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
import AdminBox from '@/components/admin/AdminBox';
import { Egg } from '@/api/admin/eggs/getEgg';
import { useHistory } from 'react-router-dom';
@ -93,7 +94,7 @@ function EggImageContainer () {
const { isSubmitting } = useFormikContext();
return (
<AdminBox icon={undefined} title={'Image'} css={tw`relative`}>
<AdminBox icon={faDocker} title={'Docker'} css={tw`relative`}>
<SpinnerOverlay visible={isSubmitting}/>
<TextareaField
@ -106,11 +107,11 @@ function EggImageContainer () {
);
}
function EggStopContainer () {
function EggLifecycleContainer () {
const { isSubmitting } = useFormikContext();
return (
<AdminBox icon={undefined} title={'Stop'} css={tw`relative`}>
<AdminBox icon={faFireAlt} title={'Lifecycle'} css={tw`relative`}>
<SpinnerOverlay visible={isSubmitting}/>
<Field
@ -124,40 +125,79 @@ function EggStopContainer () {
);
}
function EggProcessContainer ({ className, egg }: { className?: string, egg: Egg }) {
const { isSubmitting } = useFormikContext();
return (
<AdminBox title={'Process Configuration'} css={tw`relative`} className={className}>
<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>
);
interface EggProcessContainerProps {
className?: string;
egg: Egg;
}
interface EggProcessContainerRef {
getStartupConfiguration: () => Promise<string | null>;
getFilesConfiguration: () => Promise<string | null>;
}
const EggProcessContainer = forwardRef<any, EggProcessContainerProps>(
function EggProcessContainer ({ className, egg }, ref) {
const { isSubmitting } = useFormikContext();
let fetchStartupConfiguration: (() => Promise<string>) | null = null;
let fetchFilesConfiguration: (() => Promise<string>) | null = null;
useImperativeHandle<EggProcessContainerRef, EggProcessContainerRef>(ref, () => ({
getStartupConfiguration: async () => {
if (fetchStartupConfiguration === null) {
return new Promise<null>(resolve => resolve(null));
}
return await fetchStartupConfiguration();
},
getFilesConfiguration: async () => {
if (fetchFilesConfiguration === null) {
return new Promise<null>(resolve => resolve(null));
}
return await fetchFilesConfiguration();
},
}));
return (
<AdminBox icon={faMicrochip} title={'Process Configuration'} css={tw`relative`} className={className}>
<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`}
fetchContent={value => {
fetchStartupConfiguration = value;
}}
/>
</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`}
fetchContent={value => {
fetchFilesConfiguration = value;
}}
/>
</div>
</AdminBox>
);
}
);
interface Values {
name: string;
description: string;
startup: string;
dockerImages: string;
stopCommand: string;
configStartup: string;
configFiles: string;
}
export default function EggSettingsContainer ({ egg }: { egg: Egg }) {
@ -165,10 +205,13 @@ export default function EggSettingsContainer ({ egg }: { egg: Egg }) {
const { clearFlashes, clearAndAddHttpError } = useFlash();
const submit = (values: Values, { setSubmitting }: FormikHelpers<Values>) => {
const ref = useRef<EggProcessContainerRef>();
const submit = async (values: Values, { setSubmitting }: FormikHelpers<Values>) => {
clearFlashes('egg');
// TODO: Send data from code blocks.
values.configStartup = await ref.current?.getStartupConfiguration() || '';
values.configFiles = await ref.current?.getFilesConfiguration() || '';
updateEgg(egg.id, { ...values, dockerImages: values.dockerImages.split('\n') })
.catch(error => {
@ -187,6 +230,8 @@ export default function EggSettingsContainer ({ egg }: { egg: Egg }) {
startup: egg.startup,
dockerImages: egg.dockerImages.join('\n'),
stopCommand: egg.configStop || '',
configStartup: '',
configFiles: '',
}}
validationSchema={object().shape({
})}
@ -202,10 +247,14 @@ export default function EggSettingsContainer ({ egg }: { egg: Egg }) {
<div css={tw`grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 mb-6`}>
<EggImageContainer/>
<EggStopContainer/>
<EggLifecycleContainer/>
</div>
<EggProcessContainer egg={egg} css={tw`mb-6`}/>
<EggProcessContainer
ref={ref}
egg={egg}
css={tw`mb-6`}
/>
<div css={tw`bg-neutral-700 rounded shadow-md py-2 px-6 mb-16`}>
<div css={tw`flex flex-row`}>

View file

@ -36,8 +36,7 @@ export default () => {
const setDirectory = ServerContext.useStoreActions(actions => actions.files.setDirectory);
const { addError, clearFlashes } = useFlash();
// eslint-disable-next-line prefer-const
let fetchFileContent: null | (() => Promise<string>) = null;
let fetchFileContent: (() => Promise<string>) | null = null;
useEffect(() => {
if (action === 'new') return;

View file

@ -2178,6 +2178,24 @@ __metadata:
languageName: node
linkType: hard
"@fortawesome/free-brands-svg-icons@npm:^5.15.4":
version: 5.15.4
resolution: "@fortawesome/free-brands-svg-icons@npm:5.15.4"
dependencies:
"@fortawesome/fontawesome-common-types": ^0.2.36
checksum: 06e38132fbdf04d8677cf6e47e73cd566f68256f542d68d354e26f5ca7536c714b56f5f3dc6c670065b888ee08bc913bb4f213ab08225faf955d893a6a86ed02
languageName: node
linkType: hard
"@fortawesome/free-regular-svg-icons@npm:^5.15.4":
version: 5.15.4
resolution: "@fortawesome/free-regular-svg-icons@npm:5.15.4"
dependencies:
"@fortawesome/fontawesome-common-types": ^0.2.36
checksum: 2e6039e3bb2125940ed2cb5738b6562b082755c1e45b73571ee92d976773ab81118c9efb9a7b57453b664a025613e81e1dafd2235aafeaadd8f0d75f8e1fe25e
languageName: node
linkType: hard
"@fortawesome/free-solid-svg-icons@npm:^5.15.4":
version: 5.15.4
resolution: "@fortawesome/free-solid-svg-icons@npm:5.15.4"
@ -9923,6 +9941,8 @@ fsevents@^1.2.7:
"@codemirror/theme-one-dark": ^0.19.0
"@codemirror/view": ^0.19.4
"@fortawesome/fontawesome-svg-core": ^1.2.36
"@fortawesome/free-brands-svg-icons": ^5.15.4
"@fortawesome/free-regular-svg-icons": ^5.15.4
"@fortawesome/free-solid-svg-icons": ^5.15.4
"@fortawesome/react-fontawesome": ^0.1.15
"@hot-loader/react-dom": ^16.14.0