mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-21 14:50:08 +03:00
parent
6c3fe14e77
commit
bcb12e7104
@ -18,7 +18,9 @@ import {
|
||||
ModernTv as VmsIcons,
|
||||
Shuffle as VRoutersIcons,
|
||||
Archive as TemplatesIcon,
|
||||
GoogleDocs as TemplateIcon,
|
||||
EmptyPage as TemplateIcon,
|
||||
Packages as ServicesIcon,
|
||||
MultiplePagesEmpty as ServiceTemplateIcon,
|
||||
Box as StorageIcon,
|
||||
Db as DatastoreIcon,
|
||||
BoxIso as ImageIcon,
|
||||
@ -52,6 +54,14 @@ const VirtualRouters = loadable(
|
||||
{ ssr: false }
|
||||
)
|
||||
|
||||
const Services = loadable(() => import('client/containers/Services'), {
|
||||
ssr: false,
|
||||
})
|
||||
const ServiceDetail = loadable(
|
||||
() => import('client/containers/Services/Detail'),
|
||||
{ ssr: false }
|
||||
)
|
||||
|
||||
const VmTemplates = loadable(() => import('client/containers/VmTemplates'), {
|
||||
ssr: false,
|
||||
})
|
||||
@ -70,6 +80,17 @@ const VMTemplateDetail = loadable(
|
||||
// const VrTemplates = loadable(() => import('client/containers/VrTemplates'), { ssr: false })
|
||||
// const VmGroups = loadable(() => import('client/containers/VmGroups'), { ssr: false })
|
||||
|
||||
const ServiceTemplates = loadable(
|
||||
() => import('client/containers/ServiceTemplates'),
|
||||
{ ssr: false }
|
||||
)
|
||||
// const DeployServiceTemplates = loadable(() => import('client/containers/ServiceTemplates/Instantiate'), { ssr: false })
|
||||
// const CreateServiceTemplates = loadable(() => import('client/containers/ServiceTemplates/Create'), { ssr: false })
|
||||
const ServiceTemplateDetail = loadable(
|
||||
() => import('client/containers/ServiceTemplates/Detail'),
|
||||
{ ssr: false }
|
||||
)
|
||||
|
||||
const Datastores = loadable(() => import('client/containers/Datastores'), {
|
||||
ssr: false,
|
||||
})
|
||||
@ -137,6 +158,10 @@ export const PATH = {
|
||||
VROUTERS: {
|
||||
LIST: `/${RESOURCE_NAMES.V_ROUTER}`,
|
||||
},
|
||||
SERVICES: {
|
||||
LIST: `/${RESOURCE_NAMES.SERVICE}`,
|
||||
DETAIL: `/${RESOURCE_NAMES.SERVICE}/:id`,
|
||||
},
|
||||
},
|
||||
TEMPLATE: {
|
||||
VMS: {
|
||||
@ -145,6 +170,12 @@ export const PATH = {
|
||||
CREATE: `/${RESOURCE_NAMES.VM_TEMPLATE}/create`,
|
||||
DETAIL: `/${RESOURCE_NAMES.VM_TEMPLATE}/:id`,
|
||||
},
|
||||
SERVICES: {
|
||||
LIST: `/${RESOURCE_NAMES.SERVICE_TEMPLATE}`,
|
||||
DETAIL: `/${RESOURCE_NAMES.SERVICE_TEMPLATE}/:id`,
|
||||
DEPLOY: `/${RESOURCE_NAMES.SERVICE_TEMPLATE}/deploy/`,
|
||||
CREATE: `/${RESOURCE_NAMES.SERVICE_TEMPLATE}/create`,
|
||||
},
|
||||
},
|
||||
STORAGE: {
|
||||
DATASTORES: {
|
||||
@ -231,6 +262,19 @@ const ENDPOINTS = [
|
||||
icon: VRoutersIcons,
|
||||
Component: VirtualRouters,
|
||||
},
|
||||
{
|
||||
title: T.Services,
|
||||
path: PATH.INSTANCE.SERVICES.LIST,
|
||||
sidebar: true,
|
||||
icon: ServicesIcon,
|
||||
Component: Services,
|
||||
},
|
||||
{
|
||||
title: T.Service,
|
||||
description: (params) => `#${params?.id}`,
|
||||
path: PATH.INSTANCE.SERVICES.DETAIL,
|
||||
Component: ServiceDetail,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -265,6 +309,36 @@ const ENDPOINTS = [
|
||||
path: PATH.TEMPLATE.VMS.DETAIL,
|
||||
Component: VMTemplateDetail,
|
||||
},
|
||||
{
|
||||
title: T.ServiceTemplates,
|
||||
path: PATH.TEMPLATE.SERVICES.LIST,
|
||||
sidebar: true,
|
||||
icon: ServiceTemplateIcon,
|
||||
Component: ServiceTemplates,
|
||||
},
|
||||
/* {
|
||||
title: T.DeployServiceTemplate,
|
||||
description: (_, state) =>
|
||||
state?.ID !== undefined && `#${state.ID} ${state.NAME}`,
|
||||
path: PATH.TEMPLATE.SERVICES.DEPLOY,
|
||||
Component: DeployServiceTemplates,
|
||||
},
|
||||
{
|
||||
title: (_, state) =>
|
||||
state?.ID !== undefined
|
||||
? T.UpdateServiceTemplate
|
||||
: T.CreateServiceTemplate,
|
||||
description: (_, state) =>
|
||||
state?.ID !== undefined && `#${state.ID} ${state.NAME}`,
|
||||
path: PATH.TEMPLATE.SERVICES.CREATE,
|
||||
Component: CreateServiceTemplates,
|
||||
}, */
|
||||
{
|
||||
title: T.ServiceTemplate,
|
||||
description: (params) => `#${params?.id}`,
|
||||
path: PATH.TEMPLATE.SERVICES.DETAIL,
|
||||
Component: ServiceTemplateDetail,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
108
src/fireedge/src/client/components/Cards/ServiceCard.js
Normal file
108
src/fireedge/src/client/components/Cards/ServiceCard.js
Normal file
@ -0,0 +1,108 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { WarningCircledOutline as WarningIcon } from 'iconoir-react'
|
||||
import { Typography } from '@mui/material'
|
||||
|
||||
import { useViews } from 'client/features/Auth'
|
||||
import MultipleTags from 'client/components/MultipleTags'
|
||||
import Timer from 'client/components/Timer'
|
||||
import { StatusCircle } from 'client/components/Status'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
|
||||
import {
|
||||
timeFromMilliseconds,
|
||||
getUniqueLabels,
|
||||
getColorFromString,
|
||||
} from 'client/models/Helper'
|
||||
import { getState } from 'client/models/Service'
|
||||
import { T, Service, ACTIONS, RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
const ServiceCard = memo(
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @param {Service} props.service - Service resource
|
||||
* @param {object} props.rootProps - Props to root component
|
||||
* @param {function(string):Promise} [props.onDeleteLabel] - Callback to delete label
|
||||
* @param {ReactElement} [props.actions] - Actions
|
||||
* @returns {ReactElement} - Card
|
||||
*/
|
||||
({ service, rootProps, actions, onDeleteLabel }) => {
|
||||
const classes = rowStyles()
|
||||
const { [RESOURCE_NAMES.SERVICE]: serviceView } = useViews()
|
||||
|
||||
const enableEditLabels =
|
||||
serviceView?.actions?.[ACTIONS.EDIT_LABELS] === true && !!onDeleteLabel
|
||||
|
||||
const {
|
||||
ID,
|
||||
NAME,
|
||||
TEMPLATE: { BODY: { description, labels, start_time: startTime } = {} },
|
||||
} = service
|
||||
|
||||
const { color: stateColor, name: stateName } = getState(service)
|
||||
const time = useMemo(() => timeFromMilliseconds(+startTime), [startTime])
|
||||
|
||||
const uniqueLabels = useMemo(
|
||||
() =>
|
||||
getUniqueLabels(labels).map((label) => ({
|
||||
text: label,
|
||||
stateColor: getColorFromString(label),
|
||||
onDelete: enableEditLabels && onDeleteLabel,
|
||||
})),
|
||||
[labels, enableEditLabels, onDeleteLabel]
|
||||
)
|
||||
|
||||
return (
|
||||
<div {...rootProps} data-cy={`service-template-${ID}`}>
|
||||
<div className={classes.main}>
|
||||
<div className={classes.title}>
|
||||
<StatusCircle color={stateColor} tooltip={stateName} />
|
||||
<Typography noWrap component="span" title={description}>
|
||||
{NAME}
|
||||
</Typography>
|
||||
<span className={classes.labels}>
|
||||
<WarningIcon title={description} />
|
||||
<MultipleTags tags={uniqueLabels} />
|
||||
</span>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span data-cy="id">{`#${ID}`}</span>
|
||||
<span title={time.toFormat('ff')}>
|
||||
<Timer translateWord={T.RegisteredAt} initial={time} />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{actions && <div className={classes.actions}>{actions}</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
ServiceCard.propTypes = {
|
||||
service: PropTypes.object,
|
||||
rootProps: PropTypes.shape({
|
||||
className: PropTypes.string,
|
||||
}),
|
||||
onDeleteLabel: PropTypes.func,
|
||||
actions: PropTypes.any,
|
||||
}
|
||||
|
||||
ServiceCard.displayName = 'ServiceCard'
|
||||
|
||||
export default ServiceCard
|
127
src/fireedge/src/client/components/Cards/ServiceTemplateCard.js
Normal file
127
src/fireedge/src/client/components/Cards/ServiceTemplateCard.js
Normal file
@ -0,0 +1,127 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { Network, Package } from 'iconoir-react'
|
||||
import { Typography } from '@mui/material'
|
||||
|
||||
import { useViews } from 'client/features/Auth'
|
||||
import MultipleTags from 'client/components/MultipleTags'
|
||||
import Timer from 'client/components/Timer'
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
|
||||
import {
|
||||
timeFromMilliseconds,
|
||||
getUniqueLabels,
|
||||
getColorFromString,
|
||||
} from 'client/models/Helper'
|
||||
import { T, ServiceTemplate, ACTIONS, RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
const ServiceTemplateCard = memo(
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @param {ServiceTemplate} props.template - Service Template resource
|
||||
* @param {object} props.rootProps - Props to root component
|
||||
* @param {function(string):Promise} [props.onDeleteLabel] - Callback to delete label
|
||||
* @param {ReactElement} [props.actions] - Actions
|
||||
* @returns {ReactElement} - Card
|
||||
*/
|
||||
({ template, rootProps, actions, onDeleteLabel }) => {
|
||||
const classes = rowStyles()
|
||||
const { [RESOURCE_NAMES.SERVICE_TEMPLATE]: serviceView } = useViews()
|
||||
|
||||
const enableEditLabels =
|
||||
serviceView?.actions?.[ACTIONS.EDIT_LABELS] === true && !!onDeleteLabel
|
||||
|
||||
const {
|
||||
ID,
|
||||
NAME,
|
||||
TEMPLATE: {
|
||||
BODY: {
|
||||
description,
|
||||
labels,
|
||||
networks,
|
||||
roles,
|
||||
registration_time: regTime,
|
||||
} = {},
|
||||
},
|
||||
} = template
|
||||
|
||||
const numberOfRoles = useMemo(() => roles?.length ?? 0, [roles])
|
||||
|
||||
const numberOfNetworks = useMemo(
|
||||
() => Object.keys(networks)?.length ?? 0,
|
||||
[networks]
|
||||
)
|
||||
|
||||
const time = useMemo(() => timeFromMilliseconds(+regTime), [regTime])
|
||||
|
||||
const uniqueLabels = useMemo(
|
||||
() =>
|
||||
getUniqueLabels(labels).map((label) => ({
|
||||
text: label,
|
||||
stateColor: getColorFromString(label),
|
||||
onDelete: enableEditLabels && onDeleteLabel,
|
||||
})),
|
||||
[labels, enableEditLabels, onDeleteLabel]
|
||||
)
|
||||
|
||||
return (
|
||||
<div {...rootProps} data-cy={`service-template-${ID}`}>
|
||||
<div className={classes.main}>
|
||||
<div className={classes.title}>
|
||||
<Typography noWrap component="span" title={description}>
|
||||
{NAME}
|
||||
</Typography>
|
||||
<span className={classes.labels}>
|
||||
<MultipleTags tags={uniqueLabels} />
|
||||
</span>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span data-cy="id">{`#${ID}`}</span>
|
||||
<span title={time.toFormat('ff')}>
|
||||
<Timer translateWord={T.RegisteredAt} initial={time} />
|
||||
</span>
|
||||
<span title={`${Tr(T.Networks)}: ${numberOfNetworks}`}>
|
||||
<Network width={20} height={20} />
|
||||
<span data-cy="total-networks">{numberOfNetworks}</span>
|
||||
</span>
|
||||
<span title={`${Tr(T.Roles)}: ${numberOfRoles}`}>
|
||||
<Package width={20} height={20} />
|
||||
<span data-cy="total-roles">{numberOfRoles}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{actions && <div className={classes.actions}>{actions}</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
ServiceTemplateCard.propTypes = {
|
||||
template: PropTypes.object,
|
||||
rootProps: PropTypes.shape({
|
||||
className: PropTypes.string,
|
||||
}),
|
||||
onDeleteLabel: PropTypes.func,
|
||||
actions: PropTypes.any,
|
||||
}
|
||||
|
||||
ServiceTemplateCard.displayName = 'ServiceTemplateCard'
|
||||
|
||||
export default ServiceTemplateCard
|
@ -32,6 +32,8 @@ import ProvisionTemplateCard from 'client/components/Cards/ProvisionTemplateCard
|
||||
import ScheduleActionCard from 'client/components/Cards/ScheduleActionCard'
|
||||
import SecurityGroupCard from 'client/components/Cards/SecurityGroupCard'
|
||||
import SelectCard from 'client/components/Cards/SelectCard'
|
||||
import ServiceCard from 'client/components/Cards/ServiceCard'
|
||||
import ServiceTemplateCard from 'client/components/Cards/ServiceTemplateCard'
|
||||
import SnapshotCard from 'client/components/Cards/SnapshotCard'
|
||||
import TierCard from 'client/components/Cards/TierCard'
|
||||
import VirtualMachineCard from 'client/components/Cards/VirtualMachineCard'
|
||||
@ -58,6 +60,8 @@ export {
|
||||
ScheduleActionCard,
|
||||
SecurityGroupCard,
|
||||
SelectCard,
|
||||
ServiceCard,
|
||||
ServiceTemplateCard,
|
||||
SnapshotCard,
|
||||
TierCard,
|
||||
VirtualMachineCard,
|
||||
|
@ -16,7 +16,7 @@
|
||||
import { JSXElementConstructor } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { Tooltip } from '@mui/material'
|
||||
import { Box, Tooltip } from '@mui/material'
|
||||
import makeStyles from '@mui/styles/makeStyles'
|
||||
|
||||
import { TypographyWithPoint } from 'client/components/Typography'
|
||||
@ -77,10 +77,6 @@ const SingleBar = ({ legend, data, total = 0 }) => {
|
||||
{data?.map((value, idx) => {
|
||||
const label = legend[idx]?.name
|
||||
const color = legend[idx]?.color
|
||||
const style = {
|
||||
backgroundColor: color,
|
||||
'&:hover': { backgroundColor: addOpacityToColor(color, 0.6) },
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
@ -89,7 +85,12 @@ const SingleBar = ({ legend, data, total = 0 }) => {
|
||||
placement="top"
|
||||
title={`${label}: ${value}`}
|
||||
>
|
||||
<div style={style}></div>
|
||||
<Box
|
||||
sx={{
|
||||
bgcolor: color,
|
||||
'&:hover': { bgcolor: addOpacityToColor(color, 0.6) },
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
)
|
||||
})}
|
||||
|
@ -67,6 +67,7 @@ const EnhancedTable = ({
|
||||
classes = {},
|
||||
rootProps = {},
|
||||
searchProps = {},
|
||||
noDataMessage,
|
||||
}) => {
|
||||
const styles = EnhancedTableStyles()
|
||||
|
||||
@ -208,12 +209,15 @@ const EnhancedTable = ({
|
||||
|
||||
<div className={clsx(styles.body, classes.body)}>
|
||||
{/* NO DATA MESSAGE */}
|
||||
{!isLoading && !isUninitialized && page?.length === 0 && (
|
||||
<span className={styles.noDataMessage}>
|
||||
<InfoEmpty />
|
||||
<Translate word={T.NoDataAvailable} />
|
||||
</span>
|
||||
)}
|
||||
{!isLoading &&
|
||||
!isUninitialized &&
|
||||
page?.length === 0 &&
|
||||
(noDataMessage || (
|
||||
<span className={styles.noDataMessage}>
|
||||
<InfoEmpty />
|
||||
<Translate word={T.NoDataAvailable} />
|
||||
</span>
|
||||
))}
|
||||
|
||||
{/* DATALIST PER PAGE */}
|
||||
{page.map((row) => {
|
||||
@ -282,6 +286,11 @@ EnhancedTable.propTypes = {
|
||||
RowComponent: PropTypes.any,
|
||||
showPageCount: PropTypes.bool,
|
||||
singleSelect: PropTypes.bool,
|
||||
noDataMessage: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.node,
|
||||
PropTypes.bool,
|
||||
]),
|
||||
}
|
||||
|
||||
export * from 'client/components/Tables/Enhanced/Utils'
|
||||
|
@ -0,0 +1,35 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { Column } from 'react-table'
|
||||
|
||||
import { T } from 'client/constants'
|
||||
|
||||
/** @type {Column[]} Service Template columns */
|
||||
const COLUMNS = [
|
||||
{ Header: T.ID, id: 'id', accessor: 'ID', sortType: 'number' },
|
||||
{ Header: T.Name, id: 'name', accessor: 'NAME' },
|
||||
{ Header: T.Owner, id: 'owner', accessor: 'UNAME' },
|
||||
{ Header: T.Group, id: 'group', accessor: 'GNAME' },
|
||||
{
|
||||
Header: T.RegistrationTime,
|
||||
id: 'time',
|
||||
accessor: 'TEMPLATE.BODY.registration_time',
|
||||
},
|
||||
]
|
||||
|
||||
COLUMNS.noFilterIds = ['id', 'name', 'time']
|
||||
|
||||
export default COLUMNS
|
@ -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 { useMemo, ReactElement } from 'react'
|
||||
import { Alert } from '@mui/material'
|
||||
|
||||
import { useViews } from 'client/features/Auth'
|
||||
import { useGetServiceTemplatesQuery } from 'client/features/OneApi/serviceTemplate'
|
||||
|
||||
import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced'
|
||||
import ServiceTemplateColumns from 'client/components/Tables/ServiceTemplates/columns'
|
||||
import ServiceTemplateRow from 'client/components/Tables/ServiceTemplates/row'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import { T, RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
const DEFAULT_DATA_CY = 'service-templates'
|
||||
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @returns {ReactElement} Service Templates table
|
||||
*/
|
||||
const ServiceTemplatesTable = (props) => {
|
||||
const { rootProps = {}, searchProps = {}, ...rest } = props ?? {}
|
||||
rootProps['data-cy'] ??= DEFAULT_DATA_CY
|
||||
searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}`
|
||||
|
||||
const { view, getResourceView } = useViews()
|
||||
const {
|
||||
data = [],
|
||||
isFetching,
|
||||
refetch,
|
||||
error,
|
||||
} = useGetServiceTemplatesQuery()
|
||||
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
createColumns({
|
||||
filters: getResourceView(RESOURCE_NAMES.SERVICE_TEMPLATE)?.filters,
|
||||
columns: ServiceTemplateColumns,
|
||||
}),
|
||||
[view]
|
||||
)
|
||||
|
||||
return (
|
||||
<EnhancedTable
|
||||
columns={columns}
|
||||
data={useMemo(() => data, [data])}
|
||||
rootProps={rootProps}
|
||||
searchProps={searchProps}
|
||||
refetch={refetch}
|
||||
isLoading={isFetching}
|
||||
getRowId={(row) => String(row.ID)}
|
||||
RowComponent={ServiceTemplateRow}
|
||||
noDataMessage={
|
||||
error?.status === 500 && (
|
||||
<Alert severity="error" variant="outlined">
|
||||
<Translate word={T.CannotConnectOneFlow} />
|
||||
</Alert>
|
||||
)
|
||||
}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
ServiceTemplatesTable.propTypes = { ...EnhancedTable.propTypes }
|
||||
ServiceTemplatesTable.displayName = 'ServiceTemplatesTable'
|
||||
|
||||
export default ServiceTemplatesTable
|
@ -0,0 +1,70 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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, useMemo, useCallback } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import serviceTemplateApi, {
|
||||
useUpdateServiceTemplateMutation,
|
||||
} from 'client/features/OneApi/serviceTemplate'
|
||||
import { ServiceTemplateCard } from 'client/components/Cards'
|
||||
|
||||
const Row = memo(
|
||||
({ original, value, ...props }) => {
|
||||
const [update] = useUpdateServiceTemplateMutation()
|
||||
|
||||
const state =
|
||||
serviceTemplateApi.endpoints.getServiceTemplates.useQueryState(
|
||||
undefined,
|
||||
{
|
||||
selectFromResult: ({ data = [] }) =>
|
||||
data.find((template) => +template.ID === +original.ID),
|
||||
}
|
||||
)
|
||||
|
||||
const memoTemplate = useMemo(() => state ?? original, [state, original])
|
||||
|
||||
const handleDeleteLabel = useCallback(
|
||||
(label) => {
|
||||
const currentLabels = memoTemplate.TEMPLATE.BODY.labels?.split(',')
|
||||
const labels = currentLabels.filter((l) => l !== label).join(',')
|
||||
|
||||
update({ id: memoTemplate.ID, template: { labels }, append: true })
|
||||
},
|
||||
[memoTemplate.TEMPLATE.BODY?.labels, update]
|
||||
)
|
||||
|
||||
return (
|
||||
<ServiceTemplateCard
|
||||
template={memoTemplate}
|
||||
rootProps={props}
|
||||
onDeleteLabel={handleDeleteLabel}
|
||||
/>
|
||||
)
|
||||
},
|
||||
(prev, next) => prev.className === next.className
|
||||
)
|
||||
|
||||
Row.propTypes = {
|
||||
original: PropTypes.object,
|
||||
value: PropTypes.object,
|
||||
isSelected: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
handleClick: PropTypes.func,
|
||||
}
|
||||
|
||||
Row.displayName = 'ServiceTemplateRow'
|
||||
|
||||
export default Row
|
@ -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 { Column } from 'react-table'
|
||||
|
||||
import { T } from 'client/constants'
|
||||
|
||||
/** @type {Column[]} Service columns */
|
||||
const COLUMNS = [
|
||||
{ Header: T.ID, id: 'id', accessor: 'ID', sortType: 'number' },
|
||||
{ Header: T.Name, id: 'name', accessor: 'NAME' },
|
||||
{ Header: T.Owner, id: 'owner', accessor: 'UNAME' },
|
||||
{ Header: T.Group, id: 'group', accessor: 'GNAME' },
|
||||
{ Header: T.State, id: 'state', accessor: 'TEMPLATE.BODY.state' },
|
||||
{
|
||||
Header: T.Description,
|
||||
id: 'description',
|
||||
accessor: 'TEMPLATE.BODY.description',
|
||||
},
|
||||
{
|
||||
Header: T.StartTime,
|
||||
id: 'time',
|
||||
accessor: 'TEMPLATE.BODY.start_time',
|
||||
},
|
||||
]
|
||||
|
||||
COLUMNS.noFilterIds = ['id', 'name', 'description', 'time']
|
||||
|
||||
export default COLUMNS
|
76
src/fireedge/src/client/components/Tables/Services/index.js
Normal file
76
src/fireedge/src/client/components/Tables/Services/index.js
Normal file
@ -0,0 +1,76 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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, ReactElement } from 'react'
|
||||
import { Alert } from '@mui/material'
|
||||
|
||||
import { useViews } from 'client/features/Auth'
|
||||
import { useGetServicesQuery } from 'client/features/OneApi/service'
|
||||
|
||||
import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced'
|
||||
import ServiceColumns from 'client/components/Tables/Services/columns'
|
||||
import ServiceRow from 'client/components/Tables/Services/row'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import { T, RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
const DEFAULT_DATA_CY = 'services'
|
||||
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @returns {ReactElement} Service table
|
||||
*/
|
||||
const ServicesTable = (props) => {
|
||||
const { rootProps = {}, searchProps = {}, ...rest } = props ?? {}
|
||||
rootProps['data-cy'] ??= DEFAULT_DATA_CY
|
||||
searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}`
|
||||
|
||||
const { view, getResourceView } = useViews()
|
||||
const { data = [], isFetching, refetch, error } = useGetServicesQuery()
|
||||
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
createColumns({
|
||||
filters: getResourceView(RESOURCE_NAMES.SERVICE)?.filters,
|
||||
columns: ServiceColumns,
|
||||
}),
|
||||
[view]
|
||||
)
|
||||
|
||||
return (
|
||||
<EnhancedTable
|
||||
columns={columns}
|
||||
data={useMemo(() => data, [data])}
|
||||
rootProps={rootProps}
|
||||
searchProps={searchProps}
|
||||
refetch={refetch}
|
||||
isLoading={isFetching}
|
||||
getRowId={(row) => String(row.ID)}
|
||||
RowComponent={ServiceRow}
|
||||
noDataMessage={
|
||||
error?.status === 500 && (
|
||||
<Alert severity="error" variant="outlined">
|
||||
<Translate word={T.CannotConnectOneFlow} />
|
||||
</Alert>
|
||||
)
|
||||
}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
ServicesTable.propTypes = { ...EnhancedTable.propTypes }
|
||||
ServicesTable.displayName = 'ServicesTable'
|
||||
|
||||
export default ServicesTable
|
46
src/fireedge/src/client/components/Tables/Services/row.js
Normal file
46
src/fireedge/src/client/components/Tables/Services/row.js
Normal file
@ -0,0 +1,46 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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, useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import serviceApi from 'client/features/OneApi/service'
|
||||
import { ServiceCard } from 'client/components/Cards'
|
||||
|
||||
const Row = memo(
|
||||
({ original, value, ...props }) => {
|
||||
const state = serviceApi.endpoints.getServices.useQueryState(undefined, {
|
||||
selectFromResult: ({ data = [] }) =>
|
||||
data.find((service) => +service.ID === +original.ID),
|
||||
})
|
||||
|
||||
const memoService = useMemo(() => state ?? original, [state, original])
|
||||
|
||||
return <ServiceCard service={memoService} rootProps={props} />
|
||||
},
|
||||
(prev, next) => prev.className === next.className
|
||||
)
|
||||
|
||||
Row.propTypes = {
|
||||
original: PropTypes.object,
|
||||
value: PropTypes.object,
|
||||
isSelected: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
handleClick: PropTypes.func,
|
||||
}
|
||||
|
||||
Row.displayName = 'ServiceRow'
|
||||
|
||||
export default Row
|
@ -23,6 +23,8 @@ import ImagesTable from 'client/components/Tables/Images'
|
||||
import MarketplaceAppsTable from 'client/components/Tables/MarketplaceApps'
|
||||
import MarketplacesTable from 'client/components/Tables/Marketplaces'
|
||||
import SecurityGroupsTable from 'client/components/Tables/SecurityGroups'
|
||||
import ServicesTable from 'client/components/Tables/Services'
|
||||
import ServiceTemplatesTable from 'client/components/Tables/ServiceTemplates'
|
||||
import SkeletonTable from 'client/components/Tables/Skeleton'
|
||||
import UsersTable from 'client/components/Tables/Users'
|
||||
import VirtualizedTable from 'client/components/Tables/Virtualized'
|
||||
@ -46,6 +48,8 @@ export {
|
||||
MarketplaceAppsTable,
|
||||
MarketplacesTable,
|
||||
SecurityGroupsTable,
|
||||
ServicesTable,
|
||||
ServiceTemplatesTable,
|
||||
UsersTable,
|
||||
VmsTable,
|
||||
VmTemplatesTable,
|
||||
|
50
src/fireedge/src/client/components/Tabs/Service/Actions.js
Normal file
50
src/fireedge/src/client/components/Tabs/Service/Actions.js
Normal file
@ -0,0 +1,50 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { Stack } from '@mui/material'
|
||||
|
||||
import { useGetServiceQuery } from 'client/features/OneApi/service'
|
||||
// import ScheduleActionCard from 'client/components/Cards/ScheduleActionCard'
|
||||
|
||||
/**
|
||||
* Renders the list of schedule actions from a Service.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {string} props.id - Service id
|
||||
* @param {object|boolean} props.tabProps - Tab properties
|
||||
* @param {object} [props.tabProps.actions] - Actions from user view yaml
|
||||
* @returns {ReactElement} Schedule actions tab
|
||||
*/
|
||||
const SchedulingTab = ({ id, tabProps: { actions } = {} }) => {
|
||||
const { data: service = {} } = useGetServiceQuery({ id })
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack gap="1em" py="0.8em">
|
||||
{service?.NAME}
|
||||
{/* TODO: scheduler actions & form */}
|
||||
</Stack>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
SchedulingTab.propTypes = {
|
||||
tabProps: PropTypes.object,
|
||||
id: PropTypes.string,
|
||||
}
|
||||
|
||||
export default SchedulingTab
|
@ -0,0 +1,92 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { Stack } from '@mui/material'
|
||||
|
||||
import { useGetServiceQuery } from 'client/features/OneApi/service'
|
||||
import { Permissions, Ownership } from 'client/components/Tabs/Common'
|
||||
import Information from 'client/components/Tabs/Service/Info/information'
|
||||
import { getActionsAvailable } from 'client/models/Helper'
|
||||
|
||||
/**
|
||||
* Renders mainly information tab.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {object} props.tabProps - Tab information
|
||||
* @param {string} props.id - Template id
|
||||
* @returns {ReactElement} Information tab
|
||||
*/
|
||||
const ServiceInfoTab = ({ tabProps = {}, id }) => {
|
||||
const {
|
||||
information_panel: informationPanel,
|
||||
permissions_panel: permissionsPanel,
|
||||
ownership_panel: ownershipPanel,
|
||||
} = tabProps
|
||||
|
||||
const { data: service = {} } = useGetServiceQuery({ id })
|
||||
const { UNAME, UID, GNAME, GID, PERMISSIONS = {} } = service
|
||||
|
||||
const getActions = (actions) => getActionsAvailable(actions)
|
||||
|
||||
return (
|
||||
<Stack
|
||||
display="grid"
|
||||
gap="1em"
|
||||
gridTemplateColumns="repeat(auto-fit, minmax(49%, 1fr))"
|
||||
padding={{ sm: '0.8em' }}
|
||||
>
|
||||
{informationPanel?.enabled && (
|
||||
<Information
|
||||
actions={getActions(informationPanel?.actions)}
|
||||
service={service}
|
||||
/>
|
||||
)}
|
||||
{permissionsPanel?.enabled && (
|
||||
<Permissions
|
||||
actions={getActions(permissionsPanel?.actions)}
|
||||
ownerUse={PERMISSIONS.OWNER_U}
|
||||
ownerManage={PERMISSIONS.OWNER_M}
|
||||
ownerAdmin={PERMISSIONS.OWNER_A}
|
||||
groupUse={PERMISSIONS.GROUP_U}
|
||||
groupManage={PERMISSIONS.GROUP_M}
|
||||
groupAdmin={PERMISSIONS.GROUP_A}
|
||||
otherUse={PERMISSIONS.OTHER_U}
|
||||
otherManage={PERMISSIONS.OTHER_M}
|
||||
otherAdmin={PERMISSIONS.OTHER_A}
|
||||
/>
|
||||
)}
|
||||
{ownershipPanel?.enabled && (
|
||||
<Ownership
|
||||
actions={getActions(ownershipPanel?.actions)}
|
||||
userId={UID}
|
||||
userName={UNAME}
|
||||
groupId={GID}
|
||||
groupName={GNAME}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
ServiceInfoTab.propTypes = {
|
||||
tabProps: PropTypes.object,
|
||||
id: PropTypes.string,
|
||||
}
|
||||
|
||||
ServiceInfoTab.displayName = 'ServiceInfoTab'
|
||||
|
||||
export default ServiceInfoTab
|
@ -0,0 +1,106 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { Stack } from '@mui/material'
|
||||
|
||||
import { List } from 'client/components/Tabs/Common'
|
||||
import { StatusCircle, StatusChip } from 'client/components/Status'
|
||||
import { getState } from 'client/models/Service'
|
||||
import { timeToString, booleanToString } from 'client/models/Helper'
|
||||
import { T, Service } from 'client/constants'
|
||||
|
||||
/**
|
||||
* Renders mainly information tab.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {Service} props.service - Service
|
||||
* @param {string[]} props.actions - Available actions to information tab
|
||||
* @returns {ReactElement} Information tab
|
||||
*/
|
||||
const InformationPanel = ({ service = {}, actions }) => {
|
||||
const {
|
||||
ID,
|
||||
NAME,
|
||||
TEMPLATE: {
|
||||
BODY: {
|
||||
deployment,
|
||||
shutdown_action: shutdownAction,
|
||||
registration_time: regTime,
|
||||
ready_status_gate: readyStatusGate,
|
||||
automatic_deletion: autoDelete,
|
||||
} = {},
|
||||
},
|
||||
} = service || {}
|
||||
|
||||
const { name: stateName, color: stateColor } = getState(service)
|
||||
|
||||
const info = [
|
||||
{ name: T.ID, value: ID, dataCy: 'id' },
|
||||
{ name: T.Name, value: NAME, dataCy: 'name' },
|
||||
{
|
||||
name: T.State,
|
||||
value: (
|
||||
<Stack direction="row" alignItems="center" gap={1}>
|
||||
<StatusCircle color={stateColor} />
|
||||
<StatusChip dataCy="state" text={stateName} stateColor={stateColor} />
|
||||
</Stack>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: T.StartTime,
|
||||
value: timeToString(regTime),
|
||||
dataCy: 'time',
|
||||
},
|
||||
{
|
||||
name: T.Strategy,
|
||||
value: deployment,
|
||||
dataCy: 'deployment',
|
||||
},
|
||||
{
|
||||
name: T.ShutdownAction,
|
||||
value: shutdownAction,
|
||||
dataCy: 'shutdown-action',
|
||||
},
|
||||
{
|
||||
name: T.ReadyStatusGate,
|
||||
value: booleanToString(readyStatusGate),
|
||||
dataCy: 'ready-status-gate',
|
||||
},
|
||||
{
|
||||
name: T.AutomaticDeletion,
|
||||
value: booleanToString(autoDelete),
|
||||
dataCy: 'auto-delete',
|
||||
},
|
||||
].filter(Boolean)
|
||||
|
||||
return (
|
||||
<List
|
||||
title={T.Information}
|
||||
list={info}
|
||||
containerProps={{ sx: { gridRow: 'span 2' } }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
InformationPanel.displayName = 'InformationPanel'
|
||||
|
||||
InformationPanel.propTypes = {
|
||||
service: PropTypes.object,
|
||||
actions: PropTypes.arrayOf(PropTypes.string),
|
||||
}
|
||||
|
||||
export default InformationPanel
|
61
src/fireedge/src/client/components/Tabs/Service/Log.js
Normal file
61
src/fireedge/src/client/components/Tabs/Service/Log.js
Normal file
@ -0,0 +1,61 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { Stack, Typography } from '@mui/material'
|
||||
|
||||
import { useGetServiceQuery } from 'client/features/OneApi/service'
|
||||
import { timeFromMilliseconds } from 'client/models/Helper'
|
||||
import { Service, SERVICE_LOG_SEVERITY } from 'client/constants'
|
||||
|
||||
/**
|
||||
* Renders log tab.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {string} props.id - Service id
|
||||
* @returns {ReactElement} Log tab
|
||||
*/
|
||||
const LogTab = ({ id }) => {
|
||||
const { data: service = {} } = useGetServiceQuery({ id })
|
||||
|
||||
/** @type {Service} */
|
||||
const { TEMPLATE: { BODY: { log = [] } = {} } = {} } = service
|
||||
|
||||
return (
|
||||
<Stack gap="0.5em" p="1em" bgcolor="background.default">
|
||||
{log?.map(({ severity, message, timestamp } = {}) => {
|
||||
const time = timeFromMilliseconds(+timestamp)
|
||||
const isError = severity === SERVICE_LOG_SEVERITY.ERROR
|
||||
|
||||
return (
|
||||
<Typography
|
||||
key={`message-${timestamp}`}
|
||||
noWrap
|
||||
variant="body2"
|
||||
color={isError ? 'error' : 'textPrimary'}
|
||||
>
|
||||
{`${time.toFormat('ff')} [${severity}] ${message}`}
|
||||
</Typography>
|
||||
)
|
||||
})}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
LogTab.propTypes = { tabProps: PropTypes.object, id: PropTypes.string }
|
||||
LogTab.displayName = 'RolesTab'
|
||||
|
||||
export default LogTab
|
118
src/fireedge/src/client/components/Tabs/Service/Roles.js
Normal file
118
src/fireedge/src/client/components/Tabs/Service/Roles.js
Normal file
@ -0,0 +1,118 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { Link as RouterLink, generatePath } from 'react-router-dom'
|
||||
import { Box, Typography, Link, CircularProgress } from '@mui/material'
|
||||
|
||||
import { useGetServiceQuery } from 'client/features/OneApi/service'
|
||||
import { useGetTemplatesQuery } from 'client/features/OneApi/vmTemplate'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import { T, ServiceTemplateRole } from 'client/constants'
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
|
||||
const COLUMNS = [T.Name, T.Cardinality, T.VMTemplate, T.Parents]
|
||||
|
||||
/**
|
||||
* Renders template tab.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {string} props.id - Service Template id
|
||||
* @returns {ReactElement} Roles tab
|
||||
*/
|
||||
const RolesTab = ({ id }) => {
|
||||
const { data: template = {} } = useGetServiceQuery({ id })
|
||||
const roles = template?.TEMPLATE?.BODY?.roles || []
|
||||
|
||||
return (
|
||||
<Box
|
||||
display="grid"
|
||||
gridTemplateColumns="repeat(4, 1fr)"
|
||||
padding="1em"
|
||||
bgcolor="background.default"
|
||||
>
|
||||
{COLUMNS.map((col) => (
|
||||
<Typography key={col} noWrap variant="subtitle1" padding="0.5em">
|
||||
<Translate word={col} />
|
||||
</Typography>
|
||||
))}
|
||||
{roles.map((role, idx) => (
|
||||
<Box
|
||||
key={`role-${role.name ?? idx}`}
|
||||
display="contents"
|
||||
padding="0.5em"
|
||||
// hover except for the circular progress component
|
||||
sx={{ '&:hover > *:not(span)': { bgcolor: 'action.hover' } }}
|
||||
>
|
||||
<RoleComponent role={role} />
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
RolesTab.propTypes = { tabProps: PropTypes.object, id: PropTypes.string }
|
||||
RolesTab.displayName = 'RolesTab'
|
||||
|
||||
const RoleComponent = memo(({ role }) => {
|
||||
/** @type {ServiceTemplateRole} */
|
||||
const { name, cardinality, vm_template: templateId, parents } = role
|
||||
|
||||
const { data: template, isLoading } = useGetTemplatesQuery(undefined, {
|
||||
selectFromResult: ({ data = [], ...restOfQuery }) => ({
|
||||
data: data.find((item) => +item.ID === +templateId),
|
||||
...restOfQuery,
|
||||
}),
|
||||
})
|
||||
|
||||
const linkToVmTemplate = useMemo(
|
||||
() => generatePath(PATH.TEMPLATE.VMS.DETAIL, { id: templateId }),
|
||||
[templateId]
|
||||
)
|
||||
|
||||
const commonProps = { noWrap: true, variant: 'subtitle2', padding: '0.5em' }
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography {...commonProps} data-cy="name">
|
||||
{name}
|
||||
</Typography>
|
||||
<Typography {...commonProps} data-cy="cardinality">
|
||||
{cardinality}
|
||||
</Typography>
|
||||
{isLoading ? (
|
||||
<CircularProgress color="secondary" size={20} />
|
||||
) : (
|
||||
<Link
|
||||
{...commonProps}
|
||||
color="secondary"
|
||||
component={RouterLink}
|
||||
to={linkToVmTemplate}
|
||||
>
|
||||
{`#${template?.ID} ${template?.NAME}`}
|
||||
</Link>
|
||||
)}
|
||||
<Typography {...commonProps} data-cy="parents">
|
||||
{parents?.join?.()}
|
||||
</Typography>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
RoleComponent.propTypes = { role: PropTypes.object }
|
||||
RoleComponent.displayName = 'RoleComponent'
|
||||
|
||||
export default RolesTab
|
68
src/fireedge/src/client/components/Tabs/Service/index.js
Normal file
68
src/fireedge/src/client/components/Tabs/Service/index.js
Normal file
@ -0,0 +1,68 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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, useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Alert, LinearProgress } from '@mui/material'
|
||||
|
||||
import { useViews } from 'client/features/Auth'
|
||||
import { useGetServiceQuery } from 'client/features/OneApi/service'
|
||||
import { getAvailableInfoTabs } from 'client/models/Helper'
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
import Tabs from 'client/components/Tabs'
|
||||
import Info from 'client/components/Tabs/Service/Info'
|
||||
import Roles from 'client/components/Tabs/Service/Roles'
|
||||
import Log from 'client/components/Tabs/Service/Log'
|
||||
import Actions from 'client/components/Tabs/Service/Actions'
|
||||
|
||||
const getTabComponent = (tabName) =>
|
||||
({
|
||||
info: Info,
|
||||
roles: Roles,
|
||||
log: Log,
|
||||
schedulerAction: Actions,
|
||||
}[tabName])
|
||||
|
||||
const ServiceTabs = memo(({ id }) => {
|
||||
const { view, getResourceView } = useViews()
|
||||
const { isLoading, isError, error } = useGetServiceQuery({ id })
|
||||
|
||||
const tabsAvailable = useMemo(() => {
|
||||
const resource = RESOURCE_NAMES.SERVICE
|
||||
const infoTabs = getResourceView(resource)?.['info-tabs'] ?? {}
|
||||
|
||||
return getAvailableInfoTabs(infoTabs, getTabComponent, id)
|
||||
}, [view])
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<Alert severity="error" variant="outlined">
|
||||
{error.data}
|
||||
</Alert>
|
||||
)
|
||||
}
|
||||
|
||||
return isLoading ? (
|
||||
<LinearProgress color="secondary" sx={{ width: '100%' }} />
|
||||
) : (
|
||||
<Tabs addBorder tabs={tabsAvailable ?? []} />
|
||||
)
|
||||
})
|
||||
|
||||
ServiceTabs.propTypes = { id: PropTypes.string.isRequired }
|
||||
ServiceTabs.displayName = 'ServiceTabs'
|
||||
|
||||
export default ServiceTabs
|
@ -0,0 +1,119 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { Stack } from '@mui/material'
|
||||
|
||||
import {
|
||||
useGetServiceTemplateQuery,
|
||||
useChangeServiceTemplatePermissionsMutation,
|
||||
useChangeServiceTemplateOwnershipMutation,
|
||||
} from 'client/features/OneApi/serviceTemplate'
|
||||
import { Permissions, Ownership } from 'client/components/Tabs/Common'
|
||||
import Information from 'client/components/Tabs/ServiceTemplate/Info/information'
|
||||
import { getActionsAvailable, permissionsToOctal } from 'client/models/Helper'
|
||||
import { toSnakeCase } from 'client/utils'
|
||||
|
||||
/**
|
||||
* Renders mainly information tab.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {object} props.tabProps - Tab information
|
||||
* @param {string} props.id - Template id
|
||||
* @returns {ReactElement} Information tab
|
||||
*/
|
||||
const ServiceTemplateInfoTab = ({ tabProps = {}, id }) => {
|
||||
const {
|
||||
information_panel: informationPanel,
|
||||
permissions_panel: permissionsPanel,
|
||||
ownership_panel: ownershipPanel,
|
||||
} = tabProps
|
||||
|
||||
const [changePermissions] = useChangeServiceTemplatePermissionsMutation()
|
||||
const [changeOwnership] = useChangeServiceTemplateOwnershipMutation()
|
||||
const { data: template = {} } = useGetServiceTemplateQuery({ id })
|
||||
const { UNAME, UID, GNAME, GID, PERMISSIONS = {} } = template
|
||||
|
||||
const handleChangeOwnership = async (newOwnership) => {
|
||||
await changeOwnership({ id, ...newOwnership })
|
||||
}
|
||||
|
||||
const handleChangePermission = async (newPermission) => {
|
||||
const [key, value] = Object.entries(newPermission)[0]
|
||||
|
||||
// transform key to snake case concatenated by the first letter of permission type
|
||||
// example: 'OWNER_ADMIN' -> 'OWNER_A'
|
||||
const [member, permission] = toSnakeCase(key).toUpperCase().split('_')
|
||||
const fullPermissionName = `${member}_${permission[0]}`
|
||||
|
||||
const newPermissions = { ...PERMISSIONS, [fullPermissionName]: value }
|
||||
const octet = permissionsToOctal(newPermissions)
|
||||
|
||||
await changePermissions({ id, octet })
|
||||
}
|
||||
|
||||
const getActions = (actions) => getActionsAvailable(actions)
|
||||
|
||||
return (
|
||||
<Stack
|
||||
display="grid"
|
||||
gap="1em"
|
||||
gridTemplateColumns="repeat(auto-fit, minmax(49%, 1fr))"
|
||||
padding={{ sm: '0.8em' }}
|
||||
>
|
||||
{informationPanel?.enabled && (
|
||||
<Information
|
||||
actions={getActions(informationPanel?.actions)}
|
||||
template={template}
|
||||
/>
|
||||
)}
|
||||
{permissionsPanel?.enabled && (
|
||||
<Permissions
|
||||
actions={getActions(permissionsPanel?.actions)}
|
||||
handleEdit={handleChangePermission}
|
||||
ownerUse={PERMISSIONS.OWNER_U}
|
||||
ownerManage={PERMISSIONS.OWNER_M}
|
||||
ownerAdmin={PERMISSIONS.OWNER_A}
|
||||
groupUse={PERMISSIONS.GROUP_U}
|
||||
groupManage={PERMISSIONS.GROUP_M}
|
||||
groupAdmin={PERMISSIONS.GROUP_A}
|
||||
otherUse={PERMISSIONS.OTHER_U}
|
||||
otherManage={PERMISSIONS.OTHER_M}
|
||||
otherAdmin={PERMISSIONS.OTHER_A}
|
||||
/>
|
||||
)}
|
||||
{ownershipPanel?.enabled && (
|
||||
<Ownership
|
||||
actions={getActions(ownershipPanel?.actions)}
|
||||
handleEdit={handleChangeOwnership}
|
||||
userId={UID}
|
||||
userName={UNAME}
|
||||
groupId={GID}
|
||||
groupName={GNAME}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
ServiceTemplateInfoTab.propTypes = {
|
||||
tabProps: PropTypes.object,
|
||||
id: PropTypes.string,
|
||||
}
|
||||
|
||||
ServiceTemplateInfoTab.displayName = 'ServiceTemplateInfoTab'
|
||||
|
||||
export default ServiceTemplateInfoTab
|
@ -0,0 +1,100 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { List } from 'client/components/Tabs/Common'
|
||||
import { useRenameServiceTemplateMutation } from 'client/features/OneApi/serviceTemplate'
|
||||
|
||||
import { timeToString, booleanToString } from 'client/models/Helper'
|
||||
import { T, VM_TEMPLATE_ACTIONS, ServiceTemplate } from 'client/constants'
|
||||
|
||||
/**
|
||||
* Renders mainly information tab.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {ServiceTemplate} props.template - Service Template
|
||||
* @param {string[]} props.actions - Available actions to information tab
|
||||
* @returns {ReactElement} Information tab
|
||||
*/
|
||||
const InformationPanel = ({ template = {}, actions }) => {
|
||||
const [renameTemplate] = useRenameServiceTemplateMutation()
|
||||
|
||||
const {
|
||||
ID,
|
||||
NAME,
|
||||
TEMPLATE: {
|
||||
BODY: {
|
||||
description,
|
||||
registration_time: regTime,
|
||||
ready_status_gate: readyStatusGate,
|
||||
automatic_deletion: autoDelete,
|
||||
} = {},
|
||||
},
|
||||
} = template || {}
|
||||
|
||||
const handleRename = async (_, newName) => {
|
||||
await renameTemplate({ id: ID, name: newName })
|
||||
}
|
||||
|
||||
const info = [
|
||||
{ name: T.ID, value: ID, dataCy: 'id' },
|
||||
{
|
||||
name: T.Name,
|
||||
value: NAME,
|
||||
canEdit: actions?.includes?.(VM_TEMPLATE_ACTIONS.RENAME),
|
||||
handleEdit: handleRename,
|
||||
dataCy: 'name',
|
||||
},
|
||||
{
|
||||
name: T.Description,
|
||||
value: description,
|
||||
dataCy: 'description',
|
||||
},
|
||||
{
|
||||
name: T.StartTime,
|
||||
value: timeToString(regTime),
|
||||
dataCy: 'time',
|
||||
},
|
||||
{
|
||||
name: T.ReadyStatusGate,
|
||||
value: booleanToString(readyStatusGate),
|
||||
dataCy: 'ready-status-gate',
|
||||
},
|
||||
{
|
||||
name: T.AutomaticDeletion,
|
||||
value: booleanToString(autoDelete),
|
||||
dataCy: 'auto-delete',
|
||||
},
|
||||
].filter(Boolean)
|
||||
|
||||
return (
|
||||
<List
|
||||
title={T.Information}
|
||||
list={info}
|
||||
containerProps={{ sx: { gridRow: 'span 2' } }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
InformationPanel.displayName = 'InformationPanel'
|
||||
|
||||
InformationPanel.propTypes = {
|
||||
actions: PropTypes.arrayOf(PropTypes.string),
|
||||
template: PropTypes.object,
|
||||
}
|
||||
|
||||
export default InformationPanel
|
118
src/fireedge/src/client/components/Tabs/ServiceTemplate/Roles.js
Normal file
118
src/fireedge/src/client/components/Tabs/ServiceTemplate/Roles.js
Normal file
@ -0,0 +1,118 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { Link as RouterLink, generatePath } from 'react-router-dom'
|
||||
import { Box, Typography, Link, CircularProgress } from '@mui/material'
|
||||
|
||||
import { useGetServiceTemplateQuery } from 'client/features/OneApi/serviceTemplate'
|
||||
import { useGetTemplatesQuery } from 'client/features/OneApi/vmTemplate'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import { T, Role } from 'client/constants'
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
|
||||
const COLUMNS = [T.Name, T.Cardinality, T.VMTemplate, T.Parents]
|
||||
|
||||
/**
|
||||
* Renders roles tab.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {string} props.id - Service Template id
|
||||
* @returns {ReactElement} Roles tab
|
||||
*/
|
||||
const RolesTab = ({ id }) => {
|
||||
const { data: template = {} } = useGetServiceTemplateQuery({ id })
|
||||
const roles = template?.TEMPLATE?.BODY?.roles || []
|
||||
|
||||
return (
|
||||
<Box
|
||||
display="grid"
|
||||
gridTemplateColumns="repeat(4, 1fr)"
|
||||
padding="1em"
|
||||
bgcolor="background.default"
|
||||
>
|
||||
{COLUMNS.map((col) => (
|
||||
<Typography key={col} noWrap variant="subtitle1" padding="0.5em">
|
||||
<Translate word={col} />
|
||||
</Typography>
|
||||
))}
|
||||
{roles.map((role, idx) => (
|
||||
<Box
|
||||
key={`role-${role.name ?? idx}`}
|
||||
display="contents"
|
||||
padding="0.5em"
|
||||
// hover except for the circular progress component
|
||||
sx={{ '&:hover > *:not(span)': { bgcolor: 'action.hover' } }}
|
||||
>
|
||||
<RoleComponent role={role} />
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
RolesTab.propTypes = { tabProps: PropTypes.object, id: PropTypes.string }
|
||||
RolesTab.displayName = 'RolesTab'
|
||||
|
||||
const RoleComponent = memo(({ role }) => {
|
||||
/** @type {Role} */
|
||||
const { name, cardinality, vm_template: templateId, parents } = role
|
||||
|
||||
const { data: template, isLoading } = useGetTemplatesQuery(undefined, {
|
||||
selectFromResult: ({ data = [], ...restOfQuery }) => ({
|
||||
data: data.find((item) => +item.ID === +templateId),
|
||||
...restOfQuery,
|
||||
}),
|
||||
})
|
||||
|
||||
const linkToVmTemplate = useMemo(
|
||||
() => generatePath(PATH.TEMPLATE.VMS.DETAIL, { id: templateId }),
|
||||
[templateId]
|
||||
)
|
||||
|
||||
const commonProps = { noWrap: true, variant: 'subtitle2', padding: '0.5em' }
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography {...commonProps} data-cy="name">
|
||||
{name}
|
||||
</Typography>
|
||||
<Typography {...commonProps} data-cy="cardinality">
|
||||
{cardinality}
|
||||
</Typography>
|
||||
{isLoading ? (
|
||||
<CircularProgress color="secondary" size={20} />
|
||||
) : (
|
||||
<Link
|
||||
{...commonProps}
|
||||
color="secondary"
|
||||
component={RouterLink}
|
||||
to={linkToVmTemplate}
|
||||
>
|
||||
{`#${template?.ID} ${template?.NAME}`}
|
||||
</Link>
|
||||
)}
|
||||
<Typography {...commonProps} data-cy="parents">
|
||||
{parents?.join?.()}
|
||||
</Typography>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
RoleComponent.propTypes = { role: PropTypes.object }
|
||||
RoleComponent.displayName = 'RoleComponent'
|
||||
|
||||
export default RolesTab
|
@ -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 { ReactElement } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Box, Accordion, AccordionDetails } from '@mui/material'
|
||||
|
||||
import { useGetServiceTemplateQuery } from 'client/features/OneApi/serviceTemplate'
|
||||
|
||||
/**
|
||||
* Renders template tab.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {string} props.id - Service Template id
|
||||
* @returns {ReactElement} Template tab
|
||||
*/
|
||||
const TemplateTab = ({ id }) => {
|
||||
const { data: template = {} } = useGetServiceTemplateQuery({ id })
|
||||
|
||||
return (
|
||||
<Accordion variant="outlined" expanded>
|
||||
<AccordionDetails>
|
||||
<Box component="pre">
|
||||
<Box
|
||||
component="code"
|
||||
sx={{ whiteSpace: 'break-spaces', wordBreak: 'break-all' }}
|
||||
>
|
||||
{JSON.stringify(template?.TEMPLATE?.BODY ?? {}, null, 2)}
|
||||
</Box>
|
||||
</Box>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
)
|
||||
}
|
||||
|
||||
TemplateTab.propTypes = {
|
||||
tabProps: PropTypes.object,
|
||||
id: PropTypes.string,
|
||||
}
|
||||
|
||||
TemplateTab.displayName = 'TemplateTab'
|
||||
|
||||
export default TemplateTab
|
@ -0,0 +1,66 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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, useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Alert, LinearProgress } from '@mui/material'
|
||||
|
||||
import { useViews } from 'client/features/Auth'
|
||||
import { useGetServiceTemplateQuery } from 'client/features/OneApi/serviceTemplate'
|
||||
import { getAvailableInfoTabs } from 'client/models/Helper'
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
import Tabs from 'client/components/Tabs'
|
||||
import Info from 'client/components/Tabs/ServiceTemplate/Info'
|
||||
import Roles from 'client/components/Tabs/ServiceTemplate/Roles'
|
||||
import Template from 'client/components/Tabs/ServiceTemplate/Template'
|
||||
|
||||
const getTabComponent = (tabName) =>
|
||||
({
|
||||
info: Info,
|
||||
roles: Roles,
|
||||
template: Template,
|
||||
}[tabName])
|
||||
|
||||
const ServiceTemplateTabs = memo(({ id }) => {
|
||||
const { view, getResourceView } = useViews()
|
||||
const { isLoading, isError, error } = useGetServiceTemplateQuery({ id })
|
||||
|
||||
const tabsAvailable = useMemo(() => {
|
||||
const resource = RESOURCE_NAMES.SERVICE_TEMPLATE
|
||||
const infoTabs = getResourceView(resource)?.['info-tabs'] ?? {}
|
||||
|
||||
return getAvailableInfoTabs(infoTabs, getTabComponent, id)
|
||||
}, [view])
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<Alert severity="error" variant="outlined">
|
||||
{error.data}
|
||||
</Alert>
|
||||
)
|
||||
}
|
||||
|
||||
return isLoading ? (
|
||||
<LinearProgress color="secondary" sx={{ width: '100%' }} />
|
||||
) : (
|
||||
<Tabs addBorder tabs={tabsAvailable ?? []} />
|
||||
)
|
||||
})
|
||||
|
||||
ServiceTemplateTabs.propTypes = { id: PropTypes.string.isRequired }
|
||||
ServiceTemplateTabs.displayName = 'ServiceTemplateTabs'
|
||||
|
||||
export default ServiceTemplateTabs
|
@ -16,7 +16,112 @@
|
||||
import * as STATES from 'client/constants/states'
|
||||
import COLOR from 'client/constants/color'
|
||||
|
||||
export const APPLICATION_STATES = [
|
||||
/**
|
||||
* @typedef {'CHANGE'|'CARDINALITY'|'PERCENTAGE_CHANGE'} AdjustmentType
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef ElasticityPolicy
|
||||
* @property {AdjustmentType} type - Type of adjustment
|
||||
* @property {string} adjust - Adjustment type
|
||||
* @property {string} [min_adjust_type] - Minimum adjustment type
|
||||
* @property {string} [cooldown] - Cooldown period duration after a scale operation, in seconds
|
||||
* @property {string} [period] - Duration, in seconds, of each period in period_number
|
||||
* @property {string} [period_number] - Number of periods that the expression must be true before the elasticity is triggered
|
||||
* @property {string} expression - Expression to trigger the elasticity
|
||||
* @property {string} [last_eval] - Last time the policy was evaluated
|
||||
* @property {string} [true_evals] - Number of times the policy was evaluated to true
|
||||
* @property {string} [expression_evaluated] - Expression evaluated to true
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef ScheduledPolicy
|
||||
* @property {AdjustmentType} type - Type of adjustment
|
||||
* @property {string} adjust - Adjustment type
|
||||
* @property {string} [min_adjust_step] - Optional parameter for PERCENTAGE_CHANGE adjustment type.
|
||||
* If present, the policy will change the cardinality by at least the number of VMs set in this attribute.
|
||||
* @property {string} [recurrence] - Time for recurring adjustments. Time is specified with the Unix cron syntax
|
||||
* @property {string} [start_time] - Exact time for the adjustment
|
||||
* @property {string} [cooldown] - Cooldown period duration after a scale operation, in seconds
|
||||
* @property {string} [last_eval] - Last time the policy was evaluated
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef Node
|
||||
* @property {string} deploy_id - Deployment id
|
||||
* @property {object} vm_info - VM information
|
||||
* @property {object} vm_info.VM - Virtual machine object
|
||||
* @property {string} vm_info.VM.ID - VM id
|
||||
* @property {string} vm_info.VM.NAME - VM name
|
||||
* @property {string} vm_info.VM.UID - Owner id
|
||||
* @property {string} vm_info.VM.UNAME - Owner name
|
||||
* @property {string} vm_info.VM.GID - Group id
|
||||
* @property {string} vm_info.VM.GNAME - Group name
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef Role
|
||||
* @property {string} name - Name
|
||||
* @property {string} cardinality - Cardinality
|
||||
* @property {string[]} [parents] - Names of the roles that must be deployed before this one
|
||||
* @property {string} [last_vmname] - ??
|
||||
* @property {string} state - Role state (see @see ROLE_STATES for more info)
|
||||
* @property {string} vm_template - OpenNebula VM Template ID
|
||||
* @property {string} [vm_template_contents] - Contents to be used into VM template
|
||||
* @property {'shutdown'|'shutdown-hard'} [shutdown_action] - VM shutdown action
|
||||
* @property {string} [min_vms] - Minimum number of VMs for elasticity adjustments
|
||||
* @property {string} [max_vms] - Maximum number of VMs for elasticity adjustments
|
||||
* @property {string} [cooldown] - Cooldown period duration after a scale operation, in seconds.
|
||||
* If it is not set, the default set in `oneflow-server.conf` will be used.
|
||||
* @property {boolean} [on_hold] - VM role is on hold (not deployed)
|
||||
* @property {ElasticityPolicy[]} [elasticity_policies] - Elasticity Policies
|
||||
* @property {ElasticityPolicy[]} [scheduled_policies] - Scheduled Policies
|
||||
* @property {Node[]} nodes - Nodes information (see @see Node for more info)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef ServiceLogItem
|
||||
* @property {string} message - Log message
|
||||
* @property {SERVICE_LOG_SEVERITY} severity - Severity (see @see SERVICE_LOG_SEVERITY for more info)
|
||||
* @property {string} timestamp - Timestamp
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef Service
|
||||
* @property {string} ID - Id
|
||||
* @property {string} NAME - Name
|
||||
* @property {string} UID - User id
|
||||
* @property {string} UNAME - User name
|
||||
* @property {string} GID - Group id
|
||||
* @property {string} GNAME - Group name
|
||||
* @property {Permissions} [PERMISSIONS] - Permissions
|
||||
* @property {object} TEMPLATE - Template
|
||||
* @property {object} TEMPLATE.BODY - Body in JSON format
|
||||
* @property {string} TEMPLATE.BODY.name - Template name
|
||||
* @property {string} TEMPLATE.BODY.description - Template description
|
||||
* @property {string} TEMPLATE.BODY.state - Service state
|
||||
* @property {object} [TEMPLATE.BODY.custom_attrs] - Hash of custom attributes to use in the service
|
||||
* @property {object} [TEMPLATE.BODY.custom_attrs_values] - ??
|
||||
* @property {'straight'|'none'} [TEMPLATE.BODY.deployment] - Deployment strategy of the service:
|
||||
* - 'none' - all roles are deployed at the same time
|
||||
* - 'straight' - each role is deployed when all its parents are RUNNING
|
||||
* @property {ServiceLogItem[]} [TEMPLATE.BODY.log] - Log items
|
||||
* @property {object} [TEMPLATE.BODY.networks] - Network to print an special user inputs on instantiation form
|
||||
* @property {object[]} [TEMPLATE.BODY.networks_values] - Network values to include on roles
|
||||
* @property {boolean} [TEMPLATE.BODY.ready_status_gate] - If ready_status_gate is set to true,
|
||||
* a VM will only be considered to be in running state the following points are true:
|
||||
*
|
||||
* - VM is in running state for OpenNebula. Which specifically means that LCM_STATE == 3 and STATE >= 3
|
||||
* - The VM has READY=YES in the user template, this can be reported by the VM using OneGate
|
||||
* @property {'terminate'|'terminate-hard'|'shutdown'|'shutdown-hard'} [TEMPLATE.BODY.shutdown_action] - VM shutdown action
|
||||
* @property {Role[]} TEMPLATE.BODY.roles - Roles information (see @see Role for more info)
|
||||
* @property {string} [TEMPLATE.BODY.registration_time] - Registration time
|
||||
* @property {boolean} [TEMPLATE.BODY.automatic_deletion] - Automatic deletion
|
||||
* @property {boolean} [TEMPLATE.BODY.on_hold] - VMs of the service are on hold (not deployed)
|
||||
*/
|
||||
|
||||
/** @type {STATES.StateInfo[]} Service states */
|
||||
export const SERVICE_STATES = [
|
||||
{
|
||||
// 0
|
||||
name: STATES.PENDING,
|
||||
@ -29,19 +134,19 @@ export const APPLICATION_STATES = [
|
||||
// 1
|
||||
name: STATES.DEPLOYING,
|
||||
color: COLOR.info.main,
|
||||
meaning: 'Some Tiers are being deployed',
|
||||
meaning: 'Some roles are being deployed',
|
||||
},
|
||||
{
|
||||
// 2
|
||||
name: STATES.RUNNING,
|
||||
color: COLOR.success.main,
|
||||
meaning: 'All Tiers are deployed successfully',
|
||||
meaning: 'All roles are deployed successfully',
|
||||
},
|
||||
{
|
||||
// 3
|
||||
name: STATES.UNDEPLOYING,
|
||||
color: COLOR.error.light,
|
||||
meaning: 'Some Tiers are being undeployed',
|
||||
meaning: 'Some roles are being undeployed',
|
||||
},
|
||||
{
|
||||
// 4
|
||||
@ -52,10 +157,10 @@ export const APPLICATION_STATES = [
|
||||
{
|
||||
// 5
|
||||
name: STATES.DONE,
|
||||
color: COLOR.error.dark,
|
||||
color: COLOR.debug.light,
|
||||
meaning: `
|
||||
The Applications will stay in this state after
|
||||
a successful undeployment. It can be deleted`,
|
||||
a successful undeploying. It can be deleted`,
|
||||
},
|
||||
{
|
||||
// 6
|
||||
@ -72,8 +177,8 @@ export const APPLICATION_STATES = [
|
||||
{
|
||||
// 8
|
||||
name: STATES.SCALING,
|
||||
color: COLOR.error.light,
|
||||
meaning: 'A Tier is scaling up or down',
|
||||
color: COLOR.info.main,
|
||||
meaning: 'A roles is scaling up or down',
|
||||
},
|
||||
{
|
||||
// 9
|
||||
@ -84,26 +189,26 @@ export const APPLICATION_STATES = [
|
||||
{
|
||||
// 10
|
||||
name: STATES.COOLDOWN,
|
||||
color: COLOR.error.light,
|
||||
meaning: 'A Tier is in the cooldown period after a scaling operation',
|
||||
color: COLOR.info.main,
|
||||
meaning: 'A roles is in the cooldown period after a scaling operation',
|
||||
},
|
||||
{
|
||||
// 11
|
||||
name: STATES.DEPLOYING_NETS,
|
||||
color: COLOR.info.main,
|
||||
meaning: '',
|
||||
meaning: 'Service networks are being deployed, they are in LOCK state',
|
||||
},
|
||||
{
|
||||
// 12
|
||||
name: STATES.UNDEPLOYING_NETS,
|
||||
color: COLOR.error.light,
|
||||
meaning: '',
|
||||
meaning: 'An error occurred while undeploying the Service networks',
|
||||
},
|
||||
{
|
||||
// 13
|
||||
name: STATES.FAILED_DEPLOYING_NETS,
|
||||
color: COLOR.error.dark,
|
||||
meaning: '',
|
||||
meaning: 'An error occurred while deploying the Service networks',
|
||||
},
|
||||
{
|
||||
// 14
|
||||
@ -111,66 +216,146 @@ export const APPLICATION_STATES = [
|
||||
color: COLOR.error.dark,
|
||||
meaning: '',
|
||||
},
|
||||
{
|
||||
// 15
|
||||
name: STATES.HOLD,
|
||||
color: COLOR.info.main,
|
||||
meaning: 'All roles are in hold state',
|
||||
},
|
||||
]
|
||||
|
||||
export const TIER_STATES = [
|
||||
/** @type {STATES.StateInfo[]} Role states */
|
||||
export const ROLE_STATES = [
|
||||
{
|
||||
// 0
|
||||
name: STATES.PENDING,
|
||||
color: '',
|
||||
meaning: 'The Tier is waiting to be deployed',
|
||||
color: COLOR.info.light,
|
||||
meaning: 'The role is waiting to be deployed',
|
||||
},
|
||||
{
|
||||
// 1
|
||||
name: STATES.DEPLOYING,
|
||||
color: '',
|
||||
color: COLOR.info.main,
|
||||
meaning: `
|
||||
The VMs are being created, and will be
|
||||
monitored until all of them are running`,
|
||||
},
|
||||
{
|
||||
// 2
|
||||
name: STATES.RUNNING,
|
||||
color: '',
|
||||
color: COLOR.success.main,
|
||||
meaning: 'All the VMs are running',
|
||||
},
|
||||
{
|
||||
name: STATES.WARNING,
|
||||
color: '',
|
||||
meaning: 'A VM was found in a failure state',
|
||||
},
|
||||
{
|
||||
name: STATES.SCALING,
|
||||
color: '',
|
||||
meaning: 'The Tier is waiting for VMs to be deployed or to be shutdown',
|
||||
},
|
||||
{
|
||||
name: STATES.COOLDOWN,
|
||||
color: '',
|
||||
meaning: 'The Tier is in the cooldown period after a scaling operation',
|
||||
},
|
||||
{
|
||||
// 3
|
||||
name: STATES.UNDEPLOYING,
|
||||
color: '',
|
||||
color: COLOR.error.light,
|
||||
meaning: `
|
||||
The VMs are being shutdown. The Tier will stay in
|
||||
The VMs are being shutdown. The role will stay in
|
||||
this state until all VMs are done`,
|
||||
},
|
||||
{
|
||||
// 4
|
||||
name: STATES.WARNING,
|
||||
color: COLOR.error.light,
|
||||
meaning: 'A VM was found in a failure state',
|
||||
},
|
||||
{
|
||||
// 5
|
||||
name: STATES.DONE,
|
||||
color: '',
|
||||
color: COLOR.debug.light,
|
||||
meaning: 'All the VMs are done',
|
||||
},
|
||||
{
|
||||
name: STATES.FAILED_DEPLOYING,
|
||||
color: '',
|
||||
meaning: 'An error occurred while deploying the VMs',
|
||||
},
|
||||
{
|
||||
// 6
|
||||
name: STATES.FAILED_UNDEPLOYING,
|
||||
color: '',
|
||||
color: COLOR.error.dark,
|
||||
meaning: 'An error occurred while undeploying the VMs',
|
||||
},
|
||||
{
|
||||
// 7
|
||||
name: STATES.FAILED_DEPLOYING,
|
||||
color: COLOR.error.dark,
|
||||
meaning: 'An error occurred while deploying the VMs',
|
||||
},
|
||||
{
|
||||
// 8
|
||||
name: STATES.SCALING,
|
||||
color: COLOR.info.main,
|
||||
meaning: 'The role is waiting for VMs to be deployed or to be shutdown',
|
||||
},
|
||||
{
|
||||
// 9
|
||||
name: STATES.FAILED_SCALING,
|
||||
color: '',
|
||||
meaning: 'An error occurred while scaling the Tier',
|
||||
color: COLOR.error.dark,
|
||||
meaning: 'An error occurred while scaling the role',
|
||||
},
|
||||
{
|
||||
// 10
|
||||
name: STATES.COOLDOWN,
|
||||
color: COLOR.info.main,
|
||||
meaning: 'The role is in the cooldown period after a scaling operation',
|
||||
},
|
||||
{
|
||||
// 11
|
||||
name: STATES.HOLD,
|
||||
color: COLOR.info.main,
|
||||
meaning:
|
||||
'The VMs are HOLD and will not be scheduled until them are released',
|
||||
},
|
||||
]
|
||||
|
||||
/** @enum {string} Role actions */
|
||||
export const ROLE_ACTIONS = {
|
||||
CREATE_DIALOG: 'create_dialog',
|
||||
HOLD: 'hold',
|
||||
POWEROFF_HARD: 'poweroff_hard',
|
||||
POWEROFF: 'poweroff',
|
||||
REBOOT_HARD: 'reboot_hard',
|
||||
REBOOT: 'reboot',
|
||||
RELEASE: 'release',
|
||||
RESUME: 'resume',
|
||||
STOP: 'stop',
|
||||
SUSPEND: 'suspend',
|
||||
TERMINATE_HARD: 'terminate_hard',
|
||||
TERMINATE: 'terminate',
|
||||
UNDEPLOY_HARD: 'undeploy_hard',
|
||||
UNDEPLOY: 'undeploy',
|
||||
SNAPSHOT_DISK_CREATE: 'snapshot_disk_create',
|
||||
SNAPSHOT_DISK_RENAME: 'snapshot_disk_rename',
|
||||
SNAPSHOT_DISK_REVERT: 'snapshot_disk_revert',
|
||||
SNAPSHOT_DISK_DELETE: 'snapshot_disk_delete',
|
||||
SNAPSHOT_CREATE: 'snapshot_create',
|
||||
SNAPSHOT_REVERT: 'snapshot_revert',
|
||||
SNAPSHOT_DELETE: 'snapshot_delete',
|
||||
}
|
||||
|
||||
/** @type {string[]} Actions that can be scheduled */
|
||||
export const ROLE_ACTIONS_WITH_SCHEDULE = [
|
||||
ROLE_ACTIONS.HOLD,
|
||||
ROLE_ACTIONS.POWEROFF_HARD,
|
||||
ROLE_ACTIONS.POWEROFF,
|
||||
ROLE_ACTIONS.REBOOT_HARD,
|
||||
ROLE_ACTIONS.REBOOT,
|
||||
ROLE_ACTIONS.RELEASE,
|
||||
ROLE_ACTIONS.RESUME,
|
||||
ROLE_ACTIONS.SNAPSHOT_CREATE,
|
||||
ROLE_ACTIONS.SNAPSHOT_DELETE,
|
||||
ROLE_ACTIONS.SNAPSHOT_DISK_CREATE,
|
||||
ROLE_ACTIONS.SNAPSHOT_DISK_DELETE,
|
||||
ROLE_ACTIONS.SNAPSHOT_DISK_REVERT,
|
||||
ROLE_ACTIONS.SNAPSHOT_REVERT,
|
||||
ROLE_ACTIONS.STOP,
|
||||
ROLE_ACTIONS.SUSPEND,
|
||||
ROLE_ACTIONS.TERMINATE_HARD,
|
||||
ROLE_ACTIONS.TERMINATE,
|
||||
ROLE_ACTIONS.UNDEPLOY_HARD,
|
||||
ROLE_ACTIONS.UNDEPLOY,
|
||||
]
|
||||
|
||||
/** @enum {string} Log severity levels for service logs */
|
||||
export const SERVICE_LOG_SEVERITY = {
|
||||
DEBUG: 'D',
|
||||
INFO: 'I',
|
||||
ERROR: 'E',
|
||||
}
|
||||
|
@ -162,6 +162,8 @@ export const RESOURCE_NAMES = {
|
||||
VM: 'vm',
|
||||
VN_TEMPLATE: 'network-template',
|
||||
VNET: 'virtual-network',
|
||||
SERVICE: 'service',
|
||||
SERVICE_TEMPLATE: 'service-template',
|
||||
ZONE: 'zone',
|
||||
}
|
||||
|
||||
|
@ -60,6 +60,7 @@ module.exports = {
|
||||
CreateProvider: 'Create Provider',
|
||||
CreateProvision: 'Create Provision',
|
||||
CreateVmTemplate: 'Create VM Template',
|
||||
CreateServiceTemplate: 'Create Service Template',
|
||||
CurrentGroup: 'Current group: %s',
|
||||
CurrentOwner: 'Current owner: %s',
|
||||
Delete: 'Delete',
|
||||
@ -70,6 +71,7 @@ module.exports = {
|
||||
DeleteSomething: 'Delete: %s',
|
||||
DeleteTemplate: 'Delete Template',
|
||||
Deploy: 'Deploy',
|
||||
DeployServiceTemplate: 'Deploy Service Template',
|
||||
Detach: 'Detach',
|
||||
DetachSomething: 'Detach: %s',
|
||||
Disable: 'Disable',
|
||||
@ -158,6 +160,7 @@ module.exports = {
|
||||
UpdateProvider: 'Update Provider',
|
||||
UpdateScheduleAction: 'Update schedule action: %s',
|
||||
UpdateVmTemplate: 'Update VM Template',
|
||||
UpdateServiceTemplate: 'Update Service Template',
|
||||
|
||||
/* questions */
|
||||
Yes: 'Yes',
|
||||
@ -343,6 +346,10 @@ module.exports = {
|
||||
Templates: 'Templates',
|
||||
VMTemplate: 'VM Template',
|
||||
VMTemplates: 'VM Templates',
|
||||
Service: 'Service',
|
||||
Services: 'Services',
|
||||
ServiceTemplate: 'Service Template',
|
||||
ServiceTemplates: 'Service Templates',
|
||||
|
||||
/* sections - flow */
|
||||
ApplicationsTemplates: 'Applications templates',
|
||||
@ -406,11 +413,6 @@ module.exports = {
|
||||
Monitoring: 'Monitoring',
|
||||
EdgeCluster: 'Edge Cluster',
|
||||
|
||||
/* flow schema */
|
||||
Strategy: 'Strategy',
|
||||
ShutdownAction: 'Shutdown action',
|
||||
ReadyStatusGate: 'Ready status gate',
|
||||
|
||||
/* VM schema */
|
||||
/* VM schema - remote access */
|
||||
Vnc: 'VNC',
|
||||
@ -573,7 +575,6 @@ module.exports = {
|
||||
EnableHotResize: 'Enable hot resize',
|
||||
/* VM Template schema - VM Group */
|
||||
AssociateToVMGroup: 'Associate VM to a VM Group',
|
||||
Role: 'Role',
|
||||
/* VM Template schema - vCenter */
|
||||
vCenterTemplateRef: 'vCenter Template reference',
|
||||
vCenterClusterRef: 'vCenter Cluster reference',
|
||||
@ -759,6 +760,17 @@ module.exports = {
|
||||
The VM Template(s), along with any image referenced by it, will
|
||||
be unshared with the group's users. Permission changed: GROUP USE`,
|
||||
|
||||
/* Service Template schema */
|
||||
/* Service Template schema - general */
|
||||
Strategy: 'Strategy',
|
||||
ShutdownAction: 'Shutdown action',
|
||||
ReadyStatusGate: 'Ready status gate',
|
||||
AutomaticDeletion: 'Automatic deletion',
|
||||
Role: 'Role',
|
||||
Roles: 'Roles',
|
||||
Cardinality: 'Cardinality',
|
||||
Parents: 'Parents',
|
||||
|
||||
/* Virtual Network schema - network */
|
||||
Driver: 'Driver',
|
||||
IP: 'IP',
|
||||
|
@ -0,0 +1,36 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { useParams, Redirect } from 'react-router-dom'
|
||||
|
||||
import ServiceTemplateTabs from 'client/components/Tabs/ServiceTemplate'
|
||||
|
||||
/**
|
||||
* Displays the detail information about a Service Template.
|
||||
*
|
||||
* @returns {ReactElement} Service Template detail component.
|
||||
*/
|
||||
function ServiceTemplateDetail() {
|
||||
const { id } = useParams()
|
||||
|
||||
if (Number.isNaN(+id)) {
|
||||
return <Redirect to="/" />
|
||||
}
|
||||
|
||||
return <ServiceTemplateTabs id={id} />
|
||||
}
|
||||
|
||||
export default ServiceTemplateDetail
|
131
src/fireedge/src/client/containers/ServiceTemplates/index.js
Normal file
131
src/fireedge/src/client/containers/ServiceTemplates/index.js
Normal file
@ -0,0 +1,131 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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, useState, memo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { BookmarkEmpty } from 'iconoir-react'
|
||||
import { Typography, Box, Stack, Chip, IconButton } from '@mui/material'
|
||||
import { Row } from 'react-table'
|
||||
|
||||
import serviceTemplateApi from 'client/features/OneApi/serviceTemplate'
|
||||
import { ServiceTemplatesTable } from 'client/components/Tables'
|
||||
import ServiceTemplateTabs from 'client/components/Tabs/ServiceTemplate'
|
||||
import SplitPane from 'client/components/SplitPane'
|
||||
import MultipleTags from 'client/components/MultipleTags'
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
/**
|
||||
* Displays a list of Service Templates with a split pane between
|
||||
* the list and selected row(s).
|
||||
*
|
||||
* @returns {ReactElement} Service Templates list and selected row(s)
|
||||
*/
|
||||
function ServiceTemplates() {
|
||||
const [selectedRows, onSelectedRowsChange] = useState(() => [])
|
||||
|
||||
const hasSelectedRows = selectedRows?.length > 0
|
||||
const moreThanOneSelected = selectedRows?.length > 1
|
||||
const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
|
||||
|
||||
return (
|
||||
<SplitPane gridTemplateRows={gridTemplateRows}>
|
||||
{({ getGridProps, GutterComponent }) => (
|
||||
<Box {...getGridProps()}>
|
||||
<ServiceTemplatesTable onSelectedRowsChange={onSelectedRowsChange} />
|
||||
|
||||
{hasSelectedRows && (
|
||||
<>
|
||||
<GutterComponent direction="row" track={1} />
|
||||
{moreThanOneSelected ? (
|
||||
<GroupedTags tags={selectedRows} />
|
||||
) : (
|
||||
<InfoTabs
|
||||
id={selectedRows[0]?.original?.ID}
|
||||
gotoPage={selectedRows[0]?.gotoPage}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</SplitPane>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays details of a Service Template.
|
||||
*
|
||||
* @param {string} id - Service Template id to display
|
||||
* @param {Function} [gotoPage] - Function to navigate to a page of a Service Template
|
||||
* @returns {ReactElement} Service Template details
|
||||
*/
|
||||
const InfoTabs = memo(({ id, gotoPage }) => {
|
||||
const template =
|
||||
serviceTemplateApi.endpoints.getServiceTemplates.useQueryState(undefined, {
|
||||
selectFromResult: ({ data = [] }) =>
|
||||
data.find((item) => +item.ID === +id),
|
||||
})
|
||||
|
||||
return (
|
||||
<Stack overflow="auto">
|
||||
<Stack direction="row" alignItems="center" gap={1} mb={1}>
|
||||
<Typography color="text.primary" noWrap>
|
||||
{`#${id} | ${template.NAME}`}
|
||||
</Typography>
|
||||
{gotoPage && (
|
||||
<IconButton title={Tr(T.LocateOnTable)} onClick={gotoPage}>
|
||||
<BookmarkEmpty />
|
||||
</IconButton>
|
||||
)}
|
||||
</Stack>
|
||||
<ServiceTemplateTabs id={id} />
|
||||
</Stack>
|
||||
)
|
||||
})
|
||||
|
||||
InfoTabs.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
gotoPage: PropTypes.func,
|
||||
}
|
||||
|
||||
InfoTabs.displayName = 'InfoTabs'
|
||||
|
||||
/**
|
||||
* Displays a list of tags that represent the selected rows.
|
||||
*
|
||||
* @param {Row[]} tags - Row(s) to display as tags
|
||||
* @returns {ReactElement} List of tags
|
||||
*/
|
||||
const GroupedTags = memo(({ tags = [] }) => (
|
||||
<Stack direction="row" flexWrap="wrap" gap={1} alignContent="flex-start">
|
||||
<MultipleTags
|
||||
limitTags={10}
|
||||
tags={tags?.map(({ original, id, toggleRowSelected, gotoPage }) => (
|
||||
<Chip
|
||||
key={id}
|
||||
label={original?.NAME ?? id}
|
||||
onClick={gotoPage}
|
||||
onDelete={() => toggleRowSelected(false)}
|
||||
/>
|
||||
))}
|
||||
/>
|
||||
</Stack>
|
||||
))
|
||||
|
||||
GroupedTags.propTypes = { tags: PropTypes.array }
|
||||
GroupedTags.displayName = 'GroupedTags'
|
||||
|
||||
export default ServiceTemplates
|
36
src/fireedge/src/client/containers/Services/Detail.js
Normal file
36
src/fireedge/src/client/containers/Services/Detail.js
Normal file
@ -0,0 +1,36 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { useParams, Redirect } from 'react-router-dom'
|
||||
|
||||
import ServiceTabs from 'client/components/Tabs/Service'
|
||||
|
||||
/**
|
||||
* Displays the detail information about a Service.
|
||||
*
|
||||
* @returns {ReactElement} Service detail component.
|
||||
*/
|
||||
function ServiceDetail() {
|
||||
const { id } = useParams()
|
||||
|
||||
if (Number.isNaN(+id)) {
|
||||
return <Redirect to="/" />
|
||||
}
|
||||
|
||||
return <ServiceTabs id={id} />
|
||||
}
|
||||
|
||||
export default ServiceDetail
|
129
src/fireedge/src/client/containers/Services/index.js
Normal file
129
src/fireedge/src/client/containers/Services/index.js
Normal file
@ -0,0 +1,129 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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, useState, memo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { BookmarkEmpty } from 'iconoir-react'
|
||||
import { Typography, Box, Stack, Chip, IconButton } from '@mui/material'
|
||||
import { Row } from 'react-table'
|
||||
|
||||
import serviceApi from 'client/features/OneApi/service'
|
||||
import { ServicesTable } from 'client/components/Tables'
|
||||
import ServiceTabs from 'client/components/Tabs/Service'
|
||||
import SplitPane from 'client/components/SplitPane'
|
||||
import MultipleTags from 'client/components/MultipleTags'
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
/**
|
||||
* Displays a list of Service with a split pane between
|
||||
* the list and selected row(s).
|
||||
*
|
||||
* @returns {ReactElement} Service list and selected row(s)
|
||||
*/
|
||||
function Services() {
|
||||
const [selectedRows, onSelectedRowsChange] = useState(() => [])
|
||||
|
||||
const hasSelectedRows = selectedRows?.length > 0
|
||||
const moreThanOneSelected = selectedRows?.length > 1
|
||||
const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
|
||||
|
||||
return (
|
||||
<SplitPane gridTemplateRows={gridTemplateRows}>
|
||||
{({ getGridProps, GutterComponent }) => (
|
||||
<Box {...getGridProps()}>
|
||||
<ServicesTable onSelectedRowsChange={onSelectedRowsChange} />
|
||||
|
||||
{hasSelectedRows && (
|
||||
<>
|
||||
<GutterComponent direction="row" track={1} />
|
||||
{moreThanOneSelected ? (
|
||||
<GroupedTags tags={selectedRows} />
|
||||
) : (
|
||||
<InfoTabs
|
||||
id={selectedRows[0]?.original?.ID}
|
||||
gotoPage={selectedRows[0]?.gotoPage}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</SplitPane>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays details of a Service.
|
||||
*
|
||||
* @param {string} id - Service id to display
|
||||
* @param {Function} [gotoPage] - Function to navigate to a page of a Service
|
||||
* @returns {ReactElement} Service details
|
||||
*/
|
||||
const InfoTabs = memo(({ id, gotoPage }) => {
|
||||
const template = serviceApi.endpoints.getServices.useQueryState(undefined, {
|
||||
selectFromResult: ({ data = [] }) => data.find((item) => +item.ID === +id),
|
||||
})
|
||||
|
||||
return (
|
||||
<Stack overflow="auto">
|
||||
<Stack direction="row" alignItems="center" gap={1} mb={1}>
|
||||
<Typography color="text.primary" noWrap>
|
||||
{`#${id} | ${template.NAME}`}
|
||||
</Typography>
|
||||
{gotoPage && (
|
||||
<IconButton title={Tr(T.LocateOnTable)} onClick={gotoPage}>
|
||||
<BookmarkEmpty />
|
||||
</IconButton>
|
||||
)}
|
||||
</Stack>
|
||||
<ServiceTabs id={id} />
|
||||
</Stack>
|
||||
)
|
||||
})
|
||||
|
||||
InfoTabs.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
gotoPage: PropTypes.func,
|
||||
}
|
||||
|
||||
InfoTabs.displayName = 'InfoTabs'
|
||||
|
||||
/**
|
||||
* Displays a list of tags that represent the selected rows.
|
||||
*
|
||||
* @param {Row[]} tags - Row(s) to display as tags
|
||||
* @returns {ReactElement} List of tags
|
||||
*/
|
||||
const GroupedTags = memo(({ tags = [] }) => (
|
||||
<Stack direction="row" flexWrap="wrap" gap={1} alignContent="flex-start">
|
||||
<MultipleTags
|
||||
limitTags={10}
|
||||
tags={tags?.map(({ original, id, toggleRowSelected, gotoPage }) => (
|
||||
<Chip
|
||||
key={id}
|
||||
label={original?.NAME ?? id}
|
||||
onClick={gotoPage}
|
||||
onDelete={() => toggleRowSelected(false)}
|
||||
/>
|
||||
))}
|
||||
/>
|
||||
</Stack>
|
||||
))
|
||||
|
||||
GroupedTags.propTypes = { tags: PropTypes.array }
|
||||
GroupedTags.displayName = 'GroupedTags'
|
||||
|
||||
export default Services
|
@ -20,6 +20,16 @@ import groupApi from 'client/features/OneApi/group'
|
||||
import { LockLevel, Permission, User, Group } from 'client/constants'
|
||||
import { xmlToJson } from 'client/models/Helper'
|
||||
|
||||
/**
|
||||
* Checks if the parameters are valid to update the pool store.
|
||||
*
|
||||
* @param {Draft} draft - The draft to check
|
||||
* @param {string} resourceId - The resource ID
|
||||
* @returns {boolean} - True if the parameters are valid, false otherwise
|
||||
*/
|
||||
const isUpdateOnPool = (draft, resourceId) =>
|
||||
Array.isArray(draft) && resourceId !== undefined
|
||||
|
||||
/**
|
||||
* Update the pool of resources with the new data.
|
||||
*
|
||||
@ -31,7 +41,7 @@ import { xmlToJson } from 'client/models/Helper'
|
||||
export const updateResourceOnPool =
|
||||
({ id: resourceId, resourceFromQuery }) =>
|
||||
(draft) => {
|
||||
if (resourceId !== undefined && Array.isArray(draft)) return
|
||||
if (!isUpdateOnPool(draft, resourceId)) return
|
||||
|
||||
const index = draft.findIndex(({ ID }) => +ID === +resourceId)
|
||||
index !== -1 && (draft[index] = resourceFromQuery)
|
||||
@ -47,7 +57,7 @@ export const updateResourceOnPool =
|
||||
export const removeResourceOnPool =
|
||||
({ id: resourceId }) =>
|
||||
(draft) => {
|
||||
if (resourceId !== undefined && Array.isArray(draft)) return
|
||||
if (!isUpdateOnPool(draft, resourceId)) return
|
||||
|
||||
draft.filter(({ ID }) => +ID !== +resourceId)
|
||||
}
|
||||
@ -63,13 +73,13 @@ export const removeResourceOnPool =
|
||||
export const updateNameOnResource =
|
||||
({ id: resourceId, name: newName }) =>
|
||||
(draft) => {
|
||||
const updatePool = resourceId !== undefined && Array.isArray(draft)
|
||||
const updatePool = isUpdateOnPool(draft, resourceId)
|
||||
|
||||
const resource = updatePool
|
||||
? draft.find(({ ID }) => +ID === +resourceId)
|
||||
: draft
|
||||
|
||||
if ((updatePool && !resource) || newName !== undefined) return
|
||||
if ((updatePool && !resource) || newName === undefined) return
|
||||
|
||||
resource.NAME = newName
|
||||
}
|
||||
@ -79,13 +89,13 @@ export const updateNameOnResource =
|
||||
*
|
||||
* @param {string} params - The parameters from query
|
||||
* @param {string} [params.id] - The id of the resource
|
||||
* @param {LockLevel} [params.level] - The new lock level
|
||||
* @param {LockLevel} [params.level] - The new lock level. By default, the lock level is 4.
|
||||
* @returns {function(Draft):ThunkAction} - Dispatches the action
|
||||
*/
|
||||
export const updateLockLevelOnResource =
|
||||
({ id: resourceId, level = '4' }) =>
|
||||
(draft) => {
|
||||
const updatePool = resourceId !== undefined && Array.isArray(draft)
|
||||
const updatePool = isUpdateOnPool(draft, resourceId)
|
||||
|
||||
const resource = updatePool
|
||||
? draft.find(({ ID }) => +ID === +resourceId)
|
||||
@ -107,7 +117,7 @@ export const updateLockLevelOnResource =
|
||||
export const removeLockLevelOnResource =
|
||||
({ id: resourceId }) =>
|
||||
(draft) => {
|
||||
const updatePool = resourceId !== undefined && Array.isArray(draft)
|
||||
const updatePool = isUpdateOnPool(draft, resourceId)
|
||||
|
||||
const resource = updatePool
|
||||
? draft.find(({ ID }) => +ID === +resourceId)
|
||||
@ -137,7 +147,7 @@ export const removeLockLevelOnResource =
|
||||
export const updatePermissionOnResource =
|
||||
({ id: resourceId, ...permissions }) =>
|
||||
(draft) => {
|
||||
const updatePool = resourceId !== undefined && Array.isArray(draft)
|
||||
const updatePool = isUpdateOnPool(draft, resourceId)
|
||||
|
||||
const resource = updatePool
|
||||
? draft.find(({ ID }) => +ID === +resourceId)
|
||||
@ -206,7 +216,7 @@ export const updateOwnershipOnResource = (
|
||||
const { user, group } = selectOwnershipFromState(state, { userId, groupId })
|
||||
|
||||
return (draft) => {
|
||||
const updatePool = resourceId !== undefined && Array.isArray(draft)
|
||||
const updatePool = isUpdateOnPool(draft, resourceId)
|
||||
|
||||
const resource = updatePool
|
||||
? draft.find(({ ID }) => +ID === +resourceId)
|
||||
@ -232,6 +242,9 @@ export const updateOwnershipOnResource = (
|
||||
* - Update type:
|
||||
* ``0``: Replace the whole template.
|
||||
* ``1``: Merge new template with the existing one.
|
||||
* @param {boolean} [params.append]
|
||||
* - ``true``: Merge new template with the existing one.
|
||||
* - ``false``: Replace the whole template.
|
||||
* @param {string} [userTemplateAttribute] - The attribute name of the user template. By default is `USER_TEMPLATE`.
|
||||
* @returns {function(Draft):ThunkAction} - Dispatches the action
|
||||
*/
|
||||
@ -241,7 +254,7 @@ export const updateUserTemplateOnResource =
|
||||
userTemplateAttribute = 'USER_TEMPLATE'
|
||||
) =>
|
||||
(draft) => {
|
||||
const updatePool = resourceId !== undefined && Array.isArray(draft)
|
||||
const updatePool = isUpdateOnPool(draft, resourceId)
|
||||
const newTemplateJson = xmlToJson(xml)
|
||||
|
||||
const resource = updatePool
|
||||
@ -255,3 +268,32 @@ export const updateUserTemplateOnResource =
|
||||
? newTemplateJson
|
||||
: { ...resource[userTemplateAttribute], ...newTemplateJson }
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the template body of a document in the store.
|
||||
*
|
||||
* @param {object} params - Request params
|
||||
* @param {number|string} params.id - The id of the resource
|
||||
* @param {object} params.template - The new template contents on JSON format
|
||||
* @param {boolean} [params.append]
|
||||
* - ``true``: Merge new template with the existing one.
|
||||
* - ``false``: Replace the whole template.
|
||||
*
|
||||
* By default, ``true``.
|
||||
* @returns {function(Draft):ThunkAction} - Dispatches the action
|
||||
*/
|
||||
export const updateTemplateOnDocument =
|
||||
({ id: resourceId, template, append = true }) =>
|
||||
(draft) => {
|
||||
const updatePool = isUpdateOnPool(draft, resourceId)
|
||||
|
||||
const resource = updatePool
|
||||
? draft.find(({ ID }) => +ID === +resourceId)
|
||||
: draft
|
||||
|
||||
if (updatePool && !resource) return
|
||||
|
||||
resource.TEMPLATE.BODY = append
|
||||
? { ...resource.TEMPLATE.BODY, ...template }
|
||||
: template
|
||||
}
|
||||
|
@ -21,26 +21,26 @@ import { requestConfig, generateKey } from 'client/utils'
|
||||
import http from 'client/utils/rest'
|
||||
|
||||
const ONE_RESOURCES = {
|
||||
ACL: 'Acl',
|
||||
APP: 'App',
|
||||
CLUSTER: 'Cluster',
|
||||
DATASTORE: 'Datastore',
|
||||
FILE: 'File',
|
||||
GROUP: 'Group',
|
||||
HOST: 'Host',
|
||||
IMAGE: 'Image',
|
||||
MARKETPLACE: 'Marketplace',
|
||||
SECURITYGROUP: 'SecurityGroup',
|
||||
SYSTEM: 'System',
|
||||
TEMPLATE: 'Template',
|
||||
USER: 'User',
|
||||
VDC: 'Vdc',
|
||||
VM: 'Vm',
|
||||
VMGROUP: 'VmGroup',
|
||||
VNET: 'VNetwork',
|
||||
VNTEMPLATE: 'NetworkTemplate',
|
||||
VROUTER: 'VirtualRouter',
|
||||
ZONE: 'Zone',
|
||||
ACL: 'ACL',
|
||||
APP: 'APP',
|
||||
CLUSTER: 'CLUSTER',
|
||||
DATASTORE: 'DATASTORE',
|
||||
FILE: 'FILE',
|
||||
GROUP: 'GROUP',
|
||||
HOST: 'HOST',
|
||||
IMAGE: 'IMAGE',
|
||||
MARKETPLACE: 'MARKET',
|
||||
SECURITYGROUP: 'SECGROUP',
|
||||
SYSTEM: 'SYSTEM',
|
||||
TEMPLATE: 'TEMPLATE',
|
||||
USER: 'USER',
|
||||
VDC: 'VDC',
|
||||
VM: 'VM',
|
||||
VMGROUP: 'VMGROUP',
|
||||
VNET: 'VNET',
|
||||
VNTEMPLATE: 'VNTEMPLATE',
|
||||
VROUTER: 'VROUTER',
|
||||
ZONE: 'ZONE',
|
||||
}
|
||||
|
||||
const ONE_RESOURCES_POOL = Object.entries(ONE_RESOURCES).reduce(
|
||||
@ -49,10 +49,10 @@ const ONE_RESOURCES_POOL = Object.entries(ONE_RESOURCES).reduce(
|
||||
)
|
||||
|
||||
const DOCUMENT = {
|
||||
SERVICE: 'applicationService',
|
||||
SERVICE_TEMPLATE: 'applicationServiceTemplate',
|
||||
PROVISION: 'provision',
|
||||
PROVIDER: 'provider',
|
||||
SERVICE: 'SERVICE',
|
||||
SERVICE_TEMPLATE: 'SERVICE_TEMPLATE',
|
||||
PROVISION: 'PROVISION',
|
||||
PROVIDER: 'PROVIDER',
|
||||
}
|
||||
|
||||
const DOCUMENT_POOL = Object.entries(DOCUMENT).reduce(
|
||||
@ -61,19 +61,19 @@ const DOCUMENT_POOL = Object.entries(DOCUMENT).reduce(
|
||||
)
|
||||
|
||||
const PROVISION_CONFIG = {
|
||||
PROVISION_DEFAULTS: 'provisionDefaults',
|
||||
PROVIDER_CONFIG: 'providerConfig',
|
||||
PROVISION_DEFAULTS: 'PROVISION_DEFAULTS',
|
||||
PROVIDER_CONFIG: 'PROVIDER_CONFIG',
|
||||
}
|
||||
|
||||
const PROVISION_RESOURCES = {
|
||||
CLUSTER: 'provisionCluster',
|
||||
DATASTORE: 'provisionDatastore',
|
||||
HOST: 'provisionHost',
|
||||
TEMPLATE: 'provisionVmTemplate',
|
||||
IMAGE: 'provisionImage',
|
||||
NETWORK: 'provisionVNetwork',
|
||||
VNTEMPLATE: 'provisionNetworkTemplate',
|
||||
FLOWTEMPLATE: 'provisionFlowTemplate',
|
||||
CLUSTER: 'PROVISION_CLUSTER',
|
||||
DATASTORE: 'PROVISION_DATASTORE',
|
||||
HOST: 'PROVISION_HOST',
|
||||
TEMPLATE: 'PROVISION_VMTEMPLATE',
|
||||
IMAGE: 'PROVISION_IMAGE',
|
||||
NETWORK: 'PROVISION_VNET',
|
||||
VNTEMPLATE: 'PROVISION_VNTEMPLATE',
|
||||
FLOWTEMPLATE: 'PROVISION_FLOWTEMPLATE',
|
||||
}
|
||||
|
||||
const oneApi = createApi({
|
||||
|
@ -14,7 +14,12 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { Actions, Commands } from 'server/routes/api/oneflow/service/routes'
|
||||
|
||||
import { oneApi, DOCUMENT, DOCUMENT_POOL } from 'client/features/OneApi'
|
||||
import {
|
||||
updateResourceOnPool,
|
||||
removeResourceOnPool,
|
||||
} from 'client/features/OneApi/common'
|
||||
import { Service } from 'client/constants'
|
||||
|
||||
const { SERVICE } = DOCUMENT
|
||||
@ -39,7 +44,10 @@ const serviceApi = oneApi.injectEndpoints({
|
||||
providesTags: (services) =>
|
||||
services
|
||||
? [
|
||||
services.map(({ ID }) => ({ type: SERVICE_POOL, id: `${ID}` })),
|
||||
...services.map(({ ID }) => ({
|
||||
type: SERVICE_POOL,
|
||||
id: `${ID}`,
|
||||
})),
|
||||
SERVICE_POOL,
|
||||
]
|
||||
: [SERVICE_POOL],
|
||||
@ -61,21 +69,27 @@ const serviceApi = oneApi.injectEndpoints({
|
||||
},
|
||||
transformResponse: (data) => data?.DOCUMENT ?? {},
|
||||
providesTags: (_, __, { id }) => [{ type: SERVICE, id }],
|
||||
async onQueryStarted({ id }, { dispatch, queryFulfilled }) {
|
||||
async onQueryStarted(id, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const { data: queryService } = await queryFulfilled
|
||||
const { data: resourceFromQuery } = await queryFulfilled
|
||||
|
||||
dispatch(
|
||||
serviceApi.util.updateQueryData(
|
||||
'getServices',
|
||||
undefined,
|
||||
(draft) => {
|
||||
const index = draft.findIndex(({ ID }) => +ID === +id)
|
||||
index !== -1 && (draft[index] = queryService)
|
||||
}
|
||||
updateResourceOnPool({ id, resourceFromQuery })
|
||||
)
|
||||
)
|
||||
} catch {}
|
||||
} catch {
|
||||
// if the query fails, we want to remove the resource from the pool
|
||||
dispatch(
|
||||
serviceApi.util.updateQueryData(
|
||||
'getServices',
|
||||
undefined,
|
||||
removeResourceOnPool({ id })
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
}),
|
||||
}),
|
||||
|
@ -14,7 +14,15 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { Actions, Commands } from 'server/routes/api/oneflow/template/routes'
|
||||
|
||||
import { oneApi, DOCUMENT, DOCUMENT_POOL } from 'client/features/OneApi'
|
||||
import {
|
||||
updateResourceOnPool,
|
||||
removeResourceOnPool,
|
||||
updateNameOnResource,
|
||||
updateOwnershipOnResource,
|
||||
updateTemplateOnDocument,
|
||||
} from 'client/features/OneApi/common'
|
||||
import { ServiceTemplate } from 'client/constants'
|
||||
|
||||
const { SERVICE_TEMPLATE } = DOCUMENT
|
||||
@ -39,7 +47,7 @@ const serviceTemplateApi = oneApi.injectEndpoints({
|
||||
providesTags: (serviceTemplates) =>
|
||||
serviceTemplates
|
||||
? [
|
||||
serviceTemplates.map(({ ID }) => ({
|
||||
...serviceTemplates.map(({ ID }) => ({
|
||||
type: SERVICE_TEMPLATE_POOL,
|
||||
id: `${ID}`,
|
||||
})),
|
||||
@ -64,21 +72,27 @@ const serviceTemplateApi = oneApi.injectEndpoints({
|
||||
},
|
||||
transformResponse: (data) => data?.DOCUMENT ?? {},
|
||||
providesTags: (_, __, { id }) => [{ type: SERVICE_TEMPLATE, id }],
|
||||
async onQueryStarted({ id }, { dispatch, queryFulfilled }) {
|
||||
async onQueryStarted(id, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const { data: queryService } = await queryFulfilled
|
||||
const { data: resourceFromQuery } = await queryFulfilled
|
||||
|
||||
dispatch(
|
||||
serviceTemplateApi.util.updateQueryData(
|
||||
'getServiceTemplates',
|
||||
undefined,
|
||||
(draft) => {
|
||||
const index = draft.findIndex(({ ID }) => +ID === +id)
|
||||
index !== -1 && (draft[index] = queryService)
|
||||
}
|
||||
updateResourceOnPool({ id, resourceFromQuery })
|
||||
)
|
||||
)
|
||||
} catch {}
|
||||
} catch {
|
||||
// if the query fails, we want to remove the resource from the pool
|
||||
dispatch(
|
||||
serviceTemplateApi.util.updateQueryData(
|
||||
'getServiceTemplates',
|
||||
undefined,
|
||||
removeResourceOnPool({ id })
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
}),
|
||||
createServiceTemplate: builder.mutation({
|
||||
@ -96,7 +110,7 @@ const serviceTemplateApi = oneApi.injectEndpoints({
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
providesTags: [SERVICE_TEMPLATE_POOL],
|
||||
invalidatesTags: [SERVICE_TEMPLATE_POOL],
|
||||
}),
|
||||
updateServiceTemplate: builder.mutation({
|
||||
/**
|
||||
@ -104,17 +118,51 @@ const serviceTemplateApi = oneApi.injectEndpoints({
|
||||
*
|
||||
* @param {object} params - Request params
|
||||
* @param {string} params.id - Service template id
|
||||
* @param {object} [params.template] - Service template data
|
||||
* @param {object} params.template - The new template contents
|
||||
* @param {boolean} [params.append]
|
||||
* - ``true``: Merge new template with the existing one.
|
||||
* - ``false``: Replace the whole template.
|
||||
*
|
||||
* By default, ``true``.
|
||||
* @returns {number} Service template id
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: (params) => {
|
||||
const name = Actions.SERVICE_TEMPLATE_UPDATE
|
||||
query: ({ template = {}, append = true, ...params }) => {
|
||||
params.action = {
|
||||
perform: 'update',
|
||||
params: { template_json: JSON.stringify(template), append },
|
||||
}
|
||||
|
||||
const name = Actions.SERVICE_TEMPLATE_ACTION
|
||||
const command = { name, ...Commands[name] }
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
providesTags: (_, __, { id }) => [{ type: SERVICE_TEMPLATE, id }],
|
||||
invalidatesTags: (_, __, { id }) => [{ type: SERVICE_TEMPLATE, id }],
|
||||
async onQueryStarted(params, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const patchVmTemplate = dispatch(
|
||||
serviceTemplateApi.util.updateQueryData(
|
||||
'getServiceTemplates',
|
||||
{ id: params.id },
|
||||
updateTemplateOnDocument(params)
|
||||
)
|
||||
)
|
||||
|
||||
const patchVmTemplates = dispatch(
|
||||
serviceTemplateApi.util.updateQueryData(
|
||||
'getServiceTemplates',
|
||||
undefined,
|
||||
updateTemplateOnDocument(params)
|
||||
)
|
||||
)
|
||||
|
||||
queryFulfilled.catch(() => {
|
||||
patchVmTemplate.undo()
|
||||
patchVmTemplates.undo()
|
||||
})
|
||||
} catch {}
|
||||
},
|
||||
}),
|
||||
removeServiceTemplate: builder.mutation({
|
||||
/**
|
||||
@ -131,9 +179,9 @@ const serviceTemplateApi = oneApi.injectEndpoints({
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
providesTags: [SERVICE_TEMPLATE_POOL],
|
||||
invalidatesTags: [SERVICE_TEMPLATE_POOL],
|
||||
}),
|
||||
instantiateServiceTemplate: builder.mutation({
|
||||
deployServiceTemplate: builder.mutation({
|
||||
/**
|
||||
* Perform instantiate action on the service template.
|
||||
*
|
||||
@ -159,7 +207,121 @@ const serviceTemplateApi = oneApi.injectEndpoints({
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
providesTags: [SERVICE_POOL],
|
||||
invalidatesTags: [SERVICE_POOL],
|
||||
}),
|
||||
changeServiceTemplatePermissions: builder.mutation({
|
||||
/**
|
||||
* Changes the permission bits of a Service template.
|
||||
* If set any permission to -1, it's not changed.
|
||||
*
|
||||
* @param {object} params - Request parameters
|
||||
* @param {string} params.id - Service Template id
|
||||
* @param {string} params.octet - Permissions in octal format
|
||||
* @returns {number} Service Template id
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: ({ octet, ...params }) => {
|
||||
params.action = { perform: 'chmod', params: { octet } }
|
||||
|
||||
const name = Actions.SERVICE_TEMPLATE_ACTION
|
||||
const command = { name, ...Commands[name] }
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
invalidatesTags: (_, __, { id }) => [{ type: SERVICE_TEMPLATE, id }],
|
||||
}),
|
||||
changeServiceTemplateOwnership: builder.mutation({
|
||||
/**
|
||||
* Changes the ownership bits of a Service template.
|
||||
* If set to `-1`, the user or group aren't changed.
|
||||
*
|
||||
* @param {object} params - Request parameters
|
||||
* @param {string|number} params.id - Service Template id
|
||||
* @param {number|'-1'} params.user - The user id
|
||||
* @param {number|'-1'} params.group - The group id
|
||||
* @returns {number} Service Template id
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: ({ user = '-1', group = '-1', ...params }) => {
|
||||
params.action = {
|
||||
perform: 'chown',
|
||||
params: { owner_id: user, group_id: group },
|
||||
}
|
||||
|
||||
const name = Actions.SERVICE_TEMPLATE_ACTION
|
||||
const command = { name, ...Commands[name] }
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
invalidatesTags: (_, __, { id }) => [{ type: SERVICE_TEMPLATE, id }],
|
||||
async onQueryStarted(params, { getState, dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const patchServiceTemplate = dispatch(
|
||||
serviceTemplateApi.util.updateQueryData(
|
||||
'getServiceTemplate',
|
||||
{ id: params.id },
|
||||
updateOwnershipOnResource(getState(), params)
|
||||
)
|
||||
)
|
||||
|
||||
const patchServiceTemplates = dispatch(
|
||||
serviceTemplateApi.util.updateQueryData(
|
||||
'getServiceTemplates',
|
||||
undefined,
|
||||
updateOwnershipOnResource(getState(), params)
|
||||
)
|
||||
)
|
||||
|
||||
queryFulfilled.catch(() => {
|
||||
patchServiceTemplate.undo()
|
||||
patchServiceTemplates.undo()
|
||||
})
|
||||
} catch {}
|
||||
},
|
||||
}),
|
||||
renameServiceTemplate: builder.mutation({
|
||||
/**
|
||||
* Renames a Service template.
|
||||
*
|
||||
* @param {object} params - Request parameters
|
||||
* @param {string|number} params.id - Service Template id
|
||||
* @param {string} params.name - The new name
|
||||
* @returns {number} Service Template id
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: ({ name, ...params }) => {
|
||||
params.action = { perform: 'rename', params: { name } }
|
||||
|
||||
const cName = Actions.SERVICE_TEMPLATE_ACTION
|
||||
const command = { name: cName, ...Commands[cName] }
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
invalidatesTags: (_, __, { id }) => [{ type: SERVICE_TEMPLATE, id }],
|
||||
async onQueryStarted(params, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const patchServiceTemplate = dispatch(
|
||||
serviceTemplateApi.util.updateQueryData(
|
||||
'getServiceTemplate',
|
||||
{ id: params.id },
|
||||
updateNameOnResource(params)
|
||||
)
|
||||
)
|
||||
|
||||
const patchServiceTemplates = dispatch(
|
||||
serviceTemplateApi.util.updateQueryData(
|
||||
'getServiceTemplates',
|
||||
undefined,
|
||||
updateNameOnResource(params)
|
||||
)
|
||||
)
|
||||
|
||||
queryFulfilled.catch(() => {
|
||||
patchServiceTemplate.undo()
|
||||
patchServiceTemplates.undo()
|
||||
})
|
||||
} catch {}
|
||||
},
|
||||
}),
|
||||
}),
|
||||
})
|
||||
@ -175,7 +337,10 @@ export const {
|
||||
useCreateServiceTemplateMutation,
|
||||
useUpdateServiceTemplateMutation,
|
||||
useRemoveServiceTemplateMutation,
|
||||
useInstantiateServiceTemplateMutation,
|
||||
useDeployServiceTemplateMutation,
|
||||
useChangeServiceTemplatePermissionsMutation,
|
||||
useChangeServiceTemplateOwnershipMutation,
|
||||
useRenameServiceTemplateMutation,
|
||||
} = serviceTemplateApi
|
||||
|
||||
export default serviceTemplateApi
|
||||
|
@ -108,7 +108,7 @@ const vmApi = oneApi.injectEndpoints({
|
||||
},
|
||||
transformResponse: (data) => data?.VM ?? {},
|
||||
providesTags: (_, __, { id }) => [{ type: VM, id }],
|
||||
async onQueryStarted(id, { dispatch, queryFulfilled }) {
|
||||
async onQueryStarted({ id }, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const { data: resourceFromQuery } = await queryFulfilled
|
||||
|
||||
|
@ -14,13 +14,17 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { prettyBytes } from 'client/utils'
|
||||
import { DATASTORE_STATES, DATASTORE_TYPES, STATES } from 'client/constants'
|
||||
import {
|
||||
Datastore,
|
||||
DATASTORE_STATES,
|
||||
DATASTORE_TYPES,
|
||||
STATES,
|
||||
} from 'client/constants'
|
||||
|
||||
/**
|
||||
* Returns the datastore type name.
|
||||
*
|
||||
* @param {object} datastore - Datastore
|
||||
* @param {number} datastore.TYPE - Datastore type
|
||||
* @param {Datastore} datastore - Datastore
|
||||
* @returns {DATASTORE_TYPES} - Datastore type object
|
||||
*/
|
||||
export const getType = ({ TYPE } = {}) => DATASTORE_TYPES[TYPE]
|
||||
@ -28,8 +32,7 @@ export const getType = ({ TYPE } = {}) => DATASTORE_TYPES[TYPE]
|
||||
/**
|
||||
* Returns information about datastore state.
|
||||
*
|
||||
* @param {object} datastore - Datastore
|
||||
* @param {number} datastore.STATE - Datastore state ID
|
||||
* @param {Datastore} datastore - Datastore
|
||||
* @returns {STATES.StateInfo} - Datastore state object
|
||||
*/
|
||||
export const getState = ({ STATE = 0 } = {}) => DATASTORE_STATES[STATE]
|
||||
@ -37,7 +40,7 @@ export const getState = ({ STATE = 0 } = {}) => DATASTORE_STATES[STATE]
|
||||
/**
|
||||
* Return the TM_MAD_SYSTEM attribute.
|
||||
*
|
||||
* @param {object} datastore - Datastore
|
||||
* @param {Datastore} datastore - Datastore
|
||||
* @returns {string[]} - The list of deploy modes available
|
||||
*/
|
||||
export const getDeployMode = (datastore = {}) => {
|
||||
@ -52,9 +55,7 @@ export const getDeployMode = (datastore = {}) => {
|
||||
/**
|
||||
* Returns information about datastore capacity.
|
||||
*
|
||||
* @param {object} datastore - Datastore
|
||||
* @param {number} datastore.TOTAL_MB - Total capacity in MB
|
||||
* @param {number} datastore.USED_MB - Used capacity in MB
|
||||
* @param {Datastore} datastore - Datastore
|
||||
* @returns {{
|
||||
* percentOfUsed: number,
|
||||
* percentLabel: string
|
||||
@ -74,8 +75,7 @@ export const getCapacityInfo = ({ TOTAL_MB, USED_MB } = {}) => {
|
||||
/**
|
||||
* Returns `true` if Datastore allows to export to Marketplace.
|
||||
*
|
||||
* @param {object} props - Datastore ob
|
||||
* @param {object} props.NAME - Name
|
||||
* @param {Datastore} datastore - Datastore
|
||||
* @param {object} oneConfig - One config from redux
|
||||
* @returns {boolean} - Datastore supports to export
|
||||
*/
|
||||
|
@ -24,6 +24,7 @@ import {
|
||||
import { camelCase } from 'client/utils'
|
||||
import {
|
||||
T,
|
||||
Permission,
|
||||
UserInputObject,
|
||||
USER_INPUT_TYPES,
|
||||
SERVER_CONFIG,
|
||||
@ -208,9 +209,9 @@ export const levelLockToString = (level) =>
|
||||
* Returns the permission numeric code.
|
||||
*
|
||||
* @param {string[]} category - Array with Use, Manage and Access permissions.
|
||||
* @param {('YES'|'NO')} category.0 - `true` if use permission is allowed
|
||||
* @param {('YES'|'NO')} category.1 - `true` if manage permission is allowed
|
||||
* @param {('YES'|'NO')} category.2 - `true` if access permission is allowed
|
||||
* @param {Permission} category.0 - `true` or `1` if use permission is allowed
|
||||
* @param {Permission} category.1 - `true` or `1` if manage permission is allowed
|
||||
* @param {Permission} category.2 - `true` or `1` if access permission is allowed
|
||||
* @returns {number} Permission code number.
|
||||
*/
|
||||
const getCategoryValue = ([u, m, a]) =>
|
||||
@ -222,15 +223,15 @@ const getCategoryValue = ([u, m, a]) =>
|
||||
* Transform the permission from OpenNebula template to octal format.
|
||||
*
|
||||
* @param {object} permissions - Permissions object.
|
||||
* @param {('YES'|'NO')} permissions.OWNER_U - Owner use permission.
|
||||
* @param {('YES'|'NO')} permissions.OWNER_M - Owner manage permission.
|
||||
* @param {('YES'|'NO')} permissions.OWNER_A - Owner access permission.
|
||||
* @param {('YES'|'NO')} permissions.GROUP_U - Group use permission.
|
||||
* @param {('YES'|'NO')} permissions.GROUP_M - Group manage permission.
|
||||
* @param {('YES'|'NO')} permissions.GROUP_A - Group access permission.
|
||||
* @param {('YES'|'NO')} permissions.OTHER_U - Other use permission.
|
||||
* @param {('YES'|'NO')} permissions.OTHER_M - Other manage permission.
|
||||
* @param {('YES'|'NO')} permissions.OTHER_A - Other access permission.
|
||||
* @param {Permission} permissions.OWNER_U - Owner use
|
||||
* @param {Permission} permissions.OWNER_M - Owner manage
|
||||
* @param {Permission} permissions.OWNER_A - Owner access
|
||||
* @param {Permission} permissions.GROUP_U - Group use
|
||||
* @param {Permission} permissions.GROUP_M - Group manage
|
||||
* @param {Permission} permissions.GROUP_A - Group access
|
||||
* @param {Permission} permissions.OTHER_U - Other use
|
||||
* @param {Permission} permissions.OTHER_M - Other manage
|
||||
* @param {Permission} permissions.OTHER_A - Other access
|
||||
* @returns {string} - Permissions in octal format.
|
||||
*/
|
||||
export const permissionsToOctal = (permissions) => {
|
||||
|
26
src/fireedge/src/client/models/Service.js
Normal file
26
src/fireedge/src/client/models/Service.js
Normal file
@ -0,0 +1,26 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { Service, SERVICE_STATES, STATES } from 'client/constants'
|
||||
|
||||
/**
|
||||
* Returns information about Service state.
|
||||
*
|
||||
* @param {Service} service - Service
|
||||
* @returns {STATES.StateInfo} - Service state object
|
||||
*/
|
||||
export const getState = ({ TEMPLATE = {} } = {}) =>
|
||||
SERVICE_STATES[TEMPLATE?.BODY?.state]
|
@ -48,9 +48,21 @@ export const sentenceCase = (input) => {
|
||||
*
|
||||
* @param {string} input - String to transform
|
||||
* @returns {string} string
|
||||
* @example //=> "testString"
|
||||
* @example // "test-string" => "testString"
|
||||
* @example // "test_string" => "testString"
|
||||
*/
|
||||
export const camelCase = (input) =>
|
||||
input
|
||||
.toLowerCase()
|
||||
.replace(/([-_\s][a-z])/gi, ($1) => $1.toUpperCase().replace(/[-_\s]/g, ''))
|
||||
|
||||
/**
|
||||
* Transform into a snake case string.
|
||||
*
|
||||
* @param {string} input - String to transform
|
||||
* @returns {string} string
|
||||
* @example // "test-string" => "test_string"
|
||||
* @example // "testString" => "test_string"
|
||||
* @example // "TESTString" => "test_string"
|
||||
*/
|
||||
export const toSnakeCase = (input) => sentenceCase(input).replace(/\s/g, '_')
|
||||
|
@ -14,31 +14,6 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
const action = {
|
||||
id: '/Action',
|
||||
type: 'object',
|
||||
properties: {
|
||||
action: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
perform: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
params: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
merge_template: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const role = {
|
||||
id: '/Role',
|
||||
type: 'object',
|
||||
@ -216,10 +191,6 @@ const service = {
|
||||
},
|
||||
}
|
||||
|
||||
const schemas = {
|
||||
action,
|
||||
role,
|
||||
service,
|
||||
}
|
||||
const schemas = { role, service }
|
||||
|
||||
module.exports = schemas
|
||||
|
@ -47,7 +47,7 @@ module.exports = {
|
||||
Actions,
|
||||
Commands: {
|
||||
[SERVICE_SHOW]: {
|
||||
path: `${basepath}/:id`,
|
||||
path: `${basepath}/:id?`,
|
||||
httpMethod: GET,
|
||||
auth: true,
|
||||
params: {
|
||||
|
@ -15,7 +15,7 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
const { Validator } = require('jsonschema')
|
||||
const { role, service, action } = require('server/routes/api/oneflow/schemas')
|
||||
const { role, service } = require('server/routes/api/oneflow/schemas')
|
||||
const {
|
||||
oneFlowConnection,
|
||||
returnSchemaError,
|
||||
@ -262,32 +262,22 @@ const serviceTemplateAction = (
|
||||
userData = {}
|
||||
) => {
|
||||
const { user, password } = userData
|
||||
if (params && params.id && params.template && user && password) {
|
||||
const v = new Validator()
|
||||
const template = parsePostData(params.template)
|
||||
const valSchema = v.validate(template, action)
|
||||
if (valSchema.valid) {
|
||||
const config = {
|
||||
method: POST,
|
||||
path: '/service_template/{0}/action',
|
||||
user,
|
||||
password,
|
||||
request: params.id,
|
||||
post: template,
|
||||
}
|
||||
oneFlowConnection(
|
||||
config,
|
||||
(data) => success(next, res, data),
|
||||
(data) => error(next, res, data)
|
||||
)
|
||||
} else {
|
||||
res.locals.httpCode = httpResponse(
|
||||
internalServerError,
|
||||
'',
|
||||
`invalid schema ${returnSchemaError(valSchema.errors)}`
|
||||
)
|
||||
next()
|
||||
|
||||
if (params && params.id && params.action && user && password) {
|
||||
const config = {
|
||||
method: POST,
|
||||
path: '/service_template/{0}/action',
|
||||
user,
|
||||
password,
|
||||
request: params.id,
|
||||
post: { action: parsePostData(params.action) },
|
||||
}
|
||||
|
||||
oneFlowConnection(
|
||||
config,
|
||||
(data) => success(next, res, data),
|
||||
(data) => error(next, res, data)
|
||||
)
|
||||
} else {
|
||||
res.locals.httpCode = httpResponse(
|
||||
methodNotAllowed,
|
||||
|
@ -58,7 +58,7 @@ module.exports = {
|
||||
id: {
|
||||
from: resource,
|
||||
},
|
||||
template: {
|
||||
action: {
|
||||
from: postBody,
|
||||
},
|
||||
},
|
||||
|
@ -62,6 +62,7 @@ const oneFlowConnection = (
|
||||
const optionMethod = method || GET
|
||||
const optionPath = path || '/'
|
||||
const optionAuth = btoa(`${user || ''}:${password || ''}`)
|
||||
|
||||
const options = {
|
||||
method: optionMethod,
|
||||
baseURL: appConfig.oneflow_server || defaultOneFlowServer,
|
||||
@ -69,39 +70,25 @@ const oneFlowConnection = (
|
||||
headers: {
|
||||
Authorization: `Basic ${optionAuth}`,
|
||||
},
|
||||
validateStatus: (status) => status,
|
||||
validateStatus: (status) => status >= 200 && status < 400,
|
||||
}
|
||||
|
||||
if (post) {
|
||||
options.data = post
|
||||
}
|
||||
if (post) options.data = post
|
||||
|
||||
axios(options)
|
||||
.then((response) => {
|
||||
if (response && response.statusText) {
|
||||
if (response.status >= 200 && response.status < 400) {
|
||||
if (response.data) {
|
||||
return response.data
|
||||
}
|
||||
if (
|
||||
response.config.method &&
|
||||
response.config.method.toUpperCase() === DELETE
|
||||
) {
|
||||
return Array.isArray(request)
|
||||
? parseToNumber(request[0])
|
||||
: parseToNumber(request)
|
||||
}
|
||||
} else if (response.data) {
|
||||
throw Error(response.data)
|
||||
}
|
||||
if (!response.statusText) throw Error(response.statusText)
|
||||
|
||||
if (`${response.config.method}`.toUpperCase() === DELETE) {
|
||||
return Array.isArray(request)
|
||||
? parseToNumber(request[0])
|
||||
: parseToNumber(request)
|
||||
}
|
||||
throw Error(response.statusText)
|
||||
})
|
||||
.then((data) => {
|
||||
success(data)
|
||||
})
|
||||
.catch((e) => {
|
||||
error(e)
|
||||
|
||||
return response.data
|
||||
})
|
||||
.then(success)
|
||||
.catch(error)
|
||||
}
|
||||
|
||||
const functionRoutes = {
|
||||
|
Loading…
x
Reference in New Issue
Block a user