mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-22 18:50:08 +03:00
parent
3250528bef
commit
ce2d741c15
@ -43,6 +43,7 @@ import {
|
||||
} from 'iconoir-react'
|
||||
|
||||
import loadable from '@loadable/component'
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
const VirtualMachines = loadable(() => import('client/containers/VirtualMachines'), { ssr: false })
|
||||
const VirtualMachineDetail = loadable(() => import('client/containers/VirtualMachines/Detail'), { ssr: false })
|
||||
@ -57,9 +58,9 @@ const CreateVmTemplate = loadable(() => import('client/containers/VmTemplates/Cr
|
||||
|
||||
const Datastores = loadable(() => import('client/containers/Datastores'), { ssr: false })
|
||||
const Images = loadable(() => import('client/containers/Images'), { ssr: false })
|
||||
// const Files = loadable(() => import('client/containers/Files'), { ssr: false })
|
||||
const Marketplaces = loadable(() => import('client/containers/Marketplaces'), { ssr: false })
|
||||
const MarketplaceApps = loadable(() => import('client/containers/MarketplaceApps'), { ssr: false })
|
||||
const CreateMarketplaceApp = loadable(() => import('client/containers/MarketplaceApps/Create'), { ssr: false })
|
||||
|
||||
const VirtualNetworks = loadable(() => import('client/containers/VirtualNetworks'), { ssr: false })
|
||||
const VNetworkTemplates = loadable(() => import('client/containers/VNetworkTemplates'), { ssr: false })
|
||||
@ -82,79 +83,76 @@ const GroupDetail = loadable(() => import('client/containers/Groups/Detail'), {
|
||||
export const PATH = {
|
||||
INSTANCE: {
|
||||
VMS: {
|
||||
LIST: '/vm',
|
||||
DETAIL: '/vm/:id'
|
||||
LIST: `/${RESOURCE_NAMES.VM}`,
|
||||
DETAIL: `/${RESOURCE_NAMES.VM}/:id`
|
||||
},
|
||||
VROUTERS: {
|
||||
LIST: '/virtual-router'
|
||||
LIST: `/${RESOURCE_NAMES.V_ROUTER}`
|
||||
}
|
||||
},
|
||||
TEMPLATE: {
|
||||
VMS: {
|
||||
LIST: '/vm-template',
|
||||
DETAIL: '/vm-template/:id',
|
||||
INSTANTIATE: '/vm-template/instantiate',
|
||||
CREATE: '/vm-template/create'
|
||||
LIST: `/${RESOURCE_NAMES.VM_TEMPLATE}`,
|
||||
DETAIL: `/${RESOURCE_NAMES.VM_TEMPLATE}/:id`,
|
||||
INSTANTIATE: `/${RESOURCE_NAMES.VM_TEMPLATE}/instantiate`,
|
||||
CREATE: `/${RESOURCE_NAMES.VM_TEMPLATE}/create`
|
||||
}
|
||||
},
|
||||
STORAGE: {
|
||||
DATASTORES: {
|
||||
LIST: '/datastore',
|
||||
DETAIL: '/datastore/:id'
|
||||
LIST: `/${RESOURCE_NAMES.DATASTORE}`,
|
||||
DETAIL: `/${RESOURCE_NAMES.DATASTORE}/:id`
|
||||
},
|
||||
IMAGES: {
|
||||
LIST: '/image',
|
||||
DETAIL: '/image/:id'
|
||||
},
|
||||
FILES: {
|
||||
LIST: '/file',
|
||||
DETAIL: '/file/:id'
|
||||
LIST: `/${RESOURCE_NAMES.IMAGE}`,
|
||||
DETAIL: `/${RESOURCE_NAMES.IMAGE}/:id`
|
||||
},
|
||||
MARKETPLACES: {
|
||||
LIST: '/marketplace',
|
||||
DETAIL: '/marketplace/:id'
|
||||
LIST: `/${RESOURCE_NAMES.MARKETPLACE}`,
|
||||
DETAIL: `/${RESOURCE_NAMES.MARKETPLACE}/:id`
|
||||
},
|
||||
MARKETPLACE_APPS: {
|
||||
LIST: '/marketplace-app',
|
||||
DETAIL: '/marketplace-app/:id'
|
||||
LIST: `/${RESOURCE_NAMES.APP}`,
|
||||
DETAIL: `/${RESOURCE_NAMES.APP}/:id`,
|
||||
CREATE: `/${RESOURCE_NAMES.APP}/create`
|
||||
}
|
||||
},
|
||||
NETWORK: {
|
||||
VNETS: {
|
||||
LIST: '/virtual-network',
|
||||
DETAIL: '/virtual-network/:id'
|
||||
LIST: `/${RESOURCE_NAMES.VNET}`,
|
||||
DETAIL: `/${RESOURCE_NAMES.VNET}/:id`
|
||||
},
|
||||
VN_TEMPLATES: {
|
||||
LIST: '/network-template',
|
||||
DETAIL: '/network-template/:id'
|
||||
LIST: `/${RESOURCE_NAMES.VN_TEMPLATE}`,
|
||||
DETAIL: `/${RESOURCE_NAMES.VN_TEMPLATE}/:id`
|
||||
},
|
||||
SEC_GROUPS: {
|
||||
LIST: '/security-group',
|
||||
DETAIL: '/security-group/:id'
|
||||
LIST: `/${RESOURCE_NAMES.SEC_GROUP}`,
|
||||
DETAIL: `/${RESOURCE_NAMES.SEC_GROUP}/:id`
|
||||
}
|
||||
},
|
||||
INFRASTRUCTURE: {
|
||||
CLUSTERS: {
|
||||
LIST: '/cluster',
|
||||
DETAIL: '/cluster/:id'
|
||||
LIST: `/${RESOURCE_NAMES.CLUSTER}`,
|
||||
DETAIL: `/${RESOURCE_NAMES.CLUSTER}/:id`
|
||||
},
|
||||
HOSTS: {
|
||||
LIST: '/host',
|
||||
DETAIL: '/host/:id'
|
||||
LIST: `/${RESOURCE_NAMES.HOST}`,
|
||||
DETAIL: `/${RESOURCE_NAMES.HOST}/:id`
|
||||
},
|
||||
ZONES: {
|
||||
LIST: '/zone',
|
||||
DETAIL: '/zone/:id'
|
||||
LIST: `/${RESOURCE_NAMES.ZONE}`,
|
||||
DETAIL: `/${RESOURCE_NAMES.ZONE}/:id`
|
||||
}
|
||||
},
|
||||
SYSTEM: {
|
||||
USERS: {
|
||||
LIST: '/user',
|
||||
DETAIL: '/user/:id'
|
||||
LIST: `/${RESOURCE_NAMES.USER}`,
|
||||
DETAIL: `/${RESOURCE_NAMES.USER}/:id`
|
||||
},
|
||||
GROUPS: {
|
||||
LIST: '/group',
|
||||
DETAIL: '/group/:id'
|
||||
LIST: `/${RESOURCE_NAMES.GROUP}`,
|
||||
DETAIL: `/${RESOURCE_NAMES.GROUP}/:id`
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -242,6 +240,11 @@ const ENDPOINTS = [
|
||||
sidebar: true,
|
||||
icon: MarketplaceAppIcon,
|
||||
Component: MarketplaceApps
|
||||
},
|
||||
{
|
||||
label: 'Create Marketplace App',
|
||||
path: PATH.STORAGE.MARKETPLACE_APPS.CREATE,
|
||||
Component: CreateMarketplaceApp
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo } from 'react'
|
||||
import { memo, useEffect, useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useController } from 'react-hook-form'
|
||||
|
||||
@ -23,6 +23,10 @@ import { generateKey } from 'client/utils'
|
||||
|
||||
const defaultGetRowId = item => typeof item === 'object' ? item?.id ?? item?.ID : item
|
||||
|
||||
const getSelectedRowIds = value => [value ?? []]
|
||||
.flat()
|
||||
.reduce((initialSelected, rowId) => ({ ...initialSelected, [rowId]: true }), {})
|
||||
|
||||
const TableController = memo(
|
||||
({
|
||||
control,
|
||||
@ -33,15 +37,23 @@ const TableController = memo(
|
||||
Table,
|
||||
singleSelect = true,
|
||||
getRowId = defaultGetRowId,
|
||||
formContext = {}
|
||||
formContext = {},
|
||||
fieldProps: { initialState, ...fieldProps } = {}
|
||||
}) => {
|
||||
const { clearErrors } = formContext
|
||||
|
||||
const {
|
||||
field: { onChange },
|
||||
field: { value, onChange },
|
||||
fieldState: { error }
|
||||
} = useController({ name, control })
|
||||
|
||||
const [initialRows, setInitialRows] = useState(() => getSelectedRowIds(value))
|
||||
|
||||
useEffect(() => {
|
||||
onChange(singleSelect ? undefined : [])
|
||||
setInitialRows({})
|
||||
}, [Table])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Legend title={label} tooltip={tooltip} />
|
||||
@ -58,12 +70,14 @@ const TableController = memo(
|
||||
onlyGlobalSearch
|
||||
onlyGlobalSelectedRows
|
||||
getRowId={getRowId}
|
||||
initialState={{ ...initialState, selectedRowIds: initialRows }}
|
||||
onSelectedRowsChange={rows => {
|
||||
const rowValues = rows?.map(({ original }) => getRowId(original))
|
||||
|
||||
onChange(singleSelect ? rowValues?.[0] : rowValues)
|
||||
clearErrors(name)
|
||||
}}
|
||||
{...fieldProps}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
@ -71,6 +85,7 @@ const TableController = memo(
|
||||
(prevProps, nextProps) =>
|
||||
prevProps.error === nextProps.error &&
|
||||
prevProps.label === nextProps.label &&
|
||||
prevProps.Table === nextProps.Table &&
|
||||
prevProps.tooltip === nextProps.tooltip
|
||||
)
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { createElement, useMemo } from 'react'
|
||||
import { createElement, useMemo, isValidElement } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { FormControl, Grid } from '@mui/material'
|
||||
@ -27,7 +27,6 @@ import { INPUT_TYPES } from 'client/constants'
|
||||
const NOT_DEPEND_ATTRIBUTES = [
|
||||
'watcher',
|
||||
'transform',
|
||||
'Table',
|
||||
'getRowId',
|
||||
'renderValue'
|
||||
]
|
||||
@ -90,9 +89,11 @@ const FormWithSchema = ({ id, cy, fields, rootProps, className, legend, legendTo
|
||||
const [key, value] = attribute
|
||||
const isNotDependAttribute = NOT_DEPEND_ATTRIBUTES.includes(key)
|
||||
|
||||
const finalValue = typeof value === 'function' && !isNotDependAttribute
|
||||
? value(valueOfDependField, formContext)
|
||||
: value
|
||||
const finalValue = (
|
||||
typeof value === 'function' &&
|
||||
!isNotDependAttribute &&
|
||||
!isValidElement(value())
|
||||
) ? value(valueOfDependField, formContext) : value
|
||||
|
||||
return { ...field, [key]: finalValue }
|
||||
}, {})
|
||||
|
@ -0,0 +1,53 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
import { FIELDS, SCHEMA } from 'client/components/Forms/MarketplaceApp/CreateForm/Steps/BasicConfiguration/schema'
|
||||
import { Step } from 'client/utils'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
export const STEP_ID = 'configuration'
|
||||
|
||||
const Content = () => {
|
||||
return (
|
||||
<FormWithSchema
|
||||
cy={'create-marketplace-app.configuration'}
|
||||
fields={FIELDS}
|
||||
id={STEP_ID}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Step to configure the marketplace app.
|
||||
*
|
||||
* @returns {Step} Configuration step
|
||||
*/
|
||||
const ConfigurationStep = () => ({
|
||||
id: STEP_ID,
|
||||
label: T.Configuration,
|
||||
resolver: SCHEMA,
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: Content
|
||||
})
|
||||
|
||||
Content.propTypes = {
|
||||
data: PropTypes.any,
|
||||
setFormData: PropTypes.func
|
||||
}
|
||||
|
||||
export default ConfigurationStep
|
@ -0,0 +1,113 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { string, boolean, object, ObjectSchema } from 'yup'
|
||||
import { makeStyles } from '@mui/styles'
|
||||
|
||||
import { useSystem, useDatastore } from 'client/features/One'
|
||||
import { ImagesTable, VmsTable, VmTemplatesTable } from 'client/components/Tables'
|
||||
import { Field, arrayToOptions, getValidationFromFields, sentenceCase } from 'client/utils'
|
||||
import { isMarketExportSupport } from 'client/models/Datastore'
|
||||
import { T, INPUT_TYPES, STATES, RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
const TYPES = {
|
||||
IMAGE: RESOURCE_NAMES.IMAGE.toUpperCase(),
|
||||
VM: RESOURCE_NAMES.VM.toUpperCase(),
|
||||
VM_TEMPLATE: RESOURCE_NAMES.VM_TEMPLATE.toUpperCase()
|
||||
}
|
||||
|
||||
const useTableStyles = makeStyles({
|
||||
body: { gridTemplateColumns: 'repeat(auto-fill, minmax(400px, 1fr))' }
|
||||
})
|
||||
|
||||
/** @type {Field} Type field */
|
||||
const TYPE = {
|
||||
name: 'type',
|
||||
type: INPUT_TYPES.TOGGLE,
|
||||
values: arrayToOptions(Object.values(TYPES), {
|
||||
addEmpty: false,
|
||||
getText: type => sentenceCase(type).toUpperCase()
|
||||
}),
|
||||
validation: string()
|
||||
.trim()
|
||||
.required()
|
||||
.uppercase()
|
||||
.default(() => TYPES.IMAGE),
|
||||
grid: { md: 12 }
|
||||
}
|
||||
|
||||
/** @type {Field} App name field */
|
||||
const NAME = {
|
||||
name: 'name',
|
||||
label: T.Name,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string()
|
||||
.trim()
|
||||
.required()
|
||||
.default(() => undefined),
|
||||
grid: { md: 12, lg: 6 }
|
||||
}
|
||||
|
||||
/** @type {Field} Import image/templates field */
|
||||
const IMPORT = {
|
||||
name: 'image',
|
||||
label: T.DontAssociateApp,
|
||||
type: INPUT_TYPES.SWITCH,
|
||||
validation: boolean().default(() => false),
|
||||
grid: { md: 12, lg: 6 }
|
||||
}
|
||||
|
||||
/** @type {Field} Resource table field */
|
||||
const RES_TABLE = {
|
||||
name: 'id',
|
||||
type: INPUT_TYPES.TABLE,
|
||||
dependOf: 'type',
|
||||
label: type => `Select the ${
|
||||
sentenceCase(type) ?? 'resource'} to create the App`,
|
||||
Table: type => ({
|
||||
[TYPES.IMAGE]: ImagesTable,
|
||||
[TYPES.VM]: VmsTable,
|
||||
[TYPES.VM_TEMPLATE]: VmTemplatesTable
|
||||
})[type],
|
||||
validation: string()
|
||||
.trim()
|
||||
.required()
|
||||
.default(() => undefined),
|
||||
grid: { md: 12 },
|
||||
fieldProps: type => {
|
||||
const { config: oneConfig } = useSystem()
|
||||
const datastores = useDatastore()
|
||||
const classes = useTableStyles()
|
||||
|
||||
return {
|
||||
[TYPES.IMAGE]: {
|
||||
filter: image => {
|
||||
const datastore = datastores?.find(ds => ds?.ID === image?.DATASTORE_ID)
|
||||
return isMarketExportSupport(datastore, oneConfig)
|
||||
}
|
||||
},
|
||||
[TYPES.VM]: {
|
||||
initialState: { filters: [{ id: 'STATE', value: STATES.POWEROFF }] }
|
||||
},
|
||||
[TYPES.VM_TEMPLATE]: { classes }
|
||||
}[type]
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {Field[]} - List of fields */
|
||||
export const FIELDS = [TYPE, NAME, IMPORT, RES_TABLE]
|
||||
|
||||
/** @type {ObjectSchema} - Schema form */
|
||||
export const SCHEMA = object(getValidationFromFields(FIELDS))
|
@ -0,0 +1,74 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import PropTypes from 'prop-types'
|
||||
import { useFormContext } from 'react-hook-form'
|
||||
|
||||
import { useSystem } from 'client/features/One'
|
||||
import { MarketplacesTable } from 'client/components/Tables'
|
||||
import { SCHEMA } from 'client/components/Forms/MarketplaceApp/CreateForm/Steps/MarketplacesTable/schema'
|
||||
import { Step } from 'client/utils'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
export const STEP_ID = 'marketplace'
|
||||
|
||||
const Content = ({ data }) => {
|
||||
const { NAME } = data?.[0] ?? {}
|
||||
const { setValue } = useFormContext()
|
||||
const { config: oneConfig } = useSystem()
|
||||
|
||||
const handleSelectedRows = rows => {
|
||||
const { original = {} } = rows?.[0] ?? {}
|
||||
|
||||
setValue(STEP_ID, original.ID !== undefined ? [original] : [])
|
||||
}
|
||||
|
||||
return (
|
||||
<MarketplacesTable
|
||||
singleSelect
|
||||
onlyGlobalSearch
|
||||
onlyGlobalSelectedRows
|
||||
getRowId={market => String(market.NAME)}
|
||||
filter={market =>
|
||||
oneConfig?.FEDERATION?.ZONE_ID === market.ZONE_ID &&
|
||||
oneConfig?.MARKET_MAD_CONF?.some(marketMad => (
|
||||
marketMad?.APP_ACTIONS?.includes('create') &&
|
||||
`${marketMad?.NAME}`.toUpperCase() === `${market?.MARKET_MAD}`.toUpperCase()
|
||||
))
|
||||
}
|
||||
initialState={{ selectedRowIds: { [NAME]: true } }}
|
||||
onSelectedRowsChange={handleSelectedRows}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Step to select the Marketplace.
|
||||
*
|
||||
* @type {Step} Marketplace step
|
||||
*/
|
||||
const MarketplaceStep = () => ({
|
||||
id: STEP_ID,
|
||||
label: T.SelectMarketplace,
|
||||
resolver: SCHEMA,
|
||||
content: Content
|
||||
})
|
||||
|
||||
Content.propTypes = {
|
||||
data: PropTypes.any,
|
||||
setFormData: PropTypes.func
|
||||
}
|
||||
|
||||
export default MarketplaceStep
|
@ -0,0 +1,24 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { array, object, ArraySchema } from 'yup'
|
||||
|
||||
/** @type {ArraySchema} Marketplace table schema */
|
||||
export const SCHEMA = array(object())
|
||||
.min(1)
|
||||
.max(1)
|
||||
.required()
|
||||
.ensure()
|
||||
.default(() => [])
|
@ -0,0 +1,34 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import BasicConfiguration, { STEP_ID as BASIC_ID } from 'client/components/Forms/MarketplaceApp/CreateForm/Steps/BasicConfiguration'
|
||||
import MarketplacesTable, { STEP_ID as MARKET_ID } from 'client/components/Forms/MarketplaceApp/CreateForm/Steps/MarketplacesTable'
|
||||
import { createSteps } from 'client/utils'
|
||||
|
||||
const Steps = createSteps(
|
||||
[BasicConfiguration, MarketplacesTable],
|
||||
{
|
||||
transformInitialValue: (initialValues, schema) => {
|
||||
return schema.cast({ [BASIC_ID]: initialValues }, { stripUnknown: true })
|
||||
},
|
||||
transformBeforeSubmit: formData => {
|
||||
const { [BASIC_ID]: configuration, [MARKET_ID]: [market] = [] } = formData
|
||||
|
||||
return { market: market?.ID, ...configuration }
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export default Steps
|
@ -0,0 +1,65 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { useEffect, useMemo, JSXElementConstructor } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { useForm, FormProvider } from 'react-hook-form'
|
||||
import { yupResolver } from '@hookform/resolvers/yup'
|
||||
|
||||
import { useDatastoreApi } from 'client/features/One'
|
||||
import FormStepper from 'client/components/FormStepper'
|
||||
import Steps from 'client/components/Forms/MarketplaceApp/CreateForm/Steps'
|
||||
|
||||
/**
|
||||
* Form to create a Marketplace App.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {string} props.initialValues - Initial values
|
||||
* @param {function():object} props.onSubmit - Handle submit function
|
||||
* @returns {JSXElementConstructor} Form component
|
||||
*/
|
||||
const CreateForm = ({ initialValues, onSubmit }) => {
|
||||
const { getDatastores } = useDatastoreApi()
|
||||
const stepsForm = useMemo(() => Steps(initialValues, initialValues), [])
|
||||
const { steps, defaultValues, resolver, transformBeforeSubmit } = stepsForm
|
||||
|
||||
const methods = useForm({
|
||||
mode: 'onSubmit',
|
||||
defaultValues,
|
||||
resolver: yupResolver(resolver?.())
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
getDatastores()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<FormProvider {...methods}>
|
||||
<FormStepper
|
||||
steps={steps}
|
||||
schema={resolver}
|
||||
onSubmit={data => onSubmit(transformBeforeSubmit?.(data) ?? data)}
|
||||
/>
|
||||
</FormProvider>
|
||||
)
|
||||
}
|
||||
|
||||
CreateForm.propTypes = {
|
||||
initialValues: PropTypes.object,
|
||||
onSubmit: PropTypes.func
|
||||
}
|
||||
|
||||
export default CreateForm
|
@ -13,8 +13,10 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import CreateForm from 'client/components/Forms/MarketplaceApp/CreateForm'
|
||||
import ExportForm from 'client/components/Forms/MarketplaceApp/ExportForm'
|
||||
|
||||
export {
|
||||
CreateForm,
|
||||
ExportForm
|
||||
}
|
||||
|
@ -15,9 +15,10 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useMemo } from 'react'
|
||||
// import { useHistory } from 'react-router-dom'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import {
|
||||
RefreshDouble,
|
||||
AddSquare,
|
||||
CloudDownload
|
||||
} from 'iconoir-react'
|
||||
|
||||
@ -26,12 +27,10 @@ import { useGeneralApi } from 'client/features/General'
|
||||
import { useMarketplaceAppApi } from 'client/features/One'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
|
||||
import {
|
||||
ExportForm
|
||||
} from 'client/components/Forms/MarketplaceApp'
|
||||
import { ExportForm } from 'client/components/Forms/MarketplaceApp'
|
||||
|
||||
import { createActions } from 'client/components/Tables/Enhanced/Utils'
|
||||
// import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
import { T, MARKETPLACE_APP_ACTIONS } from 'client/constants'
|
||||
|
||||
const MessageToConfirmAction = rows => {
|
||||
@ -53,6 +52,7 @@ const MessageToConfirmAction = rows => {
|
||||
MessageToConfirmAction.displayName = 'MessageToConfirmAction'
|
||||
|
||||
const Actions = () => {
|
||||
const history = useHistory()
|
||||
const { view, getResourceView } = useAuth()
|
||||
const { enqueueSuccess } = useGeneralApi()
|
||||
const { getMarketplaceApps, exportApp } = useMarketplaceAppApi()
|
||||
@ -68,6 +68,14 @@ const Actions = () => {
|
||||
await getMarketplaceApps()
|
||||
}
|
||||
},
|
||||
{
|
||||
accessor: MARKETPLACE_APP_ACTIONS.CREATE_DIALOG,
|
||||
tooltip: T.CreateMarketApp,
|
||||
icon: AddSquare,
|
||||
action: () => {
|
||||
history.push(PATH.STORAGE.MARKETPLACE_APPS.CREATE)
|
||||
}
|
||||
},
|
||||
{
|
||||
accessor: MARKETPLACE_APP_ACTIONS.EXPORT,
|
||||
tooltip: T.ImportIntoDatastore,
|
||||
|
@ -36,7 +36,17 @@ export default [
|
||||
}),
|
||||
filter: 'includesValue'
|
||||
},
|
||||
{ Header: 'Market', accessor: 'MARKET_MAD' },
|
||||
{
|
||||
Header: 'Market',
|
||||
accessor: 'MARKET_MAD',
|
||||
disableFilters: false,
|
||||
Filter: ({ column }) => CategoryFilter({
|
||||
column,
|
||||
multiple: true,
|
||||
title: 'Market mad'
|
||||
}),
|
||||
filter: 'includesValue'
|
||||
},
|
||||
{ Header: 'Total Capacity', accessor: 'TOTAL_MB' },
|
||||
{ Header: 'Free Capacity', accessor: 'USED_MB' },
|
||||
{ Header: 'Zone ID', accessor: 'ZONE_ID' },
|
||||
|
@ -15,21 +15,27 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useMemo, useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useFetch } from 'client/hooks'
|
||||
import { useMarketplace, useMarketplaceApi } from 'client/features/One'
|
||||
|
||||
import { SkeletonTable, EnhancedTable } from 'client/components/Tables'
|
||||
import { createColumns } from 'client/components/Tables/Enhanced/Utils'
|
||||
import MarketplaceColumns from 'client/components/Tables/Marketplaces/columns'
|
||||
import MarketplaceRow from 'client/components/Tables/Marketplaces/row'
|
||||
|
||||
const MarketplacesTable = () => {
|
||||
const columns = useMemo(() => MarketplaceColumns, [])
|
||||
const MarketplacesTable = ({ filter, ...props }) => {
|
||||
const { view, getResourceView, filterPool } = useAuth()
|
||||
|
||||
const columns = useMemo(() => createColumns({
|
||||
filters: getResourceView('MARKETPLACE')?.filters,
|
||||
columns: MarketplaceColumns
|
||||
}), [view])
|
||||
|
||||
const marketplaces = useMarketplace()
|
||||
const { getMarketplaces } = useMarketplaceApi()
|
||||
const { filterPool } = useAuth()
|
||||
|
||||
const { status, fetchRequest, loading, reloading, STATUS } = useFetch(getMarketplaces)
|
||||
const { INIT, PENDING } = STATUS
|
||||
@ -43,12 +49,23 @@ const MarketplacesTable = () => {
|
||||
return (
|
||||
<EnhancedTable
|
||||
columns={columns}
|
||||
data={marketplaces}
|
||||
data={typeof filter === 'function'
|
||||
? marketplaces?.filter(filter)
|
||||
: marketplaces
|
||||
}
|
||||
isLoading={loading || reloading}
|
||||
getRowId={row => String(row.ID)}
|
||||
RowComponent={MarketplaceRow}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
MarketplacesTable.propTypes = {
|
||||
filter: PropTypes.func,
|
||||
...EnhancedTable.propTypes
|
||||
}
|
||||
|
||||
MarketplacesTable.displayName = 'MarketplacesTable'
|
||||
|
||||
export default MarketplacesTable
|
||||
|
@ -34,7 +34,13 @@ import { Tr, Translate } from 'client/components/HOC'
|
||||
import { CloneForm } from 'client/components/Forms/VmTemplate'
|
||||
import { createActions } from 'client/components/Tables/Enhanced/Utils'
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
import { T, VM_TEMPLATE_ACTIONS, MARKETPLACE_APP_ACTIONS } from 'client/constants'
|
||||
|
||||
import {
|
||||
T,
|
||||
VM_TEMPLATE_ACTIONS,
|
||||
MARKETPLACE_APP_ACTIONS,
|
||||
RESOURCE_NAMES
|
||||
} from 'client/constants'
|
||||
|
||||
const MessageToConfirmAction = rows => {
|
||||
const names = rows?.map?.(({ original }) => original?.NAME)
|
||||
@ -109,6 +115,18 @@ const Actions = () => {
|
||||
history.push(path, template)
|
||||
}
|
||||
},
|
||||
{
|
||||
accessor: VM_TEMPLATE_ACTIONS.CREATE_APP_DIALOG,
|
||||
tooltip: T.CreateMarketApp,
|
||||
selected: { max: 1 },
|
||||
icon: Cart,
|
||||
action: rows => {
|
||||
const template = rows?.[0]?.original ?? {}
|
||||
const path = PATH.STORAGE.MARKETPLACE_APPS.CREATE
|
||||
|
||||
history.push(path, [RESOURCE_NAMES.VM_TEMPLATE, template])
|
||||
}
|
||||
},
|
||||
{
|
||||
accessor: VM_TEMPLATE_ACTIONS.UPDATE_DIALOG,
|
||||
label: T.Update,
|
||||
|
@ -46,7 +46,7 @@ import {
|
||||
import { createActions } from 'client/components/Tables/Enhanced/Utils'
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
import { getLastHistory, isAvailableAction } from 'client/models/VirtualMachine'
|
||||
import { T, VM_ACTIONS, MARKETPLACE_APP_ACTIONS } from 'client/constants'
|
||||
import { T, VM_ACTIONS, RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
const isDisabled = action => rows =>
|
||||
isAvailableAction(action)(rows, ({ values }) => values?.STATE)
|
||||
@ -153,6 +153,19 @@ const Actions = () => {
|
||||
ids?.length > 1 && (await Promise.all(ids.map(id => getVm(id))))
|
||||
}
|
||||
},
|
||||
{
|
||||
accessor: VM_ACTIONS.CREATE_APP_DIALOG,
|
||||
disabled: isDisabled(VM_ACTIONS.CREATE_APP_DIALOG),
|
||||
tooltip: T.CreateMarketApp,
|
||||
selected: { max: 1 },
|
||||
icon: Cart,
|
||||
action: rows => {
|
||||
const vm = rows?.[0]?.original ?? {}
|
||||
const path = PATH.STORAGE.MARKETPLACE_APPS.CREATE
|
||||
|
||||
history.push(path, [RESOURCE_NAMES.VM, vm])
|
||||
}
|
||||
},
|
||||
{
|
||||
accessor: VM_ACTIONS.SAVE_AS_TEMPLATE,
|
||||
disabled: isDisabled(VM_ACTIONS.SAVE_AS_TEMPLATE),
|
||||
@ -518,23 +531,7 @@ const Actions = () => {
|
||||
]
|
||||
}), [view])
|
||||
|
||||
const marketplaceAppActions = useMemo(() => createActions({
|
||||
filters: getResourceView('MARKETPLACE-APP')?.actions,
|
||||
actions: [
|
||||
{
|
||||
accessor: MARKETPLACE_APP_ACTIONS.CREATE_DIALOG,
|
||||
tooltip: T.CreateMarketApp,
|
||||
icon: Cart,
|
||||
selected: { max: 1 },
|
||||
disabled: true,
|
||||
action: rows => {
|
||||
// TODO: go to Marketplace App CREATE form
|
||||
}
|
||||
}
|
||||
]
|
||||
}), [view])
|
||||
|
||||
return [...vmActions, ...marketplaceAppActions]
|
||||
return vmActions
|
||||
}
|
||||
|
||||
export default Actions
|
||||
|
@ -87,6 +87,25 @@ export const SOCKETS = {
|
||||
PROVISION: 'provision'
|
||||
}
|
||||
|
||||
/** @enum {string} Names of resource */
|
||||
export const RESOURCE_NAMES = {
|
||||
APP: 'marketplace-app',
|
||||
CLUSTER: 'cluster',
|
||||
DATASTORE: 'datastore',
|
||||
GROUP: 'group',
|
||||
HOST: 'host',
|
||||
IMAGE: 'image',
|
||||
MARKETPLACE: 'marketplace',
|
||||
SEC_GROUP: 'security-group',
|
||||
USER: 'user',
|
||||
V_ROUTER: 'virtual-router',
|
||||
VM_TEMPLATE: 'vm-template',
|
||||
VM: 'vm',
|
||||
VN_TEMPLATE: 'network-template',
|
||||
VNET: 'virtual-network',
|
||||
ZONE: 'zone'
|
||||
}
|
||||
|
||||
export * as T from 'client/constants/translates'
|
||||
export * as ACTIONS from 'client/constants/actions'
|
||||
export * as STATES from 'client/constants/states'
|
||||
|
@ -106,6 +106,7 @@ module.exports = {
|
||||
SelectRequest: 'Select request',
|
||||
SelectVmTemplate: 'Select a VM Template',
|
||||
SelectDatastore: 'Select a Datastore to store the resource',
|
||||
SelectMarketplace: 'Select Marketplace',
|
||||
Share: 'Share',
|
||||
Show: 'Show',
|
||||
ShowAll: 'Show all',
|
||||
|
@ -434,6 +434,7 @@ export const VM_LCM_STATES = [
|
||||
export const VM_ACTIONS = {
|
||||
REFRESH: 'refresh',
|
||||
CREATE_DIALOG: 'create_dialog',
|
||||
CREATE_APP_DIALOG: 'create_app_dialog',
|
||||
DEPLOY: 'deploy',
|
||||
HOLD: 'hold',
|
||||
LOCK: 'lock',
|
||||
@ -515,6 +516,7 @@ export const VM_ACTIONS_BY_STATE = {
|
||||
STATES.UNDEPLOYED,
|
||||
STATES.UNKNOWN
|
||||
],
|
||||
[VM_ACTIONS.CREATE_APP_DIALOG]: [STATES.POWEROFF],
|
||||
[VM_ACTIONS.HOLD]: [STATES.PENDING],
|
||||
[VM_ACTIONS.LOCK]: [],
|
||||
[VM_ACTIONS.MIGRATE_LIVE]: [STATES.RUNNING, STATES.UNKNOWN],
|
||||
|
@ -21,6 +21,7 @@ export const VM_TEMPLATE_ACTIONS = {
|
||||
IMPORT_DIALOG: 'import_dialog',
|
||||
UPDATE_DIALOG: 'update_dialog',
|
||||
INSTANTIATE_DIALOG: 'instantiate_dialog',
|
||||
CREATE_APP_DIALOG: 'create_app_dialog',
|
||||
CLONE: 'clone',
|
||||
DELETE: 'delete',
|
||||
LOCK: 'lock',
|
||||
|
55
src/fireedge/src/client/containers/MarketplaceApps/Create.js
Normal file
55
src/fireedge/src/client/containers/MarketplaceApps/Create.js
Normal file
@ -0,0 +1,55 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { useMemo, JSXElementConstructor } from 'react'
|
||||
import { useHistory, useLocation } from 'react-router'
|
||||
import { Container } from '@mui/material'
|
||||
|
||||
import { useGeneralApi } from 'client/features/General'
|
||||
// import { useMarketplaceAppApi } from 'client/features/One'
|
||||
import { CreateForm } from 'client/components/Forms/MarketplaceApp'
|
||||
import { isDevelopment } from 'client/utils'
|
||||
|
||||
/**
|
||||
* Displays the creation or modification form to a Marketplace App.
|
||||
*
|
||||
* @returns {JSXElementConstructor} Marketplace App form
|
||||
*/
|
||||
function CreateMarketplaceApp () {
|
||||
const history = useHistory()
|
||||
const { state: [resourceName, { ID } = {}] = [] } = useLocation()
|
||||
const initialValues = useMemo(() => ({ type: resourceName, id: ID }), [])
|
||||
|
||||
const { enqueueSuccess } = useGeneralApi()
|
||||
// const { } = useMarketplaceAppApi()
|
||||
|
||||
const onSubmit = async template => {
|
||||
try {
|
||||
isDevelopment() && console.log({ template })
|
||||
history.goBack()
|
||||
enqueueSuccess('TODO: Marketplace app request')
|
||||
} catch (err) {
|
||||
isDevelopment() && console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Container style={{ display: 'flex', flexFlow: 'column' }} disableGutters>
|
||||
<CreateForm initialValues={initialValues} onSubmit={onSubmit} />
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateMarketplaceApp
|
@ -23,6 +23,7 @@ import * as provisionActions from 'client/features/Auth/provision'
|
||||
import * as sunstoneActions from 'client/features/Auth/sunstone'
|
||||
import { name as authSlice } from 'client/features/Auth/slice'
|
||||
import { name as oneSlice, RESOURCES } from 'client/features/One/slice'
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
export const useAuth = () => {
|
||||
const auth = useSelector(state => state[authSlice], shallowEqual)
|
||||
@ -40,7 +41,7 @@ export const useAuth = () => {
|
||||
/**
|
||||
* Looking for resource view of user authenticated.
|
||||
*
|
||||
* @param {string} resourceName - Name of resource: VM, HOST, IMAGE, etc
|
||||
* @param {RESOURCE_NAMES} resourceName - Name of resource
|
||||
* @returns {{
|
||||
* resource_name: string,
|
||||
* actions: object[],
|
||||
|
@ -52,9 +52,9 @@ export const getDeployMode = (datastore = {}) => {
|
||||
/**
|
||||
* Returns information about datastore capacity.
|
||||
*
|
||||
* @param {object} props - Props object
|
||||
* @param {number} props.TOTAL_MB - Datastore total space in MB
|
||||
* @param {number} props.USED_MB - Datastore used space in MB
|
||||
* @param {object} datastore - Datastore
|
||||
* @param {number} datastore.TOTAL_MB - Total capacity in MB
|
||||
* @param {number} datastore.USED_MB - Used capacity in MB
|
||||
* @returns {{
|
||||
* percentOfUsed: number,
|
||||
* percentLabel: string
|
||||
@ -68,3 +68,19 @@ export const getCapacityInfo = ({ TOTAL_MB, USED_MB } = {}) => {
|
||||
|
||||
return { percentOfUsed, percentLabel }
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if Datastore allows to export to Marketplace.
|
||||
*
|
||||
* @param {object} props - Datastore ob
|
||||
* @param {object} props.NAME - Name
|
||||
* @param {object} oneConfig - One config from redux
|
||||
* @returns {boolean} - Datastore supports to export
|
||||
*/
|
||||
export const isMarketExportSupport = ({ NAME } = {}, oneConfig) => {
|
||||
// When in doubt, allow the action and let oned return failure
|
||||
return !NAME || oneConfig?.DS_MAD_CONF?.some(dsMad => (
|
||||
dsMad?.NAME === NAME &&
|
||||
dsMad?.MARKETPLACE_ACTIONS?.includes?.('export')
|
||||
))
|
||||
}
|
||||
|
@ -20,7 +20,7 @@
|
||||
* @param {string} input - Input string
|
||||
* @returns {string} Input string modified
|
||||
*/
|
||||
export const upperCaseFirst = input => input.charAt(0).toUpperCase() + input.substr(1)
|
||||
export const upperCaseFirst = input => input?.charAt(0)?.toUpperCase() + input?.substr(1)
|
||||
|
||||
/**
|
||||
* Transform into a lower case with spaces between words, then capitalize the string.
|
||||
@ -34,10 +34,10 @@ export const upperCaseFirst = input => input.charAt(0).toUpperCase() + input.sub
|
||||
*/
|
||||
export const sentenceCase = input => {
|
||||
const sentence = input
|
||||
.replace(/[-_]([A-Za-z])/g, ' $1')
|
||||
.replace(/([A-Z])([A-Z][a-z])/g, '$1 $2')
|
||||
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
||||
.toLowerCase()
|
||||
?.replace(/[-_]([A-Za-z])/g, ' $1')
|
||||
?.replace(/([A-Z])([A-Z][a-z])/g, '$1 $2')
|
||||
?.replace(/([a-z])([A-Z])/g, '$1 $2')
|
||||
?.toLowerCase()
|
||||
|
||||
return upperCaseFirst(sentence)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user