diff --git a/src/fireedge/package.json b/src/fireedge/package.json
index 8a1ccbf9b9..f04f30550d 100644
--- a/src/fireedge/package.json
+++ b/src/fireedge/package.json
@@ -41,6 +41,7 @@
"compression": "^1.7.4",
"concurrently": "^5.2.0",
"cors": "^2.8.5",
+ "dagre": "^0.8.5",
"express": "^4.17.1",
"fs-extra": "^9.0.1",
"fuse.js": "^6.4.1",
diff --git a/src/fireedge/src/public/components/Cards/RoleCard.js b/src/fireedge/src/public/components/Cards/RoleCard.js
deleted file mode 100644
index d6209ea453..0000000000
--- a/src/fireedge/src/public/components/Cards/RoleCard.js
+++ /dev/null
@@ -1,98 +0,0 @@
-import React from 'react';
-
-import {
- makeStyles,
- Card,
- Button,
- CardHeader,
- CardActions,
- Badge,
- Fade
-} from '@material-ui/core';
-import DesktopWindowsIcon from '@material-ui/icons/DesktopWindows';
-
-import { Tr } from 'client/components/HOC';
-
-const useStyles = makeStyles(theme => ({
- root: {
- height: '100%',
- minHeight: 140,
- display: 'flex',
- flexDirection: 'column'
- },
- header: {
- overflowX: 'hidden',
- flexGrow: 1
- },
- headerContent: {},
- title: {
- overflow: 'hidden',
- textOverflow: 'ellipsis',
- whiteSpace: 'initial',
- display: '-webkit-box',
- lineClamp: 2,
- boxOrient: 'vertical'
- }
-}));
-
-const RoleCard = React.memo(
- ({ info, handleEdit, handleClone, handleRemove }) => {
- const classes = useStyles();
- const {
- name = 'Role name',
- cardinality,
- vm_template = 0,
- elasticity_policies,
- scheduled_policies
- } = info;
-
- return (
-
-
-
-
-
- }
- className={classes.header}
- classes={{ content: classes.headerContent }}
- title={name}
- titleTypographyProps={{
- variant: 'body2',
- noWrap: true,
- className: classes.title,
- title: name
- }}
- subheader={`Template id: ${vm_template}`}
- subheaderTypographyProps={{
- variant: 'body2',
- noWrap: true,
- title: `Template id: ${vm_template}`
- }}
- />
-
-
-
-
-
-
-
- );
- }
-);
-
-export default RoleCard;
diff --git a/src/fireedge/src/public/components/Cards/TierCard.js b/src/fireedge/src/public/components/Cards/TierCard.js
new file mode 100644
index 0000000000..4d982b872c
--- /dev/null
+++ b/src/fireedge/src/public/components/Cards/TierCard.js
@@ -0,0 +1,89 @@
+import React from 'react';
+
+import {
+ makeStyles,
+ Card,
+ Button,
+ CardHeader,
+ CardActions,
+ Badge
+} from '@material-ui/core';
+import DesktopWindowsIcon from '@material-ui/icons/DesktopWindows';
+
+import { Tr } from 'client/components/HOC';
+
+const useStyles = makeStyles(() => ({
+ root: {
+ height: '100%',
+ minHeight: 140,
+ display: 'flex',
+ flexDirection: 'column'
+ },
+ header: {
+ overflowX: 'hidden',
+ flexGrow: 1
+ },
+ headerContent: {},
+ title: {
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ whiteSpace: 'initial',
+ display: '-webkit-box',
+ lineClamp: 2,
+ boxOrient: 'vertical'
+ }
+}));
+
+const TierCard = React.memo(
+ ({ values, handleEdit, handleClone, handleRemove, cardProps }) => {
+ const classes = useStyles();
+ const { name = 'Tier name', cardinality } = values;
+
+ return (
+
+
+
+
+ }
+ className={classes.header}
+ classes={{ content: classes.headerContent }}
+ title={name}
+ titleTypographyProps={{
+ variant: 'body2',
+ noWrap: true,
+ className: classes.title,
+ title: name
+ }}
+ />
+
+ {handleEdit && (
+
+ )}
+ {handleClone && (
+
+ )}
+ {handleRemove && (
+
+ )}
+
+
+ );
+ }
+);
+
+export default TierCard;
diff --git a/src/fireedge/src/public/components/Cards/index.js b/src/fireedge/src/public/components/Cards/index.js
index 0b168c2cd4..b6caee1ede 100644
--- a/src/fireedge/src/public/components/Cards/index.js
+++ b/src/fireedge/src/public/components/Cards/index.js
@@ -1,7 +1,7 @@
import ClusterCard from 'client/components/Cards/ClusterCard';
import NetworkCard from 'client/components/Cards/NetworkCard';
-import RoleCard from 'client/components/Cards/RoleCard';
+import TierCard from 'client/components/Cards/TierCard';
import EmptyCard from 'client/components/Cards/EmptyCard';
import SelectCard from 'client/components/Cards/SelectCard';
-export { ClusterCard, NetworkCard, RoleCard, EmptyCard, SelectCard };
+export { ClusterCard, NetworkCard, TierCard, EmptyCard, SelectCard };
diff --git a/src/fireedge/src/public/components/SpeedDials/index.js b/src/fireedge/src/public/components/SpeedDials/index.js
new file mode 100644
index 0000000000..5acecdbe0b
--- /dev/null
+++ b/src/fireedge/src/public/components/SpeedDials/index.js
@@ -0,0 +1,72 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { SpeedDial, SpeedDialIcon, SpeedDialAction } from '@material-ui/lab';
+import { makeStyles } from '@material-ui/core/styles';
+
+const useStyles = makeStyles(theme => ({
+ root: {
+ position: 'absolute',
+ '&.MuiSpeedDial-directionUp, &.MuiSpeedDial-directionLeft': {
+ bottom: theme.spacing(2),
+ right: theme.spacing(2)
+ },
+ '&.MuiSpeedDial-directionDown, &.MuiSpeedDial-directionRight': {
+ top: theme.spacing(2),
+ left: theme.spacing(2)
+ }
+ }
+}));
+
+const SpeedDials = ({ hidden = false, actions = [] }) => {
+ const classes = useStyles();
+ const [open, setOpen] = React.useState(false);
+
+ const handleClose = () => {
+ setOpen(false);
+ };
+
+ const handleOpen = () => {
+ setOpen(true);
+ };
+
+ return (
+ }
+ onClose={handleClose}
+ onOpen={handleOpen}
+ open={open}
+ direction="up"
+ >
+ {actions?.map(action => (
+
+ ))}
+
+ );
+};
+
+SpeedDials.propTypes = {
+ hidden: PropTypes.bool,
+ actions: PropTypes.arrayOf(
+ PropTypes.shape({
+ name: PropTypes.string.isRequired,
+ icon: PropTypes.node.isRequired,
+ handleClick: PropTypes.func
+ })
+ )
+};
+
+SpeedDials.defaultProps = {
+ hidden: false,
+ actions: []
+};
+
+export default SpeedDials;
diff --git a/src/fireedge/src/public/containers/Application/Create/Steps/Roles/index.js b/src/fireedge/src/public/containers/Application/Create/Steps/Roles/index.js
deleted file mode 100644
index 4b421d9f5d..0000000000
--- a/src/fireedge/src/public/containers/Application/Create/Steps/Roles/index.js
+++ /dev/null
@@ -1,125 +0,0 @@
-import React, { useEffect, useState, useCallback } from 'react';
-
-import * as yup from 'yup';
-import { useWatch } from 'react-hook-form';
-
-import useListForm from 'client/hooks/useListForm';
-import FormStepper from 'client/components/FormStepper';
-import { DialogForm } from 'client/components/Dialogs';
-import FlowWithFAB from 'client/components/Flows/FlowWithFAB';
-
-import Steps from './Steps';
-
-export const Context = React.createContext({});
-export const STEP_ID = 'tiers';
-
-const Roles = () => {
- const { steps, defaultValues, resolvers } = Steps();
-
- return {
- id: STEP_ID,
- label: 'Tier Definition',
- DEFAULT_DATA: defaultValues,
- resolver: yup
- .array()
- .of(resolvers)
- .min(1)
- .required()
- .default([]),
- content: useCallback(({ data, setFormData }) => {
- const [showDialog, setShowDialog] = useState(false);
- const [nestedForm, setNestedForm] = useState({});
- const form = useWatch({});
-
- const { editingData, handleEdit, handleSave } = useListForm({
- key: STEP_ID,
- list: data,
- setList: setFormData,
- defaultValue: defaultValues
- });
-
- useEffect(() => {
- setNestedForm(form);
- }, []);
-
- return (
- <>
-
-
-
{JSON.stringify(data)}
-
- {showDialog && (
-
- setShowDialog(false)}
- >
-
- {
- handleSave(values);
- setShowDialog(false);
- }}
- />
-
-
-
- )}
- >
- );
- }, [])
- /* DialogComponent: ({ values, onSubmit, onCancel, ...props }) => {
- const form = useWatch({});
- const [nestedForm, setNestedForm] = useState({});
-
- useEffect(() => {
- setNestedForm(form);
- }, []);
-
- return (
-
-
-
-
-
-
-
- ); */
- };
-};
-
-export default Roles;
diff --git a/src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Flow/CustomNode.js b/src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Flow/CustomNode.js
new file mode 100644
index 0000000000..2911378328
--- /dev/null
+++ b/src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Flow/CustomNode.js
@@ -0,0 +1,46 @@
+import React, { memo } from 'react';
+import PropTypes from 'prop-types';
+
+import { Handle } from 'react-flow-renderer';
+
+import { TierCard } from 'client/components/Cards';
+
+const CustomNode = memo(({ data }) => {
+ const { tier, handleEdit } = data;
+
+ const isValidConnection = ({ target }) => !tier?.parents?.includes(target);
+
+ return (
+ <>
+
+
+
+ >
+ );
+});
+
+CustomNode.propTypes = {
+ data: PropTypes.objectOf(PropTypes.any)
+};
+
+CustomNode.defaultProps = {
+ data: {}
+};
+
+export default CustomNode;
diff --git a/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/BasicConfiguration/index.js b/src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Steps/BasicConfiguration/index.js
similarity index 93%
rename from src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/BasicConfiguration/index.js
rename to src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Steps/BasicConfiguration/index.js
index bf704fde68..44b2dba61b 100644
--- a/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/BasicConfiguration/index.js
+++ b/src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Steps/BasicConfiguration/index.js
@@ -4,7 +4,7 @@ import FormWithSchema from 'client/components/Forms/FormWithSchema';
import { FORM_FIELDS, STEP_FORM_SCHEMA } from './schema';
-export const STEP_ID = 'role';
+export const STEP_ID = 'tier';
const BasicConfiguration = () => ({
id: STEP_ID,
diff --git a/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/BasicConfiguration/schema.js b/src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Steps/BasicConfiguration/schema.js
similarity index 100%
rename from src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/BasicConfiguration/schema.js
rename to src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Steps/BasicConfiguration/schema.js
diff --git a/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Networks/index.js b/src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Steps/Networks/index.js
similarity index 99%
rename from src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Networks/index.js
rename to src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Steps/Networks/index.js
index bb67ae262f..1c81536dc4 100644
--- a/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Networks/index.js
+++ b/src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Steps/Networks/index.js
@@ -5,7 +5,7 @@ import ListCards from 'client/components/List/ListCards';
import { SelectCard } from 'client/components/Cards';
import { STEP_ID as NETWORKING } from 'client/containers/Application/Create/Steps/Networking';
-import { Context } from 'client/containers/Application/Create/Steps/Roles';
+import { Context } from 'client/containers/Application/Create/Steps/Tiers';
import { STEP_FORM_SCHEMA } from './schema';
export const STEP_ID = 'networks';
diff --git a/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Networks/schema.js b/src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Steps/Networks/schema.js
similarity index 100%
rename from src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Networks/schema.js
rename to src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Steps/Networks/schema.js
diff --git a/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Policies/index.js b/src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Steps/Policies/index.js
similarity index 100%
rename from src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Policies/index.js
rename to src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Steps/Policies/index.js
diff --git a/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Policies/schema.js b/src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Steps/Policies/schema.js
similarity index 100%
rename from src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Policies/schema.js
rename to src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Steps/Policies/schema.js
diff --git a/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/List/Docker.js b/src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Steps/Template/List/Docker.js
similarity index 100%
rename from src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/List/Docker.js
rename to src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Steps/Template/List/Docker.js
diff --git a/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/List/MarketApps.js b/src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Steps/Template/List/MarketApps.js
similarity index 100%
rename from src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/List/MarketApps.js
rename to src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Steps/Template/List/MarketApps.js
diff --git a/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/List/Templates.js b/src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Steps/Template/List/Templates.js
similarity index 100%
rename from src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/List/Templates.js
rename to src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Steps/Template/List/Templates.js
diff --git a/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/index.js b/src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Steps/Template/index.js
similarity index 97%
rename from src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/index.js
rename to src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Steps/Template/index.js
index 2b68f3fefb..bd56a56556 100644
--- a/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/index.js
+++ b/src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Steps/Template/index.js
@@ -58,7 +58,7 @@ const Template = () => ({
useEffect(() => {
if (Object.keys(data).length > 0) {
const currentScreen = Object.keys(data)[0];
- setScreen(SCREENS.find(src => src.id === currentScreen.id));
+ setScreen(SCREENS.find(src => src.id === currentScreen));
}
}, []);
diff --git a/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/schema.js b/src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Steps/Template/schema.js
similarity index 100%
rename from src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/schema.js
rename to src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Steps/Template/schema.js
diff --git a/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/index.js b/src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Steps/index.js
similarity index 100%
rename from src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/index.js
rename to src/fireedge/src/public/containers/Application/Create/Steps/Tiers/Steps/index.js
diff --git a/src/fireedge/src/public/containers/Application/Create/Steps/Tiers/index.js b/src/fireedge/src/public/containers/Application/Create/Steps/Tiers/index.js
new file mode 100644
index 0000000000..e6ece56aa7
--- /dev/null
+++ b/src/fireedge/src/public/containers/Application/Create/Steps/Tiers/index.js
@@ -0,0 +1,158 @@
+import React, { useEffect, useState, useCallback, createContext } from 'react';
+
+import * as yup from 'yup';
+import { useWatch } from 'react-hook-form';
+
+import { Add as AddIcon, Refresh as RefreshIcon } from '@material-ui/icons';
+import ReactFlow, {
+ ReactFlowProvider,
+ Background,
+ addEdge
+} from 'react-flow-renderer';
+import dagre from 'dagre';
+
+import useListForm from 'client/hooks/useListForm';
+import FormStepper from 'client/components/FormStepper';
+import { DialogForm } from 'client/components/Dialogs';
+import { generateFlow } from 'client/utils/flow';
+import SpeedDials from 'client/components/SpeedDials';
+
+import Steps from './Steps';
+import CustomNode from './Flow/CustomNode';
+
+export const Context = createContext({});
+export const STEP_ID = 'tiers';
+
+const Tiers = () => {
+ const { steps, defaultValues, resolvers } = Steps();
+
+ return {
+ id: STEP_ID,
+ label: 'Tier Definition',
+ resolver: yup
+ .array()
+ .of(resolvers)
+ .min(1)
+ .required()
+ .default([]),
+ content: useCallback(({ data, setFormData }) => {
+ const [flow, setFlow] = useState([]);
+ const [showDialog, setShowDialog] = useState(false);
+ const [nestedForm, setNestedForm] = useState({});
+ const form = useWatch({});
+
+ const { editingData, handleEdit, handleSave } = useListForm({
+ key: STEP_ID,
+ list: data,
+ setList: setFormData,
+ defaultValue: defaultValues
+ });
+
+ const graph = new dagre.graphlib.Graph();
+ graph.setGraph({});
+ graph.setDefaultEdgeLabel(() => ({}));
+
+ const reDrawFlow = () => {
+ setFlow(
+ generateFlow(
+ graph,
+ data?.map((item, index) => ({
+ id: item.tier.name,
+ type: 'tier',
+ data: {
+ ...item,
+ handleEdit: () => {
+ handleEdit(index);
+ setShowDialog(true);
+ }
+ },
+ parents: item.tier.parents ?? []
+ })) ?? []
+ )
+ );
+ };
+
+ useEffect(() => {
+ setNestedForm(form);
+ }, []);
+
+ const actions = [
+ {
+ icon: ,
+ name: 'Add',
+ handleClick: () => {
+ handleEdit();
+ setShowDialog(true);
+ }
+ },
+ {
+ icon: ,
+ name: 'Refresh',
+ handleClick: () => reDrawFlow()
+ }
+ ];
+
+ return (
+
+
+ {
+ const indexChild = data?.findIndex(
+ item => item?.tier?.name === target
+ );
+ const child = { ...data[indexChild] };
+ child.tier.parents = [...(child?.tier?.parents ?? []), source];
+
+ handleEdit(indexChild);
+ handleSave(child);
+ setFlow(prevFlow =>
+ addEdge({ source, target, animated: true }, prevFlow)
+ );
+ }}
+ onLoad={reactFlowInstance => {
+ reDrawFlow();
+ reactFlowInstance.fitView();
+ }}
+ >
+
+
+
+
+ {showDialog && (
+
+ setShowDialog(false)}
+ >
+
+ {
+ handleSave(values);
+ setShowDialog(false);
+ reDrawFlow();
+ }}
+ />
+
+
+
+ )}
+
+ );
+ }, [])
+ };
+};
+
+export default Tiers;
diff --git a/src/fireedge/src/public/containers/Application/Create/Steps/index.js b/src/fireedge/src/public/containers/Application/Create/Steps/index.js
index 70d810977b..88d4cb40cf 100644
--- a/src/fireedge/src/public/containers/Application/Create/Steps/index.js
+++ b/src/fireedge/src/public/containers/Application/Create/Steps/index.js
@@ -3,21 +3,21 @@ import * as yup from 'yup';
import BasicConfiguration from './BasicConfiguration';
import Clusters from './Clusters';
import Networking from './Networking';
-import Roles from './Roles';
+import Tiers from './Tiers';
const Steps = () => {
const basic = BasicConfiguration();
const clusters = Clusters();
const networking = Networking();
- const roles = Roles();
+ const tiers = Tiers();
- const steps = [basic, clusters, networking, roles];
+ const steps = [basic, clusters, networking, tiers];
const resolvers = yup.object({
[basic.id]: basic.resolver,
[clusters.id]: clusters.resolver,
[networking.id]: networking.resolver,
- [roles.id]: roles.resolver
+ [tiers.id]: tiers.resolver
});
const defaultValues = resolvers.default();
diff --git a/src/fireedge/src/public/hooks/useList.js b/src/fireedge/src/public/hooks/useList.js
index 7e9bb0963f..7d6aa4ce33 100644
--- a/src/fireedge/src/public/hooks/useList.js
+++ b/src/fireedge/src/public/hooks/useList.js
@@ -16,11 +16,9 @@ function useList({ list, initLength }) {
if (list?.length === 0 || shortList.length !== 0) return;
setLoading(true);
-
- fakeDelay(200)
- .then(() => setFullList(list))
- .then(() => setShortList(list.slice(0, initLength)))
- .then(() => setLoading(false));
+ setFullList(list);
+ setShortList(list.slice(0, initLength));
+ setLoading(false);
}, [list]);
useEffect(() => {
diff --git a/src/fireedge/src/public/hooks/useListForm.js b/src/fireedge/src/public/hooks/useListForm.js
index 49bcfbf356..0f141f13a3 100644
--- a/src/fireedge/src/public/hooks/useListForm.js
+++ b/src/fireedge/src/public/hooks/useListForm.js
@@ -1,51 +1,69 @@
-import { useState } from 'react';
+import { useCallback, useState } from 'react';
const useListSelect = ({ multiple, key, list, setList, defaultValue }) => {
const [editingData, setEditingData] = useState({});
- const handleSelect = index =>
- setList(prevData => ({
- ...prevData,
- [key]: multiple ? [...(prevData[key] ?? []), index] : [index]
- }));
+ const handleSelect = useCallback(
+ index =>
+ setList(prevData => ({
+ ...prevData,
+ [key]: multiple ? [...(prevData[key] ?? []), index] : [index]
+ })),
+ [key, list, multiple]
+ );
- const handleUnselect = indexRemove =>
- setList(prevData => ({
- ...prevData,
- [key]: prevData[key]?.filter(index => index !== indexRemove)
- }));
+ const handleUnselect = useCallback(
+ indexRemove =>
+ setList(prevData => ({
+ ...prevData,
+ [key]: prevData[key]?.filter(index => index !== indexRemove)
+ })),
+ [key, list]
+ );
- const handleSave = values => {
- setList(prevData => ({
- ...prevData,
- [key]: Object.assign(prevData[key], {
- [editingData.index]: values
- })
- }));
- };
+ const handleSave = useCallback(
+ values => {
+ setList(prevData => ({
+ ...prevData,
+ [key]: Object.assign(prevData[key], {
+ [editingData.index]: values
+ })
+ }));
+ },
+ [key, list, editingData]
+ );
- const handleEdit = (index = list?.length) => {
- const openData = list[index] ?? defaultValue;
+ const handleEdit = useCallback(
+ (index = list?.length) => {
+ const openData = list[index] ?? defaultValue;
- setEditingData({ index, data: openData });
- };
+ setEditingData({ index, data: openData });
+ },
+ [list, defaultValue]
+ );
- const handleClone = index => {
- const item = list[index];
- const cloneItem = { ...item, name: `${item?.name}_clone` };
- const cloneData = [...list];
- cloneData.splice(index + 1, 0, cloneItem);
+ const handleClone = useCallback(
+ index => {
+ const item = list[index];
+ const cloneItem = { ...item, name: `${item?.name}_clone` };
+ const cloneData = [...list];
+ cloneData.splice(index + 1, 0, cloneItem);
- setList(prevData => ({ ...prevData, [key]: cloneData }));
- };
+ setList(prevData => ({ ...prevData, [key]: cloneData }));
+ },
+ [list]
+ );
- const handleRemove = indexRemove => {
- // TODO confirmation??
- setList(prevData => ({
- ...prevData,
- [key]: prevData[key]?.filter((_, index) => index !== indexRemove)
- }));
- };
+ const handleRemove = useCallback(
+ indexRemove => {
+ // TODO confirmation??
+ setList(prevData => ({
+ ...prevData,
+ [key]: prevData[key]?.filter((_, index) => index !== indexRemove)
+ }));
+ },
+ [key, list]
+ );
return {
editingData,
diff --git a/src/fireedge/src/public/utils/flow.js b/src/fireedge/src/public/utils/flow.js
new file mode 100644
index 0000000000..b39f148847
--- /dev/null
+++ b/src/fireedge/src/public/utils/flow.js
@@ -0,0 +1,44 @@
+import dagre from 'dagre';
+
+const generateFlow = (graph, elements = []) => {
+ const NODE_WIDTH = 400;
+ const NODE_HEIGHT = 200;
+
+ elements.forEach(({ id, type, data, parents = [] }) => {
+ graph.setNode(id, {
+ data,
+ type: type ?? 'default',
+ width: NODE_WIDTH,
+ height: NODE_HEIGHT
+ });
+ parents.forEach(parent => {
+ graph.setEdge(parent, id);
+ });
+ });
+
+ dagre.layout(graph);
+
+ const nodes = graph.nodes().map(id => {
+ const node = graph.node(id);
+ return {
+ id,
+ type: node?.type,
+ data: node?.data,
+ position: {
+ x: node.x - node.width / 2,
+ y: node.y - node.height / 2
+ }
+ };
+ });
+
+ const edges = graph.edges().map(({ v: source, w: target }) => ({
+ id: `__${source}__${target}`,
+ source,
+ target,
+ animated: true
+ }));
+
+ return [...nodes, ...edges];
+};
+
+export { generateFlow };