diff --git a/src/fireedge/src/client/apps/sunstone/routesOne.js b/src/fireedge/src/client/apps/sunstone/routesOne.js
index 603338767ae..8d96526d1f8 100644
--- a/src/fireedge/src/client/apps/sunstone/routesOne.js
+++ b/src/fireedge/src/client/apps/sunstone/routesOne.js
@@ -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,
+ },
],
},
{
diff --git a/src/fireedge/src/client/components/Cards/ServiceCard.js b/src/fireedge/src/client/components/Cards/ServiceCard.js
new file mode 100644
index 00000000000..235ee9df37f
--- /dev/null
+++ b/src/fireedge/src/client/components/Cards/ServiceCard.js
@@ -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 (
+
+
+
+
+
+ {NAME}
+
+
+
+
+
+
+
+ {`#${ID}`}
+
+
+
+
+
+ {actions &&
{actions}
}
+
+ )
+ }
+)
+
+ServiceCard.propTypes = {
+ service: PropTypes.object,
+ rootProps: PropTypes.shape({
+ className: PropTypes.string,
+ }),
+ onDeleteLabel: PropTypes.func,
+ actions: PropTypes.any,
+}
+
+ServiceCard.displayName = 'ServiceCard'
+
+export default ServiceCard
diff --git a/src/fireedge/src/client/components/Cards/ServiceTemplateCard.js b/src/fireedge/src/client/components/Cards/ServiceTemplateCard.js
new file mode 100644
index 00000000000..fbedf928ad1
--- /dev/null
+++ b/src/fireedge/src/client/components/Cards/ServiceTemplateCard.js
@@ -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 (
+
+
+
+
+ {NAME}
+
+
+
+
+
+
+
{`#${ID}`}
+
+
+
+
+
+ {numberOfNetworks}
+
+
+
+ {numberOfRoles}
+
+
+
+ {actions &&
{actions}
}
+
+ )
+ }
+)
+
+ServiceTemplateCard.propTypes = {
+ template: PropTypes.object,
+ rootProps: PropTypes.shape({
+ className: PropTypes.string,
+ }),
+ onDeleteLabel: PropTypes.func,
+ actions: PropTypes.any,
+}
+
+ServiceTemplateCard.displayName = 'ServiceTemplateCard'
+
+export default ServiceTemplateCard
diff --git a/src/fireedge/src/client/components/Cards/index.js b/src/fireedge/src/client/components/Cards/index.js
index 5926326285a..29752ff131a 100644
--- a/src/fireedge/src/client/components/Cards/index.js
+++ b/src/fireedge/src/client/components/Cards/index.js
@@ -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,
diff --git a/src/fireedge/src/client/components/Charts/SingleBar.js b/src/fireedge/src/client/components/Charts/SingleBar.js
index 1a9f669682c..2c92859f4ea 100644
--- a/src/fireedge/src/client/components/Charts/SingleBar.js
+++ b/src/fireedge/src/client/components/Charts/SingleBar.js
@@ -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 (
{
placement="top"
title={`${label}: ${value}`}
>
-
+
)
})}
diff --git a/src/fireedge/src/client/components/Tables/Enhanced/index.js b/src/fireedge/src/client/components/Tables/Enhanced/index.js
index 1dfc409846a..06637f07738 100644
--- a/src/fireedge/src/client/components/Tables/Enhanced/index.js
+++ b/src/fireedge/src/client/components/Tables/Enhanced/index.js
@@ -67,6 +67,7 @@ const EnhancedTable = ({
classes = {},
rootProps = {},
searchProps = {},
+ noDataMessage,
}) => {
const styles = EnhancedTableStyles()
@@ -208,12 +209,15 @@ const EnhancedTable = ({
{/* NO DATA MESSAGE */}
- {!isLoading && !isUninitialized && page?.length === 0 && (
-
-
-
-
- )}
+ {!isLoading &&
+ !isUninitialized &&
+ page?.length === 0 &&
+ (noDataMessage || (
+
+
+
+
+ ))}
{/* 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'
diff --git a/src/fireedge/src/client/components/Tables/ServiceTemplates/columns.js b/src/fireedge/src/client/components/Tables/ServiceTemplates/columns.js
new file mode 100644
index 00000000000..e26b2a318db
--- /dev/null
+++ b/src/fireedge/src/client/components/Tables/ServiceTemplates/columns.js
@@ -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
diff --git a/src/fireedge/src/client/components/Tables/ServiceTemplates/index.js b/src/fireedge/src/client/components/Tables/ServiceTemplates/index.js
new file mode 100644
index 00000000000..4b2bdaaca70
--- /dev/null
+++ b/src/fireedge/src/client/components/Tables/ServiceTemplates/index.js
@@ -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 (
+
data, [data])}
+ rootProps={rootProps}
+ searchProps={searchProps}
+ refetch={refetch}
+ isLoading={isFetching}
+ getRowId={(row) => String(row.ID)}
+ RowComponent={ServiceTemplateRow}
+ noDataMessage={
+ error?.status === 500 && (
+
+
+
+ )
+ }
+ {...rest}
+ />
+ )
+}
+
+ServiceTemplatesTable.propTypes = { ...EnhancedTable.propTypes }
+ServiceTemplatesTable.displayName = 'ServiceTemplatesTable'
+
+export default ServiceTemplatesTable
diff --git a/src/fireedge/src/client/components/Tables/ServiceTemplates/row.js b/src/fireedge/src/client/components/Tables/ServiceTemplates/row.js
new file mode 100644
index 00000000000..9d1e2ad7a18
--- /dev/null
+++ b/src/fireedge/src/client/components/Tables/ServiceTemplates/row.js
@@ -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 (
+
+ )
+ },
+ (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
diff --git a/src/fireedge/src/client/components/Tables/Services/columns.js b/src/fireedge/src/client/components/Tables/Services/columns.js
new file mode 100644
index 00000000000..49d612cce5a
--- /dev/null
+++ b/src/fireedge/src/client/components/Tables/Services/columns.js
@@ -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
diff --git a/src/fireedge/src/client/components/Tables/Services/index.js b/src/fireedge/src/client/components/Tables/Services/index.js
new file mode 100644
index 00000000000..f5553e162b6
--- /dev/null
+++ b/src/fireedge/src/client/components/Tables/Services/index.js
@@ -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 (
+ data, [data])}
+ rootProps={rootProps}
+ searchProps={searchProps}
+ refetch={refetch}
+ isLoading={isFetching}
+ getRowId={(row) => String(row.ID)}
+ RowComponent={ServiceRow}
+ noDataMessage={
+ error?.status === 500 && (
+
+
+
+ )
+ }
+ {...rest}
+ />
+ )
+}
+
+ServicesTable.propTypes = { ...EnhancedTable.propTypes }
+ServicesTable.displayName = 'ServicesTable'
+
+export default ServicesTable
diff --git a/src/fireedge/src/client/components/Tables/Services/row.js b/src/fireedge/src/client/components/Tables/Services/row.js
new file mode 100644
index 00000000000..f52099f0173
--- /dev/null
+++ b/src/fireedge/src/client/components/Tables/Services/row.js
@@ -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
+ },
+ (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
diff --git a/src/fireedge/src/client/components/Tables/index.js b/src/fireedge/src/client/components/Tables/index.js
index 5537d8fd7be..064407bedb3 100644
--- a/src/fireedge/src/client/components/Tables/index.js
+++ b/src/fireedge/src/client/components/Tables/index.js
@@ -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,
diff --git a/src/fireedge/src/client/components/Tabs/Service/Actions.js b/src/fireedge/src/client/components/Tabs/Service/Actions.js
new file mode 100644
index 00000000000..5770b81fa5a
--- /dev/null
+++ b/src/fireedge/src/client/components/Tabs/Service/Actions.js
@@ -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 (
+ <>
+
+ {service?.NAME}
+ {/* TODO: scheduler actions & form */}
+
+ >
+ )
+}
+
+SchedulingTab.propTypes = {
+ tabProps: PropTypes.object,
+ id: PropTypes.string,
+}
+
+export default SchedulingTab
diff --git a/src/fireedge/src/client/components/Tabs/Service/Info/index.js b/src/fireedge/src/client/components/Tabs/Service/Info/index.js
new file mode 100644
index 00000000000..3da6965f51f
--- /dev/null
+++ b/src/fireedge/src/client/components/Tabs/Service/Info/index.js
@@ -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 (
+
+ {informationPanel?.enabled && (
+
+ )}
+ {permissionsPanel?.enabled && (
+
+ )}
+ {ownershipPanel?.enabled && (
+
+ )}
+
+ )
+}
+
+ServiceInfoTab.propTypes = {
+ tabProps: PropTypes.object,
+ id: PropTypes.string,
+}
+
+ServiceInfoTab.displayName = 'ServiceInfoTab'
+
+export default ServiceInfoTab
diff --git a/src/fireedge/src/client/components/Tabs/Service/Info/information.js b/src/fireedge/src/client/components/Tabs/Service/Info/information.js
new file mode 100644
index 00000000000..74f3f967ed9
--- /dev/null
+++ b/src/fireedge/src/client/components/Tabs/Service/Info/information.js
@@ -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: (
+
+
+
+
+ ),
+ },
+ {
+ 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 (
+
+ )
+}
+
+InformationPanel.displayName = 'InformationPanel'
+
+InformationPanel.propTypes = {
+ service: PropTypes.object,
+ actions: PropTypes.arrayOf(PropTypes.string),
+}
+
+export default InformationPanel
diff --git a/src/fireedge/src/client/components/Tabs/Service/Log.js b/src/fireedge/src/client/components/Tabs/Service/Log.js
new file mode 100644
index 00000000000..9f0328cdd8b
--- /dev/null
+++ b/src/fireedge/src/client/components/Tabs/Service/Log.js
@@ -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 (
+
+ {log?.map(({ severity, message, timestamp } = {}) => {
+ const time = timeFromMilliseconds(+timestamp)
+ const isError = severity === SERVICE_LOG_SEVERITY.ERROR
+
+ return (
+
+ {`${time.toFormat('ff')} [${severity}] ${message}`}
+
+ )
+ })}
+
+ )
+}
+
+LogTab.propTypes = { tabProps: PropTypes.object, id: PropTypes.string }
+LogTab.displayName = 'RolesTab'
+
+export default LogTab
diff --git a/src/fireedge/src/client/components/Tabs/Service/Roles.js b/src/fireedge/src/client/components/Tabs/Service/Roles.js
new file mode 100644
index 00000000000..a11986f2ab9
--- /dev/null
+++ b/src/fireedge/src/client/components/Tabs/Service/Roles.js
@@ -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 (
+
+ {COLUMNS.map((col) => (
+
+
+
+ ))}
+ {roles.map((role, idx) => (
+ *:not(span)': { bgcolor: 'action.hover' } }}
+ >
+
+
+ ))}
+
+ )
+}
+
+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 (
+ <>
+
+ {name}
+
+
+ {cardinality}
+
+ {isLoading ? (
+
+ ) : (
+
+ {`#${template?.ID} ${template?.NAME}`}
+
+ )}
+
+ {parents?.join?.()}
+
+ >
+ )
+})
+
+RoleComponent.propTypes = { role: PropTypes.object }
+RoleComponent.displayName = 'RoleComponent'
+
+export default RolesTab
diff --git a/src/fireedge/src/client/components/Tabs/Service/index.js b/src/fireedge/src/client/components/Tabs/Service/index.js
new file mode 100644
index 00000000000..82d95b1a828
--- /dev/null
+++ b/src/fireedge/src/client/components/Tabs/Service/index.js
@@ -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 (
+
+ {error.data}
+
+ )
+ }
+
+ return isLoading ? (
+
+ ) : (
+
+ )
+})
+
+ServiceTabs.propTypes = { id: PropTypes.string.isRequired }
+ServiceTabs.displayName = 'ServiceTabs'
+
+export default ServiceTabs
diff --git a/src/fireedge/src/client/components/Tabs/ServiceTemplate/Info/index.js b/src/fireedge/src/client/components/Tabs/ServiceTemplate/Info/index.js
new file mode 100644
index 00000000000..ed60e983c01
--- /dev/null
+++ b/src/fireedge/src/client/components/Tabs/ServiceTemplate/Info/index.js
@@ -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 (
+
+ {informationPanel?.enabled && (
+
+ )}
+ {permissionsPanel?.enabled && (
+
+ )}
+ {ownershipPanel?.enabled && (
+
+ )}
+
+ )
+}
+
+ServiceTemplateInfoTab.propTypes = {
+ tabProps: PropTypes.object,
+ id: PropTypes.string,
+}
+
+ServiceTemplateInfoTab.displayName = 'ServiceTemplateInfoTab'
+
+export default ServiceTemplateInfoTab
diff --git a/src/fireedge/src/client/components/Tabs/ServiceTemplate/Info/information.js b/src/fireedge/src/client/components/Tabs/ServiceTemplate/Info/information.js
new file mode 100644
index 00000000000..76fbd071497
--- /dev/null
+++ b/src/fireedge/src/client/components/Tabs/ServiceTemplate/Info/information.js
@@ -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 (
+
+ )
+}
+
+InformationPanel.displayName = 'InformationPanel'
+
+InformationPanel.propTypes = {
+ actions: PropTypes.arrayOf(PropTypes.string),
+ template: PropTypes.object,
+}
+
+export default InformationPanel
diff --git a/src/fireedge/src/client/components/Tabs/ServiceTemplate/Roles.js b/src/fireedge/src/client/components/Tabs/ServiceTemplate/Roles.js
new file mode 100644
index 00000000000..59758260771
--- /dev/null
+++ b/src/fireedge/src/client/components/Tabs/ServiceTemplate/Roles.js
@@ -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 (
+
+ {COLUMNS.map((col) => (
+
+
+
+ ))}
+ {roles.map((role, idx) => (
+ *:not(span)': { bgcolor: 'action.hover' } }}
+ >
+
+
+ ))}
+
+ )
+}
+
+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 (
+ <>
+
+ {name}
+
+
+ {cardinality}
+
+ {isLoading ? (
+
+ ) : (
+
+ {`#${template?.ID} ${template?.NAME}`}
+
+ )}
+
+ {parents?.join?.()}
+
+ >
+ )
+})
+
+RoleComponent.propTypes = { role: PropTypes.object }
+RoleComponent.displayName = 'RoleComponent'
+
+export default RolesTab
diff --git a/src/fireedge/src/client/components/Tabs/ServiceTemplate/Template.js b/src/fireedge/src/client/components/Tabs/ServiceTemplate/Template.js
new file mode 100644
index 00000000000..dc5f76ab02e
--- /dev/null
+++ b/src/fireedge/src/client/components/Tabs/ServiceTemplate/Template.js
@@ -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 (
+
+
+
+
+ {JSON.stringify(template?.TEMPLATE?.BODY ?? {}, null, 2)}
+
+
+
+
+ )
+}
+
+TemplateTab.propTypes = {
+ tabProps: PropTypes.object,
+ id: PropTypes.string,
+}
+
+TemplateTab.displayName = 'TemplateTab'
+
+export default TemplateTab
diff --git a/src/fireedge/src/client/components/Tabs/ServiceTemplate/index.js b/src/fireedge/src/client/components/Tabs/ServiceTemplate/index.js
new file mode 100644
index 00000000000..4e9cb643644
--- /dev/null
+++ b/src/fireedge/src/client/components/Tabs/ServiceTemplate/index.js
@@ -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 (
+
+ {error.data}
+
+ )
+ }
+
+ return isLoading ? (
+
+ ) : (
+
+ )
+})
+
+ServiceTemplateTabs.propTypes = { id: PropTypes.string.isRequired }
+ServiceTemplateTabs.displayName = 'ServiceTemplateTabs'
+
+export default ServiceTemplateTabs
diff --git a/src/fireedge/src/client/constants/flow.js b/src/fireedge/src/client/constants/flow.js
index 8bee91834e1..4f3b03b53dc 100644
--- a/src/fireedge/src/client/constants/flow.js
+++ b/src/fireedge/src/client/constants/flow.js
@@ -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',
},
{
+ // 6
+ name: STATES.FAILED_UNDEPLOYING,
+ color: COLOR.error.dark,
+ meaning: 'An error occurred while undeploying the VMs',
+ },
+ {
+ // 7
name: STATES.FAILED_DEPLOYING,
- color: '',
+ color: COLOR.error.dark,
meaning: 'An error occurred while deploying the VMs',
},
{
- name: STATES.FAILED_UNDEPLOYING,
- color: '',
- meaning: 'An error occurred while undeploying 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',
+}
diff --git a/src/fireedge/src/client/constants/index.js b/src/fireedge/src/client/constants/index.js
index 58cea46aa9b..c3053298317 100644
--- a/src/fireedge/src/client/constants/index.js
+++ b/src/fireedge/src/client/constants/index.js
@@ -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',
}
diff --git a/src/fireedge/src/client/constants/translates.js b/src/fireedge/src/client/constants/translates.js
index ad75c177c23..d5485b780c7 100644
--- a/src/fireedge/src/client/constants/translates.js
+++ b/src/fireedge/src/client/constants/translates.js
@@ -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',
diff --git a/src/fireedge/src/client/containers/ServiceTemplates/Detail.js b/src/fireedge/src/client/containers/ServiceTemplates/Detail.js
new file mode 100644
index 00000000000..476eeb7c3bc
--- /dev/null
+++ b/src/fireedge/src/client/containers/ServiceTemplates/Detail.js
@@ -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
+ }
+
+ return
+}
+
+export default ServiceTemplateDetail
diff --git a/src/fireedge/src/client/containers/ServiceTemplates/index.js b/src/fireedge/src/client/containers/ServiceTemplates/index.js
new file mode 100644
index 00000000000..6393cbdca9a
--- /dev/null
+++ b/src/fireedge/src/client/containers/ServiceTemplates/index.js
@@ -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 (
+
+ {({ getGridProps, GutterComponent }) => (
+
+
+
+ {hasSelectedRows && (
+ <>
+
+ {moreThanOneSelected ? (
+
+ ) : (
+
+ )}
+ >
+ )}
+
+ )}
+
+ )
+}
+
+/**
+ * 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 (
+
+
+
+ {`#${id} | ${template.NAME}`}
+
+ {gotoPage && (
+
+
+
+ )}
+
+
+
+ )
+})
+
+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 = [] }) => (
+
+ (
+ toggleRowSelected(false)}
+ />
+ ))}
+ />
+
+))
+
+GroupedTags.propTypes = { tags: PropTypes.array }
+GroupedTags.displayName = 'GroupedTags'
+
+export default ServiceTemplates
diff --git a/src/fireedge/src/client/containers/Services/Detail.js b/src/fireedge/src/client/containers/Services/Detail.js
new file mode 100644
index 00000000000..55336097fb8
--- /dev/null
+++ b/src/fireedge/src/client/containers/Services/Detail.js
@@ -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
+ }
+
+ return
+}
+
+export default ServiceDetail
diff --git a/src/fireedge/src/client/containers/Services/index.js b/src/fireedge/src/client/containers/Services/index.js
new file mode 100644
index 00000000000..fb73f74de65
--- /dev/null
+++ b/src/fireedge/src/client/containers/Services/index.js
@@ -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 (
+
+ {({ getGridProps, GutterComponent }) => (
+
+
+
+ {hasSelectedRows && (
+ <>
+
+ {moreThanOneSelected ? (
+
+ ) : (
+
+ )}
+ >
+ )}
+
+ )}
+
+ )
+}
+
+/**
+ * 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 (
+
+
+
+ {`#${id} | ${template.NAME}`}
+
+ {gotoPage && (
+
+
+
+ )}
+
+
+
+ )
+})
+
+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 = [] }) => (
+
+ (
+ toggleRowSelected(false)}
+ />
+ ))}
+ />
+
+))
+
+GroupedTags.propTypes = { tags: PropTypes.array }
+GroupedTags.displayName = 'GroupedTags'
+
+export default Services
diff --git a/src/fireedge/src/client/features/OneApi/common.js b/src/fireedge/src/client/features/OneApi/common.js
index 4ff6fd92131..100cd2cb6bd 100644
--- a/src/fireedge/src/client/features/OneApi/common.js
+++ b/src/fireedge/src/client/features/OneApi/common.js
@@ -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
+ }
diff --git a/src/fireedge/src/client/features/OneApi/index.js b/src/fireedge/src/client/features/OneApi/index.js
index 08fa55863a6..058194a3122 100644
--- a/src/fireedge/src/client/features/OneApi/index.js
+++ b/src/fireedge/src/client/features/OneApi/index.js
@@ -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({
diff --git a/src/fireedge/src/client/features/OneApi/service.js b/src/fireedge/src/client/features/OneApi/service.js
index cc980531e99..6cfb1450158 100644
--- a/src/fireedge/src/client/features/OneApi/service.js
+++ b/src/fireedge/src/client/features/OneApi/service.js
@@ -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 {
+ // if the query fails, we want to remove the resource from the pool
+ dispatch(
+ serviceApi.util.updateQueryData(
+ 'getServices',
+ undefined,
+ removeResourceOnPool({ id })
)
)
- } catch {}
+ }
},
}),
}),
diff --git a/src/fireedge/src/client/features/OneApi/serviceTemplate.js b/src/fireedge/src/client/features/OneApi/serviceTemplate.js
index aa1e4f042ab..7bc90b6c4dc 100644
--- a/src/fireedge/src/client/features/OneApi/serviceTemplate.js
+++ b/src/fireedge/src/client/features/OneApi/serviceTemplate.js
@@ -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
diff --git a/src/fireedge/src/client/features/OneApi/vm.js b/src/fireedge/src/client/features/OneApi/vm.js
index 9654409806d..8d14e60237f 100644
--- a/src/fireedge/src/client/features/OneApi/vm.js
+++ b/src/fireedge/src/client/features/OneApi/vm.js
@@ -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
diff --git a/src/fireedge/src/client/models/Datastore.js b/src/fireedge/src/client/models/Datastore.js
index 70f4d11bda3..3ba9e3f52dd 100644
--- a/src/fireedge/src/client/models/Datastore.js
+++ b/src/fireedge/src/client/models/Datastore.js
@@ -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
*/
diff --git a/src/fireedge/src/client/models/Helper.js b/src/fireedge/src/client/models/Helper.js
index 157a5b18ad4..ceec4bee3f7 100644
--- a/src/fireedge/src/client/models/Helper.js
+++ b/src/fireedge/src/client/models/Helper.js
@@ -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) => {
diff --git a/src/fireedge/src/client/models/Service.js b/src/fireedge/src/client/models/Service.js
new file mode 100644
index 00000000000..12a7f0548d1
--- /dev/null
+++ b/src/fireedge/src/client/models/Service.js
@@ -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]
diff --git a/src/fireedge/src/client/utils/string.js b/src/fireedge/src/client/utils/string.js
index 5638cdfc069..da42810dcdb 100644
--- a/src/fireedge/src/client/utils/string.js
+++ b/src/fireedge/src/client/utils/string.js
@@ -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, '_')
diff --git a/src/fireedge/src/server/routes/api/oneflow/schemas.js b/src/fireedge/src/server/routes/api/oneflow/schemas.js
index d7929df9a64..e5cd00d27b9 100644
--- a/src/fireedge/src/server/routes/api/oneflow/schemas.js
+++ b/src/fireedge/src/server/routes/api/oneflow/schemas.js
@@ -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
diff --git a/src/fireedge/src/server/routes/api/oneflow/service/routes.js b/src/fireedge/src/server/routes/api/oneflow/service/routes.js
index 2518cbc6908..f4d28616787 100644
--- a/src/fireedge/src/server/routes/api/oneflow/service/routes.js
+++ b/src/fireedge/src/server/routes/api/oneflow/service/routes.js
@@ -47,7 +47,7 @@ module.exports = {
Actions,
Commands: {
[SERVICE_SHOW]: {
- path: `${basepath}/:id`,
+ path: `${basepath}/:id?`,
httpMethod: GET,
auth: true,
params: {
diff --git a/src/fireedge/src/server/routes/api/oneflow/template/functions.js b/src/fireedge/src/server/routes/api/oneflow/template/functions.js
index 84b25ae8a31..b7d1fbd8d84 100644
--- a/src/fireedge/src/server/routes/api/oneflow/template/functions.js
+++ b/src/fireedge/src/server/routes/api/oneflow/template/functions.js
@@ -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,
diff --git a/src/fireedge/src/server/routes/api/oneflow/template/routes.js b/src/fireedge/src/server/routes/api/oneflow/template/routes.js
index 1f72f3e9fc5..55247adcd0b 100644
--- a/src/fireedge/src/server/routes/api/oneflow/template/routes.js
+++ b/src/fireedge/src/server/routes/api/oneflow/template/routes.js
@@ -58,7 +58,7 @@ module.exports = {
id: {
from: resource,
},
- template: {
+ action: {
from: postBody,
},
},
diff --git a/src/fireedge/src/server/routes/api/oneflow/utils.js b/src/fireedge/src/server/routes/api/oneflow/utils.js
index 8db1284fcf8..236b19d0765 100644
--- a/src/fireedge/src/server/routes/api/oneflow/utils.js
+++ b/src/fireedge/src/server/routes/api/oneflow/utils.js
@@ -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 = {