@ -4,10 +4,10 @@ description: 'Elastic cluster on AWS in Frankfurt'
|
||||
provider: 'aws'
|
||||
|
||||
plain:
|
||||
image: 'AWS'
|
||||
location_key: 'region'
|
||||
provision_type: 'hybrid+'
|
||||
image: 'AWS.webp'
|
||||
|
||||
location_key: 'region'
|
||||
connection:
|
||||
access_key: 'AWS access key'
|
||||
secret_key: 'AWS secret key'
|
||||
|
@ -4,10 +4,10 @@ description: 'Elastic cluster on AWS in London'
|
||||
provider: 'aws'
|
||||
|
||||
plain:
|
||||
provision_type: 'hybrid+_qemu'
|
||||
image: 'AWS.webp'
|
||||
image: 'AWS'
|
||||
location_key: 'region'
|
||||
provision_type: 'hybrid+'
|
||||
|
||||
location_key: 'region'
|
||||
connection:
|
||||
access_key: 'AWS access key'
|
||||
secret_key: 'AWS secret key'
|
||||
|
@ -4,10 +4,10 @@ description: 'Elastic cluster on AWS in North Virginia'
|
||||
provider: 'aws'
|
||||
|
||||
plain:
|
||||
image: 'AWS'
|
||||
location_key: 'region'
|
||||
provision_type: 'hybrid+'
|
||||
image: 'AWS.webp'
|
||||
|
||||
location_key: 'region'
|
||||
connection:
|
||||
access_key: 'AWS access key'
|
||||
secret_key: 'AWS secret key'
|
||||
|
@ -4,10 +4,10 @@ description: 'Elastic cluster on AWS in North California'
|
||||
provider: 'aws'
|
||||
|
||||
plain:
|
||||
image: 'AWS'
|
||||
location_key: 'region'
|
||||
provision_type: 'hybrid+'
|
||||
image: 'AWS.webp'
|
||||
|
||||
location_key: 'region'
|
||||
connection:
|
||||
access_key: 'AWS access key'
|
||||
secret_key: 'AWS secret key'
|
||||
|
@ -4,10 +4,10 @@ description: 'Elastic cluster on Packet in Amsterdam'
|
||||
provider: 'packet'
|
||||
|
||||
plain:
|
||||
image: 'EQUINIX'
|
||||
location_key: 'facility'
|
||||
provision_type: 'hybrid+'
|
||||
image: 'EQUINIX.webp'
|
||||
|
||||
location_key: 'facility'
|
||||
connection:
|
||||
token: 'Packet token'
|
||||
project: 'Packet project'
|
||||
|
@ -4,10 +4,10 @@ description: 'Elastic cluster on Packet in Parsippany, USA, NJ'
|
||||
provider: 'packet'
|
||||
|
||||
plain:
|
||||
image: 'EQUINIX'
|
||||
location_key: 'facility'
|
||||
provision_type: 'hybrid+'
|
||||
image: 'EQUINIX.webp'
|
||||
|
||||
location_key: 'facility'
|
||||
connection:
|
||||
token: 'Packet token'
|
||||
project: 'Packet project'
|
||||
|
@ -4,10 +4,10 @@ description: 'Elastic cluster on Packet in Tokyo, Japan'
|
||||
provider: 'packet'
|
||||
|
||||
plain:
|
||||
image: 'EQUINIX'
|
||||
location_key: 'facility'
|
||||
provision_type: 'hybrid+'
|
||||
image: 'EQUINIX.webp'
|
||||
|
||||
location_key: 'facility'
|
||||
connection:
|
||||
token: 'Packet token'
|
||||
project: 'Packet project'
|
||||
|
@ -4,10 +4,10 @@ description: 'Elastic cluster on Packet in Sunnyvale, USA, CA'
|
||||
provider: 'packet'
|
||||
|
||||
plain:
|
||||
image: 'EQUINIX'
|
||||
location_key: 'facility'
|
||||
provision_type: 'hybrid+'
|
||||
image: 'EQUINIX.webp'
|
||||
|
||||
location_key: 'facility'
|
||||
connection:
|
||||
token: 'Packet token'
|
||||
project: 'Packet project'
|
||||
|
@ -0,0 +1,20 @@
|
||||
---
|
||||
# ---------------------------------------------------------------------------- #
|
||||
# Copyright 2002-2021, OpenNebula Project, OpenNebula Systems #
|
||||
# #
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
|
||||
# not use this file except in compliance with the License. You may obtain #
|
||||
# a copy of the License at #
|
||||
# #
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 #
|
||||
# #
|
||||
# Unless required by applicable law or agreed to in writing, software #
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, #
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
|
||||
# See the License for the specific language governing permissions and #
|
||||
# limitations under the License. #
|
||||
# ---------------------------------------------------------------------------- #
|
||||
|
||||
image: 'OPENNEBULA-AWS'
|
||||
provider: 'aws'
|
||||
provision_type: 'hybrid+'
|
@ -21,10 +21,6 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
name: 'aws-cluster'
|
||||
provision_type: 'hybrid+'
|
||||
provider: 'aws'
|
||||
|
||||
image: 'OPENNEBULA-AWS.png'
|
||||
|
||||
extends:
|
||||
- common.d/defaults.yml
|
||||
@ -32,6 +28,7 @@ extends:
|
||||
- common.d/kvm_hosts.yml
|
||||
- aws.d/datastores.yml
|
||||
- aws.d/defaults.yml
|
||||
- aws.d/fireedge.yml
|
||||
- aws.d/inputs.yml
|
||||
- aws.d/networks.yml
|
||||
|
||||
|
@ -0,0 +1,20 @@
|
||||
---
|
||||
# ---------------------------------------------------------------------------- #
|
||||
# Copyright 2002-2021, OpenNebula Project, OpenNebula Systems #
|
||||
# #
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
|
||||
# not use this file except in compliance with the License. You may obtain #
|
||||
# a copy of the License at #
|
||||
# #
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 #
|
||||
# #
|
||||
# Unless required by applicable law or agreed to in writing, software #
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, #
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
|
||||
# See the License for the specific language governing permissions and #
|
||||
# limitations under the License. #
|
||||
# ---------------------------------------------------------------------------- #
|
||||
|
||||
image: 'OPENNEBULA-EQUINIX'
|
||||
provider: 'packet'
|
||||
provision_type: 'hybrid+'
|
@ -21,17 +21,14 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
name: 'packet-cluster'
|
||||
provision_type: 'hybrid+'
|
||||
provider: 'packet'
|
||||
|
||||
image: 'OPENNEBULA-EQUINIX.png'
|
||||
|
||||
extends:
|
||||
- common.d/defaults.yml
|
||||
- common.d/resources.yml
|
||||
- common.d/kvm_hosts.yml
|
||||
- packet.d/defaults.yml
|
||||
- packet.d/datastores.yml
|
||||
- packet.d/defaults.yml
|
||||
- packet.d/fireedge.yml
|
||||
- packet.d/inputs.yml
|
||||
- packet.d/networks.yml
|
||||
|
||||
|
@ -4,10 +4,10 @@ description: 'Elastic cluster on AWS in Frankfurt'
|
||||
provider: 'aws'
|
||||
|
||||
plain:
|
||||
image: 'AWS'
|
||||
location_key: 'region'
|
||||
provision_type: 'hybrid+_firecracker'
|
||||
image: 'AWS.webp'
|
||||
|
||||
location_key: 'region'
|
||||
connection:
|
||||
access_key: 'AWS access key'
|
||||
secret_key: 'AWS secret key'
|
||||
|
@ -4,10 +4,10 @@ description: 'Elastic cluster on AWS in London'
|
||||
provider: 'aws'
|
||||
|
||||
plain:
|
||||
image: 'AWS'
|
||||
location_key: 'region'
|
||||
provision_type: 'hybrid+_firecracker'
|
||||
image: 'AWS.webp'
|
||||
|
||||
location_key: 'region'
|
||||
connection:
|
||||
access_key: 'AWS access key'
|
||||
secret_key: 'AWS secret key'
|
||||
|
@ -4,10 +4,10 @@ description: 'Elastic cluster on AWS in North Virginia'
|
||||
provider: 'aws'
|
||||
|
||||
plain:
|
||||
image: 'AWS'
|
||||
location_key: 'region'
|
||||
provision_type: 'hybrid+_firecracker'
|
||||
image: 'AWS.webp'
|
||||
|
||||
location_key: 'region'
|
||||
connection:
|
||||
access_key: 'AWS access key'
|
||||
secret_key: 'AWS secret key'
|
||||
|
@ -4,10 +4,10 @@ description: 'Elastic cluster on AWS in North California'
|
||||
provider: 'aws'
|
||||
|
||||
plain:
|
||||
image: 'AWS'
|
||||
location_key: 'region'
|
||||
provision_type: 'hybrid+_firecracker'
|
||||
image: 'AWS.webp'
|
||||
|
||||
location_key: 'region'
|
||||
connection:
|
||||
access_key: 'AWS access key'
|
||||
secret_key: 'AWS secret key'
|
||||
|
@ -0,0 +1,20 @@
|
||||
---
|
||||
# ---------------------------------------------------------------------------- #
|
||||
# Copyright 2002-2021, OpenNebula Project, OpenNebula Systems #
|
||||
# #
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
|
||||
# not use this file except in compliance with the License. You may obtain #
|
||||
# a copy of the License at #
|
||||
# #
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 #
|
||||
# #
|
||||
# Unless required by applicable law or agreed to in writing, software #
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, #
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
|
||||
# See the License for the specific language governing permissions and #
|
||||
# limitations under the License. #
|
||||
# ---------------------------------------------------------------------------- #
|
||||
|
||||
image: 'OPENNEBULA-AWS'
|
||||
provider: 'aws'
|
||||
provision_type: 'hybrid+_firecracker'
|
@ -21,10 +21,6 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
name: 'aws-cluster'
|
||||
provision_type: 'hybrid+_firecracker'
|
||||
provider: 'aws'
|
||||
|
||||
image: 'OPENNEBULA-AWS.png'
|
||||
|
||||
extends:
|
||||
- common.d/defaults.yml
|
||||
@ -32,6 +28,7 @@ extends:
|
||||
- common.d/firecracker_hosts.yml
|
||||
- aws.d/datastores.yml
|
||||
- aws.d/defaults.yml
|
||||
- aws.d/fireedge.yml
|
||||
- aws.d/inputs.yml
|
||||
- aws.d/networks.yml
|
||||
|
||||
|
@ -4,10 +4,10 @@ description: 'Elastic cluster on AWS in Frankfurt'
|
||||
provider: 'aws'
|
||||
|
||||
plain:
|
||||
image: 'AWS'
|
||||
location_key: 'region'
|
||||
provision_type: 'hybrid+_qemu'
|
||||
image: 'AWS.webp'
|
||||
|
||||
location_key: 'region'
|
||||
connection:
|
||||
access_key: 'AWS access key'
|
||||
secret_key: 'AWS secret key'
|
||||
|
@ -4,10 +4,10 @@ description: 'Elastic cluster on AWS in London'
|
||||
provider: 'aws'
|
||||
|
||||
plain:
|
||||
image: 'AWS'
|
||||
location_key: 'region'
|
||||
provision_type: 'hybrid+_qemu'
|
||||
image: 'AWS.webp'
|
||||
|
||||
location_key: 'region'
|
||||
connection:
|
||||
access_key: 'AWS access key'
|
||||
secret_key: 'AWS secret key'
|
||||
|
@ -4,10 +4,10 @@ description: 'Elastic cluster on AWS in North Virginia'
|
||||
provider: 'aws'
|
||||
|
||||
plain:
|
||||
image: 'AWS'
|
||||
location_key: 'region'
|
||||
provision_type: 'hybrid+_qemu'
|
||||
image: 'AWS.webp'
|
||||
|
||||
location_key: 'region'
|
||||
connection:
|
||||
access_key: 'AWS access key'
|
||||
secret_key: 'AWS secret key'
|
||||
|
@ -4,10 +4,10 @@ description: 'Elastic cluster on AWS in North California'
|
||||
provider: 'aws'
|
||||
|
||||
plain:
|
||||
image: 'AWS'
|
||||
location_key: 'region'
|
||||
provision_type: 'hybrid+_qemu'
|
||||
image: 'AWS.webp'
|
||||
|
||||
location_key: 'region'
|
||||
connection:
|
||||
access_key: 'AWS access key'
|
||||
secret_key: 'AWS secret key'
|
||||
|
@ -0,0 +1,20 @@
|
||||
---
|
||||
# ---------------------------------------------------------------------------- #
|
||||
# Copyright 2002-2021, OpenNebula Project, OpenNebula Systems #
|
||||
# #
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
|
||||
# not use this file except in compliance with the License. You may obtain #
|
||||
# a copy of the License at #
|
||||
# #
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 #
|
||||
# #
|
||||
# Unless required by applicable law or agreed to in writing, software #
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, #
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
|
||||
# See the License for the specific language governing permissions and #
|
||||
# limitations under the License. #
|
||||
# ---------------------------------------------------------------------------- #
|
||||
|
||||
image: 'OPENNEBULA-AWS'
|
||||
provider: 'aws'
|
||||
provision_type: 'hybrid+_qemu'
|
@ -21,10 +21,6 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
name: 'aws-cluster'
|
||||
provision_type: 'hybrid+_qemu'
|
||||
provider: 'aws'
|
||||
|
||||
image: 'OPENNEBULA-AWS.png'
|
||||
|
||||
extends:
|
||||
- common.d/defaults.yml
|
||||
@ -32,6 +28,7 @@ extends:
|
||||
- common.d/qemu_hosts.yml
|
||||
- aws.d/datastores.yml
|
||||
- aws.d/defaults.yml
|
||||
- aws.d/fireedge.yml
|
||||
- aws.d/inputs.yml
|
||||
- aws.d/networks.yml
|
||||
|
||||
|
BIN
src/fireedge/src/client/assets/images/providers/ALIBABA.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
src/fireedge/src/client/assets/images/providers/AWS.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
src/fireedge/src/client/assets/images/providers/AZURE.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
src/fireedge/src/client/assets/images/providers/EQUINIX.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
src/fireedge/src/client/assets/images/providers/GOOGLECLOUD.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 4.9 KiB |
@ -8,11 +8,13 @@ import SelectCard from 'client/components/Cards/SelectCard'
|
||||
import Action from 'client/components/Cards/SelectCard/Action'
|
||||
import { StatusBadge } from 'client/components/Status'
|
||||
import { isExternalURL } from 'client/utils'
|
||||
import * as Types from 'client/types/provision'
|
||||
import {
|
||||
PROVISIONS_STATES,
|
||||
PROVIDER_IMAGES_URL,
|
||||
PROVISION_IMAGES_URL,
|
||||
DEFAULT_IMAGE
|
||||
DEFAULT_IMAGE,
|
||||
IMAGE_FORMATS
|
||||
} from 'client/constants'
|
||||
|
||||
const ProvisionCard = memo(
|
||||
@ -28,23 +30,42 @@ const ProvisionCard = memo(
|
||||
setBody({ ...json, image: json.image ?? DEFAULT_IMAGE })
|
||||
}, [])
|
||||
|
||||
const onError = evt => {
|
||||
evt.target.src = evt.target.src === DEFAULT_IMAGE ? DEFAULT_IMAGE : ''
|
||||
}
|
||||
const isExternalImage = isExternalURL(image)
|
||||
|
||||
const mediaProps = useMemo(() => {
|
||||
const src = isExternalImage ? image : `${IMAGES_URL}/${image}`
|
||||
const onError = evt => { evt.target.src = DEFAULT_IMAGE }
|
||||
|
||||
return {
|
||||
component: 'picture',
|
||||
children: (
|
||||
<>
|
||||
{(image && !isExternalImage) && IMAGE_FORMATS.map(format => (
|
||||
<source
|
||||
key={format}
|
||||
srcSet={`${src}.${format}`}
|
||||
type={`image/${format}`}
|
||||
/>
|
||||
))}
|
||||
<img
|
||||
decoding='async'
|
||||
draggable={false}
|
||||
loading='lazy'
|
||||
src={src}
|
||||
onError={onError}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}, [image, isExternalImage])
|
||||
|
||||
const imgSource = useMemo(() => (
|
||||
isExternalURL(image) ? image : `${IMAGES_URL}/${image}`
|
||||
), [image])
|
||||
const dataCy = isProvider ? 'provider' : 'provision'
|
||||
return (
|
||||
<SelectCard
|
||||
title={NAME}
|
||||
subheader={`#${ID}`}
|
||||
isSelected={isSelected}
|
||||
handleClick={handleClick}
|
||||
action={actions?.map(action =>
|
||||
<Action key={action?.cy} {...action} />
|
||||
)}
|
||||
dataCy={isProvider ? 'provider' : 'provision'}
|
||||
handleClick={handleClick}
|
||||
icon={
|
||||
isProvider ? (
|
||||
<ProviderIcon />
|
||||
@ -54,14 +75,11 @@ const ProvisionCard = memo(
|
||||
</StatusBadge>
|
||||
)
|
||||
}
|
||||
cardProps={{ 'data-cy': `${dataCy}-card` }}
|
||||
cardHeaderProps={{ titleTypographyProps: { 'data-cy': `${dataCy}-card-title` } }}
|
||||
mediaProps={{
|
||||
component: 'img',
|
||||
image: imgSource,
|
||||
draggable: false,
|
||||
onError
|
||||
}}
|
||||
isSelected={isSelected}
|
||||
mediaProps={mediaProps}
|
||||
subheader={`#${ID}`}
|
||||
title={NAME}
|
||||
disableFilterImage={isExternalImage}
|
||||
/>
|
||||
)
|
||||
}, (prev, next) => (
|
||||
@ -73,17 +91,10 @@ const ProvisionCard = memo(
|
||||
)
|
||||
|
||||
ProvisionCard.propTypes = {
|
||||
value: PropTypes.shape({
|
||||
ID: PropTypes.string.isRequired,
|
||||
NAME: PropTypes.string.isRequired,
|
||||
TEMPLATE: PropTypes.shape({
|
||||
PLAIN: PropTypes.object,
|
||||
BODY: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.object
|
||||
])
|
||||
})
|
||||
}),
|
||||
value: PropTypes.oneOfType([
|
||||
Types.Provider,
|
||||
Types.Provision
|
||||
]),
|
||||
isSelected: PropTypes.bool,
|
||||
handleClick: PropTypes.func,
|
||||
isProvider: PropTypes.bool,
|
||||
@ -97,11 +108,11 @@ ProvisionCard.propTypes = {
|
||||
}
|
||||
|
||||
ProvisionCard.defaultProps = {
|
||||
value: {},
|
||||
isSelected: undefined,
|
||||
actions: undefined,
|
||||
handleClick: undefined,
|
||||
isProvider: false,
|
||||
actions: undefined
|
||||
isSelected: undefined,
|
||||
value: {}
|
||||
}
|
||||
|
||||
ProvisionCard.displayName = 'ProvisionCard'
|
||||
|
@ -1,6 +1,8 @@
|
||||
import * as React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import * as Types from 'client/types/provision'
|
||||
|
||||
import ProvidersIcon from '@material-ui/icons/Public'
|
||||
import SelectCard from 'client/components/Cards/SelectCard'
|
||||
|
||||
@ -8,38 +10,56 @@ import { isExternalURL } from 'client/utils'
|
||||
import {
|
||||
PROVIDER_IMAGES_URL,
|
||||
PROVISION_IMAGES_URL,
|
||||
DEFAULT_IMAGE
|
||||
DEFAULT_IMAGE,
|
||||
IMAGE_FORMATS
|
||||
} from 'client/constants'
|
||||
|
||||
const ProvisionTemplateCard = React.memo(
|
||||
({ value, isProvider, isSelected, handleClick }) => {
|
||||
({ value, isProvider, isSelected, isValid, handleClick }) => {
|
||||
const { description, name, plain = {} } = value
|
||||
const { image } = isProvider ? plain : value
|
||||
const IMAGES_URL = isProvider ? PROVIDER_IMAGES_URL : PROVISION_IMAGES_URL
|
||||
const { image = '' } = isProvider ? plain : value
|
||||
|
||||
const onError = evt => {
|
||||
evt.target.src = evt.target.src === DEFAULT_IMAGE ? DEFAULT_IMAGE : ''
|
||||
}
|
||||
const isExternalImage = isExternalURL(image)
|
||||
|
||||
const mediaProps = React.useMemo(() => {
|
||||
const IMAGES_URL = isProvider ? PROVIDER_IMAGES_URL : PROVISION_IMAGES_URL
|
||||
const src = isExternalImage ? image : `${IMAGES_URL}/${image}`
|
||||
const onError = evt => { evt.target.src = DEFAULT_IMAGE }
|
||||
|
||||
return {
|
||||
component: 'picture',
|
||||
children: (
|
||||
<>
|
||||
{(image && !isExternalImage) && IMAGE_FORMATS.map(format => (
|
||||
<source
|
||||
key={format}
|
||||
srcSet={`${IMAGES_URL}/${image}.${format}`}
|
||||
type={`image/${format}`}
|
||||
/>
|
||||
))}
|
||||
<img
|
||||
decoding='async'
|
||||
draggable={false}
|
||||
loading='lazy'
|
||||
src={src}
|
||||
onError={onError}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}, [image, isProvider])
|
||||
|
||||
const imgSource = React.useMemo(() =>
|
||||
isExternalURL(image) ? image : `${IMAGES_URL}/${image}`
|
||||
, [image])
|
||||
const dataCy = isProvider ? 'provider' : 'provision'
|
||||
return (
|
||||
<SelectCard
|
||||
title={name}
|
||||
subheader={description}
|
||||
icon={<ProvidersIcon />}
|
||||
isSelected={isSelected}
|
||||
dataCy={isProvider ? 'provider' : 'provision'}
|
||||
disableFilterImage={isExternalImage}
|
||||
handleClick={handleClick}
|
||||
mediaProps={image && {
|
||||
component: 'img',
|
||||
image: imgSource,
|
||||
draggable: false,
|
||||
onError
|
||||
}}
|
||||
cardProps={{ 'data-cy': `${dataCy}-template-card` }}
|
||||
cardHeaderProps={{ titleTypographyProps: { 'data-cy': `${dataCy}-template-card-title` } }}
|
||||
icon={<ProvidersIcon />}
|
||||
cardActionAreaProps={{ disabled: !isValid }}
|
||||
isSelected={isSelected}
|
||||
mediaProps={mediaProps}
|
||||
subheader={description}
|
||||
title={name}
|
||||
/>
|
||||
)
|
||||
},
|
||||
@ -47,23 +67,22 @@ const ProvisionTemplateCard = React.memo(
|
||||
)
|
||||
|
||||
ProvisionTemplateCard.propTypes = {
|
||||
value: PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
description: PropTypes.string,
|
||||
plain: PropTypes.shape({
|
||||
image: PropTypes.string
|
||||
})
|
||||
}),
|
||||
handleClick: PropTypes.func,
|
||||
isProvider: PropTypes.bool,
|
||||
isSelected: PropTypes.bool,
|
||||
handleClick: PropTypes.func
|
||||
isValid: PropTypes.bool,
|
||||
value: PropTypes.oneOfType([
|
||||
Types.ProviderTemplate,
|
||||
Types.ProvisionTemplate
|
||||
])
|
||||
}
|
||||
|
||||
ProvisionTemplateCard.defaultProps = {
|
||||
value: { name: '', description: '' },
|
||||
handleClick: undefined,
|
||||
isProvider: undefined,
|
||||
isSelected: false,
|
||||
handleClick: undefined
|
||||
isValid: true,
|
||||
value: { name: '', description: '' }
|
||||
}
|
||||
|
||||
ProvisionTemplateCard.displayName = 'ProvisionTemplateCard'
|
||||
|
@ -13,22 +13,25 @@ import Action from 'client/components/Cards/SelectCard/Action'
|
||||
import selectCardStyles from 'client/components/Cards/SelectCard/styles'
|
||||
|
||||
const SelectCard = memo(({
|
||||
stylesProps,
|
||||
action,
|
||||
actions,
|
||||
cardActionsProps,
|
||||
icon,
|
||||
title,
|
||||
subheader,
|
||||
cardHeaderProps,
|
||||
mediaProps,
|
||||
isSelected,
|
||||
handleClick,
|
||||
cardProps,
|
||||
cardActionAreaProps,
|
||||
children,
|
||||
dataCy,
|
||||
disableFilterImage,
|
||||
handleClick,
|
||||
icon,
|
||||
isSelected,
|
||||
mediaProps,
|
||||
observerOff,
|
||||
children
|
||||
stylesProps,
|
||||
subheader,
|
||||
title
|
||||
}) => {
|
||||
const classes = selectCardStyles({ ...stylesProps, isSelected })
|
||||
const classes = selectCardStyles({ ...stylesProps, isSelected, disableFilterImage })
|
||||
const { isNearScreen, fromRef } = useNearScreen({
|
||||
distance: '100px'
|
||||
})
|
||||
@ -39,17 +42,22 @@ const SelectCard = memo(({
|
||||
wrap={children => <span ref={fromRef}>{children}</span>}>
|
||||
{observerOff || isNearScreen ? (
|
||||
<Card
|
||||
{...cardProps}
|
||||
className={clsx(classes.root, cardProps?.className, {
|
||||
[classes.actionArea]: !handleClick
|
||||
})}
|
||||
{...cardProps}
|
||||
data-cy={dataCy ? `${dataCy}-card` : undefined}
|
||||
>
|
||||
|
||||
{/* CARD ACTION AREA */}
|
||||
<ConditionalWrap
|
||||
condition={handleClick && !action}
|
||||
wrap={children =>
|
||||
<CardActionArea className={classes.actionArea} onClick={handleClick}>
|
||||
<CardActionArea
|
||||
{...cardActionAreaProps}
|
||||
className={clsx(classes.actionArea, cardActionAreaProps?.className)}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{children}
|
||||
</CardActionArea>
|
||||
}
|
||||
@ -57,6 +65,7 @@ const SelectCard = memo(({
|
||||
{/* CARD HEADER */}
|
||||
{(title || subheader || icon || action) && (
|
||||
<CardHeader
|
||||
{...cardHeaderProps}
|
||||
action={action}
|
||||
avatar={icon}
|
||||
classes={{
|
||||
@ -69,14 +78,16 @@ const SelectCard = memo(({
|
||||
variant: 'body1',
|
||||
noWrap: true,
|
||||
className: classes.header,
|
||||
title: typeof title === 'string' ? title : undefined
|
||||
title: typeof title === 'string' ? title : undefined,
|
||||
...(dataCy) && { 'data-cy': `${dataCy}-card-title` }
|
||||
}}
|
||||
subheader={subheader}
|
||||
subheaderTypographyProps={{
|
||||
variant: 'body2',
|
||||
noWrap: true,
|
||||
className: classes.subheader,
|
||||
title: typeof subheader === 'string' ? subheader : undefined
|
||||
title: typeof subheader === 'string' ? subheader : undefined,
|
||||
...(dataCy) && { 'data-cy': `${dataCy}-card-subheader` }
|
||||
}}
|
||||
{...cardHeaderProps}
|
||||
/>
|
||||
@ -161,29 +172,35 @@ SelectCard.propTypes = {
|
||||
isSelected: PropTypes.bool,
|
||||
handleClick: PropTypes.func,
|
||||
cardProps: PropTypes.object,
|
||||
cardActionAreaProps: PropTypes.object,
|
||||
observerOff: PropTypes.bool,
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
PropTypes.string
|
||||
])
|
||||
]),
|
||||
dataCy: PropTypes.string,
|
||||
disableFilterImage: PropTypes.bool
|
||||
}
|
||||
|
||||
SelectCard.defaultProps = {
|
||||
stylesProps: undefined,
|
||||
action: undefined,
|
||||
actions: undefined,
|
||||
cardActionsProps: undefined,
|
||||
icon: undefined,
|
||||
title: undefined,
|
||||
subheader: undefined,
|
||||
cardHeaderProps: undefined,
|
||||
mediaProps: undefined,
|
||||
isSelected: false,
|
||||
handleClick: undefined,
|
||||
cardProps: {},
|
||||
cardActionAreaProps: {},
|
||||
children: undefined,
|
||||
dataCy: undefined,
|
||||
disableFilterImage: false,
|
||||
handleClick: undefined,
|
||||
icon: undefined,
|
||||
isSelected: false,
|
||||
mediaProps: undefined,
|
||||
observerOff: false,
|
||||
children: undefined
|
||||
stylesProps: undefined,
|
||||
subheader: undefined,
|
||||
title: undefined
|
||||
}
|
||||
|
||||
SelectCard.displayName = 'SelectCard'
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { makeStyles } from '@material-ui/core'
|
||||
|
||||
export default makeStyles(theme => ({
|
||||
root: ({ isSelected }) => ({
|
||||
root: ({ isSelected, disableFilterImage }) => ({
|
||||
height: '100%',
|
||||
transition: theme.transitions.create(
|
||||
['background-color', 'box-shadow'], { duration: '0.2s' }
|
||||
@ -21,7 +21,12 @@ export default makeStyles(theme => ({
|
||||
}),
|
||||
actionArea: {
|
||||
height: '100%',
|
||||
minHeight: ({ minHeight = 140 }) => minHeight
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
minHeight: ({ minHeight = 140 }) => minHeight,
|
||||
'&:disabled': {
|
||||
filter: 'brightness(0.5)'
|
||||
}
|
||||
},
|
||||
mediaActionArea: {
|
||||
display: 'flex',
|
||||
@ -32,12 +37,25 @@ export default makeStyles(theme => ({
|
||||
}
|
||||
},
|
||||
media: {
|
||||
'& img': {
|
||||
width: '100%',
|
||||
objectFit: 'cover',
|
||||
maxHeight: ({ mediaHeight = 140 }) => mediaHeight
|
||||
},
|
||||
flexGrow: 1,
|
||||
transition: theme.transitions.create('filter', { duration: '0.2s' }),
|
||||
filter: ({ isSelected }) => (theme.palette.type === 'dark' || isSelected)
|
||||
? 'contrast(0) brightness(2)'
|
||||
: 'contrast(0) brightness(0.8)'
|
||||
filter: ({ isSelected, disableFilterImage }) => {
|
||||
return disableFilterImage
|
||||
? 'none'
|
||||
: (theme.palette.type === 'dark' || isSelected)
|
||||
? 'contrast(0) brightness(2)'
|
||||
: 'contrast(0) brightness(0.8)'
|
||||
}
|
||||
},
|
||||
headerRoot: {
|
||||
alignItems: 'end',
|
||||
alignSelf: 'stretch'
|
||||
},
|
||||
headerRoot: { alignItems: 'end' },
|
||||
headerContent: { overflow: 'auto' },
|
||||
headerAvatar: {
|
||||
display: 'flex',
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { memo } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { CSSTransition, TransitionGroup } from 'react-transition-group'
|
||||
|
||||
|
@ -34,6 +34,7 @@ export const IMAGES_URL = `${STATIC_FILES_URL}/images`
|
||||
export const PROVIDER_IMAGES_URL = `${IMAGES_URL}/providers`
|
||||
export const PROVISION_IMAGES_URL = `${IMAGES_URL}/provisions`
|
||||
export const DEFAULT_IMAGE = `${IMAGES_URL}/default.webp`
|
||||
export const IMAGE_FORMATS = ['webp', 'png', 'jpg']
|
||||
|
||||
export const FONTS_URL = `${STATIC_FILES_URL}/fonts`
|
||||
export const LANGUAGES_URL = `${STATIC_FILES_URL}/languages`
|
||||
|
@ -1,14 +1,12 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
import { useFormContext } from 'react-hook-form'
|
||||
|
||||
import { useProvision } from 'client/hooks'
|
||||
import FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
import { EmptyCard } from 'client/components/Cards'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
import { STEP_ID as TEMPLATE_ID } from 'client/containers/Providers/Form/Create/Steps/Template'
|
||||
import { FORM_FIELDS, STEP_FORM_SCHEMA } from './schema'
|
||||
import {
|
||||
FORM_FIELDS, STEP_FORM_SCHEMA
|
||||
} from 'client/containers/Providers/Form/Create/Steps/Connection/schema'
|
||||
|
||||
export const STEP_ID = 'connection'
|
||||
|
||||
@ -19,37 +17,13 @@ const Connection = () => ({
|
||||
label: T.ConfigureConnection,
|
||||
resolver: () => STEP_FORM_SCHEMA(connection),
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: useCallback(() => {
|
||||
content: useCallback(({ data }) => {
|
||||
const [fields, setFields] = useState([])
|
||||
const { provisionsTemplates } = useProvision()
|
||||
const { watch, reset } = useFormContext()
|
||||
|
||||
useEffect(() => {
|
||||
const {
|
||||
[TEMPLATE_ID]: templateSelected,
|
||||
[STEP_ID]: currentConnections
|
||||
} = watch()
|
||||
|
||||
const { name, provision, provider } = templateSelected?.[0] ?? {}
|
||||
const providerTemplate = provisionsTemplates
|
||||
?.[provision]
|
||||
?.providers?.[provider]
|
||||
?.find(providerSelected => providerSelected.name === name) ?? {}
|
||||
|
||||
const {
|
||||
location_key: locationKey = '',
|
||||
connection: { [locationKey]: _, ...connectionEditable } = {}
|
||||
} = providerTemplate
|
||||
|
||||
connection = connectionEditable
|
||||
connection = data
|
||||
setFields(FORM_FIELDS(connection))
|
||||
|
||||
// set defaults connection values when first render
|
||||
!currentConnections && reset({
|
||||
...watch(),
|
||||
[STEP_ID]: STEP_FORM_SCHEMA(connection).default()
|
||||
})
|
||||
}, [])
|
||||
}, [data])
|
||||
|
||||
return (fields?.length === 0) ? (
|
||||
<EmptyCard title={'✔️ There is not connections to fill'} />
|
||||
|
@ -1,17 +1,17 @@
|
||||
import * as yup from 'yup'
|
||||
import { INPUT_TYPES } from 'client/constants'
|
||||
import { getValidationFromFields } from 'client/utils'
|
||||
import { capitalize, getValidationFromFields } from 'client/utils'
|
||||
|
||||
export const FORM_FIELDS = connection =>
|
||||
Object.entries(connection)?.map(([name, label]) => ({
|
||||
Object.entries(connection)?.map(([name, value]) => ({
|
||||
name,
|
||||
label,
|
||||
label: capitalize(name),
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: yup
|
||||
.string()
|
||||
.trim()
|
||||
.required(`${label} field is required`)
|
||||
.default('')
|
||||
.required(`${name} field is required`)
|
||||
.default(value)
|
||||
}))
|
||||
|
||||
export const STEP_FORM_SCHEMA = connection => yup.object(
|
||||
|
@ -2,10 +2,11 @@ import React, { useCallback } from 'react'
|
||||
import { Divider, Select, Breadcrumbs, Link } from '@material-ui/core'
|
||||
import ArrowIcon from '@material-ui/icons/ArrowForwardIosRounded'
|
||||
|
||||
import { useProvision, useListForm, useGeneral } from 'client/hooks'
|
||||
import { useProvision, useListForm } from 'client/hooks'
|
||||
import { ListCards } from 'client/components/List'
|
||||
import { EmptyCard, ProvisionTemplateCard } from 'client/components/Cards'
|
||||
import { isExternalURL, sanitize } from 'client/utils'
|
||||
import * as ProviderTemplateModel from 'client/models/ProviderTemplate'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
import { STEP_ID as CONFIGURATION_ID } from 'client/containers/Providers/Form/Create/Steps/BasicConfiguration'
|
||||
@ -18,129 +19,130 @@ const Template = () => ({
|
||||
id: STEP_ID,
|
||||
label: T.ProviderTemplate,
|
||||
resolver: () => STEP_FORM_SCHEMA,
|
||||
content: useCallback(({ data, setFormData }) => {
|
||||
const templateSelected = data?.[0]
|
||||
content: useCallback(
|
||||
({ data, setFormData }) => {
|
||||
const templateSelected = data?.[0]
|
||||
|
||||
const [provisionSelected, setProvision] = React.useState(templateSelected?.provision)
|
||||
const [providerSelected, setProvider] = React.useState(templateSelected?.provider)
|
||||
const [provisionSelected, setProvision] = React.useState(() => templateSelected?.plain?.provision_type)
|
||||
const [providerSelected, setProvider] = React.useState(() => templateSelected?.provider)
|
||||
|
||||
const { showError } = useGeneral()
|
||||
const { provisionsTemplates } = useProvision()
|
||||
const provisionSelectedDescription = provisionsTemplates?.[provisionSelected]?.description
|
||||
const providersTypes = provisionsTemplates?.[provisionSelected]?.providers ?? []
|
||||
const templatesAvailable = providersTypes?.[providerSelected]
|
||||
const { provisionsTemplates } = useProvision()
|
||||
const provisionSelectedDescription = provisionsTemplates?.[provisionSelected]?.description
|
||||
const providersTypes = provisionsTemplates?.[provisionSelected]?.providers ?? []
|
||||
const templatesAvailable = providersTypes?.[providerSelected]
|
||||
|
||||
const {
|
||||
handleSelect,
|
||||
handleUnselect,
|
||||
handleClear
|
||||
} = useListForm({ key: STEP_ID, setList: setFormData })
|
||||
const {
|
||||
handleSelect,
|
||||
handleUnselect,
|
||||
handleClear
|
||||
} = useListForm({ key: STEP_ID, setList: setFormData })
|
||||
|
||||
const handleChangeProvision = evt => {
|
||||
setProvision(evt.target.value)
|
||||
setProvider(undefined)
|
||||
templateSelected && handleClear()
|
||||
}
|
||||
const handleChangeProvision = evt => {
|
||||
setProvision(evt.target.value)
|
||||
setProvider(undefined)
|
||||
templateSelected && handleClear()
|
||||
}
|
||||
|
||||
const handleChangeProvider = evt => {
|
||||
setProvider(evt.target.value)
|
||||
templateSelected && handleClear()
|
||||
}
|
||||
const handleChangeProvider = evt => {
|
||||
setProvider(evt.target.value)
|
||||
templateSelected && handleClear()
|
||||
}
|
||||
|
||||
const handleClick = ({ name, description, provider, plain = {} }, isSelected) => {
|
||||
const { provision_type: provisionType } = plain
|
||||
const handleClick = (template, isSelected) => {
|
||||
const { name, description, plain = {}, connection } = template
|
||||
const { location_key: locationKey = '' } = plain
|
||||
const { [locationKey]: _, ...connectionEditable } = connection
|
||||
|
||||
if ([name, provisionType, provider].includes(undefined)) {
|
||||
showError({ message: 'This template has bad format. Ask your cloud administrator' })
|
||||
} else {
|
||||
// reset rest of form when change template
|
||||
setFormData({ [CONFIGURATION_ID]: { name, description }, [CONNECTION_ID]: undefined })
|
||||
setFormData({ [CONFIGURATION_ID]: { name, description }, [CONNECTION_ID]: connectionEditable })
|
||||
|
||||
isSelected
|
||||
? handleUnselect(name, item => item.name === name)
|
||||
: handleSelect({ name, provider, provision: provisionType })
|
||||
: handleSelect(template)
|
||||
}
|
||||
}
|
||||
|
||||
const RenderOptions = ({ options = {} }) => Object.keys(options)?.map(option => (
|
||||
<option key={option} value={option}>{option}</option>
|
||||
))
|
||||
const RenderOptions = ({ options = {} }) => Object.keys(options)?.map(option => (
|
||||
<option key={option} value={option}>{option}</option>
|
||||
))
|
||||
|
||||
const RenderDescription = ({ description = '' }) => (
|
||||
<p>{(sanitize`${description}`)?.split(' ').map((string, idx) =>
|
||||
isExternalURL(string)
|
||||
? <Link key={`link-${idx}`} color='textPrimary' target='_blank' href={string}>{string}</Link>
|
||||
: ` ${string}`
|
||||
)}</p>
|
||||
)
|
||||
const RenderDescription = ({ description = '' }) => (
|
||||
<p>{(sanitize`${description}`)?.split(' ').map((string, idx) =>
|
||||
isExternalURL(string)
|
||||
? <Link key={`link-${idx}`} color='textPrimary' target='_blank' href={string}>{string}</Link>
|
||||
: ` ${string}`
|
||||
)}</p>
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* -- 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'
|
||||
>
|
||||
<option value="">{T.None}</option>
|
||||
<RenderOptions options={provisionsTemplates} />
|
||||
</Select>
|
||||
{provisionSelected && <Select
|
||||
color='secondary'
|
||||
inputProps = {{ 'data-cy': 'select-provider-type' }}
|
||||
native
|
||||
style={{ minWidth: '8em' }}
|
||||
onChange={handleChangeProvider}
|
||||
value={providerSelected}
|
||||
variant='outlined'
|
||||
>
|
||||
<option value="">{T.None}</option>
|
||||
<RenderOptions options={providersTypes} />
|
||||
</Select>}
|
||||
</Breadcrumbs>
|
||||
return (
|
||||
<>
|
||||
{/* -- 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'
|
||||
>
|
||||
<option value="">{T.None}</option>
|
||||
<RenderOptions options={provisionsTemplates} />
|
||||
</Select>
|
||||
{provisionSelected && <Select
|
||||
color='secondary'
|
||||
inputProps = {{ 'data-cy': 'select-provider-type' }}
|
||||
native
|
||||
style={{ minWidth: '8em' }}
|
||||
onChange={handleChangeProvider}
|
||||
value={providerSelected}
|
||||
variant='outlined'
|
||||
>
|
||||
<option value="">{T.None}</option>
|
||||
<RenderOptions options={providersTypes} />
|
||||
</Select>}
|
||||
</Breadcrumbs>
|
||||
|
||||
{/* -- DESCRIPTION -- */}
|
||||
{React.useMemo(() => provisionSelectedDescription && (
|
||||
<RenderDescription description={provisionSelectedDescription} />
|
||||
), [provisionSelectedDescription])}
|
||||
{/* -- DESCRIPTION -- */}
|
||||
{React.useMemo(() => provisionSelectedDescription && (
|
||||
<RenderDescription description={provisionSelectedDescription} />
|
||||
), [provisionSelectedDescription])}
|
||||
|
||||
<Divider style={{ margin: '1rem 0' }} />
|
||||
<Divider style={{ margin: '1rem 0' }} />
|
||||
|
||||
{/* -- LIST -- */}
|
||||
<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
|
||||
)
|
||||
|
||||
return {
|
||||
isProvider: true,
|
||||
isSelected,
|
||||
handleClick: () => handleClick(value, isSelected)
|
||||
{/* -- LIST -- */}
|
||||
<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 isValid = ProviderTemplateModel.isValidProviderTemplate(value)
|
||||
|
||||
return {
|
||||
isProvider: true,
|
||||
isSelected,
|
||||
isValid,
|
||||
handleClick: () => handleClick(value, isSelected)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}, [])
|
||||
})
|
||||
|
||||
export default Template
|
||||
|
@ -1,39 +1,7 @@
|
||||
import * as yup from 'yup'
|
||||
import { getValidationFromFields } from 'client/utils'
|
||||
|
||||
const NAME = {
|
||||
name: 'name',
|
||||
validation: yup
|
||||
.string()
|
||||
.trim()
|
||||
.required('Template field is required')
|
||||
.default(undefined)
|
||||
}
|
||||
|
||||
const PROVISION = {
|
||||
name: 'provision',
|
||||
validation: yup
|
||||
.string()
|
||||
.trim()
|
||||
.required('Provision type field is required')
|
||||
.default(undefined)
|
||||
}
|
||||
|
||||
const PROVIDER = {
|
||||
name: 'provider',
|
||||
validation: yup
|
||||
.string()
|
||||
.trim()
|
||||
.required('Provider type field is required')
|
||||
.default(undefined)
|
||||
}
|
||||
|
||||
export const PROVIDER_TEMPLATE_SCHEMA = yup.object(
|
||||
getValidationFromFields([NAME, PROVISION, PROVIDER])
|
||||
)
|
||||
|
||||
export const STEP_FORM_SCHEMA = yup
|
||||
.array(PROVIDER_TEMPLATE_SCHEMA)
|
||||
.array(yup.object())
|
||||
.min(1, 'Select provider template')
|
||||
.max(1, 'Max. one template selected')
|
||||
.required('Provider template field is required')
|
||||
|
@ -9,6 +9,7 @@ import FormStepper from 'client/components/FormStepper'
|
||||
import Steps from 'client/containers/Providers/Form/Create/Steps'
|
||||
|
||||
import { useFetch, useProvision, useGeneral } from 'client/hooks'
|
||||
import * as ProviderTemplateModel from 'client/models/ProviderTemplate'
|
||||
import { PATH } from 'client/router/provision'
|
||||
|
||||
function ProviderCreateForm () {
|
||||
@ -19,8 +20,7 @@ function ProviderCreateForm () {
|
||||
const {
|
||||
getProvider,
|
||||
createProvider,
|
||||
updateProvider,
|
||||
provisionsTemplates
|
||||
updateProvider
|
||||
} = useProvision()
|
||||
|
||||
const { data, fetchRequest, loading, error } = useFetch(getProvider)
|
||||
@ -33,61 +33,66 @@ function ProviderCreateForm () {
|
||||
resolver: yupResolver(resolvers())
|
||||
})
|
||||
|
||||
const redirectWithError = (name = '') => {
|
||||
showError({
|
||||
message: `
|
||||
Cannot found provider template (${name}),
|
||||
ask your cloud administrator`
|
||||
})
|
||||
|
||||
const redirectWithError = (message = 'Error') => {
|
||||
showError({ message })
|
||||
history.push(PATH.PROVIDERS.LIST)
|
||||
}
|
||||
|
||||
const getProviderTemplateByDir = ({ provision, provider, name }) =>
|
||||
provisionsTemplates
|
||||
?.[provision]
|
||||
?.providers
|
||||
?.[provider]
|
||||
?.find(providerSelected => providerSelected.name === name)
|
||||
|
||||
const onSubmit = formData => {
|
||||
const { template, configuration, connection, registration_time: time } = formData
|
||||
const { name, description } = configuration
|
||||
const callCreateProvider = formData => {
|
||||
const { template, configuration, connection } = formData
|
||||
|
||||
const templateSelected = template?.[0]
|
||||
const providerTemplate = getProviderTemplateByDir(templateSelected)
|
||||
const { name, description } = configuration
|
||||
|
||||
if (!providerTemplate) return redirectWithError(templateSelected?.name)
|
||||
const isValid = ProviderTemplateModel.isValidProviderTemplate(templateSelected)
|
||||
|
||||
const {
|
||||
inputs,
|
||||
name: templateName,
|
||||
plain,
|
||||
provider,
|
||||
location_key: locationKey,
|
||||
connection: { [locationKey]: connectionFixed }
|
||||
} = providerTemplate
|
||||
!isValid && redirectWithError(`
|
||||
The template selected has a bad format.
|
||||
Ask your cloud administrator`
|
||||
)
|
||||
|
||||
const { inputs, plain, provider } = templateSelected
|
||||
const { location_key: locationKey } = plain
|
||||
|
||||
const { [locationKey]: connectionFixed } = templateSelected.connection
|
||||
|
||||
const formatData = {
|
||||
...(!isUpdate && {
|
||||
name,
|
||||
plain,
|
||||
provider,
|
||||
inputs,
|
||||
template: templateName
|
||||
}),
|
||||
description,
|
||||
connection: { ...connection, [locationKey]: connectionFixed },
|
||||
registration_time: time
|
||||
description,
|
||||
inputs,
|
||||
name,
|
||||
plain,
|
||||
provider
|
||||
}
|
||||
|
||||
if (isUpdate) {
|
||||
updateProvider({ id, data: formatData })
|
||||
.then(() => history.push(PATH.PROVIDERS.LIST))
|
||||
} else {
|
||||
createProvider({ data: formatData })
|
||||
.then(() => history.push(PATH.PROVIDERS.LIST))
|
||||
createProvider({ data: formatData })
|
||||
.then(() => history.push(PATH.PROVIDERS.LIST))
|
||||
}
|
||||
|
||||
const callUpdateProvider = formData => {
|
||||
const { configuration, connection: connectionEditable } = formData
|
||||
const { description } = configuration
|
||||
|
||||
const {
|
||||
PLAIN: { location_key: locationKey } = {},
|
||||
PROVISION_BODY: {
|
||||
connection: { [locationKey]: connectionFixed },
|
||||
registration_time: registrationTime
|
||||
}
|
||||
} = data?.TEMPLATE
|
||||
|
||||
const formatData = {
|
||||
description,
|
||||
connection: { ...connectionEditable, [locationKey]: connectionFixed },
|
||||
registration_time: registrationTime
|
||||
}
|
||||
|
||||
updateProvider({ id, data: formatData })
|
||||
.then(() => history.push(PATH.PROVIDERS.LIST))
|
||||
}
|
||||
|
||||
const onSubmit = formData => {
|
||||
isUpdate ? callUpdateProvider(formData) : callCreateProvider(formData)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@ -97,29 +102,17 @@ function ProviderCreateForm () {
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
const {
|
||||
connection,
|
||||
description,
|
||||
name,
|
||||
provider,
|
||||
template: templateName,
|
||||
registration_time: time
|
||||
} = data?.TEMPLATE?.PROVISION_BODY ?? {}
|
||||
|
||||
const { provision_type: provisionType } = data?.TEMPLATE?.PLAIN ?? {}
|
||||
|
||||
const templateSelected = { name: templateName, provision: provisionType, provider }
|
||||
const template = getProviderTemplateByDir(templateSelected)
|
||||
|
||||
if (!template) return redirectWithError(templateName)
|
||||
|
||||
const { location_key: locationKey } = template
|
||||
const { [locationKey]: _, ...connectionEditable } = connection
|
||||
PLAIN: { location_key: locationKey } = {},
|
||||
PROVISION_BODY: {
|
||||
connection: { [locationKey]: connectionEditable },
|
||||
description,
|
||||
name
|
||||
}
|
||||
} = data?.TEMPLATE
|
||||
|
||||
methods.reset({
|
||||
connection: connectionEditable,
|
||||
configuration: { name, description },
|
||||
template: [templateSelected],
|
||||
registration_time: time
|
||||
configuration: { name, description }
|
||||
}, { errors: false })
|
||||
}
|
||||
}, [data])
|
||||
|
@ -6,6 +6,7 @@ import DeleteIcon from '@material-ui/icons/Delete'
|
||||
import { useProvision, useOpennebula, useFetchAll } from 'client/hooks'
|
||||
import { ListCards } from 'client/components/List'
|
||||
import { DatastoreCard } from 'client/components/Cards'
|
||||
import * as Types from 'client/types/provision'
|
||||
|
||||
const Datastores = memo(({ hidden, data, fetchRequest }) => {
|
||||
const {
|
||||
@ -51,7 +52,7 @@ const Datastores = memo(({ hidden, data, fetchRequest }) => {
|
||||
prev.hidden === next.hidden && prev.data === next.data)
|
||||
|
||||
Datastores.propTypes = {
|
||||
data: PropTypes.object.isRequired,
|
||||
data: Types.Provision.isRequired,
|
||||
hidden: PropTypes.bool,
|
||||
fetchRequest: PropTypes.func
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import ConfigureIcon from '@material-ui/icons/Settings'
|
||||
import { useProvision, useOpennebula, useFetchAll } from 'client/hooks'
|
||||
import { ListCards } from 'client/components/List'
|
||||
import { HostCard } from 'client/components/Cards'
|
||||
import * as Types from 'client/types/provision'
|
||||
|
||||
const Hosts = memo(({ hidden, data, fetchRequest }) => {
|
||||
const {
|
||||
@ -59,7 +60,7 @@ const Hosts = memo(({ hidden, data, fetchRequest }) => {
|
||||
prev.hidden === next.hidden && prev.data === next.data)
|
||||
|
||||
Hosts.propTypes = {
|
||||
data: PropTypes.object.isRequired,
|
||||
data: Types.Provision.isRequired,
|
||||
hidden: PropTypes.bool,
|
||||
fetchRequest: PropTypes.func
|
||||
}
|
||||
|
@ -14,6 +14,8 @@ import NetworksTab from 'client/containers/Provisions/DialogInfo/networks'
|
||||
import HostsTab from 'client/containers/Provisions/DialogInfo/hosts'
|
||||
import LogTab from 'client/containers/Provisions/DialogInfo/log'
|
||||
|
||||
import * as Types from 'client/types/provision'
|
||||
|
||||
const TABS = [
|
||||
{ name: 'info', icon: InfoIcon, content: InfoTab },
|
||||
{ name: 'datastores', icon: DatastoreIcon, content: DatastoresTab },
|
||||
@ -69,7 +71,7 @@ const DialogInfo = ({ data, ...methods }) => {
|
||||
}
|
||||
|
||||
DialogInfo.propTypes = {
|
||||
data: PropTypes.object.isRequired,
|
||||
data: Types.Provision.isRequired,
|
||||
fetchRequest: PropTypes.func
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React, { memo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { List, ListItem, Typography, Grid, Paper, Divider } from '@material-ui/core'
|
||||
import { CheckBox, CheckBoxOutlineBlank } from '@material-ui/icons'
|
||||
@ -8,6 +7,7 @@ import clsx from 'clsx'
|
||||
import useStyles from 'client/containers/Provisions/DialogInfo/styles'
|
||||
import { StatusChip } from 'client/components/Status'
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import * as Types from 'client/types/provision'
|
||||
import { T, PROVISIONS_STATES } from 'client/constants'
|
||||
|
||||
const Info = memo(({ data }) => {
|
||||
@ -124,7 +124,7 @@ const Info = memo(({ data }) => {
|
||||
})
|
||||
|
||||
Info.propTypes = {
|
||||
data: PropTypes.object.isRequired
|
||||
data: Types.Provision.isRequired
|
||||
}
|
||||
|
||||
Info.defaultProps = {
|
||||
|
@ -5,6 +5,7 @@ import { LinearProgress } from '@material-ui/core'
|
||||
|
||||
import { useProvision, useFetch, useSocket } from 'client/hooks'
|
||||
import DebugLog from 'client/components/DebugLog'
|
||||
import * as Types from 'client/types/provision'
|
||||
|
||||
const Log = React.memo(({ hidden, data: { ID } }) => {
|
||||
const { getProvision } = useSocket()
|
||||
@ -46,7 +47,7 @@ const Log = React.memo(({ hidden, data: { ID } }) => {
|
||||
prev.hidden === next.hidden && prev.data === next.data)
|
||||
|
||||
Log.propTypes = {
|
||||
data: PropTypes.object.isRequired,
|
||||
data: Types.Provision.isRequired,
|
||||
hidden: PropTypes.bool,
|
||||
fetchRequest: PropTypes.func
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import DeleteIcon from '@material-ui/icons/Delete'
|
||||
import { useProvision, useOpennebula, useFetchAll } from 'client/hooks'
|
||||
import { ListCards } from 'client/components/List'
|
||||
import { NetworkCard } from 'client/components/Cards'
|
||||
import * as Types from 'client/types/provision'
|
||||
|
||||
const Networks = memo(({ hidden, data, fetchRequest }) => {
|
||||
const {
|
||||
@ -51,7 +52,7 @@ const Networks = memo(({ hidden, data, fetchRequest }) => {
|
||||
prev.hidden === next.hidden && prev.data === next.data)
|
||||
|
||||
Networks.propTypes = {
|
||||
data: PropTypes.object.isRequired,
|
||||
data: Types.Provision.isRequired,
|
||||
hidden: PropTypes.bool,
|
||||
fetchRequest: PropTypes.func
|
||||
}
|
||||
|
@ -26,23 +26,16 @@ const Inputs = () => ({
|
||||
content: useCallback(() => {
|
||||
const [fields, setFields] = useState(undefined)
|
||||
const { changeLoading } = useGeneral()
|
||||
const { provisionsTemplates, getProvider } = useProvision()
|
||||
const { getProvider } = useProvision()
|
||||
const { data: fetchData, fetchRequest, loading } = useFetch(getProvider)
|
||||
const { watch, reset } = useFormContext()
|
||||
|
||||
const getProvisionTemplateByDir = ({ provision, provider, name }) =>
|
||||
provisionsTemplates
|
||||
?.[provision]
|
||||
?.provisions
|
||||
?.[provider]
|
||||
?.find(provisionTemplate => provisionTemplate.name === name)
|
||||
|
||||
useEffect(() => {
|
||||
const { [PROVIDER_ID]: providerSelected, [STEP_ID]: currentInputs } = watch()
|
||||
const { [PROVIDER_ID]: providerSelected = [], [STEP_ID]: currentInputs } = watch()
|
||||
|
||||
if (!currentInputs) {
|
||||
changeLoading(true) // disable finish button until provider is fetched
|
||||
fetchRequest({ id: providerSelected[0] })
|
||||
fetchRequest({ id: providerSelected[0]?.ID })
|
||||
} else {
|
||||
setFields(FORM_FIELDS(inputs))
|
||||
}
|
||||
@ -53,7 +46,7 @@ const Inputs = () => ({
|
||||
const { [TEMPLATE_ID]: provisionTemplateSelected = [] } = watch()
|
||||
const { TEMPLATE: { PROVISION_BODY } = {} } = fetchData
|
||||
|
||||
const provisionTemplate = getProvisionTemplateByDir(provisionTemplateSelected?.[0])
|
||||
const provisionTemplate = provisionTemplateSelected?.[0]
|
||||
|
||||
// MERGE INPUTS provision template + PROVISION_BODY.inputs (provider fetch)
|
||||
inputs = provisionTemplate.inputs.map(templateInput =>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { useWatch } from 'react-hook-form'
|
||||
|
||||
import { useProvision, useListForm } from 'client/hooks'
|
||||
@ -18,31 +18,30 @@ const Provider = () => ({
|
||||
resolver: () => STEP_FORM_SCHEMA,
|
||||
content: useCallback(({ data, setFormData }) => {
|
||||
const { providers } = useProvision()
|
||||
const template = useWatch({ name: TEMPLATE_ID })
|
||||
const templateSelected = template?.[0] ?? {}
|
||||
const provisionTemplate = useWatch({ name: TEMPLATE_ID })
|
||||
const provisionTemplateSelected = provisionTemplate?.[0] ?? {}
|
||||
|
||||
const providersByTypeAndService = React.useMemo(() =>
|
||||
providers.filter(({ TEMPLATE: { PLAIN = {} } = {} }) =>
|
||||
PLAIN.provider === templateSelected.provider &&
|
||||
PLAIN.provision_type === templateSelected.provision
|
||||
PLAIN.provider === provisionTemplateSelected.provider &&
|
||||
PLAIN.provision_type === provisionTemplateSelected.provision_type
|
||||
)
|
||||
, [providers])
|
||||
, [])
|
||||
|
||||
const {
|
||||
handleSelect,
|
||||
handleUnselect
|
||||
} = useListForm({ key: STEP_ID, setList: setFormData })
|
||||
|
||||
useEffect(() => {
|
||||
// delete provider selected at template if not exists
|
||||
const existsProvider = providers?.some(({ ID }) => ID === data?.[0])
|
||||
!existsProvider && handleUnselect(data?.[0])
|
||||
}, [])
|
||||
const handleClick = (provider, isSelected) => {
|
||||
const { ID } = provider
|
||||
|
||||
const handleClick = (id, isSelected) => {
|
||||
// reset inputs when change provider
|
||||
// reset inputs when selected provider changes
|
||||
setFormData(prev => ({ ...prev, [INPUTS_ID]: undefined }))
|
||||
isSelected ? handleUnselect(id) : handleSelect(id)
|
||||
|
||||
isSelected
|
||||
? handleUnselect(ID, item => item.ID === ID)
|
||||
: handleSelect(provider)
|
||||
}
|
||||
|
||||
return (
|
||||
@ -51,13 +50,13 @@ const Provider = () => ({
|
||||
EmptyComponent={<EmptyCard title={'Your providers list is empty'} />}
|
||||
CardComponent={ProvisionCard}
|
||||
gridProps={{ 'data-cy': 'providers' }}
|
||||
cardsProps={({ value: { ID } }) => {
|
||||
const isSelected = data?.some(selected => selected === ID)
|
||||
cardsProps={({ value = {} }) => {
|
||||
const isSelected = data?.some(selected => selected.ID === value.ID)
|
||||
|
||||
return {
|
||||
isProvider: true,
|
||||
isSelected,
|
||||
handleClick: () => handleClick(ID, isSelected)
|
||||
handleClick: () => handleClick(value, isSelected)
|
||||
}
|
||||
}}
|
||||
breakpoints={{ xs: 12, sm: 6, md: 4 }}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as yup from 'yup'
|
||||
|
||||
export const STEP_FORM_SCHEMA = yup
|
||||
.array(yup.string().trim())
|
||||
.array(yup.object())
|
||||
.min(1, 'Select provider')
|
||||
.max(1, 'Max. one provider selected')
|
||||
.required('Provider field is required')
|
||||
|
@ -2,10 +2,11 @@ import React, { useCallback } from 'react'
|
||||
import { Divider, Select, Breadcrumbs, Link } from '@material-ui/core'
|
||||
import ArrowIcon from '@material-ui/icons/ArrowForwardIosRounded'
|
||||
|
||||
import { useProvision, useListForm, useGeneral } from 'client/hooks'
|
||||
import { useProvision, useListForm } from 'client/hooks'
|
||||
import { ListCards } from 'client/components/List'
|
||||
import { EmptyCard, ProvisionTemplateCard } from 'client/components/Cards'
|
||||
import { isExternalURL, sanitize } from 'client/utils'
|
||||
import * as ProvisionTemplateModel from 'client/models/ProvisionTemplate'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
import { STEP_ID as PROVIDER_ID } from 'client/containers/Provisions/Form/Create/Steps/Provider'
|
||||
@ -22,10 +23,9 @@ const Template = () => ({
|
||||
content: useCallback(({ data, setFormData }) => {
|
||||
const templateSelected = data?.[0]
|
||||
|
||||
const [provisionSelected, setProvision] = React.useState(templateSelected?.provision)
|
||||
const [provisionSelected, setProvision] = React.useState(templateSelected?.provision_type)
|
||||
const [providerSelected, setProvider] = React.useState(templateSelected?.provider)
|
||||
|
||||
const { showError } = useGeneral()
|
||||
const { provisionsTemplates, providers } = useProvision()
|
||||
const provisionSelectedDescription = provisionsTemplates?.[provisionSelected]?.description
|
||||
const providersTypes = provisionsTemplates?.[provisionSelected]?.provisions ?? []
|
||||
@ -49,24 +49,21 @@ const Template = () => ({
|
||||
}
|
||||
|
||||
const handleClick = (template, isSelected) => {
|
||||
const { name, description, provision_type: provisionType, provider, defaults, hosts } = template
|
||||
const { name, description, defaults, hosts } = template
|
||||
|
||||
if ([name, provisionType, provider].includes(undefined)) {
|
||||
showError({ message: 'This template has bad format. Ask your cloud administrator' })
|
||||
} else {
|
||||
// reset rest of form when change template
|
||||
const providerName = defaults?.provision?.provider_name ?? hosts?.[0]?.provision.provider_name
|
||||
const { ID } = providers?.find(({ NAME }) => NAME === providerName) ?? {}
|
||||
setFormData({
|
||||
[PROVIDER_ID]: [ID],
|
||||
[CONFIGURATION_ID]: { name, description },
|
||||
[INPUTS_ID]: undefined
|
||||
})
|
||||
// reset rest of form when change template
|
||||
const providerName = defaults?.provision?.provider_name ?? hosts?.[0]?.provision.provider_name
|
||||
const providerFromProvisionTemplate = providers?.find(({ NAME }) => NAME === providerName) ?? {}
|
||||
|
||||
isSelected
|
||||
? handleUnselect(name, item => item.name === name)
|
||||
: handleSelect({ name, provider, provision: provisionType })
|
||||
}
|
||||
setFormData({
|
||||
[PROVIDER_ID]: [providerFromProvisionTemplate],
|
||||
[CONFIGURATION_ID]: { name, description },
|
||||
[INPUTS_ID]: undefined
|
||||
})
|
||||
|
||||
isSelected
|
||||
? handleUnselect(name, item => item.name === name)
|
||||
: handleSelect(template)
|
||||
}
|
||||
|
||||
const RenderOptions = ({ options = {} }) => Object.keys(options)?.map(option => (
|
||||
@ -134,12 +131,13 @@ const Template = () => ({
|
||||
gridProps={{ 'data-cy': 'provisions-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 = ProvisionTemplateModel.isValidProvisionTemplate(value)
|
||||
|
||||
return {
|
||||
isSelected,
|
||||
isValid,
|
||||
handleClick: () => handleClick(value, isSelected)
|
||||
}
|
||||
}}
|
||||
|
@ -1,39 +1,7 @@
|
||||
import * as yup from 'yup'
|
||||
import { getValidationFromFields } from 'client/utils'
|
||||
|
||||
const NAME = {
|
||||
name: 'name',
|
||||
validation: yup
|
||||
.string()
|
||||
.trim()
|
||||
.required('Template field is required')
|
||||
.default(undefined)
|
||||
}
|
||||
|
||||
const PROVISION = {
|
||||
name: 'provision',
|
||||
validation: yup
|
||||
.string()
|
||||
.trim()
|
||||
.required('Provision type field is required')
|
||||
.default(undefined)
|
||||
}
|
||||
|
||||
const PROVIDER = {
|
||||
name: 'provider',
|
||||
validation: yup
|
||||
.string()
|
||||
.trim()
|
||||
.required('Provider type field is required')
|
||||
.default(undefined)
|
||||
}
|
||||
|
||||
export const PROVIDER_TEMPLATE_SCHEMA = yup.object(
|
||||
getValidationFromFields([NAME, PROVISION, PROVIDER])
|
||||
)
|
||||
|
||||
export const STEP_FORM_SCHEMA = yup
|
||||
.array(PROVIDER_TEMPLATE_SCHEMA)
|
||||
.array(yup.object())
|
||||
.min(1, 'Select provision template')
|
||||
.max(1, 'Max. one template selected')
|
||||
.required('Provision template field is required')
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { Redirect, useHistory } from 'react-router'
|
||||
import { Redirect } from 'react-router'
|
||||
|
||||
import { Container, LinearProgress } from '@material-ui/core'
|
||||
import { useForm, FormProvider } from 'react-hook-form'
|
||||
@ -9,16 +9,16 @@ import FormStepper from 'client/components/FormStepper'
|
||||
import Steps from 'client/containers/Provisions/Form/Create/Steps'
|
||||
import DebugLog from 'client/components/DebugLog'
|
||||
|
||||
import { useGeneral, useProvision, useSocket, useFetch } from 'client/hooks'
|
||||
import { useProvision, useSocket, useFetch } from 'client/hooks'
|
||||
import { PATH } from 'client/router/provision'
|
||||
import { set, mapUserInputs } from 'client/utils'
|
||||
|
||||
function ProvisionCreateForm () {
|
||||
const [uuid, setUuid] = useState(undefined)
|
||||
const history = useHistory()
|
||||
const { showError } = useGeneral()
|
||||
const { getProvision } = useSocket()
|
||||
const { getProviders, createProvision, provisionsTemplates, providers } = useProvision()
|
||||
|
||||
const { getProviders, createProvision, providers } = useProvision()
|
||||
|
||||
const { data, fetchRequest, loading, error } = useFetch(getProviders)
|
||||
|
||||
const { steps, defaultValues, resolvers } = Steps()
|
||||
@ -29,39 +29,17 @@ function ProvisionCreateForm () {
|
||||
resolver: yupResolver(resolvers())
|
||||
})
|
||||
|
||||
const redirectWithError = (name = '') => {
|
||||
showError({
|
||||
message: `
|
||||
Cannot found provision template (${name}),
|
||||
ask your cloud administrator`
|
||||
})
|
||||
|
||||
history.push(PATH.PROVIDERS.LIST)
|
||||
}
|
||||
|
||||
const getProvisionTemplateByDir = ({ provision, provider, name }) =>
|
||||
provisionsTemplates
|
||||
?.[provision]
|
||||
?.provisions
|
||||
?.[provider]
|
||||
?.find(provisionTemplate => provisionTemplate.name === name)
|
||||
|
||||
const onSubmit = formData => {
|
||||
const { template, provider, configuration, inputs } = formData
|
||||
const { name, description } = configuration
|
||||
const provisionTemplateSelected = template?.[0] ?? {}
|
||||
const providerIdSelected = provider?.[0]
|
||||
const providerName = providers?.find(({ ID }) => ID === providerIdSelected)?.NAME
|
||||
|
||||
const provisionTemplate = getProvisionTemplateByDir(provisionTemplateSelected)
|
||||
|
||||
if (!provisionTemplate) return redirectWithError(provisionTemplateSelected?.name)
|
||||
const providerName = provider?.[0]?.NAME
|
||||
|
||||
// update provider name if changed during form
|
||||
if (provisionTemplate.defaults?.provision?.provider_name) {
|
||||
set(provisionTemplate, 'defaults.provision.provider_name', providerName)
|
||||
} else if (provisionTemplate.hosts?.length > 0) {
|
||||
provisionTemplate.hosts.forEach(host => {
|
||||
if (provisionTemplateSelected.defaults?.provision?.provider_name) {
|
||||
set(provisionTemplateSelected, 'defaults.provision.provider_name', providerName)
|
||||
} else if (provisionTemplateSelected.hosts?.length > 0) {
|
||||
provisionTemplateSelected.hosts.forEach(host => {
|
||||
set(host, 'provision.provider_name', providerName)
|
||||
})
|
||||
}
|
||||
@ -69,10 +47,10 @@ function ProvisionCreateForm () {
|
||||
const parseInputs = mapUserInputs(inputs)
|
||||
|
||||
const formatData = {
|
||||
...provisionTemplate,
|
||||
...provisionTemplateSelected,
|
||||
name,
|
||||
description,
|
||||
inputs: provisionTemplate?.inputs
|
||||
inputs: provisionTemplateSelected?.inputs
|
||||
?.map(input => ({ ...input, value: `${parseInputs[input?.name]}` }))
|
||||
}
|
||||
|
||||
|
@ -35,8 +35,28 @@ export default function useProvision () {
|
||||
.catch(err => {
|
||||
dispatch(enqueueError(err ?? 'Error GET templates'))
|
||||
throw err
|
||||
}),
|
||||
[dispatch]
|
||||
})
|
||||
, [dispatch]
|
||||
)
|
||||
|
||||
const getProviderTemplateByDir = useCallback(
|
||||
({ provision, provider, name } = {}) =>
|
||||
provisionsTemplates
|
||||
?.[provision]
|
||||
?.providers
|
||||
?.[provider]
|
||||
?.find(provider => provider.name === name)
|
||||
, [provisionsTemplates]
|
||||
)
|
||||
|
||||
const getProvisionTemplateByDir = useCallback(
|
||||
({ provision, provider, name } = {}) =>
|
||||
provisionsTemplates
|
||||
?.[provision]
|
||||
?.provisions
|
||||
?.[provider]
|
||||
?.find(provisionTemplate => provisionTemplate.name === name)
|
||||
, [provisionsTemplates]
|
||||
)
|
||||
|
||||
// --------------------------------------------
|
||||
@ -54,8 +74,8 @@ export default function useProvision () {
|
||||
.catch(err => {
|
||||
dispatch(enqueueError(err ?? `Error GET (${id}) provider`))
|
||||
throw err
|
||||
}),
|
||||
[dispatch]
|
||||
})
|
||||
, [dispatch]
|
||||
)
|
||||
|
||||
const getProviders = useCallback(
|
||||
@ -69,8 +89,8 @@ export default function useProvision () {
|
||||
.catch(err => {
|
||||
dispatch(enqueueError(err ?? 'Error GET providers'))
|
||||
return err
|
||||
}),
|
||||
[dispatch]
|
||||
})
|
||||
, [dispatch]
|
||||
)
|
||||
|
||||
const createProvider = useCallback(
|
||||
@ -226,6 +246,8 @@ export default function useProvision () {
|
||||
|
||||
return {
|
||||
getProvisionsTemplates,
|
||||
getProviderTemplateByDir,
|
||||
getProvisionTemplateByDir,
|
||||
provisionsTemplates,
|
||||
|
||||
providers,
|
||||
|
10
src/fireedge/src/client/models/ProviderTemplate.js
Normal file
@ -0,0 +1,10 @@
|
||||
export const isValidProviderTemplate = ({ name, provider, plain = {}, connection }) => {
|
||||
const { provision_type: provisionType, location_key: locationKey } = plain
|
||||
|
||||
const locationKeyConnectionNotExists = connection[locationKey] === undefined
|
||||
|
||||
return (
|
||||
!(locationKey && locationKeyConnectionNotExists) ||
|
||||
[name, provisionType, provider].includes(undefined)
|
||||
)
|
||||
}
|
14
src/fireedge/src/client/models/ProvisionTemplate.js
Normal file
@ -0,0 +1,14 @@
|
||||
export const isValidProvisionTemplate = ({
|
||||
defaults,
|
||||
hosts,
|
||||
name,
|
||||
provider,
|
||||
provision_type: provisionType
|
||||
}) => {
|
||||
const providerName = defaults?.provision?.provider_name ?? hosts?.[0]?.provision.provider_name
|
||||
|
||||
return !(
|
||||
providerName === undefined ||
|
||||
[name, provisionType, provider].includes(undefined)
|
||||
)
|
||||
}
|
157
src/fireedge/src/client/types/provision.js
Normal file
@ -0,0 +1,157 @@
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
export const UserInput = PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
description: PropTypes.string,
|
||||
type: PropTypes.oneOf([
|
||||
'text',
|
||||
'text64',
|
||||
'password',
|
||||
'number',
|
||||
'number-float',
|
||||
'range',
|
||||
'range-float',
|
||||
'boolean',
|
||||
'list',
|
||||
'array',
|
||||
'list-multiple'
|
||||
]).isRequired,
|
||||
options: PropTypes.arrayOf(
|
||||
PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
||||
),
|
||||
min_value: PropTypes.number,
|
||||
max_value: PropTypes.number,
|
||||
default: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number
|
||||
])
|
||||
})
|
||||
|
||||
export const ProviderType = PropTypes.oneOf(['aws', 'packet'])
|
||||
|
||||
export const ProvisionType = PropTypes.oneOf([
|
||||
'hybrid+',
|
||||
'hybrid+_qemu',
|
||||
'hybrid+_firecracker'
|
||||
])
|
||||
|
||||
export const ProvisionHost = PropTypes.shape({
|
||||
im_mad: PropTypes.string.isRequired,
|
||||
vm_mad: PropTypes.string.isRequired,
|
||||
provision: PropTypes.shape({
|
||||
count: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number
|
||||
]),
|
||||
hostname: PropTypes.string
|
||||
})
|
||||
})
|
||||
|
||||
export const ProviderPlainInfo = PropTypes.shape({
|
||||
image: PropTypes.string,
|
||||
location_key: PropTypes.string,
|
||||
provision_type: ProvisionType.isRequired
|
||||
})
|
||||
|
||||
export const ProviderTemplate = PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
description: PropTypes.string,
|
||||
provider: ProviderType.isRequired,
|
||||
plain: ProviderPlainInfo.isRequired,
|
||||
connection: PropTypes.objectOf(PropTypes.string),
|
||||
inputs: PropTypes.arrayOf(UserInput)
|
||||
})
|
||||
|
||||
export const ProvisionTemplate = PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
description: PropTypes.string,
|
||||
provider: ProviderType.isRequired,
|
||||
provision_type: ProvisionType.isRequired,
|
||||
defaults: PropTypes.shape({
|
||||
provision: PropTypes.shape({
|
||||
provider_name: PropTypes.string
|
||||
})
|
||||
}).isRequired,
|
||||
hosts: PropTypes.arrayOf(ProvisionHost),
|
||||
inputs: PropTypes.arrayOf(UserInput)
|
||||
})
|
||||
|
||||
export const Provider = PropTypes.shape({
|
||||
ID: PropTypes.string.isRequired,
|
||||
UID: PropTypes.string.isRequired,
|
||||
GID: PropTypes.string.isRequired,
|
||||
UNAME: PropTypes.string.isRequired,
|
||||
GNAME: PropTypes.string.isRequired,
|
||||
NAME: PropTypes.string.isRequired,
|
||||
TYPE: PropTypes.string.isRequired,
|
||||
PERMISSIONS: PropTypes.shape({
|
||||
OWNER_U: PropTypes.oneOf(['0', '1']).isRequired,
|
||||
OWNER_M: PropTypes.oneOf(['0', '1']).isRequired,
|
||||
OWNER_A: PropTypes.oneOf(['0', '1']).isRequired,
|
||||
GROUP_U: PropTypes.oneOf(['0', '1']).isRequired,
|
||||
GROUP_M: PropTypes.oneOf(['0', '1']).isRequired,
|
||||
GROUP_A: PropTypes.oneOf(['0', '1']).isRequired,
|
||||
OTHER_U: PropTypes.oneOf(['0', '1']).isRequired,
|
||||
OTHER_M: PropTypes.oneOf(['0', '1']).isRequired,
|
||||
OTHER_A: PropTypes.oneOf(['0', '1']).isRequired
|
||||
}).isRequired,
|
||||
TEMPLATE: PropTypes.shape({
|
||||
PLAIN: ProviderPlainInfo,
|
||||
PROVISION_BODY: PropTypes.oneOfType([
|
||||
// encrypted
|
||||
PropTypes.string,
|
||||
PropTypes.shape({
|
||||
provider: ProviderType,
|
||||
connection: PropTypes.objectOf(PropTypes.string),
|
||||
registration_time: PropTypes.number,
|
||||
description: PropTypes.string,
|
||||
inputs: PropTypes.arrayOf(UserInput)
|
||||
}).isRequired
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
const ProvisionInfrastructure = PropTypes.shape({
|
||||
id: PropTypes.number.isRequired,
|
||||
name: PropTypes.string.isRequired
|
||||
})
|
||||
|
||||
export const Provision = PropTypes.shape({
|
||||
ID: PropTypes.string.isRequired,
|
||||
UID: PropTypes.string.isRequired,
|
||||
GID: PropTypes.string.isRequired,
|
||||
UNAME: PropTypes.string.isRequired,
|
||||
GNAME: PropTypes.string.isRequired,
|
||||
NAME: PropTypes.string.isRequired,
|
||||
TYPE: PropTypes.string.isRequired,
|
||||
PERMISSIONS: PropTypes.shape({
|
||||
OWNER_U: PropTypes.oneOf(['0', '1']).isRequired,
|
||||
OWNER_M: PropTypes.oneOf(['0', '1']).isRequired,
|
||||
OWNER_A: PropTypes.oneOf(['0', '1']).isRequired,
|
||||
GROUP_U: PropTypes.oneOf(['0', '1']).isRequired,
|
||||
GROUP_M: PropTypes.oneOf(['0', '1']).isRequired,
|
||||
GROUP_A: PropTypes.oneOf(['0', '1']).isRequired,
|
||||
OTHER_U: PropTypes.oneOf(['0', '1']).isRequired,
|
||||
OTHER_M: PropTypes.oneOf(['0', '1']).isRequired,
|
||||
OTHER_A: PropTypes.oneOf(['0', '1']).isRequired
|
||||
}).isRequired,
|
||||
TEMPLATE: PropTypes.shape({
|
||||
BODY: PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
description: PropTypes.string,
|
||||
start_time: PropTypes.number,
|
||||
state: PropTypes.number,
|
||||
provider: PropTypes.string,
|
||||
provision: PropTypes.shape({
|
||||
infrastructure: PropTypes.shape({
|
||||
clusters: PropTypes.arrayOf(ProvisionInfrastructure),
|
||||
datastores: PropTypes.arrayOf(ProvisionInfrastructure),
|
||||
networks: PropTypes.arrayOf(ProvisionInfrastructure)
|
||||
}),
|
||||
resource: PropTypes.object
|
||||
}),
|
||||
image: PropTypes.string,
|
||||
provision_type: ProvisionType
|
||||
}).isRequired
|
||||
})
|
||||
})
|