1
0
mirror of https://github.com/OpenNebula/one.git synced 2024-12-22 13:33:52 +03:00

M #~: Google and digitalocean support in Fireedge (#1177)

This commit is contained in:
Sergio Betanzos 2021-05-04 13:40:00 +02:00 committed by GitHub
parent 6deedca690
commit 0c2956e138
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 400 additions and 122 deletions

View File

@ -19,9 +19,7 @@ networks:
- name: "${provision}-public"
vn_mad: 'bridge'
phydev: "${input.public_phydev}"
bridge: '${input.public_network_bridge}'
netrole: 'public'
dns: "${input.dns}"
ar:
- type: IP4
ip: "${input.first_public_ip}"

View File

@ -5,7 +5,9 @@ provider: 'google'
plain:
image: 'GOOGLE'
location_key: 'zone'
location_key:
- 'region'
- 'zone'
provision_type: 'virtual'
connection:

View File

@ -5,7 +5,9 @@ provider: 'google'
plain:
image: 'GOOGLE'
location_key: 'zone'
location_key:
- 'region'
- 'zone'
provision_type: 'virtual'
connection:

View File

@ -5,7 +5,9 @@ provider: 'google'
plain:
image: 'GOOGLE'
location_key: 'zone'
location_key:
- 'region'
- 'zone'
provision_type: 'virtual'
connection:

View File

@ -5,7 +5,9 @@ provider: 'google'
plain:
image: 'GOOGLE'
location_key: 'zone'
location_key:
- 'region'
- 'zone'
provision_type: 'virtual'
connection:

View File

@ -1,5 +1,15 @@
{
"presets": [ "@babel/preset-env", "@babel/preset-react" ],
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "10"
}
}
],
"@babel/preset-react"
],
"plugins": [
["module-resolver", {
"root": ["./src"],

View File

@ -68,7 +68,6 @@
"babel-loader": "8.1.0",
"babel-plugin-module-resolver": "4.0.0",
"babel-preset-react-hmre": "1.1.1",
"body-parser": "1.19.0",
"btoa": "1.2.1",
"clsx": "1.1.1",
"colors": "1.4.0",
@ -105,7 +104,7 @@
"react-flow-renderer": "5.11.1",
"react-hook-form": "6.8.6",
"react-json-pretty": "2.2.0",
"react-minimal-pie-chart": "8.1.0",
"react-minimal-pie-chart": "8.2.0",
"react-redux": "7.2.1",
"react-router": "5.2.0",
"react-router-dom": "5.2.0",
@ -127,7 +126,7 @@
"xml2js": "0.4.23",
"xmlrpc": "1.3.2",
"yaml": "1.10.0",
"yup": "0.29.3",
"yup": "0.32.9",
"zeromq": "5.2.0"
}
}

View File

@ -0,0 +1,166 @@
import React, { memo, useState, useRef, useEffect } from 'react'
import PropTypes from 'prop-types'
import clsx from 'clsx'
import { makeStyles, FormControl, FormHelperText } from '@material-ui/core'
import { Check, InsertDriveFile } from '@material-ui/icons'
import { Controller } from 'react-hook-form'
import { ErrorHelper, SubmitButton } from 'client/components/FormControl'
const useStyles = makeStyles(theme => ({
hide: {
display: 'none'
},
label: {
display: 'flex',
alignItems: 'center',
gap: '1em',
padding: '0.5em',
borderBottom: `1px solid ${theme.palette.text.secondary}`
},
button: {
'&:hover': {
backgroundColor: theme.palette.secondary.dark
}
},
buttonSuccess: {
backgroundColor: theme.palette.success.main,
'&:hover': {
backgroundColor: theme.palette.success.dark
}
}
}))
const FileController = memo(
({ control, cy, name, label, error, fieldProps, validationBeforeTransform, transform, formContext }) => {
const { setValue, setError, clearErrors, watch, register } = formContext
const classes = useStyles()
const [isLoading, setLoading] = useState(() => false)
const [success, setSuccess] = useState(() => !error && !!watch(name))
const timer = useRef()
useEffect(() => {
return () => {
clearTimeout(timer.current)
}
}, [])
const handleDelayState = message => {
// simulate is loading for one second
timer.current = window.setTimeout(() => {
setSuccess(!message)
setLoading(false)
message && setError(name, { type: 'manual', message })
}, 1000)
}
const handleChange = async event => {
try {
const file = event.target.files?.[0]
if (!file) return
setSuccess(false)
setLoading(true)
clearErrors(name)
const errorMessage = validationBeforeTransform
?.map(({ message, test }) => test(file) && message)
?.filter(Boolean)
if (errorMessage?.length) throw errorMessage[0]
const parsedValue = transform ? await transform(file) : file
setValue(name, parsedValue)
handleDelayState()
} catch (message) {
setValue(name, undefined)
handleDelayState(message)
}
}
return (
<FormControl fullWidth>
<Controller
render={() => (
<input
{...register(name)}
className={classes.hide}
id={cy}
type='file'
onChange={handleChange}
{...fieldProps}
/>
)}
name={name}
control={control}
/>
<label htmlFor={cy} className={classes.label}>
<SubmitButton
color='secondary'
component='span'
data-cy={`${cy}-button`}
isSubmitting={isLoading}
label={success ? <Check /> : <InsertDriveFile />}
className={clsx({
[classes.buttonSuccess]: success
})}
/>
{label}
</label>
{Boolean(error) && (
<FormHelperText data-cy={`${cy}-error`}>
<ErrorHelper label={error?.message} />
</FormHelperText>
)}
</FormControl>
)
},
(prevProps, nextProps) =>
prevProps.error === nextProps.error && prevProps.type === nextProps.type
)
FileController.propTypes = {
control: PropTypes.object,
cy: PropTypes.string,
multiline: PropTypes.bool,
name: PropTypes.string.isRequired,
label: PropTypes.string,
error: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.objectOf(PropTypes.any)
]),
validationBeforeTransform: PropTypes.arrayOf(
PropTypes.shape({
message: PropTypes.string,
test: PropTypes.func
})
),
transform: PropTypes.func,
fieldProps: PropTypes.object,
formContext: PropTypes.shape({
setValue: PropTypes.func,
setError: PropTypes.func,
clearErrors: PropTypes.func,
watch: PropTypes.func,
register: PropTypes.func
})
}
FileController.defaultProps = {
control: {},
cy: 'cy',
name: '',
label: '',
error: false,
validationBeforeTransform: undefined,
transform: undefined,
fieldProps: undefined
}
FileController.displayName = 'FileController'
export default FileController

