diff --git a/services/frontend/package.json b/services/frontend/package.json index 37512cd..bd23d2c 100644 --- a/services/frontend/package.json +++ b/services/frontend/package.json @@ -45,7 +45,8 @@ "typescript": "^4.5.5", "uuid": "^8.3.2", "web-vitals": "^2.1.4", - "yaml": "^1.10.2" + "yaml": "^1.10.2", + "yup": "^0.32.11" }, "scripts": { "start": "react-scripts start", diff --git a/services/frontend/src/components/Canvas/ServiceNode.tsx b/services/frontend/src/components/Canvas/ServiceNode.tsx index 48e4a0b..cbd3d71 100644 --- a/services/frontend/src/components/Canvas/ServiceNode.tsx +++ b/services/frontend/src/components/Canvas/ServiceNode.tsx @@ -1,4 +1,6 @@ import { useEffect, useState } from "react"; +import { ServerIcon } from "@heroicons/react/outline"; +import { truncateStr } from "../../utils"; import { IServiceNodeItem, CallbackFunction } from "../../types"; import eventBus from "../../events/eventBus"; import { Popover } from "./Popover"; @@ -52,14 +54,21 @@ export default function ServiceNode(props: INodeProps) { }} > )} -
+
<> -
- {node.canvasConfig.service_name} -
-
- {node.serviceConfig.container_name} -
+ {node.canvasConfig.service_name && ( +
+ {truncateStr(node.canvasConfig.service_name, 12)} +
+ )} + + {node.serviceConfig.container_name && ( +
+ {truncateStr(node.serviceConfig.container_name, 20)} +
+ )} + +
diff --git a/services/frontend/src/components/Canvas/VolumeNode.tsx b/services/frontend/src/components/Canvas/VolumeNode.tsx index b067d74..759f2dd 100644 --- a/services/frontend/src/components/Canvas/VolumeNode.tsx +++ b/services/frontend/src/components/Canvas/VolumeNode.tsx @@ -1,4 +1,6 @@ import { useEffect, useState } from "react"; +import { DatabaseIcon } from "@heroicons/react/outline"; +import { truncateStr } from "../../utils"; import { IVolumeNodeItem, CallbackFunction } from "../../types"; import eventBus from "../../events/eventBus"; import { Popover } from "./Popover"; @@ -52,11 +54,15 @@ export default function VolumeNode(props: INodeProps) { }} > )} -
+
<> -
- {node.volumeConfig.name} -
+ {node.volumeConfig.name && ( +
+ {truncateStr(node.volumeConfig.name, 20)} +
+ )} + +
diff --git a/services/frontend/src/components/Modal/Network/General.tsx b/services/frontend/src/components/Modal/Network/General.tsx index 00639c8..7d006fd 100644 --- a/services/frontend/src/components/Modal/Network/General.tsx +++ b/services/frontend/src/components/Modal/Network/General.tsx @@ -1,32 +1,11 @@ -const General = (props: any) => { - const { formik } = props; +import TextField from "../../global/FormElements/InputField"; +const General = () => { return ( <> -
-
-
- -
- -
-
-
-
+ ); }; + export default General; diff --git a/services/frontend/src/components/Modal/Network/IPam.tsx b/services/frontend/src/components/Modal/Network/IPam.tsx index 05b471f..2ea3f71 100644 --- a/services/frontend/src/components/Modal/Network/IPam.tsx +++ b/services/frontend/src/components/Modal/Network/IPam.tsx @@ -1,6 +1,5 @@ -const IPam = (props: any) => { - const { formik } = props; - +const IPam = () => { return <>; }; + export default IPam; diff --git a/services/frontend/src/components/Modal/Network/Labels.tsx b/services/frontend/src/components/Modal/Network/Labels.tsx index 9838a34..12d9e27 100644 --- a/services/frontend/src/components/Modal/Network/Labels.tsx +++ b/services/frontend/src/components/Modal/Network/Labels.tsx @@ -1,6 +1,5 @@ -const Labels = (props: any) => { - const { formik } = props; - +const Labels = () => { return <>; }; + export default Labels; diff --git a/services/frontend/src/components/Modal/Network/index.tsx b/services/frontend/src/components/Modal/Network/index.tsx index c65ccf4..de0e46a 100644 --- a/services/frontend/src/components/Modal/Network/index.tsx +++ b/services/frontend/src/components/Modal/Network/index.tsx @@ -1,5 +1,6 @@ import { useState } from "react"; -import { useFormik } from "formik"; +import { Formik } from "formik"; +import * as yup from "yup"; import { XIcon } from "@heroicons/react/outline"; import General from "./General"; import IPam from "./IPam"; @@ -14,12 +15,14 @@ interface IModalNetworkProps { const ModalNetwork = (props: IModalNetworkProps) => { const { onHide } = props; const [openTab, setOpenTab] = useState("General"); - - const formik = useFormik({ - initialValues: { - ...topLevelNetworkConfigInitialValues() - }, - onSubmit: () => undefined + const handleCreate = (values: any, formik: any) => { + formik.resetForm(); + }; + const validationSchema = yup.object({ + name: yup + .string() + .max(256, "name should be 256 characters or less") + .required("name is required") }); const tabs = [ { @@ -66,54 +69,70 @@ const ModalNetwork = (props: IModalNetworkProps) => { -
-
-
- -
-
+ { + handleCreate(values, formik); + }} + validationSchema={validationSchema} + > + {(formik) => ( + <> +
+
+ +
+
-
-
- {openTab === "General" && } - {openTab === "IPam" && } - {openTab === "Labels" && } - -
-
+
+ {openTab === "General" && } + {openTab === "IPam" && } + {openTab === "Labels" && } +
-
- -
+
+ +
+ + )} + diff --git a/services/frontend/src/components/Modal/Service/Create.tsx b/services/frontend/src/components/Modal/Service/Create.tsx index 3315933..aba5516 100644 --- a/services/frontend/src/components/Modal/Service/Create.tsx +++ b/services/frontend/src/components/Modal/Service/Create.tsx @@ -1,5 +1,6 @@ import { useState } from "react"; -import { useFormik } from "formik"; +import { Formik } from "formik"; +import * as yup from "yup"; import { XIcon } from "@heroicons/react/outline"; import General from "./General"; import Environment from "./Environment"; @@ -16,22 +17,23 @@ interface IModalServiceProps { const ModalServiceCreate = (props: IModalServiceProps) => { const { onHide, onAddEndpoint } = props; const [openTab, setOpenTab] = useState("General"); - - const formik = useFormik({ - initialValues: { - canvasConfig: { - ...serviceConfigCanvasInitialValues() - }, - serviceConfig: { - container_name: "" - }, - key: "service", - type: "SERVICE", - inputs: ["op_source"], - outputs: [], - config: {} - }, - onSubmit: () => undefined + const handleCreate = (values: any, formik: any) => { + onAddEndpoint(values); + formik.resetForm(); + }; + const validationSchema = yup.object({ + canvasConfig: yup.object({ + service_name: yup + .string() + .max(256, "service name should be 256 characters or less") + .required("service name is required") + }), + serviceConfig: yup.object({ + container_name: yup + .string() + .max(256, "container name should be 256 characters or less") + .required("container name is required") + }) }); const tabs = [ { @@ -84,56 +86,77 @@ const ModalServiceCreate = (props: IModalServiceProps) => { -
-
-
- -
-
+ { + handleCreate(values, formik); + }} + validationSchema={validationSchema} + > + {(formik) => ( + <> +
+
+ +
+
-
-
- {openTab === "General" && } - {openTab === "Environment" && } - {openTab === "Volumes" && } - {openTab === "Labels" && } - -
-
+
+ {openTab === "General" && } + {openTab === "Environment" && } + {openTab === "Volumes" && } + {openTab === "Labels" && } +
-
- -
+
+ +
+ + )} + diff --git a/services/frontend/src/components/Modal/Service/Edit.tsx b/services/frontend/src/components/Modal/Service/Edit.tsx index 2eb49be..0001646 100644 --- a/services/frontend/src/components/Modal/Service/Edit.tsx +++ b/services/frontend/src/components/Modal/Service/Edit.tsx @@ -1,17 +1,12 @@ import { useState, useEffect } from "react"; -import { useFormik } from "formik"; +import { Formik } from "formik"; +import * as yup from "yup"; import { XIcon } from "@heroicons/react/outline"; import General from "./General"; import Environment from "./Environment"; import Volumes from "./Volumes"; import Labels from "./Labels"; -import { serviceConfigCanvasInitialValues } from "../../../utils"; -import { - CallbackFunction, - ICanvasConfig, - IServiceNodeItem, - IService -} from "../../../types"; +import { CallbackFunction, IServiceNodeItem } from "../../../types"; interface IModalServiceProps { node: IServiceNodeItem; @@ -23,16 +18,26 @@ const ModalServiceEdit = (props: IModalServiceProps) => { const { node, onHide, onUpdateEndpoint } = props; const [openTab, setOpenTab] = useState("General"); const [selectedNode, setSelectedNode] = useState(); - const formik = useFormik({ - initialValues: { - canvasConfig: { - ...serviceConfigCanvasInitialValues() - }, - serviceConfig: { - container_name: "" - } - }, - onSubmit: () => undefined + + const handleUpdate = (values: any) => { + const updated = { ...selectedNode }; + updated.canvasConfig = values.canvasConfig; + updated.serviceConfig = values.serviceConfig; + onUpdateEndpoint(updated); + }; + const validationSchema = yup.object({ + canvasConfig: yup.object({ + service_name: yup + .string() + .max(256, "service name should be 256 characters or less") + .required("service name is required") + }), + serviceConfig: yup.object({ + container_name: yup + .string() + .max(256, "container name should be 256 characters or less") + .required("container name is required") + }) }); const tabs = [ { @@ -70,25 +75,6 @@ const ModalServiceEdit = (props: IModalServiceProps) => { } }, [node]); - useEffect(() => { - formik.resetForm(); - - if (selectedNode) { - formik.initialValues.canvasConfig = { - ...selectedNode.canvasConfig - } as ICanvasConfig; - formik.initialValues.serviceConfig = { - ...selectedNode.serviceConfig - } as IService; - } - }, [selectedNode]); - - useEffect(() => { - return () => { - formik.resetForm(); - }; - }, []); - return (
@@ -110,58 +96,76 @@ const ModalServiceEdit = (props: IModalServiceProps) => {
-
-
- -
- -
-
- {openTab === "General" && } - {openTab === "Environment" && } - {openTab === "Volumes" && } - {openTab === "Labels" && } - -
-
- -
- -
+ {(formik) => ( + <> +
+
+ +
+
+ +
+ {openTab === "General" && } + {openTab === "Environment" && } + {openTab === "Volumes" && } + {openTab === "Labels" && } +
+ +
+ +
+ + )} + + )}
diff --git a/services/frontend/src/components/Modal/Service/Environment.tsx b/services/frontend/src/components/Modal/Service/Environment.tsx index f68aaa3..066dfb9 100644 --- a/services/frontend/src/components/Modal/Service/Environment.tsx +++ b/services/frontend/src/components/Modal/Service/Environment.tsx @@ -1,6 +1,5 @@ -const Environment = (props: any) => { - const { formik } = props; - +const Environment = () => { return <>; }; + export default Environment; diff --git a/services/frontend/src/components/Modal/Service/General.tsx b/services/frontend/src/components/Modal/Service/General.tsx index 3ae0c59..40b26e4 100644 --- a/services/frontend/src/components/Modal/Service/General.tsx +++ b/services/frontend/src/components/Modal/Service/General.tsx @@ -1,56 +1,12 @@ -const General = (props: any) => { - const { formik } = props; +import TextField from "../../global/FormElements/InputField"; +const General = () => { return ( <> -
-
-
- -
- -
-
-
-
- -
-
-
- -
- -
-
-
-
+ + ); }; + export default General; diff --git a/services/frontend/src/components/Modal/Service/Labels.tsx b/services/frontend/src/components/Modal/Service/Labels.tsx index 9838a34..12d9e27 100644 --- a/services/frontend/src/components/Modal/Service/Labels.tsx +++ b/services/frontend/src/components/Modal/Service/Labels.tsx @@ -1,6 +1,5 @@ -const Labels = (props: any) => { - const { formik } = props; - +const Labels = () => { return <>; }; + export default Labels; diff --git a/services/frontend/src/components/Modal/Service/Volumes.tsx b/services/frontend/src/components/Modal/Service/Volumes.tsx index 8ed7f94..054bed2 100644 --- a/services/frontend/src/components/Modal/Service/Volumes.tsx +++ b/services/frontend/src/components/Modal/Service/Volumes.tsx @@ -1,6 +1,5 @@ -const Volumes = (props: any) => { - const { formik } = props; - +const Volumes = () => { return <>; }; + export default Volumes; diff --git a/services/frontend/src/components/Modal/Volume/Create.tsx b/services/frontend/src/components/Modal/Volume/Create.tsx index cde3c62..6d8ef0c 100644 --- a/services/frontend/src/components/Modal/Volume/Create.tsx +++ b/services/frontend/src/components/Modal/Volume/Create.tsx @@ -1,5 +1,6 @@ import { useState } from "react"; -import { useFormik } from "formik"; +import { Formik } from "formik"; +import * as yup from "yup"; import { XIcon } from "@heroicons/react/outline"; import General from "./General"; import Labels from "./Labels"; @@ -14,19 +15,17 @@ interface IModalVolumeCreate { const ModalVolumeCreate = (props: IModalVolumeCreate) => { const { onHide, onAddEndpoint } = props; const [openTab, setOpenTab] = useState("General"); - - const formik = useFormik({ - initialValues: { - volumeConfig: { - ...topLevelVolumeConfigInitialValues() - }, - key: "volume", - type: "VOLUME", - inputs: [], - outputs: [], - config: {} - }, - onSubmit: () => undefined + const handleCreate = (values: any, formik: any) => { + onAddEndpoint(values); + formik.resetForm(); + }; + const validationSchema = yup.object({ + volumeConfig: yup.object({ + name: yup + .string() + .max(256, "name should be 256 characters or less") + .required("name is required") + }) }); const tabs = [ { @@ -67,54 +66,71 @@ const ModalVolumeCreate = (props: IModalVolumeCreate) => { -
- + { + handleCreate(values, formik); + }} + validationSchema={validationSchema} + > + {(formik) => ( + <> + -
-
- {openTab === "General" && } - {openTab === "Labels" && } - -
-
+
+ {openTab === "General" && } + {openTab === "Labels" && } +
-
- -
+
+ +
+ + )} + diff --git a/services/frontend/src/components/Modal/Volume/Edit.tsx b/services/frontend/src/components/Modal/Volume/Edit.tsx index 06e452d..2424560 100644 --- a/services/frontend/src/components/Modal/Volume/Edit.tsx +++ b/services/frontend/src/components/Modal/Volume/Edit.tsx @@ -1,14 +1,10 @@ import { useEffect, useState } from "react"; -import { useFormik } from "formik"; +import { Formik } from "formik"; +import * as yup from "yup"; import { XIcon } from "@heroicons/react/outline"; import General from "./General"; import Labels from "./Labels"; -import { topLevelVolumeConfigInitialValues } from "../../../utils"; -import { - CallbackFunction, - IVolumeNodeItem, - IVolumeTopLevel -} from "../../../types"; +import { CallbackFunction, IVolumeNodeItem } from "../../../types"; interface IModalVolumeEdit { node: IVolumeNodeItem; @@ -20,14 +16,18 @@ const ModalVolumeEdit = (props: IModalVolumeEdit) => { const { node, onHide, onUpdateEndpoint } = props; const [openTab, setOpenTab] = useState("General"); const [selectedNode, setSelectedNode] = useState(); - - const formik = useFormik({ - initialValues: { - volumeConfig: { - ...topLevelVolumeConfigInitialValues() - } - }, - onSubmit: () => undefined + const handleUpdate = (values: any) => { + const updated = { ...selectedNode }; + updated.volumeConfig = values.volumeConfig; + onUpdateEndpoint(updated); + }; + const validationSchema = yup.object({ + volumeConfig: yup.object({ + name: yup + .string() + .max(256, "name should be 256 characters or less") + .required("name is required") + }) }); const tabs = [ { @@ -53,22 +53,6 @@ const ModalVolumeEdit = (props: IModalVolumeEdit) => { } }, [node]); - useEffect(() => { - formik.resetForm(); - - if (selectedNode) { - formik.initialValues.volumeConfig = { - ...selectedNode.volumeConfig - } as IVolumeTopLevel; - } - }, [selectedNode]); - - useEffect(() => { - return () => { - formik.resetForm(); - }; - }, []); - return (
@@ -92,55 +76,71 @@ const ModalVolumeEdit = (props: IModalVolumeEdit) => {
-
- - -
-
- {openTab === "General" && } - {openTab === "Labels" && } - -
-
- -
- -
+ {(formik) => ( + <> + + +
+ {openTab === "General" && } + {openTab === "Labels" && } +
+ +
+ +
+ + )} + + )}
diff --git a/services/frontend/src/components/Modal/Volume/General.tsx b/services/frontend/src/components/Modal/Volume/General.tsx index 1e18ea2..01d817a 100644 --- a/services/frontend/src/components/Modal/Volume/General.tsx +++ b/services/frontend/src/components/Modal/Volume/General.tsx @@ -1,32 +1,11 @@ -const General = (props: any) => { - const { formik } = props; +import TextField from "../../global/FormElements/InputField"; +const General = () => { return ( <> -
-
-
- -
- -
-
-
-
+ ); }; + export default General; diff --git a/services/frontend/src/components/Modal/Volume/Labels.tsx b/services/frontend/src/components/Modal/Volume/Labels.tsx index 9838a34..710b9e5 100644 --- a/services/frontend/src/components/Modal/Volume/Labels.tsx +++ b/services/frontend/src/components/Modal/Volume/Labels.tsx @@ -1,6 +1,4 @@ -const Labels = (props: any) => { - const { formik } = props; - +const Labels = () => { return <>; }; export default Labels; diff --git a/services/frontend/src/components/Project/index.tsx b/services/frontend/src/components/Project/index.tsx index ec7b9a2..19ffb9f 100644 --- a/services/frontend/src/components/Project/index.tsx +++ b/services/frontend/src/components/Project/index.tsx @@ -245,9 +245,9 @@ export default function Project() { if (existingIndex !== -1) { _connections.splice(existingIndex, 1); + setConnections(_connections); + stateConnectionsRef.current = _connections; } - - setConnections(_connections); }; const onConnectionAttached = (data: any) => { diff --git a/services/frontend/src/components/global/FormElements/InputField.tsx b/services/frontend/src/components/global/FormElements/InputField.tsx new file mode 100644 index 0000000..f3d0480 --- /dev/null +++ b/services/frontend/src/components/global/FormElements/InputField.tsx @@ -0,0 +1,50 @@ +import _ from "lodash"; +import { useFormikContext } from "formik"; + +interface Props { + name: string; + help?: string; + [key: string]: any; +} + +const TextField = (props: Props) => { + const { label, name, help, ...otherProps } = props; + const formik = useFormikContext(); + const error = _.get(formik.touched, name) && _.get(formik.errors, name); + + return ( +
+
+
+ +
+ + { +
+ {error &&
{error}
} + {!error && help} +
+ } +
+
+
+
+ ); +}; + +export default TextField;