diff --git a/src/fireedge/src/public/components/Cards/ClusterCard.js b/src/fireedge/src/public/components/Cards/ClusterCard.js
new file mode 100644
index 0000000000..7d31f4c3c9
--- /dev/null
+++ b/src/fireedge/src/public/components/Cards/ClusterCard.js
@@ -0,0 +1,131 @@
+import React from 'react';
+import clsx from 'clsx';
+
+import {
+ makeStyles,
+ Card,
+ CardHeader,
+ Fade,
+ CardActionArea,
+ CardContent,
+ Badge,
+ Box
+} from '@material-ui/core';
+import StorageIcon from '@material-ui/icons/Storage';
+import VideogameAssetIcon from '@material-ui/icons/VideogameAsset';
+import AccountTreeIcon from '@material-ui/icons/AccountTree';
+import FolderOpenIcon from '@material-ui/icons/FolderOpen';
+
+import { Tr } from 'client/components/HOC';
+
+const useStyles = makeStyles(theme => ({
+ root: {
+ height: '100%',
+ minHeight: 140,
+ display: 'flex',
+ flexDirection: 'column'
+ },
+ selected: {
+ color: theme.palette.primary.contrastText,
+ backgroundColor: theme.palette.primary.main,
+ '& $badge': {
+ color: theme.palette.primary.main,
+ backgroundColor: theme.palette.common.white
+ }
+ },
+ actionArea: {
+ height: '100%'
+ },
+ header: {
+ overflowX: 'hidden',
+ flexGrow: 1
+ },
+ subheader: {
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ whiteSpace: 'initial',
+ display: '-webkit-box',
+ lineClamp: 2,
+ boxOrient: 'vertical'
+ },
+ badgesWrapper: {
+ display: 'flex',
+ gap: theme.typography.pxToRem(12)
+ },
+ badge: {},
+ icon: {}
+}));
+
+const ClusterCard = React.memo(
+ ({ info, isSelected, handleSelect, handleUnselect }) => {
+ const classes = useStyles();
+ const { ID, NAME, HOSTS, VNETS, DATASTORES } = info;
+
+ const hosts = [HOSTS?.ID ?? []].flat();
+ const vnets = [VNETS?.ID ?? []].flat();
+ const datastores = [DATASTORES?.ID ?? []].flat();
+
+ const badgePosition = { vertical: 'top', horizontal: 'right' };
+
+ return (
+
+
+ (isSelected ? handleUnselect(ID) : handleSelect(ID))}
+ >
+ }
+ className={classes.header}
+ classes={{ content: classes.headerContent }}
+ title={NAME}
+ titleTypographyProps={{
+ variant: 'body2',
+ noWrap: true,
+ title: NAME
+ }}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+);
+
+export default ClusterCard;
diff --git a/src/fireedge/src/public/components/Cards/RoleCard.js b/src/fireedge/src/public/components/Cards/RoleCard.js
index 6cb7445efd..d6209ea453 100644
--- a/src/fireedge/src/public/components/Cards/RoleCard.js
+++ b/src/fireedge/src/public/components/Cards/RoleCard.js
@@ -46,31 +46,21 @@ const RoleCard = React.memo(
scheduled_policies
} = info;
- const ConditionalWrapper = ({ condition, wrapper, children }) =>
- condition ? wrapper(children) : children;
- console.log(info);
return (
1}
- wrapper={children => (
-
- {children}
-
- )}
+
-
+
}
className={classes.header}
classes={{ content: classes.headerContent }}
@@ -92,6 +82,9 @@ const RoleCard = React.memo(
+
diff --git a/src/fireedge/src/public/components/Cards/index.js b/src/fireedge/src/public/components/Cards/index.js
index 81d55ce04a..cc45f28981 100644
--- a/src/fireedge/src/public/components/Cards/index.js
+++ b/src/fireedge/src/public/components/Cards/index.js
@@ -1,4 +1,5 @@
-import NetworkCard from './NetworkCard';
-import RoleCard from './RoleCard';
+import ClusterCard from 'client/components/Cards/ClusterCard';
+import NetworkCard from 'client/components/Cards/NetworkCard';
+import RoleCard from 'client/components/Cards/RoleCard';
-export { NetworkCard, RoleCard };
+export { ClusterCard, NetworkCard, RoleCard };
diff --git a/src/fireedge/src/public/components/Dialogs/index.js b/src/fireedge/src/public/components/Dialogs/index.js
index 537beed14d..25126df23d 100644
--- a/src/fireedge/src/public/components/Dialogs/index.js
+++ b/src/fireedge/src/public/components/Dialogs/index.js
@@ -1,4 +1,4 @@
-import NetworkDialog from './NetworkDialog';
-import RoleDialog from './RoleDialog';
+import NetworkDialog from 'client/components/Dialogs/NetworkDialog';
+import RoleDialog from 'client/components/Dialogs/RoleDialog';
export { NetworkDialog, RoleDialog };
diff --git a/src/fireedge/src/public/components/FormControl/ErrorHelper.js b/src/fireedge/src/public/components/FormControl/ErrorHelper.js
index d2ff911474..c38dc9d3e3 100644
--- a/src/fireedge/src/public/components/FormControl/ErrorHelper.js
+++ b/src/fireedge/src/public/components/FormControl/ErrorHelper.js
@@ -10,8 +10,6 @@ import {
} from '@material-ui/core';
import { Info as InfoIcon } from '@material-ui/icons';
-import { Translate } from 'client/components/HOC';
-
const useStyles = makeStyles(theme => {
const getColor = theme.palette.type === 'light' ? darken : lighten;
const getBackgroundColor = theme.palette.type === 'light' ? lighten : darken;
@@ -37,9 +35,13 @@ const ErrorHelper = ({ label, ...rest }) => {
const classes = useStyles();
return (
-
+
-
+
{label}
diff --git a/src/fireedge/src/public/components/FormControl/SubmitButton.js b/src/fireedge/src/public/components/FormControl/SubmitButton.js
index 86c6d1a85b..ff60e11b9b 100644
--- a/src/fireedge/src/public/components/FormControl/SubmitButton.js
+++ b/src/fireedge/src/public/components/FormControl/SubmitButton.js
@@ -3,8 +3,8 @@ import PropTypes from 'prop-types';
import { makeStyles, CircularProgress, Button } from '@material-ui/core';
+import { Submit } from 'client/constants/translates';
import { Tr } from 'client/components/HOC';
-import * as CONSTANT from 'client/constants';
const useStyles = makeStyles(theme => ({
button: {
@@ -13,7 +13,7 @@ const useStyles = makeStyles(theme => ({
}
}));
-const ButtonSubmit = ({ isSubmitting, label, ...rest }) => {
+const SubmitButton = ({ isSubmitting, label, ...rest }) => {
const classes = useStyles();
return (
@@ -26,19 +26,19 @@ const ButtonSubmit = ({ isSubmitting, label, ...rest }) => {
{...rest}
>
{isSubmitting && }
- {!isSubmitting && Tr(label)}
+ {!isSubmitting && (label ?? Tr(Submit))}
);
};
-ButtonSubmit.propTypes = {
+SubmitButton.propTypes = {
isSubmitting: PropTypes.bool,
label: PropTypes.string
};
-ButtonSubmit.defaultProps = {
+SubmitButton.defaultProps = {
isSubmitting: false,
- label: CONSTANT.default.Submit
+ label: undefined
};
-export default ButtonSubmit;
+export default SubmitButton;
diff --git a/src/fireedge/src/public/components/FormStepper/FormDialog.js b/src/fireedge/src/public/components/FormStepper/FormDialog.js
new file mode 100644
index 0000000000..9cebd7a399
--- /dev/null
+++ b/src/fireedge/src/public/components/FormStepper/FormDialog.js
@@ -0,0 +1,128 @@
+import React, { useEffect, useState } from 'react';
+import PropTypes from 'prop-types';
+
+import {
+ makeStyles,
+ Box,
+ CardActionArea,
+ CardContent,
+ Card,
+ Grid
+} from '@material-ui/core';
+import { Add } from '@material-ui/icons';
+
+const useStyles = makeStyles(() => ({
+ cardPlus: {
+ height: '100%',
+ minHeight: 140,
+ display: 'flex',
+ textAlign: 'center'
+ }
+}));
+
+function FormDialog({ step, data, setFormData }) {
+ const classes = useStyles();
+ const [dialogFormData, setDialogFormData] = useState({});
+ const [showDialog, setShowDialog] = useState(false);
+
+ const {
+ id,
+ addCardAction,
+ preRender,
+ InfoComponent,
+ DialogComponent,
+ DEFAULT_DATA
+ } = step;
+
+ useEffect(() => {
+ preRender && preRender();
+ }, []);
+
+ const handleSubmit = values => {
+ setFormData(prevData => ({
+ ...prevData,
+ [id]: Object.assign(prevData[id], {
+ [dialogFormData.index]: values
+ })
+ }));
+
+ setShowDialog(false);
+ };
+
+ const handleOpen = (index = data?.length) => {
+ const openData = data[index] ?? DEFAULT_DATA;
+
+ setDialogFormData({ index, data: openData });
+ setShowDialog(true);
+ };
+
+ const handleClone = index => {
+ const item = data[index];
+ const cloneItem = { ...item, name: `${item?.name}_clone` };
+ const cloneData = [...data];
+ cloneData.splice(index + 1, 0, cloneItem);
+
+ setFormData(prevData => ({ ...prevData, [id]: cloneData }));
+ };
+
+ const handleRemove = indexRemove => {
+ // TODO confirmation??
+ setFormData(prevData => ({
+ ...prevData,
+ [id]: prevData[id]?.filter((_, index) => index !== indexRemove)
+ }));
+ };
+
+ const handleClose = () => setShowDialog(false);
+
+ return (
+
+
+ {addCardAction && (
+
+
+ handleOpen()}>
+
+
+
+
+
+
+ )}
+ {Array.isArray(data) &&
+ data?.map((info, index) => (
+
+ handleOpen(index)}
+ handleClone={() => handleClone(index)}
+ handleRemove={() => handleRemove(index)}
+ />
+
+ ))}
+
+ {showDialog && DialogComponent && (
+
+ )}
+
+ );
+}
+
+FormDialog.propTypes = {
+ step: PropTypes.objectOf(PropTypes.any).isRequired,
+ data: PropTypes.arrayOf(PropTypes.object).isRequired,
+ setFormData: PropTypes.func.isRequired
+};
+
+FormDialog.defaultProps = {
+ step: {},
+ data: [],
+ setFormData: data => data
+};
+
+export default FormDialog;
diff --git a/src/fireedge/src/public/components/FormStepper/FormListSelect.js b/src/fireedge/src/public/components/FormStepper/FormListSelect.js
new file mode 100644
index 0000000000..e9e433574c
--- /dev/null
+++ b/src/fireedge/src/public/components/FormStepper/FormListSelect.js
@@ -0,0 +1,60 @@
+import React, { useEffect } from 'react';
+import PropTypes from 'prop-types';
+
+import { Box, Grid } from '@material-ui/core';
+
+function FormListSelect({ step, data, setFormData }) {
+ const { id, onlyOneSelect, preRender, list, InfoComponent } = step;
+
+ useEffect(() => {
+ preRender && preRender();
+ }, []);
+
+ const handleSelect = index => {
+ // select index => add select to data form
+ setFormData(prevData => ({
+ ...prevData,
+ [id]: onlyOneSelect ? [index] : [...prevData[id], index]
+ }));
+ };
+
+ const handleUnselect = indexRemove => {
+ // unselect index => remove selected from data form
+ setFormData(prevData => ({
+ ...prevData,
+ [id]: prevData[id]?.filter(index => index !== indexRemove)
+ }));
+ };
+
+ return (
+
+
+ {Array.isArray(list) &&
+ list?.map((info, index) => (
+
+ selected === info?.ID)}
+ handleSelect={handleSelect}
+ handleUnselect={handleUnselect}
+ />
+
+ ))}
+
+
+ );
+}
+
+FormListSelect.propTypes = {
+ step: PropTypes.objectOf(PropTypes.any).isRequired,
+ data: PropTypes.arrayOf(PropTypes.any).isRequired,
+ setFormData: PropTypes.func.isRequired
+};
+
+FormListSelect.defaultProps = {
+ step: {},
+ data: [],
+ setFormData: data => data
+};
+
+export default FormListSelect;
diff --git a/src/fireedge/src/public/components/FormStepper/FormStep.js b/src/fireedge/src/public/components/FormStepper/FormStep.js
index 1aad95efca..9872bdd230 100644
--- a/src/fireedge/src/public/components/FormStepper/FormStep.js
+++ b/src/fireedge/src/public/components/FormStepper/FormStep.js
@@ -1,118 +1,32 @@
-import React, { useEffect, useState } from 'react';
+import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { useFormContext } from 'react-hook-form';
-import {
- makeStyles,
- Box,
- CardActionArea,
- CardContent,
- Card,
- Grid
-} from '@material-ui/core';
-import { Add } from '@material-ui/icons';
-
-const useStyles = makeStyles(() => ({
- cardPlus: {
- height: '100%',
- minHeight: 140,
- display: 'flex',
- textAlign: 'center'
- }
-}));
+import { debounce } from '@material-ui/core';
function FormStep({ step, data, setFormData }) {
- const classes = useStyles();
- const [dialogFormData, setDialogFormData] = useState({});
- const [showDialog, setShowDialog] = useState(false);
- const { reset } = useFormContext();
-
- const { id, addAction, InfoComponent, DialogComponent, DEFAULT_DATA } = step;
- const { [id]: stepData } = data;
+ const { reset, errors } = useFormContext();
+ const { id, preRender, FormComponent } = step;
useEffect(() => {
- reset({ ...data }, { errors: true });
- }, [id, data]);
+ preRender && preRender();
+ reset({ [id]: data }, { errors: true });
+ }, []);
- const handleSubmit = values => {
- setFormData(prevData => ({
- ...prevData,
- [id]: Object.assign(prevData[id], {
- [dialogFormData.index]: values
- })
- }));
+ useEffect(() => {
+ // setFormData(prev => ({ ...prev, [id]: watch() }));
+ console.log('errors', errors);
+ }, [errors]);
- setShowDialog(false);
- };
+ const handleSubmit = dataForm => console.log(dataForm);
- const handleOpen = (index = stepData?.length) => {
- const openData = stepData[index] ?? DEFAULT_DATA;
-
- setDialogFormData({ index, data: openData });
- setShowDialog(true);
- };
-
- const handleClone = index => {
- const cloneData = { ...stepData[index], name: 'clone' };
-
- setFormData(prevData => {
- prevData[id].splice(index + 1, 0, cloneData);
- return prevData;
- });
- };
-
- const handleRemove = indexRemove => {
- // TODO confirmation??
- setFormData(prevData => ({
- ...prevData,
- [id]: prevData[id]?.filter((_, index) => index !== indexRemove)
- }));
- };
-
- const handleClose = () => setShowDialog(false);
-
- return (
-
-
- {addAction && (
-
-
- handleOpen()}>
-
-
-
-
-
-
- )}
- {Array.isArray(stepData) &&
- stepData?.map((info, index) => (
-
- handleOpen(index)}
- handleClone={() => handleClone(index)}
- handleRemove={() => handleRemove(index)}
- />
-
- ))}
-
- {showDialog && (
-
- )}
-
- );
+ return React.useMemo(() => , [id, errors]);
}
FormStep.propTypes = {
- step: PropTypes.objectOf(PropTypes.object).isRequired,
- data: PropTypes.objectOf(PropTypes.object).isRequired,
- setFormData: PropTypes.func
+ step: PropTypes.objectOf(PropTypes.any).isRequired,
+ data: PropTypes.oneOfType([PropTypes.array, PropTypes.object]).isRequired,
+ setFormData: PropTypes.func.isRequired
};
FormStep.defaultProps = {
diff --git a/src/fireedge/src/public/components/FormStepper/index.js b/src/fireedge/src/public/components/FormStepper/index.js
index 0c8dcfccf8..637fc0f0af 100644
--- a/src/fireedge/src/public/components/FormStepper/index.js
+++ b/src/fireedge/src/public/components/FormStepper/index.js
@@ -1,4 +1,4 @@
-import React, { useState, useMemo, useCallback } from 'react';
+import React, { useState, useMemo, useCallback, useEffect } from 'react';
import PropTypes from 'prop-types';
import { useFormContext } from 'react-hook-form';
@@ -12,7 +12,11 @@ const FIRST_STEP = 0;
const FormStepper = ({ steps, initialValue, onSubmit }) => {
const isMobile = useMediaQuery(theme => theme.breakpoints.only('xs'));
- const { watch } = useFormContext();
+ const {
+ watch,
+ formState: { isValid }
+ } = useFormContext();
+
const [activeStep, setActiveStep] = useState(FIRST_STEP);
const [formData, setFormData] = useState(initialValue);
@@ -20,19 +24,20 @@ const FormStepper = ({ steps, initialValue, onSubmit }) => {
const lastStep = useMemo(() => totalSteps - 1, [totalSteps]);
const disabledBack = useMemo(() => activeStep === FIRST_STEP, [activeStep]);
- const handleNext = useCallback(() => {
- setFormData(data => ({ ...data, ...watch() }));
-
+ const handleNext = () => {
+ // TODO check if errors
if (activeStep === lastStep) {
onSubmit(formData);
- } else setActiveStep(prevActiveStep => prevActiveStep + 1);
- }, [activeStep]);
+ } else if (isValid) {
+ setFormData(prevData => ({ ...prevData, ...watch() }));
+ setActiveStep(prevActiveStep => prevActiveStep + 1);
+ }
+ };
const handleBack = useCallback(() => {
if (activeStep <= FIRST_STEP) return;
setActiveStep(prevActiveStep => prevActiveStep - 1);
- setFormData(data => ({ ...data, ...watch() }));
}, [activeStep]);
return (
@@ -60,16 +65,19 @@ const FormStepper = ({ steps, initialValue, onSubmit }) => {
{/* FORM CONTENT */}
{React.useMemo(() => {
- const { content: Content, ...rest } = steps[activeStep];
+ const { id, content: Content } = steps[activeStep];
return (
-
+ Content && (
+
+ )
);
- }, [activeStep, formData, setFormData])}
+ }, [steps, formData, activeStep, setFormData])}
>
);
};
@@ -77,13 +85,12 @@ const FormStepper = ({ steps, initialValue, onSubmit }) => {
FormStepper.propTypes = {
steps: PropTypes.arrayOf(
PropTypes.shape({
- id: PropTypes.number.isRequired,
+ id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
label: PropTypes.string.isRequired,
- content: PropTypes.node.isRequired,
- dependOf: PropTypes.bool
+ content: PropTypes.func.isRequired
})
),
- initialValue: PropTypes.objectOf(PropTypes.object),
+ initialValue: PropTypes.objectOf(PropTypes.any),
onSubmit: PropTypes.func
};
diff --git a/src/fireedge/src/public/components/Forms/FormWithSchema.js b/src/fireedge/src/public/components/Forms/FormWithSchema.js
new file mode 100644
index 0000000000..b2f4a384c8
--- /dev/null
+++ b/src/fireedge/src/public/components/Forms/FormWithSchema.js
@@ -0,0 +1,69 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import {
+ Grid,
+ TextField,
+ MenuItem,
+ FormControlLabel,
+ Checkbox
+} from '@material-ui/core';
+import { useFormContext, Controller } from 'react-hook-form';
+
+import { TYPE_INPUT } from 'client/constants';
+import ErrorHelper from 'client/components/FormControl/ErrorHelper';
+
+const FormWithSchema = ({ id, cy, schema }) => {
+ const { register, control, errors } = useFormContext();
+
+ return (
+
+ {schema?.map(({ name, type, label, values }) => (
+
+ {(type === TYPE_INPUT.TEXT || type === TYPE_INPUT.SELECT) && (
+
+ )
+ }
+ FormHelperTextProps={{ 'data-cy': `${cy}-${name}-error` }}
+ >
+ {type === TYPE_INPUT.SELECT &&
+ values?.map(({ text, value }) => (
+
+ ))}
+
+ }
+ name={`${id}.${name}`}
+ control={control}
+ />
+ )}
+
+ ))}
+
+ );
+};
+
+FormWithSchema.propTypes = {
+ id: PropTypes.string,
+ cy: PropTypes.string,
+ schema: PropTypes.arrayOf(PropTypes.object)
+};
+
+FormWithSchema.defaultProps = {
+ id: '',
+ cy: 'form',
+ schema: []
+};
+
+export default FormWithSchema;
diff --git a/src/fireedge/src/public/components/Forms/index.js b/src/fireedge/src/public/components/Forms/index.js
new file mode 100644
index 0000000000..002de10459
--- /dev/null
+++ b/src/fireedge/src/public/components/Forms/index.js
@@ -0,0 +1,3 @@
+import FormWithSchema from 'client/components/Forms/FormWithSchema';
+
+export { FormWithSchema };
diff --git a/src/fireedge/src/public/components/LoadingScreen/index.js b/src/fireedge/src/public/components/LoadingScreen/index.js
index 473ecb709b..e65954b362 100644
--- a/src/fireedge/src/public/components/LoadingScreen/index.js
+++ b/src/fireedge/src/public/components/LoadingScreen/index.js
@@ -1,9 +1,9 @@
import React from 'react';
-import { styled } from '@material-ui/core';
+import { styled, Box } from '@material-ui/core';
import Logo from 'client/icons/logo';
-const ScreenBox = styled('div')({
+const ScreenBox = styled(Box)({
width: '100%',
height: '100vh',
backgroundColor: '#ffffff',
@@ -15,18 +15,7 @@ const ScreenBox = styled('div')({
});
const LoadingScreen = () => (
-
+
);
diff --git a/src/fireedge/src/public/containers/Application/Create/index.js b/src/fireedge/src/public/containers/Application/Create/index.js
index 8c772cfe06..998ac536ff 100644
--- a/src/fireedge/src/public/containers/Application/Create/index.js
+++ b/src/fireedge/src/public/containers/Application/Create/index.js
@@ -1,43 +1,31 @@
-import React, { useEffect } from 'react';
+import React from 'react';
import { useForm, FormProvider } from 'react-hook-form';
+import { yupResolver } from '@hookform/resolvers';
import FormStepper from 'client/components/FormStepper';
import Steps from 'client/containers/Application/Create/steps';
-import useOpennebula from 'client/hooks/useOpennebula';
-
-const INITIAL_VALUE = {
- networks: [],
- roles: []
-};
+import { Container } from '@material-ui/core';
function ApplicationCreate() {
- const { getVNetworks, getVNetworksTemplates, getTemplates } = useOpennebula();
+ const { steps, defaultValues } = Steps();
const methods = useForm({
- mode: 'onBlur'
+ mode: 'onBlur',
+ defaultValues
});
- const { watch, errors } = methods;
-
- useEffect(() => {
- getVNetworks();
- getVNetworksTemplates();
- getTemplates();
- }, []);
-
- useEffect(() => {
- // console.log('FORM CONTEXT', watch(), errors);
- }, [watch, errors]);
const onSubmit = formData => console.log('submit', formData);
return (
-
-
-
+
+
+
+
+
);
}
diff --git a/src/fireedge/src/public/containers/Application/Create/schema.js b/src/fireedge/src/public/containers/Application/Create/schema.js
new file mode 100644
index 0000000000..03d4767632
--- /dev/null
+++ b/src/fireedge/src/public/containers/Application/Create/schema.js
@@ -0,0 +1,49 @@
+import { TYPE_INPUT } from 'client/constants';
+
+export const STRATEGIES_DEPLOY = [
+ { text: 'None', value: 'none' },
+ { text: 'Straight', value: 'straight' }
+];
+
+export const SHUTDOWN_ACTIONS = [
+ { text: 'None', value: '' },
+ { text: 'Terminate', value: 'shutdown' },
+ { text: 'Terminate hard', value: 'shutdown-hard' }
+];
+
+export default [
+ {
+ name: 'name',
+ label: 'Name',
+ type: TYPE_INPUT.TEXT,
+ initial: ''
+ },
+ {
+ name: 'description',
+ label: 'Description',
+ type: TYPE_INPUT.TEXT,
+ multiline: true,
+ initial: ''
+ },
+ {
+ name: 'deployment',
+ label: 'Select a strategy',
+ type: TYPE_INPUT.SELECT,
+ initial: STRATEGIES_DEPLOY[1].value,
+ values: STRATEGIES_DEPLOY
+ },
+ {
+ name: 'shutdown_action',
+ label: 'Select a VM shutdown action',
+ type: TYPE_INPUT.SELECT,
+ initial: SHUTDOWN_ACTIONS[1].value,
+ values: SHUTDOWN_ACTIONS
+ },
+ {
+ name: 'ready_status_gate',
+ label:
+ 'Wait for VMs to report that they are READY via OneGate to consider them running',
+ type: TYPE_INPUT.CHECKBOX,
+ initial: false
+ }
+];
diff --git a/src/fireedge/src/public/containers/Application/Create/steps.js b/src/fireedge/src/public/containers/Application/Create/steps.js
index f76b5bdcac..4ef54e998e 100644
--- a/src/fireedge/src/public/containers/Application/Create/steps.js
+++ b/src/fireedge/src/public/containers/Application/Create/steps.js
@@ -1,45 +1,101 @@
-import { NetworkDialog, RoleDialog } from 'client/components/Dialogs';
-import { NetworkCard, RoleCard } from 'client/components/Cards';
-import FormStep from 'client/components/FormStepper/FormStep';
+import React, { useMemo } from 'react';
-export default [
- {
- id: 'networks',
- label: 'Networks configuration',
- content: FormStep,
- preRender: () => undefined,
- addAction: true,
- DEFAULT_DATA: {
- mandatory: true,
- name: 'Public_dev',
- description: 'Public network in development mode',
- type: 'id',
- id: '0',
- extra: 'size=5'
- },
- InfoComponent: NetworkCard,
- DialogComponent: NetworkDialog
- },
- {
- id: 'roles',
- label: 'Defining each role',
- content: FormStep,
- preRender: () => undefined,
- addAction: true,
- DEFAULT_DATA: {
- name: 'Master_dev',
- cardinality: 2,
- vm_template: 0,
- elasticity_policies: [],
- scheduled_policies: []
- },
- InfoComponent: RoleCard,
- DialogComponent: RoleDialog
- },
- {
- id: 'where',
- label: 'Where will it run?',
- content: FormStep,
- preRender: () => undefined
- }
-];
+import useOpennebula from 'client/hooks/useOpennebula';
+
+import { NetworkDialog, RoleDialog } from 'client/components/Dialogs';
+import { NetworkCard, RoleCard, ClusterCard } from 'client/components/Cards';
+import FormStep from 'client/components/FormStepper/FormStep';
+import FormDialog from 'client/components/FormStepper/FormDialog';
+import FormListSelect from 'client/components/FormStepper/FormListSelect';
+
+import FormWithSchema from 'client/components/Forms/FormWithSchema';
+import Schema from 'client/containers/Application/Create/schema';
+
+function Steps() {
+ const {
+ clusters,
+ getClusters,
+ getVNetworks,
+ getVNetworksTemplates,
+ getTemplates
+ } = useOpennebula();
+
+ const steps = useMemo(
+ () => [
+ {
+ id: 'service',
+ label: 'Service configuration',
+ content: FormStep,
+ defaultValue: Schema?.reduce(
+ (val, { name, initial }) => ({ ...val, [name]: initial }),
+ {}
+ ),
+ FormComponent: props => (
+
+ )
+ },
+ {
+ id: 'networks',
+ label: 'Networks configuration',
+ content: FormDialog,
+ preRender: () => {
+ getVNetworks();
+ getVNetworksTemplates();
+ },
+ defaultValue: [],
+ addCardAction: true,
+ DEFAULT_DATA: {
+ mandatory: true,
+ name: 'Public_dev',
+ description: 'Public network in development mode',
+ type: 'id',
+ id: '0',
+ extra: 'size=5'
+ },
+ InfoComponent: NetworkCard,
+ DialogComponent: NetworkDialog
+ },
+ {
+ id: 'roles',
+ label: 'Defining each role',
+ content: FormDialog,
+ preRender: getTemplates,
+ defaultValue: [],
+ addCardAction: true,
+ DEFAULT_DATA: {
+ name: 'Master_dev',
+ cardinality: 2,
+ vm_template: 0,
+ elasticity_policies: [],
+ scheduled_policies: []
+ },
+ InfoComponent: RoleCard,
+ DialogComponent: RoleDialog
+ },
+ {
+ id: 'clusters',
+ label: 'Where will it run?',
+ content: FormListSelect,
+ defaultValue: [],
+ onlyOneSelect: true,
+ preRender: getClusters,
+ list: clusters,
+ InfoComponent: ClusterCard
+ }
+ ],
+ [getVNetworks, getVNetworksTemplates, getTemplates, getClusters, clusters]
+ );
+
+ const defaultValues = useMemo(
+ () =>
+ steps.reduce(
+ (values, { id, defaultValue }) => ({ ...values, [id]: defaultValue }),
+ {}
+ ),
+ [steps]
+ );
+
+ return { steps, defaultValues };
+}
+
+export default Steps;
diff --git a/src/fireedge/src/public/containers/Application/Deploy.js b/src/fireedge/src/public/containers/Application/Deploy.js
index 55089a96aa..51c9d3bb96 100644
--- a/src/fireedge/src/public/containers/Application/Deploy.js
+++ b/src/fireedge/src/public/containers/Application/Deploy.js
@@ -41,7 +41,7 @@ const useStyles = makeStyles({
function ApplicationDeploy() {
const classes = useStyles();
const { isLoading } = useGeneral();
- const { users, groups, getUsers } = useOpennebula();
+ const { groups, getUsers } = useOpennebula();
useEffect(() => {
if (!isLoading) {
@@ -54,28 +54,7 @@ function ApplicationDeploy() {
return (
<>
{isLoading && }
- {users?.map(({ NAME, GROUPS }, index) => (
-
-
-
- {NAME}
- {[GROUPS?.ID ?? []].flat().map(ID => {
- const group = getGroupById(ID);
- return group ? (
-
- ) : null;
- })}
-
-
-
- ))}
+ Deploy
>
);
}
diff --git a/src/fireedge/src/public/hooks/useOpennebula.js b/src/fireedge/src/public/hooks/useOpennebula.js
index 49356181b8..4d7140a0bb 100644
--- a/src/fireedge/src/public/hooks/useOpennebula.js
+++ b/src/fireedge/src/public/hooks/useOpennebula.js
@@ -16,6 +16,7 @@ export default function useOpennebula() {
vNetworks,
vNetworksTemplates,
templates,
+ clusters,
filterPool: filter
} = useSelector(
state => ({
@@ -65,6 +66,14 @@ export default function useOpennebula() {
.catch(err => dispatch(failureOneRequest({ error: err })));
}, [dispatch, filter]);
+ const getClusters = useCallback(() => {
+ dispatch(startOneRequest());
+ return servicePool
+ .getClusters({ filter })
+ .then(data => dispatch(actions.setCluster(data)))
+ .catch(err => dispatch(failureOneRequest({ error: err })));
+ }, [dispatch, filter]);
+
return {
groups,
getGroups,
@@ -75,6 +84,8 @@ export default function useOpennebula() {
vNetworksTemplates,
getVNetworksTemplates,
templates,
- getTemplates
+ getTemplates,
+ clusters,
+ getClusters
};
}
diff --git a/src/fireedge/src/public/services/pool.js b/src/fireedge/src/public/services/pool.js
index f6439c1e2b..89e94cadf4 100644
--- a/src/fireedge/src/public/services/pool.js
+++ b/src/fireedge/src/public/services/pool.js
@@ -3,6 +3,7 @@ import Group from 'server/utils/constants/commands/group';
import VNet from 'server/utils/constants/commands/vn';
import VNetTemplate from 'server/utils/constants/commands/vntemplate';
import Template from 'server/utils/constants/commands/template';
+import Cluster from 'server/utils/constants/commands/cluster';
import httpCodes from 'server/utils/constants/http-codes';
import { requestData, requestParams } from 'client/utils';
@@ -77,10 +78,25 @@ export const getTemplates = ({ filter }) => {
});
};
+export const getClusters = ({ filter }) => {
+ const name = Cluster.Actions.CLUSTER_POOL_INFO;
+ const { url, options } = requestParams(
+ { filter },
+ { name, ...Cluster.Commands[name] }
+ );
+
+ return requestData(url, options).then(res => {
+ if (!res?.id || res?.id !== httpCodes.ok.id) throw res;
+
+ return [res?.data?.CLUSTER_POOL?.CLUSTER ?? []].flat();
+ });
+};
+
export default {
getUsers,
getGroups,
getVNetworks,
getVNetworksTemplates,
- getTemplates
+ getTemplates,
+ getClusters
};
diff --git a/src/fireedge/src/public/utils/helpers.js b/src/fireedge/src/public/utils/helpers.js
index 94cab89185..a683912db1 100644
--- a/src/fireedge/src/public/utils/helpers.js
+++ b/src/fireedge/src/public/utils/helpers.js
@@ -1 +1,18 @@
export const fakeDelay = ms => new Promise(resolve => setTimeout(resolve, ms));
+
+export const debounce = (func, delay, immediate) => {
+ let timerId;
+ return (...args) => {
+ const boundFunc = func.bind(this, ...args);
+ clearTimeout(timerId);
+ if (immediate && !timerId) {
+ boundFunc();
+ }
+ const calleeFunc = immediate
+ ? () => {
+ timerId = null;
+ }
+ : boundFunc;
+ timerId = setTimeout(calleeFunc, delay);
+ };
+};