View File

@ -4,6 +4,7 @@ import SelectController from 'client/components/FormControl/SelectController'
import SliderController from 'client/components/FormControl/SliderController'
import CheckboxController from 'client/components/FormControl/CheckboxController'
import AutocompleteController from 'client/components/FormControl/AutocompleteController'
import FileController from 'client/components/FormControl/FileController'
import SubmitButton from 'client/components/FormControl/SubmitButton'
import InputCode from 'client/components/FormControl/InputCode'
@ -16,6 +17,7 @@ export {
SliderController,
CheckboxController,
AutocompleteController,
FileController,
SubmitButton,
InputCode,

View File

@ -21,7 +21,7 @@ const useStyles = makeStyles(theme => ({
position: 'sticky',
top: -15,
minHeight: 100,
background: fade(theme.palette.background.paper, 0.65),
background: fade(theme.palette.background.paper, 0.95),
zIndex: theme.zIndex.mobileStepper
},
icon: {
@ -79,7 +79,7 @@ const CustomStepper = ({
error: classes.error
}
}}
{...(Boolean(errors[id]) && { error: true })}
{...(Boolean(errors[id]?.message) && { error: true })}
>{Tr(label)}</StepLabel>
</StepButton>
</Step>

View File

@ -37,19 +37,19 @@ const FormStepper = ({ steps, schema, onSubmit }) => {
.then(() => ({ id, data: stepData }))
}
const setErrors = ({ inner: errors = [], ...rest }) => {
const setErrors = ({ inner = [], ...rest }) => {
changeLoading(false)
const errorsByPath = groupBy(errors, 'path') ?? {}
const errorsByPath = groupBy(inner, 'path') ?? {}
const totalErrors = Object.keys(errorsByPath).length
totalErrors > 0
? setError(id, {
type: 'manual',
message: `${totalErrors} error(s) occurred`
})
type: 'manual',
message: `${totalErrors} error(s) occurred`
})
: setError(id, rest)
errors?.forEach(({ path, type, message }) =>
inner?.forEach(({ path, type, message }) =>
setError(`${id}.${path}`, { type, message })
)
}

View File

@ -14,13 +14,14 @@ const InputController = {
[INPUT_TYPES.SELECT]: FC.SelectController,
[INPUT_TYPES.SLIDER]: FC.SliderController,
[INPUT_TYPES.CHECKBOX]: FC.CheckboxController,
[INPUT_TYPES.AUTOCOMPLETE]: FC.AutocompleteController
[INPUT_TYPES.AUTOCOMPLETE]: FC.AutocompleteController,
[INPUT_TYPES.FILE]: FC.FileController
}
const HiddenInput = ({ isHidden, children }) =>
isHidden ? <Box display="none">{children}</Box> : children
const FormWithSchema = ({ id, cy, fields }) => {
const { control, errors } = useFormContext()
const { control, errors, ...formContext } = useFormContext()
return (
<Grid container spacing={1}>
@ -48,12 +49,13 @@ const FormWithSchema = ({ id, cy, fields }) => {
{React.createElement(InputController[type], {
control,
cy: dataCy,
type: htmlTypeValue,
error: inputError,
formContext,
name: inputName,
type: htmlTypeValue,
values: typeof values === 'function'
? values(dependValue)
: values,
error: inputError,
...restOfProps
})}
</Grid>

View File

@ -21,7 +21,7 @@ const TotalProviders = () => {
const chartData = React.useMemo(() => {
const groups = groupBy(providers, 'TEMPLATE.PLAIN.provider')
return PROVIDERS_TYPES?.map(({ id, name, color }) => ({
return Object.values(PROVIDERS_TYPES).map(({ id, name, color }) => ({
color,
title: name,
value: groups[id]?.length ?? 0

View File

@ -61,7 +61,8 @@ export const INPUT_TYPES = {
SELECT: 'select',
CHECKBOX: 'checkbox',
SLIDER: 'slider',
AUTOCOMPLETE: 'autocomplete'
AUTOCOMPLETE: 'autocomplete',
FILE: 'file'
}
export const DEBUG_LEVEL = {

View File

@ -34,30 +34,35 @@ export const PROVISIONS_STATES = [
}
]
export const PROVIDERS_TYPES = [
{
export const PROVIDERS_TYPES = {
aws: {
id: 'aws',
name: 'AWS',
color: '#ef931f'
},
{
packet: {
id: 'packet',
name: 'Packet',
color: '#364562'
},
{
dummy: {
id: 'dummy',
name: 'Dummy',
color: '#436637'
},
{
google: {
id: 'google',
name: 'Google Cloud',
color: 'linear-gradient(90deg, #fbbc05 0%, #ea4335 33%, #34a853 66%, #4285f4 100%)'
color: '#dc382b'
},
{
digitalocean: {
id: 'digitalocean',
name: 'Digital Ocean',
color: '#2381f5'
}
]
}
export const CREDENTIALS_FILE = {
// Google Cloud provider needs an input file to credential connection
[PROVIDERS_TYPES.google.id]: 'credentials'
}

View File

@ -1,36 +1,58 @@
import React, { useCallback, useEffect, useState } from 'react'
import { useFormContext } from 'react-hook-form'
import FormWithSchema from 'client/components/Forms/FormWithSchema'
import { EmptyCard } from 'client/components/Cards'
import { capitalize } from 'client/utils'
import { T } from 'client/constants'
import * as ProviderTemplateModel from 'client/models/ProviderTemplate'
import {
FORM_FIELDS, STEP_FORM_SCHEMA
} from 'client/containers/Providers/Form/Create/Steps/Connection/schema'
import {
STEP_ID as TEMPLATE_ID
} from 'client/containers/Providers/Form/Create/Steps/Template'
export const STEP_ID = 'connection'
let connection = {}
let providerType
const Connection = () => ({
const Connection = ({ isUpdate }) => ({
id: STEP_ID,
label: T.ConfigureConnection,
resolver: () => STEP_FORM_SCHEMA(connection),
resolver: () => STEP_FORM_SCHEMA({ connection, providerType }),
optionsValidate: { abortEarly: false },
content: useCallback(({ data }) => {
const [fields, setFields] = useState([])
const { watch } = useFormContext()
useEffect(() => {
connection = data
setFields(FORM_FIELDS(connection))
const { [TEMPLATE_ID]: templateSelected, [STEP_ID]: currentConnection } = watch()
const { provider, ...template } = templateSelected?.[0]
providerType = provider
connection = isUpdate
// when is updating, connections have the name as input label
? Object.keys(currentConnection)
.reduce((res, name) => ({ ...res, [name]: capitalize(name) }), {})
// set connections from template, to take value as input labels
: ProviderTemplateModel.getConnectionEditable(template)
setFields(FORM_FIELDS({ connection, providerType }))
}, [data])
return (fields?.length === 0) ? (
<EmptyCard title={'✔️ There is not connections to fill'} />
<EmptyCard title={"✔️ There aren't connections to fill"} />
) : (
<FormWithSchema cy="form-provider" fields={fields} id={STEP_ID} />
)
}, [])
})
export * from 'client/containers/Providers/Form/Create/Steps/Connection/schema'
export default Connection

View File

@ -1,19 +1,46 @@
import * as yup from 'yup'
import { INPUT_TYPES } from 'client/constants'
import { capitalize, getValidationFromFields } from 'client/utils'
import { INPUT_TYPES, CREDENTIALS_FILE } from 'client/constants'
import { getValidationFromFields, isBase64, prettyBytes } from 'client/utils'
export const FORM_FIELDS = connection =>
Object.entries(connection)?.map(([name, value]) => ({
name,
label: capitalize(name),
type: INPUT_TYPES.PASSWORD,
validation: yup
const MAX_SIZE_JSON = 102_400
const JSON_FORMAT = 'application/json'
export const FORM_FIELDS = ({ connection, providerType }) =>
Object.entries(connection)?.map(([name, label]) => {
const isInputFile = CREDENTIALS_FILE[providerType] === String(name).toLowerCase()
let validation = yup
.string()
.trim()
.required(`${name} field is required`)
.default(value)
}))
.default(undefined)
export const STEP_FORM_SCHEMA = connection => yup.object(
getValidationFromFields(FORM_FIELDS(connection))
if (isInputFile) {
validation = validation.test('is-base64', 'File has invalid format', isBase64)
}
return {
name,
label,
type: isInputFile ? INPUT_TYPES.FILE : INPUT_TYPES.PASSWORD,
validation,
...(isInputFile && {
fieldProps: { accept: JSON_FORMAT },
validationBeforeTransform: [{
message: `Only the following formats are accepted: ${JSON_FORMAT}`,
test: value => value?.type !== JSON_FORMAT
}, {
message: `The file is too large. Max ${prettyBytes(MAX_SIZE_JSON, '')}`,
test: value => value?.size > MAX_SIZE_JSON
}],
transform: async file => {
const json = await new Response(file).json()
return btoa(JSON.stringify(json))
}
})
}
})
export const STEP_FORM_SCHEMA = props => yup.object(
getValidationFromFields(FORM_FIELDS(props))
)

View File

@ -1,18 +1,19 @@
import React, { useCallback, useEffect } from 'react'
import { Divider, Select, Breadcrumbs } from '@material-ui/core'
import { Divider, Select, Breadcrumbs, InputLabel, FormControl } from '@material-ui/core'
import ArrowIcon from '@material-ui/icons/ArrowForwardIosRounded'
import Marked from 'marked'
import { useProvision, useListForm } from 'client/hooks'
import { ListCards } from 'client/components/List'
import { EmptyCard, ProvisionTemplateCard } from 'client/components/Cards'
import { ProvisionTemplateCard } from 'client/components/Cards'
import { sanitize } from 'client/utils'
import * as ProviderTemplateModel from 'client/models/ProviderTemplate'
import { T } from 'client/constants'
import { STEP_FORM_SCHEMA } from 'client/containers/Providers/Form/Create/Steps/Template/schema'
import { STEP_ID as CONFIGURATION_ID } from 'client/containers/Providers/Form/Create/Steps/BasicConfiguration'
import { STEP_ID as CONNECTION_ID } from 'client/containers/Providers/Form/Create/Steps/Connection'
import { STEP_FORM_SCHEMA } from 'client/containers/Providers/Form/Create/Steps/Template/schema'
export const STEP_ID = 'template'
@ -59,14 +60,12 @@ const Template = () => ({
}
const handleClick = (template, isSelected) => {
const { name, description, plain = {}, connection } = template
const { location_key: locationKey = '' } = plain
const { [locationKey]: _, ...connectionEditable } = connection ?? {}
const { name, description } = template
// reset rest of form when change template
setFormData({
[CONFIGURATION_ID]: { name, description },
[CONNECTION_ID]: connectionEditable
[CONNECTION_ID]: {}
})
isSelected
@ -94,28 +93,40 @@ const Template = () => ({
<>
{/* -- SELECTORS -- */}
<Breadcrumbs separator={<ArrowIcon color="secondary" />}>
<Select
color='secondary'
inputProps = {{ 'data-cy': 'select-provision-type' }}
native
style={{ minWidth: '8em' }}
onChange={handleChangeProvision}
value={provisionSelected}
variant='outlined'
>
<RenderOptions options={provisionsTemplates} />
</Select>
{provisionSelected && <Select
color='secondary'
inputProps = {{ 'data-cy': 'select-provider-type' }}
native
style={{ minWidth: '8em' }}
onChange={handleChangeProvider}
value={providerSelected}
variant='outlined'
>
<RenderOptions options={providersTypes} />
</Select>}
<FormControl>
<InputLabel color='secondary' shrink id='select-provision-type-label'>
{'Provision type'}
</InputLabel>
<Select
color='secondary'
inputProps={{ 'data-cy': 'select-provision-type' }}
labelId='select-provision-type-label'
native
style={{ marginTop: '1em', minWidth: '8em' }}
onChange={handleChangeProvision}
value={provisionSelected}
variant='outlined'
>
<RenderOptions options={provisionsTemplates} />
</Select>
</FormControl>
<FormControl>
<InputLabel color='secondary' shrink id='select-provider-type-label'>
{'Provider type'}
</InputLabel>
<Select
color='secondary'
inputProps={{ 'data-cy': 'select-provider-type' }}
labelId='select-provider-type-label'
native
style={{ marginTop: '1em', minWidth: '8em' }}
onChange={handleChangeProvider}
value={providerSelected}
variant='outlined'
>
<RenderOptions options={providersTypes} />
</Select>
</FormControl>
</Breadcrumbs>
{/* -- DESCRIPTION -- */}
@ -129,22 +140,10 @@ const Template = () => ({
<ListCards
keyProp='name'
list={templatesAvailable}
EmptyComponent={
<EmptyCard title={
!provisionSelected
? 'Please choose your provision type'
: !providerSelected
? 'Please choose your provider type'
: 'Your providers templates list is empty'
} />
}
gridProps={{ 'data-cy': 'providers-templates' }}
CardComponent={ProvisionTemplateCard}
cardsProps={({ value = {} }) => {
const isSelected = data?.some(selected =>
selected.name === value.name
)
const isSelected = data?.some(selected => selected.name === value.name)
const isValid = ProviderTemplateModel.isValidProviderTemplate(value)
return {

View File

@ -7,7 +7,7 @@ import Connection from './Connection'
const Steps = ({ isUpdate }) => {
const template = Template()
const configuration = BasicConfiguration({ isUpdate })
const connection = Connection()
const connection = Connection({ isUpdate })
const steps = [configuration, connection]
!isUpdate && steps.unshift(template)

View File

@ -39,31 +39,26 @@ function ProviderCreateForm () {
history.push(PATH.PROVIDERS.LIST)
}
const callCreateProvider = formData => {
const callCreateProvider = async formData => {
const { template, configuration, connection } = formData
const templateSelected = template?.[0]
const { name, description } = configuration
const isValid = ProviderTemplateModel.isValidProviderTemplate(templateSelected)
!isValid && redirectWithError(`
The template selected has a bad format.
Ask your cloud administrator`
The template selected has a bad format.
Ask your cloud administrator`
)
const { inputs, plain, provider } = templateSelected
const { location_key: locationKey } = plain
const connectionFixed = templateSelected.connection?.[locationKey]
const { name, description } = configuration
const connectionFixed = ProviderTemplateModel.getConnectionFixed(templateSelected)
const formatData = {
connection: { ...connection, [locationKey]: connectionFixed },
...templateSelected,
connection: { ...connection, ...connectionFixed },
description,
inputs,
name,
plain,
provider
name
}
createProvider({ data: formatData })
@ -75,17 +70,12 @@ function ProviderCreateForm () {
const { description } = configuration
const [provider = {}, connection = []] = data
const {
PLAIN: { location_key: locationKey } = {},
PROVISION_BODY: currentBodyTemplate
} = provider?.TEMPLATE
const { [locationKey]: connectionFixed } = connection
const { PROVISION_BODY: currentBodyTemplate } = provider?.TEMPLATE
const formatData = {
...currentBodyTemplate,
description,
connection: { ...connectionEditable, [locationKey]: connectionFixed }
connection: { ...connection, ...connectionEditable }
}
updateProvider({ id, data: formatData })
@ -109,15 +99,17 @@ function ProviderCreateForm () {
const [provider = {}, connection = []] = data
const {
PLAIN: { location_key: locationKey } = {},
PROVISION_BODY: { description, name }
PLAIN = {},
PROVISION_BODY: { description, ...currentBodyTemplate }
} = provider?.TEMPLATE
const { [locationKey]: _, ...connectionEditable } = connection
const connectionEditable = ProviderTemplateModel
.getConnectionEditable({ plain: PLAIN, connection })
methods.reset({
template: [currentBodyTemplate],
connection: connectionEditable,
configuration: { name, description }
configuration: { description }
}, { errors: false })
}
}, [data])

View File

@ -1,11 +1,36 @@
export const isValidProviderTemplate = ({ name, provider, plain = {}, connection }) => {
const { provision_type: provisionType, location_key: locationKey } = plain
const keys = typeof locationKey === 'string' ? locationKey.split(',') : locationKey
const hasConnection = connection !== undefined
const locationKeyConnectionNotExists = !hasConnection || connection?.[locationKey] === undefined
const locationKeyConnectionNotExists =
!hasConnection || keys.some(key => connection?.[key] === undefined)
return (
!(locationKey && locationKeyConnectionNotExists) ||
[name, provisionType, provider].includes(undefined)
)
}
export const getLocationKeys = ({ location_key: locationKey }) =>
typeof locationKey === 'string' ? locationKey.split(',') : locationKey
export const getConnectionFixed = ({ connection = {}, ...template }) => {
const keys = getLocationKeys(template?.plain)
return Object.entries(connection).reduce((res, [name, value]) => ({
...res,
...keys.includes(name) && { [name]: value }
}), {})
}
export const getConnectionEditable = ({ connection = {}, ...template }) => {
const keys = getLocationKeys(template?.plain)
return Object.entries(connection).reduce((res, [name, value]) => ({
...res,
...!keys.includes(name) && { [name]: value }
}), {})
}

View File

@ -55,7 +55,10 @@ export const ProvisionHost = PropTypes.shape({
export const ProviderPlainInfo = PropTypes.shape({
image: PropTypes.string,
location_key: PropTypes.string,
location_key: PropTypes.oneOfType([
PropTypes.string,
PropTypes.arrayOf(PropTypes.string)
]),
provision_type: ProvisionType.isRequired
})

View File

@ -111,3 +111,23 @@ export const groupBy = (array, key) =>
}, {})
export const cloneObject = obj => JSON.parse(JSON.stringify(obj))
/**
* Check if value is in base64
*
* @param {String} stringToValidate String to check
* @param {Boolean} options.exact Only match and exact string
* @returns {Boolean}
*/
export const isBase64 = (stringToValidate, options = {}) => {
if (stringToValidate === '') return false
const { exact = true } = options
const BASE64_REG = /(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)/g
const EXACT_BASE64_REG = /(?:^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$)/
const regex = exact ? EXACT_BASE64_REG : BASE64_REG
return regex.test(stringToValidate)
}

View File

@ -5,7 +5,6 @@ import helmet from 'helmet'
import morgan from 'morgan'
import cors from 'cors'
import compression from 'compression'
import bodyParser from 'body-parser'
import { env } from 'process'
import {
accessSync,
@ -124,8 +123,8 @@ if (appConfig.cors) {
app.use(cors())
}
// post params parser body
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
app.use(express.urlencoded({ extended: false }))
app.use(express.json())
app.use(`${basename}/api`, entrypointApi) // opennebula Api routes
const frontApps = Object.keys(defaultApps)

View File

@ -12,7 +12,7 @@
/* See the License for the specific language governing permissions and */
/* limitations under the License. */
/* -------------------------------------------------------------------------- */
const providers = ['aws', 'packet', 'dummy']
const providers = ['aws', 'packet', 'dummy', 'google', 'digitalocean']
const provider = {
id: '/Provider',