diff --git a/src/fireedge/src/public/actions/pool.js b/src/fireedge/src/public/actions/pool.js
index 002b50e493..b4bc73bd55 100644
--- a/src/fireedge/src/public/actions/pool.js
+++ b/src/fireedge/src/public/actions/pool.js
@@ -50,13 +50,13 @@ module.exports = {
type: SUCCESS_ONE_REQUEST,
payload: { apps }
}),
- setVNetworks: virtualNetworks => ({
+ setVNetworks: vNetworks => ({
type: SUCCESS_ONE_REQUEST,
- payload: { virtualNetworks }
+ payload: { vNetworks }
}),
- setNetworkTemplates: networkTemplates => ({
+ setVNetworkTemplates: vNetworksTemplates => ({
type: SUCCESS_ONE_REQUEST,
- payload: { networkTemplates }
+ payload: { vNetworksTemplates }
}),
setSecGroups: securityGroups => ({
type: SUCCESS_ONE_REQUEST,
diff --git a/src/fireedge/src/public/assets/theme/defaults.js b/src/fireedge/src/public/assets/theme/defaults.js
index 0aaa41fcb1..c54fd59b48 100644
--- a/src/fireedge/src/public/assets/theme/defaults.js
+++ b/src/fireedge/src/public/assets/theme/defaults.js
@@ -16,6 +16,10 @@ export const toolbar = {
sm: 64
};
+export const footer = {
+ regular: 30
+};
+
export const sidebar = {
minified: 60,
fixed: 240
diff --git a/src/fireedge/src/public/components/Cards/NetworkCard.js b/src/fireedge/src/public/components/Cards/NetworkCard.js
new file mode 100644
index 0000000000..00e8c320ec
--- /dev/null
+++ b/src/fireedge/src/public/components/Cards/NetworkCard.js
@@ -0,0 +1,81 @@
+import React from 'react';
+
+import {
+ makeStyles,
+ Card,
+ Button,
+ CardHeader,
+ CardActions,
+ Fade
+} from '@material-ui/core';
+
+import { Tr } from 'client/components/HOC';
+
+const useStyles = makeStyles(theme => ({
+ root: {
+ height: '100%',
+ minHeight: 140,
+ display: 'flex',
+ flexDirection: 'column'
+ },
+ header: {
+ overflowX: 'hidden',
+ flexGrow: 1
+ },
+ subheader: {
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ whiteSpace: 'initial',
+ display: '-webkit-box',
+ lineClamp: 2,
+ boxOrient: 'vertical'
+ },
+ remove: {
+ backgroundColor: theme.palette.error.dark
+ }
+}));
+
+const NetworkCard = React.memo(
+ ({ info, handleEdit, handleClone, handleRemove }) => {
+ const classes = useStyles();
+ const { mandatory, name, description, type, id, extra } = info;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+);
+
+export default NetworkCard;
diff --git a/src/fireedge/src/public/components/Cards/RoleCard.js b/src/fireedge/src/public/components/Cards/RoleCard.js
new file mode 100644
index 0000000000..6cb7445efd
--- /dev/null
+++ b/src/fireedge/src/public/components/Cards/RoleCard.js
@@ -0,0 +1,105 @@
+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;
+
+ const ConditionalWrapper = ({ condition, wrapper, children }) =>
+ condition ? wrapper(children) : children;
+ console.log(info);
+ return (
+
+
+ 1}
+ wrapper={children => (
+
+ {children}
+
+ )}
+ >
+
+
+ }
+ 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/index.js b/src/fireedge/src/public/components/Cards/index.js
new file mode 100644
index 0000000000..81d55ce04a
--- /dev/null
+++ b/src/fireedge/src/public/components/Cards/index.js
@@ -0,0 +1,4 @@
+import NetworkCard from './NetworkCard';
+import RoleCard from './RoleCard';
+
+export { NetworkCard, RoleCard };
diff --git a/src/fireedge/src/public/components/Dialogs/NetworkDialog.js b/src/fireedge/src/public/components/Dialogs/NetworkDialog.js
new file mode 100644
index 0000000000..6fe08c36ef
--- /dev/null
+++ b/src/fireedge/src/public/components/Dialogs/NetworkDialog.js
@@ -0,0 +1,234 @@
+import React, { useMemo, useEffect } from 'react';
+
+import {
+ makeStyles,
+ useMediaQuery,
+ Button,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Grid,
+ FormControlLabel,
+ Checkbox,
+ TextField,
+ MenuItem
+} from '@material-ui/core';
+import { useForm, Controller } from 'react-hook-form';
+import { yupResolver } from '@hookform/resolvers';
+import * as yup from 'yup';
+
+import useOpennebula from 'client/hooks/useOpennebula';
+import ErrorHelper from 'client/components/FormControl/ErrorHelper';
+import { Tr } from 'client/components/HOC';
+
+const useStyles = makeStyles(theme => ({}));
+
+const SELECT = {
+ template: 'template',
+ network: 'network'
+};
+
+const TYPES_NETWORKS = {
+ template_id: { text: 'Create', select: SELECT.template, extra: true },
+ reserve_from: { text: 'Reserve', select: SELECT.network, extra: true },
+ id: { text: 'Existing', select: SELECT.network, extra: false }
+};
+
+const ID_CY = 'form-network';
+
+const NetworkDialog = React.memo(
+ ({ open, info: network, onSubmit, onCancel }) => {
+ const classes = useStyles();
+ const { vNetworks, vNetworksTemplates } = useOpennebula();
+ const isMobile = useMediaQuery(theme => theme.breakpoints.only('xs'));
+
+ const { register, handleSubmit, errors, control, watch } = useForm({
+ reValidateMode: 'onSubmit',
+ defaultValues: {
+ type: Object.keys(TYPES_NETWORKS)[0],
+ ...network
+ },
+ resolver: yupResolver(
+ yup.object().shape({
+ mandatory: yup.boolean().required(),
+ name: yup.string().required('Name is a required field'),
+ description: yup.string(),
+ type: yup
+ .string()
+ .oneOf(Object.keys(TYPES_NETWORKS))
+ .required('Type is required field'),
+ id: yup
+ .string()
+ .when('type', {
+ is: type =>
+ Object.entries(TYPES_NETWORKS)?.some(
+ ([key, { select }]) =>
+ type === key && select === SELECT.network
+ ),
+ then: yup.string().required('Network is required field'),
+ otherwise: yup
+ .string()
+ .required('Network template is required field')
+ })
+ .required(),
+ extra: yup.string()
+ })
+ )
+ });
+
+ const { type } = watch();
+ const typeSelected = TYPES_NETWORKS[type]?.select;
+
+ const selectType =
+ typeSelected === SELECT.network ? vNetworks : vNetworksTemplates;
+
+ return (
+
+ );
+ }
+);
+
+export default NetworkDialog;
diff --git a/src/fireedge/src/public/components/Dialogs/RoleDialog.js b/src/fireedge/src/public/components/Dialogs/RoleDialog.js
new file mode 100644
index 0000000000..68ffbaed45
--- /dev/null
+++ b/src/fireedge/src/public/components/Dialogs/RoleDialog.js
@@ -0,0 +1,96 @@
+import React, { useMemo, useEffect } from 'react';
+
+import {
+ makeStyles,
+ useMediaQuery,
+ Button,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Grid,
+ FormControlLabel,
+ Checkbox,
+ TextField,
+ MenuItem
+} from '@material-ui/core';
+import { useForm, Controller } from 'react-hook-form';
+import { yupResolver } from '@hookform/resolvers';
+import * as yup from 'yup';
+
+import useOpennebula from 'client/hooks/useOpennebula';
+import ErrorHelper from 'client/components/FormControl/ErrorHelper';
+import { Tr } from 'client/components/HOC';
+
+const useStyles = makeStyles(theme => ({}));
+
+const ID_CY = 'form-role';
+
+const NetworkDialog = React.memo(({ open, info: role, onSubmit, onCancel }) => {
+ // const classes = useStyles();
+ const { templates } = useOpennebula();
+ const isMobile = useMediaQuery(theme => theme.breakpoints.only('xs'));
+
+ const { register, handleSubmit, errors, control } = useForm({
+ reValidateMode: 'onSubmit',
+ defaultValues: role
+ // resolver: yupResolver(yup.object().shape({}))
+ });
+
+ return (
+
+ );
+});
+
+export default NetworkDialog;
diff --git a/src/fireedge/src/public/components/Dialogs/index.js b/src/fireedge/src/public/components/Dialogs/index.js
new file mode 100644
index 0000000000..537beed14d
--- /dev/null
+++ b/src/fireedge/src/public/components/Dialogs/index.js
@@ -0,0 +1,4 @@
+import NetworkDialog from './NetworkDialog';
+import RoleDialog from './RoleDialog';
+
+export { NetworkDialog, RoleDialog };
diff --git a/src/fireedge/src/public/components/Footer/index.js b/src/fireedge/src/public/components/Footer/index.js
index 80ad160676..f2a0c74152 100644
--- a/src/fireedge/src/public/components/Footer/index.js
+++ b/src/fireedge/src/public/components/Footer/index.js
@@ -14,7 +14,7 @@
/* -------------------------------------------------------------------------- */
import React from 'react';
-import { Box, Link } from '@material-ui/core';
+import { Box, Link, Typography } from '@material-ui/core';
import footerStyles from 'client/components/Footer/styles';
import { by } from 'client/constants';
@@ -26,14 +26,16 @@ const Footer = React.memo(() => {
return (
- {'Made with'}
-
- {'❤️'}
-
- {'by'}
-
- {text}
-
+
+ {'Made with'}
+
+ {'❤️'}
+
+ {'by'}
+
+ {text}
+
+
);
});
diff --git a/src/fireedge/src/public/components/FormControl/GroupSelect.js b/src/fireedge/src/public/components/FormControl/GroupSelect.js
index 23400a297c..195e962cf5 100644
--- a/src/fireedge/src/public/components/FormControl/GroupSelect.js
+++ b/src/fireedge/src/public/components/FormControl/GroupSelect.js
@@ -42,7 +42,14 @@ const GroupSelect = props => {
?.map(({ ID, NAME }) => (
)),
[groups]
diff --git a/src/fireedge/src/public/components/FormStepper/FormStep.js b/src/fireedge/src/public/components/FormStepper/FormStep.js
new file mode 100644
index 0000000000..1aad95efca
--- /dev/null
+++ b/src/fireedge/src/public/components/FormStepper/FormStep.js
@@ -0,0 +1,124 @@
+import React, { useEffect, useState } 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'
+ }
+}));
+
+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;
+
+ useEffect(() => {
+ reset({ ...data }, { errors: true });
+ }, [id, data]);
+
+ const handleSubmit = values => {
+ setFormData(prevData => ({
+ ...prevData,
+ [id]: Object.assign(prevData[id], {
+ [dialogFormData.index]: values
+ })
+ }));
+
+ setShowDialog(false);
+ };
+
+ 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 && (
+
+ )}
+
+ );
+}
+
+FormStep.propTypes = {
+ step: PropTypes.objectOf(PropTypes.object).isRequired,
+ data: PropTypes.objectOf(PropTypes.object).isRequired,
+ setFormData: PropTypes.func
+};
+
+FormStep.defaultProps = {
+ step: {},
+ data: {},
+ setFormData: data => data
+};
+
+export default FormStep;
diff --git a/src/fireedge/src/public/components/FormStepper/MobileStepper.js b/src/fireedge/src/public/components/FormStepper/MobileStepper.js
new file mode 100644
index 0000000000..f4a23fd197
--- /dev/null
+++ b/src/fireedge/src/public/components/FormStepper/MobileStepper.js
@@ -0,0 +1,55 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { Button, MobileStepper } from '@material-ui/core';
+import { KeyboardArrowLeft, KeyboardArrowRight } from '@material-ui/icons';
+
+import { Tr } from 'client/components/HOC';
+
+const CustomMobileStepper = ({
+ totalSteps,
+ activeStep,
+ lastStep,
+ disabledBack,
+ handleNext,
+ handleBack
+}) => (
+
+ {Tr('Back')}
+
+ }
+ nextButton={
+
+ }
+ />
+);
+
+CustomMobileStepper.propTypes = {
+ totalSteps: PropTypes.number,
+ activeStep: PropTypes.number,
+ lastStep: PropTypes.number,
+ disabledBack: PropTypes.bool,
+ handleNext: PropTypes.func,
+ handleBack: PropTypes.func
+};
+
+CustomMobileStepper.defaultProps = {
+ totalSteps: 0,
+ activeStep: 0,
+ lastStep: 0,
+ disabledBack: false,
+ handleNext: () => undefined,
+ handleBack: () => undefined
+};
+
+export default CustomMobileStepper;
diff --git a/src/fireedge/src/public/components/FormStepper/Stepper.js b/src/fireedge/src/public/components/FormStepper/Stepper.js
new file mode 100644
index 0000000000..a0d5a194a6
--- /dev/null
+++ b/src/fireedge/src/public/components/FormStepper/Stepper.js
@@ -0,0 +1,66 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { Button, Stepper, Step, StepLabel, Box } from '@material-ui/core';
+
+import { Tr } from 'client/components/HOC';
+
+/*
+position: sticky;
+top: 0;
+backdrop-filter: blur(5px);
+background: #000000aa;
+z-index: 1;
+*/
+
+const CustomStepper = ({
+ steps,
+ activeStep,
+ lastStep,
+ disabledBack,
+ handleNext,
+ handleBack
+}) => (
+ <>
+
+ {steps?.map(({ label }) => (
+
+ {label}
+
+ ))}
+
+
+
+
+
+ >
+);
+
+CustomStepper.propTypes = {
+ steps: PropTypes.arrayOf(
+ PropTypes.shape({
+ id: PropTypes.oneOf([PropTypes.string, PropTypes.number]).isRequired,
+ label: PropTypes.string.isRequired
+ })
+ ),
+ activeStep: PropTypes.number.isRequired,
+ lastStep: PropTypes.number.isRequired,
+ disabledBack: PropTypes.bool.isRequired,
+ handleNext: PropTypes.func,
+ handleBack: PropTypes.func
+};
+
+CustomStepper.defaultProps = {
+ steps: [],
+ activeStep: 0,
+ lastStep: 0,
+ disabledBack: false,
+ handleNext: () => undefined,
+ handleBack: () => undefined
+};
+
+export default CustomStepper;
diff --git a/src/fireedge/src/public/components/FormStepper/index.js b/src/fireedge/src/public/components/FormStepper/index.js
new file mode 100644
index 0000000000..0c8dcfccf8
--- /dev/null
+++ b/src/fireedge/src/public/components/FormStepper/index.js
@@ -0,0 +1,96 @@
+import React, { useState, useMemo, useCallback } from 'react';
+import PropTypes from 'prop-types';
+
+import { useFormContext } from 'react-hook-form';
+import { useMediaQuery } from '@material-ui/core';
+
+import CustomMobileStepper from 'client/components/FormStepper/MobileStepper';
+import CustomStepper from 'client/components/FormStepper/Stepper';
+import { console } from 'window-or-global';
+
+const FIRST_STEP = 0;
+
+const FormStepper = ({ steps, initialValue, onSubmit }) => {
+ const isMobile = useMediaQuery(theme => theme.breakpoints.only('xs'));
+ const { watch } = useFormContext();
+ const [activeStep, setActiveStep] = useState(FIRST_STEP);
+ const [formData, setFormData] = useState(initialValue);
+
+ const totalSteps = useMemo(() => steps?.length, [steps]);
+ const lastStep = useMemo(() => totalSteps - 1, [totalSteps]);
+ const disabledBack = useMemo(() => activeStep === FIRST_STEP, [activeStep]);
+
+ const handleNext = useCallback(() => {
+ setFormData(data => ({ ...data, ...watch() }));
+
+ if (activeStep === lastStep) {
+ onSubmit(formData);
+ } else setActiveStep(prevActiveStep => prevActiveStep + 1);
+ }, [activeStep]);
+
+ const handleBack = useCallback(() => {
+ if (activeStep <= FIRST_STEP) return;
+
+ setActiveStep(prevActiveStep => prevActiveStep - 1);
+ setFormData(data => ({ ...data, ...watch() }));
+ }, [activeStep]);
+
+ return (
+ <>
+ {/* STEPPER */}
+ {isMobile ? (
+
+ ) : (
+
+ )}
+
+ {/* FORM CONTENT */}
+ {React.useMemo(() => {
+ const { content: Content, ...rest } = steps[activeStep];
+
+ return (
+
+ );
+ }, [activeStep, formData, setFormData])}
+ >
+ );
+};
+
+FormStepper.propTypes = {
+ steps: PropTypes.arrayOf(
+ PropTypes.shape({
+ id: PropTypes.number.isRequired,
+ label: PropTypes.string.isRequired,
+ content: PropTypes.node.isRequired,
+ dependOf: PropTypes.bool
+ })
+ ),
+ initialValue: PropTypes.objectOf(PropTypes.object),
+ onSubmit: PropTypes.func
+};
+
+FormStepper.defaultProps = {
+ steps: [],
+ initialValue: {},
+ onSubmit: dataForm => console.log(dataForm)
+};
+
+export default FormStepper;
diff --git a/src/fireedge/src/public/components/HOC/InternalLayout/styles.js b/src/fireedge/src/public/components/HOC/InternalLayout/styles.js
index 31515daf5d..355876b410 100644
--- a/src/fireedge/src/public/components/HOC/InternalLayout/styles.js
+++ b/src/fireedge/src/public/components/HOC/InternalLayout/styles.js
@@ -1,5 +1,5 @@
import { makeStyles } from '@material-ui/core';
-import { sidebar, toolbar } from 'client/assets/theme/defaults';
+import { sidebar, toolbar, footer } from 'client/assets/theme/defaults';
export default makeStyles(theme => ({
root: {
@@ -23,10 +23,9 @@ export default makeStyles(theme => ({
}
},
main: {
- paddingBottom: 30,
height: '100vh',
width: '100%',
- // paddingTop: 64
+ paddingBottom: footer.regular,
paddingTop: toolbar.regular,
[`${theme.breakpoints.up('xs')} and (orientation: landscape)`]: {
paddingTop: toolbar.xs
diff --git a/src/fireedge/src/public/containers/Application/Create/index.js b/src/fireedge/src/public/containers/Application/Create/index.js
new file mode 100644
index 0000000000..8c772cfe06
--- /dev/null
+++ b/src/fireedge/src/public/containers/Application/Create/index.js
@@ -0,0 +1,48 @@
+import React, { useEffect } from 'react';
+
+import { useForm, FormProvider } from 'react-hook-form';
+
+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: []
+};
+
+function ApplicationCreate() {
+ const { getVNetworks, getVNetworksTemplates, getTemplates } = useOpennebula();
+ const methods = useForm({
+ mode: 'onBlur'
+ });
+ 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 (
+
+
+
+ );
+}
+
+ApplicationCreate.propTypes = {};
+
+ApplicationCreate.defaultProps = {};
+
+export default ApplicationCreate;
diff --git a/src/fireedge/src/public/containers/Application/Create/steps.js b/src/fireedge/src/public/containers/Application/Create/steps.js
new file mode 100644
index 0000000000..f76b5bdcac
--- /dev/null
+++ b/src/fireedge/src/public/containers/Application/Create/steps.js
@@ -0,0 +1,45 @@
+import { NetworkDialog, RoleDialog } from 'client/components/Dialogs';
+import { NetworkCard, RoleCard } from 'client/components/Cards';
+import FormStep from 'client/components/FormStepper/FormStep';
+
+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
+ }
+];
diff --git a/src/fireedge/src/public/containers/Application/Create/styles.js b/src/fireedge/src/public/containers/Application/Create/styles.js
new file mode 100644
index 0000000000..64648b6714
--- /dev/null
+++ b/src/fireedge/src/public/containers/Application/Create/styles.js
@@ -0,0 +1,3 @@
+import { makeStyles } from '@material-ui/core';
+
+export default makeStyles(theme => ({}));
diff --git a/src/fireedge/src/public/containers/System/Users.js b/src/fireedge/src/public/containers/Application/Deploy.js
similarity index 85%
rename from src/fireedge/src/public/containers/System/Users.js
rename to src/fireedge/src/public/containers/Application/Deploy.js
index f0e62dab77..55089a96aa 100644
--- a/src/fireedge/src/public/containers/System/Users.js
+++ b/src/fireedge/src/public/containers/Application/Deploy.js
@@ -38,7 +38,7 @@ const useStyles = makeStyles({
}
});
-function Users() {
+function ApplicationDeploy() {
const classes = useStyles();
const { isLoading } = useGeneral();
const { users, groups, getUsers } = useOpennebula();
@@ -49,22 +49,22 @@ function Users() {
}
}, [getUsers]);
- const getGroupById = id => groups?.find(({ ID }) => ID === id);
+ const getGroupById = findId => groups?.find(({ ID }) => ID === findId);
return (
<>
{isLoading && }
- {users?.map(({ ID, NAME, GROUPS }, index) => (
+ {users?.map(({ NAME, GROUPS }, index) => (
{NAME}
- {[GROUPS?.ID ?? []].flat().map(id => {
- const group = getGroupById(id);
+ {[GROUPS?.ID ?? []].flat().map(ID => {
+ const group = getGroupById(ID);
return group ? (
- Groups
-
- );
+function ApplicationManage() {
+ return Manage
;
}
-Groups.propTypes = {
- name: PropTypes.string
-};
+ApplicationManage.propTypes = {};
-Groups.defaultProps = {
- name: ''
-};
+ApplicationManage.defaultProps = {};
-export default Groups;
+export default ApplicationManage;
diff --git a/src/fireedge/src/public/containers/Application/index.js b/src/fireedge/src/public/containers/Application/index.js
index cac823721a..8514305eb6 100644
--- a/src/fireedge/src/public/containers/Application/index.js
+++ b/src/fireedge/src/public/containers/Application/index.js
@@ -1,5 +1,5 @@
-import Create from 'client/containers/Application/Create';
-import Deploy from 'client/containers/Application/Deploy';
-import Manage from 'client/containers/Application/Manage';
+import ApplicationCreate from 'client/containers/Application/Create';
+import ApplicationDeploy from 'client/containers/Application/Deploy';
+import ApplicationManage from 'client/containers/Application/Manage';
-export { Create, Deploy, Manage };
+export { ApplicationCreate, ApplicationDeploy, ApplicationManage };
diff --git a/src/fireedge/src/public/containers/Dashboard/index.js b/src/fireedge/src/public/containers/Dashboard/index.js
index 56983aee50..7a71aaff3d 100644
--- a/src/fireedge/src/public/containers/Dashboard/index.js
+++ b/src/fireedge/src/public/containers/Dashboard/index.js
@@ -15,14 +15,9 @@
import React from 'react';
-import { makeStyles, Box, Typography } from '@material-ui/core';
+import { Box, Typography } from '@material-ui/core';
-const dashboardStyles = makeStyles(theme => ({
- root: {},
- title: {
- color: theme.palette.common.black
- }
-}));
+import dashboardStyles from 'client/containers/Dashboard/styles';
function Dashboard() {
const classes = dashboardStyles();
diff --git a/src/fireedge/src/public/containers/Dashboard/styles.js b/src/fireedge/src/public/containers/Dashboard/styles.js
new file mode 100644
index 0000000000..e284b043c2
--- /dev/null
+++ b/src/fireedge/src/public/containers/Dashboard/styles.js
@@ -0,0 +1,8 @@
+import { makeStyles } from '@material-ui/core';
+
+export default makeStyles(theme => ({
+ root: {},
+ title: {
+ color: theme.palette.common.black
+ }
+}));
diff --git a/src/fireedge/src/public/containers/Login/Form2fa.js b/src/fireedge/src/public/containers/Login/Forms/Form2fa.js
similarity index 100%
rename from src/fireedge/src/public/containers/Login/Form2fa.js
rename to src/fireedge/src/public/containers/Login/Forms/Form2fa.js
diff --git a/src/fireedge/src/public/containers/Login/FormGroup.js b/src/fireedge/src/public/containers/Login/Forms/FormGroup.js
similarity index 100%
rename from src/fireedge/src/public/containers/Login/FormGroup.js
rename to src/fireedge/src/public/containers/Login/Forms/FormGroup.js
diff --git a/src/fireedge/src/public/containers/Login/FormUser.js b/src/fireedge/src/public/containers/Login/Forms/FormUser.js
similarity index 100%
rename from src/fireedge/src/public/containers/Login/FormUser.js
rename to src/fireedge/src/public/containers/Login/Forms/FormUser.js
diff --git a/src/fireedge/src/public/containers/Login/index.js b/src/fireedge/src/public/containers/Login/index.js
index 05be257a78..29230eb0ad 100644
--- a/src/fireedge/src/public/containers/Login/index.js
+++ b/src/fireedge/src/public/containers/Login/index.js
@@ -25,9 +25,9 @@ import {
import useAuth from 'client/hooks/useAuth';
-import FormUser from 'client/containers/Login/FormUser';
-import Form2fa from 'client/containers/Login/Form2fa';
-import FormGroup from 'client/containers/Login/FormGroup';
+import FormUser from 'client/containers/Login/Forms/FormUser';
+import Form2fa from 'client/containers/Login/Forms/Form2fa';
+import FormGroup from 'client/containers/Login/Forms/FormGroup';
import loginStyles from 'client/containers/Login/styles';
import Logo from 'client/icons/logo';
diff --git a/src/fireedge/src/public/containers/System/index.js b/src/fireedge/src/public/containers/System/index.js
deleted file mode 100644
index 2049efce7d..0000000000
--- a/src/fireedge/src/public/containers/System/index.js
+++ /dev/null
@@ -1,4 +0,0 @@
-import Groups from './Groups';
-import Users from './Users';
-
-export { Groups, Users };
diff --git a/src/fireedge/src/public/containers/TestApi/styles.js b/src/fireedge/src/public/containers/TestApi/styles.js
index 9e8c2195b5..0084bdcbd3 100644
--- a/src/fireedge/src/public/containers/TestApi/styles.js
+++ b/src/fireedge/src/public/containers/TestApi/styles.js
@@ -2,6 +2,7 @@ import { makeStyles } from '@material-ui/core';
export default makeStyles(() => ({
root: {
- minHeight: '100%'
+ minHeight: '100%',
+ width: '100%'
}
}));
diff --git a/src/fireedge/src/public/hooks/useAuth.js b/src/fireedge/src/public/hooks/useAuth.js
index 514fb9e242..3cf10eebb2 100644
--- a/src/fireedge/src/public/hooks/useAuth.js
+++ b/src/fireedge/src/public/hooks/useAuth.js
@@ -7,7 +7,7 @@ import { fakeDelay } from 'client/utils/helpers';
import * as serviceAuth from 'client/services/auth';
import * as serviceUsers from 'client/services/users';
-import * as serviceGroups from 'client/services/groups';
+import * as servicePool from 'client/services/pool';
import {
startAuth,
selectFilterGroup,
@@ -80,7 +80,7 @@ export default function useAuth() {
return serviceAuth
.getUser()
.then(user => dispatch(successAuth({ user })))
- .then(serviceGroups.getGroups)
+ .then(servicePool.getGroups)
.then(groups => dispatch(setGroups(groups)))
.catch(err => dispatch(failureAuth({ error: err })));
}, [dispatch, jwtName, authUser]);
diff --git a/src/fireedge/src/public/hooks/useOpennebula.js b/src/fireedge/src/public/hooks/useOpennebula.js
index 168e66c93d..49356181b8 100644
--- a/src/fireedge/src/public/hooks/useOpennebula.js
+++ b/src/fireedge/src/public/hooks/useOpennebula.js
@@ -1,43 +1,80 @@
import { useCallback } from 'react';
import { useSelector, useDispatch, shallowEqual } from 'react-redux';
-import {
- setGroups,
- setUsers,
+import actions, {
startOneRequest,
failureOneRequest
} from 'client/actions/pool';
-import * as servicesGroups from 'client/services/groups';
-import * as servicesUsers from 'client/services/users';
+import * as servicePool from 'client/services/pool';
export default function useOpennebula() {
const dispatch = useDispatch();
- const { groups, users } = useSelector(
- state => state?.Opennebula,
+ const {
+ groups,
+ users,
+ vNetworks,
+ vNetworksTemplates,
+ templates,
+ filterPool: filter
+ } = useSelector(
+ state => ({
+ ...state?.Opennebula,
+ filterPool: state?.Authenticated?.filterPool
+ }),
shallowEqual
);
const getGroups = useCallback(() => {
dispatch(startOneRequest());
- return servicesGroups
- .getGroups()
- .then(data => dispatch(setGroups(data)))
+ return servicePool
+ .getGroups({ filter })
+ .then(data => dispatch(actions.setGroups(data)))
.catch(err => dispatch(failureOneRequest({ error: err })));
- }, [dispatch]);
+ }, [dispatch, filter]);
const getUsers = useCallback(() => {
dispatch(startOneRequest());
- return servicesUsers
- .getUsers()
- .then(data => dispatch(setUsers(data)))
+ return servicePool
+ .getUsers({ filter })
+ .then(data => dispatch(actions.setUsers(data)))
.catch(err => dispatch(failureOneRequest({ error: err })));
- }, [dispatch]);
+ }, [dispatch, filter]);
+
+ const getVNetworks = useCallback(() => {
+ dispatch(startOneRequest());
+ return servicePool
+ .getVNetworks({ filter })
+ .then(data => dispatch(actions.setVNetworks(data)))
+ .catch(err => dispatch(failureOneRequest({ error: err })));
+ }, [dispatch, filter]);
+
+ const getVNetworksTemplates = useCallback(() => {
+ dispatch(startOneRequest());
+ return servicePool
+ .getVNetworksTemplates({ filter })
+ .then(data => dispatch(actions.setVNetworkTemplates(data)))
+ .catch(err => dispatch(failureOneRequest({ error: err })));
+ }, [dispatch, filter]);
+
+ const getTemplates = useCallback(() => {
+ dispatch(startOneRequest());
+ return servicePool
+ .getTemplates({ filter })
+ .then(data => dispatch(actions.setTemplates(data)))
+ .catch(err => dispatch(failureOneRequest({ error: err })));
+ }, [dispatch, filter]);
return {
groups,
getGroups,
users,
- getUsers
+ getUsers,
+ vNetworks,
+ getVNetworks,
+ vNetworksTemplates,
+ getVNetworksTemplates,
+ templates,
+ getTemplates
};
}
diff --git a/src/fireedge/src/public/reducers/opennebula.js b/src/fireedge/src/public/reducers/opennebula.js
index 1e09894597..9bf334ca9a 100644
--- a/src/fireedge/src/public/reducers/opennebula.js
+++ b/src/fireedge/src/public/reducers/opennebula.js
@@ -27,8 +27,8 @@ const initial = {
files: [],
marketPlaces: [],
apps: [],
- virtualNetworks: [],
- networkTemplates: [],
+ vNetworks: [],
+ vNetworksTemplates: [],
securityGroups: [],
clusters: [],
hosts: [],
diff --git a/src/fireedge/src/public/router/endpoints.js b/src/fireedge/src/public/router/endpoints.js
index e7f09042f0..9fe66c77e6 100644
--- a/src/fireedge/src/public/router/endpoints.js
+++ b/src/fireedge/src/public/router/endpoints.js
@@ -16,54 +16,50 @@
import {
Dashboard as DashboardIcon,
Settings as SettingsIcon,
- Ballot as BallotIcon
+ Ballot as BallotIcon,
+ Palette as PaletteIcon,
+ Reddit as RedditIcon,
+ Build as BuildIcon
} from '@material-ui/icons';
import Login from 'client/containers/Login';
-import { Clusters, Hosts, Zones } from 'client/containers/Infrastructure';
-import { Users, Groups } from 'client/containers/System';
+import Dashboard from 'client/containers/Dashboard';
import Settings from 'client/containers/Settings';
import TestApi from 'client/containers/TestApi';
-import Dashboard from 'client/containers/Dashboard';
+import {
+ ApplicationCreate,
+ ApplicationDeploy,
+ ApplicationManage
+} from 'client/containers/Application';
export const PATH = {
LOGIN: '/',
DASHBOARD: '/dashboard',
+ APPLICATION: {
+ CREATE: '/application/create',
+ MANAGE: '/application/manage',
+ DEPLOY: '/application/deploy'
+ },
SETTINGS: '/settings',
- TEST_API: '/test-api',
- INFRASTRUCTURE: {
- CLUSTERS: '/clusters',
- HOSTS: '/hosts',
- ZONES: '/zones'
- },
- SYSTEM: {
- USERS: '/users',
- GROUPS: '/groups'
- },
- NETWORKS: {
- VNETS: '/vnets',
- VNETS_TEMPLATES: '/vnets-templates',
- VNETS_TOPOLOGY: '/vnets-topology',
- SEC_GROUPS: '/secgroups'
- }
+ TEST_API: '/test-api'
};
const ENDPOINTS = [
{
- label: 'login',
+ label: 'Login',
path: PATH.LOGIN,
authenticated: false,
component: Login
},
{
- label: 'dashboard',
+ label: 'Dashboard',
path: PATH.DASHBOARD,
authenticated: true,
icon: DashboardIcon,
component: Dashboard
},
{
- label: 'settings',
+ label: 'Settings',
path: PATH.SETTINGS,
authenticated: true,
header: true,
@@ -71,7 +67,7 @@ const ENDPOINTS = [
component: Settings
},
{
- label: 'test api',
+ label: 'Test API',
path: PATH.TEST_API,
authenticated: true,
devMode: true,
@@ -79,75 +75,25 @@ const ENDPOINTS = [
component: TestApi
},
{
- label: 'infrastructure',
+ label: 'Create Application',
+ path: PATH.APPLICATION.CREATE,
authenticated: true,
- icon: BallotIcon,
- routes: [
- {
- label: 'clusters',
- path: PATH.INFRASTRUCTURE.CLUSTERS,
- authenticated: true,
- component: Clusters
- },
- {
- label: 'hosts',
- path: PATH.INFRASTRUCTURE.HOSTS,
- authenticated: true,
- component: Hosts
- },
- {
- label: 'zones',
- path: PATH.INFRASTRUCTURE.ZONES,
- authenticated: true,
- component: Zones
- }
- ]
+ icon: PaletteIcon,
+ component: ApplicationCreate
},
{
- label: 'system',
+ label: 'Deploy Application',
+ path: PATH.APPLICATION.DEPLOY,
authenticated: true,
- icon: BallotIcon,
- routes: [
- {
- label: 'users',
- path: PATH.SYSTEM.USERS,
- authenticated: true,
- component: Users
- },
- {
- label: 'groups',
- path: PATH.SYSTEM.GROUPS,
- authenticated: true,
- component: Groups
- }
- ]
+ icon: RedditIcon,
+ component: ApplicationDeploy
},
{
- label: 'networks',
+ label: 'Manage Application',
+ path: PATH.APPLICATION.MANAGE,
authenticated: true,
- icon: BallotIcon,
- routes: [
- {
- label: 'vnets',
- path: PATH.NETWORKS.VNETS,
- authenticated: true
- },
- {
- label: 'vnets templates',
- path: PATH.NETWORKS.VNETS_TEMPLATES,
- authenticated: true
- },
- {
- label: 'vnets topology',
- path: PATH.NETWORKS.VNETS_TOPOLOGY,
- authenticated: true
- },
- {
- label: 'vnets secgroup',
- path: PATH.NETWORKS.SEC_GROUPS,
- authenticated: true
- }
- ]
+ icon: BuildIcon,
+ component: ApplicationManage
}
];
diff --git a/src/fireedge/src/public/services/groups.js b/src/fireedge/src/public/services/groups.js
index 8cfb8d8a2b..547b433c57 100644
--- a/src/fireedge/src/public/services/groups.js
+++ b/src/fireedge/src/public/services/groups.js
@@ -2,17 +2,4 @@ import { Actions, Commands } from 'server/utils/constants/commands/group';
import { requestData, requestParams } from 'client/utils';
import httpCodes from 'server/utils/constants/http-codes';
-export const getGroups = () => {
- const name = Actions.GROUP_POOL_INFO;
- const { url, options } = requestParams({}, { name, ...Commands[name] });
-
- return requestData(url, options).then(res => {
- if (!res?.id || res?.id !== httpCodes.ok.id) throw res;
-
- return [res?.data?.GROUP_POOL?.GROUP ?? []].flat();
- });
-};
-
-export default {
- getGroups
-};
+export default {};
diff --git a/src/fireedge/src/public/services/pool.js b/src/fireedge/src/public/services/pool.js
new file mode 100644
index 0000000000..f6439c1e2b
--- /dev/null
+++ b/src/fireedge/src/public/services/pool.js
@@ -0,0 +1,86 @@
+import User from 'server/utils/constants/commands/user';
+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 httpCodes from 'server/utils/constants/http-codes';
+import { requestData, requestParams } from 'client/utils';
+
+export const getUsers = ({ filter }) => {
+ const name = User.Actions.USER_POOL_INFO;
+ const { url, options } = requestParams(
+ { filter },
+ { name, ...User.Commands[name] }
+ );
+
+ return requestData(url, options).then(res => {
+ if (!res?.id || res?.id !== httpCodes.ok.id) throw res;
+
+ return [res?.data?.USER_POOL?.USER ?? []].flat();
+ });
+};
+
+export const getGroups = ({ filter }) => {
+ const name = Group.Actions.GROUP_POOL_INFO;
+ const { url, options } = requestParams(
+ { filter },
+ { name, ...Group.Commands[name] }
+ );
+
+ return requestData(url, options).then(res => {
+ if (!res?.id || res?.id !== httpCodes.ok.id) throw res;
+
+ return [res?.data?.GROUP_POOL?.GROUP ?? []].flat();
+ });
+};
+
+export const getVNetworks = ({ filter }) => {
+ const name = VNet.Actions.VN_POOL_INFO;
+ const { url, options } = requestParams(
+ { filter },
+ { name, ...VNet.Commands[name] }
+ );
+
+ return requestData(url, options).then(res => {
+ if (!res?.id || res?.id !== httpCodes.ok.id) throw res;
+
+ return [res?.data?.VNET_POOL?.VNET ?? []].flat();
+ });
+};
+
+export const getVNetworksTemplates = ({ filter }) => {
+ const name = VNetTemplate.Actions.VNTEMPLATE_POOL_INFO;
+ const { url, options } = requestParams(
+ { filter },
+ { name, ...VNetTemplate.Commands[name] }
+ );
+
+ return requestData(url, options).then(res => {
+ if (!res?.id || res?.id !== httpCodes.ok.id) throw res;
+
+ return [res?.data?.VNTEMPLATE_POOL?.VNTEMPLATE ?? []].flat();
+ });
+};
+
+export const getTemplates = ({ filter }) => {
+ const name = Template.Actions.TEMPLATE_POOL_INFO;
+ const { url, options } = requestParams(
+ { filter },
+ { name, ...Template.Commands[name] }
+ );
+
+ return requestData(url, options).then(res => {
+ if (!res?.id || res?.id !== httpCodes.ok.id) throw res;
+
+ return [res?.data?.VMTEMPLATE_POOL?.VMTEMPLATE ?? []].flat();
+ });
+};
+
+export default {
+ getUsers,
+ getGroups,
+ getVNetworks,
+ getVNetworksTemplates,
+ getTemplates
+};
diff --git a/src/fireedge/src/public/services/users.js b/src/fireedge/src/public/services/users.js
index f054a01f6d..ca7f68f27f 100644
--- a/src/fireedge/src/public/services/users.js
+++ b/src/fireedge/src/public/services/users.js
@@ -13,18 +13,6 @@ export const changeGroup = values => {
});
};
-export const getUsers = () => {
- const name = Actions.USER_POOL_INFO;
- const { url, options } = requestParams({}, { name, ...Commands[name] });
-
- return requestData(url, options).then(res => {
- if (!res?.id || res?.id !== httpCodes.ok.id) throw res;
-
- return [res?.data?.USER_POOL?.USER ?? []].flat();
- });
-};
-
export default {
- changeGroup,
- getUsers
+ changeGroup
};
diff --git a/src/fireedge/src/public/utils/request.js b/src/fireedge/src/public/utils/request.js
index ab94c6fb60..1f54915034 100644
--- a/src/fireedge/src/public/utils/request.js
+++ b/src/fireedge/src/public/utils/request.js
@@ -4,7 +4,9 @@ import { from as resourceFrom } from 'server/utils/constants/defaults';
export const getQueries = params =>
Object.entries(params)
- ?.filter(([, { from }]) => from === resourceFrom.query)
+ ?.filter(([, { from, value }]) =>
+ Boolean(from === resourceFrom.query && value)
+ )
?.map(([name, { value }]) => `${name}=${encodeURI(value)}`)
?.join('&');