mirror of
https://github.com/OpenNebula/one.git
synced 2025-01-06 13:17:42 +03:00
F OpenNebula/one#6053: Add VM restore action to FireEdge (#3113)
Signed-off-by: Victor Hansson <vhansson@opennebula.io>
This commit is contained in:
parent
02e993a456
commit
b1c92d811e
@ -40,6 +40,7 @@ actions:
|
|||||||
rdp: true
|
rdp: true
|
||||||
edit_labels: true
|
edit_labels: true
|
||||||
backup: true
|
backup: true
|
||||||
|
restore: true
|
||||||
|
|
||||||
# Filters - List of criteria to filter the resources
|
# Filters - List of criteria to filter the resources
|
||||||
|
|
||||||
|
@ -40,6 +40,7 @@ actions:
|
|||||||
rdp: true
|
rdp: true
|
||||||
edit_labels: false
|
edit_labels: false
|
||||||
backup: true
|
backup: true
|
||||||
|
restore: false
|
||||||
|
|
||||||
# Filters - List of criteria to filter the resources
|
# Filters - List of criteria to filter the resources
|
||||||
|
|
||||||
|
@ -23,12 +23,10 @@ import {
|
|||||||
ReactElement,
|
ReactElement,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import { BaseSchema } from 'yup'
|
import { BaseSchema } from 'yup'
|
||||||
import { useForm, FormProvider, useFormContext } from 'react-hook-form'
|
import { useForm, FormProvider, useFormContext } from 'react-hook-form'
|
||||||
import { yupResolver } from '@hookform/resolvers/yup'
|
import { yupResolver } from '@hookform/resolvers/yup'
|
||||||
import { useMediaQuery } from '@mui/material'
|
import { useMediaQuery } from '@mui/material'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useGeneral,
|
useGeneral,
|
||||||
updateDisabledSteps,
|
updateDisabledSteps,
|
||||||
@ -99,6 +97,7 @@ const DisableStepContext = createContext(() => {})
|
|||||||
* disableStep('step1', true); // This will disable 'step1'
|
* disableStep('step1', true); // This will disable 'step1'
|
||||||
*/
|
*/
|
||||||
export const useDisableStep = () => useContext(DisableStepContext)
|
export const useDisableStep = () => useContext(DisableStepContext)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a form with one or more steps.
|
* Represents a form with one or more steps.
|
||||||
* Finally, it submit the result.
|
* Finally, it submit the result.
|
||||||
@ -123,8 +122,12 @@ const FormStepper = ({
|
|||||||
formState: { errors },
|
formState: { errors },
|
||||||
setError,
|
setError,
|
||||||
} = useFormContext()
|
} = useFormContext()
|
||||||
|
|
||||||
const { setModifiedFields } = useGeneralApi()
|
const { setModifiedFields } = useGeneralApi()
|
||||||
|
const { isLoading } = useGeneral()
|
||||||
|
const [steps, setSteps] = useState(initialSteps)
|
||||||
|
const [disabledSteps, setDisabledSteps] = useState({})
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const currentState = useSelector((state) => state)
|
||||||
|
|
||||||
// Used to control the default visibility of a step
|
// Used to control the default visibility of a step
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -151,29 +154,22 @@ const FormStepper = ({
|
|||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Set the initial state of the steps accessible from redux
|
// Set the initial state of the steps accessible from redux
|
||||||
dispatch(updateDisabledSteps(newState))
|
dispatch(updateDisabledSteps(newState))
|
||||||
setDisabledSteps(newState)
|
setDisabledSteps(newState)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const { isLoading } = useGeneral()
|
|
||||||
const [steps, setSteps] = useState(initialSteps)
|
|
||||||
const [disabledSteps, setDisabledSteps] = useState({})
|
|
||||||
const dispatch = useDispatch()
|
|
||||||
|
|
||||||
const currentState = useSelector((state) => state)
|
|
||||||
|
|
||||||
const disableStep = useCallback((stepIds, shouldDisable) => {
|
const disableStep = useCallback((stepIds, shouldDisable) => {
|
||||||
const ids = Array.isArray(stepIds) ? stepIds : [stepIds]
|
const ids = Array.isArray(stepIds) ? stepIds : [stepIds]
|
||||||
|
|
||||||
setDisabledSteps((prev) => {
|
setDisabledSteps((prev) => {
|
||||||
let newDisabledSteps = { ...prev }
|
let newDisabledSteps = { ...prev }
|
||||||
|
|
||||||
// eslint-disable-next-line no-shadow
|
ids.forEach((sId) => {
|
||||||
ids.forEach((stepId) => {
|
|
||||||
newDisabledSteps = shouldDisable
|
newDisabledSteps = shouldDisable
|
||||||
? { ...newDisabledSteps, [stepId]: true }
|
? { ...newDisabledSteps, [sId]: true }
|
||||||
: (({ [stepId]: _, ...rest }) => rest)(newDisabledSteps)
|
: (({ [sId]: _, ...rest }) => rest)(newDisabledSteps)
|
||||||
})
|
})
|
||||||
|
|
||||||
return newDisabledSteps
|
return newDisabledSteps
|
||||||
@ -300,12 +296,12 @@ const FormStepper = ({
|
|||||||
Number.isInteger(stepToBack) ? stepToBack : prevStep - 1
|
Number.isInteger(stepToBack) ? stepToBack : prevStep - 1
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
[activeStep]
|
[activeStep, steps]
|
||||||
)
|
)
|
||||||
|
|
||||||
const { id: stepId, content: Content } = useMemo(
|
const { id: stepId, content: Content } = useMemo(
|
||||||
() => steps[activeStep] || { id: null, content: null },
|
() => steps[activeStep] || { id: null, content: null },
|
||||||
[formData, activeStep]
|
[steps, activeStep]
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -358,5 +354,4 @@ FormStepper.propTypes = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export { DefaultFormStepper, SkeletonStepsForm }
|
export { DefaultFormStepper, SkeletonStepsForm }
|
||||||
|
|
||||||
export default FormStepper
|
export default FormStepper
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
/* ------------------------------------------------------------------------- *
|
||||||
|
* Copyright 2002-2023, 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 { BackupsTable } from 'client/components/Tables'
|
||||||
|
import { SCHEMA } from 'client/components/Forms/Backup/RestoreForm/Steps/BackupsTable/schema'
|
||||||
|
|
||||||
|
import { Step } from 'client/utils'
|
||||||
|
import { T } from 'client/constants'
|
||||||
|
|
||||||
|
export const STEP_ID = 'image'
|
||||||
|
|
||||||
|
const Content = ({ data, app: { backupIds = [] } = {} }) => {
|
||||||
|
const { ID } = data?.[0] ?? {}
|
||||||
|
|
||||||
|
const { setValue } = useFormContext()
|
||||||
|
|
||||||
|
const handleSelectedRows = (rows) => {
|
||||||
|
const { original = {} } = rows?.[0] ?? {}
|
||||||
|
|
||||||
|
setValue(STEP_ID, original.ID !== undefined ? [original] : [])
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BackupsTable
|
||||||
|
singleSelect
|
||||||
|
disableGlobalSort
|
||||||
|
displaySelectedRows
|
||||||
|
pageSize={5}
|
||||||
|
getRowId={(row) => String(row.ID)}
|
||||||
|
filter={(images) =>
|
||||||
|
images?.filter(({ ID: imgId }) => backupIds?.includes(imgId)) ?? []
|
||||||
|
}
|
||||||
|
initialState={{
|
||||||
|
selectedRowIds: { [ID]: true },
|
||||||
|
}}
|
||||||
|
onSelectedRowsChange={handleSelectedRows}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step to select the Image.
|
||||||
|
*
|
||||||
|
* @param {object} app - Marketplace App resource
|
||||||
|
* @returns {Step} Image step
|
||||||
|
*/
|
||||||
|
const ImageStep = (app) => {
|
||||||
|
const { disableImageSelection } = app
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: STEP_ID,
|
||||||
|
label: T.SelectBackupImage,
|
||||||
|
resolver: SCHEMA,
|
||||||
|
content: (props) => Content({ ...props, app }),
|
||||||
|
defaultDisabled: {
|
||||||
|
condition: () => disableImageSelection,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Content.propTypes = {
|
||||||
|
data: PropTypes.any,
|
||||||
|
setFormData: PropTypes.func,
|
||||||
|
app: PropTypes.object,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ImageStep
|
@ -0,0 +1,24 @@
|
|||||||
|
/* ------------------------------------------------------------------------- *
|
||||||
|
* Copyright 2002-2023, 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, ArraySchema } from 'yup'
|
||||||
|
|
||||||
|
/** @type {ArraySchema} Image backups table schema */
|
||||||
|
export const SCHEMA = array()
|
||||||
|
.min(1)
|
||||||
|
.max(1)
|
||||||
|
.required()
|
||||||
|
.ensure()
|
||||||
|
.default(() => [])
|
@ -18,11 +18,17 @@ import { timeFromMilliseconds } from 'client/models/Helper'
|
|||||||
import { Field, arrayToOptions, getValidationFromFields } from 'client/utils'
|
import { Field, arrayToOptions, getValidationFromFields } from 'client/utils'
|
||||||
import { ObjectSchema, boolean, object, string } from 'yup'
|
import { ObjectSchema, boolean, object, string } from 'yup'
|
||||||
import { STEP_ID as VM_DISK_ID } from 'client/components/Forms/Backup/RestoreForm/Steps/VmDisksTable'
|
import { STEP_ID as VM_DISK_ID } from 'client/components/Forms/Backup/RestoreForm/Steps/VmDisksTable'
|
||||||
|
import { STEP_ID as BACKUP_IMG_ID } from 'client/components/Forms/Backup/RestoreForm/Steps/BackupsTable'
|
||||||
|
|
||||||
const NO_NIC = {
|
const NO_NIC = {
|
||||||
name: 'no_nic',
|
name: 'no_nic',
|
||||||
label: T.DoNotRestoreNICAttributes,
|
label: T.DoNotRestoreNICAttributes,
|
||||||
type: INPUT_TYPES.SWITCH,
|
type: INPUT_TYPES.SWITCH,
|
||||||
|
htmlType: (deps) => {
|
||||||
|
const selectedImage = deps?.[BACKUP_IMG_ID]?.[0]
|
||||||
|
|
||||||
|
return selectedImage ? INPUT_TYPES.HIDDEN : INPUT_TYPES.SWITCH
|
||||||
|
},
|
||||||
validation: boolean().yesOrNo(),
|
validation: boolean().yesOrNo(),
|
||||||
grid: { md: 12 },
|
grid: { md: 12 },
|
||||||
}
|
}
|
||||||
@ -31,6 +37,11 @@ const NO_IP = {
|
|||||||
name: 'no_ip',
|
name: 'no_ip',
|
||||||
label: T.DoNotRestoreIPAttributes,
|
label: T.DoNotRestoreIPAttributes,
|
||||||
type: INPUT_TYPES.SWITCH,
|
type: INPUT_TYPES.SWITCH,
|
||||||
|
htmlType: (deps) => {
|
||||||
|
const selectedImage = deps?.[BACKUP_IMG_ID]?.[0]
|
||||||
|
|
||||||
|
return selectedImage ? INPUT_TYPES.HIDDEN : INPUT_TYPES.SWITCH
|
||||||
|
},
|
||||||
validation: boolean().yesOrNo(),
|
validation: boolean().yesOrNo(),
|
||||||
grid: { md: 12 },
|
grid: { md: 12 },
|
||||||
}
|
}
|
||||||
@ -61,19 +72,37 @@ const INCREMENT_ID = ({ increments = [] }) => ({
|
|||||||
name: 'increment_id',
|
name: 'increment_id',
|
||||||
label: T.IncrementId,
|
label: T.IncrementId,
|
||||||
type: INPUT_TYPES.SELECT,
|
type: INPUT_TYPES.SELECT,
|
||||||
values: arrayToOptions(increments, {
|
values: (deps) => {
|
||||||
addEmpty: true,
|
const selectedImage = deps?.[BACKUP_IMG_ID]?.[0]
|
||||||
getText: (increment) =>
|
let backupIncrements = [].concat(
|
||||||
`${increment.id}: ${timeFromMilliseconds(increment.date)
|
selectedImage?.BACKUP_INCREMENTS?.INCREMENT ?? []
|
||||||
.toFormat('ff')
|
)
|
||||||
.replace(',', '')} (${increment.source})`,
|
|
||||||
getValue: (increment) => increment.id,
|
backupIncrements = backupIncrements.map((increment) => ({
|
||||||
}),
|
id: increment.ID,
|
||||||
|
date: increment.DATE,
|
||||||
|
source: increment.SOURCE,
|
||||||
|
}))
|
||||||
|
|
||||||
|
return arrayToOptions(
|
||||||
|
backupIncrements?.length > 0 ? backupIncrements : increments,
|
||||||
|
{
|
||||||
|
addEmpty: true,
|
||||||
|
getText: (increment) =>
|
||||||
|
`${increment.id}: ${timeFromMilliseconds(increment.date)
|
||||||
|
.toFormat('ff')
|
||||||
|
.replace(',', '')} (${increment.source})`,
|
||||||
|
getValue: (increment) => increment.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
validation: string(),
|
validation: string(),
|
||||||
grid: { md: 6 },
|
grid: { md: 6 },
|
||||||
fieldProps: {
|
fieldProps: (deps) => ({
|
||||||
disabled: increments.length === 0,
|
disabled:
|
||||||
},
|
deps?.[BACKUP_IMG_ID]?.[0]?.BACKUP_INCRMENETS?.INCREMENT?.length === 0 &&
|
||||||
|
increments.length === 0,
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -63,12 +63,20 @@ const Content = ({ data, app }) => {
|
|||||||
* @param {object} app - Marketplace App resource
|
* @param {object} app - Marketplace App resource
|
||||||
* @returns {Step} Datastore step
|
* @returns {Step} Datastore step
|
||||||
*/
|
*/
|
||||||
const DatastoreStep = (app) => ({
|
const DatastoreStep = (app) => {
|
||||||
id: STEP_ID,
|
const { disableImageSelection } = app
|
||||||
label: T.SelectDatastoreImage,
|
|
||||||
resolver: SCHEMA,
|
return {
|
||||||
content: (props) => Content({ ...props, app }),
|
id: STEP_ID,
|
||||||
})
|
label: T.SelectDatastoreImage,
|
||||||
|
resolver: SCHEMA,
|
||||||
|
content: (props) => Content({ ...props, app }),
|
||||||
|
defaultDisabled: {
|
||||||
|
// Disabled when image selection is enabled, aka when in restore operation
|
||||||
|
condition: () => !disableImageSelection,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Content.propTypes = {
|
Content.propTypes = {
|
||||||
data: PropTypes.any,
|
data: PropTypes.any,
|
||||||
|
@ -49,6 +49,8 @@ const Content = ({ data, app: { backupDiskIds = [], vmsId = [] } = {} }) => {
|
|||||||
selectedRowIds: { [selectedRow]: true },
|
selectedRowIds: { [selectedRow]: true },
|
||||||
}}
|
}}
|
||||||
filter={(disks) =>
|
filter={(disks) =>
|
||||||
|
disks &&
|
||||||
|
disks?.length > 0 &&
|
||||||
disks?.filter((disk) => backupDiskIds?.includes(disk?.DISK_ID))
|
disks?.filter((disk) => backupDiskIds?.includes(disk?.DISK_ID))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -13,6 +13,9 @@
|
|||||||
* See the License for the specific language governing permissions and *
|
* See the License for the specific language governing permissions and *
|
||||||
* limitations under the License. *
|
* limitations under the License. *
|
||||||
* ------------------------------------------------------------------------- */
|
* ------------------------------------------------------------------------- */
|
||||||
|
import BackupsTable, {
|
||||||
|
STEP_ID as BACKUP_IMG_ID,
|
||||||
|
} from 'client/components/Forms/Backup/RestoreForm/Steps/BackupsTable'
|
||||||
import BasicConfiguration, {
|
import BasicConfiguration, {
|
||||||
STEP_ID as BASIC_ID,
|
STEP_ID as BASIC_ID,
|
||||||
} from 'client/components/Forms/Backup/RestoreForm/Steps/BasicConfiguration'
|
} from 'client/components/Forms/Backup/RestoreForm/Steps/BasicConfiguration'
|
||||||
@ -24,37 +27,42 @@ import VmDisksTable, {
|
|||||||
} from 'client/components/Forms/Backup/RestoreForm/Steps/VmDisksTable'
|
} from 'client/components/Forms/Backup/RestoreForm/Steps/VmDisksTable'
|
||||||
import { createSteps } from 'client/utils'
|
import { createSteps } from 'client/utils'
|
||||||
|
|
||||||
const Steps = createSteps([BasicConfiguration, VmDisksTable, DatastoresTable], {
|
const Steps = createSteps(
|
||||||
transformInitialValue: (initialValues, schema) => {
|
[BackupsTable, BasicConfiguration, VmDisksTable, DatastoresTable],
|
||||||
const { increments } = initialValues
|
{
|
||||||
const castedValuesBasic = schema.cast(
|
transformInitialValue: (initialValues, schema) => {
|
||||||
{ [BASIC_ID]: { increments } },
|
const { increments } = initialValues
|
||||||
{ stripUnknown: true }
|
const castedValuesBasic = schema.cast(
|
||||||
)
|
{ [BASIC_ID]: { increments } },
|
||||||
|
{ stripUnknown: true }
|
||||||
|
)
|
||||||
|
|
||||||
const castedValuesDatastore = schema.cast(
|
const castedValuesDatastore = schema.cast(
|
||||||
{ [DATASTORE_ID]: {} },
|
{ [DATASTORE_ID]: {} },
|
||||||
{ stripUnknown: true }
|
{ stripUnknown: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
[BASIC_ID]: castedValuesBasic[BASIC_ID],
|
[BASIC_ID]: castedValuesBasic[BASIC_ID],
|
||||||
[DATASTORE_ID]: castedValuesDatastore[DATASTORE_ID],
|
[DATASTORE_ID]: castedValuesDatastore[DATASTORE_ID],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
transformBeforeSubmit: (formData) => {
|
transformBeforeSubmit: (formData) => {
|
||||||
const {
|
const {
|
||||||
[BASIC_ID]: configuration,
|
[BACKUP_IMG_ID]: backupImgId = [],
|
||||||
[VM_DISK_ID]: individualDisk = [],
|
[BASIC_ID]: configuration,
|
||||||
[DATASTORE_ID]: [datastore] = [],
|
[VM_DISK_ID]: individualDisk = [],
|
||||||
} = formData
|
[DATASTORE_ID]: [datastore] = [],
|
||||||
|
} = formData
|
||||||
|
|
||||||
return {
|
return {
|
||||||
datastore: datastore?.ID,
|
datastore: datastore?.ID,
|
||||||
individualDisk: individualDisk?.[0] ?? [],
|
individualDisk: individualDisk?.[0] ?? [],
|
||||||
...configuration,
|
backupImgId: backupImgId?.[0] ?? [],
|
||||||
}
|
...configuration,
|
||||||
},
|
}
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
export default Steps
|
export default Steps
|
||||||
|
@ -114,7 +114,12 @@ const Actions = () => {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
return RestoreForm({
|
return RestoreForm({
|
||||||
stepProps: { increments, backupDiskIds, vmsId },
|
stepProps: {
|
||||||
|
increments,
|
||||||
|
backupDiskIds,
|
||||||
|
vmsId,
|
||||||
|
disableImageSelection: true,
|
||||||
|
},
|
||||||
initialValues: {
|
initialValues: {
|
||||||
increments: increments,
|
increments: increments,
|
||||||
backupDiskIds: backupDiskIds,
|
backupDiskIds: backupDiskIds,
|
||||||
|
@ -35,6 +35,7 @@ const BackupsTable = (props) => {
|
|||||||
searchProps = {},
|
searchProps = {},
|
||||||
vm,
|
vm,
|
||||||
refetchVm,
|
refetchVm,
|
||||||
|
filter,
|
||||||
isFetchingVm,
|
isFetchingVm,
|
||||||
...rest
|
...rest
|
||||||
} = props ?? {}
|
} = props ?? {}
|
||||||
@ -54,11 +55,13 @@ const BackupsTable = (props) => {
|
|||||||
: [vm?.BACKUPS?.BACKUP_IDS?.ID]
|
: [vm?.BACKUPS?.BACKUP_IDS?.ID]
|
||||||
: []
|
: []
|
||||||
|
|
||||||
|
const backupData = result?.data?.filter((backup) =>
|
||||||
|
vm ? backupsIds?.includes(backup.ID) : true
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...result,
|
...result,
|
||||||
data: result?.data?.filter((backup) =>
|
data: typeof filter === 'function' ? filter(backupData) : backupData,
|
||||||
vm ? backupsIds?.includes(backup.ID) : true
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -76,10 +79,13 @@ const BackupsTable = (props) => {
|
|||||||
* Refetch vms and backups. If a new backup is created, the id of the backup will be in the data of a vm, so we need to refetch also the vms query.
|
* Refetch vms and backups. If a new backup is created, the id of the backup will be in the data of a vm, so we need to refetch also the vms query.
|
||||||
*/
|
*/
|
||||||
const refetchAll = () => {
|
const refetchAll = () => {
|
||||||
refetchVm()
|
refetchVm && refetchVm()
|
||||||
refetch()
|
refetch()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isFetchingAll = () =>
|
||||||
|
isFetchingVm ? !!(isFetchingVm && isFetching) : isFetching
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EnhancedTable
|
<EnhancedTable
|
||||||
columns={columns}
|
columns={columns}
|
||||||
@ -87,7 +93,7 @@ const BackupsTable = (props) => {
|
|||||||
rootProps={rootProps}
|
rootProps={rootProps}
|
||||||
searchProps={searchProps}
|
searchProps={searchProps}
|
||||||
refetch={refetchAll}
|
refetch={refetchAll}
|
||||||
isLoading={isFetching && isFetchingVm}
|
isLoading={isFetchingAll()}
|
||||||
getRowId={(row) => String(row.ID)}
|
getRowId={(row) => String(row.ID)}
|
||||||
RowComponent={BackupRow}
|
RowComponent={BackupRow}
|
||||||
{...rest}
|
{...rest}
|
||||||
|
@ -41,7 +41,7 @@ const VmDisksTable = (props) => {
|
|||||||
const { data, isFetching, refetch } = useGetVmQuery({ id: vmId })
|
const { data, isFetching, refetch } = useGetVmQuery({ id: vmId })
|
||||||
|
|
||||||
const disks =
|
const disks =
|
||||||
typeof filter === 'function'
|
typeof filter === 'function' && Array.isArray(data?.TEMPLATE?.DISK)
|
||||||
? filter(data?.TEMPLATE?.DISK ?? [])
|
? filter(data?.TEMPLATE?.DISK ?? [])
|
||||||
: data?.TEMPLATE?.DISK ?? []
|
: data?.TEMPLATE?.DISK ?? []
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ import { useGetDatastoresQuery } from 'client/features/OneApi/datastore'
|
|||||||
import {
|
import {
|
||||||
useActionVmMutation,
|
useActionVmMutation,
|
||||||
useBackupMutation,
|
useBackupMutation,
|
||||||
|
useRestoreMutation,
|
||||||
useChangeVmOwnershipMutation,
|
useChangeVmOwnershipMutation,
|
||||||
useDeployMutation,
|
useDeployMutation,
|
||||||
useLockVmMutation,
|
useLockVmMutation,
|
||||||
@ -52,6 +53,7 @@ import {
|
|||||||
RecoverForm,
|
RecoverForm,
|
||||||
SaveAsTemplateForm,
|
SaveAsTemplateForm,
|
||||||
} from 'client/components/Forms/Vm'
|
} from 'client/components/Forms/Vm'
|
||||||
|
import { RestoreForm } from 'client/components/Forms/Backup'
|
||||||
import {
|
import {
|
||||||
GlobalAction,
|
GlobalAction,
|
||||||
createActions,
|
createActions,
|
||||||
@ -124,6 +126,7 @@ const Actions = () => {
|
|||||||
const [actionVm] = useActionVmMutation()
|
const [actionVm] = useActionVmMutation()
|
||||||
const [recover] = useRecoverMutation()
|
const [recover] = useRecoverMutation()
|
||||||
const [backup] = useBackupMutation()
|
const [backup] = useBackupMutation()
|
||||||
|
const [restore] = useRestoreMutation()
|
||||||
const [changeOwnership] = useChangeVmOwnershipMutation()
|
const [changeOwnership] = useChangeVmOwnershipMutation()
|
||||||
const [deploy] = useDeployMutation()
|
const [deploy] = useDeployMutation()
|
||||||
const [migrate] = useMigrateMutation()
|
const [migrate] = useMigrateMutation()
|
||||||
@ -522,6 +525,41 @@ const Actions = () => {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
accessor: VM_ACTIONS.RESTORE,
|
||||||
|
disabled: isDisabled(VM_ACTIONS.RESTORE),
|
||||||
|
name: T.Restore,
|
||||||
|
selected: { max: 1 },
|
||||||
|
dialogProps: {
|
||||||
|
title: T.RestoreVm,
|
||||||
|
subheader: SubHeader,
|
||||||
|
},
|
||||||
|
form: (row) => {
|
||||||
|
const vm = row?.[0]?.original
|
||||||
|
const vmId = vm?.ID
|
||||||
|
const backupIds = [].concat(vm?.BACKUPS?.BACKUP_IDS?.ID ?? [])
|
||||||
|
|
||||||
|
return RestoreForm({
|
||||||
|
stepProps: {
|
||||||
|
disableImageSelection: false,
|
||||||
|
vmsId: [vmId],
|
||||||
|
backupIds,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onSubmit: (rows) => async (formData) => {
|
||||||
|
const vmId = rows?.[0]?.id
|
||||||
|
const imageId = formData?.backupImgId?.ID
|
||||||
|
const incrementId = formData?.increment_id
|
||||||
|
const diskId = formData?.individualDisk
|
||||||
|
await restore({
|
||||||
|
id: vmId,
|
||||||
|
imageId,
|
||||||
|
incrementId,
|
||||||
|
diskId,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -123,6 +123,7 @@ export const PROLOG_RESUME_FAILURE = 'PROLOG_RESUME_FAILURE'
|
|||||||
export const PROLOG_UNDEPLOY = 'PROLOG_UNDEPLOY'
|
export const PROLOG_UNDEPLOY = 'PROLOG_UNDEPLOY'
|
||||||
export const PROLOG_UNDEPLOY_FAILURE = 'PROLOG_UNDEPLOY_FAILURE'
|
export const PROLOG_UNDEPLOY_FAILURE = 'PROLOG_UNDEPLOY_FAILURE'
|
||||||
export const READY = 'READY'
|
export const READY = 'READY'
|
||||||
|
export const RESTORE = 'RESTORE'
|
||||||
export const RUNNING = 'RUNNING'
|
export const RUNNING = 'RUNNING'
|
||||||
export const SAVE_MIGRATE = 'SAVE_MIGRATE'
|
export const SAVE_MIGRATE = 'SAVE_MIGRATE'
|
||||||
export const SAVE_STOP = 'SAVE_STOP'
|
export const SAVE_STOP = 'SAVE_STOP'
|
||||||
|
@ -611,6 +611,7 @@ module.exports = {
|
|||||||
IncrementMode: 'Increment Mode',
|
IncrementMode: 'Increment Mode',
|
||||||
IncrementId: 'Increment ID',
|
IncrementId: 'Increment ID',
|
||||||
RestoreBackup: 'Restore backup',
|
RestoreBackup: 'Restore backup',
|
||||||
|
RestoreVm: 'Restore VM',
|
||||||
BackupJobs: 'BackupJobs',
|
BackupJobs: 'BackupJobs',
|
||||||
BackupJob: 'BackupJob',
|
BackupJob: 'BackupJob',
|
||||||
|
|
||||||
@ -1689,6 +1690,7 @@ module.exports = {
|
|||||||
ImportAssociateApp: 'Import associated VM templates/images',
|
ImportAssociateApp: 'Import associated VM templates/images',
|
||||||
SelectResourceToCreateTheApp: 'Select the resource to create the App',
|
SelectResourceToCreateTheApp: 'Select the resource to create the App',
|
||||||
SelectImageToCreateTheApp: 'Select the Image to create the App',
|
SelectImageToCreateTheApp: 'Select the Image to create the App',
|
||||||
|
SelectBackupImage: 'Select backup image',
|
||||||
SelectVmToCreateTheApp: 'Select the VM to create the App',
|
SelectVmToCreateTheApp: 'Select the VM to create the App',
|
||||||
SelectVmTemplateToCreateTheApp: 'Select the VM Template to create the App',
|
SelectVmTemplateToCreateTheApp: 'Select the VM Template to create the App',
|
||||||
|
|
||||||
|
@ -724,11 +724,18 @@ export const VM_LCM_STATES = [
|
|||||||
color: COLOR.info.main,
|
color: COLOR.info.main,
|
||||||
meaning: '',
|
meaning: '',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// 71
|
||||||
|
name: STATES.RESTORE,
|
||||||
|
color: COLOR.info.main,
|
||||||
|
meaning: '',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
/** @enum {string} Virtual machine actions */
|
/** @enum {string} Virtual machine actions */
|
||||||
export const VM_ACTIONS = {
|
export const VM_ACTIONS = {
|
||||||
BACKUP: 'backup',
|
BACKUP: 'backup',
|
||||||
|
RESTORE: 'restore',
|
||||||
CREATE_DIALOG: 'create_dialog',
|
CREATE_DIALOG: 'create_dialog',
|
||||||
CREATE_APP_DIALOG: 'create_app_dialog',
|
CREATE_APP_DIALOG: 'create_app_dialog',
|
||||||
DEPLOY: 'deploy',
|
DEPLOY: 'deploy',
|
||||||
@ -846,6 +853,7 @@ export const DEFAULT_VM_ACTIONS_BY_STATE = {
|
|||||||
STATES.PROLOG_UNDEPLOY_FAILURE,
|
STATES.PROLOG_UNDEPLOY_FAILURE,
|
||||||
STATES.UPDATE_FAILURE,
|
STATES.UPDATE_FAILURE,
|
||||||
],
|
],
|
||||||
|
[VM_ACTIONS.RESTORE]: [STATES.POWEROFF],
|
||||||
[VM_ACTIONS.TERMINATE_HARD]: [
|
[VM_ACTIONS.TERMINATE_HARD]: [
|
||||||
STATES.INIT,
|
STATES.INIT,
|
||||||
STATES.PENDING,
|
STATES.PENDING,
|
||||||
@ -972,6 +980,7 @@ export const DUMMY_VM_ACTIONS_BY_STATE = {
|
|||||||
STATES.PROLOG_UNDEPLOY_FAILURE,
|
STATES.PROLOG_UNDEPLOY_FAILURE,
|
||||||
STATES.UPDATE_FAILURE,
|
STATES.UPDATE_FAILURE,
|
||||||
],
|
],
|
||||||
|
[VM_ACTIONS.RESTORE]: [STATES.POWEROFF],
|
||||||
[VM_ACTIONS.RELEASE]: [STATES.HOLD],
|
[VM_ACTIONS.RELEASE]: [STATES.HOLD],
|
||||||
[VM_ACTIONS.RESCHED]: [STATES.POWEROFF, STATES.RUNNING, STATES.UNKNOWN],
|
[VM_ACTIONS.RESCHED]: [STATES.POWEROFF, STATES.RUNNING, STATES.UNKNOWN],
|
||||||
[VM_ACTIONS.RESUME]: [
|
[VM_ACTIONS.RESUME]: [
|
||||||
@ -1203,6 +1212,7 @@ export const KVM_VM_ACTIONS_BY_STATE = {
|
|||||||
STATES.PROLOG_UNDEPLOY_FAILURE,
|
STATES.PROLOG_UNDEPLOY_FAILURE,
|
||||||
STATES.UPDATE_FAILURE,
|
STATES.UPDATE_FAILURE,
|
||||||
],
|
],
|
||||||
|
[VM_ACTIONS.RESTORE]: [STATES.POWEROFF],
|
||||||
[VM_ACTIONS.RELEASE]: [STATES.HOLD],
|
[VM_ACTIONS.RELEASE]: [STATES.HOLD],
|
||||||
[VM_ACTIONS.RESCHED]: [STATES.POWEROFF, STATES.RUNNING, STATES.UNKNOWN],
|
[VM_ACTIONS.RESCHED]: [STATES.POWEROFF, STATES.RUNNING, STATES.UNKNOWN],
|
||||||
[VM_ACTIONS.RESUME]: [
|
[VM_ACTIONS.RESUME]: [
|
||||||
@ -1433,6 +1443,7 @@ export const VCENTER_VM_ACTIONS_BY_STATE = {
|
|||||||
STATES.PROLOG_UNDEPLOY_FAILURE,
|
STATES.PROLOG_UNDEPLOY_FAILURE,
|
||||||
STATES.UPDATE_FAILURE,
|
STATES.UPDATE_FAILURE,
|
||||||
],
|
],
|
||||||
|
[VM_ACTIONS.RESTORE]: [STATES.POWEROFF],
|
||||||
[VM_ACTIONS.RELEASE]: [STATES.HOLD],
|
[VM_ACTIONS.RELEASE]: [STATES.HOLD],
|
||||||
[VM_ACTIONS.RESCHED]: [STATES.POWEROFF, STATES.RUNNING, STATES.UNKNOWN],
|
[VM_ACTIONS.RESCHED]: [STATES.POWEROFF, STATES.RUNNING, STATES.UNKNOWN],
|
||||||
[VM_ACTIONS.RESUME]: [
|
[VM_ACTIONS.RESUME]: [
|
||||||
@ -1648,6 +1659,7 @@ export const FIRECRACKER_VM_ACTIONS_BY_STATE = {
|
|||||||
STATES.PROLOG_UNDEPLOY_FAILURE,
|
STATES.PROLOG_UNDEPLOY_FAILURE,
|
||||||
STATES.UPDATE_FAILURE,
|
STATES.UPDATE_FAILURE,
|
||||||
],
|
],
|
||||||
|
[VM_ACTIONS.RESTORE]: [STATES.POWEROFF],
|
||||||
[VM_ACTIONS.RELEASE]: [STATES.HOLD],
|
[VM_ACTIONS.RELEASE]: [STATES.HOLD],
|
||||||
[VM_ACTIONS.RESCHED]: [STATES.POWEROFF, STATES.RUNNING, STATES.UNKNOWN],
|
[VM_ACTIONS.RESCHED]: [STATES.POWEROFF, STATES.RUNNING, STATES.UNKNOWN],
|
||||||
[VM_ACTIONS.RESUME]: [
|
[VM_ACTIONS.RESUME]: [
|
||||||
@ -1863,6 +1875,7 @@ export const LXC_VM_ACTIONS_BY_STATE = {
|
|||||||
STATES.PROLOG_UNDEPLOY_FAILURE,
|
STATES.PROLOG_UNDEPLOY_FAILURE,
|
||||||
STATES.UPDATE_FAILURE,
|
STATES.UPDATE_FAILURE,
|
||||||
],
|
],
|
||||||
|
[VM_ACTIONS.RESTORE]: [STATES.POWEROFF],
|
||||||
[VM_ACTIONS.RELEASE]: [STATES.HOLD],
|
[VM_ACTIONS.RELEASE]: [STATES.HOLD],
|
||||||
[VM_ACTIONS.RESCHED]: [STATES.POWEROFF, STATES.RUNNING, STATES.UNKNOWN],
|
[VM_ACTIONS.RESCHED]: [STATES.POWEROFF, STATES.RUNNING, STATES.UNKNOWN],
|
||||||
[VM_ACTIONS.RESUME]: [
|
[VM_ACTIONS.RESUME]: [
|
||||||
|
@ -959,6 +959,26 @@ const vmApi = oneApi.injectEndpoints({
|
|||||||
},
|
},
|
||||||
invalidatesTags: (_, __, { id }) => [{ type: VM, id }],
|
invalidatesTags: (_, __, { id }) => [{ type: VM, id }],
|
||||||
}),
|
}),
|
||||||
|
restore: builder.mutation({
|
||||||
|
/**
|
||||||
|
* Restore the VM.
|
||||||
|
*
|
||||||
|
* @param {object} params - Request parameters
|
||||||
|
* @param {string} params.id - Virtual machine id
|
||||||
|
* @param {number} params.imageId - Image backup id
|
||||||
|
* @param {boolean} params.incrementId - Backup increment ID
|
||||||
|
* @param {number} params.diskId - Individual disk id
|
||||||
|
* @returns {number} Virtual machine id
|
||||||
|
* @throws Fails when response isn't code 200
|
||||||
|
*/
|
||||||
|
query: (params) => {
|
||||||
|
const name = Actions.VM_RESTORE
|
||||||
|
const command = { name, ...Commands[name] }
|
||||||
|
|
||||||
|
return { params, command }
|
||||||
|
},
|
||||||
|
invalidatesTags: (_, __, { id }) => [{ type: VM, id }],
|
||||||
|
}),
|
||||||
lockVm: builder.mutation({
|
lockVm: builder.mutation({
|
||||||
/**
|
/**
|
||||||
* Locks a Virtual Machine. Lock certain actions depending on blocking level.
|
* Locks a Virtual Machine. Lock certain actions depending on blocking level.
|
||||||
@ -1191,6 +1211,7 @@ export const {
|
|||||||
useUpdateConfigurationMutation,
|
useUpdateConfigurationMutation,
|
||||||
useRecoverMutation,
|
useRecoverMutation,
|
||||||
useBackupMutation,
|
useBackupMutation,
|
||||||
|
useRestoreMutation,
|
||||||
useLockVmMutation,
|
useLockVmMutation,
|
||||||
useUnlockVmMutation,
|
useUnlockVmMutation,
|
||||||
useAddScheduledActionMutation,
|
useAddScheduledActionMutation,
|
||||||
|
@ -31,6 +31,7 @@ const VM_RESIZE = 'vm.resize'
|
|||||||
const VM_UPDATE = 'vm.update'
|
const VM_UPDATE = 'vm.update'
|
||||||
const VM_CONF_UPDATE = 'vm.updateconf'
|
const VM_CONF_UPDATE = 'vm.updateconf'
|
||||||
const VM_RECOVER = 'vm.recover'
|
const VM_RECOVER = 'vm.recover'
|
||||||
|
const VM_RESTORE = 'vm.restore'
|
||||||
const VM_INFO = 'vm.info'
|
const VM_INFO = 'vm.info'
|
||||||
const VM_MONITORING = 'vm.monitoring'
|
const VM_MONITORING = 'vm.monitoring'
|
||||||
const VM_LOCK = 'vm.lock'
|
const VM_LOCK = 'vm.lock'
|
||||||
@ -75,6 +76,7 @@ const Actions = {
|
|||||||
VM_UPDATE,
|
VM_UPDATE,
|
||||||
VM_CONF_UPDATE,
|
VM_CONF_UPDATE,
|
||||||
VM_RECOVER,
|
VM_RECOVER,
|
||||||
|
VM_RESTORE,
|
||||||
VM_INFO,
|
VM_INFO,
|
||||||
VM_MONITORING,
|
VM_MONITORING,
|
||||||
VM_LOCK,
|
VM_LOCK,
|
||||||
@ -606,6 +608,28 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
[VM_RESTORE]: {
|
||||||
|
// inspected
|
||||||
|
httpMethod: POST,
|
||||||
|
params: {
|
||||||
|
id: {
|
||||||
|
from: postBody,
|
||||||
|
default: -1,
|
||||||
|
},
|
||||||
|
imageId: {
|
||||||
|
from: postBody,
|
||||||
|
default: -1,
|
||||||
|
},
|
||||||
|
incrementId: {
|
||||||
|
from: postBody,
|
||||||
|
default: -1,
|
||||||
|
},
|
||||||
|
diskId: {
|
||||||
|
from: postBody,
|
||||||
|
default: -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
[VM_INFO]: {
|
[VM_INFO]: {
|
||||||
// inspected
|
// inspected
|
||||||
httpMethod: GET,
|
httpMethod: GET,
|
||||||
|
Loading…
Reference in New Issue
Block a user