mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-21 14:50:08 +03:00
parent
2c3f629236
commit
b619854c3e
@ -55,6 +55,7 @@ actions:
|
||||
ssh: true
|
||||
rdp: true
|
||||
edit_labels: true
|
||||
backup: true
|
||||
|
||||
# Filters - List of criteria to filter the resources
|
||||
|
||||
@ -71,7 +72,6 @@ filters:
|
||||
# Info Tabs - Which info tabs are used to show extended information
|
||||
|
||||
info-tabs:
|
||||
|
||||
info:
|
||||
enabled: true
|
||||
information_panel:
|
||||
|
18
src/fireedge/package-lock.json
generated
18
src/fireedge/package-lock.json
generated
@ -57,7 +57,7 @@
|
||||
"http": "0.0.1-security",
|
||||
"http-proxy-middleware": "1.0.5",
|
||||
"https": "1.0.0",
|
||||
"iconoir-react": "4.7.1",
|
||||
"iconoir-react": "5.3.2",
|
||||
"immutable": "4.0.0-rc.12",
|
||||
"intersection-observer": "0.11.0",
|
||||
"jsdom": "19.0.0",
|
||||
@ -7858,11 +7858,11 @@
|
||||
"integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ=="
|
||||
},
|
||||
"node_modules/iconoir-react": {
|
||||
"version": "4.7.1",
|
||||
"resolved": "https://registry.npmjs.org/iconoir-react/-/iconoir-react-4.7.1.tgz",
|
||||
"integrity": "sha512-4QJr7qL4zU5Z7/wBSS0hUy/ThMRVqIuxInz79lPc1yIIdwsegqAKvPVfCeg1bHoFhgi5LbWB7pyx8/t/O9KHRQ==",
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/iconoir-react/-/iconoir-react-5.3.2.tgz",
|
||||
"integrity": "sha512-5p5FQAkX6ZAuk1UE/4RmjI9BgI5FSqTbgyUT/m74bfHxl57YAuU7sRdnvrLe97I9IbbiC2uKnGl90xVcBD07rQ==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.6 || ^17"
|
||||
"react": "^16.8.6 || ^17 || ^18"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
@ -9132,6 +9132,7 @@
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz",
|
||||
"integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==",
|
||||
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.1",
|
||||
"tiny-warning": "^1.0.3"
|
||||
@ -12723,6 +12724,7 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",
|
||||
"integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==",
|
||||
"deprecated": "Use your platform's native performance.now() and performance.timeOrigin.",
|
||||
"dependencies": {
|
||||
"browser-process-hrtime": "^1.0.0"
|
||||
}
|
||||
@ -19155,9 +19157,9 @@
|
||||
"integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ=="
|
||||
},
|
||||
"iconoir-react": {
|
||||
"version": "4.7.1",
|
||||
"resolved": "https://registry.npmjs.org/iconoir-react/-/iconoir-react-4.7.1.tgz",
|
||||
"integrity": "sha512-4QJr7qL4zU5Z7/wBSS0hUy/ThMRVqIuxInz79lPc1yIIdwsegqAKvPVfCeg1bHoFhgi5LbWB7pyx8/t/O9KHRQ==",
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/iconoir-react/-/iconoir-react-5.3.2.tgz",
|
||||
"integrity": "sha512-5p5FQAkX6ZAuk1UE/4RmjI9BgI5FSqTbgyUT/m74bfHxl57YAuU7sRdnvrLe97I9IbbiC2uKnGl90xVcBD07rQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"iconv-lite": {
|
||||
|
@ -91,7 +91,7 @@
|
||||
"http": "0.0.1-security",
|
||||
"http-proxy-middleware": "1.0.5",
|
||||
"https": "1.0.0",
|
||||
"iconoir-react": "4.7.1",
|
||||
"iconoir-react": "5.3.2",
|
||||
"immutable": "4.0.0-rc.12",
|
||||
"intersection-observer": "0.11.0",
|
||||
"jsdom": "19.0.0",
|
||||
|
@ -38,6 +38,7 @@ import {
|
||||
User as UserIcon,
|
||||
Group as GroupIcon,
|
||||
HistoricShield as SecurityGroupIcon,
|
||||
RefreshDouble as BackupIcon,
|
||||
} from 'iconoir-react'
|
||||
|
||||
import loadable from '@loadable/component'
|
||||
@ -117,6 +118,9 @@ const CreateSecurityGroups = loadable(
|
||||
ssr: false,
|
||||
}
|
||||
)
|
||||
const Backups = loadable(() => import('client/containers/Backups'), {
|
||||
ssr: false,
|
||||
})
|
||||
const CreateImages = loadable(() => import('client/containers/Images/Create'), {
|
||||
ssr: false,
|
||||
})
|
||||
@ -229,6 +233,10 @@ export const PATH = {
|
||||
DETAIL: `/${RESOURCE_NAMES.FILE}/:id`,
|
||||
CREATE: `/${RESOURCE_NAMES.FILE}/create`,
|
||||
},
|
||||
BACKUPS: {
|
||||
LIST: `/${RESOURCE_NAMES.BACKUP}`,
|
||||
DETAIL: `/${RESOURCE_NAMES.BACKUP}/:id`,
|
||||
},
|
||||
MARKETPLACES: {
|
||||
LIST: `/${RESOURCE_NAMES.MARKETPLACE}`,
|
||||
DETAIL: `/${RESOURCE_NAMES.MARKETPLACE}/:id`,
|
||||
@ -426,6 +434,13 @@ const ENDPOINTS = [
|
||||
path: PATH.STORAGE.IMAGES.DOCKERFILE,
|
||||
Component: CreateDockerfile,
|
||||
},
|
||||
{
|
||||
title: T.Backups,
|
||||
path: PATH.STORAGE.BACKUPS.LIST,
|
||||
sidebar: true,
|
||||
icon: BackupIcon,
|
||||
Component: Backups,
|
||||
},
|
||||
{
|
||||
title: T.Marketplaces,
|
||||
path: PATH.STORAGE.MARKETPLACES.LIST,
|
||||
|
@ -0,0 +1,59 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
|
||||
* *
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
|
||||
* not use this file except in compliance with the License. You may obtain *
|
||||
* a copy of the License at *
|
||||
* *
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 *
|
||||
* *
|
||||
* Unless required by applicable law or agreed to in writing, software *
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, *
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
|
||||
import {
|
||||
SCHEMA,
|
||||
FIELDS,
|
||||
} from 'client/components/Forms/Backup/RestoreForm/Steps/BasicConfiguration/schema'
|
||||
import { Step } from 'client/utils'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
export const STEP_ID = 'configuration'
|
||||
|
||||
const Content = (props) => (
|
||||
<FormWithSchema
|
||||
cy="restore-configuration"
|
||||
id={STEP_ID}
|
||||
fields={() => FIELDS(props)}
|
||||
/>
|
||||
)
|
||||
|
||||
/**
|
||||
* Step to configure the marketplace app.
|
||||
*
|
||||
* @param {object} isMultiple - is multiple rows
|
||||
* @returns {Step} Configuration step
|
||||
*/
|
||||
const ConfigurationStep = (isMultiple) => ({
|
||||
id: STEP_ID,
|
||||
label: T.Configuration,
|
||||
resolver: () => SCHEMA(isMultiple),
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: () => Content(isMultiple),
|
||||
})
|
||||
|
||||
Content.propTypes = {
|
||||
data: PropTypes.any,
|
||||
setFormData: PropTypes.func,
|
||||
nics: PropTypes.array,
|
||||
isMultiple: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default ConfigurationStep
|
@ -0,0 +1,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 { boolean, object, ObjectSchema } from 'yup'
|
||||
import { Field, getValidationFromFields } from 'client/utils'
|
||||
import { T, INPUT_TYPES } from 'client/constants'
|
||||
|
||||
const NO_NIC = {
|
||||
name: 'no_nic',
|
||||
label: T.DoNotRestoreNICAttributes,
|
||||
type: INPUT_TYPES.SWITCH,
|
||||
validation: boolean().yesOrNo(),
|
||||
grid: { xs: 12, md: 6 },
|
||||
}
|
||||
|
||||
const NO_IP = {
|
||||
name: 'no_ip',
|
||||
label: T.DoNotRestoreIPAttributes,
|
||||
type: INPUT_TYPES.SWITCH,
|
||||
validation: boolean().yesOrNo(),
|
||||
grid: { xs: 12, md: 6 },
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Field[]} Fields
|
||||
*/
|
||||
export const FIELDS = () => [NO_NIC, NO_IP]
|
||||
|
||||
/**
|
||||
* @param {object} [stepProps] - Step props
|
||||
* @returns {ObjectSchema} Schema
|
||||
*/
|
||||
export const SCHEMA = (stepProps) =>
|
||||
object(getValidationFromFields(FIELDS(stepProps)))
|
@ -0,0 +1,72 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { useFormContext } from 'react-hook-form'
|
||||
|
||||
import { DatastoresTable } from 'client/components/Tables'
|
||||
import { SCHEMA } from 'client/components/Forms/Backup/RestoreForm/Steps/DatastoresTable/schema'
|
||||
|
||||
import { Step } from 'client/utils'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
export const STEP_ID = 'datastore'
|
||||
|
||||
const Content = ({ data, app }) => {
|
||||
const { NAME } = data?.[0] ?? {}
|
||||
const { setValue } = useFormContext()
|
||||
|
||||
const handleSelectedRows = (rows) => {
|
||||
const { original = {} } = rows?.[0] ?? {}
|
||||
|
||||
setValue(STEP_ID, original.ID !== undefined ? [original] : [])
|
||||
}
|
||||
|
||||
return (
|
||||
<DatastoresTable
|
||||
singleSelect
|
||||
disableGlobalSort
|
||||
displaySelectedRows
|
||||
pageSize={5}
|
||||
getRowId={(row) => String(row.NAME)}
|
||||
initialState={{
|
||||
selectedRowIds: { [NAME]: true },
|
||||
filters: [{ id: 'TYPE', value: 'IMAGE' }],
|
||||
}}
|
||||
onSelectedRowsChange={handleSelectedRows}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Step to select the Datastore.
|
||||
*
|
||||
* @param {object} app - Marketplace App resource
|
||||
* @returns {Step} Datastore step
|
||||
*/
|
||||
const DatastoreStep = (app) => ({
|
||||
id: STEP_ID,
|
||||
label: T.SelectDatastoreImage,
|
||||
resolver: SCHEMA,
|
||||
content: (props) => Content({ ...props, app }),
|
||||
})
|
||||
|
||||
Content.propTypes = {
|
||||
data: PropTypes.any,
|
||||
setFormData: PropTypes.func,
|
||||
app: PropTypes.object,
|
||||
}
|
||||
|
||||
export default DatastoreStep
|
@ -0,0 +1,24 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
|
||||
* *
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
|
||||
* not use this file except in compliance with the License. You may obtain *
|
||||
* a copy of the License at *
|
||||
* *
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 *
|
||||
* *
|
||||
* Unless required by applicable law or agreed to in writing, software *
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, *
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { array, object, ArraySchema } from 'yup'
|
||||
|
||||
/** @type {ArraySchema} Datastore table schema */
|
||||
export const SCHEMA = array(object())
|
||||
.min(1)
|
||||
.max(1)
|
||||
.required()
|
||||
.ensure()
|
||||
.default(() => [])
|
@ -0,0 +1,41 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
|
||||
* *
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
|
||||
* not use this file except in compliance with the License. You may obtain *
|
||||
* a copy of the License at *
|
||||
* *
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 *
|
||||
* *
|
||||
* Unless required by applicable law or agreed to in writing, software *
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, *
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import BasicConfiguration, {
|
||||
STEP_ID as BASIC_ID,
|
||||
} from 'client/components/Forms/Backup/RestoreForm/Steps/BasicConfiguration'
|
||||
import DatastoresTable, {
|
||||
STEP_ID as DATASTORE_ID,
|
||||
} from 'client/components/Forms/Backup/RestoreForm/Steps/DatastoresTable'
|
||||
import { createSteps } from 'client/utils'
|
||||
|
||||
const Steps = createSteps(
|
||||
(app) => [BasicConfiguration, DatastoresTable].filter(Boolean),
|
||||
{
|
||||
transformInitialValue: (app, schema) =>
|
||||
schema.cast({}, { context: { app } }),
|
||||
transformBeforeSubmit: (formData) => {
|
||||
const { [BASIC_ID]: configuration, [DATASTORE_ID]: [datastore] = [] } =
|
||||
formData
|
||||
|
||||
return {
|
||||
datastore: datastore?.ID,
|
||||
...configuration,
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export default Steps
|
27
src/fireedge/src/client/components/Forms/Backup/index.js
Normal file
27
src/fireedge/src/client/components/Forms/Backup/index.js
Normal file
@ -0,0 +1,27 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
|
||||
* *
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
|
||||
* not use this file except in compliance with the License. You may obtain *
|
||||
* a copy of the License at *
|
||||
* *
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 *
|
||||
* *
|
||||
* Unless required by applicable law or agreed to in writing, software *
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, *
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { ReactElement } from 'react'
|
||||
import { AsyncLoadForm, ConfigurationProps } from 'client/components/HOC'
|
||||
import { CreateFormCallback } from 'client/utils/schema'
|
||||
|
||||
/**
|
||||
* @param {ConfigurationProps} configProps - Configuration
|
||||
* @returns {ReactElement|CreateFormCallback} Asynchronous loaded form
|
||||
*/
|
||||
const RestoreForm = (configProps) =>
|
||||
AsyncLoadForm({ formPath: 'Backup/RestoreForm' }, configProps)
|
||||
|
||||
export { RestoreForm }
|
@ -0,0 +1,21 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { SCHEMA, FIELDS } from 'client/components/Forms/Vm/BackupForm/schema'
|
||||
import { createForm } from 'client/utils'
|
||||
|
||||
const BackupForm = createForm(SCHEMA, FIELDS)
|
||||
|
||||
export default BackupForm
|
@ -0,0 +1,44 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { number, object } from 'yup'
|
||||
import { getValidationFromFields, arrayToOptions } from 'client/utils'
|
||||
import { T, INPUT_TYPES } from 'client/constants'
|
||||
import { useGetDatastoresQuery } from 'client/features/OneApi/datastore'
|
||||
|
||||
const DS_ID = {
|
||||
name: 'dsId',
|
||||
label: T.BackupDatastore,
|
||||
type: INPUT_TYPES.SELECT,
|
||||
values: () => {
|
||||
const { data: datastores = [] } = useGetDatastoresQuery()
|
||||
|
||||
return arrayToOptions(
|
||||
datastores.filter(({ TEMPLATE }) => TEMPLATE.TYPE === 'BACKUP_DS'),
|
||||
{
|
||||
getText: ({ NAME, ID } = {}) => `${ID}: ${NAME}`,
|
||||
getValue: ({ ID } = {}) => ID,
|
||||
}
|
||||
)
|
||||
},
|
||||
validation: number()
|
||||
.positive()
|
||||
.required()
|
||||
.default(() => undefined),
|
||||
}
|
||||
|
||||
export const FIELDS = [DS_ID]
|
||||
|
||||
export const SCHEMA = object(getValidationFromFields(FIELDS))
|
@ -38,6 +38,7 @@ import {
|
||||
dateToMilliseconds,
|
||||
} from 'client/models/Helper'
|
||||
import { getSnapshotList, getDisks } from 'client/models/VirtualMachine'
|
||||
import { useGetDatastoresQuery } from 'client/features/OneApi/datastore'
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Constants
|
||||
@ -113,6 +114,26 @@ const ACTION_FIELD_FOR_CHARTERS = {
|
||||
}),
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Field} Datastore id field
|
||||
*/
|
||||
const ARGS_DS_ID_FIELD = {
|
||||
...createArgField(ARGS_TYPES.DS_ID),
|
||||
label: T.BackupDatastore,
|
||||
type: INPUT_TYPES.SELECT,
|
||||
values: () => {
|
||||
const { data: datastores = [] } = useGetDatastoresQuery()
|
||||
|
||||
return arrayToOptions(
|
||||
datastores.filter(({ TEMPLATE }) => TEMPLATE.TYPE === 'BACKUP_DS'),
|
||||
{
|
||||
getText: ({ NAME, ID } = {}) => `${ID}: ${NAME}`,
|
||||
getValue: ({ ID } = {}) => ID,
|
||||
}
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} vm - Vm resource
|
||||
* @returns {Field} Disk id field
|
||||
@ -462,6 +483,7 @@ export const PUNCTUAL_FIELDS = {
|
||||
ARGS_NAME_FIELD,
|
||||
ARGS_DISK_ID_FIELD,
|
||||
ARGS_SNAPSHOT_ID_FIELD,
|
||||
ARGS_DS_ID_FIELD,
|
||||
PERIODIC_FIELD,
|
||||
REPEAT_FIELD,
|
||||
WEEKLY_FIELD,
|
||||
|
@ -34,6 +34,7 @@ const ARG_SCHEMAS = {
|
||||
[ARGS_TYPES.DISK_ID]: ARG_SCHEMA,
|
||||
[ARGS_TYPES.NAME]: ARG_SCHEMA,
|
||||
[ARGS_TYPES.SNAPSHOT_ID]: ARG_SCHEMA,
|
||||
[ARGS_TYPES.DS_ID]: ARG_SCHEMA,
|
||||
}
|
||||
|
||||
/**
|
||||
@ -44,6 +45,7 @@ const COMMON_FIELDS = (vm) => [
|
||||
PUNCTUAL_FIELDS.ARGS_NAME_FIELD,
|
||||
PUNCTUAL_FIELDS.ARGS_DISK_ID_FIELD(vm),
|
||||
PUNCTUAL_FIELDS.ARGS_SNAPSHOT_ID_FIELD(vm),
|
||||
PUNCTUAL_FIELDS.ARGS_DS_ID_FIELD,
|
||||
PUNCTUAL_FIELDS.PERIODIC_FIELD,
|
||||
PUNCTUAL_FIELDS.REPEAT_FIELD,
|
||||
PUNCTUAL_FIELDS.WEEKLY_FIELD,
|
||||
|
@ -156,9 +156,17 @@ const CreateRelativeCharterForm = (configProps) =>
|
||||
const UpdateConfigurationForm = (configProps) =>
|
||||
AsyncLoadForm({ formPath: 'Vm/UpdateConfigurationForm' }, configProps)
|
||||
|
||||
/**
|
||||
* @param {ConfigurationProps} configProps - Configuration
|
||||
* @returns {ReactElement|CreateFormCallback} Asynchronous loaded form
|
||||
*/
|
||||
const BackupForm = (configProps) =>
|
||||
AsyncLoadForm({ formPath: 'Vm/BackupForm' }, configProps)
|
||||
|
||||
export {
|
||||
AttachNicForm,
|
||||
AttachSecGroupForm,
|
||||
BackupForm,
|
||||
ChangeGroupForm,
|
||||
ChangeUserForm,
|
||||
CreateCharterForm,
|
||||
|
189
src/fireedge/src/client/components/Tables/Backups/actions.js
Normal file
189
src/fireedge/src/client/components/Tables/Backups/actions.js
Normal file
@ -0,0 +1,189 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { Typography } from '@mui/material'
|
||||
import { Group, Trash } from 'iconoir-react'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { useViews } from 'client/features/Auth'
|
||||
import {
|
||||
useChangeImageOwnershipMutation,
|
||||
useRemoveImageMutation,
|
||||
useRestoreBackupMutation,
|
||||
} from 'client/features/OneApi/image'
|
||||
|
||||
import { ChangeGroupForm, ChangeUserForm } from 'client/components/Forms/Vm'
|
||||
import { RestoreForm } from 'client/components/Forms/Backup'
|
||||
import {
|
||||
createActions,
|
||||
GlobalAction,
|
||||
} from 'client/components/Tables/Enhanced/Utils'
|
||||
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import { IMAGE_ACTIONS, RESOURCE_NAMES, T } from 'client/constants'
|
||||
import { isAvailableAction } from 'client/models/VirtualMachine'
|
||||
|
||||
const isDisabled = (action) => (rows) =>
|
||||
!isAvailableAction(
|
||||
action,
|
||||
rows.map(({ original }) => original)
|
||||
)
|
||||
|
||||
const ListImagesNames = ({ rows = [] }) =>
|
||||
rows?.map?.(({ id, original }) => {
|
||||
const { ID, NAME } = original
|
||||
|
||||
return (
|
||||
<Typography
|
||||
key={`image-${id}`}
|
||||
variant="inherit"
|
||||
component="span"
|
||||
display="block"
|
||||
>
|
||||
{`#${ID} ${NAME}`}
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
|
||||
const SubHeader = (rows) => <ListImagesNames rows={rows} />
|
||||
|
||||
const MessageToConfirmAction = (rows) => (
|
||||
<>
|
||||
<ListImagesNames rows={rows} />
|
||||
<Translate word={T.DoYouWantProceed} />
|
||||
</>
|
||||
)
|
||||
|
||||
/**
|
||||
* Generates the actions to operate resources on Image table.
|
||||
*
|
||||
* @returns {GlobalAction} - Actions
|
||||
*/
|
||||
const Actions = () => {
|
||||
const { view, getResourceView } = useViews()
|
||||
const [changeOwnership] = useChangeImageOwnershipMutation()
|
||||
const [restoreBackup] = useRestoreBackupMutation()
|
||||
const [deleteImage] = useRemoveImageMutation()
|
||||
|
||||
const resourcesView = getResourceView(RESOURCE_NAMES.BACKUP)?.actions
|
||||
|
||||
const imageActions = useMemo(
|
||||
() =>
|
||||
createActions({
|
||||
filters: resourcesView,
|
||||
actions: [
|
||||
{
|
||||
accessor: IMAGE_ACTIONS.RESTORE,
|
||||
color: 'secondary',
|
||||
dataCy: `image-${IMAGE_ACTIONS.RESTORE}`,
|
||||
label: T.Restore,
|
||||
tooltip: T.Restore,
|
||||
selected: { min: 1 },
|
||||
options: [
|
||||
{
|
||||
dialogProps: {
|
||||
title: T.SelectCluster,
|
||||
dataCy: 'modal-select-cluster',
|
||||
},
|
||||
form: (rows) => RestoreForm(),
|
||||
onSubmit: (rows) => async (formData) => {
|
||||
const ids = rows?.map?.(({ original }) => original?.ID)
|
||||
await Promise.all(
|
||||
ids.map((id) =>
|
||||
restoreBackup({
|
||||
id: id,
|
||||
datastore: formData.datastore,
|
||||
options: `NO_IP="${formData.no_ip}"\nNO_NIC="${formData.no_nic}"`,
|
||||
})
|
||||
)
|
||||
)
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
tooltip: T.Ownership,
|
||||
icon: Group,
|
||||
selected: true,
|
||||
color: 'secondary',
|
||||
dataCy: 'image-ownership',
|
||||
options: [
|
||||
{
|
||||
accessor: IMAGE_ACTIONS.CHANGE_OWNER,
|
||||
disabled: isDisabled(IMAGE_ACTIONS.CHANGE_OWNER),
|
||||
name: T.ChangeOwner,
|
||||
dialogProps: {
|
||||
title: T.ChangeOwner,
|
||||
subheader: SubHeader,
|
||||
dataCy: `modal-${IMAGE_ACTIONS.CHANGE_OWNER}`,
|
||||
},
|
||||
form: ChangeUserForm,
|
||||
onSubmit: (rows) => async (newOwnership) => {
|
||||
const ids = rows?.map?.(({ original }) => original?.ID)
|
||||
await Promise.all(
|
||||
ids.map((id) => changeOwnership({ id, ...newOwnership }))
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
accessor: IMAGE_ACTIONS.CHANGE_GROUP,
|
||||
disabled: isDisabled(IMAGE_ACTIONS.CHANGE_GROUP),
|
||||
name: T.ChangeGroup,
|
||||
dialogProps: {
|
||||
title: T.ChangeGroup,
|
||||
subheader: SubHeader,
|
||||
dataCy: `modal-${IMAGE_ACTIONS.CHANGE_GROUP}`,
|
||||
},
|
||||
form: ChangeGroupForm,
|
||||
onSubmit: (rows) => async (newOwnership) => {
|
||||
const ids = rows?.map?.(({ original }) => original?.ID)
|
||||
await Promise.all(
|
||||
ids.map((id) => changeOwnership({ id, ...newOwnership }))
|
||||
)
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
accessor: IMAGE_ACTIONS.DELETE,
|
||||
tooltip: T.Delete,
|
||||
icon: Trash,
|
||||
color: 'error',
|
||||
selected: { min: 1 },
|
||||
dataCy: `image_${IMAGE_ACTIONS.DELETE}`,
|
||||
options: [
|
||||
{
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
title: T.Delete,
|
||||
dataCy: `modal-${IMAGE_ACTIONS.DELETE}`,
|
||||
children: MessageToConfirmAction,
|
||||
},
|
||||
onSubmit: (rows) => async () => {
|
||||
const ids = rows?.map?.(({ original }) => original?.ID)
|
||||
await Promise.all(ids.map((id) => deleteImage({ id })))
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
[view]
|
||||
)
|
||||
|
||||
return imageActions
|
||||
}
|
||||
|
||||
export default Actions
|
66
src/fireedge/src/client/components/Tables/Backups/columns.js
Normal file
66
src/fireedge/src/client/components/Tables/Backups/columns.js
Normal file
@ -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. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { CategoryFilter } from 'client/components/Tables/Enhanced/Utils'
|
||||
import * as ImageModel from 'client/models/Image'
|
||||
|
||||
const getTotalOfResources = (resources) =>
|
||||
[resources?.ID ?? []].flat().length || 0
|
||||
|
||||
export default [
|
||||
{ Header: 'ID', accessor: 'ID', sortType: 'number' },
|
||||
{ Header: 'Name', accessor: 'NAME' },
|
||||
{ Header: 'Owner', accessor: 'UNAME' },
|
||||
{ Header: 'Group', accessor: 'GNAME' },
|
||||
{ Header: 'Locked', id: 'locked', accessor: 'LOCK' },
|
||||
{
|
||||
Header: 'State',
|
||||
id: 'STATE',
|
||||
accessor: (row) => ImageModel.getState(row)?.name,
|
||||
disableFilters: false,
|
||||
Filter: ({ column }) =>
|
||||
CategoryFilter({
|
||||
column,
|
||||
multiple: true,
|
||||
title: 'State',
|
||||
}),
|
||||
filter: 'includesValue',
|
||||
},
|
||||
{
|
||||
Header: 'Type',
|
||||
id: 'TYPE',
|
||||
accessor: (row) => ImageModel.getType(row),
|
||||
},
|
||||
{
|
||||
Header: 'Disk Type',
|
||||
id: 'DISK_TYPE',
|
||||
accessor: (row) => ImageModel.getDiskType(row),
|
||||
},
|
||||
{ Header: 'Registration Time', accessor: 'REGTIME' },
|
||||
{ Header: 'Datastore', accessor: 'DATASTORE' },
|
||||
{ Header: 'Persistent', accessor: 'PERSISTENT' },
|
||||
{
|
||||
Header: 'Running VMs',
|
||||
accessor: 'RUNNING_VMS',
|
||||
sortType: 'number',
|
||||
},
|
||||
{
|
||||
Header: 'Total VMs',
|
||||
id: 'TOTAL_VMS',
|
||||
accessor: (row) => getTotalOfResources(row?.VMS),
|
||||
sortType: 'number',
|
||||
},
|
||||
]
|
83
src/fireedge/src/client/components/Tables/Backups/index.js
Normal file
83
src/fireedge/src/client/components/Tables/Backups/index.js
Normal file
@ -0,0 +1,83 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { useViews } from 'client/features/Auth'
|
||||
import { useGetBackupsQuery } from 'client/features/OneApi/image'
|
||||
|
||||
import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced'
|
||||
import backupColumns from 'client/components/Tables/Backups/columns'
|
||||
import BackupRow from 'client/components/Tables/Backups/row'
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
const DEFAULT_DATA_CY = 'backups'
|
||||
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @returns {ReactElement} Backups table
|
||||
*/
|
||||
const BackupsTable = (props) => {
|
||||
const { rootProps = {}, searchProps = {}, vm, ...rest } = props ?? {}
|
||||
rootProps['data-cy'] ??= DEFAULT_DATA_CY
|
||||
searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}`
|
||||
|
||||
const { view, getResourceView } = useViews()
|
||||
const {
|
||||
data = [],
|
||||
isFetching,
|
||||
refetch,
|
||||
} = useGetBackupsQuery(undefined, {
|
||||
selectFromResult: (result) => {
|
||||
const backupsIds = vm?.BACKUPS?.BACKUP_IDS?.ID
|
||||
? Array.isArray(vm?.BACKUPS?.BACKUP_IDS?.ID)
|
||||
? vm?.BACKUPS?.BACKUP_IDS?.ID
|
||||
: [vm?.BACKUPS?.BACKUP_IDS?.ID]
|
||||
: []
|
||||
|
||||
return {
|
||||
...result,
|
||||
data: result?.data?.filter((backup) => backupsIds?.includes(backup.ID)),
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
createColumns({
|
||||
filters: getResourceView(RESOURCE_NAMES.BACKUP)?.filters,
|
||||
columns: backupColumns,
|
||||
}),
|
||||
[view]
|
||||
)
|
||||
|
||||
return (
|
||||
<EnhancedTable
|
||||
columns={columns}
|
||||
data={useMemo(() => data, [data])}
|
||||
rootProps={rootProps}
|
||||
searchProps={searchProps}
|
||||
refetch={refetch}
|
||||
isLoading={isFetching}
|
||||
getRowId={(row) => String(row.ID)}
|
||||
RowComponent={BackupRow}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
BackupsTable.displayName = 'BackupsTable'
|
||||
|
||||
export default BackupsTable
|
131
src/fireedge/src/client/components/Tables/Backups/row.js
Normal file
131
src/fireedge/src/client/components/Tables/Backups/row.js
Normal file
@ -0,0 +1,131 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
|
||||
* *
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
|
||||
* not use this file except in compliance with the License. You may obtain *
|
||||
* a copy of the License at *
|
||||
* *
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 *
|
||||
* *
|
||||
* Unless required by applicable law or agreed to in writing, software *
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, *
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import {
|
||||
Lock,
|
||||
User,
|
||||
Group,
|
||||
Db as DatastoreIcon,
|
||||
ModernTv,
|
||||
Pin as PersistentIcon,
|
||||
Archive as DiskTypeIcon,
|
||||
} from 'iconoir-react'
|
||||
import { Typography } from '@mui/material'
|
||||
|
||||
import Timer from 'client/components/Timer'
|
||||
import { StatusCircle, StatusChip } from 'client/components/Status'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
import * as ImageModel from 'client/models/Image'
|
||||
import * as Helper from 'client/models/Helper'
|
||||
|
||||
const Row = ({ original, value, ...props }) => {
|
||||
const classes = rowStyles()
|
||||
const {
|
||||
ID,
|
||||
NAME,
|
||||
UNAME,
|
||||
GNAME,
|
||||
REGTIME,
|
||||
TYPE,
|
||||
DISK_TYPE,
|
||||
PERSISTENT,
|
||||
locked,
|
||||
DATASTORE,
|
||||
TOTAL_VMS,
|
||||
RUNNING_VMS,
|
||||
} = value
|
||||
|
||||
const labels = [...new Set([TYPE])].filter(Boolean)
|
||||
|
||||
const { color: stateColor, name: stateName } = ImageModel.getState(original)
|
||||
|
||||
const time = Helper.timeFromMilliseconds(+REGTIME)
|
||||
|
||||
return (
|
||||
<div {...props} data-cy={`image-${ID}`}>
|
||||
<div className={classes.main}>
|
||||
<div className={classes.title}>
|
||||
<StatusCircle color={stateColor} tooltip={stateName} />
|
||||
<Typography noWrap component="span" data-cy="name">
|
||||
{NAME}
|
||||
</Typography>
|
||||
{locked && <Lock />}
|
||||
<span className={classes.labels}>
|
||||
{labels.map((label) => (
|
||||
<StatusChip key={label} text={label} />
|
||||
))}
|
||||
</span>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span>{`${ID}`}</span>
|
||||
<span title={time.toFormat('ff')}>
|
||||
<Timer translateWord={T.RegisteredAt} initial={time} />
|
||||
</span>
|
||||
<span title={`${T.Owner}: ${UNAME}`}>
|
||||
<User />
|
||||
<span>{` ${UNAME}`}</span>
|
||||
</span>
|
||||
<span title={`${T.Group}: ${GNAME}`}>
|
||||
<Group />
|
||||
<span>{` ${GNAME}`}</span>
|
||||
</span>
|
||||
<span title={`${T.Datastore}: ${DATASTORE}`}>
|
||||
<DatastoreIcon />
|
||||
<span>{` ${DATASTORE}`}</span>
|
||||
</span>
|
||||
<span
|
||||
title={
|
||||
PERSISTENT
|
||||
? T.Persistent.toLowerCase()
|
||||
: T.NonPersistent.toLowerCase()
|
||||
}
|
||||
>
|
||||
<PersistentIcon />
|
||||
<span>
|
||||
{PERSISTENT
|
||||
? T.Persistent.toLowerCase()
|
||||
: T.NonPersistent.toLowerCase()}
|
||||
</span>
|
||||
</span>
|
||||
<span title={`${T.DiskType}: ${DISK_TYPE.toLowerCase()}`}>
|
||||
<DiskTypeIcon />
|
||||
<span>{` ${DISK_TYPE.toLowerCase()}`}</span>
|
||||
</span>
|
||||
<span
|
||||
title={`${T.Running} / ${T.Used} ${T.VMs}: ${RUNNING_VMS} / ${TOTAL_VMS}`}
|
||||
>
|
||||
<ModernTv />
|
||||
<span>{` ${RUNNING_VMS} / ${TOTAL_VMS}`}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.secondary}></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Row.propTypes = {
|
||||
original: PropTypes.object,
|
||||
value: PropTypes.object,
|
||||
isSelected: PropTypes.bool,
|
||||
handleClick: PropTypes.func,
|
||||
}
|
||||
|
||||
export default Row
|
@ -16,11 +16,21 @@
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { Lock, User, Group, Folder, ModernTv } from 'iconoir-react'
|
||||
import {
|
||||
Lock,
|
||||
User,
|
||||
Group,
|
||||
Db as DatastoreIcon,
|
||||
ModernTv,
|
||||
Pin as PersistentIcon,
|
||||
Archive as DiskTypeIcon,
|
||||
} from 'iconoir-react'
|
||||
import { Typography } from '@mui/material'
|
||||
|
||||
import Timer from 'client/components/Timer'
|
||||
import { StatusCircle, StatusChip } from 'client/components/Status'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
import * as ImageModel from 'client/models/Image'
|
||||
import * as Helper from 'client/models/Helper'
|
||||
@ -42,14 +52,11 @@ const Row = ({ original, value, ...props }) => {
|
||||
RUNNING_VMS,
|
||||
} = value
|
||||
|
||||
const labels = [
|
||||
...new Set([PERSISTENT && 'PERSISTENT', TYPE, DISK_TYPE]),
|
||||
].filter(Boolean)
|
||||
const labels = [...new Set([TYPE])].filter(Boolean)
|
||||
|
||||
const { color: stateColor, name: stateName } = ImageModel.getState(original)
|
||||
|
||||
const time = Helper.timeFromMilliseconds(+REGTIME)
|
||||
const timeAgo = `registered ${time.toRelative()}`
|
||||
|
||||
return (
|
||||
<div {...props} data-cy={`image-${ID}`}>
|
||||
@ -67,20 +74,43 @@ const Row = ({ original, value, ...props }) => {
|
||||
</span>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span title={time.toFormat('ff')}>{`#${ID} ${timeAgo}`}</span>
|
||||
<span title={`Owner: ${UNAME}`}>
|
||||
<span>{`${ID}`}</span>
|
||||
<span title={time.toFormat('ff')}>
|
||||
<Timer translateWord={T.RegisteredAt} initial={time} />
|
||||
</span>
|
||||
<span title={`${T.Owner}: ${UNAME}`}>
|
||||
<User />
|
||||
<span>{` ${UNAME}`}</span>
|
||||
</span>
|
||||
<span title={`Group: ${GNAME}`}>
|
||||
<span title={`${T.Group}: ${GNAME}`}>
|
||||
<Group />
|
||||
<span>{` ${GNAME}`}</span>
|
||||
</span>
|
||||
<span title={`Datastore: ${DATASTORE}`}>
|
||||
<Folder />
|
||||
<span title={`${T.Datastore}: ${DATASTORE}`}>
|
||||
<DatastoreIcon />
|
||||
<span>{` ${DATASTORE}`}</span>
|
||||
</span>
|
||||
<span title={`Running / Used VMs: ${RUNNING_VMS} / ${TOTAL_VMS}`}>
|
||||
<span
|
||||
title={
|
||||
PERSISTENT
|
||||
? T.Persistent.toLowerCase()
|
||||
: T.NonPersistent.toLowerCase()
|
||||
}
|
||||
>
|
||||
<PersistentIcon />
|
||||
<span>
|
||||
{PERSISTENT
|
||||
? T.Persistent.toLowerCase()
|
||||
: T.NonPersistent.toLowerCase()}
|
||||
</span>
|
||||
</span>
|
||||
<span title={`${T.DiskType}: ${DISK_TYPE.toLowerCase()}`}>
|
||||
<DiskTypeIcon />
|
||||
<span>{` ${DISK_TYPE.toLowerCase()}`}</span>
|
||||
</span>
|
||||
<span
|
||||
title={`${T.Running} / ${T.Used} ${T.VMs}: ${RUNNING_VMS} / ${TOTAL_VMS}`}
|
||||
>
|
||||
<ModernTv />
|
||||
<span>{` ${RUNNING_VMS} / ${TOTAL_VMS}`}</span>
|
||||
</span>
|
||||
|
@ -16,11 +16,21 @@
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { Lock, User, Group, Folder, ModernTv } from 'iconoir-react'
|
||||
import {
|
||||
Lock,
|
||||
User,
|
||||
Group,
|
||||
Db as DatastoreIcon,
|
||||
ModernTv,
|
||||
Pin as PersistentIcon,
|
||||
Archive as DiskTypeIcon,
|
||||
} from 'iconoir-react'
|
||||
import { Typography } from '@mui/material'
|
||||
|
||||
import Timer from 'client/components/Timer'
|
||||
import { StatusCircle, StatusChip } from 'client/components/Status'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
import * as ImageModel from 'client/models/Image'
|
||||
import * as Helper from 'client/models/Helper'
|
||||
@ -42,14 +52,11 @@ const Row = ({ original, value, ...props }) => {
|
||||
RUNNING_VMS,
|
||||
} = value
|
||||
|
||||
const labels = [
|
||||
...new Set([PERSISTENT && 'PERSISTENT', TYPE, DISK_TYPE]),
|
||||
].filter(Boolean)
|
||||
const labels = [...new Set([TYPE])].filter(Boolean)
|
||||
|
||||
const { color: stateColor, name: stateName } = ImageModel.getState(original)
|
||||
|
||||
const time = Helper.timeFromMilliseconds(+REGTIME)
|
||||
const timeAgo = `registered ${time.toRelative()}`
|
||||
|
||||
return (
|
||||
<div {...props} data-cy={`image-${ID}`}>
|
||||
@ -67,20 +74,43 @@ const Row = ({ original, value, ...props }) => {
|
||||
</span>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span title={time.toFormat('ff')}>{`#${ID} ${timeAgo}`}</span>
|
||||
<span title={`Owner: ${UNAME}`}>
|
||||
<span>{`${ID}`}</span>
|
||||
<span title={time.toFormat('ff')}>
|
||||
<Timer translateWord={T.RegisteredAt} initial={time} />
|
||||
</span>
|
||||
<span title={`${T.Owner}: ${UNAME}`}>
|
||||
<User />
|
||||
<span>{` ${UNAME}`}</span>
|
||||
</span>
|
||||
<span title={`Group: ${GNAME}`}>
|
||||
<span title={`${T.Group}: ${GNAME}`}>
|
||||
<Group />
|
||||
<span>{` ${GNAME}`}</span>
|
||||
</span>
|
||||
<span title={`Datastore: ${DATASTORE}`}>
|
||||
<Folder />
|
||||
<span title={`${T.Datastore}: ${DATASTORE}`}>
|
||||
<DatastoreIcon />
|
||||
<span>{` ${DATASTORE}`}</span>
|
||||
</span>
|
||||
<span title={`Running / Used VMs: ${RUNNING_VMS} / ${TOTAL_VMS}`}>
|
||||
<span
|
||||
title={
|
||||
PERSISTENT
|
||||
? T.Persistent.toLowerCase()
|
||||
: T.NonPersistent.toLowerCase()
|
||||
}
|
||||
>
|
||||
<PersistentIcon />
|
||||
<span>
|
||||
{PERSISTENT
|
||||
? T.Persistent.toLowerCase()
|
||||
: T.NonPersistent.toLowerCase()}
|
||||
</span>
|
||||
</span>
|
||||
<span title={`${T.DiskType}: ${DISK_TYPE.toLowerCase()}`}>
|
||||
<DiskTypeIcon />
|
||||
<span>{` ${DISK_TYPE.toLowerCase()}`}</span>
|
||||
</span>
|
||||
<span
|
||||
title={`${T.Running} / ${T.Used} ${T.VMs}: ${RUNNING_VMS} / ${TOTAL_VMS}`}
|
||||
>
|
||||
<ModernTv />
|
||||
<span>{` ${RUNNING_VMS} / ${TOTAL_VMS}`}</span>
|
||||
</span>
|
||||
|
@ -41,9 +41,11 @@ import {
|
||||
useMigrateMutation,
|
||||
useChangeVmOwnershipMutation,
|
||||
useRecoverMutation,
|
||||
useBackupMutation,
|
||||
} from 'client/features/OneApi/vm'
|
||||
|
||||
import {
|
||||
BackupForm,
|
||||
RecoverForm,
|
||||
ChangeUserForm,
|
||||
ChangeGroupForm,
|
||||
@ -121,6 +123,7 @@ const Actions = () => {
|
||||
const [saveAsTemplate] = useSaveAsTemplateMutation()
|
||||
const [actionVm] = useActionVmMutation()
|
||||
const [recover] = useRecoverMutation()
|
||||
const [backup] = useBackupMutation()
|
||||
const [changeOwnership] = useChangeVmOwnershipMutation()
|
||||
const [deploy] = useDeployMutation()
|
||||
const [migrate] = useMigrateMutation()
|
||||
@ -502,6 +505,23 @@ const Actions = () => {
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
accessor: VM_ACTIONS.BACKUP,
|
||||
disabled: isDisabled(VM_ACTIONS.BACKUP),
|
||||
name: T.Backup,
|
||||
dialogProps: {
|
||||
title: T.Backup,
|
||||
subheader: SubHeader,
|
||||
dataCy: `modal-${VM_ACTIONS.BACKUP}`,
|
||||
},
|
||||
form: BackupForm,
|
||||
onSubmit: (rows) => async (formData) => {
|
||||
const ids = rows?.map?.(({ original }) => original?.ID)
|
||||
await Promise.all(
|
||||
ids.map((id) => backup({ id, ...formData }))
|
||||
)
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import BackupsTable from 'client/components/Tables/Backups'
|
||||
import ClustersTable from 'client/components/Tables/Clusters'
|
||||
import DatastoresTable from 'client/components/Tables/Datastores'
|
||||
import DockerHubTagsTable from 'client/components/Tables/DockerHubTags'
|
||||
@ -41,6 +42,7 @@ export * from 'client/components/Tables/Enhanced/Utils'
|
||||
export {
|
||||
SkeletonTable,
|
||||
EnhancedTable,
|
||||
BackupsTable,
|
||||
FilesTable,
|
||||
VirtualizedTable,
|
||||
ClustersTable,
|
||||
|
145
src/fireedge/src/client/components/Tabs/Backup/Info/index.js
Normal file
145
src/fireedge/src/client/components/Tabs/Backup/Info/index.js
Normal file
@ -0,0 +1,145 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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, useCallback } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Stack } from '@mui/material'
|
||||
|
||||
import {
|
||||
useGetImageQuery,
|
||||
useChangeImageOwnershipMutation,
|
||||
useChangeImagePermissionsMutation,
|
||||
useUpdateImageMutation,
|
||||
} from 'client/features/OneApi/image'
|
||||
import {
|
||||
Permissions,
|
||||
Ownership,
|
||||
AttributePanel,
|
||||
} from 'client/components/Tabs/Common'
|
||||
import Information from 'client/components/Tabs/Backup/Info/information'
|
||||
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import { T } from 'client/constants'
|
||||
import { getActionsAvailable, jsonToXml } from 'client/models/Helper'
|
||||
import { cloneObject, set } from 'client/utils'
|
||||
|
||||
/**
|
||||
* Renders mainly information tab.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {object} props.tabProps - Tab information
|
||||
* @param {string} props.id - Image id
|
||||
* @returns {ReactElement} Information tab
|
||||
*/
|
||||
const ImageInfoTab = ({ tabProps = {}, id }) => {
|
||||
const {
|
||||
information_panel: informationPanel,
|
||||
permissions_panel: permissionsPanel,
|
||||
ownership_panel: ownershipPanel,
|
||||
attributes_panel: attributesPanel,
|
||||
} = tabProps
|
||||
|
||||
const [changeOwnership] = useChangeImageOwnershipMutation()
|
||||
const [changePermissions] = useChangeImagePermissionsMutation()
|
||||
const [update] = useUpdateImageMutation()
|
||||
const { data: image } = useGetImageQuery({ id })
|
||||
|
||||
const { UNAME, UID, GNAME, GID, PERMISSIONS, TEMPLATE } = image
|
||||
|
||||
const handleChangeOwnership = async (newOwnership) => {
|
||||
await changeOwnership({ id, ...newOwnership })
|
||||
}
|
||||
|
||||
const handleChangePermission = async (newPermission) => {
|
||||
await changePermissions({ id, ...newPermission })
|
||||
}
|
||||
|
||||
const handleAttributeInXml = async (path, newValue) => {
|
||||
const newTemplate = cloneObject(TEMPLATE)
|
||||
set(newTemplate, path, newValue)
|
||||
|
||||
const xml = jsonToXml(newTemplate)
|
||||
await update({ id, template: xml, replace: 0 })
|
||||
}
|
||||
|
||||
const getActions = useCallback(
|
||||
(actions) => getActionsAvailable(actions),
|
||||
[getActionsAvailable]
|
||||
)
|
||||
|
||||
const ATTRIBUTE_FUNCTION = {
|
||||
handleAdd: handleAttributeInXml,
|
||||
handleEdit: handleAttributeInXml,
|
||||
handleDelete: handleAttributeInXml,
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack
|
||||
display="grid"
|
||||
gap="1em"
|
||||
gridTemplateColumns="repeat(auto-fit, minmax(49%, 1fr))"
|
||||
padding={{ sm: '0.8em' }}
|
||||
>
|
||||
{informationPanel?.enabled && (
|
||||
<Information
|
||||
image={image}
|
||||
actions={getActions(informationPanel?.actions)}
|
||||
/>
|
||||
)}
|
||||
{permissionsPanel?.enabled && (
|
||||
<Permissions
|
||||
actions={getActions(permissionsPanel?.actions)}
|
||||
handleEdit={handleChangePermission}
|
||||
ownerUse={PERMISSIONS.OWNER_U}
|
||||
ownerManage={PERMISSIONS.OWNER_M}
|
||||
ownerAdmin={PERMISSIONS.OWNER_A}
|
||||
groupUse={PERMISSIONS.GROUP_U}
|
||||
groupManage={PERMISSIONS.GROUP_M}
|
||||
groupAdmin={PERMISSIONS.GROUP_A}
|
||||
otherUse={PERMISSIONS.OTHER_U}
|
||||
otherManage={PERMISSIONS.OTHER_M}
|
||||
otherAdmin={PERMISSIONS.OTHER_A}
|
||||
/>
|
||||
)}
|
||||
{ownershipPanel?.enabled && (
|
||||
<Ownership
|
||||
actions={getActions(ownershipPanel?.actions)}
|
||||
handleEdit={handleChangeOwnership}
|
||||
userId={UID}
|
||||
userName={UNAME}
|
||||
groupId={GID}
|
||||
groupName={GNAME}
|
||||
/>
|
||||
)}
|
||||
{attributesPanel?.enabled && (
|
||||
<AttributePanel
|
||||
{...ATTRIBUTE_FUNCTION}
|
||||
attributes={TEMPLATE}
|
||||
actions={getActions(attributesPanel?.actions)}
|
||||
title={Tr(T.Attributes)}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
ImageInfoTab.propTypes = {
|
||||
tabProps: PropTypes.object,
|
||||
id: PropTypes.string,
|
||||
}
|
||||
|
||||
ImageInfoTab.displayName = 'ImageInfoTab'
|
||||
|
||||
export default ImageInfoTab
|
@ -0,0 +1,163 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { generatePath } from 'react-router-dom'
|
||||
|
||||
import {
|
||||
useRenameImageMutation,
|
||||
useChangeImageTypeMutation,
|
||||
usePersistentImageMutation,
|
||||
} from 'client/features/OneApi/image'
|
||||
import { StatusChip } from 'client/components/Status'
|
||||
import { List } from 'client/components/Tabs/Common'
|
||||
|
||||
import { getDiskType, getType, getState } from 'client/models/Image'
|
||||
import { timeToString, booleanToString } from 'client/models/Helper'
|
||||
import { arrayToOptions, prettyBytes } from 'client/utils'
|
||||
import { T, Image, IMAGE_ACTIONS, IMAGE_TYPES } from 'client/constants'
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
|
||||
/**
|
||||
* Renders mainly information tab.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {Image} props.image - Image resource
|
||||
* @param {string[]} props.actions - Available actions to information tab
|
||||
* @returns {ReactElement} Information tab
|
||||
*/
|
||||
const InformationPanel = ({ image = {}, actions }) => {
|
||||
const [rename] = useRenameImageMutation()
|
||||
const [changeType] = useChangeImageTypeMutation()
|
||||
const [persistent] = usePersistentImageMutation()
|
||||
|
||||
const {
|
||||
ID,
|
||||
NAME,
|
||||
SIZE,
|
||||
PERSISTENT,
|
||||
REGTIME,
|
||||
DATASTORE_ID,
|
||||
DATASTORE = '--',
|
||||
VMS,
|
||||
} = image
|
||||
|
||||
const { color: stateColor, name: stateName } = getState(image)
|
||||
const imageTypeName = getType(image)
|
||||
const imageDiskTypeName = getDiskType(image)
|
||||
|
||||
const handleRename = async (_, newName) => {
|
||||
await rename({ id: ID, name: newName })
|
||||
}
|
||||
|
||||
const handleChangeType = async (_, newType) => {
|
||||
await changeType({ id: ID, type: newType })
|
||||
}
|
||||
|
||||
const handleChangePersistent = async (_, newPersistent) => {
|
||||
await persistent({ id: ID, persistent: !!+newPersistent })
|
||||
}
|
||||
|
||||
const getTypeOptions = () => arrayToOptions(IMAGE_TYPES, { addEmpty: false })
|
||||
|
||||
const getPersistentOptions = () =>
|
||||
arrayToOptions([0, 1], {
|
||||
addEmpty: false,
|
||||
getText: booleanToString,
|
||||
getValue: String,
|
||||
})
|
||||
|
||||
const info = [
|
||||
{ name: T.ID, value: ID, dataCy: 'id' },
|
||||
{
|
||||
name: T.Name,
|
||||
value: NAME,
|
||||
dataCy: 'name',
|
||||
canEdit: actions?.includes?.(IMAGE_ACTIONS.RENAME),
|
||||
handleEdit: handleRename,
|
||||
},
|
||||
DATASTORE_ID && {
|
||||
name: T.Datastore,
|
||||
value: `#${DATASTORE_ID} ${DATASTORE}`,
|
||||
link:
|
||||
!Number.isNaN(+DATASTORE_ID) &&
|
||||
generatePath(PATH.STORAGE.DATASTORES.DETAIL, { id: DATASTORE_ID }),
|
||||
dataCy: 'datastoreId',
|
||||
},
|
||||
{
|
||||
name: T.RegistrationTime,
|
||||
value: timeToString(REGTIME),
|
||||
dataCy: 'regtime',
|
||||
},
|
||||
{
|
||||
name: T.Type,
|
||||
value: imageTypeName,
|
||||
valueInOptionList: imageTypeName,
|
||||
canEdit: actions?.includes?.(IMAGE_ACTIONS.CHANGE_TYPE),
|
||||
handleGetOptionList: getTypeOptions,
|
||||
handleEdit: handleChangeType,
|
||||
dataCy: 'type',
|
||||
},
|
||||
{
|
||||
name: T.DiskType,
|
||||
value: imageDiskTypeName,
|
||||
valueInOptionList: imageDiskTypeName,
|
||||
dataCy: 'diskType',
|
||||
},
|
||||
{
|
||||
name: T.Persistent,
|
||||
value: booleanToString(+PERSISTENT),
|
||||
valueInOptionList: PERSISTENT,
|
||||
canEdit: actions?.includes?.(IMAGE_ACTIONS.CHANGE_PERS),
|
||||
handleGetOptionList: getPersistentOptions,
|
||||
handleEdit: handleChangePersistent,
|
||||
dataCy: 'persistent',
|
||||
},
|
||||
{
|
||||
name: T.Size,
|
||||
value: prettyBytes(SIZE, 'MB'),
|
||||
dataCy: 'size',
|
||||
},
|
||||
{
|
||||
name: T.State,
|
||||
value: <StatusChip text={stateName} stateColor={stateColor} />,
|
||||
dataCy: 'state',
|
||||
},
|
||||
{
|
||||
name: T.RunningVMs,
|
||||
value: `${[VMS?.ID ?? []].flat().length || 0}`,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
<List
|
||||
title={T.Information}
|
||||
list={info}
|
||||
containerProps={{ sx: { gridRow: 'span 3' } }}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
InformationPanel.propTypes = {
|
||||
image: PropTypes.object,
|
||||
actions: PropTypes.arrayOf(PropTypes.string),
|
||||
}
|
||||
|
||||
InformationPanel.displayName = 'InformationPanel'
|
||||
|
||||
export default InformationPanel
|
59
src/fireedge/src/client/components/Tabs/Backup/Vms/index.js
Normal file
59
src/fireedge/src/client/components/Tabs/Backup/Vms/index.js
Normal file
@ -0,0 +1,59 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
|
||||
* *
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
|
||||
* not use this file except in compliance with the License. You may obtain *
|
||||
* a copy of the License at *
|
||||
* *
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 *
|
||||
* *
|
||||
* Unless required by applicable law or agreed to in writing, software *
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, *
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { ReactElement } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { T } from 'client/constants'
|
||||
import EmptyTab from 'client/components/Tabs/EmptyTab'
|
||||
import { useHistory, generatePath } from 'react-router-dom'
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
import { useGetImageQuery } from 'client/features/OneApi/image'
|
||||
import { VmsTable } from 'client/components/Tables'
|
||||
|
||||
/**
|
||||
* Renders mainly Vms tab.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {string} props.id - Image id
|
||||
* @returns {ReactElement} vms tab
|
||||
*/
|
||||
const VmsTab = ({ id }) => {
|
||||
const { data: image = {} } = useGetImageQuery({ id })
|
||||
const path = PATH.INSTANCE.VMS.DETAIL
|
||||
const history = useHistory()
|
||||
|
||||
const handleRowClick = (rowId) => {
|
||||
history.push(generatePath(path, { id: String(rowId) }))
|
||||
}
|
||||
|
||||
return (
|
||||
<VmsTable
|
||||
disableGlobalSort
|
||||
displaySelectedRows
|
||||
host={image}
|
||||
onRowClick={(row) => handleRowClick(row.ID)}
|
||||
noDataMessage={<EmptyTab label={T.NotVmsCurrentyImage} />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
VmsTab.propTypes = {
|
||||
tabProps: PropTypes.object,
|
||||
id: PropTypes.string,
|
||||
}
|
||||
|
||||
VmsTab.displayName = 'VmsTab'
|
||||
|
||||
export default VmsTab
|
64
src/fireedge/src/client/components/Tabs/Backup/index.js
Normal file
64
src/fireedge/src/client/components/Tabs/Backup/index.js
Normal file
@ -0,0 +1,64 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
|
||||
* *
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
|
||||
* not use this file except in compliance with the License. You may obtain *
|
||||
* a copy of the License at *
|
||||
* *
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 *
|
||||
* *
|
||||
* Unless required by applicable law or agreed to in writing, software *
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, *
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo, useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Alert, LinearProgress } from '@mui/material'
|
||||
|
||||
import { useViews } from 'client/features/Auth'
|
||||
import { useGetImageQuery } from 'client/features/OneApi/image'
|
||||
import { getAvailableInfoTabs } from 'client/models/Helper'
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
import Tabs from 'client/components/Tabs'
|
||||
import Info from 'client/components/Tabs/Backup/Info'
|
||||
import Vms from 'client/components/Tabs/Backup/Vms'
|
||||
|
||||
const getTabComponent = (tabName) =>
|
||||
({
|
||||
info: Info,
|
||||
vms: Vms,
|
||||
}[tabName])
|
||||
|
||||
const BackupTabs = memo(({ id }) => {
|
||||
const { view, getResourceView } = useViews()
|
||||
const { isLoading, isError, error } = useGetImageQuery({ id })
|
||||
|
||||
const tabsAvailable = useMemo(() => {
|
||||
const resource = RESOURCE_NAMES.IMAGE
|
||||
const infoTabs = getResourceView(resource)?.['info-tabs'] ?? {}
|
||||
|
||||
return getAvailableInfoTabs(infoTabs, getTabComponent, id)
|
||||
}, [view])
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<Alert severity="error" variant="outlined">
|
||||
{error.data}
|
||||
</Alert>
|
||||
)
|
||||
}
|
||||
|
||||
return isLoading ? (
|
||||
<LinearProgress color="secondary" sx={{ width: '100%' }} />
|
||||
) : (
|
||||
<Tabs addBorder tabs={tabsAvailable ?? []} />
|
||||
)
|
||||
})
|
||||
|
||||
BackupTabs.propTypes = { id: PropTypes.string.isRequired }
|
||||
BackupTabs.displayName = 'BackupTabs'
|
||||
|
||||
export default BackupTabs
|
@ -25,7 +25,7 @@ import {
|
||||
import { StatusChip } from 'client/components/Status'
|
||||
import { List } from 'client/components/Tabs/Common'
|
||||
|
||||
import { getType, getState } from 'client/models/Image'
|
||||
import { getDiskType, getType, getState } from 'client/models/Image'
|
||||
import {
|
||||
timeToString,
|
||||
booleanToString,
|
||||
@ -62,6 +62,7 @@ const InformationPanel = ({ image = {}, actions }) => {
|
||||
|
||||
const { color: stateColor, name: stateName } = getState(image)
|
||||
const imageTypeName = getType(image)
|
||||
const imageDiskTypeName = getDiskType(image)
|
||||
|
||||
const handleRename = async (_, newName) => {
|
||||
await rename({ id: ID, name: newName })
|
||||
@ -115,6 +116,12 @@ const InformationPanel = ({ image = {}, actions }) => {
|
||||
handleEdit: handleChangeType,
|
||||
dataCy: 'type',
|
||||
},
|
||||
{
|
||||
name: T.DiskType,
|
||||
value: imageDiskTypeName,
|
||||
valueInOptionList: imageDiskTypeName,
|
||||
dataCy: 'diskType',
|
||||
},
|
||||
{
|
||||
name: T.Locked,
|
||||
value: levelLockToString(LOCK?.LOCKED),
|
||||
|
@ -25,7 +25,7 @@ import {
|
||||
import { StatusChip } from 'client/components/Status'
|
||||
import { List } from 'client/components/Tabs/Common'
|
||||
|
||||
import { getType, getState } from 'client/models/Image'
|
||||
import { getDiskType, getType, getState } from 'client/models/Image'
|
||||
import {
|
||||
timeToString,
|
||||
booleanToString,
|
||||
@ -62,6 +62,7 @@ const InformationPanel = ({ image = {}, actions }) => {
|
||||
|
||||
const { color: stateColor, name: stateName } = getState(image)
|
||||
const imageTypeName = getType(image)
|
||||
const imageDiskTypeName = getDiskType(image)
|
||||
|
||||
const handleRename = async (_, newName) => {
|
||||
await rename({ id: ID, name: newName })
|
||||
@ -115,6 +116,12 @@ const InformationPanel = ({ image = {}, actions }) => {
|
||||
handleEdit: handleChangeType,
|
||||
dataCy: 'type',
|
||||
},
|
||||
{
|
||||
name: T.DiskType,
|
||||
value: imageDiskTypeName,
|
||||
valueInOptionList: imageDiskTypeName,
|
||||
dataCy: 'diskType',
|
||||
},
|
||||
{
|
||||
name: T.Locked,
|
||||
value: levelLockToString(LOCK?.LOCKED),
|
||||
|
41
src/fireedge/src/client/components/Tabs/Vm/Backup.js
Normal file
41
src/fireedge/src/client/components/Tabs/Vm/Backup.js
Normal file
@ -0,0 +1,41 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
|
||||
* *
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
|
||||
* not use this file except in compliance with the License. You may obtain *
|
||||
* a copy of the License at *
|
||||
* *
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 *
|
||||
* *
|
||||
* Unless required by applicable law or agreed to in writing, software *
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, *
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { ReactElement } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useGetVmQuery } from 'client/features/OneApi/vm'
|
||||
import { BackupsTable } from 'client/components/Tables'
|
||||
|
||||
/**
|
||||
* Renders the list of backups from a VM.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {string} props.id - Virtual Machine id
|
||||
* @returns {ReactElement} Backups tab
|
||||
*/
|
||||
const VmBackupTab = ({ id }) => {
|
||||
const { data: vm = {} } = useGetVmQuery({ id })
|
||||
|
||||
return <BackupsTable disableRowSelect disableGlobalSort vm={vm} />
|
||||
}
|
||||
|
||||
VmBackupTab.propTypes = {
|
||||
tabProps: PropTypes.object,
|
||||
id: PropTypes.string,
|
||||
}
|
||||
|
||||
VmBackupTab.displayName = 'VmBackupTab'
|
||||
|
||||
export default VmBackupTab
|
@ -28,6 +28,7 @@ import Network from 'client/components/Tabs/Vm/Network'
|
||||
import History from 'client/components/Tabs/Vm/History'
|
||||
import SchedActions from 'client/components/Tabs/Vm/SchedActions'
|
||||
import Snapshot from 'client/components/Tabs/Vm/Snapshot'
|
||||
import Backup from 'client/components/Tabs/Vm/Backup'
|
||||
import Storage from 'client/components/Tabs/Vm/Storage'
|
||||
import Configuration from 'client/components/Tabs/Vm/Configuration'
|
||||
import Template from 'client/components/Tabs/Vm/Template'
|
||||
@ -39,6 +40,7 @@ const getTabComponent = (tabName) =>
|
||||
history: History,
|
||||
sched_actions: SchedActions,
|
||||
snapshot: Snapshot,
|
||||
backup: Backup,
|
||||
storage: Storage,
|
||||
configuration: Configuration,
|
||||
template: Template,
|
||||
|
@ -54,7 +54,7 @@ import { Permissions } from 'client/constants/common'
|
||||
*/
|
||||
|
||||
/** @type {string[]} Datastore type information */
|
||||
export const DATASTORE_TYPES = ['IMAGE', 'SYSTEM', 'FILE']
|
||||
export const DATASTORE_TYPES = ['IMAGE', 'SYSTEM', 'FILE', 'BACKUP']
|
||||
|
||||
/** @type {STATES.StateInfo[]} Datastore states */
|
||||
export const DATASTORE_STATES = [
|
||||
|
@ -63,6 +63,7 @@ export const IMAGE_TYPES_STR = {
|
||||
KERNEL: 'KERNEL',
|
||||
RAMDISK: 'RAMDISK',
|
||||
CONTEXT: 'CONTEXT',
|
||||
BACKUP: 'BACKUP',
|
||||
}
|
||||
|
||||
/** @type {IMAGE_TYPES_STR[]} Return the string representation of an Image type */
|
||||
@ -73,6 +74,7 @@ export const IMAGE_TYPES = [
|
||||
IMAGE_TYPES_STR.KERNEL,
|
||||
IMAGE_TYPES_STR.RAMDISK,
|
||||
IMAGE_TYPES_STR.CONTEXT,
|
||||
IMAGE_TYPES_STR.BACKUP,
|
||||
]
|
||||
|
||||
/** @type {IMAGE_TYPES_STR[]} Return the string representation of an Image type for tab files */
|
||||
@ -89,6 +91,9 @@ export const IMAGE_TYPES_FOR_IMAGES = [
|
||||
IMAGE_TYPES_STR.DATABLOCK,
|
||||
]
|
||||
|
||||
/** @type {IMAGE_TYPES_STR[]} Return the string representation of an Image type for tab files */
|
||||
export const IMAGE_TYPES_FOR_BACKUPS = [IMAGE_TYPES_STR.BACKUP]
|
||||
|
||||
/** @enum {string} Disk type */
|
||||
export const DISK_TYPES_STR = {
|
||||
FILE: 'FILE',
|
||||
@ -186,6 +191,7 @@ export const IMAGE_ACTIONS = {
|
||||
DISABLE: 'disable',
|
||||
PERSISTENT: 'persistent',
|
||||
NON_PERSISTENT: 'nonpersistent',
|
||||
RESTORE: 'restore',
|
||||
|
||||
// INFORMATION
|
||||
RENAME: ACTIONS.RENAME,
|
||||
|
@ -150,6 +150,7 @@ export const SOCKETS = {
|
||||
/** @enum {string} Names of resource */
|
||||
export const RESOURCE_NAMES = {
|
||||
APP: 'marketplace-app',
|
||||
BACKUP: 'backup',
|
||||
CLUSTER: 'cluster',
|
||||
DATASTORE: 'datastore',
|
||||
GROUP: 'group',
|
||||
|
@ -66,6 +66,7 @@ export const ARGS_TYPES = {
|
||||
DISK_ID: 'DISK_ID',
|
||||
NAME: 'NAME',
|
||||
SNAPSHOT_ID: 'SNAPSHOT_ID',
|
||||
DS_ID: 'DS_ID',
|
||||
}
|
||||
|
||||
/** @enum {string} Period type */
|
||||
|
@ -137,3 +137,5 @@ export const UNKNOWN = 'UNKNOWN'
|
||||
export const USED = 'USED'
|
||||
export const USED_PERS = 'USED_PERS'
|
||||
export const WARNING = 'WARNING'
|
||||
export const BACKUP = 'BACKUP'
|
||||
export const BACKUP_POWEROFF = 'BACKUP_POWEROFF'
|
||||
|
@ -50,6 +50,7 @@ module.exports = {
|
||||
AttachNic: 'Attach NIC',
|
||||
AttachVolatile: 'Attach volatile disk',
|
||||
BackToList: 'Back to %s list',
|
||||
Backup: 'Backup',
|
||||
Cancel: 'Cancel',
|
||||
Change: 'Change',
|
||||
ChangeGroup: 'Change group',
|
||||
@ -139,6 +140,7 @@ module.exports = {
|
||||
ResizeSomething: 'Resize: %s',
|
||||
Resume: 'Resume',
|
||||
Retry: 'Retry',
|
||||
Restore: 'Restore',
|
||||
Revert: 'Revert',
|
||||
RevertSomething: 'Revert: %s',
|
||||
Save: 'Save',
|
||||
@ -360,6 +362,9 @@ module.exports = {
|
||||
SecurityGroups: 'Security groups',
|
||||
|
||||
/* sections - storage */
|
||||
Backups: 'Backups',
|
||||
BackupDatastore: 'Backup Datastore',
|
||||
BackupRestored: 'Backup restored',
|
||||
Datastore: 'Datastore',
|
||||
Datastores: 'Datastores',
|
||||
Image: 'Image',
|
||||
@ -385,6 +390,9 @@ module.exports = {
|
||||
Fs: 'Fs',
|
||||
CustomFormat: 'Custom Format',
|
||||
Dockerfile: 'Dockerfile',
|
||||
Running: 'Running',
|
||||
DoNotRestoreNICAttributes: 'Do not restore NIC attributes',
|
||||
DoNotRestoreIPAttributes: 'Do not restore IP attributes',
|
||||
|
||||
/* sections - templates & instances */
|
||||
Instances: 'Instances',
|
||||
@ -1079,7 +1087,7 @@ module.exports = {
|
||||
BasePath: 'Base path',
|
||||
FileSystemType: 'Filesystem type',
|
||||
Persistent: 'Persistent',
|
||||
NonPersistyent: 'Non Persistent',
|
||||
NonPersistent: 'Non Persistent',
|
||||
RunningVMs: 'Running VMs',
|
||||
/* Disk - general */
|
||||
DiskType: 'Disk type',
|
||||
|
@ -709,10 +709,23 @@ export const VM_LCM_STATES = [
|
||||
color: COLOR.info.main,
|
||||
meaning: '',
|
||||
},
|
||||
{
|
||||
// 69
|
||||
name: STATES.BACKUP,
|
||||
color: COLOR.info.main,
|
||||
meaning: '',
|
||||
},
|
||||
{
|
||||
// 70
|
||||
name: STATES.BACKUP_POWEROFF,
|
||||
color: COLOR.info.main,
|
||||
meaning: '',
|
||||
},
|
||||
]
|
||||
|
||||
/** @enum {string} Virtual machine actions */
|
||||
export const VM_ACTIONS = {
|
||||
BACKUP: 'backup',
|
||||
CREATE_DIALOG: 'create_dialog',
|
||||
CREATE_APP_DIALOG: 'create_app_dialog',
|
||||
DEPLOY: 'deploy',
|
||||
@ -791,6 +804,7 @@ export const VM_ACTIONS = {
|
||||
|
||||
/** @enum {string} Virtual machine actions by state */
|
||||
export const VM_ACTIONS_BY_STATE = {
|
||||
[VM_ACTIONS.BACKUP]: [STATES.POWEROFF, STATES.RUNNING],
|
||||
[VM_ACTIONS.DEPLOY]: [
|
||||
STATES.PENDING,
|
||||
STATES.HOLD,
|
||||
@ -980,6 +994,7 @@ export const HYPERVISORS = {
|
||||
|
||||
/** @type {string[]} Actions that can be scheduled */
|
||||
export const VM_ACTIONS_WITH_SCHEDULE = [
|
||||
VM_ACTIONS.BACKUP,
|
||||
VM_ACTIONS.TERMINATE,
|
||||
VM_ACTIONS.TERMINATE_HARD,
|
||||
VM_ACTIONS.UNDEPLOY,
|
||||
@ -1069,7 +1084,8 @@ export const VM_ACTIONS_IN_CHARTER = [
|
||||
* 'alias-attach' |
|
||||
* 'alias-detach' |
|
||||
* 'poweroff-migrate' |
|
||||
* 'poweroff-hard-migrate'
|
||||
* 'poweroff-hard-migrate' |
|
||||
* 'backup'
|
||||
* )} History actions
|
||||
*/
|
||||
export const HISTORY_ACTIONS = [
|
||||
@ -1123,6 +1139,7 @@ export const HISTORY_ACTIONS = [
|
||||
'alias-detach',
|
||||
'poweroff-migrate',
|
||||
'poweroff-hard-migrate',
|
||||
'backup',
|
||||
]
|
||||
|
||||
/**
|
||||
|
156
src/fireedge/src/client/containers/Backups/index.js
Normal file
156
src/fireedge/src/client/containers/Backups/index.js
Normal file
@ -0,0 +1,156 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 GotoIcon from 'iconoir-react/dist/Pin'
|
||||
import RefreshDouble from 'iconoir-react/dist/RefreshDouble'
|
||||
import Cancel from 'iconoir-react/dist/Cancel'
|
||||
import { Typography, Box, Stack, Chip } from '@mui/material'
|
||||
import { Row } from 'react-table'
|
||||
|
||||
import { useLazyGetImageQuery } from 'client/features/OneApi/image'
|
||||
import { BackupsTable } from 'client/components/Tables'
|
||||
import BackupActions from 'client/components/Tables/Backups/actions'
|
||||
import BackupTabs from 'client/components/Tabs/Backup'
|
||||
import SplitPane from 'client/components/SplitPane'
|
||||
import MultipleTags from 'client/components/MultipleTags'
|
||||
import { SubmitButton } from 'client/components/FormControl'
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import { T, Image } from 'client/constants'
|
||||
|
||||
/**
|
||||
* Displays a list of Backups with a split pane between the list and selected row(s).
|
||||
*
|
||||
* @returns {ReactElement} Backups list and selected row(s)
|
||||
*/
|
||||
function Backups() {
|
||||
const [selectedRows, onSelectedRowsChange] = useState(() => [])
|
||||
const actions = BackupActions()
|
||||
|
||||
const hasSelectedRows = selectedRows?.length > 0
|
||||
const moreThanOneSelected = selectedRows?.length > 1
|
||||
|
||||
return (
|
||||
<SplitPane gridTemplateRows="1fr auto 1fr">
|
||||
{({ getGridProps, GutterComponent }) => (
|
||||
<Box height={1} {...(hasSelectedRows && getGridProps())}>
|
||||
<BackupsTable
|
||||
onSelectedRowsChange={onSelectedRowsChange}
|
||||
globalActions={actions}
|
||||
/>
|
||||
|
||||
{hasSelectedRows && (
|
||||
<>
|
||||
<GutterComponent direction="row" track={1} />
|
||||
{moreThanOneSelected ? (
|
||||
<GroupedTags tags={selectedRows} />
|
||||
) : (
|
||||
<InfoTabs
|
||||
image={selectedRows[0]?.original}
|
||||
gotoPage={selectedRows[0]?.gotoPage}
|
||||
unselect={() => selectedRows[0]?.toggleRowSelected(false)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</SplitPane>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays details of an Image.
|
||||
*
|
||||
* @param {Image} image - Image to display
|
||||
* @param {Function} [gotoPage] - Function to navigate to a page of an Image
|
||||
* @param {Function} [unselect] - Function to unselect a Image
|
||||
* @returns {ReactElement} Image details
|
||||
*/
|
||||
const InfoTabs = memo(({ image, gotoPage, unselect }) => {
|
||||
const [getImage, { data: lazyData, isFetching }] = useLazyGetImageQuery()
|
||||
const id = lazyData?.ID ?? image.ID
|
||||
const name = lazyData?.NAME ?? image.NAME
|
||||
|
||||
return (
|
||||
<Stack overflow="auto">
|
||||
<Stack direction="row" alignItems="center" gap={1} mx={1} mb={1}>
|
||||
<Typography color="text.primary" noWrap flexGrow={1}>
|
||||
{`#${id} | ${name}`}
|
||||
</Typography>
|
||||
<SubmitButton
|
||||
data-cy="detail-refresh"
|
||||
icon={<RefreshDouble />}
|
||||
tooltip={Tr(T.Refresh)}
|
||||
isSubmitting={isFetching}
|
||||
onClick={() => getImage({ id })}
|
||||
/>
|
||||
{typeof gotoPage === 'function' && (
|
||||
<SubmitButton
|
||||
data-cy="locate-on-table"
|
||||
icon={<GotoIcon />}
|
||||
tooltip={Tr(T.LocateOnTable)}
|
||||
onClick={() => gotoPage()}
|
||||
/>
|
||||
)}
|
||||
{typeof unselect === 'function' && (
|
||||
<SubmitButton
|
||||
data-cy="unselect"
|
||||
icon={<Cancel />}
|
||||
tooltip={Tr(T.Close)}
|
||||
onClick={() => unselect()}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
<BackupTabs id={image.ID} />
|
||||
</Stack>
|
||||
)
|
||||
})
|
||||
|
||||
InfoTabs.propTypes = {
|
||||
image: PropTypes.object.isRequired,
|
||||
gotoPage: PropTypes.func,
|
||||
unselect: PropTypes.func,
|
||||
}
|
||||
|
||||
InfoTabs.displayName = 'InfoTabs'
|
||||
|
||||
/**
|
||||
* Displays a list of tags that represent the selected rows.
|
||||
*
|
||||
* @param {Row[]} tags - Row(s) to display as tags
|
||||
* @returns {ReactElement} List of tags
|
||||
*/
|
||||
const GroupedTags = memo(({ tags = [] }) => (
|
||||
<Stack direction="row" flexWrap="wrap" gap={1} alignContent="flex-start">
|
||||
<MultipleTags
|
||||
limitTags={10}
|
||||
tags={tags?.map(({ original, id, toggleRowSelected, gotoPage }) => (
|
||||
<Chip
|
||||
key={id}
|
||||
label={original?.NAME ?? id}
|
||||
onClick={gotoPage}
|
||||
onDelete={() => toggleRowSelected(false)}
|
||||
/>
|
||||
))}
|
||||
/>
|
||||
</Stack>
|
||||
))
|
||||
|
||||
GroupedTags.propTypes = { tags: PropTypes.array }
|
||||
GroupedTags.displayName = 'GroupedTags'
|
||||
|
||||
export default Backups
|
@ -19,8 +19,8 @@ import { useHistory } from 'react-router-dom'
|
||||
import { Box, CircularProgress, Grid } from '@mui/material'
|
||||
import {
|
||||
ModernTv as VmsIcons,
|
||||
List as TemplatesIcon,
|
||||
Archive as ImageIcon,
|
||||
EmptyPage as TemplatesIcon,
|
||||
BoxIso as ImageIcon,
|
||||
NetworkAlt as NetworkIcon,
|
||||
} from 'iconoir-react'
|
||||
|
||||
|
@ -28,6 +28,7 @@ import {
|
||||
IMAGE_TYPES_STR,
|
||||
IMAGE_TYPES_FOR_FILES,
|
||||
IMAGE_TYPES_FOR_IMAGES,
|
||||
IMAGE_TYPES_FOR_BACKUPS,
|
||||
} from 'client/constants'
|
||||
import { getType } from 'client/models/Image'
|
||||
|
||||
@ -55,7 +56,9 @@ const imageApi = oneApi.injectEndpoints({
|
||||
},
|
||||
transformResponse: (data) => {
|
||||
const images = data?.IMAGE_POOL?.IMAGE?.filter?.((image) =>
|
||||
IMAGE_TYPES_FOR_IMAGES.some(() => getType(image))
|
||||
IMAGE_TYPES_FOR_IMAGES.some(
|
||||
(imageType) => imageType === getType(image)
|
||||
)
|
||||
)
|
||||
|
||||
return [images ?? []].flat()
|
||||
@ -87,7 +90,43 @@ const imageApi = oneApi.injectEndpoints({
|
||||
},
|
||||
transformResponse: (data) => {
|
||||
const images = data?.IMAGE_POOL?.IMAGE?.filter?.((image) =>
|
||||
IMAGE_TYPES_FOR_FILES.some(() => getType(image))
|
||||
IMAGE_TYPES_FOR_FILES.some(
|
||||
(imageType) => imageType === getType(image)
|
||||
)
|
||||
)
|
||||
|
||||
return [images ?? []].flat()
|
||||
},
|
||||
providesTags: (images) =>
|
||||
images
|
||||
? [
|
||||
...images.map(({ ID }) => ({ type: IMAGE_POOL, id: `${ID}` })),
|
||||
IMAGE_POOL,
|
||||
]
|
||||
: [IMAGE_POOL],
|
||||
}),
|
||||
getBackups: builder.query({
|
||||
/**
|
||||
* Retrieves information for all or part of the images in the pool.
|
||||
*
|
||||
* @param {object} params - Request params
|
||||
* @param {FilterFlag} [params.filter] - Filter flag
|
||||
* @param {number} [params.start] - Range start ID
|
||||
* @param {number} [params.end] - Range end ID
|
||||
* @returns {Image[]} List of images
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: (params) => {
|
||||
const name = Actions.IMAGE_POOL_INFO
|
||||
const command = { name, ...Commands[name] }
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
transformResponse: (data) => {
|
||||
const images = data?.IMAGE_POOL?.IMAGE?.filter?.((image) =>
|
||||
IMAGE_TYPES_FOR_BACKUPS.some(
|
||||
(imageType) => imageType === getType(image)
|
||||
)
|
||||
)
|
||||
|
||||
return [images ?? []].flat()
|
||||
@ -452,6 +491,25 @@ const imageApi = oneApi.injectEndpoints({
|
||||
},
|
||||
invalidatesTags: (_, __, { id }) => [{ type: IMAGE, id }, IMAGE_POOL],
|
||||
}),
|
||||
restoreBackup: builder.mutation({
|
||||
/**
|
||||
* Restores an image.
|
||||
*
|
||||
* @param {number|string} params - Request params
|
||||
* @param {string} params.id - Image id
|
||||
* @param {number} params.datastore - New type for the Image
|
||||
* @param {string} params.options - New type for the Image
|
||||
* @returns {number} Image id
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: (params) => {
|
||||
const name = Actions.IMAGE_RESTORE
|
||||
const command = { name, ...Commands[name] }
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
invalidatesTags: (_, __, id) => [{ type: IMAGE, id }],
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
@ -463,6 +521,8 @@ export const {
|
||||
useLazyGetImagesQuery,
|
||||
useGetFilesQuery,
|
||||
useLazyGetFilesQuery,
|
||||
useGetBackupsQuery,
|
||||
useLazyGetBackupsQuery,
|
||||
|
||||
// Mutations
|
||||
useAllocateImageMutation,
|
||||
@ -482,4 +542,5 @@ export const {
|
||||
useLockImageMutation,
|
||||
useUnlockImageMutation,
|
||||
useUploadImageMutation,
|
||||
useRestoreBackupMutation,
|
||||
} = imageApi
|
||||
|
@ -875,6 +875,24 @@ const vmApi = oneApi.injectEndpoints({
|
||||
},
|
||||
invalidatesTags: (_, __, { id }) => [{ type: VM, id }],
|
||||
}),
|
||||
backup: builder.mutation({
|
||||
/**
|
||||
* Backup the VM.
|
||||
*
|
||||
* @param {object} params - Request parameters
|
||||
* @param {string} params.id - Virtual machine id
|
||||
* @param {number} params.dsId - Backup Datastore id
|
||||
* @returns {number} Virtual machine id
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: (params) => {
|
||||
const name = Actions.VM_BACKUP
|
||||
const command = { name, ...Commands[name] }
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
invalidatesTags: (_, __, { id }) => [{ type: VM, id }],
|
||||
}),
|
||||
lockVm: builder.mutation({
|
||||
/**
|
||||
* Locks a Virtual Machine. Lock certain actions depending on blocking level.
|
||||
@ -1064,6 +1082,7 @@ export const {
|
||||
useUpdateUserTemplateMutation,
|
||||
useUpdateConfigurationMutation,
|
||||
useRecoverMutation,
|
||||
useBackupMutation,
|
||||
useLockVmMutation,
|
||||
useUnlockVmMutation,
|
||||
useAddScheduledActionMutation,
|
||||
|
@ -26,6 +26,7 @@ import {
|
||||
} from 'client/constants'
|
||||
|
||||
const {
|
||||
BACKUP,
|
||||
SNAPSHOT_DISK_CREATE,
|
||||
SNAPSHOT_DISK_REVERT,
|
||||
SNAPSHOT_DISK_DELETE,
|
||||
@ -136,10 +137,11 @@ export const getRepeatInformation = (action) => {
|
||||
* @returns {ARGS_TYPES[]} Arguments
|
||||
*/
|
||||
export const getRequiredArgsByAction = (action) => {
|
||||
const { DISK_ID, NAME, SNAPSHOT_ID } = ARGS_TYPES
|
||||
const { DISK_ID, NAME, SNAPSHOT_ID, DS_ID } = ARGS_TYPES
|
||||
|
||||
return (
|
||||
{
|
||||
[BACKUP]: [DS_ID],
|
||||
[SNAPSHOT_DISK_CREATE]: [DISK_ID, NAME],
|
||||
[SNAPSHOT_DISK_REVERT]: [DISK_ID, SNAPSHOT_ID],
|
||||
[SNAPSHOT_DISK_DELETE]: [DISK_ID, SNAPSHOT_ID],
|
||||
@ -159,12 +161,13 @@ export const getRequiredArgsByAction = (action) => {
|
||||
export const transformStringToArgsObject = ({ ACTION, ARGS = {} } = {}) => {
|
||||
if (typeof ARGS !== 'string') return ARGS
|
||||
|
||||
// IMPORTANT - String data from ARGS has strict order: DISK_ID, NAME, SNAPSHOT_ID
|
||||
// IMPORTANT - String data from ARGS has strict order: DISK_ID, NAME, SNAPSHOT_ID, DS_ID
|
||||
const [arg1, arg2] = ARGS.split(',')
|
||||
const { DISK_ID, NAME, SNAPSHOT_ID } = ARGS_TYPES
|
||||
const { DISK_ID, NAME, SNAPSHOT_ID, DS_ID } = ARGS_TYPES
|
||||
|
||||
return (
|
||||
{
|
||||
[BACKUP]: { [DS_ID]: arg1 },
|
||||
[SNAPSHOT_DISK_CREATE]: { [DISK_ID]: arg1, [NAME]: arg2 },
|
||||
[SNAPSHOT_DISK_REVERT]: { [DISK_ID]: arg1, [SNAPSHOT_ID]: arg2 },
|
||||
[SNAPSHOT_DISK_DELETE]: { [DISK_ID]: arg1, [SNAPSHOT_ID]: arg2 },
|
||||
|
@ -35,6 +35,7 @@ const IMAGE_SNAPFLAT = 'image.snapshotflatten'
|
||||
const IMAGE_INFO = 'image.info'
|
||||
const IMAGE_LOCK = 'image.lock'
|
||||
const IMAGE_UNLOCK = 'image.unlock'
|
||||
const IMAGE_RESTORE = 'image.restore'
|
||||
const IMAGE_POOL_INFO = 'imagepool.info'
|
||||
|
||||
const Actions = {
|
||||
@ -54,6 +55,7 @@ const Actions = {
|
||||
IMAGE_INFO,
|
||||
IMAGE_LOCK,
|
||||
IMAGE_UNLOCK,
|
||||
IMAGE_RESTORE,
|
||||
IMAGE_POOL_INFO,
|
||||
}
|
||||
|
||||
@ -324,6 +326,24 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
},
|
||||
[IMAGE_RESTORE]: {
|
||||
// inspected
|
||||
httpMethod: POST,
|
||||
params: {
|
||||
id: {
|
||||
from: resource,
|
||||
default: 0,
|
||||
},
|
||||
datastore: {
|
||||
from: postBody,
|
||||
default: -1,
|
||||
},
|
||||
options: {
|
||||
from: postBody,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
[IMAGE_POOL_INFO]: {
|
||||
// inspected
|
||||
httpMethod: GET,
|
||||
|
@ -52,6 +52,7 @@ const VM_SEC_GROUP_DETACH = 'vm.detachsg'
|
||||
const VM_SCHED_ADD = 'vm.schedadd'
|
||||
const VM_SCHED_UPDATE = 'vm.schedupdate'
|
||||
const VM_SCHED_DELETE = 'vm.scheddelete'
|
||||
const VM_BACKUP = 'vm.backup'
|
||||
const VM_POOL_INFO = 'vmpool.info'
|
||||
const VM_POOL_INFO_EXTENDED = 'vmpool.infoextended'
|
||||
const VM_POOL_MONITORING = 'vmpool.monitoring'
|
||||
@ -93,6 +94,7 @@ const Actions = {
|
||||
VM_SCHED_ADD,
|
||||
VM_SCHED_UPDATE,
|
||||
VM_SCHED_DELETE,
|
||||
VM_BACKUP,
|
||||
VM_POOL_INFO,
|
||||
VM_POOL_INFO_EXTENDED,
|
||||
VM_POOL_MONITORING,
|
||||
@ -675,6 +677,19 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
},
|
||||
[VM_BACKUP]: {
|
||||
httpMethod: POST,
|
||||
params: {
|
||||
id: {
|
||||
from: resource,
|
||||
default: 0,
|
||||
},
|
||||
dsId: {
|
||||
from: postBody,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
[VM_POOL_INFO]: {
|
||||
// inspected
|
||||
httpMethod: GET,
|
||||
|
Loading…
x
Reference in New Issue
Block a user