mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-21 14:50:08 +03:00
parent
1b57b97b26
commit
17ba0e63b0
@ -5,5 +5,7 @@
|
||||
"client/*": ["public/*"],
|
||||
"server/*": ["*"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"include": ["./src/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
@ -8,7 +8,7 @@
|
||||
"build-node": "webpack --mode=production --env.node --env.ssr",
|
||||
"build-front": "npm run copy_static_assets && concurrently \"webpack --mode=production --env.front\"",
|
||||
"dev": "npm run copy_static_assets && concurrently \"nodemon --inspect dist\" \"webpack --mode=development --env.node --env.front --env.ssr --env.hotreload --watch\"",
|
||||
"dev-front": "npm run copy_static_assets && concurrently \"nodemon --inspect dist\" \"webpack --mode=development --env.node --env.hotreload\" \"webpack --mode=development --env.front --env.hotreload --watch\"" ,
|
||||
"dev-front": "npm run copy_static_assets && concurrently \"nodemon --inspect dist\" \"webpack --mode=development --env.node --env.hotreload\" \"webpack --mode=development --env.front --env.hotreload --watch\"",
|
||||
"start": "node dist/index",
|
||||
"cypress:open": "cypress open",
|
||||
"cypress:run": "cypress run --headless --browser chrome --spec \"cypress/integration/**/*.spec.js\"",
|
||||
@ -58,6 +58,7 @@
|
||||
"react": "^16.8.6",
|
||||
"react-ace": "^9.1.1",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-flow-renderer": "^5.7.1",
|
||||
"react-hook-form": "^6.0.0",
|
||||
"react-json-pretty": "^2.2.0",
|
||||
"react-redux": "^7.2.0",
|
||||
|
@ -42,9 +42,9 @@ module.exports = {
|
||||
type: SUCCESS_ONE_REQUEST,
|
||||
payload: { files }
|
||||
}),
|
||||
setMarketplaces: marketPlaces => ({
|
||||
setMarketplaces: marketplaces => ({
|
||||
type: SUCCESS_ONE_REQUEST,
|
||||
payload: { marketPlaces }
|
||||
payload: { marketplaces }
|
||||
}),
|
||||
setApps: apps => ({
|
||||
type: SUCCESS_ONE_REQUEST,
|
||||
|
@ -57,9 +57,9 @@ const useStyles = makeStyles(theme => ({
|
||||
}));
|
||||
|
||||
const ClusterCard = React.memo(
|
||||
({ info, isSelected, handleSelect, handleUnselect }) => {
|
||||
({ value, isSelected, handleSelect, handleUnselect }) => {
|
||||
const classes = useStyles();
|
||||
const { ID, NAME, HOSTS, VNETS, DATASTORES } = info;
|
||||
const { ID, NAME, HOSTS, VNETS, DATASTORES } = value;
|
||||
|
||||
const hosts = [HOSTS?.ID ?? []].flat();
|
||||
const vnets = [VNETS?.ID ?? []].flat();
|
||||
|
@ -36,9 +36,9 @@ const useStyles = makeStyles(theme => ({
|
||||
}));
|
||||
|
||||
const NetworkCard = React.memo(
|
||||
({ info, handleEdit, handleClone, handleRemove }) => {
|
||||
({ value, handleEdit, handleClone, handleRemove }) => {
|
||||
const classes = useStyles();
|
||||
const { mandatory, name, description, type, id, extra } = info;
|
||||
const { mandatory, name, description, type, id, extra } = value;
|
||||
|
||||
return (
|
||||
<Fade in unmountOnExit={false}>
|
||||
|
83
src/fireedge/src/public/components/Dialogs/DialogForm.js
Normal file
83
src/fireedge/src/public/components/Dialogs/DialogForm.js
Normal file
@ -0,0 +1,83 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
useMediaQuery,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions
|
||||
} from '@material-ui/core';
|
||||
import { useForm, FormProvider } from 'react-hook-form';
|
||||
import { yupResolver } from '@hookform/resolvers';
|
||||
|
||||
import { Tr } from 'client/components/HOC';
|
||||
|
||||
const DialogForm = React.memo(
|
||||
({ open, title, values, resolver, onSubmit, onCancel, children }) => {
|
||||
const isMobile = useMediaQuery(theme => theme.breakpoints.only('xs'));
|
||||
|
||||
const { handleSubmit, ...methods } = useForm({
|
||||
reValidateMode: 'onSubmit',
|
||||
defaultValues: values,
|
||||
resolver: yupResolver(resolver)
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
fullScreen={isMobile}
|
||||
open={open}
|
||||
maxWidth="lg"
|
||||
scroll="paper"
|
||||
PaperProps={{ style: { height: '80%', minWidth: '80%' } }}
|
||||
>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<FormProvider {...methods}>{children}</FormProvider>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onCancel} color="primary">
|
||||
{Tr('Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
>
|
||||
{Tr('Save')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
DialogForm.propTypes = {
|
||||
open: PropTypes.bool.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
values: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.any),
|
||||
PropTypes.objectOf(PropTypes.any)
|
||||
]),
|
||||
resolver: PropTypes.oneOfType([PropTypes.func, PropTypes.object]).isRequired,
|
||||
onSubmit: PropTypes.func,
|
||||
onCancel: PropTypes.func,
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node
|
||||
])
|
||||
};
|
||||
|
||||
DialogForm.defaultProps = {
|
||||
open: true,
|
||||
title: 'Title dialog form',
|
||||
values: {},
|
||||
resolver: {},
|
||||
onSubmit: () => undefined,
|
||||
onCancel: () => undefined,
|
||||
children: null
|
||||
};
|
||||
|
||||
export default DialogForm;
|
@ -1,234 +0,0 @@
|
||||
import React 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 (
|
||||
<Dialog fullScreen={isMobile} open={open} maxWidth="lg" scroll="paper">
|
||||
<DialogTitle id={`${ID_CY}-title`}>
|
||||
{network?.name ? 'Edit network' : 'New network'}
|
||||
</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
name="mandatory"
|
||||
color="primary"
|
||||
defaultChecked={network?.mandatory}
|
||||
inputProps={{ 'data-cy': `${ID_CY}-mandatory` }}
|
||||
inputRef={register}
|
||||
/>
|
||||
}
|
||||
label={Tr('Mandatory')}
|
||||
labelPlacement="end"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="name"
|
||||
label={Tr('Name')}
|
||||
inputRef={register}
|
||||
inputProps={{ 'data-cy': `${ID_CY}-name` }}
|
||||
error={errors.name}
|
||||
helperText={
|
||||
errors.name && <ErrorHelper label={errors.name?.message} />
|
||||
}
|
||||
FormHelperTextProps={{ 'data-cy': `${ID_CY}-name-error` }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
multiline
|
||||
name="description"
|
||||
label={Tr('Description')}
|
||||
inputRef={register}
|
||||
inputProps={{ 'data-cy': `${ID_CY}-description` }}
|
||||
error={errors.description}
|
||||
helperText={
|
||||
errors.description && (
|
||||
<ErrorHelper label={errors.description?.message} />
|
||||
)
|
||||
}
|
||||
FormHelperTextProps={{
|
||||
'data-cy': `${ID_CY}-description-error`
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Controller
|
||||
as={
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
inputProps={{ 'data-cy': `${ID_CY}-type` }}
|
||||
label={Tr('Select a type')}
|
||||
error={errors.type}
|
||||
helperText={
|
||||
errors.type && (
|
||||
<ErrorHelper label={errors.type?.message} />
|
||||
)
|
||||
}
|
||||
FormHelperTextProps={{
|
||||
'data-cy': `${ID_CY}-type-error`
|
||||
}}
|
||||
>
|
||||
{Object.entries(TYPES_NETWORKS).map(([key, { text }]) => (
|
||||
<MenuItem key={`type-${key}`} value={key}>
|
||||
{text}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
}
|
||||
name="type"
|
||||
control={control}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Controller
|
||||
as={
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
inputProps={{ 'data-cy': `${ID_CY}-id` }}
|
||||
label={Tr('Select a') + SELECT[typeSelected]}
|
||||
error={errors.id}
|
||||
helperText={
|
||||
errors.id && <ErrorHelper label={errors.id?.message} />
|
||||
}
|
||||
FormHelperTextProps={{
|
||||
'data-cy': `${ID_CY}-id-error`
|
||||
}}
|
||||
>
|
||||
{selectType?.map(({ ID, NAME }) => (
|
||||
<MenuItem key={`${typeSelected}-${ID}`} value={ID}>
|
||||
{NAME}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
}
|
||||
name="id"
|
||||
control={control}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
multiline
|
||||
name="extra"
|
||||
label={Tr('Extra template')}
|
||||
inputRef={register}
|
||||
inputProps={{ 'data-cy': `${ID_CY}-extra` }}
|
||||
error={errors.extra}
|
||||
helperText={
|
||||
errors.extra && <ErrorHelper label={errors.extra?.message} />
|
||||
}
|
||||
FormHelperTextProps={{
|
||||
'data-cy': `${ID_CY}-extra-error`
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onCancel} color="primary">
|
||||
{Tr('Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
>
|
||||
{Tr('Save')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default NetworkDialog;
|
@ -1,96 +0,0 @@
|
||||
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 (
|
||||
<Dialog fullScreen={isMobile} open={open} maxWidth="lg" scroll="paper">
|
||||
<DialogTitle id={`${ID_CY}-title`}>
|
||||
{role?.name ? 'Edit role' : 'New role'}
|
||||
</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
{'ROLE FORM'}
|
||||
{/* <Controller
|
||||
as={
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
inputProps={{ 'data-cy': `${ID_CY}-id` }}
|
||||
label={Tr('Select a vm')}
|
||||
error={errors.template}
|
||||
helperText={
|
||||
errors.template && (
|
||||
<ErrorHelper label={errors.template?.message} />
|
||||
)
|
||||
}
|
||||
FormHelperTextProps={{
|
||||
'data-cy': `${ID_CY}-id-error`
|
||||
}}
|
||||
>
|
||||
{templates?.map(({ ID, NAME }) => (
|
||||
<MenuItem key={`template-${ID}`} value={ID}>
|
||||
{NAME}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
}
|
||||
name="template"
|
||||
control={control}
|
||||
/> */}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onCancel} color="primary">
|
||||
{Tr('Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={handleSubmit(() => onSubmit(role))}
|
||||
>
|
||||
{Tr('Save')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
});
|
||||
|
||||
export default NetworkDialog;
|
@ -1,4 +1,3 @@
|
||||
import NetworkDialog from 'client/components/Dialogs/NetworkDialog';
|
||||
import RoleDialog from 'client/components/Dialogs/RoleDialog';
|
||||
import DialogForm from 'client/components/Dialogs/DialogForm';
|
||||
|
||||
export { NetworkDialog, RoleDialog };
|
||||
export { DialogForm };
|
||||
|
101
src/fireedge/src/public/components/Flows/FlowWithFAB.js
Normal file
101
src/fireedge/src/public/components/Flows/FlowWithFAB.js
Normal file
@ -0,0 +1,101 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Fab, Box, Card } from '@material-ui/core';
|
||||
import AddIcon from '@material-ui/icons/Add';
|
||||
|
||||
import ReactFlow, {
|
||||
removeElements,
|
||||
addEdge,
|
||||
MiniMap,
|
||||
Background,
|
||||
isNode
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
const initialElements = [];
|
||||
|
||||
const onNodeDragStart = (event, node) => console.log('drag start', node);
|
||||
const onNodeDragStop = (event, node) => console.log('drag stop', node);
|
||||
const onSelectionDrag = (event, nodes) => console.log('selection drag', nodes);
|
||||
const onSelectionDragStart = (event, nodes) =>
|
||||
console.log('selection drag start', nodes);
|
||||
const onSelectionDragStop = (event, nodes) =>
|
||||
console.log('selection drag stop', nodes);
|
||||
const onElementClick = (event, element) =>
|
||||
console.log(`${isNode(element) ? 'node' : 'edge'} click:`, element);
|
||||
const onSelectionChange = elements => console.log('selection change', elements);
|
||||
const onLoad = reactFlowInstance => {
|
||||
console.log('flow loaded:', reactFlowInstance);
|
||||
reactFlowInstance.fitView();
|
||||
};
|
||||
|
||||
const onMoveEnd = transform => console.log('zoom/move end', transform);
|
||||
|
||||
const connectionLineStyle = { stroke: '#ddd' };
|
||||
const snapGrid = [16, 16];
|
||||
|
||||
const CustomNode = React.memo(({ data }) => (
|
||||
<Card style={{ height: 100 }}>
|
||||
<div>Custom node</div>
|
||||
</Card>
|
||||
));
|
||||
|
||||
const FlowWithFAB = ({ handleClick }) => {
|
||||
const [elements, setElements] = useState(initialElements);
|
||||
const onElementsRemove = elementsToRemove =>
|
||||
setElements(els => removeElements(elementsToRemove, els));
|
||||
const onConnect = params => setElements(els => addEdge(params, els));
|
||||
|
||||
return (
|
||||
<Box flexGrow={1} height={1}>
|
||||
<ReactFlow
|
||||
elements={elements}
|
||||
onElementClick={onElementClick}
|
||||
onElementsRemove={onElementsRemove}
|
||||
onConnect={onConnect}
|
||||
onNodeDragStart={onNodeDragStart}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
onSelectionDragStart={onSelectionDragStart}
|
||||
onSelectionDrag={onSelectionDrag}
|
||||
onSelectionDragStop={onSelectionDragStop}
|
||||
onSelectionChange={onSelectionChange}
|
||||
onMoveEnd={onMoveEnd}
|
||||
onLoad={onLoad}
|
||||
connectionLineStyle={connectionLineStyle}
|
||||
snapToGrid
|
||||
snapGrid={snapGrid}
|
||||
nodeTypes={{ custom: CustomNode }}
|
||||
>
|
||||
<MiniMap
|
||||
nodeColor={n => {
|
||||
if (n.style?.background) return n.style.background;
|
||||
if (n.type === 'input') return '#9999ff';
|
||||
if (n.type === 'output') return '#79c9b7';
|
||||
if (n.type === 'default') return '#ff6060';
|
||||
|
||||
return '#eee';
|
||||
}}
|
||||
/>
|
||||
<Fab
|
||||
color="primary"
|
||||
aria-label="add-role"
|
||||
onClick={handleClick}
|
||||
style={{ top: 10, left: 10, zIndex: 5 }}
|
||||
>
|
||||
<AddIcon />
|
||||
</Fab>
|
||||
<Background color="#aaa" gap={16} />
|
||||
</ReactFlow>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
FlowWithFAB.propTypes = {
|
||||
handleClick: PropTypes.func
|
||||
};
|
||||
|
||||
FlowWithFAB.defaultProps = {
|
||||
handleClick: evt => evt
|
||||
};
|
||||
|
||||
export default FlowWithFAB;
|
@ -1,42 +1,17 @@
|
||||
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';
|
||||
import { Box } from '@material-ui/core';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
|
||||
import ErrorHelper from 'client/components/FormControl/ErrorHelper';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
cardPlus: {
|
||||
height: '100%',
|
||||
minHeight: 140,
|
||||
display: 'flex',
|
||||
textAlign: 'center'
|
||||
}
|
||||
}));
|
||||
|
||||
function FormDialog({ step, data, setFormData }) {
|
||||
const classes = useStyles();
|
||||
function FormList({ step, data, setFormData }) {
|
||||
const { errors } = useFormContext();
|
||||
const [dialogFormData, setDialogFormData] = useState({});
|
||||
const [showDialog, setShowDialog] = useState(false);
|
||||
|
||||
const {
|
||||
id,
|
||||
addCardAction,
|
||||
preRender,
|
||||
InfoComponent,
|
||||
DialogComponent,
|
||||
DEFAULT_DATA
|
||||
} = step;
|
||||
const { id, preRender, ListComponent, DialogComponent, DEFAULT_DATA } = step;
|
||||
|
||||
useEffect(() => {
|
||||
preRender && preRender();
|
||||
@ -81,43 +56,22 @@ function FormDialog({ step, data, setFormData }) {
|
||||
|
||||
return (
|
||||
<Box component="form">
|
||||
<Grid container spacing={3}>
|
||||
{typeof errors[id]?.message === 'string' && (
|
||||
<Grid item xs={12}>
|
||||
<ErrorHelper label={errors[id]?.message} />
|
||||
</Grid>
|
||||
)}
|
||||
{addCardAction &&
|
||||
React.useMemo(
|
||||
() => (
|
||||
<Grid item xs={12} sm={4} md={3} lg={2}>
|
||||
<Card className={classes.cardPlus} raised>
|
||||
<CardActionArea onClick={() => handleOpen()}>
|
||||
<CardContent>
|
||||
<Add />
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
</Grid>
|
||||
),
|
||||
[handleOpen, classes]
|
||||
)}
|
||||
{Array.isArray(data) &&
|
||||
data?.map((info, index) => (
|
||||
<Grid key={`${id}-${index}`} item xs={12} sm={4} md={3} lg={2}>
|
||||
<InfoComponent
|
||||
info={info}
|
||||
handleEdit={() => handleOpen(index)}
|
||||
handleClone={() => handleClone(index)}
|
||||
handleRemove={() => handleRemove(index)}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
{typeof errors[id]?.message === 'string' && (
|
||||
<ErrorHelper label={errors[id]?.message} />
|
||||
)}
|
||||
<ListComponent
|
||||
list={data}
|
||||
addCardClick={() => handleOpen()}
|
||||
itemsProps={({ index }) => ({
|
||||
handleEdit: () => handleOpen(index),
|
||||
handleClone: () => handleClone(index),
|
||||
handleRemove: () => handleRemove(index)
|
||||
})}
|
||||
/>
|
||||
{showDialog && DialogComponent && (
|
||||
<DialogComponent
|
||||
open={showDialog}
|
||||
info={dialogFormData?.data}
|
||||
values={dialogFormData?.data}
|
||||
onSubmit={handleSubmit}
|
||||
onCancel={handleClose}
|
||||
/>
|
||||
@ -126,16 +80,16 @@ function FormDialog({ step, data, setFormData }) {
|
||||
);
|
||||
}
|
||||
|
||||
FormDialog.propTypes = {
|
||||
FormList.propTypes = {
|
||||
step: PropTypes.objectOf(PropTypes.any).isRequired,
|
||||
data: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
setFormData: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
FormDialog.defaultProps = {
|
||||
FormList.defaultProps = {
|
||||
step: {},
|
||||
data: [],
|
||||
setFormData: data => data
|
||||
};
|
||||
|
||||
export default FormDialog;
|
||||
export default FormList;
|
@ -8,7 +8,7 @@ import ErrorHelper from '../FormControl/ErrorHelper';
|
||||
|
||||
function FormListSelect({ step, data, setFormData }) {
|
||||
const { errors } = useFormContext();
|
||||
const { id, onlyOneSelect, preRender, list, InfoComponent } = step;
|
||||
const { id, onlyOneSelect, preRender, list, ItemComponent } = step;
|
||||
|
||||
useEffect(() => {
|
||||
preRender && preRender();
|
||||
@ -37,7 +37,7 @@ function FormListSelect({ step, data, setFormData }) {
|
||||
{Array.isArray(list) &&
|
||||
list?.map((info, index) => (
|
||||
<Grid key={`${id}-${index}`} item xs={6} sm={4} md={3} lg={1}>
|
||||
<InfoComponent
|
||||
<ItemComponent
|
||||
info={info}
|
||||
isSelected={data?.some(selected => selected === info?.ID)}
|
||||
handleSelect={handleSelect}
|
||||
|
@ -1,18 +1,44 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
|
||||
import ErrorHelper from 'client/components/FormControl/ErrorHelper';
|
||||
|
||||
function FormStep({ step, data, setFormData }) {
|
||||
const { reset, errors } = useFormContext();
|
||||
const { id, preRender, FormComponent } = step;
|
||||
const { errors } = useFormContext();
|
||||
const [showDialog, setShowDialog] = useState(false);
|
||||
const { id, preRender, FormComponent, DialogComponent } = step;
|
||||
|
||||
useEffect(() => {
|
||||
preRender && preRender();
|
||||
reset({ [id]: data }, { errors: true });
|
||||
}, []);
|
||||
|
||||
return React.useMemo(() => <FormComponent id={id} />, [id]);
|
||||
const handleOpen = () => setShowDialog(true);
|
||||
const handleClose = () => setShowDialog(false);
|
||||
const handleSubmit = d => console.log(d);
|
||||
|
||||
return (
|
||||
<>
|
||||
{typeof errors[id]?.message === 'string' && (
|
||||
<ErrorHelper label={errors[id]?.message} />
|
||||
)}
|
||||
{React.useMemo(
|
||||
() => (
|
||||
<FormComponent id={id} handleClick={handleOpen} />
|
||||
),
|
||||
[id, handleOpen]
|
||||
)}
|
||||
{showDialog && DialogComponent && (
|
||||
<DialogComponent
|
||||
open={showDialog}
|
||||
values={data}
|
||||
onSubmit={handleSubmit}
|
||||
onCancel={handleClose}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
FormStep.propTypes = {
|
||||
|
@ -1,11 +1,19 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Button, MobileStepper } from '@material-ui/core';
|
||||
import { styled, Button, MobileStepper } from '@material-ui/core';
|
||||
import { KeyboardArrowLeft, KeyboardArrowRight } from '@material-ui/icons';
|
||||
|
||||
import { Tr } from 'client/components/HOC';
|
||||
|
||||
const StickyMobileStepper = styled(MobileStepper)({
|
||||
position: 'sticky',
|
||||
top: -15,
|
||||
backdropFilter: 'blur(5px)',
|
||||
background: '#fafafa9c',
|
||||
zIndex: 1
|
||||
});
|
||||
|
||||
const CustomMobileStepper = ({
|
||||
totalSteps,
|
||||
activeStep,
|
||||
@ -14,12 +22,11 @@ const CustomMobileStepper = ({
|
||||
handleNext,
|
||||
handleBack
|
||||
}) => (
|
||||
<MobileStepper
|
||||
<StickyMobileStepper
|
||||
variant="progress"
|
||||
position="static"
|
||||
steps={totalSteps}
|
||||
activeStep={activeStep}
|
||||
style={{ flexGrow: 1 }}
|
||||
backButton={
|
||||
<Button size="small" onClick={handleBack} disabled={disabledBack}>
|
||||
<KeyboardArrowLeft /> {Tr('Back')}
|
||||
|
@ -1,17 +1,24 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Button, Stepper, Step, StepLabel, Box } from '@material-ui/core';
|
||||
import {
|
||||
styled,
|
||||
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 StickyStepper = styled(Stepper)({
|
||||
position: 'sticky',
|
||||
top: -15,
|
||||
backdropFilter: 'blur(5px)',
|
||||
background: '#fafafa9c',
|
||||
zIndex: 1
|
||||
});
|
||||
|
||||
const CustomStepper = ({
|
||||
steps,
|
||||
@ -22,13 +29,13 @@ const CustomStepper = ({
|
||||
handleBack
|
||||
}) => (
|
||||
<>
|
||||
<Stepper activeStep={activeStep}>
|
||||
<StickyStepper activeStep={activeStep}>
|
||||
{steps?.map(({ label }) => (
|
||||
<Step key={label}>
|
||||
<StepLabel>{label}</StepLabel>
|
||||
</Step>
|
||||
))}
|
||||
</Stepper>
|
||||
</StickyStepper>
|
||||
<Box marginY={2}>
|
||||
<Button onClick={handleBack} disabled={disabledBack}>
|
||||
{Tr('Back')}
|
||||
|
@ -15,12 +15,12 @@ const InputController = {
|
||||
[TYPE_INPUT.CHECKBOX]: CheckboxController
|
||||
};
|
||||
|
||||
const FormWithSchema = ({ id, cy, schema }) => {
|
||||
const FormWithSchema = ({ id, cy, fields }) => {
|
||||
const { control, errors } = useFormContext();
|
||||
|
||||
return (
|
||||
<Grid container spacing={3}>
|
||||
{schema?.map(({ name, type, label, values }) => {
|
||||
<Grid container spacing={1}>
|
||||
{fields?.map(({ name, type, label, values }) => {
|
||||
const dataCy = `${cy}-${name}`;
|
||||
const inputName = id ? `${id}.${name}` : name;
|
||||
const formError = id ? errors[id] : errors;
|
||||
@ -28,14 +28,15 @@ const FormWithSchema = ({ id, cy, schema }) => {
|
||||
|
||||
return (
|
||||
<Grid key={`${cy}-${name}`} item xs={12} md={6}>
|
||||
{React.createElement(InputController[type], {
|
||||
control,
|
||||
cy: dataCy,
|
||||
name: inputName,
|
||||
label,
|
||||
values,
|
||||
error: inputError
|
||||
})}
|
||||
{InputController[type] &&
|
||||
React.createElement(InputController[type], {
|
||||
control,
|
||||
cy: dataCy,
|
||||
name: inputName,
|
||||
label,
|
||||
values,
|
||||
error: inputError
|
||||
})}
|
||||
</Grid>
|
||||
);
|
||||
})}
|
||||
@ -46,13 +47,13 @@ const FormWithSchema = ({ id, cy, schema }) => {
|
||||
FormWithSchema.propTypes = {
|
||||
id: PropTypes.string,
|
||||
cy: PropTypes.string,
|
||||
schema: PropTypes.arrayOf(PropTypes.object)
|
||||
fields: PropTypes.arrayOf(PropTypes.object)
|
||||
};
|
||||
|
||||
FormWithSchema.defaultProps = {
|
||||
id: '',
|
||||
cy: 'form',
|
||||
schema: []
|
||||
fields: []
|
||||
};
|
||||
|
||||
export default FormWithSchema;
|
||||
|
70
src/fireedge/src/public/components/List/ListCards.js
Normal file
70
src/fireedge/src/public/components/List/ListCards.js
Normal file
@ -0,0 +1,70 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
makeStyles,
|
||||
CardActionArea,
|
||||
CardContent,
|
||||
Card,
|
||||
Grid
|
||||
} from '@material-ui/core';
|
||||
import AddIcon from '@material-ui/icons/Add';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
cardPlus: {
|
||||
height: '100%',
|
||||
minHeight: 140,
|
||||
display: 'flex',
|
||||
textAlign: 'center'
|
||||
}
|
||||
}));
|
||||
|
||||
function ListCards({ addCardClick, list, CardComponent, cardsProps }) {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<Grid container spacing={3}>
|
||||
{addCardClick &&
|
||||
React.useMemo(
|
||||
() => (
|
||||
<Grid item xs={12} sm={4} md={3} lg={2}>
|
||||
<Card className={classes.cardPlus} raised>
|
||||
<CardActionArea onClick={addCardClick}>
|
||||
<CardContent>
|
||||
<AddIcon />
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
</Grid>
|
||||
),
|
||||
[addCardClick, classes]
|
||||
)}
|
||||
{Array.isArray(list) &&
|
||||
list?.map((value, index) => (
|
||||
<Grid key={`card-${index}`} item xs={12} sm={4} md={3} lg={2}>
|
||||
<CardComponent value={value} {...cardsProps({ index })} />
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
ListCards.propTypes = {
|
||||
list: PropTypes.arrayOf(PropTypes.any).isRequired,
|
||||
addCardClick: PropTypes.func,
|
||||
CardComponent: PropTypes.oneOfType([
|
||||
PropTypes.node,
|
||||
PropTypes.object,
|
||||
PropTypes.element
|
||||
]),
|
||||
cardsProps: PropTypes.func
|
||||
};
|
||||
|
||||
ListCards.defaultProps = {
|
||||
list: [],
|
||||
addCardClick: [],
|
||||
CardComponent: null,
|
||||
cardsProps: () => undefined
|
||||
};
|
||||
|
||||
export default ListCards;
|
@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
|
||||
import FormStep from 'client/components/FormStepper/FormStep';
|
||||
import FormWithSchema from 'client/components/Forms/FormWithSchema';
|
||||
|
||||
import { FORM_FIELDS, STEP_FORM_SCHEMA } from './schema';
|
||||
|
||||
const BasicConfiguration = () => {
|
||||
const STEP_ID = 'service';
|
||||
|
||||
return {
|
||||
id: STEP_ID,
|
||||
label: 'Service configuration',
|
||||
content: FormStep,
|
||||
resolver: STEP_FORM_SCHEMA,
|
||||
FormComponent: () => (
|
||||
<FormWithSchema cy="form-flow" fields={FORM_FIELDS} id={STEP_ID} />
|
||||
)
|
||||
};
|
||||
};
|
||||
|
||||
export default BasicConfiguration;
|
@ -1,4 +1,6 @@
|
||||
import * as yup from 'yup';
|
||||
import { TYPE_INPUT } from 'client/constants';
|
||||
import { getValidationFromFields } from 'client/utils/helpers';
|
||||
|
||||
export const STRATEGIES_DEPLOY = [
|
||||
{ text: 'None', value: 'none' },
|
||||
@ -11,39 +13,58 @@ export const SHUTDOWN_ACTIONS = [
|
||||
{ text: 'Terminate hard', value: 'shutdown-hard' }
|
||||
];
|
||||
|
||||
export default [
|
||||
export const FORM_FIELDS = [
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
type: TYPE_INPUT.TEXT,
|
||||
initial: ''
|
||||
validation: yup
|
||||
.string()
|
||||
.min(5)
|
||||
.trim()
|
||||
.required('Name field is required')
|
||||
.default('One_service')
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
type: TYPE_INPUT.TEXT,
|
||||
multiline: true,
|
||||
initial: ''
|
||||
validation: yup
|
||||
.string()
|
||||
.trim()
|
||||
.default('OpenNebula is so cool!')
|
||||
},
|
||||
{
|
||||
name: 'deployment',
|
||||
label: 'Select a strategy',
|
||||
type: TYPE_INPUT.SELECT,
|
||||
initial: STRATEGIES_DEPLOY[1].value,
|
||||
values: STRATEGIES_DEPLOY
|
||||
values: STRATEGIES_DEPLOY,
|
||||
validation: yup
|
||||
.string()
|
||||
.required()
|
||||
.oneOf(STRATEGIES_DEPLOY.map(({ value }) => value))
|
||||
.default(STRATEGIES_DEPLOY[0].value)
|
||||
},
|
||||
{
|
||||
name: 'shutdown_action',
|
||||
label: 'Select a VM shutdown action',
|
||||
type: TYPE_INPUT.SELECT,
|
||||
initial: SHUTDOWN_ACTIONS[0].value,
|
||||
values: SHUTDOWN_ACTIONS
|
||||
values: SHUTDOWN_ACTIONS,
|
||||
validation: yup
|
||||
.string()
|
||||
.oneOf(SHUTDOWN_ACTIONS.map(({ value }) => value))
|
||||
.default(SHUTDOWN_ACTIONS[0].value)
|
||||
},
|
||||
{
|
||||
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
|
||||
validation: yup.boolean().default(false)
|
||||
}
|
||||
];
|
||||
|
||||
export const STEP_FORM_SCHEMA = yup.object(
|
||||
getValidationFromFields(FORM_FIELDS)
|
||||
);
|
@ -0,0 +1,29 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import useOpennebula from 'client/hooks/useOpennebula';
|
||||
|
||||
import { ClusterCard } from 'client/components/Cards';
|
||||
import FormListSelect from 'client/components/FormStepper/FormListSelect';
|
||||
|
||||
import { STEP_FORM_SCHEMA } from './schema';
|
||||
|
||||
const Clusters = () => {
|
||||
const STEP_ID = 'clusters';
|
||||
const { clusters, getClusters } = useOpennebula();
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
id: STEP_ID,
|
||||
label: 'Where will it run?',
|
||||
content: FormListSelect,
|
||||
resolver: STEP_FORM_SCHEMA,
|
||||
onlyOneSelect: true,
|
||||
preRender: getClusters,
|
||||
list: clusters,
|
||||
ItemComponent: ClusterCard
|
||||
}),
|
||||
[getClusters, clusters]
|
||||
);
|
||||
};
|
||||
|
||||
export default Clusters;
|
@ -0,0 +1,9 @@
|
||||
import * as yup from 'yup';
|
||||
|
||||
export const STEP_FORM_SCHEMA = yup
|
||||
.array()
|
||||
.of(yup.string().trim())
|
||||
.min(1)
|
||||
.max(1)
|
||||
.required()
|
||||
.default([]);
|
@ -0,0 +1,52 @@
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import useOpennebula from 'client/hooks/useOpennebula';
|
||||
|
||||
import { DialogForm } from 'client/components/Dialogs';
|
||||
import { NetworkCard } from 'client/components/Cards';
|
||||
|
||||
import FormList from 'client/components/FormStepper/FormList';
|
||||
import FormWithSchema from 'client/components/Forms/FormWithSchema';
|
||||
import ListCards from 'client/components/List/ListCards';
|
||||
|
||||
import { FORM_FIELDS, NETWORK_FORM_SCHEMA, STEP_FORM_SCHEMA } from './schema';
|
||||
|
||||
const Networks = () => {
|
||||
const STEP_ID = 'networks';
|
||||
const { getVNetworks, getVNetworksTemplates } = useOpennebula();
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
id: STEP_ID,
|
||||
label: 'Networks configuration',
|
||||
content: FormList,
|
||||
preRender: () => {
|
||||
getVNetworks();
|
||||
getVNetworksTemplates();
|
||||
},
|
||||
resolver: STEP_FORM_SCHEMA,
|
||||
DEFAULT_DATA: NETWORK_FORM_SCHEMA.default(),
|
||||
ListComponent: ({ list, addCardClick, itemsProps }) => (
|
||||
<ListCards
|
||||
list={list}
|
||||
addCardClick={addCardClick}
|
||||
CardComponent={NetworkCard}
|
||||
cardsProps={itemsProps}
|
||||
/>
|
||||
),
|
||||
ItemComponent: NetworkCard,
|
||||
DialogComponent: props => (
|
||||
<DialogForm
|
||||
title={'Network form'}
|
||||
resolver={NETWORK_FORM_SCHEMA}
|
||||
{...props}
|
||||
>
|
||||
<FormWithSchema cy="form-dg-network" fields={FORM_FIELDS} />
|
||||
</DialogForm>
|
||||
)
|
||||
}),
|
||||
[getVNetworks, getVNetworksTemplates]
|
||||
);
|
||||
};
|
||||
|
||||
export default Networks;
|
@ -0,0 +1,110 @@
|
||||
import * as yup from 'yup';
|
||||
import { TYPE_INPUT } from 'client/constants';
|
||||
import { getValidationFromFields } from 'client/utils/helpers';
|
||||
|
||||
const DEFAULT_NETWORK = {
|
||||
mandatory: true,
|
||||
name: 'Public_dev',
|
||||
description: 'Public network in development mode',
|
||||
type: 'id',
|
||||
id: '0',
|
||||
extra: 'size=5'
|
||||
};
|
||||
|
||||
export const SELECT = {
|
||||
template: 'template',
|
||||
network: 'network'
|
||||
};
|
||||
|
||||
export const TYPES_NETWORKS = [
|
||||
{ text: 'Create', value: 'template_id', select: SELECT.template },
|
||||
{ text: 'Reserve', value: 'reserve_from', select: SELECT.network },
|
||||
{ text: 'Existing', value: 'id', select: SELECT.network }
|
||||
];
|
||||
|
||||
export const FORM_FIELDS = [
|
||||
{
|
||||
name: 'mandatory',
|
||||
label: 'Mandatory',
|
||||
type: TYPE_INPUT.CHECKBOX,
|
||||
validation: yup
|
||||
.boolean()
|
||||
.required()
|
||||
.default(false)
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
type: TYPE_INPUT.TEXT,
|
||||
validation: yup
|
||||
.string()
|
||||
.trim()
|
||||
.required('Name is a required field')
|
||||
.default('')
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
type: TYPE_INPUT.TEXT,
|
||||
multiline: true,
|
||||
validation: yup
|
||||
.string()
|
||||
.trim()
|
||||
.default('')
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
label: 'Select a type',
|
||||
type: TYPE_INPUT.SELECT,
|
||||
values: TYPES_NETWORKS,
|
||||
validation: yup
|
||||
.string()
|
||||
.oneOf(TYPES_NETWORKS.map(({ value }) => value))
|
||||
.required('Type is required field')
|
||||
.default(TYPES_NETWORKS[0].value)
|
||||
},
|
||||
{
|
||||
name: 'id',
|
||||
label: `Select a network`,
|
||||
type: TYPE_INPUT.TEXT,
|
||||
validation: yup
|
||||
.string()
|
||||
.when('type', {
|
||||
is: type =>
|
||||
TYPES_NETWORKS.some(
|
||||
({ value, select }) => type === value && select === SELECT.network
|
||||
),
|
||||
then: yup
|
||||
.string()
|
||||
.trim()
|
||||
.required('Network is required field'),
|
||||
otherwise: yup
|
||||
.string()
|
||||
.trim()
|
||||
.required('Network template is required field')
|
||||
})
|
||||
.required()
|
||||
.default('')
|
||||
},
|
||||
{
|
||||
name: 'extra',
|
||||
label: 'Extra',
|
||||
multiline: true,
|
||||
type: TYPE_INPUT.TEXT,
|
||||
validation: yup
|
||||
.string()
|
||||
.trim()
|
||||
.default('')
|
||||
}
|
||||
];
|
||||
|
||||
export const NETWORK_FORM_SCHEMA = yup.object(
|
||||
getValidationFromFields(FORM_FIELDS)
|
||||
);
|
||||
|
||||
export const STEP_FORM_SCHEMA = yup
|
||||
.array()
|
||||
.of(NETWORK_FORM_SCHEMA)
|
||||
.min(2)
|
||||
.required()
|
||||
.default([DEFAULT_NETWORK, DEFAULT_NETWORK]);
|
@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
|
||||
import FormStep from 'client/components/FormStepper/FormStep';
|
||||
import FormWithSchema from 'client/components/Forms/FormWithSchema';
|
||||
|
||||
import { FORM_FIELDS, STEP_FORM_SCHEMA } from './schema';
|
||||
|
||||
const BasicConfiguration = () => {
|
||||
const STEP_ID = 'role';
|
||||
|
||||
return {
|
||||
id: STEP_ID,
|
||||
label: 'Role configuration',
|
||||
content: FormStep,
|
||||
resolver: STEP_FORM_SCHEMA,
|
||||
FormComponent: () => (
|
||||
<FormWithSchema cy="form-flow" fields={FORM_FIELDS} id={STEP_ID} />
|
||||
)
|
||||
};
|
||||
};
|
||||
|
||||
export default BasicConfiguration;
|
@ -0,0 +1,31 @@
|
||||
import * as yup from 'yup';
|
||||
import { TYPE_INPUT } from 'client/constants';
|
||||
import { getValidationFromFields } from 'client/utils/helpers';
|
||||
|
||||
export const FORM_FIELDS = [
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
type: TYPE_INPUT.TEXT,
|
||||
validation: yup
|
||||
.string()
|
||||
.min(1)
|
||||
.trim()
|
||||
.required('Name field is required')
|
||||
.default('Main')
|
||||
},
|
||||
{
|
||||
name: 'cardinality',
|
||||
label: 'Cardinality',
|
||||
type: TYPE_INPUT.TEXT,
|
||||
validation: yup
|
||||
.number()
|
||||
.min(1)
|
||||
.required()
|
||||
.default(1)
|
||||
}
|
||||
];
|
||||
|
||||
export const STEP_FORM_SCHEMA = yup.object(
|
||||
getValidationFromFields(FORM_FIELDS)
|
||||
);
|
@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
|
||||
import FormStep from 'client/components/FormStepper/FormStep';
|
||||
|
||||
import { STEP_FORM_SCHEMA } from './schema';
|
||||
|
||||
const Template = () => {
|
||||
const STEP_ID = 'template';
|
||||
|
||||
return {
|
||||
id: STEP_ID,
|
||||
label: 'Template VM',
|
||||
content: FormStep,
|
||||
resolver: STEP_FORM_SCHEMA,
|
||||
FormComponent: () => <h1>Screen with options</h1>
|
||||
};
|
||||
};
|
||||
|
||||
export default Template;
|
@ -0,0 +1,21 @@
|
||||
import * as yup from 'yup';
|
||||
import { TYPE_INPUT } from 'client/constants';
|
||||
import { getValidationFromFields } from 'client/utils/helpers';
|
||||
|
||||
export const FORM_FIELDS = [
|
||||
{
|
||||
name: 'template',
|
||||
label: 'Template VM',
|
||||
type: TYPE_INPUT.TEXT,
|
||||
validation: yup
|
||||
.string()
|
||||
.min(1)
|
||||
.trim()
|
||||
.required('Template field is required')
|
||||
.default('0')
|
||||
}
|
||||
];
|
||||
|
||||
export const STEP_FORM_SCHEMA = yup.object(
|
||||
getValidationFromFields(FORM_FIELDS)
|
||||
);
|
@ -0,0 +1,25 @@
|
||||
import * as yup from 'yup';
|
||||
|
||||
import BasicConfiguration from './BasicConfiguration';
|
||||
import Template from './Template';
|
||||
// import Policies from './Policies';
|
||||
|
||||
const Steps = () => {
|
||||
const basic = BasicConfiguration();
|
||||
const template = Template();
|
||||
// const policies = Policies();
|
||||
|
||||
const steps = [basic, template];
|
||||
|
||||
const resolvers = yup.object({
|
||||
[basic.id]: basic.resolver,
|
||||
[template.id]: template.resolver
|
||||
// [policies.id]: policies.resolver
|
||||
});
|
||||
|
||||
const defaultValues = resolvers.default();
|
||||
|
||||
return { steps, defaultValues, resolvers };
|
||||
};
|
||||
|
||||
export default Steps;
|
@ -0,0 +1,54 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import * as yup from 'yup';
|
||||
|
||||
import { useForm, FormProvider } from 'react-hook-form';
|
||||
import { yupResolver } from '@hookform/resolvers';
|
||||
|
||||
import FormStepper from 'client/components/FormStepper';
|
||||
import { DialogForm } from 'client/components/Dialogs';
|
||||
import FormStep from 'client/components/FormStepper/FormStep';
|
||||
import FlowWithFAB from 'client/components/Flows/FlowWithFAB';
|
||||
|
||||
import Steps from './Steps';
|
||||
|
||||
const Roles = () => {
|
||||
const STEP_ID = 'roles';
|
||||
const { steps, defaultValues, resolvers } = Steps();
|
||||
|
||||
const methods = useForm({
|
||||
mode: 'onBlur',
|
||||
defaultValues,
|
||||
resolver: yupResolver(resolvers)
|
||||
});
|
||||
|
||||
const onSubmit = d => console.log('role data form', d);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
id: STEP_ID,
|
||||
label: 'Defining each role',
|
||||
content: FormStep,
|
||||
resolver: yup
|
||||
.array()
|
||||
.of(resolvers)
|
||||
.min(1)
|
||||
.required()
|
||||
.default([]),
|
||||
DialogComponent: props => (
|
||||
<DialogForm title={'Role form'} resolver={resolvers} {...props}>
|
||||
<FormProvider {...methods}>
|
||||
<FormStepper
|
||||
steps={steps}
|
||||
initialValue={defaultValues}
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
</FormProvider>
|
||||
</DialogForm>
|
||||
),
|
||||
FormComponent: FlowWithFAB
|
||||
}),
|
||||
[]
|
||||
);
|
||||
};
|
||||
|
||||
export default Roles;
|
@ -0,0 +1,28 @@
|
||||
import * as yup from 'yup';
|
||||
|
||||
import BasicConfiguration from './BasicConfiguration';
|
||||
import Networks from './Networks';
|
||||
import Roles from './Roles';
|
||||
import Clusters from './Clusters';
|
||||
|
||||
const Steps = () => {
|
||||
const basic = BasicConfiguration();
|
||||
const networks = Networks();
|
||||
const roles = Roles();
|
||||
const clusters = Clusters();
|
||||
|
||||
const steps = [basic, networks, roles, clusters];
|
||||
|
||||
const resolvers = yup.object({
|
||||
[basic.id]: basic.resolver,
|
||||
[networks.id]: networks.resolver,
|
||||
[roles.id]: roles.resolver,
|
||||
[clusters.id]: clusters.resolver
|
||||
});
|
||||
|
||||
const defaultValues = resolvers.default();
|
||||
|
||||
return { steps, defaultValues, resolvers };
|
||||
};
|
||||
|
||||
export default Steps;
|
@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Container } from '@material-ui/core';
|
||||
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 { Container } from '@material-ui/core';
|
||||
import Steps from 'client/containers/Application/Create/Steps';
|
||||
|
||||
function ApplicationCreate() {
|
||||
const { steps, defaultValues, resolvers } = Steps();
|
||||
@ -19,7 +19,10 @@ function ApplicationCreate() {
|
||||
const onSubmit = formData => console.log('submit', formData, methods.errors);
|
||||
|
||||
return (
|
||||
<Container disableGutters>
|
||||
<Container
|
||||
disableGutters
|
||||
style={{ height: '100%', display: 'flex', flexDirection: 'column' }}
|
||||
>
|
||||
<FormProvider {...methods}>
|
||||
<FormStepper
|
||||
steps={steps}
|
||||
|
@ -1,148 +0,0 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import * as yup from 'yup';
|
||||
|
||||
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, {
|
||||
SHUTDOWN_ACTIONS,
|
||||
STRATEGIES_DEPLOY
|
||||
} 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 }),
|
||||
{}
|
||||
),
|
||||
resolver: yup.object().shape({
|
||||
name: yup
|
||||
.string()
|
||||
.min(5)
|
||||
.trim()
|
||||
.required('is required'),
|
||||
description: yup.string().trim(),
|
||||
deployment: yup
|
||||
.string()
|
||||
.required()
|
||||
.oneOf(STRATEGIES_DEPLOY.map(({ value }) => value)),
|
||||
shutdown_action: yup
|
||||
.string()
|
||||
.oneOf(SHUTDOWN_ACTIONS.map(({ value }) => value)),
|
||||
ready_status_gate: yup.boolean()
|
||||
}),
|
||||
FormComponent: props => (
|
||||
<FormWithSchema cy="form-flow" schema={Schema} {...props} />
|
||||
)
|
||||
},
|
||||
{
|
||||
id: 'networks',
|
||||
label: 'Networks configuration',
|
||||
content: FormDialog,
|
||||
preRender: () => {
|
||||
getVNetworks();
|
||||
getVNetworksTemplates();
|
||||
},
|
||||
defaultValue: [],
|
||||
resolver: yup
|
||||
.array()
|
||||
.min(2)
|
||||
.required(),
|
||||
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: [],
|
||||
resolver: yup
|
||||
.array()
|
||||
.min(1)
|
||||
.required(),
|
||||
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: [],
|
||||
resolver: yup
|
||||
.array()
|
||||
.min(1)
|
||||
.max(1)
|
||||
.required(),
|
||||
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]
|
||||
);
|
||||
|
||||
const resolvers = useMemo(
|
||||
() =>
|
||||
yup
|
||||
.object()
|
||||
.shape(
|
||||
steps.reduce(
|
||||
(values, { id, resolver }) => ({ ...values, [id]: resolver }),
|
||||
{}
|
||||
)
|
||||
)
|
||||
.required(),
|
||||
[steps]
|
||||
);
|
||||
|
||||
return { steps, defaultValues, resolvers };
|
||||
}
|
||||
|
||||
export default Steps;
|
@ -17,6 +17,7 @@ export default function useOpennebula() {
|
||||
vNetworksTemplates,
|
||||
templates,
|
||||
clusters,
|
||||
apps,
|
||||
filterPool: filter
|
||||
} = useSelector(
|
||||
state => ({
|
||||
@ -66,6 +67,14 @@ export default function useOpennebula() {
|
||||
.catch(err => dispatch(failureOneRequest({ error: err })));
|
||||
}, [dispatch, filter]);
|
||||
|
||||
const getMarketApps = useCallback(() => {
|
||||
dispatch(startOneRequest());
|
||||
return servicePool
|
||||
.getMarketApps({ filter })
|
||||
.then(data => dispatch(actions.setApps(data)))
|
||||
.catch(err => dispatch(failureOneRequest({ error: err })));
|
||||
}, [dispatch, filter]);
|
||||
|
||||
const getClusters = useCallback(() => {
|
||||
dispatch(startOneRequest());
|
||||
return servicePool
|
||||
@ -85,6 +94,8 @@ export default function useOpennebula() {
|
||||
getVNetworksTemplates,
|
||||
templates,
|
||||
getTemplates,
|
||||
apps,
|
||||
getMarketApps,
|
||||
clusters,
|
||||
getClusters
|
||||
};
|
||||
|
@ -25,7 +25,7 @@ const initial = {
|
||||
vmGroups: [],
|
||||
images: [],
|
||||
files: [],
|
||||
marketPlaces: [],
|
||||
marketplaces: [],
|
||||
apps: [],
|
||||
vNetworks: [],
|
||||
vNetworksTemplates: [],
|
||||
|
@ -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 MarketApp from 'server/utils/constants/commands/marketapp';
|
||||
import Cluster from 'server/utils/constants/commands/cluster';
|
||||
|
||||
import httpCodes from 'server/utils/constants/http-codes';
|
||||
@ -78,6 +79,20 @@ export const getTemplates = ({ filter }) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const getMarketApps = ({ filter }) => {
|
||||
const name = MarketApp.Actions.MARKETAPP_POOL_INFO;
|
||||
const { url, options } = requestParams(
|
||||
{ filter },
|
||||
{ name, ...MarketApp.Commands[name] }
|
||||
);
|
||||
|
||||
return requestData(url, options).then(res => {
|
||||
if (!res?.id || res?.id !== httpCodes.ok.id) throw res;
|
||||
|
||||
return [res?.data?.MARKETPLACEAPP_POOL?.MARKETPLACEAPP ?? []].flat();
|
||||
});
|
||||
};
|
||||
|
||||
export const getClusters = ({ filter }) => {
|
||||
const name = Cluster.Actions.CLUSTER_POOL_INFO;
|
||||
const { url, options } = requestParams(
|
||||
@ -98,5 +113,6 @@ export default {
|
||||
getVNetworks,
|
||||
getVNetworksTemplates,
|
||||
getTemplates,
|
||||
getMarketApps,
|
||||
getClusters
|
||||
};
|
||||
|
@ -1 +1,10 @@
|
||||
export const fakeDelay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
export const getValidationFromFields = schema =>
|
||||
schema.reduce(
|
||||
(validation, field) => ({
|
||||
...validation,
|
||||
[field?.name]: field?.validation
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user