mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-21 14:50:08 +03:00
Co-authored-by: Sergio Betanzos <sbetanzos@opennebula.io> (cherry picked from commit f5f8266da88ea68f3132c4b2e19bade343be5ffc)
This commit is contained in:
parent
aa54a8ca55
commit
a29d070d36
@ -29,6 +29,7 @@ import { useGeneralApi } from 'client/features/General'
|
||||
import systemApi from 'client/features/OneApi/system'
|
||||
import Sidebar from 'client/components/Sidebar'
|
||||
import Notifier from 'client/components/Notifier'
|
||||
import NotifierUpload from 'client/components/Notifier/upload'
|
||||
import { AuthLayout } from 'client/components/HOC'
|
||||
import { isDevelopment } from 'client/utils'
|
||||
import { _APPS } from 'client/constants'
|
||||
@ -73,6 +74,7 @@ const SunstoneApp = () => {
|
||||
<>
|
||||
<Sidebar endpoints={endpoints} />
|
||||
<Notifier />
|
||||
<NotifierUpload />
|
||||
</>
|
||||
)}
|
||||
<Router redirectWhenAuth={PATH.DASHBOARD} endpoints={endpoints} />
|
||||
|
@ -97,6 +97,15 @@ const Datastores = loadable(() => import('client/containers/Datastores'), {
|
||||
const Images = loadable(() => import('client/containers/Images'), {
|
||||
ssr: false,
|
||||
})
|
||||
const CreateImages = loadable(() => import('client/containers/Images/Create'), {
|
||||
ssr: false,
|
||||
})
|
||||
const CreateDockerfile = loadable(
|
||||
() => import('client/containers/Images/Dockerfile'),
|
||||
{
|
||||
ssr: false,
|
||||
}
|
||||
)
|
||||
const Marketplaces = loadable(() => import('client/containers/Marketplaces'), {
|
||||
ssr: false,
|
||||
})
|
||||
@ -189,6 +198,8 @@ export const PATH = {
|
||||
IMAGES: {
|
||||
LIST: `/${RESOURCE_NAMES.IMAGE}`,
|
||||
DETAIL: `/${RESOURCE_NAMES.IMAGE}/:id`,
|
||||
CREATE: `/${RESOURCE_NAMES.IMAGE}/create`,
|
||||
DOCKERFILE: `/${RESOURCE_NAMES.IMAGE}/dockerfile`,
|
||||
},
|
||||
MARKETPLACES: {
|
||||
LIST: `/${RESOURCE_NAMES.MARKETPLACE}`,
|
||||
@ -364,6 +375,16 @@ const ENDPOINTS = [
|
||||
icon: ImageIcon,
|
||||
Component: Images,
|
||||
},
|
||||
{
|
||||
title: T.CreateImage,
|
||||
path: PATH.STORAGE.IMAGES.CREATE,
|
||||
Component: CreateImages,
|
||||
},
|
||||
{
|
||||
title: T.CreateDockerfile,
|
||||
path: PATH.STORAGE.IMAGES.DOCKERFILE,
|
||||
Component: CreateDockerfile,
|
||||
},
|
||||
{
|
||||
title: T.Marketplaces,
|
||||
path: PATH.STORAGE.MARKETPLACES.LIST,
|
||||
|
98
src/fireedge/src/client/components/Cards/ImageCreateCard.js
Normal file
98
src/fireedge/src/client/components/Cards/ImageCreateCard.js
Normal file
@ -0,0 +1,98 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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 { ReactElement, memo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Typography, Paper, Grid, Box } from '@mui/material'
|
||||
import makeStyles from '@mui/styles/makeStyles'
|
||||
|
||||
const useStyles = makeStyles(
|
||||
({ palette, typography, breakpoints, shadows }) => ({
|
||||
root: {
|
||||
padding: '0.8em',
|
||||
color: palette.text.primary,
|
||||
backgroundColor: palette.background.paper,
|
||||
fontWeight: typography.fontWeightRegular,
|
||||
fontSize: '1em',
|
||||
borderRadius: 6,
|
||||
display: 'flex',
|
||||
gap: 8,
|
||||
cursor: 'pointer',
|
||||
[breakpoints.down('md')]: {
|
||||
flexWrap: 'wrap',
|
||||
},
|
||||
},
|
||||
figure: {
|
||||
flexBasis: '10%',
|
||||
aspectRatio: '16/9',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
main: {
|
||||
flex: 'auto',
|
||||
overflow: 'hidden',
|
||||
alignSelf: 'center',
|
||||
},
|
||||
title: {
|
||||
color: palette.text.primary,
|
||||
display: 'flex',
|
||||
gap: 6,
|
||||
alignItems: 'center',
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
const ImageCreateCard = memo(
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @param {string} props.name - Card name
|
||||
* @param {Function} props.onClick - Card name
|
||||
* @param {ReactElement} props.Icon - Card Icon
|
||||
* @returns {ReactElement} - Card
|
||||
*/
|
||||
({ name = '', onClick, Icon }) => {
|
||||
const classes = useStyles()
|
||||
|
||||
return (
|
||||
<Grid item xs={12} md={6} onClick={onClick}>
|
||||
<Paper variant="outlined" className={classes.root}>
|
||||
{Icon && (
|
||||
<Box className={classes.figure}>
|
||||
<Icon />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<div className={classes.main}>
|
||||
<div className={classes.title}>
|
||||
<Typography noWrap component="span">
|
||||
{name}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</Paper>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
ImageCreateCard.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
Icon: PropTypes.oneOfType([PropTypes.node, PropTypes.object]),
|
||||
}
|
||||
|
||||
ImageCreateCard.displayName = 'ImageCreateCard'
|
||||
|
||||
export default ImageCreateCard
|
@ -0,0 +1,99 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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 { ReactElement, memo, useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { ModernTv } from 'iconoir-react'
|
||||
import { Typography, Paper } from '@mui/material'
|
||||
|
||||
import { StatusChip } from 'client/components/Status'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
import { Tr, Translate } from 'client/components/HOC'
|
||||
|
||||
import { stringToBoolean, timeFromMilliseconds } from 'client/models/Helper'
|
||||
import { prettyBytes } from 'client/utils'
|
||||
import { T, DiskSnapshot } from 'client/constants'
|
||||
|
||||
const ImageSnapshotCard = memo(
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @param {DiskSnapshot} props.snapshot - Disk snapshot
|
||||
* @param {function({ snapshot: DiskSnapshot }):ReactElement} [props.actions] - Actions
|
||||
* @returns {ReactElement} - Card
|
||||
*/
|
||||
({ snapshot = {}, actions }) => {
|
||||
const classes = rowStyles()
|
||||
|
||||
const {
|
||||
ID,
|
||||
NAME,
|
||||
ACTIVE,
|
||||
DATE,
|
||||
SIZE: SNAPSHOT_SIZE,
|
||||
MONITOR_SIZE: SNAPSHOT_MONITOR_SIZE,
|
||||
} = snapshot
|
||||
|
||||
const isActive = useMemo(() => stringToBoolean(ACTIVE), [ACTIVE])
|
||||
const time = useMemo(() => timeFromMilliseconds(+DATE), [DATE])
|
||||
const timeFormat = useMemo(() => time.toFormat('ff'), [DATE])
|
||||
const timeAgo = useMemo(() => `created ${time.toRelative()}`, [DATE])
|
||||
|
||||
const sizeInfo = useMemo(() => {
|
||||
const size = +SNAPSHOT_SIZE ? prettyBytes(+SNAPSHOT_SIZE, 'MB') : '-'
|
||||
const monitorSize = +SNAPSHOT_MONITOR_SIZE
|
||||
? prettyBytes(+SNAPSHOT_MONITOR_SIZE, 'MB')
|
||||
: '-'
|
||||
|
||||
return `${monitorSize}/${size}`
|
||||
}, [SNAPSHOT_SIZE, SNAPSHOT_MONITOR_SIZE])
|
||||
|
||||
return (
|
||||
<Paper variant="outlined" className={classes.root}>
|
||||
<div className={classes.main}>
|
||||
<div className={classes.title}>
|
||||
<Typography noWrap component="span">
|
||||
{NAME}
|
||||
</Typography>
|
||||
<span className={classes.labels}>
|
||||
{isActive && <StatusChip text={<Translate word={T.Active} />} />}
|
||||
<StatusChip text={<Translate word={T.Snapshot} />} />
|
||||
</span>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span title={timeFormat}>{`#${ID} ${timeAgo}`}</span>
|
||||
<span
|
||||
title={`${Tr(T.Monitoring)} / ${Tr(T.DiskSize)}: ${sizeInfo}`}
|
||||
>
|
||||
<ModernTv />
|
||||
<span>{` ${sizeInfo}`}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{typeof actions === 'function' && (
|
||||
<div className={classes.actions}>{actions({ snapshot })}</div>
|
||||
)}
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
ImageSnapshotCard.propTypes = {
|
||||
snapshot: PropTypes.object.isRequired,
|
||||
actions: PropTypes.func,
|
||||
}
|
||||
|
||||
ImageSnapshotCard.displayName = 'ImageSnapshotCard'
|
||||
|
||||
export default ImageSnapshotCard
|
@ -23,6 +23,8 @@ import DiskCard from 'client/components/Cards/DiskCard'
|
||||
import DiskSnapshotCard from 'client/components/Cards/DiskSnapshotCard'
|
||||
import EmptyCard from 'client/components/Cards/EmptyCard'
|
||||
import HostCard from 'client/components/Cards/HostCard'
|
||||
import ImageCreateCard from 'client/components/Cards/ImageCreateCard'
|
||||
import ImageSnapshotCard from 'client/components/Cards/ImageSnapshotCard'
|
||||
import MarketplaceAppCard from 'client/components/Cards/MarketplaceAppCard'
|
||||
import MarketplaceCard from 'client/components/Cards/MarketplaceCard'
|
||||
import NetworkCard from 'client/components/Cards/NetworkCard'
|
||||
@ -52,6 +54,8 @@ export {
|
||||
DiskSnapshotCard,
|
||||
EmptyCard,
|
||||
HostCard,
|
||||
ImageCreateCard,
|
||||
ImageSnapshotCard,
|
||||
MarketplaceAppCard,
|
||||
MarketplaceCard,
|
||||
NetworkCard,
|
||||
|
@ -0,0 +1,69 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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 { memo, useEffect, useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useFormContext } from 'react-hook-form'
|
||||
import { ErrorHelper } from 'client/components/FormControl'
|
||||
|
||||
import { generateKey } from 'client/utils'
|
||||
import InputCode from 'client/components/FormControl/InputCode'
|
||||
|
||||
const DockerfileController = memo(
|
||||
({ control, cy = `input-${generateKey()}`, name = '' }) => {
|
||||
const {
|
||||
getValues,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useFormContext()
|
||||
|
||||
const [internalError, setInternalError] = useState()
|
||||
const messageError = name
|
||||
.split('.')
|
||||
.reduce((errs, current) => errs?.[current], errors)?.message?.[0]
|
||||
|
||||
useEffect(() => {
|
||||
setInternalError(messageError)
|
||||
}, [messageError])
|
||||
|
||||
return (
|
||||
<div data-cy={cy}>
|
||||
<InputCode
|
||||
mode="dockerfile"
|
||||
height="600px"
|
||||
value={getValues(name)}
|
||||
onChange={(value) => {
|
||||
setValue(name, value)
|
||||
}}
|
||||
onFocus={(e) => {
|
||||
setInternalError()
|
||||
}}
|
||||
/>
|
||||
{internalError && <ErrorHelper label={internalError} />}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
(prevProps, nextProps) => prevProps.cy === nextProps.cy
|
||||
)
|
||||
|
||||
DockerfileController.propTypes = {
|
||||
control: PropTypes.object,
|
||||
cy: PropTypes.string,
|
||||
name: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
DockerfileController.displayName = 'DockerfileController'
|
||||
|
||||
export default DockerfileController
|
@ -35,9 +35,9 @@ const WrapperToLoadMode = ({ children, mode }) => {
|
||||
|
||||
return () => {
|
||||
// remove all styles when component will be unmounted
|
||||
document
|
||||
.querySelectorAll('[id^=ace]')
|
||||
.forEach((child) => child.parentNode.removeChild(child))
|
||||
document.querySelectorAll('[id^=ace-]').forEach((child) => {
|
||||
child.parentNode.removeChild(child)
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
@ -29,6 +29,7 @@ import SubmitButton, {
|
||||
SubmitButtonPropTypes,
|
||||
} from 'client/components/FormControl/SubmitButton'
|
||||
import InputCode from 'client/components/FormControl/InputCode'
|
||||
import DockerfileController from 'client/components/FormControl/DockerfileController'
|
||||
import ErrorHelper from 'client/components/FormControl/ErrorHelper'
|
||||
import Tooltip from 'client/components/FormControl/Tooltip'
|
||||
|
||||
@ -47,6 +48,7 @@ export {
|
||||
SubmitButton,
|
||||
SubmitButtonPropTypes,
|
||||
InputCode,
|
||||
DockerfileController,
|
||||
ErrorHelper,
|
||||
Tooltip,
|
||||
}
|
||||
|
@ -51,6 +51,7 @@ const INPUT_CONTROLLER = {
|
||||
[INPUT_TYPES.TIME]: FC.TimeController,
|
||||
[INPUT_TYPES.TABLE]: FC.TableController,
|
||||
[INPUT_TYPES.TOGGLE]: FC.ToggleController,
|
||||
[INPUT_TYPES.DOCKERFILE]: FC.DockerfileController,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,59 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
|
||||
import {
|
||||
SCHEMA,
|
||||
FIELDS,
|
||||
} from 'client/components/Forms/Image/CloneForm/Steps/BasicConfiguration/schema'
|
||||
import { Step } from 'client/utils'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
export const STEP_ID = 'configuration'
|
||||
|
||||
const Content = (props) => (
|
||||
<FormWithSchema
|
||||
cy="clone-configuration"
|
||||
id={STEP_ID}
|
||||
fields={() => FIELDS(props)}
|
||||
/>
|
||||
)
|
||||
|
||||
/**
|
||||
* Step to configure the marketplace app.
|
||||
*
|
||||
* @param {object} isMultiple - is multiple rows
|
||||
* @returns {Step} Configuration step
|
||||
*/
|
||||
const ConfigurationStep = (isMultiple) => ({
|
||||
id: STEP_ID,
|
||||
label: T.Configuration,
|
||||
resolver: () => SCHEMA(isMultiple),
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: () => Content(isMultiple),
|
||||
})
|
||||
|
||||
Content.propTypes = {
|
||||
data: PropTypes.any,
|
||||
setFormData: PropTypes.func,
|
||||
nics: PropTypes.array,
|
||||
isMultiple: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default ConfigurationStep
|
@ -0,0 +1,55 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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, object, ObjectSchema } from 'yup'
|
||||
import { Field, getValidationFromFields } from 'client/utils'
|
||||
import { T, INPUT_TYPES } from 'client/constants'
|
||||
|
||||
const PREFIX = {
|
||||
name: 'prefix',
|
||||
label: T.Prefix,
|
||||
tooltip: T.PrefixMultipleConcept,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string()
|
||||
.trim()
|
||||
.required()
|
||||
.default(() => T.CopyOf),
|
||||
}
|
||||
|
||||
const NAME = {
|
||||
name: 'name',
|
||||
label: T.Name,
|
||||
tooltip: T.NewTemplateNameConcept,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string()
|
||||
.trim()
|
||||
.required()
|
||||
.default(() => undefined),
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} [stepProps] - Step props
|
||||
* @param {boolean} [stepProps.isMultiple]
|
||||
* - If true, the prefix will be added to the name of the new template
|
||||
* @returns {Field[]} Fields
|
||||
*/
|
||||
export const FIELDS = ({ isMultiple } = {}) => [isMultiple ? PREFIX : NAME]
|
||||
|
||||
/**
|
||||
* @param {object} [stepProps] - Step props
|
||||
* @returns {ObjectSchema} Schema
|
||||
*/
|
||||
export const SCHEMA = (stepProps) =>
|
||||
object(getValidationFromFields(FIELDS(stepProps)))
|
@ -0,0 +1,79 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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 } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useFormContext } from 'react-hook-form'
|
||||
|
||||
import { DatastoresTable } from 'client/components/Tables'
|
||||
import { SCHEMA } from 'client/components/Forms/Image/CloneForm/Steps/DatastoresTable/schema'
|
||||
|
||||
import { Step, decodeBase64 } from 'client/utils'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
export const STEP_ID = 'datastore'
|
||||
|
||||
const Content = ({ data, app }) => {
|
||||
const { NAME } = data?.[0] ?? {}
|
||||
const { setValue } = useFormContext()
|
||||
|
||||
const isKernelType = useMemo(() => {
|
||||
const appTemplate = String(decodeBase64(app?.TEMPLATE?.APPTEMPLATE64, ''))
|
||||
|
||||
return appTemplate.includes('TYPE="KERNEL"')
|
||||
}, [])
|
||||
|
||||
const handleSelectedRows = (rows) => {
|
||||
const { original = {} } = rows?.[0] ?? {}
|
||||
|
||||
setValue(STEP_ID, original.ID !== undefined ? [original] : [])
|
||||
}
|
||||
|
||||
return (
|
||||
<DatastoresTable
|
||||
singleSelect
|
||||
disableGlobalSort
|
||||
displaySelectedRows
|
||||
pageSize={5}
|
||||
getRowId={(row) => String(row.NAME)}
|
||||
initialState={{
|
||||
selectedRowIds: { [NAME]: true },
|
||||
filters: [{ id: 'TYPE', value: isKernelType ? 'FILE' : 'IMAGE' }],
|
||||
}}
|
||||
onSelectedRowsChange={handleSelectedRows}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Step to select the Datastore.
|
||||
*
|
||||
* @param {object} app - Marketplace App resource
|
||||
* @returns {Step} Datastore step
|
||||
*/
|
||||
const DatastoreStep = (app) => ({
|
||||
id: STEP_ID,
|
||||
label: T.SelectDatastoreImage,
|
||||
resolver: SCHEMA,
|
||||
content: (props) => Content({ ...props, app }),
|
||||
})
|
||||
|
||||
Content.propTypes = {
|
||||
data: PropTypes.any,
|
||||
setFormData: PropTypes.func,
|
||||
app: PropTypes.object,
|
||||
}
|
||||
|
||||
export default DatastoreStep
|
@ -0,0 +1,24 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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} Datastore table schema */
|
||||
export const SCHEMA = array(object())
|
||||
.min(1)
|
||||
.max(1)
|
||||
.required()
|
||||
.ensure()
|
||||
.default(() => [])
|
@ -0,0 +1,41 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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/Image/CloneForm/Steps/BasicConfiguration'
|
||||
import DatastoresTable, {
|
||||
STEP_ID as DATASTORE_ID,
|
||||
} from 'client/components/Forms/Image/CloneForm/Steps/DatastoresTable'
|
||||
import { createSteps } from 'client/utils'
|
||||
|
||||
const Steps = createSteps(
|
||||
(app) => [BasicConfiguration, DatastoresTable].filter(Boolean),
|
||||
{
|
||||
transformInitialValue: (app, schema) =>
|
||||
schema.cast({}, { context: { app } }),
|
||||
transformBeforeSubmit: (formData) => {
|
||||
const { [BASIC_ID]: configuration, [DATASTORE_ID]: [datastore] = [] } =
|
||||
formData
|
||||
|
||||
return {
|
||||
datastore: datastore?.ID,
|
||||
...configuration,
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export default Steps
|
@ -0,0 +1,41 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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 FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
|
||||
import {
|
||||
SCHEMA,
|
||||
FIELDS,
|
||||
} from 'client/components/Forms/Image/CreateDockerfile/Steps/Dockerfile/schema'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
export const STEP_ID = 'dockerfile'
|
||||
|
||||
const Content = () => <FormWithSchema id={STEP_ID} fields={FIELDS} />
|
||||
|
||||
/**
|
||||
* Docker file.
|
||||
*
|
||||
* @returns {object} Dockerfile step
|
||||
*/
|
||||
const DockerFile = () => ({
|
||||
id: STEP_ID,
|
||||
label: T.Dockerfile,
|
||||
resolver: SCHEMA,
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: Content,
|
||||
})
|
||||
|
||||
export default DockerFile
|
@ -0,0 +1,41 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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, object, ObjectSchema } from 'yup'
|
||||
import { Field, getValidationFromFields, encodeBase64 } from 'client/utils'
|
||||
import { T, INPUT_TYPES } from 'client/constants'
|
||||
|
||||
/** @type {Field} Dockerfile field */
|
||||
export const DOCKERFILE = {
|
||||
name: 'PATH',
|
||||
label: T.Dockerfile,
|
||||
type: INPUT_TYPES.DOCKERFILE,
|
||||
cy: 'dockerfile',
|
||||
validation: string()
|
||||
.trim()
|
||||
.required()
|
||||
.afterSubmit((value) => encodeBase64(value)),
|
||||
grid: { md: 12 },
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Field[]} Fields
|
||||
*/
|
||||
export const FIELDS = [DOCKERFILE]
|
||||
|
||||
/**
|
||||
* @returns {ObjectSchema} Schema
|
||||
*/
|
||||
export const SCHEMA = object(getValidationFromFields(FIELDS))
|
@ -0,0 +1,42 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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 FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
|
||||
import {
|
||||
SCHEMA,
|
||||
FIELDS,
|
||||
} from 'client/components/Forms/Image/CreateDockerfile/Steps/General/schema'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
export const STEP_ID = 'general'
|
||||
|
||||
const Content = () => <FormWithSchema id={STEP_ID} fields={FIELDS} />
|
||||
|
||||
/**
|
||||
* General configuration about VM Template.
|
||||
*
|
||||
* @returns {object} General configuration step
|
||||
*/
|
||||
const General = () => ({
|
||||
id: STEP_ID,
|
||||
label: T.Configuration,
|
||||
resolver: SCHEMA,
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: Content,
|
||||
})
|
||||
|
||||
export default General
|
@ -0,0 +1,64 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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, number, object, ObjectSchema } from 'yup'
|
||||
import { Field, getValidationFromFields } from 'client/utils'
|
||||
import { T, INPUT_TYPES } from 'client/constants'
|
||||
|
||||
export const IMAGE_LOCATION_TYPES = {
|
||||
PATH: 'path',
|
||||
UPLOAD: 'upload',
|
||||
EMPTY: 'empty',
|
||||
}
|
||||
|
||||
/** @type {Field} Name field */
|
||||
export const NAME = {
|
||||
name: 'NAME',
|
||||
label: T.Name,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string().trim().required(),
|
||||
grid: { xs: 12, md: 6 },
|
||||
}
|
||||
|
||||
/** @type {Field} Context field */
|
||||
export const CONTEXT = {
|
||||
name: 'CONTEXT',
|
||||
label: T.Context,
|
||||
type: INPUT_TYPES.SWITCH,
|
||||
validation: boolean().yesOrNo(),
|
||||
grid: { xs: 12, md: 6 },
|
||||
}
|
||||
|
||||
/** @type {Field} Size field */
|
||||
export const SIZE = {
|
||||
name: 'SIZE',
|
||||
htmlType: 'number',
|
||||
label: T.Size,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
tooltip: T.ImageSize,
|
||||
validation: number().positive().required(),
|
||||
grid: { md: 12 },
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Field[]} Fields
|
||||
*/
|
||||
export const FIELDS = [NAME, CONTEXT, SIZE]
|
||||
|
||||
/**
|
||||
* @param {object} [stepProps] - Step props
|
||||
* @returns {ObjectSchema} Schema
|
||||
*/
|
||||
export const SCHEMA = object(getValidationFromFields(FIELDS))
|
@ -0,0 +1,63 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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 Datastore, {
|
||||
STEP_ID as DATASTORE_ID,
|
||||
} from 'client/components/Forms/Image/CloneForm/Steps/DatastoresTable'
|
||||
|
||||
import General, {
|
||||
STEP_ID as GENERAL_ID,
|
||||
} from 'client/components/Forms/Image/CreateForm/Steps/General'
|
||||
|
||||
import AdvancedOptions, {
|
||||
STEP_ID as ADVANCE_ID,
|
||||
} from 'client/components/Forms/Image/CreateForm/Steps/AdvancedOptions'
|
||||
|
||||
import CustomAttributes, {
|
||||
STEP_ID as CUSTOM_ID,
|
||||
} from 'client/components/Forms/Image/CreateForm/Steps/CustomAttributes'
|
||||
|
||||
import { jsonToXml } from 'client/models/Helper'
|
||||
import { createSteps, cloneObject, set } from 'client/utils'
|
||||
|
||||
const Steps = createSteps(
|
||||
[General, Datastore, AdvancedOptions, CustomAttributes],
|
||||
{
|
||||
transformBeforeSubmit: (formData) => {
|
||||
const {
|
||||
[GENERAL_ID]: general = {},
|
||||
[DATASTORE_ID]: [datastore] = [],
|
||||
[ADVANCE_ID]: advanced = {},
|
||||
[CUSTOM_ID]: custom = {},
|
||||
} = formData ?? {}
|
||||
|
||||
const generalData = cloneObject(general)
|
||||
set(generalData, 'UPLOAD', undefined)
|
||||
set(generalData, 'IMAGE_LOCATION', undefined)
|
||||
|
||||
return {
|
||||
template: jsonToXml({
|
||||
...custom,
|
||||
...advanced,
|
||||
...generalData,
|
||||
}),
|
||||
datastore: datastore?.ID,
|
||||
file: general?.UPLOAD,
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export default Steps
|
@ -0,0 +1,55 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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 Datastore, {
|
||||
STEP_ID as DATASTORE_ID,
|
||||
} from 'client/components/Forms/Image/CloneForm/Steps/DatastoresTable'
|
||||
|
||||
import General, {
|
||||
STEP_ID as GENERAL_ID,
|
||||
} from 'client/components/Forms/Image/CreateDockerfile/Steps/General'
|
||||
|
||||
import Dockerfile, {
|
||||
STEP_ID as DOCKERFILE_ID,
|
||||
} from 'client/components/Forms/Image/CreateDockerfile/Steps/Dockerfile'
|
||||
|
||||
import { jsonToXml } from 'client/models/Helper'
|
||||
import { createSteps, cloneObject, set } from 'client/utils'
|
||||
|
||||
const Steps = createSteps([General, Datastore, Dockerfile], {
|
||||
transformBeforeSubmit: (formData) => {
|
||||
const {
|
||||
[GENERAL_ID]: general = {},
|
||||
[DATASTORE_ID]: [datastore] = [],
|
||||
[DOCKERFILE_ID]: dockerfile = {},
|
||||
} = formData ?? {}
|
||||
|
||||
const generalData = cloneObject(general)
|
||||
set(generalData, 'CONTEXT', undefined)
|
||||
set(generalData, 'SIZE', undefined)
|
||||
|
||||
return {
|
||||
template: jsonToXml({
|
||||
...{
|
||||
PATH: `dockerfile://?fileb64=${dockerfile.PATH}&context=${general.CONTEXT}&size=${general.SIZE}`,
|
||||
},
|
||||
...generalData,
|
||||
}),
|
||||
datastore: datastore?.ID,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export default Steps
|
@ -0,0 +1,41 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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 FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
|
||||
import {
|
||||
SCHEMA,
|
||||
FIELDS,
|
||||
} from 'client/components/Forms/Image/CreateForm/Steps/AdvancedOptions/schema'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
export const STEP_ID = 'advanced'
|
||||
|
||||
const Content = () => <FormWithSchema id={STEP_ID} fields={FIELDS} />
|
||||
|
||||
/**
|
||||
* Advanced options create image.
|
||||
*
|
||||
* @returns {object} Advanced options configuration step
|
||||
*/
|
||||
const AdvancedOptions = () => ({
|
||||
id: STEP_ID,
|
||||
label: T.AdvancedOptions,
|
||||
resolver: SCHEMA,
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: Content,
|
||||
})
|
||||
|
||||
export default AdvancedOptions
|
@ -0,0 +1,180 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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, object, ObjectSchema } from 'yup'
|
||||
import { Field, arrayToOptions, getValidationFromFields } from 'client/utils'
|
||||
import { T, INPUT_TYPES } from 'client/constants'
|
||||
import {
|
||||
IMAGE_LOCATION_TYPES,
|
||||
IMAGE_LOCATION_FIELD,
|
||||
} from 'client/components/Forms/Image/CreateForm/Steps/General/schema'
|
||||
import { useGetSunstoneConfigQuery } from 'client/features/OneApi/system'
|
||||
|
||||
export const BUS_TYPES = {
|
||||
VD: 'path',
|
||||
SD: 'upload',
|
||||
HD: 'empty',
|
||||
CUSTOM: 'custom',
|
||||
}
|
||||
|
||||
const FORMAT_TYPES = {
|
||||
RAW: 'raw',
|
||||
QCOW2: 'qcow2',
|
||||
CUSTOM: 'custom',
|
||||
}
|
||||
|
||||
const BUS = {
|
||||
[BUS_TYPES.VD]: T.Vd,
|
||||
[BUS_TYPES.SD]: T.Sd,
|
||||
[BUS_TYPES.HD]: T.Hd,
|
||||
[BUS_TYPES.CUSTOM]: T.Custom,
|
||||
}
|
||||
|
||||
const htmlType = (opt) => (value) => value !== opt && INPUT_TYPES.HIDDEN
|
||||
|
||||
/** @type {Field} Bus field */
|
||||
export const DEV_PREFIX = {
|
||||
name: 'DEV_PREFIX',
|
||||
label: T.Bus,
|
||||
type: INPUT_TYPES.SELECT,
|
||||
values: arrayToOptions(Object.entries(BUS), {
|
||||
addEmpty: true,
|
||||
getText: ([_, name]) => name,
|
||||
getValue: ([key]) => key,
|
||||
}),
|
||||
validation: string()
|
||||
.trim()
|
||||
.default(() => undefined)
|
||||
.afterSubmit((value, { context }) => {
|
||||
const notEmptyString = value === '' ? undefined : value
|
||||
|
||||
return BUS_TYPES.CUSTOM === value
|
||||
? context?.advanced?.CUSTOM_DEV_PREFIX
|
||||
: notEmptyString
|
||||
}),
|
||||
grid: { md: 6 },
|
||||
}
|
||||
|
||||
/** @type {Field} Dev Prefix field */
|
||||
export const CUSTOM_DEV_PREFIX = {
|
||||
name: 'CUSTOM_DEV_PREFIX',
|
||||
dependOf: DEV_PREFIX.name,
|
||||
htmlType: htmlType(BUS_TYPES.CUSTOM),
|
||||
label: T.CustomBus,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string()
|
||||
.trim()
|
||||
.when(DEV_PREFIX.name, {
|
||||
is: (location) => location === BUS_TYPES.CUSTOM,
|
||||
then: (schema) => schema.required(),
|
||||
otherwise: (schema) => schema.strip(),
|
||||
})
|
||||
.default(() => undefined),
|
||||
grid: { md: 12 },
|
||||
}
|
||||
|
||||
/** @type {Field} Device field */
|
||||
export const DEVICE = {
|
||||
name: 'DEVICE',
|
||||
label: T.TargetDevice,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string()
|
||||
.trim()
|
||||
.default(() => undefined),
|
||||
grid: { md: 6 },
|
||||
}
|
||||
|
||||
/** @type {Field} Format field */
|
||||
export const FORMAT_FIELD = {
|
||||
name: 'FORMAT',
|
||||
dependOf: `$general.${IMAGE_LOCATION_FIELD.name}`,
|
||||
htmlType: htmlType(IMAGE_LOCATION_TYPES.EMPTY),
|
||||
label: T.Format,
|
||||
type: INPUT_TYPES.SELECT,
|
||||
values: arrayToOptions(Object.values(FORMAT_TYPES), {
|
||||
addEmpty: true,
|
||||
getText: (type) => type.toUpperCase(),
|
||||
getValue: (type) => type,
|
||||
}),
|
||||
validation: string()
|
||||
.trim()
|
||||
.afterSubmit((value, { context }) => {
|
||||
const notEmptyString = value === '' ? undefined : value
|
||||
|
||||
return FORMAT_TYPES.CUSTOM === value
|
||||
? context?.advanced?.CUSTOM_FORMAT
|
||||
: notEmptyString
|
||||
}),
|
||||
grid: { md: 6 },
|
||||
}
|
||||
|
||||
/** @type {Field} Custom format field */
|
||||
export const CUSTOM_FORMAT = {
|
||||
name: 'CUSTOM_FORMAT',
|
||||
dependOf: [`$general.${IMAGE_LOCATION_FIELD.name}`, FORMAT_FIELD.name],
|
||||
htmlType: ([imageLocation, formatField] = []) =>
|
||||
(imageLocation !== IMAGE_LOCATION_TYPES.EMPTY ||
|
||||
formatField !== FORMAT_TYPES.CUSTOM) &&
|
||||
INPUT_TYPES.HIDDEN,
|
||||
label: T.CustomFormat,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string()
|
||||
.trim()
|
||||
.when(FORMAT_FIELD.name, {
|
||||
is: (format) => format === FORMAT_TYPES.CUSTOM,
|
||||
then: (schema) => schema.required(),
|
||||
otherwise: (schema) => schema.strip(),
|
||||
}),
|
||||
grid: { md: 12 },
|
||||
}
|
||||
|
||||
/** @type {Field} FS field */
|
||||
export const FS = {
|
||||
name: 'FS',
|
||||
dependOf: `$general.${IMAGE_LOCATION_FIELD.name}`,
|
||||
htmlType: htmlType(IMAGE_LOCATION_TYPES.EMPTY),
|
||||
label: T.Fs,
|
||||
type: INPUT_TYPES.SELECT,
|
||||
values: () => {
|
||||
const { data: sunstoneConfig = {} } = useGetSunstoneConfigQuery()
|
||||
|
||||
return arrayToOptions(sunstoneConfig?.supported_fs || [], {
|
||||
addEmpty: true,
|
||||
getText: (fs) => fs.toUpperCase(),
|
||||
getValue: (fs) => fs,
|
||||
})
|
||||
},
|
||||
validation: string()
|
||||
.trim()
|
||||
.afterSubmit((value) => (value === '' ? undefined : value)),
|
||||
grid: { md: 6 },
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Field[]} Fields
|
||||
*/
|
||||
export const FIELDS = [
|
||||
DEV_PREFIX,
|
||||
DEVICE,
|
||||
CUSTOM_DEV_PREFIX,
|
||||
FORMAT_FIELD,
|
||||
FS,
|
||||
CUSTOM_FORMAT,
|
||||
]
|
||||
|
||||
/**
|
||||
* @returns {ObjectSchema} Schema
|
||||
*/
|
||||
export const SCHEMA = object(getValidationFromFields(FIELDS))
|
@ -0,0 +1,67 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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 { useCallback } from 'react'
|
||||
import { object } from 'yup'
|
||||
import { useFormContext, useWatch } from 'react-hook-form'
|
||||
import { Box } from '@mui/material'
|
||||
|
||||
import { AttributePanel } from 'client/components/Tabs/Common'
|
||||
import { cleanEmpty, cloneObject, set } from 'client/utils'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
export const STEP_ID = 'custom-attributes'
|
||||
|
||||
const Content = () => {
|
||||
const { setValue } = useFormContext()
|
||||
const customVars = useWatch({ name: STEP_ID, defaultValue: {} })
|
||||
|
||||
const handleChangeAttribute = useCallback(
|
||||
(path, newValue) => {
|
||||
const newCustomVars = cloneObject(customVars)
|
||||
set(newCustomVars, path, newValue)
|
||||
setValue(STEP_ID, cleanEmpty(newCustomVars))
|
||||
},
|
||||
[customVars]
|
||||
)
|
||||
|
||||
return (
|
||||
<Box display="grid" gap="1em">
|
||||
<AttributePanel
|
||||
allActionsEnabled
|
||||
handleAdd={handleChangeAttribute}
|
||||
handleEdit={handleChangeAttribute}
|
||||
handleDelete={handleChangeAttribute}
|
||||
attributes={customVars}
|
||||
filtersSpecialAttributes={false}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom variables about VM Template.
|
||||
*
|
||||
* @returns {object} Custom configuration step
|
||||
*/
|
||||
const CustomAttributes = () => ({
|
||||
id: STEP_ID,
|
||||
label: T.CustomAttributes,
|
||||
resolver: object(),
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: Content,
|
||||
})
|
||||
|
||||
export default CustomAttributes
|
@ -0,0 +1,42 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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 FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
|
||||
import {
|
||||
SCHEMA,
|
||||
FIELDS,
|
||||
} from 'client/components/Forms/Image/CreateForm/Steps/General/schema'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
export const STEP_ID = 'general'
|
||||
|
||||
const Content = () => <FormWithSchema id={STEP_ID} fields={FIELDS} />
|
||||
|
||||
/**
|
||||
* General configuration about VM Template.
|
||||
*
|
||||
* @returns {object} General configuration step
|
||||
*/
|
||||
const General = () => ({
|
||||
id: STEP_ID,
|
||||
label: T.Configuration,
|
||||
resolver: SCHEMA,
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: Content,
|
||||
})
|
||||
|
||||
export default General
|
@ -0,0 +1,185 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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, number, object, ObjectSchema, mixed } from 'yup'
|
||||
import {
|
||||
Field,
|
||||
arrayToOptions,
|
||||
getValidationFromFields,
|
||||
upperCaseFirst,
|
||||
} from 'client/utils'
|
||||
import { T, INPUT_TYPES, IMAGE_TYPES_STR } from 'client/constants'
|
||||
|
||||
export const IMAGE_LOCATION_TYPES = {
|
||||
PATH: 'path',
|
||||
UPLOAD: 'upload',
|
||||
EMPTY: 'empty',
|
||||
}
|
||||
|
||||
const IMAGE_LOCATION = {
|
||||
[IMAGE_LOCATION_TYPES.PATH]: T.Path,
|
||||
[IMAGE_LOCATION_TYPES.UPLOAD]: T.Upload,
|
||||
[IMAGE_LOCATION_TYPES.EMPTY]: T.EmptyDisk,
|
||||
}
|
||||
|
||||
const htmlType = (opt, inputNumber) => (location) => {
|
||||
if (location === opt && inputNumber) {
|
||||
return 'number'
|
||||
}
|
||||
|
||||
return location !== opt && INPUT_TYPES.HIDDEN
|
||||
}
|
||||
|
||||
/** @type {Field} name field */
|
||||
export const NAME = {
|
||||
name: 'NAME',
|
||||
label: T.Name,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string().trim().required(),
|
||||
grid: { xs: 12, md: 6 },
|
||||
}
|
||||
|
||||
/** @type {Field} Description field */
|
||||
export const DESCRIPTION = {
|
||||
name: 'DESCRIPTION',
|
||||
label: T.Description,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
multiline: true,
|
||||
validation: string().trim(),
|
||||
grid: { xs: 12, md: 6 },
|
||||
}
|
||||
|
||||
/** @type {Field} Type field */
|
||||
export const TYPE = {
|
||||
name: 'TYPE',
|
||||
label: T.Type,
|
||||
type: INPUT_TYPES.SELECT,
|
||||
values: arrayToOptions(Object.values(IMAGE_TYPES_STR), {
|
||||
addEmpty: false,
|
||||
getText: (type) => {
|
||||
switch (type) {
|
||||
case IMAGE_TYPES_STR.OS:
|
||||
return T.Os
|
||||
case IMAGE_TYPES_STR.CDROM:
|
||||
return T.Cdrom
|
||||
case IMAGE_TYPES_STR.DATABLOCK:
|
||||
return T.Datablock
|
||||
default:
|
||||
return upperCaseFirst(type.toLowerCase())
|
||||
}
|
||||
},
|
||||
getValue: (type) => type,
|
||||
}),
|
||||
validation: string()
|
||||
.trim()
|
||||
.default(() => IMAGE_TYPES_STR.OS),
|
||||
grid: { xs: 12, md: 6 },
|
||||
}
|
||||
|
||||
/** @type {Field} Persistent field */
|
||||
export const PERSISTENT = {
|
||||
name: 'PERSISTENT',
|
||||
label: T.MakePersistent,
|
||||
type: INPUT_TYPES.SWITCH,
|
||||
validation: boolean().yesOrNo(),
|
||||
grid: { xs: 12, md: 6 },
|
||||
}
|
||||
|
||||
/** @type {Field} Toggle select type image */
|
||||
export const IMAGE_LOCATION_FIELD = {
|
||||
name: 'IMAGE_LOCATION',
|
||||
type: INPUT_TYPES.TOGGLE,
|
||||
values: arrayToOptions(Object.entries(IMAGE_LOCATION), {
|
||||
addEmpty: false,
|
||||
getText: ([_, name]) => name,
|
||||
getValue: ([image]) => image,
|
||||
}),
|
||||
validation: string()
|
||||
.trim()
|
||||
.required()
|
||||
.default(() => IMAGE_LOCATION_TYPES.PATH),
|
||||
grid: { md: 12 },
|
||||
notNull: true,
|
||||
}
|
||||
/** @type {Field} path field */
|
||||
export const PATH_FIELD = {
|
||||
name: 'PATH',
|
||||
dependOf: IMAGE_LOCATION_FIELD.name,
|
||||
htmlType: htmlType(IMAGE_LOCATION_TYPES.PATH),
|
||||
label: T.ImagePath,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string()
|
||||
.trim()
|
||||
.when(IMAGE_LOCATION_FIELD.name, {
|
||||
is: (location) => location === IMAGE_LOCATION_TYPES.PATH,
|
||||
then: (schema) => schema.required(),
|
||||
otherwise: (schema) => schema.strip(),
|
||||
}),
|
||||
grid: { md: 12 },
|
||||
}
|
||||
|
||||
/** @type {Field} upload field */
|
||||
export const UPLOAD_FIELD = {
|
||||
name: 'UPLOAD',
|
||||
dependOf: IMAGE_LOCATION_FIELD.name,
|
||||
htmlType: htmlType(IMAGE_LOCATION_TYPES.UPLOAD),
|
||||
label: T.Upload,
|
||||
type: INPUT_TYPES.FILE,
|
||||
validation: mixed().when(IMAGE_LOCATION_FIELD.name, {
|
||||
is: (location) => location === IMAGE_LOCATION_TYPES.UPLOAD,
|
||||
then: (schema) => schema.required(),
|
||||
otherwise: (schema) => schema.strip(),
|
||||
}),
|
||||
grid: { md: 12 },
|
||||
}
|
||||
|
||||
/** @type {Field} size field */
|
||||
export const SIZE = {
|
||||
name: 'SIZE',
|
||||
dependOf: IMAGE_LOCATION_FIELD.name,
|
||||
htmlType: htmlType(IMAGE_LOCATION_TYPES.EMPTY, true),
|
||||
label: T.Size,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
tooltip: T.ImageSize,
|
||||
validation: number()
|
||||
.positive()
|
||||
.default(() => undefined)
|
||||
.when(IMAGE_LOCATION_FIELD.name, {
|
||||
is: (location) => location === IMAGE_LOCATION_TYPES.EMPTY,
|
||||
then: (schema) => schema.required(),
|
||||
otherwise: (schema) => schema.strip(),
|
||||
}),
|
||||
grid: { md: 12 },
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Field[]} Fields
|
||||
*/
|
||||
export const FIELDS = [
|
||||
NAME,
|
||||
DESCRIPTION,
|
||||
TYPE,
|
||||
PERSISTENT,
|
||||
IMAGE_LOCATION_FIELD,
|
||||
PATH_FIELD,
|
||||
UPLOAD_FIELD,
|
||||
SIZE,
|
||||
]
|
||||
|
||||
/**
|
||||
* @param {object} [stepProps] - Step props
|
||||
* @returns {ObjectSchema} Schema
|
||||
*/
|
||||
export const SCHEMA = object(getValidationFromFields(FIELDS))
|
@ -0,0 +1,62 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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 Datastore, {
|
||||
STEP_ID as DATASTORE_ID,
|
||||
} from 'client/components/Forms/Image/CloneForm/Steps/DatastoresTable'
|
||||
|
||||
import General, {
|
||||
STEP_ID as GENERAL_ID,
|
||||
} from 'client/components/Forms/Image/CreateForm/Steps/General'
|
||||
|
||||
import AdvancedOptions, {
|
||||
STEP_ID as ADVANCE_ID,
|
||||
} from 'client/components/Forms/Image/CreateForm/Steps/AdvancedOptions'
|
||||
|
||||
import CustomAttributes, {
|
||||
STEP_ID as CUSTOM_ID,
|
||||
} from 'client/components/Forms/Image/CreateForm/Steps/CustomAttributes'
|
||||
|
||||
import { createSteps, cloneObject, set } from 'client/utils'
|
||||
|
||||
const Steps = createSteps(
|
||||
[General, Datastore, AdvancedOptions, CustomAttributes],
|
||||
{
|
||||
transformBeforeSubmit: (formData) => {
|
||||
const {
|
||||
[GENERAL_ID]: general = {},
|
||||
[DATASTORE_ID]: [datastore] = [],
|
||||
[ADVANCE_ID]: advanced = {},
|
||||
[CUSTOM_ID]: custom = {},
|
||||
} = formData ?? {}
|
||||
|
||||
const generalData = cloneObject(general)
|
||||
set(generalData, 'UPLOAD', undefined)
|
||||
set(generalData, 'IMAGE_LOCATION', undefined)
|
||||
|
||||
return {
|
||||
template: {
|
||||
...custom,
|
||||
...advanced,
|
||||
...generalData,
|
||||
},
|
||||
datastore: datastore?.ID,
|
||||
file: general?.UPLOAD,
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export default Steps
|
@ -0,0 +1,16 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
export { default } from 'client/components/Forms/Image/CreateForm/Steps'
|
41
src/fireedge/src/client/components/Forms/Image/index.js
Normal file
41
src/fireedge/src/client/components/Forms/Image/index.js
Normal file
@ -0,0 +1,41 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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 { ReactElement } from 'react'
|
||||
import { AsyncLoadForm, ConfigurationProps } from 'client/components/HOC'
|
||||
import { CreateFormCallback, CreateStepsCallback } from 'client/utils/schema'
|
||||
|
||||
/**
|
||||
* @param {ConfigurationProps} configProps - Configuration
|
||||
* @returns {ReactElement|CreateFormCallback} Asynchronous loaded form
|
||||
*/
|
||||
const CloneForm = (configProps) =>
|
||||
AsyncLoadForm({ formPath: 'Image/CloneForm' }, configProps)
|
||||
|
||||
/**
|
||||
* @param {ConfigurationProps} configProps - Configuration
|
||||
* @returns {ReactElement|CreateStepsCallback} Asynchronous loaded form
|
||||
*/
|
||||
const CreateForm = (configProps) =>
|
||||
AsyncLoadForm({ formPath: 'Image/CreateForm' }, configProps)
|
||||
|
||||
/**
|
||||
* @param {ConfigurationProps} configProps - Configuration
|
||||
* @returns {ReactElement|CreateStepsCallback} Asynchronous loaded form
|
||||
*/
|
||||
const CreateDockerfileForm = (configProps) =>
|
||||
AsyncLoadForm({ formPath: 'Image/CreateDockerfile' }, configProps)
|
||||
|
||||
export { CloneForm, CreateForm, CreateDockerfileForm }
|
84
src/fireedge/src/client/components/Notifier/upload.js
Normal file
84
src/fireedge/src/client/components/Notifier/upload.js
Normal file
@ -0,0 +1,84 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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 { ReactElement } from 'react'
|
||||
import {
|
||||
Snackbar,
|
||||
Alert,
|
||||
CircularProgress,
|
||||
Box,
|
||||
Typography,
|
||||
} from '@mui/material'
|
||||
import { useGeneral, useGeneralApi } from 'client/features/General'
|
||||
|
||||
/**
|
||||
* @returns {ReactElement} App rendered.
|
||||
*/
|
||||
const NotifierUpload = () => {
|
||||
const { upload } = useGeneral()
|
||||
const { uploadSnackbar } = useGeneralApi()
|
||||
|
||||
const handleClose = () => uploadSnackbar(0)
|
||||
|
||||
return (
|
||||
<Snackbar
|
||||
open={upload > 0}
|
||||
autoHideDuration={10000}
|
||||
onClose={handleClose}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
>
|
||||
<Alert
|
||||
icon={false}
|
||||
onClose={handleClose}
|
||||
severity="info"
|
||||
variant="filled"
|
||||
sx={{ width: '100%' }}
|
||||
>
|
||||
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
|
||||
<CircularProgress
|
||||
variant="determinate"
|
||||
value={upload}
|
||||
color="inherit"
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
position: 'absolute',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="caption"
|
||||
component="div"
|
||||
color="text.secondary"
|
||||
>
|
||||
{`${upload}%`}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
)
|
||||
}
|
||||
|
||||
export default NotifierUpload
|
388
src/fireedge/src/client/components/Tables/Images/actions.js
Normal file
388
src/fireedge/src/client/components/Tables/Images/actions.js
Normal file
@ -0,0 +1,388 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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 } from 'react'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import { Typography, Grid } from '@mui/material'
|
||||
import {
|
||||
MoreVert,
|
||||
AddCircledOutline,
|
||||
Lock,
|
||||
Cart,
|
||||
Group,
|
||||
Trash,
|
||||
Code,
|
||||
PageEdit,
|
||||
} from 'iconoir-react'
|
||||
|
||||
import { useViews } from 'client/features/Auth'
|
||||
import {
|
||||
useLockImageMutation,
|
||||
useCloneImageMutation,
|
||||
useUnlockImageMutation,
|
||||
useEnableImageMutation,
|
||||
useDisableImageMutation,
|
||||
usePersistentImageMutation,
|
||||
useChangeImageOwnershipMutation,
|
||||
useRemoveImageMutation,
|
||||
} from 'client/features/OneApi/image'
|
||||
|
||||
import { ChangeUserForm, ChangeGroupForm } from 'client/components/Forms/Vm'
|
||||
import { CloneForm } from 'client/components/Forms/Image'
|
||||
import {
|
||||
createActions,
|
||||
GlobalAction,
|
||||
} from 'client/components/Tables/Enhanced/Utils'
|
||||
import ImageCreateCard from 'client/components/Cards/ImageCreateCard'
|
||||
|
||||
import { Tr, Translate } from 'client/components/HOC'
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
import { isAvailableAction } from 'client/models/VirtualMachine'
|
||||
import { T, IMAGE_ACTIONS, VM_ACTIONS, RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
const isDisabled = (action) => (rows) =>
|
||||
!isAvailableAction(
|
||||
action,
|
||||
rows.map(({ original }) => original)
|
||||
)
|
||||
|
||||
const ListImagesNames = ({ rows = [] }) =>
|
||||
rows?.map?.(({ id, original }) => {
|
||||
const { ID, NAME } = original
|
||||
|
||||
return (
|
||||
<Typography
|
||||
key={`image-${id}`}
|
||||
variant="inherit"
|
||||
component="span"
|
||||
display="block"
|
||||
>
|
||||
{`#${ID} ${NAME}`}
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
|
||||
const SubHeader = (rows) => <ListImagesNames rows={rows} />
|
||||
|
||||
const MessageToConfirmAction = (rows) => (
|
||||
<>
|
||||
<ListImagesNames rows={rows} />
|
||||
<Translate word={T.DoYouWantProceed} />
|
||||
</>
|
||||
)
|
||||
|
||||
/**
|
||||
* Generates the actions to operate resources on Image table.
|
||||
*
|
||||
* @returns {GlobalAction} - Actions
|
||||
*/
|
||||
const Actions = () => {
|
||||
const history = useHistory()
|
||||
const { view, getResourceView } = useViews()
|
||||
const [clone] = useCloneImageMutation()
|
||||
const [lock] = useLockImageMutation()
|
||||
const [unlock] = useUnlockImageMutation()
|
||||
const [enable] = useEnableImageMutation()
|
||||
const [disable] = useDisableImageMutation()
|
||||
const [persistent] = usePersistentImageMutation()
|
||||
const [changeOwnership] = useChangeImageOwnershipMutation()
|
||||
const [deleteImage] = useRemoveImageMutation()
|
||||
|
||||
const resourcesView = getResourceView(RESOURCE_NAMES.IMAGE)?.actions
|
||||
|
||||
const imageActions = useMemo(
|
||||
() =>
|
||||
createActions({
|
||||
filters: resourcesView,
|
||||
actions: [
|
||||
{
|
||||
accessor: IMAGE_ACTIONS.CREATE_DIALOG,
|
||||
dataCy: `image_${IMAGE_ACTIONS.CREATE_DIALOG}`,
|
||||
tooltip: T.Create,
|
||||
icon: AddCircledOutline,
|
||||
options: [
|
||||
{
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
title: T.CreateImage,
|
||||
children: () => (
|
||||
<Grid container spacing={3}>
|
||||
<ImageCreateCard
|
||||
name={Tr(T.CreateImage)}
|
||||
Icon={PageEdit}
|
||||
onClick={() => history.push(PATH.STORAGE.IMAGES.CREATE)}
|
||||
/>
|
||||
{resourcesView?.dockerfile_dialog && (
|
||||
<ImageCreateCard
|
||||
name={Tr(T.CreateDockerfile)}
|
||||
Icon={Code}
|
||||
onClick={() =>
|
||||
history.push(PATH.STORAGE.IMAGES.DOCKERFILE)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Grid>
|
||||
),
|
||||
fixedWidth: true,
|
||||
fixedHeight: true,
|
||||
handleAccept: undefined,
|
||||
dataCy: `modal-${IMAGE_ACTIONS.CREATE_DIALOG}`,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
accessor: VM_ACTIONS.CREATE_APP_DIALOG,
|
||||
dataCy: `image_${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: IMAGE_ACTIONS.CLONE,
|
||||
label: T.Clone,
|
||||
tooltip: T.Clone,
|
||||
selected: true,
|
||||
color: 'secondary',
|
||||
options: [
|
||||
{
|
||||
dialogProps: {
|
||||
title: (rows) => {
|
||||
const isMultiple = rows?.length > 1
|
||||
const { ID, NAME } = rows?.[0]?.original ?? {}
|
||||
|
||||
return [
|
||||
Tr(
|
||||
isMultiple ? T.CloneSeveralTemplates : T.CloneTemplate
|
||||
),
|
||||
!isMultiple && `#${ID} ${NAME}`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' - ')
|
||||
},
|
||||
dataCy: 'modal-clone',
|
||||
},
|
||||
form: (rows) => {
|
||||
const names = rows?.map(({ original }) => original?.NAME)
|
||||
const stepProps = { isMultiple: names.length > 1 }
|
||||
const initialValues = { name: `Copy of ${names?.[0]}` }
|
||||
|
||||
return CloneForm({ stepProps, initialValues })
|
||||
},
|
||||
onSubmit:
|
||||
(rows) =>
|
||||
async ({ prefix, name, datastore } = {}) => {
|
||||
const images = rows?.map?.(
|
||||
({ original: { ID, NAME } = {} }) =>
|
||||
// overwrite all names with prefix+NAME
|
||||
({
|
||||
id: ID,
|
||||
name: prefix ? `${prefix} ${NAME}` : name,
|
||||
datastore,
|
||||
})
|
||||
)
|
||||
|
||||
await Promise.all(images.map(clone))
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
tooltip: T.Lock,
|
||||
icon: Lock,
|
||||
selected: true,
|
||||
color: 'secondary',
|
||||
dataCy: 'image-lock',
|
||||
options: [
|
||||
{
|
||||
accessor: IMAGE_ACTIONS.LOCK,
|
||||
name: T.Lock,
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
title: T.Lock,
|
||||
dataCy: `modal-${IMAGE_ACTIONS.LOCK}`,
|
||||
children: MessageToConfirmAction,
|
||||
},
|
||||
onSubmit: (rows) => async () => {
|
||||
const ids = rows?.map?.(({ original }) => original?.ID)
|
||||
await Promise.all(ids.map((id) => lock({ id })))
|
||||
},
|
||||
},
|
||||
{
|
||||
accessor: IMAGE_ACTIONS.UNLOCK,
|
||||
name: T.Unlock,
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
title: T.Unlock,
|
||||
dataCy: `modal-${IMAGE_ACTIONS.UNLOCK}`,
|
||||
children: MessageToConfirmAction,
|
||||
},
|
||||
onSubmit: (rows) => async () => {
|
||||
const ids = rows?.map?.(({ original }) => original?.ID)
|
||||
await Promise.all(ids.map((id) => unlock({ id })))
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
tooltip: T.Enable,
|
||||
icon: MoreVert,
|
||||
selected: true,
|
||||
color: 'secondary',
|
||||
dataCy: 'image-enable',
|
||||
options: [
|
||||
{
|
||||
accessor: IMAGE_ACTIONS.ENABLE,
|
||||
name: T.Enable,
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
title: T.Enable,
|
||||
children: MessageToConfirmAction,
|
||||
dataCy: `modal-${IMAGE_ACTIONS.ENABLE}`,
|
||||
},
|
||||
onSubmit: (rows) => async () => {
|
||||
const ids = rows?.map?.(({ original }) => original?.ID)
|
||||
await Promise.all(ids.map((id) => enable(id)))
|
||||
},
|
||||
},
|
||||
{
|
||||
accessor: IMAGE_ACTIONS.DISABLE,
|
||||
name: T.Disable,
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
title: T.Disable,
|
||||
children: MessageToConfirmAction,
|
||||
dataCy: `modal-${IMAGE_ACTIONS.DISABLE}`,
|
||||
},
|
||||
onSubmit: (rows) => async () => {
|
||||
const ids = rows?.map?.(({ original }) => original?.ID)
|
||||
await Promise.all(ids.map((id) => disable(id)))
|
||||
},
|
||||
},
|
||||
{
|
||||
accessor: IMAGE_ACTIONS.PERSISTENT,
|
||||
name: T.Persistent,
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
title: T.Persistent,
|
||||
children: MessageToConfirmAction,
|
||||
dataCy: `modal-${IMAGE_ACTIONS.PERSISTENT}`,
|
||||
},
|
||||
onSubmit: (rows) => async () => {
|
||||
const ids = rows?.map?.(({ original }) => original?.ID)
|
||||
await Promise.all(
|
||||
ids.map((id) => persistent({ id, persistent: true }))
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
accessor: IMAGE_ACTIONS.NON_PERSISTENT,
|
||||
name: T.NonPersistyent,
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
title: T.NonPersistyent,
|
||||
children: MessageToConfirmAction,
|
||||
dataCy: `modal-${IMAGE_ACTIONS.NON_PERSISTENT}`,
|
||||
},
|
||||
onSubmit: (rows) => async () => {
|
||||
const ids = rows?.map?.(({ original }) => original?.ID)
|
||||
await Promise.all(
|
||||
ids.map((id) => persistent({ id, persistent: false }))
|
||||
)
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
tooltip: T.Ownership,
|
||||
icon: Group,
|
||||
selected: true,
|
||||
color: 'secondary',
|
||||
dataCy: 'image-ownership',
|
||||
options: [
|
||||
{
|
||||
accessor: IMAGE_ACTIONS.CHANGE_OWNER,
|
||||
disabled: isDisabled(IMAGE_ACTIONS.CHANGE_OWNER),
|
||||
name: T.ChangeOwner,
|
||||
dialogProps: {
|
||||
title: T.ChangeOwner,
|
||||
subheader: SubHeader,
|
||||
dataCy: `modal-${IMAGE_ACTIONS.CHANGE_OWNER}`,
|
||||
},
|
||||
form: ChangeUserForm,
|
||||
onSubmit: (rows) => async (newOwnership) => {
|
||||
const ids = rows?.map?.(({ original }) => original?.ID)
|
||||
await Promise.all(
|
||||
ids.map((id) => changeOwnership({ id, ...newOwnership }))
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
accessor: IMAGE_ACTIONS.CHANGE_GROUP,
|
||||
disabled: isDisabled(IMAGE_ACTIONS.CHANGE_GROUP),
|
||||
name: T.ChangeGroup,
|
||||
dialogProps: {
|
||||
title: T.ChangeGroup,
|
||||
subheader: SubHeader,
|
||||
dataCy: `modal-${IMAGE_ACTIONS.CHANGE_GROUP}`,
|
||||
},
|
||||
form: ChangeGroupForm,
|
||||
onSubmit: (rows) => async (newOwnership) => {
|
||||
const ids = rows?.map?.(({ original }) => original?.ID)
|
||||
await Promise.all(
|
||||
ids.map((id) => changeOwnership({ id, ...newOwnership }))
|
||||
)
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
accessor: IMAGE_ACTIONS.DELETE,
|
||||
tooltip: T.Delete,
|
||||
icon: Trash,
|
||||
color: 'error',
|
||||
selected: { min: 1 },
|
||||
dataCy: `image_${IMAGE_ACTIONS.DELETE}`,
|
||||
options: [
|
||||
{
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
title: T.Delete,
|
||||
dataCy: `modal-${IMAGE_ACTIONS.DELETE}`,
|
||||
children: MessageToConfirmAction,
|
||||
},
|
||||
onSubmit: (rows) => async () => {
|
||||
const ids = rows?.map?.(({ original }) => original?.ID)
|
||||
await Promise.all(ids.map((id) => deleteImage({ id })))
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
[view]
|
||||
)
|
||||
|
||||
return imageActions
|
||||
}
|
||||
|
||||
export default Actions
|
@ -21,7 +21,7 @@ import { useGetVmsQuery } from 'client/features/OneApi/vm'
|
||||
import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced'
|
||||
import VmColumns from 'client/components/Tables/Vms/columns'
|
||||
import VmRow from 'client/components/Tables/Vms/row'
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
import { RESOURCE_NAMES, VM_STATES, STATES } from 'client/constants'
|
||||
|
||||
const DEFAULT_DATA_CY = 'vms'
|
||||
|
||||
@ -55,7 +55,7 @@ const VmsTable = (props) => {
|
||||
?.filter((vm) =>
|
||||
host?.ID ? [host?.VMS?.ID ?? []].flat().includes(vm.ID) : true
|
||||
)
|
||||
?.filter(({ STATE }) => STATE !== '6') ?? [],
|
||||
?.filter(({ STATE }) => VM_STATES[STATE]?.name !== STATES.DONE) ?? [],
|
||||
}),
|
||||
})
|
||||
|
||||
|
@ -1,18 +1,3 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
|
||||
* *
|
||||
|
@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import PropTypes from 'prop-types'
|
||||
import { ReactElement } from 'react'
|
||||
import { InfoEmpty } from 'iconoir-react'
|
||||
|
||||
@ -24,19 +25,23 @@ import { T } from 'client/constants'
|
||||
/**
|
||||
* Renders default empty tab.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {string} props.label - label string
|
||||
* @returns {ReactElement} Empty tab
|
||||
*/
|
||||
const EmptyTab = () => {
|
||||
const EmptyTab = ({ label = T.NoDataAvailable }) => {
|
||||
const classes = useStyles()
|
||||
|
||||
return (
|
||||
<span className={classes.noDataMessage}>
|
||||
<InfoEmpty />
|
||||
<Translate word={T.NoDataAvailable} />
|
||||
<Translate word={label} />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
EmptyTab.propTypes = {
|
||||
label: PropTypes.string,
|
||||
}
|
||||
EmptyTab.displayName = 'EmptyTab'
|
||||
|
||||
export default EmptyTab
|
||||
|
@ -0,0 +1,142 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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 { memo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { Trash, UndoAction } from 'iconoir-react'
|
||||
|
||||
import {
|
||||
useFlattenImageSnapshotMutation,
|
||||
useRevertImageSnapshotMutation,
|
||||
useDeleteImageSnapshotMutation,
|
||||
} from 'client/features/OneApi/image'
|
||||
import ButtonToTriggerForm from 'client/components/Forms/ButtonToTriggerForm'
|
||||
|
||||
import { Tr, Translate } from 'client/components/HOC'
|
||||
import { T, IMAGE_ACTIONS } from 'client/constants'
|
||||
|
||||
const SnapshotFlattenAction = memo(({ id, snapshot }) => {
|
||||
const [flattenImageSnapshot] = useFlattenImageSnapshotMutation()
|
||||
const { ID, NAME = T.Snapshot } = snapshot
|
||||
|
||||
const handleDelete = async () => {
|
||||
await flattenImageSnapshot({ id, snapshot: ID })
|
||||
}
|
||||
|
||||
return (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
'data-cy': IMAGE_ACTIONS.SNAPSHOT_FLATTEN,
|
||||
tooltip: Tr(T.Flatten),
|
||||
label: Tr(T.Flatten),
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
title: (
|
||||
<Translate word={T.FlattenSnapshot} values={`#${ID} - ${NAME}`} />
|
||||
),
|
||||
children: (
|
||||
<>
|
||||
<p>{Tr(T.DeleteOtherSnapshots)}</p>
|
||||
<p>{Tr(T.DoYouWantProceed)}</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
onSubmit: handleDelete,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
const SnapshotRevertAction = memo(({ id, snapshot }) => {
|
||||
const [revertImageSnapshot] = useRevertImageSnapshotMutation()
|
||||
const { ID, NAME = T.Snapshot } = snapshot
|
||||
const handleRevert = async () => {
|
||||
await revertImageSnapshot({ id, snapshot: ID })
|
||||
}
|
||||
|
||||
return (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
'data-cy': IMAGE_ACTIONS.SNAPSHOT_REVERT,
|
||||
icon: <UndoAction />,
|
||||
tooltip: Tr(T.Revert),
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
title: (
|
||||
<Translate word={T.RevertSomething} values={`#${ID} - ${NAME}`} />
|
||||
),
|
||||
children: <p>{Tr(T.DoYouWantProceed)}</p>,
|
||||
},
|
||||
onSubmit: handleRevert,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
const SnapshotDeleteAction = memo(({ id, snapshot }) => {
|
||||
const [deleteImageSnapshot] = useDeleteImageSnapshotMutation()
|
||||
const { ID, NAME = T.Snapshot } = snapshot
|
||||
|
||||
const handleDelete = async () => {
|
||||
await deleteImageSnapshot({ id, snapshot: ID })
|
||||
}
|
||||
|
||||
return (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
'data-cy': IMAGE_ACTIONS.SNAPSHOT_DELETE,
|
||||
icon: <Trash />,
|
||||
tooltip: Tr(T.Delete),
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
title: (
|
||||
<Translate word={T.DeleteSomething} values={`#${ID} - ${NAME}`} />
|
||||
),
|
||||
children: <p>{Tr(T.DoYouWantProceed)}</p>,
|
||||
},
|
||||
onSubmit: handleDelete,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
const ActionPropTypes = {
|
||||
id: PropTypes.string,
|
||||
snapshot: PropTypes.object,
|
||||
}
|
||||
|
||||
SnapshotFlattenAction.propTypes = ActionPropTypes
|
||||
SnapshotFlattenAction.displayName = 'SnapshotFlattenAction'
|
||||
|
||||
SnapshotRevertAction.propTypes = ActionPropTypes
|
||||
SnapshotRevertAction.displayName = 'SnapshotRevertAction'
|
||||
|
||||
SnapshotDeleteAction.propTypes = ActionPropTypes
|
||||
SnapshotDeleteAction.displayName = 'SnapshotDeleteAction'
|
||||
|
||||
export { SnapshotFlattenAction, SnapshotDeleteAction, SnapshotRevertAction }
|
@ -0,0 +1,84 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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 { ReactElement, useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { Stack } from '@mui/material'
|
||||
import { T } from 'client/constants'
|
||||
import { useGetImageQuery } from 'client/features/OneApi/image'
|
||||
import EmptyTab from 'client/components/Tabs/EmptyTab'
|
||||
import ImageSnapshotCard from 'client/components/Cards/ImageSnapshotCard'
|
||||
import {
|
||||
SnapshotFlattenAction,
|
||||
SnapshotDeleteAction,
|
||||
SnapshotRevertAction,
|
||||
} from 'client/components/Tabs/Image/Snapshots/Actions'
|
||||
|
||||
import { getSnapshots } from 'client/models/Image'
|
||||
|
||||
/**
|
||||
* Renders the list of disks from a VM.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {object} props.tabProps - Tab information
|
||||
* @param {string[]} props.tabProps.actions - Actions tab
|
||||
* @param {string} props.id - Image id
|
||||
* @returns {ReactElement} Storage tab
|
||||
*/
|
||||
const ImageStorageTab = ({ tabProps: { actions } = {}, id }) => {
|
||||
const { data: image = {} } = useGetImageQuery({ id })
|
||||
|
||||
const [snapshots] = useMemo(() => [getSnapshots(image)], [image])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Stack gap="1em" py="0.8em">
|
||||
{snapshots.length ? (
|
||||
snapshots?.map?.((snapshot) => (
|
||||
<ImageSnapshotCard
|
||||
key={snapshot.ID}
|
||||
snapshot={snapshot}
|
||||
actions={() => (
|
||||
<>
|
||||
{actions.snapshot_flatten && (
|
||||
<SnapshotFlattenAction id={id} snapshot={snapshot} />
|
||||
)}
|
||||
{actions.snapshot_revert && (
|
||||
<SnapshotRevertAction id={id} snapshot={snapshot} />
|
||||
)}
|
||||
{actions.snapshot_delete && (
|
||||
<SnapshotDeleteAction id={id} snapshot={snapshot} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<EmptyTab label={T.NotSnapshotCurrenty} />
|
||||
)}
|
||||
</Stack>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ImageStorageTab.propTypes = {
|
||||
tabProps: PropTypes.object,
|
||||
id: PropTypes.string,
|
||||
}
|
||||
|
||||
ImageStorageTab.displayName = 'ImageStorageTab'
|
||||
|
||||
export default ImageStorageTab
|
59
src/fireedge/src/client/components/Tabs/Image/Vms/index.js
Normal file
59
src/fireedge/src/client/components/Tabs/Image/Vms/index.js
Normal file
@ -0,0 +1,59 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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 { ReactElement } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { T } from 'client/constants'
|
||||
import EmptyTab from 'client/components/Tabs/EmptyTab'
|
||||
import { useHistory, generatePath } from 'react-router-dom'
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
import { useGetImageQuery } from 'client/features/OneApi/image'
|
||||
import { VmsTable } from 'client/components/Tables'
|
||||
|
||||
/**
|
||||
* Renders mainly Vms tab.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {string} props.id - Image id
|
||||
* @returns {ReactElement} vms tab
|
||||
*/
|
||||
const VmsTab = ({ id }) => {
|
||||
const { data: image = {} } = useGetImageQuery({ id })
|
||||
const path = PATH.INSTANCE.VMS.DETAIL
|
||||
const history = useHistory()
|
||||
|
||||
const handleRowClick = (rowId) => {
|
||||
history.push(generatePath(path, { id: String(rowId) }))
|
||||
}
|
||||
|
||||
return (
|
||||
<VmsTable
|
||||
disableGlobalSort
|
||||
displaySelectedRows
|
||||
host={image}
|
||||
onRowClick={(row) => handleRowClick(row.ID)}
|
||||
noDataMessage={<EmptyTab label={T.NotVmsCurrenty} />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
VmsTab.propTypes = {
|
||||
tabProps: PropTypes.object,
|
||||
id: PropTypes.string,
|
||||
}
|
||||
|
||||
VmsTab.displayName = 'VmsTab'
|
||||
|
||||
export default VmsTab
|
@ -24,10 +24,14 @@ import { RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
import Tabs from 'client/components/Tabs'
|
||||
import Info from 'client/components/Tabs/Image/Info'
|
||||
import Vms from 'client/components/Tabs/Image/Vms'
|
||||
import Snapshots from 'client/components/Tabs/Image/Snapshots'
|
||||
|
||||
const getTabComponent = (tabName) =>
|
||||
({
|
||||
info: Info,
|
||||
vms: Vms,
|
||||
snapshot: Snapshots,
|
||||
}[tabName])
|
||||
|
||||
const ImageTabs = memo(({ id }) => {
|
||||
|
@ -165,6 +165,13 @@ export const IMAGE_STATES = [
|
||||
export const IMAGE_ACTIONS = {
|
||||
CREATE_DIALOG: 'create_dialog',
|
||||
DELETE: 'delete',
|
||||
LOCK: 'lock',
|
||||
UNLOCK: 'unlock',
|
||||
CLONE: 'clone',
|
||||
ENABLE: 'enable',
|
||||
DISABLE: 'disable',
|
||||
PERSISTENT: 'persistent',
|
||||
NON_PERSISTENT: 'nonpersistent',
|
||||
|
||||
// INFORMATION
|
||||
RENAME: ACTIONS.RENAME,
|
||||
@ -172,4 +179,7 @@ export const IMAGE_ACTIONS = {
|
||||
CHANGE_GROUP: ACTIONS.CHANGE_GROUP,
|
||||
CHANGE_TYPE: 'chtype',
|
||||
CHANGE_PERS: 'persistent',
|
||||
SNAPSHOT_FLATTEN: 'flatten',
|
||||
SNAPSHOT_REVERT: 'revert',
|
||||
SNAPSHOT_DELETE: 'delete',
|
||||
}
|
||||
|
@ -128,6 +128,7 @@ export const INPUT_TYPES = {
|
||||
TEXT: 'text',
|
||||
TABLE: 'table',
|
||||
TOGGLE: 'toggle',
|
||||
DOCKERFILE: 'dockerfile',
|
||||
}
|
||||
|
||||
export const DEBUG_LEVEL = {
|
||||
|
@ -71,6 +71,8 @@ module.exports = {
|
||||
CreateServiceTemplate: 'Create Service Template',
|
||||
CreateVirtualNetwork: 'Create Virtual Network',
|
||||
CreateVmTemplate: 'Create VM Template',
|
||||
CreateImage: 'Create Image',
|
||||
CreateDockerfile: 'Create Dockerfile',
|
||||
CurrentGroup: 'Current group: %s',
|
||||
CurrentOwner: 'Current owner: %s',
|
||||
Delete: 'Delete',
|
||||
@ -83,6 +85,7 @@ module.exports = {
|
||||
DeleteAddressRange: 'Delete Address Range',
|
||||
DeleteTemplate: 'Delete Template',
|
||||
DeleteVirtualNetwork: 'Delete Virtual Network',
|
||||
DeleteOtherSnapshots: 'This will delete all the other image snapshots',
|
||||
Deploy: 'Deploy',
|
||||
DeployServiceTemplate: 'Deploy Service Template',
|
||||
Detach: 'Detach',
|
||||
@ -95,6 +98,8 @@ module.exports = {
|
||||
Enable: 'Enable',
|
||||
Failure: 'Failure',
|
||||
Finish: 'Finish',
|
||||
Flatten: 'Flatten',
|
||||
FlattenSnapshot: 'Flatten %s',
|
||||
Hold: 'Hold',
|
||||
Import: 'Import',
|
||||
Info: 'Info',
|
||||
@ -138,6 +143,7 @@ module.exports = {
|
||||
Select: 'Select',
|
||||
SelectCluster: 'Select Cluster',
|
||||
SelectDatastore: 'Select a Datastore to store the resource',
|
||||
SelectDatastoreImage: 'Select a Datastore',
|
||||
SelectDockerHubTag: 'Select DockerHub image tag (default latest)',
|
||||
SelectGroup: 'Select a group',
|
||||
SelectHost: 'Select a host',
|
||||
@ -358,6 +364,21 @@ module.exports = {
|
||||
Marketplaces: 'Marketplaces',
|
||||
App: 'App',
|
||||
Apps: 'Apps',
|
||||
Os: 'Operating system image',
|
||||
Cdrom: 'Readonly CD-ROM',
|
||||
Datablock: 'Generic storage datablock',
|
||||
Path: 'Path/URL',
|
||||
ImagePath: 'Path in OpenNebula server or URL',
|
||||
Upload: 'Upload',
|
||||
EmptyDisk: 'Empty disk image',
|
||||
ImageSize: 'Image size, in Megabytes',
|
||||
Vd: 'Virtio',
|
||||
Sd: 'SCSI/SATA',
|
||||
Hd: 'Parallel ATA (IDE)',
|
||||
CustomBus: 'Custom bus',
|
||||
Fs: 'Fs',
|
||||
CustomFormat: 'Custom Format',
|
||||
Dockerfile: 'Dockerfile',
|
||||
|
||||
/* sections - templates & instances */
|
||||
Instances: 'Instances',
|
||||
@ -560,6 +581,7 @@ module.exports = {
|
||||
CustomAttributes: 'Custom Attributes',
|
||||
Hypervisor: 'Hypervisor',
|
||||
Logo: 'Logo',
|
||||
MakePersistent: 'Make Persistent',
|
||||
MakeNewImagePersistent: 'Make the new images persistent',
|
||||
TemplateName: 'Template name',
|
||||
Virtualization: 'Virtualization',
|
||||
@ -1039,6 +1061,7 @@ module.exports = {
|
||||
BasePath: 'Base path',
|
||||
FileSystemType: 'Filesystem type',
|
||||
Persistent: 'Persistent',
|
||||
NonPersistyent: 'Non Persistent',
|
||||
RunningVMs: 'Running VMs',
|
||||
/* Disk - general */
|
||||
DiskType: 'Disk type',
|
||||
@ -1074,6 +1097,7 @@ module.exports = {
|
||||
Iothread id used by this disk. Default is round robin.
|
||||
Can be used only if IOTHREADS > 0. If this input is disabled
|
||||
please first configure IOTHREADS value on OS & CPU -> Features`,
|
||||
ImageLocation: 'Image Location',
|
||||
|
||||
/* Provision schema */
|
||||
/* Provision - general */
|
||||
@ -1097,6 +1121,11 @@ module.exports = {
|
||||
Mandatory: 'Mandatory',
|
||||
PressKeysToAddAValue: 'Press any of the following keys to add a value: %s',
|
||||
|
||||
/** Image */
|
||||
NotVmsCurrenty: 'There are currently no VMs associated with this image',
|
||||
NotSnapshotCurrenty:
|
||||
'There are currently no snapshots associated with this image',
|
||||
|
||||
/* Validation */
|
||||
/* Validation - mixed */
|
||||
'validation.mixed.default': 'Is invalid',
|
||||
|
81
src/fireedge/src/client/containers/Images/Create.js
Normal file
81
src/fireedge/src/client/containers/Images/Create.js
Normal file
@ -0,0 +1,81 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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 { ReactElement } from 'react'
|
||||
import { useHistory } from 'react-router'
|
||||
|
||||
import { jsonToXml } from 'client/models/Helper'
|
||||
import { useGeneralApi } from 'client/features/General'
|
||||
import {
|
||||
useAllocateImageMutation,
|
||||
useUploadImageMutation,
|
||||
} from 'client/features/OneApi/image'
|
||||
import { useGetDatastoresQuery } from 'client/features/OneApi/datastore'
|
||||
|
||||
import {
|
||||
DefaultFormStepper,
|
||||
SkeletonStepsForm,
|
||||
} from 'client/components/FormStepper'
|
||||
import { CreateForm } from 'client/components/Forms/Image'
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
|
||||
/**
|
||||
* Displays the creation or modification form to a VM Template.
|
||||
*
|
||||
* @returns {ReactElement} VM Template form
|
||||
*/
|
||||
function CreateImage() {
|
||||
const history = useHistory()
|
||||
const [allocate] = useAllocateImageMutation()
|
||||
const [upload] = useUploadImageMutation()
|
||||
const { enqueueSuccess, uploadSnackbar } = useGeneralApi()
|
||||
useGetDatastoresQuery(undefined, { refetchOnMountOrArgChange: false })
|
||||
|
||||
const onSubmit = async ({ template, datastore, file }) => {
|
||||
if (file) {
|
||||
const uploadProcess = (progressEvent) => {
|
||||
const percentCompleted = Math.round(
|
||||
(progressEvent.loaded / progressEvent.total) * 100
|
||||
)
|
||||
uploadSnackbar(percentCompleted)
|
||||
percentCompleted === 100 && uploadSnackbar(0)
|
||||
}
|
||||
try {
|
||||
const fileUploaded = await upload({
|
||||
file,
|
||||
uploadProcess,
|
||||
}).unwrap()
|
||||
template.PATH = fileUploaded[0]
|
||||
} catch {}
|
||||
}
|
||||
|
||||
try {
|
||||
const newTemplateId = await allocate({
|
||||
template: jsonToXml(template),
|
||||
datastore,
|
||||
}).unwrap()
|
||||
history.push(PATH.STORAGE.IMAGES.LIST)
|
||||
enqueueSuccess(`Image created - #${newTemplateId}`)
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return (
|
||||
<CreateForm onSubmit={onSubmit} fallback={<SkeletonStepsForm />}>
|
||||
{(config) => <DefaultFormStepper {...config} />}
|
||||
</CreateForm>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateImage
|
56
src/fireedge/src/client/containers/Images/Dockerfile.js
Normal file
56
src/fireedge/src/client/containers/Images/Dockerfile.js
Normal file
@ -0,0 +1,56 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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 { ReactElement } from 'react'
|
||||
import { useHistory } from 'react-router'
|
||||
|
||||
import { useGeneralApi } from 'client/features/General'
|
||||
import { useAllocateImageMutation } from 'client/features/OneApi/image'
|
||||
import { useGetDatastoresQuery } from 'client/features/OneApi/datastore'
|
||||
|
||||
import {
|
||||
DefaultFormStepper,
|
||||
SkeletonStepsForm,
|
||||
} from 'client/components/FormStepper'
|
||||
import { CreateDockerfileForm } from 'client/components/Forms/Image'
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
|
||||
/**
|
||||
* Displays the creation or modification form to a VM Template.
|
||||
*
|
||||
* @returns {ReactElement} VM Template form
|
||||
*/
|
||||
function CreateDockerfile() {
|
||||
const history = useHistory()
|
||||
const [allocate] = useAllocateImageMutation()
|
||||
const { enqueueSuccess } = useGeneralApi()
|
||||
useGetDatastoresQuery(undefined, { refetchOnMountOrArgChange: false })
|
||||
|
||||
const onSubmit = async (stepTemplate) => {
|
||||
try {
|
||||
const newTemplateId = await allocate(stepTemplate).unwrap()
|
||||
history.push(PATH.STORAGE.IMAGES.LIST)
|
||||
enqueueSuccess(`Image created - #${newTemplateId}`)
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return (
|
||||
<CreateDockerfileForm onSubmit={onSubmit} fallback={<SkeletonStepsForm />}>
|
||||
{(config) => <DefaultFormStepper {...config} />}
|
||||
</CreateDockerfileForm>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateDockerfile
|
@ -21,11 +21,9 @@ import Cancel from 'iconoir-react/dist/Cancel'
|
||||
import { Typography, Box, Stack, Chip } from '@mui/material'
|
||||
import { Row } from 'react-table'
|
||||
|
||||
import {
|
||||
useLazyGetImageQuery,
|
||||
useUpdateImageMutation,
|
||||
} from 'client/features/OneApi/image'
|
||||
import { useLazyGetImageQuery } from 'client/features/OneApi/image'
|
||||
import { ImagesTable } from 'client/components/Tables'
|
||||
import ImageActions from 'client/components/Tables/Images/actions'
|
||||
import ImageTabs from 'client/components/Tabs/Image'
|
||||
import SplitPane from 'client/components/SplitPane'
|
||||
import MultipleTags from 'client/components/MultipleTags'
|
||||
@ -40,6 +38,7 @@ import { T, Image } from 'client/constants'
|
||||
*/
|
||||
function Images() {
|
||||
const [selectedRows, onSelectedRowsChange] = useState(() => [])
|
||||
const actions = ImageActions()
|
||||
|
||||
const hasSelectedRows = selectedRows?.length > 0
|
||||
const moreThanOneSelected = selectedRows?.length > 1
|
||||
@ -50,7 +49,7 @@ function Images() {
|
||||
<Box height={1} {...(hasSelectedRows && getGridProps())}>
|
||||
<ImagesTable
|
||||
onSelectedRowsChange={onSelectedRowsChange}
|
||||
useUpdateMutation={useUpdateImageMutation}
|
||||
globalActions={actions}
|
||||
/>
|
||||
|
||||
{hasSelectedRows && (
|
||||
@ -82,24 +81,19 @@ function Images() {
|
||||
* @returns {ReactElement} Image details
|
||||
*/
|
||||
const InfoTabs = memo(({ image, gotoPage, unselect }) => {
|
||||
const [get, { data: lazyData, isFetching }] = useLazyGetImageQuery()
|
||||
const [getImage, { data: lazyData, isFetching }] = useLazyGetImageQuery()
|
||||
const id = lazyData?.ID ?? image.ID
|
||||
const name = lazyData?.NAME ?? image.NAME
|
||||
|
||||
return (
|
||||
<Stack overflow="auto">
|
||||
<Stack direction="row" alignItems="center" gap={1} mx={1} mb={1}>
|
||||
<Typography color="text.primary" noWrap flexGrow={1}>
|
||||
{`#${id} | ${name}`}
|
||||
</Typography>
|
||||
|
||||
{/* -- ACTIONS -- */}
|
||||
<Stack direction="row" alignItems="center" gap={1} mb={1}>
|
||||
<SubmitButton
|
||||
data-cy="detail-refresh"
|
||||
icon={<RefreshDouble />}
|
||||
tooltip={Tr(T.Refresh)}
|
||||
isSubmitting={isFetching}
|
||||
onClick={() => get({ id })}
|
||||
onClick={() => getImage({ id })}
|
||||
/>
|
||||
{typeof gotoPage === 'function' && (
|
||||
<SubmitButton
|
||||
@ -117,9 +111,11 @@ const InfoTabs = memo(({ image, gotoPage, unselect }) => {
|
||||
onClick={() => unselect()}
|
||||
/>
|
||||
)}
|
||||
{/* -- END ACTIONS -- */}
|
||||
<Typography color="text.primary" noWrap>
|
||||
{`#${id} | ${name}`}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<ImageTabs id={id} />
|
||||
<ImageTabs id={image.ID} />
|
||||
</Stack>
|
||||
)
|
||||
})
|
||||
|
@ -22,6 +22,7 @@ export const changeAppTitle = createAction('Change App title')
|
||||
|
||||
export const dismissSnackbar = createAction('Dismiss snackbar')
|
||||
export const deleteSnackbar = createAction('Delete snackbar')
|
||||
export const setUploadSnackbar = createAction('Change upload snackbar')
|
||||
|
||||
export const enqueueSnackbar = createAction(
|
||||
'Enqueue snackbar',
|
||||
|
@ -32,6 +32,8 @@ export const useGeneralApi = () => {
|
||||
changeAppTitle: (appTitle) => dispatch(actions.changeAppTitle(appTitle)),
|
||||
changeZone: (zone) => dispatch(actions.changeZone(zone)),
|
||||
|
||||
uploadSnackbar: (percent) => dispatch(actions.setUploadSnackbar(percent)),
|
||||
|
||||
// dismiss all if no key has been defined
|
||||
dismissSnackbar: (key) =>
|
||||
dispatch(actions.dismissSnackbar({ key, dismissAll: !key })),
|
||||
|
@ -27,6 +27,7 @@ const initial = {
|
||||
withGroupSwitcher: false,
|
||||
isLoading: false,
|
||||
isFixMenu: false,
|
||||
upload: 0,
|
||||
notifications: [],
|
||||
}
|
||||
|
||||
@ -64,7 +65,11 @@ const slice = createSlice({
|
||||
...state,
|
||||
zone: payload,
|
||||
}))
|
||||
|
||||
/* UPLOAD NOTIFICATION */
|
||||
.addCase(actions.setUploadSnackbar, (state, { payload }) => ({
|
||||
...state,
|
||||
upload: payload,
|
||||
}))
|
||||
/* NOTIFICATION ACTIONS */
|
||||
.addCase(actions.enqueueSnackbar, (state, { payload }) => {
|
||||
const { key, options, message } = payload
|
||||
|
@ -20,6 +20,7 @@ import {
|
||||
ONE_RESOURCES_POOL,
|
||||
} from 'client/features/OneApi'
|
||||
import { UpdateFromSocket } from 'client/features/OneApi/socket'
|
||||
import http from 'client/utils/rest'
|
||||
import {
|
||||
FilterFlag,
|
||||
Image,
|
||||
@ -101,6 +102,35 @@ const imageApi = oneApi.injectEndpoints({
|
||||
},
|
||||
invalidatesTags: [IMAGE_POOL],
|
||||
}),
|
||||
uploadImage: builder.mutation({
|
||||
/**
|
||||
* Upload image.
|
||||
*
|
||||
* @param {object} params - request params
|
||||
* @param {object} params.file - image file
|
||||
* @param {Function} params.uploadProcess - upload process function
|
||||
* @returns {number} Virtual machine id
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
queryFn: async ({ file, uploadProcess }) => {
|
||||
try {
|
||||
const data = new FormData()
|
||||
data.append('files', file)
|
||||
const response = await http.request({
|
||||
url: '/api/image/upload',
|
||||
method: 'POST',
|
||||
data,
|
||||
onUploadProgress: uploadProcess,
|
||||
})
|
||||
|
||||
return { data: response.data }
|
||||
} catch (axiosError) {
|
||||
const { response } = axiosError
|
||||
|
||||
return { error: { status: response?.status, data: response?.data } }
|
||||
}
|
||||
},
|
||||
}),
|
||||
cloneImage: builder.mutation({
|
||||
/**
|
||||
* Clones an existing image.
|
||||
@ -408,4 +438,5 @@ export const {
|
||||
useFlattenImageSnapshotMutation,
|
||||
useLockImageMutation,
|
||||
useUnlockImageMutation,
|
||||
useUploadImageMutation,
|
||||
} = imageApi
|
||||
|
@ -81,6 +81,22 @@ const systemApi = oneApi.injectEndpoints({
|
||||
providesTags: [{ type: SYSTEM, id: 'sunstone-views' }],
|
||||
keepUnusedDataFor: 600,
|
||||
}),
|
||||
getSunstoneConfig: builder.query({
|
||||
/**
|
||||
* Returns the Sunstone configuration for resource tabs.
|
||||
*
|
||||
* @returns {object} The loaded sunstone view files
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: () => {
|
||||
const name = SunstoneActions.SUNSTONE_CONFIG
|
||||
const command = { name, ...SunstoneCommands[name] }
|
||||
|
||||
return { command }
|
||||
},
|
||||
providesTags: [{ type: SYSTEM, id: 'sunstone-config' }],
|
||||
keepUnusedDataFor: 600,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
@ -90,6 +106,8 @@ export const {
|
||||
useLazyGetOneVersionQuery,
|
||||
useGetOneConfigQuery,
|
||||
useLazyGetOneConfigQuery,
|
||||
useGetSunstoneConfigQuery,
|
||||
useLazyGetSunstoneConfigQuery,
|
||||
useGetSunstoneViewsQuery,
|
||||
useLazyGetSunstoneViewsQuery,
|
||||
} = systemApi
|
||||
|
@ -19,6 +19,7 @@ import {
|
||||
IMAGE_STATES,
|
||||
STATES,
|
||||
Image,
|
||||
DiskSnapshot,
|
||||
} from 'client/constants'
|
||||
import { prettyBytes } from 'client/utils'
|
||||
|
||||
@ -60,3 +61,15 @@ export const getDiskName = ({ IMAGE, SIZE, TYPE, FORMAT } = {}) => {
|
||||
|
||||
return IMAGE ?? { fs: `${FORMAT} - ${size}`, swap: size }[type]
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Image} image - Image
|
||||
* @returns {DiskSnapshot[]} List of snapshots from resource
|
||||
*/
|
||||
export const getSnapshots = (image) => {
|
||||
const {
|
||||
SNAPSHOTS: { SNAPSHOT },
|
||||
} = image ?? {}
|
||||
|
||||
return [SNAPSHOT].flat().filter(Boolean)
|
||||
}
|
||||
|
@ -157,59 +157,60 @@ const upload = (
|
||||
const { app, files, public: publicFile } = params
|
||||
const { id, user, password } = userData
|
||||
if (
|
||||
global.paths.CPI &&
|
||||
app &&
|
||||
checkValidApp(app) &&
|
||||
files &&
|
||||
id &&
|
||||
user &&
|
||||
password
|
||||
) {
|
||||
const oneConnect = oneConnection(user, password)
|
||||
checkUserAdmin(
|
||||
oneConnect,
|
||||
id,
|
||||
(admin = false) => {
|
||||
const pathUserData =
|
||||
publicFile && admin ? `${app}` : `${app}${sep}${id}`
|
||||
const pathUser = `${global.paths.CPI}${sep}${pathUserData}`
|
||||
if (!existsSync(pathUser)) {
|
||||
mkdirsSync(pathUser)
|
||||
}
|
||||
let method = ok
|
||||
let message = ''
|
||||
const data = []
|
||||
for (const file of files) {
|
||||
if (file && file.originalname && file.path && file.filename) {
|
||||
const extFile = extname(file.originalname)
|
||||
try {
|
||||
const filenameApi = `${pathUserData}${sep}${file.filename}${extFile}`
|
||||
const filename = `${pathUser}${sep}${file.filename}${extFile}`
|
||||
moveSync(file.path, filename)
|
||||
data.push(filenameApi)
|
||||
} catch (error) {
|
||||
method = internalServerError
|
||||
message = error && error.message
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
res.locals.httpCode = httpResponse(
|
||||
method,
|
||||
data.length ? data : '',
|
||||
message
|
||||
)
|
||||
next()
|
||||
},
|
||||
() => {
|
||||
res.locals.httpCode = internalServerError
|
||||
next()
|
||||
}
|
||||
!(
|
||||
global.paths.CPI &&
|
||||
app &&
|
||||
checkValidApp(app) &&
|
||||
files &&
|
||||
id &&
|
||||
user &&
|
||||
password
|
||||
)
|
||||
} else {
|
||||
) {
|
||||
res.locals.httpCode = httpBadRequest
|
||||
next()
|
||||
}
|
||||
|
||||
const oneConnect = oneConnection(user, password)
|
||||
checkUserAdmin(
|
||||
oneConnect,
|
||||
id,
|
||||
(admin = false) => {
|
||||
const pathUserData = publicFile && admin ? `${app}` : `${app}${sep}${id}`
|
||||
const pathUser = `${global.paths.CPI}${sep}${pathUserData}`
|
||||
if (!existsSync(pathUser)) {
|
||||
mkdirsSync(pathUser)
|
||||
}
|
||||
let method = ok
|
||||
let message = ''
|
||||
const data = []
|
||||
for (const file of files) {
|
||||
if (file && file.originalname && file.path && file.filename) {
|
||||
const extFile = extname(file.originalname)
|
||||
try {
|
||||
const filenameApi = `${pathUserData}${sep}${file.filename}${extFile}`
|
||||
const filename = `${pathUser}${sep}${file.filename}${extFile}`
|
||||
moveSync(file.path, filename)
|
||||
data.push(filenameApi)
|
||||
} catch (error) {
|
||||
method = internalServerError
|
||||
message = error && error.message
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
res.locals.httpCode = httpResponse(
|
||||
method,
|
||||
data.length ? data : '',
|
||||
message
|
||||
)
|
||||
next()
|
||||
},
|
||||
() => {
|
||||
res.locals.httpCode = internalServerError
|
||||
next()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
55
src/fireedge/src/server/routes/api/image/functions.js
Normal file
55
src/fireedge/src/server/routes/api/image/functions.js
Normal file
@ -0,0 +1,55 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
const { defaults, httpCodes } = require('server/utils/constants')
|
||||
|
||||
const { httpResponse } = require('server/utils/server')
|
||||
|
||||
const { defaultEmptyFunction } = defaults
|
||||
|
||||
const { ok, badRequest } = httpCodes
|
||||
|
||||
const httpBadRequest = httpResponse(badRequest, '', '')
|
||||
|
||||
/**
|
||||
* Upload File.
|
||||
*
|
||||
* @param {object} res - response http
|
||||
* @param {Function} next - express stepper
|
||||
* @param {string} params - data response http
|
||||
* @param {object} userData - user of http request
|
||||
*/
|
||||
const upload = (
|
||||
res = {},
|
||||
next = defaultEmptyFunction,
|
||||
params = {},
|
||||
userData = {}
|
||||
) => {
|
||||
const { files } = params
|
||||
const { user, password } = userData
|
||||
if (!(files && user && password)) {
|
||||
res.locals.httpCode = httpBadRequest
|
||||
next()
|
||||
}
|
||||
|
||||
const data = files.map((file) => file.path)
|
||||
res.locals.httpCode = httpResponse(ok, data.length ? data : '')
|
||||
next()
|
||||
}
|
||||
|
||||
const functionRoutes = {
|
||||
upload,
|
||||
}
|
||||
module.exports = functionRoutes
|
27
src/fireedge/src/server/routes/api/image/index.js
Normal file
27
src/fireedge/src/server/routes/api/image/index.js
Normal file
@ -0,0 +1,27 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
const { Actions, Commands } = require('server/routes/api/image/routes')
|
||||
const { upload } = require('server/routes/api/image/functions')
|
||||
|
||||
const { IMAGE_UPLOAD } = Actions
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
...Commands[IMAGE_UPLOAD],
|
||||
action: upload,
|
||||
},
|
||||
]
|
42
src/fireedge/src/server/routes/api/image/routes.js
Normal file
42
src/fireedge/src/server/routes/api/image/routes.js
Normal file
@ -0,0 +1,42 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
const { httpMethod } = require('../../../utils/constants/defaults')
|
||||
|
||||
const { POST } = httpMethod
|
||||
|
||||
const basepath = '/image'
|
||||
const IMAGE_UPLOAD = 'image.upload'
|
||||
|
||||
const Actions = {
|
||||
IMAGE_UPLOAD,
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Actions,
|
||||
Commands: {
|
||||
[IMAGE_UPLOAD]: {
|
||||
path: `${basepath}/upload`,
|
||||
httpMethod: POST,
|
||||
auth: true,
|
||||
params: {
|
||||
files: {
|
||||
from: 'files',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
@ -36,6 +36,7 @@ const routes = [
|
||||
'2fa',
|
||||
'auth',
|
||||
'files',
|
||||
'image',
|
||||
'marketapp',
|
||||
'oneflow',
|
||||
'vcenter',
|
||||
|
Loading…
x
Reference in New Issue
Block a user