1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-30 22:50:10 +03:00

F #5755: React Sunstone host tab functionality (#1873)

Co-authored-by: ie-saiko <i.saiko@spd-ukraine.com>
Co-authored-by: Tino Vázquez <cvazquez@opennebula.io>
This commit is contained in:
bmoskovchenko 2022-04-08 14:51:09 +03:00 committed by GitHub
parent 07c752c353
commit 7aaec867e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 2087 additions and 25 deletions

View File

@ -106,6 +106,9 @@ const Hosts = loadable(() => import('client/containers/Hosts'), { ssr: false })
const HostDetail = loadable(() => import('client/containers/Hosts/Detail'), {
ssr: false,
})
const CreateHost = loadable(() => import('client/containers/Hosts/Create'), {
ssr: false,
})
const Zones = loadable(() => import('client/containers/Zones'), { ssr: false })
const Users = loadable(() => import('client/containers/Users'), { ssr: false })
@ -180,6 +183,7 @@ export const PATH = {
HOSTS: {
LIST: `/${RESOURCE_NAMES.HOST}`,
DETAIL: `/${RESOURCE_NAMES.HOST}/:id`,
CREATE: `/${RESOURCE_NAMES.HOST}/create`,
},
ZONES: {
LIST: `/${RESOURCE_NAMES.ZONE}`,
@ -329,6 +333,11 @@ const ENDPOINTS = [
icon: HostIcon,
Component: Hosts,
},
{
label: 'Create Host',
path: PATH.INFRASTRUCTURE.HOSTS.CREATE,
Component: CreateHost,
},
{
label: (params) => `Hosts #${params.id}`,
path: PATH.INFRASTRUCTURE.HOSTS.DETAIL,

View File

@ -50,10 +50,8 @@ const ToggleController = memo(
fieldProps = {},
readOnly = false,
}) => {
const defaultValue = multiple ? [values?.[0]?.value] : values?.[0]?.value
const {
field: { ref, value: optionSelected = defaultValue, onChange },
field: { ref, value: optionSelected, onChange },
fieldState: { error: { message } = {} },
} = useController({ name, control })

View File

@ -0,0 +1,24 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
* not use this file except in compliance with the License. You may obtain *
* a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import { createForm } from 'client/utils'
import {
SCHEMA,
FIELDS,
} from 'client/components/Forms/Host/ChangeClusterForm/schema'
const ChangeClusterForm = createForm(SCHEMA, FIELDS)
export default ChangeClusterForm

View File

@ -0,0 +1,39 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
* not use this file except in compliance with the License. You may obtain *
* a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import { string, object, ObjectSchema } from 'yup'
import { ClustersTable } from 'client/components/Tables'
import { T, INPUT_TYPES } from 'client/constants'
import { Field, getValidationFromFields } from 'client/utils'
/** @type {Field} Cluster field */
const CLUSTER = {
name: 'cluster',
label: T.SelectNewCluster,
type: INPUT_TYPES.TABLE,
Table: () => ClustersTable,
validation: string()
.trim()
.required()
.default(() => undefined),
grid: { md: 12 },
}
/** @type {Field[]} List of fields */
export const FIELDS = [CLUSTER]
/** @type {ObjectSchema} Schema */
export const SCHEMA = object(getValidationFromFields(FIELDS))

View File

@ -0,0 +1,69 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
* not use this file except in compliance with the License. You may obtain *
* a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import PropTypes from 'prop-types'
import { useListForm } from 'client/hooks'
import { ClustersTable } from 'client/components/Tables'
import { Step } from 'client/utils'
import { SCHEMA } from 'client/components/Forms/Host/CreateForm/Steps/ClustersTable/schema'
import { T } from 'client/constants'
export const STEP_ID = 'cluster'
const Content = ({ data, setFormData }) => {
const { ID } = data?.[0] ?? {}
const { handleSelect, handleClear } = useListForm({
key: STEP_ID,
setList: setFormData,
})
const handleSelectedRows = (rows) => {
const { original = {} } = rows?.[0] ?? {}
original.ID !== undefined ? handleSelect(original) : handleClear()
}
return (
<ClustersTable
singleSelect
onlyGlobalSearch
onlyGlobalSelectedRows
initialState={{ selectedRowIds: { [ID]: true } }}
onSelectedRowsChange={handleSelectedRows}
/>
)
}
/**
* Step to select the Cluster.
*
* @returns {Step} Cluster Selection step
*/
const ClustersTableStep = () => ({
id: STEP_ID,
label: T.SelectCluster,
resolver: SCHEMA,
content: Content,
})
Content.propTypes = {
data: PropTypes.any,
setFormData: PropTypes.func,
}
export default ClustersTableStep

View File

@ -0,0 +1,23 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
* not use this file except in compliance with the License. You may obtain *
* a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import { array, object } from 'yup'
export const SCHEMA = array(object())
.min(1)
.max(1)
.required()
.ensure()
.default(() => [])

View File

@ -0,0 +1,84 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
* not use this file except in compliance with the License. You may obtain *
* a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import { useMemo } from 'react'
import { useWatch } from 'react-hook-form'
import PropTypes from 'prop-types'
import FormWithSchema from 'client/components/Forms/FormWithSchema'
import { Step } from 'client/utils'
import { T, CUSTOM_HOST_HYPERVISOR } from 'client/constants'
import {
SCHEMA,
INFORMATION_FIELD,
DRIVERS_FIELDS,
HYPERVISOR_FIELD,
} from 'client/components/Forms/Host/CreateForm/Steps/General/schema'
export const STEP_ID = 'general'
const Content = () => {
const hypervisor = useWatch({ name: `${STEP_ID}.vmmMad` })
const driversFields = useMemo(
() => hypervisor === CUSTOM_HOST_HYPERVISOR.NAME && DRIVERS_FIELDS,
[hypervisor]
)
return (
<>
<FormWithSchema
id={STEP_ID}
cy={`${STEP_ID}-hypervisor`}
legend={T.Hypervisor}
fields={[HYPERVISOR_FIELD]}
/>
<FormWithSchema
id={STEP_ID}
cy={`${STEP_ID}-information`}
legend={T.Information}
fields={[INFORMATION_FIELD]}
/>
{driversFields && (
<FormWithSchema
id={STEP_ID}
cy={`${STEP_ID}-drivers`}
legend={T.Drivers}
fields={driversFields}
/>
)}
</>
)
}
/**
* Step to input the Host General information.
*
* @returns {Step} General information step
*/
const General = () => ({
id: STEP_ID,
label: T.General,
resolver: SCHEMA,
optionsValidate: { abortEarly: false },
content: Content,
})
General.propTypes = {
data: PropTypes.object,
setFormData: PropTypes.func,
}
export default General

View File

@ -0,0 +1,162 @@
/* ------------------------------------------------------------------------- *
* 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 { BaseSchema, string } from 'yup'
import { T, INPUT_TYPES, CUSTOM_HOST_HYPERVISOR } from 'client/constants'
import {
Field,
getObjectSchemaFromFields,
OPTION_SORTERS,
arrayToOptions,
} from 'client/utils'
import { getHostHypervisors } from 'client/models/Host'
/** @type {Field} Hypervisor field */
const HYPERVISOR_FIELD = {
name: 'vmmMad',
type: INPUT_TYPES.TOGGLE,
values: () =>
arrayToOptions(getHostHypervisors({ includeCustom: true }), {
addEmpty: false,
getText: (hypervisor) => hypervisor.displayName,
getValue: (hypervisor) => hypervisor.driverName,
sorter: OPTION_SORTERS.unsort,
}),
validation: string()
.trim()
.required()
.default(() => undefined),
grid: { md: 12 },
}
/** @type {Field} Name field */
const INFORMATION_FIELD = {
name: 'hostname',
label: T.Name,
type: INPUT_TYPES.TEXT,
validation: string()
.trim()
.required()
.default(() => undefined),
grid: { md: 12 },
}
/** @type {Field} Custom virtualization selector field */
const CUSTOM_VM_MAD = {
name: 'customVmmMad',
label: T.Virtualization,
type: INPUT_TYPES.AUTOCOMPLETE,
dependOf: HYPERVISOR_FIELD.name,
htmlType: (vmmMad) =>
vmmMad !== CUSTOM_HOST_HYPERVISOR.NAME && INPUT_TYPES.HIDDEN,
values: () =>
arrayToOptions(getHostHypervisors({ includeCustom: true }), {
addEmpty: false,
getText: (item) => item.displayName,
getValue: (item) => item.driverName,
sorter: OPTION_SORTERS.unsort,
}),
validation: string()
.trim()
.default(() => undefined)
.when(HYPERVISOR_FIELD.name, (vmmMad, schema) =>
vmmMad === CUSTOM_HOST_HYPERVISOR.NAME
? schema.required()
: schema.strip().notRequired()
),
grid: { md: 7 },
}
/** @type {Field} Custom information selector field */
const CUSTOM_IM_MAD = {
name: 'customImMad',
label: T.Information,
type: INPUT_TYPES.AUTOCOMPLETE,
dependOf: HYPERVISOR_FIELD.name,
htmlType: (vmmMad) =>
vmmMad !== CUSTOM_HOST_HYPERVISOR.NAME && INPUT_TYPES.HIDDEN,
values: () =>
arrayToOptions(getHostHypervisors({ includeCustom: true }), {
addEmpty: false,
getText: (item) => item.displayName,
getValue: (item) => item.driverName,
sorter: OPTION_SORTERS.unsort,
}),
validation: string()
.trim()
.default(() => undefined)
.when(HYPERVISOR_FIELD.name, (vmmMad, schema) =>
vmmMad === CUSTOM_HOST_HYPERVISOR.NAME
? schema.required()
: schema.strip().notRequired()
),
grid: { md: 7 },
}
/** @type {Field} Custom Virtualization field */
const CUSTOM_VIRTUALIZATION = {
name: 'customVmm',
label: T.CustomVirtualization,
type: INPUT_TYPES.TEXT,
dependOf: CUSTOM_VM_MAD.name,
htmlType: (vmm) => vmm !== CUSTOM_HOST_HYPERVISOR.NAME && INPUT_TYPES.HIDDEN,
validation: string()
.trim()
.default(() => undefined)
.when(CUSTOM_VM_MAD.name, (vmm, schema) =>
vmm === CUSTOM_HOST_HYPERVISOR.NAME
? schema.required()
: schema.strip().notRequired()
),
grid: { md: 5 },
}
/** @type {Field} Custom Information field */
const CUSTOM_INFORMATION = {
name: 'customIm',
label: T.CustomInformation,
type: INPUT_TYPES.TEXT,
dependOf: CUSTOM_IM_MAD.name,
htmlType: (im) => im !== CUSTOM_HOST_HYPERVISOR.NAME && INPUT_TYPES.HIDDEN,
validation: string()
.trim()
.default(() => undefined)
.when(CUSTOM_IM_MAD.name, (im, schema) =>
im === CUSTOM_HOST_HYPERVISOR.NAME
? schema.required()
: schema.notRequired()
),
grid: { md: 5 },
}
/** @type {Field[]} List of drivers fields */
const DRIVERS_FIELDS = [
CUSTOM_VM_MAD,
CUSTOM_VIRTUALIZATION,
CUSTOM_IM_MAD,
CUSTOM_INFORMATION,
]
/** @type {BaseSchema} General step schema */
const SCHEMA = getObjectSchemaFromFields([
HYPERVISOR_FIELD,
INFORMATION_FIELD,
...DRIVERS_FIELDS,
])
export { SCHEMA, HYPERVISOR_FIELD, INFORMATION_FIELD, DRIVERS_FIELDS }

View File

@ -0,0 +1,37 @@
/* ------------------------------------------------------------------------- *
* 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 ClustersTable, {
STEP_ID as CLUSTER_ID,
} from 'client/components/Forms/Host/CreateForm/Steps/ClustersTable'
import General, {
STEP_ID as GENERAL_ID,
} from 'client/components/Forms/Host/CreateForm/Steps/General'
import { createSteps } from 'client/utils'
const Steps = createSteps([General, ClustersTable], {
transformBeforeSubmit: (formData) => {
const { [GENERAL_ID]: general, [CLUSTER_ID]: [cluster] = [] } = formData
return {
hostname: general.hostname,
vmmMad: general.customVmm || general.customVmmMad || general.vmmMad,
imMad: general.customIm || general.customImMad || general.vmmMad,
cluster: cluster.ID,
}
},
})
export default Steps

View File

@ -0,0 +1,16 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
* not use this file except in compliance with the License. You may obtain *
* a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
export { default } from 'client/components/Forms/Host/CreateForm/Steps'

View File

@ -0,0 +1,34 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
* not use this file except in compliance with the License. You may obtain *
* a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import { ReactElement } from 'react'
import { AsyncLoadForm, ConfigurationProps } from 'client/components/HOC'
import { CreateFormCallback, CreateStepsCallback } from 'client/utils/schema'
/**
* @param {ConfigurationProps} configProps - Configuration
* @returns {ReactElement|CreateFormCallback} Asynchronous loaded form
*/
const ChangeClusterForm = (configProps) =>
AsyncLoadForm({ formPath: 'Host/ChangeClusterForm' }, configProps)
/**
* @param {ConfigurationProps} configProps - Configuration
* @returns {ReactElement|CreateStepsCallback} Asynchronous loaded form
*/
const CreateForm = (configProps) =>
AsyncLoadForm({ formPath: 'Host/CreateForm' }, configProps)
export { ChangeClusterForm, CreateForm }

View File

@ -0,0 +1,172 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
* not use this file except in compliance with the License. You may obtain *
* a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import { useMemo } from 'react'
import { useHistory } from 'react-router-dom'
import { AddSquare, Trash } from 'iconoir-react'
import { useViews } from 'client/features/Auth'
import { useAddHostToClusterMutation } from 'client/features/OneApi/cluster'
import {
useDisableHostMutation,
useEnableHostMutation,
useOfflineHostMutation,
useRemoveHostMutation,
} from 'client/features/OneApi/host'
import { Translate } from 'client/components/HOC'
import { ChangeClusterForm } from 'client/components/Forms/Host'
import {
createActions,
GlobalAction,
} from 'client/components/Tables/Enhanced/Utils'
import { PATH } from 'client/apps/sunstone/routesOne'
import { T, HOST_ACTIONS, RESOURCE_NAMES } from 'client/constants'
const MessageToConfirmAction = (rows) => {
const names = rows?.map?.(({ original }) => original?.NAME)
return (
<>
<p>
<Translate word={T.Hosts} />
{`: ${names.join(', ')}`}
</p>
<p>
<Translate word={T.DoYouWantProceed} />
</p>
</>
)
}
MessageToConfirmAction.displayName = 'MessageToConfirmAction'
/**
* Generates the actions to operate resources on Host table.
*
* @returns {GlobalAction} - Actions
*/
const Actions = () => {
const history = useHistory()
const { view, getResourceView } = useViews()
const [enable] = useEnableHostMutation()
const [remove] = useRemoveHostMutation()
const [disable] = useDisableHostMutation()
const [offline] = useOfflineHostMutation()
const [changeCluster] = useAddHostToClusterMutation()
const hostActions = useMemo(
() =>
createActions({
filters: getResourceView(RESOURCE_NAMES.HOST)?.actions,
actions: [
{
accessor: HOST_ACTIONS.CREATE_DIALOG,
dataCy: `host_${HOST_ACTIONS.CREATE_DIALOG}`,
tooltip: T.Create,
icon: AddSquare,
action: () => history.push(PATH.INFRASTRUCTURE.HOSTS.CREATE),
},
{
accessor: HOST_ACTIONS.CHANGE_CLUSTER,
color: 'secondary',
dataCy: `host-${HOST_ACTIONS.CHANGE_CLUSTER}`,
label: T.SelectCluster,
tooltip: T.SelectCluster,
selected: true,
options: [
{
dialogProps: {
title: T.SelectCluster,
},
form: (rows) => ChangeClusterForm(),
onSubmit: (rows) => async (formData) => {
const ids = rows?.map?.(({ original }) => original?.ID)
await Promise.all(
ids.map((id) =>
changeCluster({ id: formData.cluster, host: id })
)
)
},
},
],
},
{
accessor: HOST_ACTIONS.ENABLE,
color: 'secondary',
dataCy: `host_${HOST_ACTIONS.ENABLE}`,
label: T.Enable,
tooltip: T.Enable,
selected: true,
action: async (rows) => {
const ids = rows?.map?.(({ original }) => original?.ID)
await Promise.all(ids.map((id) => enable(id)))
},
},
{
accessor: HOST_ACTIONS.DISABLE,
color: 'secondary',
dataCy: `host_${HOST_ACTIONS.DISABLE}`,
label: T.Disable,
tooltip: T.Disable,
selected: true,
action: async (rows) => {
const ids = rows?.map?.(({ original }) => original?.ID)
await Promise.all(ids.map((id) => disable(id)))
},
},
{
accessor: HOST_ACTIONS.OFFLINE,
color: 'secondary',
dataCy: `host_${HOST_ACTIONS.OFFLINE}`,
label: T.Offline,
tooltip: T.Offline,
selected: true,
action: async (rows) => {
const ids = rows?.map?.(({ original }) => original?.ID)
await Promise.all(ids.map((id) => offline(id)))
},
},
{
accessor: HOST_ACTIONS.DELETE,
color: 'error',
dataCy: 'host-delete',
icon: Trash,
tooltip: T.Delete,
selected: true,
options: [
{
isConfirmDialog: true,
dialogProps: {
title: T.Delete,
children: MessageToConfirmAction,
},
onSubmit: (rows) => async () => {
const ids = rows?.map?.(({ original }) => original?.ID)
await Promise.all(ids.map((id) => remove({ id })))
},
},
],
},
],
}),
[view]
)
return hostActions
}
export default Actions

View File

@ -20,8 +20,9 @@ import { HostCard } from 'client/components/Cards'
const Row = memo(
({ original, ...props }) => {
const detail = hostApi.endpoints.getHost.useQueryState(original.ID, {
selectFromResult: ({ data }) => data,
const detail = hostApi.endpoints.getHosts.useQueryState(undefined, {
selectFromResult: ({ data }) =>
[data ?? []].flat().find((host) => +host?.ID === +original.ID),
})
return <HostCard host={detail ?? original} rootProps={props} />

View File

@ -34,6 +34,7 @@ const VmsTable = (props) => {
rootProps = {},
searchProps = {},
initialState = {},
host,
...rest
} = props ?? {}
@ -45,10 +46,16 @@ const VmsTable = (props) => {
)
const { view, getResourceView } = useViews()
const { data, refetch, isFetching } = useGetVmsQuery(undefined, {
selectFromResult: (result) => ({
...result,
data: result?.data?.filter(({ STATE }) => STATE !== '6') ?? [],
data:
result?.data
?.filter((vm) =>
host?.ID ? [host?.VMS?.ID ?? []].flat().includes(vm.ID) : true
)
?.filter(({ STATE }) => STATE !== '6') ?? [],
}),
})

View File

@ -0,0 +1,19 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
* not use this file except in compliance with the License. You may obtain *
* a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
export default [
{ Header: 'VM name', accessor: 'VM_NAME', sortType: 'string' },
{ Header: 'Remote ID', accessor: 'DEPLOY_ID' },
]

View File

@ -0,0 +1,51 @@
/* ------------------------------------------------------------------------- *
* 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 EnhancedTable from 'client/components/Tables/Enhanced'
import WildColumns from 'client/components/Tables/Wilds/columns'
import WildRow from 'client/components/Tables/Wilds/row'
const DEFAULT_DATA_CY = 'wilds'
/**
* @param {object} props - Props
* @returns {ReactElement} - Wilds table
*/
const WildsTable = (props) => {
const { rootProps = {}, searchProps = {}, wilds, ...rest } = props ?? {}
rootProps['data-cy'] ??= DEFAULT_DATA_CY
searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}`
const columns = useMemo(() => WildColumns, [])
return (
<EnhancedTable
columns={columns}
data={useMemo(() => wilds, [wilds])}
rootProps={rootProps}
searchProps={searchProps}
getRowId={(row) => String(row.DEPLOY_ID)}
RowComponent={WildRow}
{...rest}
/>
)
}
WildsTable.propTypes = { ...EnhancedTable.propTypes }
WildsTable.displayName = 'WildsTable'
export default WildsTable

View File

@ -0,0 +1,57 @@
/* ------------------------------------------------------------------------- *
* 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, ReactElement } from 'react'
import PropTypes from 'prop-types'
import { Typography } from '@mui/material'
import { rowStyles } from 'client/components/Tables/styles'
import { Row as RowType } from 'react-table'
/**
* @param {RowType} props - Props
* @param {object} props.original - Wild
* @param {boolean} props.isSelected - Wild selection
* @param {Function} props.handleClick - Action by click
* @returns {ReactElement} - Table row
*/
const Row = memo(({ original, ...props }) => {
const classes = rowStyles()
const { DEPLOY_ID, VM_NAME } = original
return (
<div {...props}>
<div className={classes.main}>
<div className={classes.title}>
<Typography component="span">{VM_NAME}</Typography>
</div>
<div className={classes.caption}>
<span>{`#${DEPLOY_ID}`}</span>
</div>
</div>
</div>
)
})
Row.propTypes = {
original: PropTypes.object,
isSelected: PropTypes.bool,
handleClick: PropTypes.func,
}
Row.displayName = 'WildsRow'
export default Row

View File

@ -0,0 +1,18 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
* not use this file except in compliance with the License. You may obtain *
* a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
export default [
{ Header: 'VM name', accessor: 'ZOMBIE_VM', sortType: 'string' },
]

View File

@ -0,0 +1,51 @@
/* ------------------------------------------------------------------------- *
* 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 EnhancedTable from 'client/components/Tables/Enhanced'
import ZombieColumns from 'client/components/Tables/Zombies/columns'
import ZombieRow from 'client/components/Tables/Zombies/row'
const DEFAULT_DATA_CY = 'zombie'
/**
* @param {object} props - Props
* @returns {ReactElement} - Zombies table
*/
const ZombiesTable = (props) => {
const { rootProps = {}, searchProps = {}, zombies, ...rest } = props ?? {}
rootProps['data-cy'] ??= DEFAULT_DATA_CY
searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}`
const columns = useMemo(() => ZombieColumns, [])
return (
<EnhancedTable
columns={columns}
data={useMemo(() => zombies, [zombies])}
rootProps={rootProps}
searchProps={searchProps}
getRowId={(row) => String(row.ZOMBIE_VM)}
RowComponent={ZombieRow}
{...rest}
/>
)
}
ZombiesTable.propTypes = { ...EnhancedTable.propTypes }
ZombiesTable.displayName = 'ZombiesTable'
export default ZombiesTable

View File

@ -0,0 +1,54 @@
/* ------------------------------------------------------------------------- *
* 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, ReactElement } from 'react'
import PropTypes from 'prop-types'
import { Typography } from '@mui/material'
import { rowStyles } from 'client/components/Tables/styles'
import { Row as RowType } from 'react-table'
/**
* @param {RowType} props - Props
* @param {object} props.original - Zombie
* @param {boolean} props.isSelected - Zombie selection
* @param {Function} props.handleClick - Action by click
* @returns {ReactElement} - Table row
*/
const Row = memo(({ original, ...props }) => {
const classes = rowStyles()
const { ZOMBIE_VM } = original
return (
<div {...props}>
<div className={classes.main}>
<div className={classes.title}>
<Typography component="span">{ZOMBIE_VM}</Typography>
</div>
</div>
</div>
)
})
Row.propTypes = {
original: PropTypes.object,
isSelected: PropTypes.bool,
handleClick: PropTypes.func,
}
Row.displayName = 'ZombiesRow'
export default Row

View File

@ -0,0 +1,42 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
* not use this file except in compliance with the License. You may obtain *
* a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import { ReactElement } from 'react'
import { InfoEmpty } from 'iconoir-react'
import { Translate } from 'client/components/HOC/Translate'
import { useStyles } from 'client/components/Tabs/EmptyTab/styles'
import { T } from 'client/constants'
/**
* Renders default empty tab.
*
* @returns {ReactElement} Empty tab
*/
const EmptyTab = () => {
const classes = useStyles()
return (
<span className={classes.noDataMessage}>
<InfoEmpty />
<Translate word={T.NoDataAvailable} />
</span>
)
}
EmptyTab.displayName = 'EmptyTab'
export default EmptyTab

View File

@ -0,0 +1,27 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
* not use this file except in compliance with the License. You may obtain *
* a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import makeStyles from '@mui/styles/makeStyles'
export const useStyles = makeStyles(({ typography, palette }) => ({
noDataMessage: {
...typography.h6,
color: palette.text.hint,
display: 'inline-flex',
alignItems: 'center',
gap: '0.8em',
padding: '1em',
},
}))

View File

@ -0,0 +1,52 @@
/* ------------------------------------------------------------------------- *
* 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, Grid, Paper, Typography } from '@mui/material'
import { Translate } from 'client/components/HOC'
import { T, CPU_STATUS } from 'client/constants'
/**
* @param {object} props - Props
* @param {string} props.core - Numa core
* @param {object} props.cpus - List of numa cores
* @returns {ReactElement} Information tab
*/
const NumaCoreCPU = ({ core, cpus }) => (
<Grid item xs={6}>
<Paper sx={{ pt: '0.3rem', pb: '0.1rem' }}>
<Box sx={{ flexGrow: 1 }}>
<Typography gutterBottom variant="body2" component="div" align="center">
<Translate word={T.NumaNodeCPUItem} values={core} />
</Typography>
<Typography gutterBottom variant="body2" component="div" align="center">
{CPU_STATUS[String(cpus[core])]}
</Typography>
</Box>
</Paper>
</Grid>
)
NumaCoreCPU.propTypes = {
core: PropTypes.string.isRequired,
cpus: PropTypes.object.isRequired,
}
NumaCoreCPU.displayName = 'NumaCoreCPU'
export default NumaCoreCPU

View File

@ -0,0 +1,57 @@
/* ------------------------------------------------------------------------- *
* 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 { Box, Grid, Typography } from '@mui/material'
import PropTypes from 'prop-types'
import { Translate } from 'client/components/HOC'
import { T } from 'client/constants'
import NumaCoreCPU from 'client/components/Tabs/Host/Numa/CPU'
/**
* @param {object} props - Props
* @param {object} props.core - Numa core
* @returns {ReactElement} Information tab
*/
const NumaCore = ({ core }) => {
const cpus = Object.fromEntries(
core.CPUS.split(',').map((item) => item.split(':'))
)
return (
<Grid item xs={12} sm={6} md={3} display="flex" justifyContent="center">
<Box width="200px">
<Typography gutterBottom variant="body1" component="div" align="center">
<Translate word={T.NumaCore} values={core.ID} />
</Typography>
<Grid container spacing={1}>
{Object.keys(cpus).map((cpu, index) => (
<NumaCoreCPU key={index} core={cpu} cpus={cpus} />
))}
</Grid>
</Box>
</Grid>
)
}
NumaCore.propTypes = {
core: PropTypes.object.isRequired,
}
NumaCore.displayName = 'Core'
export default NumaCore

View File

@ -0,0 +1,76 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
* not use this file except in compliance with the License. You may obtain *
* a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import { ReactElement } from 'react'
import PropTypes from 'prop-types'
import { Box, List, ListItem, Paper, Typography } from '@mui/material'
import { Translate } from 'client/components/HOC'
import { useStyles } from 'client/components/Tabs/Host/Numa/Hugepage/styles'
import { T } from 'client/constants'
/**
* @param {object} props - Props
* @param {string[]} props.hugepage - Numa hugepage tab info
* @returns {ReactElement} Information view
*/
const NumaHugepage = ({ hugepage }) => {
const classes = useStyles()
return (
<Box>
<Typography gutterBottom variant="subtitle1" component="h3">
<Translate word={T.HugepageNode} />
</Typography>
<Paper variant="outlined">
<List className={classes.list}>
<ListItem className={classes.title}>
<Typography noWrap>
<Translate word={T.HugepageNodeSize} />
</Typography>
<Typography noWrap>
<Translate word={T.HugepageNodeFree} />
</Typography>
<Typography noWrap>
<Translate word={T.HugepageNodePages} />
</Typography>
<Typography noWrap>
<Translate word={T.HugepageNodeUsage} />
</Typography>
</ListItem>
{hugepage.length > 0 &&
hugepage.map(({ FREE, PAGES, SIZE, USAGE }, index) => (
<ListItem key={index} className={classes.item} dense>
<Typography noWrap>{SIZE}</Typography>
<Typography noWrap>{FREE}</Typography>
<Typography noWrap>{PAGES}</Typography>
<Typography noWrap>{USAGE}</Typography>
</ListItem>
))}
</List>
</Paper>
</Box>
)
}
NumaHugepage.propTypes = {
hugepage: PropTypes.array,
}
NumaHugepage.displayName = 'NumaHugepage'
export default NumaHugepage

View File

@ -0,0 +1,29 @@
/* ------------------------------------------------------------------------- *
* 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 makeStyles from '@mui/styles/makeStyles'
export const useStyles = makeStyles((theme) => ({
list: {
'& > * > *': {
width: '25%',
},
},
title: {
fontWeight: theme.typography.fontWeightBold,
textTransform: 'uppercase',
borderBottom: `1px solid ${theme.palette.divider}`,
},
}))

View File

@ -0,0 +1,57 @@
/* ------------------------------------------------------------------------- *
* 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, Paper, Typography } from '@mui/material'
import LinearProgressWithLabel from 'client/components/Status/LinearProgressWithLabel'
import { T } from 'client/constants'
import { Tr, Translate } from 'client/components/HOC'
import { getNumaMemory } from 'client/models/Host'
/**
* @param {object} props - Props
* @param {string} props.node - Numa node
* @returns {ReactElement} Information tab
*/
const NumaMemory = ({ node }) => {
const { percentMemUsed, percentMemLabel } = getNumaMemory(node)
return (
<Box>
<Typography gutterBottom variant="subtitle1" component="h3">
<Translate word={T.NumaNodeMemory} />
</Typography>
<Paper variant="outlined" sx={{ p: '1.25rem' }}>
<LinearProgressWithLabel
value={percentMemUsed}
label={percentMemLabel}
title={`${Tr(T.AllocatedCpu)}`}
/>
</Paper>
</Box>
)
}
NumaMemory.propTypes = {
node: PropTypes.object.isRequired,
}
NumaMemory.displayName = 'NumaMemory'
export default NumaMemory

View File

@ -0,0 +1,114 @@
/* ------------------------------------------------------------------------- *
* 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 { FormProvider, useForm } from 'react-hook-form'
import PropTypes from 'prop-types'
import { Box } from '@mui/material'
import { yupResolver } from '@hookform/resolvers/yup'
import {
FORM_FIELDS_ISOLATION,
FORM_SCHEMA_ISOLATION,
} from 'client/components/Tabs/Host/Numa/UpdateIsolatedCPUS/schema'
import SubmitButton from 'client/components/FormControl/SubmitButton'
import FormWithSchema from 'client/components/Forms/FormWithSchema'
import { useUpdateHostMutation } from 'client/features/OneApi/host'
import { useGeneralApi } from 'client/features/General/hooks'
import { jsonToXml } from 'client/models/Helper'
import { cloneObject } from 'client/utils'
import { T, Host } from 'client/constants'
import { Tr } from 'client/components/HOC'
/**
* @param {object} props - Props
* @param {Host} props.host - Host resource
* @returns {ReactElement} Form for updating isolated CPU
*/
const UpdateIsolatedCPUSForm = ({ host }) => {
const { TEMPLATE } = host
const { enqueueError } = useGeneralApi()
const [updateUserTemplate] = useUpdateHostMutation()
const { handleSubmit, reset, formState, ...methods } = useForm({
reValidateMode: 'onSubmit',
defaultValues: {
ISOLATION: TEMPLATE.ISOLCPUS,
},
resolver: yupResolver(FORM_SCHEMA_ISOLATION),
})
const onSubmit = async (formData) => {
try {
const newTemplate = cloneObject(TEMPLATE)
newTemplate.ISOLCPUS = formData.ISOLATION
const xml = jsonToXml(newTemplate)
await updateUserTemplate({ id: host.ID, template: xml, replace: 0 })
// Reset either the entire form state or part of the form state
reset(formData)
} catch {
enqueueError(T.SomethingWrong)
}
}
return (
<Box component="form" onSubmit={handleSubmit(onSubmit)}>
<FormProvider {...methods}>
<Box display="flex">
<Box sx={{ flexGrow: 1 }}>
<FormWithSchema
cy="numa-isolate-cpus"
fields={FORM_FIELDS_ISOLATION}
legend={T.ISOLCPUS}
legendTooltip={T.TemplateToIsolateCpus}
/>
</Box>
<Box
display="flex"
alignItems="end"
justifyContent="center"
minWidth="7rem"
paddingBottom="1.25rem"
>
<SubmitButton
color="secondary"
data-cy="isolate-cpus-submit-button"
label={Tr(T.Update)}
onClick={handleSubmit}
disabled={!formState.isDirty}
isSubmitting={formState.isSubmitting}
/>
</Box>
</Box>
</FormProvider>
</Box>
)
}
UpdateIsolatedCPUSForm.propTypes = {
host: PropTypes.object.isRequired,
}
UpdateIsolatedCPUSForm.displayName = 'UpdateIsolatedCPUSForm'
export default UpdateIsolatedCPUSForm

View File

@ -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 { object, string, ObjectSchema } from 'yup'
import { Field, getValidationFromFields } from 'client/utils'
import { INPUT_TYPES } from 'client/constants'
/** @type {Field} Isolation field */
const ISOLATION = {
name: 'ISOLATION',
type: INPUT_TYPES.TEXT,
validation: string().default(() => ''),
grid: { md: 12 },
}
/** @type {Field[]} List of fields */
export const FORM_FIELDS_ISOLATION = [ISOLATION]
/** @type {ObjectSchema} Schema */
export const FORM_SCHEMA_ISOLATION = object(
getValidationFromFields(FORM_FIELDS_ISOLATION)
)

View File

@ -0,0 +1,85 @@
/* ------------------------------------------------------------------------- *
* 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, useEffect } from 'react'
import { FormProvider, useForm } from 'react-hook-form'
import PropTypes from 'prop-types'
import { yupResolver } from '@hookform/resolvers/yup'
import {
FORM_FIELDS_PIN_POLICY,
FORM_SCHEMA_PIN_POLICY,
} from 'client/components/Tabs/Host/Numa/UpdatePinPolicy/schema'
import FormWithSchema from 'client/components/Forms/FormWithSchema'
import { useGeneralApi } from 'client/features/General'
import { useUpdateHostMutation } from 'client/features/OneApi/host'
import { T, Host } from 'client/constants'
/**
* @param {object} props - Props
* @param {Host} props.host - Host resource
* @returns {ReactElement} Form for updating pin policy
*/
const UpdatePinPolicyForm = ({ host }) => {
const { TEMPLATE } = host
const { enqueueError } = useGeneralApi()
const [updateUserTemplate] = useUpdateHostMutation()
const { watch, ...methods } = useForm({
reValidateMode: 'onSubmit',
defaultValues: {
PIN_POLICY: TEMPLATE.PIN_POLICY,
},
resolver: yupResolver(FORM_SCHEMA_PIN_POLICY),
})
useEffect(() => {
watch((data) => {
try {
updateUserTemplate({
id: host.ID,
template: `PIN_POLICY = ${data.PIN_POLICY}`,
replace: 1,
})
} catch {
enqueueError(T.SomethingWrong)
}
})
}, [watch])
return (
<FormProvider {...methods}>
<FormWithSchema
cy="numa-pinned-policy"
fields={FORM_FIELDS_PIN_POLICY}
legend={T.PinPolicy}
/>
</FormProvider>
)
}
UpdatePinPolicyForm.propTypes = {
host: PropTypes.object.isRequired,
}
UpdatePinPolicyForm.displayName = 'UpdateIsolatedCPUSForm'
export default UpdatePinPolicyForm

View File

@ -0,0 +1,43 @@
/* ------------------------------------------------------------------------- *
* 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 { object, string, ObjectSchema } from 'yup'
import { Field, getValidationFromFields } from 'client/utils'
import { PIN_POLICY, INPUT_TYPES } from 'client/constants'
/** @type {Field} Pin Policy field */
const PIN_POLICY_FIELD = {
name: 'PIN_POLICY',
type: INPUT_TYPES.SELECT,
values: [
{ text: 'None', value: PIN_POLICY.NONE },
{ text: 'Pinned', value: PIN_POLICY.PINNED },
],
validation: string()
.trim()
.required()
.default(() => PIN_POLICY.NONE),
grid: { md: 12 },
}
/** @type {Field[]} List of fields */
export const FORM_FIELDS_PIN_POLICY = [PIN_POLICY_FIELD]
/** @type {ObjectSchema} Schema */
export const FORM_SCHEMA_PIN_POLICY = object(
getValidationFromFields(FORM_FIELDS_PIN_POLICY)
)

View File

@ -0,0 +1,61 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
* not use this file except in compliance with the License. You may obtain *
* a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import { ReactElement } from 'react'
import PropTypes from 'prop-types'
import EmptyTab from 'client/components/Tabs/EmptyTab'
import Information from 'client/components/Tabs/Host/Numa/information'
import { getHostNuma } from 'client/models/Host'
import { useGetHostQuery } from 'client/features/OneApi/host'
import UpdatePinPolicyForm from 'client/components/Tabs/Host/Numa/UpdatePinPolicy'
import UpdateIsolatedCPUSForm from 'client/components/Tabs/Host/Numa/UpdateIsolatedCPUS'
/**
* Renders mainly information tab.
*
* @param {object} props - Props
* @param {string} props.id - Host id
* @returns {ReactElement} Information tab
*/
const NumaInfoTab = ({ id }) => {
const { data: host = {} } = useGetHostQuery(id)
const numa = getHostNuma(host)
return (
<>
<UpdatePinPolicyForm host={host} />
<UpdateIsolatedCPUSForm host={host} />
{numa?.length > 0 ? (
numa.map((node) => (
<Information key={node.NODE_ID} node={node} host={host} />
))
) : (
<EmptyTab />
)}
</>
)
}
NumaInfoTab.propTypes = {
tabProps: PropTypes.object,
id: PropTypes.string,
}
NumaInfoTab.displayName = 'NumaInfoTab'
export default NumaInfoTab

View File

@ -0,0 +1,75 @@
/* ------------------------------------------------------------------------- *
* 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 { Divider, Grid, Stack, Typography } from '@mui/material'
import { Translate } from 'client/components/HOC'
import NumaCore from 'client/components/Tabs/Host/Numa/Core'
import NumaMemory from 'client/components/Tabs/Host/Numa/Memory'
import NumaHugepage from 'client/components/Tabs/Host/Numa/Hugepage'
import { T } from 'client/constants'
/**
* @param {object} props - Props
* @param {object} props.node - Numa Node
* @returns {ReactElement} Information tab
*/
const InformationPanel = ({ node = {} }) => {
const { CORE, HUGEPAGE } = node
return (
<>
<Stack
gap="1em"
gridTemplateColumns="repeat(auto-fit, minmax(480px, 1fr))"
padding="0.8em"
>
<Typography gutterBottom variant="h2" component="h2">
<Translate word={T.NumaNodeItem} values={node.NODE_ID} />
</Typography>
<Typography gutterBottom variant="subtitle1" component="h3">
<Translate word={T.NumaNodeTitle} />
</Typography>
</Stack>
<Divider variant="middle" />
<Grid container spacing={2} sx={{ padding: '10px 0 20px' }}>
{CORE.length &&
CORE.map((core) => <NumaCore key={core.ID} core={core} />)}
</Grid>
<Divider variant="middle" />
<Stack
display="grid"
gap="1em"
gridTemplateColumns="repeat(auto-fit, minmax(480px, 1fr))"
padding="0.8em"
>
<NumaHugepage hugepage={[HUGEPAGE].flat()} />
<NumaMemory node={node} />
</Stack>
</>
)
}
InformationPanel.propTypes = {
node: PropTypes.object.isRequired,
}
InformationPanel.displayName = 'InformationPanel'
export default InformationPanel

View File

@ -0,0 +1,62 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
* not use this file except in compliance with the License. You may obtain *
* a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import { ReactElement } from 'react'
import PropTypes from 'prop-types'
import { Container, Stack } from '@mui/material'
import { useHistory, generatePath } from 'react-router-dom'
import { PATH } from 'client/apps/sunstone/routesOne'
import { useGetHostQuery } from 'client/features/OneApi/host'
import { VmsTable } from 'client/components/Tables'
/**
* Renders mainly information tab.
*
* @param {object} props - Props
* @param {string} props.id - Host id
* @returns {ReactElement} Information tab
*/
const VmsInfoTab = ({ id }) => {
const { data: host = {} } = useGetHostQuery(id)
const path = PATH.INSTANCE.VMS.DETAIL
const history = useHistory()
const handleRowClick = (rowId) => {
history.push(generatePath(path, { id: String(rowId) }))
}
return (
<Stack height={1} py={2} overflow="auto" component={Container}>
<VmsTable
disableRowSelect
disableGlobalSort
host={host}
onRowClick={(row) => handleRowClick(row.ID)}
/>
</Stack>
)
}
VmsInfoTab.propTypes = {
tabProps: PropTypes.object,
id: PropTypes.string,
}
VmsInfoTab.displayName = 'WildsInfoTab'
export default VmsInfoTab

View File

@ -0,0 +1,55 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
* not use this file except in compliance with the License. You may obtain *
* a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import { ReactElement } from 'react'
import PropTypes from 'prop-types'
import { Stack } from '@mui/material'
import { getHostWilds } from 'client/models/Host'
import { useGetHostQuery } from 'client/features/OneApi/host'
import WildsTable from 'client/components/Tables/Wilds'
/**
* Renders mainly information tab.
*
* @param {object} props - Props
* @param {string} props.id - Host id
* @returns {ReactElement} - Wild information tab
*/
const WildsInfoTab = ({ id }) => {
const { data: host = {} } = useGetHostQuery(id)
const wilds = getHostWilds(host)
return (
<Stack
display="grid"
gap="1em"
gridTemplateColumns="repeat(auto-fit, minmax(480px, 1fr))"
padding="0.8em"
>
<WildsTable wilds={wilds} />
</Stack>
)
}
WildsInfoTab.propTypes = {
tabProps: PropTypes.object,
id: PropTypes.string,
}
WildsInfoTab.displayName = 'WildsInfoTab'
export default WildsInfoTab

View File

@ -0,0 +1,55 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
* not use this file except in compliance with the License. You may obtain *
* a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import { ReactElement } from 'react'
import PropTypes from 'prop-types'
import { Stack } from '@mui/material'
import { useGetHostQuery } from 'client/features/OneApi/host'
import { getHostZombies } from 'client/models/Host'
import ZombiesTable from 'client/components/Tables/Zombies'
/**
* Renders mainly information tab.
*
* @param {object} props - Props
* @param {string} props.id - Host id
* @returns {ReactElement} - Zombies information tab
*/
const ZombiesInfoTab = ({ id }) => {
const { data: host = {} } = useGetHostQuery(id)
const zombies = getHostZombies(host)
return (
<Stack
display="grid"
gap="1em"
gridTemplateColumns="repeat(auto-fit, minmax(480px, 1fr))"
padding="0.8em"
>
<ZombiesTable disableRowSelect disableGlobalSort zombies={zombies} />
</Stack>
)
}
ZombiesInfoTab.propTypes = {
tabProps: PropTypes.object,
id: PropTypes.string,
}
ZombiesInfoTab.displayName = 'ZombiesInfoTab'
export default ZombiesInfoTab

View File

@ -17,21 +17,29 @@ import { memo, useMemo } from 'react'
import PropTypes from 'prop-types'
import { Alert, LinearProgress } from '@mui/material'
import { useAuth } from 'client/features/Auth'
import { useViews } from 'client/features/Auth/hooks'
import { useGetHostQuery } from 'client/features/OneApi/host'
import { getAvailableInfoTabs } from 'client/models/Helper'
import { RESOURCE_NAMES } from 'client/constants'
import Tabs from 'client/components/Tabs'
import Info from 'client/components/Tabs/Host/Info'
import Wilds from 'client/components/Tabs/Host/Wilds'
import Numa from 'client/components/Tabs/Host/Numa'
import Zombies from 'client/components/Tabs/Host/Zombies'
import Vms from 'client/components/Tabs/Host/Vms'
const getTabComponent = (tabName) =>
({
info: Info,
vms: Vms,
wild: Wilds,
numa: Numa,
zombies: Zombies,
}[tabName])
const HostTabs = memo(({ id }) => {
const { view, getResourceView } = useAuth()
const { view, getResourceView } = useViews()
const { isLoading, isError, error } = useGetHostQuery(id)
const tabsAvailable = useMemo(() => {

View File

@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import * as STATES from 'client/constants/states'
import * as ACTIONS from 'client/constants/actions'
import COLOR from 'client/constants/color'
import * as STATES from 'client/constants/states'
import * as T from 'client/constants/translates'
/**
* @typedef {object} PciDevice - PCI device
* @property {string} ADDRESS - Address, bus, slot and function
@ -183,3 +183,21 @@ export const HOST_ACTIONS = {
OFFLINE: 'offline',
DELETE: 'delete',
}
/** @enum {string} Numa Node CPU Status */
export const CPU_STATUS = {
'-1': 'FREE',
'-2': 'ISOLATED',
}
/** @enum {string} Pin Policy */
export const PIN_POLICY = {
NONE: 'NONE',
PINNED: 'PINNED',
}
/** @type {object} Custom Hypervisor */
export const CUSTOM_HOST_HYPERVISOR = {
NAME: 'Custom',
SUNSTONE_NAME: T.CustomHypervisor,
}

View File

@ -64,10 +64,12 @@ module.exports = {
Deploy: 'Deploy',
Detach: 'Detach',
DetachSomething: 'Detach: %s',
Disable: 'Disable',
Dismiss: 'Dismiss',
Done: 'Done',
Edit: 'Edit',
EditSomething: 'Edit: %s',
Enable: 'Enable',
Failure: 'Failure',
Finish: 'Finish',
Hold: 'Hold',
@ -77,6 +79,7 @@ module.exports = {
Lock: 'Lock',
Migrate: 'Migrate',
MigrateLive: 'Migrate live',
Offline: 'Offline',
Pin: 'Pin',
Poweroff: 'Poweroff',
PoweroffHard: 'Poweroff hard',
@ -105,12 +108,14 @@ module.exports = {
SaveAsTemplate: 'Save as Template',
Search: 'Search',
Select: 'Select',
SelectCluster: 'Select Cluster',
SelectDatastore: 'Select a Datastore to store the resource',
SelectDockerHubTag: 'Select DockerHub image tag (default latest)',
SelectGroup: 'Select a group',
SelectHost: 'Select a host',
SelectMarketplace: 'Select Marketplace',
SelectNetwork: 'Select a network',
SelectNewCluster: 'Select a new Cluster',
SelectRequest: 'Select request',
SelectTheNewDatastore: 'Select the new datastore',
SelectTheNewGroup: 'Select the new group',
@ -324,6 +329,7 @@ module.exports = {
Provisions: 'Provisions',
/* tabs */
Drivers: 'Drivers',
General: 'General',
Information: 'Information',
Placement: 'Placement',
@ -455,11 +461,15 @@ module.exports = {
/* VM Template schema */
/* VM Template schema - general */
Logo: 'Logo',
Hypervisor: 'Hypervisor',
TemplateName: 'Template name',
MakeNewImagePersistent: 'Make the new images persistent',
CustomHypervisor: 'Custom',
CustomVariables: 'Custom Variables',
Hypervisor: 'Hypervisor',
Logo: 'Logo',
MakeNewImagePersistent: 'Make the new images persistent',
TemplateName: 'Template name',
Virtualization: 'Virtualization',
CustomInformation: 'Custom information',
CustomVirtualization: 'Custom virtualization',
VmTemplateNameHelper: `
Defaults to 'template name-<vmid>' when empty.
When creating several VMs, the wildcard %%idx will be
@ -656,11 +666,21 @@ module.exports = {
NumaTopologyConcept:
'These settings will help you to fine tune the performance of VMs',
PinPolicy: 'Pin Policy',
NumaNodeItem: 'Node #%s',
NumaNodeTitle: 'Cores & CPUS',
PinPolicyConcept: 'Virtual CPU pinning preference: %s',
NumaSocketsConcept: 'Number of sockets or NUMA nodes',
NumaCoresConcept: 'Number of cores per node',
NumaNodeMemory: 'Memory',
NumaCore: 'Core %s',
NumaNodeCPUItem: 'CPU #%s',
Threads: 'Threads',
ThreadsConcept: 'Number of threads per core',
HugepageNode: 'Hugepage',
HugepageNodeFree: 'Free',
HugepageNodePages: 'Pages',
HugepageNodeSize: 'Size',
HugepageNodeUsage: 'Usage',
HugepagesSize: 'Hugepages size',
HugepagesSizeConcept:
'Size of hugepages (MB). If not defined no hugepages will be used',
@ -740,6 +760,10 @@ module.exports = {
RealMemory: 'Real Memory',
RealCpu: 'Real CPU',
Overcommitment: 'Overcommitment',
/* Host schema - template */
ISOLCPUS: 'Isolated CPUS',
TemplateToIsolateCpus:
'Comma separated list of CPU IDs that will be isolated from the NUMA scheduler',
/* Cluster schema */
/* Cluster schema - capacity */

View File

@ -0,0 +1,69 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
* not use this file except in compliance with the License. You may obtain *
* a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import { ReactElement } from 'react'
import { useHistory, useLocation } from 'react-router'
import { Container } from '@mui/material'
import { useGeneralApi } from 'client/features/General'
import {
useUpdateHostMutation,
useAllocateHostMutation,
} from 'client/features/OneApi/host'
import {
DefaultFormStepper,
SkeletonStepsForm,
} from 'client/components/FormStepper'
import { CreateForm } from 'client/components/Forms/Host'
import { PATH } from 'client/apps/sunstone/routesOne'
/**
* Displays the creation or modification form to a Host.
*
* @returns {ReactElement} Host form
*/
function CreateHost() {
const history = useHistory()
const { state: { ID: id, NAME } = {} } = useLocation()
const { enqueueSuccess } = useGeneralApi()
const [update] = useUpdateHostMutation()
const [allocate] = useAllocateHostMutation()
const onSubmit = async (props) => {
try {
if (!id) {
const newHostId = await allocate(props).unwrap()
history.push(PATH.INFRASTRUCTURE.HOSTS.LIST)
enqueueSuccess(`Host created - #${newHostId}`)
} else {
await update({ id, ...props })
history.push(PATH.INFRASTRUCTURE.HOSTS.LIST)
enqueueSuccess(`Host updated - #${id} ${NAME}`)
}
} catch {}
}
return (
<Container sx={{ display: 'flex', flexFlow: 'column' }} disableGutters>
<CreateForm onSubmit={onSubmit} fallback={<SkeletonStepsForm />}>
{(config) => <DefaultFormStepper {...config} />}
</CreateForm>
</Container>
)
}
export default CreateHost

View File

@ -19,16 +19,21 @@ import { Container, Stack, Chip } from '@mui/material'
import { HostsTable } from 'client/components/Tables'
import HostTabs from 'client/components/Tabs/Host'
import HostActions from 'client/components/Tables/Hosts/actions'
import SplitPane from 'client/components/SplitPane'
import MultipleTags from 'client/components/MultipleTags'
function Hosts() {
const [selectedRows, onSelectedRowsChange] = useState(() => [])
const actions = HostActions()
return (
<Stack height={1} py={2} overflow="auto" component={Container}>
<SplitPane>
<HostsTable onSelectedRowsChange={onSelectedRowsChange} />
<HostsTable
onSelectedRowsChange={onSelectedRowsChange}
globalActions={actions}
/>
{selectedRows?.length > 0 && (
<Stack overflow="auto">

View File

@ -21,8 +21,8 @@ import {
} from 'client/features/OneApi'
import { Cluster } from 'client/constants'
const { CLUSTER } = ONE_RESOURCES
const { CLUSTER_POOL } = ONE_RESOURCES_POOL
const { CLUSTER, HOST } = ONE_RESOURCES
const { CLUSTER_POOL, HOST_POOL } = ONE_RESOURCES_POOL
const clusterApi = oneApi.injectEndpoints({
endpoints: (builder) => ({
@ -157,7 +157,12 @@ const clusterApi = oneApi.injectEndpoints({
return { params, command }
},
invalidatesTags: (_, __, { id }) => [{ type: CLUSTER, id }, CLUSTER_POOL],
invalidatesTags: (_, __, { id, host }) => [
{ type: CLUSTER, id },
{ type: HOST, id: host },
CLUSTER_POOL,
HOST_POOL,
],
}),
removeHostFromCluster: builder.mutation({
/**

View File

@ -16,22 +16,26 @@
import { prettyBytes } from 'client/utils'
import {
DEFAULT_CPU_MODELS,
CUSTOM_HOST_HYPERVISOR,
Host,
HOST_STATES,
HYPERVISORS,
NumaNode,
PciDevice,
StateInfo,
} from 'client/constants'
import { useGetOneConfigQuery } from 'client/features/OneApi/system'
/**
* Returns information about the host state.
*
* @param {object} host - Host
* @param {Host} host - Host
* @returns {StateInfo} Host state object
*/
export const getState = (host) => HOST_STATES[+host?.STATE ?? 0]
/**
* @param {object} host - Host
* @param {Host} host - Host
* @returns {Array} List of datastores from resource
*/
export const getDatastores = (host) =>
@ -40,7 +44,7 @@ export const getDatastores = (host) =>
/**
* Returns the allocate information.
*
* @param {object} host - Host
* @param {Host} host - Host
* @returns {{
* percentCpuUsed: number,
* percentCpuLabel: string,
@ -75,7 +79,7 @@ export const getAllocatedInfo = (host) => {
/**
* Returns list of hugepage sizes from the host numa nodes.
*
* @param {object} host - Host
* @param {Host} host - Host
* @returns {Array} List of hugepages sizes from resource
*/
export const getHugepageSizes = (host) => {
@ -90,7 +94,7 @@ export const getHugepageSizes = (host) => {
/**
* Returns list of PCI devices from the host.
*
* @param {object} host - Host
* @param {Host} host - Host
* @returns {PciDevice[]} List of PCI devices from resource
*/
export const getPciDevices = (host) =>
@ -99,7 +103,7 @@ export const getPciDevices = (host) =>
/**
* Returns list of KVM CPU Models available from the host pool.
*
* @param {object[]} hosts - Hosts
* @param {Host[]} hosts - Hosts
* @returns {Array} List of KVM CPU Models from the pool
*/
export const getKvmCpuModels = (hosts = []) =>
@ -111,7 +115,7 @@ export const getKvmCpuModels = (hosts = []) =>
/**
* Returns list of KVM Machines available from the host pool.
*
* @param {object[]} hosts - Hosts
* @param {Host[]} hosts - Hosts
* @returns {Array} List of KVM Machines from the pool
*/
export const getKvmMachines = (hosts = []) => {
@ -122,3 +126,81 @@ export const getKvmMachines = (hosts = []) => {
return [DEFAULT_CPU_MODELS, ...machineTypes]
}
/**
* Returns list of Zombies available from the host.
*
* @param {Host} host - Host
* @returns {object[]} - List of zombies from host
*/
export const getHostZombies = (host = {}) =>
[
host?.TEMPLATE?.ZOMBIES?.split(', ')?.map((zombie) => ({
ZOMBIE_VM: zombie,
})) ?? [],
].flat()
/**
* Returns list of Wilds available from the host.
*
* @param {Host} host - Host
* @returns {object[]} - List of wilds from host
*/
export const getHostWilds = (host = {}) =>
[host?.TEMPLATE?.VM ?? []]
.flat()
.filter((vm) => vm.VCENTER_TEMPLATE === 'YES')
/**
* Returns list of Numa available from the host.
*
* @param {Host} host - Host
* @returns {NumaNode[]} - List of Numa nodes from host
*/
export const getHostNuma = (host = {}) =>
[host?.HOST_SHARE?.NUMA_NODES?.NODE ?? []].flat()
/**
* Returns the Numa Node memory information.
*
* @param {NumaNode} numa - Host Numa
* @returns {{
* percentMemUsed: number,
* percentMemLabel: string
* }} Numa Node memory information
*/
export const getNumaMemory = (numa) => {
const { TOTAL, USED } = numa?.MEMORY ?? {}
const percentMemUsed = (+USED * 100) / +TOTAL || 0
const usedMemBytes = prettyBytes(Math.abs(+USED))
const totalMemBytes = prettyBytes(+TOTAL)
const percentMemLabel = `${usedMemBytes} / ${totalMemBytes}
(${Math.round(isFinite(percentMemUsed) ? percentMemUsed : '--')}%)`
return {
percentMemUsed,
percentMemLabel,
}
}
/**
* Returns list of Hypervisors available to the host.
*
* @param {object} [options] - Options to conversion
* @param {boolean} [options.includeCustom] - If `true`, add an Custom hypervisor
* @returns {object[]} - List of hypervisors
*/
export const getHostHypervisors = (options = {}) => {
const { includeCustom = false } = options
const { data } = useGetOneConfigQuery()
const { VM_MAD } = data
return [VM_MAD ?? [], includeCustom ? CUSTOM_HOST_HYPERVISOR : []]
.flat()
.filter((hypervisor) => hypervisor.NAME !== HYPERVISORS.vcenter)
.map((hypervisor) => ({
displayName: hypervisor.SUNSTONE_NAME,
driverName: hypervisor.NAME,
}))
}

View File

@ -208,6 +208,7 @@ export const OPTION_SORTERS = {
numeric: true,
ignorePunctuation: true,
}),
unsort: () => null,
}
const SEMICOLON_CHAR = ';'