diff --git a/src/fireedge/src/client/components/Forms/Vm/SaveAsTemplateForm/index.js b/src/fireedge/src/client/components/Forms/Vm/SaveAsTemplateForm/index.js new file mode 100644 index 0000000000..aad9b61dae --- /dev/null +++ b/src/fireedge/src/client/components/Forms/Vm/SaveAsTemplateForm/index.js @@ -0,0 +1,21 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ +import { createForm } from 'client/utils' +import { SCHEMA, FIELDS } from 'client/components/Forms/Vm/SaveAsTemplateForm/schema' + +const SaveAsTemplateForm = createForm(SCHEMA, FIELDS) + +export default SaveAsTemplateForm diff --git a/src/fireedge/src/client/components/Forms/Vm/SaveAsTemplateForm/schema.js b/src/fireedge/src/client/components/Forms/Vm/SaveAsTemplateForm/schema.js new file mode 100644 index 0000000000..533674fcde --- /dev/null +++ b/src/fireedge/src/client/components/Forms/Vm/SaveAsTemplateForm/schema.js @@ -0,0 +1,45 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ +import { string, boolean, object, ObjectSchema } from 'yup' + +import { T, INPUT_TYPES } from 'client/constants' +import { Field, getValidationFromFields } from 'client/utils' + +/** @type {Field} Template name field */ +const NAME = { + name: 'name', + label: T.TemplateName, + type: INPUT_TYPES.TEXT, + validation: string() + .trim() + .required() + .default(() => undefined) +} + +/** @type {Field} Persistent field */ +const PERSISTENT = { + name: 'persistent', + label: T.MakeNewImagePersistent, + type: INPUT_TYPES.CHECKBOX, + validation: boolean().default(() => false), + grid: { md: 12 } +} + +/** @type {Field[]} List of fields */ +export const FIELDS = [NAME, PERSISTENT] + +/** @type {ObjectSchema} Schema */ +export const SCHEMA = object(getValidationFromFields(FIELDS)) diff --git a/src/fireedge/src/client/components/Forms/Vm/index.js b/src/fireedge/src/client/components/Forms/Vm/index.js index 9608bc6253..5374615762 100644 --- a/src/fireedge/src/client/components/Forms/Vm/index.js +++ b/src/fireedge/src/client/components/Forms/Vm/index.js @@ -23,6 +23,7 @@ import RecoverForm from 'client/components/Forms/Vm/RecoverForm' import ResizeCapacityForm from 'client/components/Forms/Vm/ResizeCapacityForm' import ResizeDiskForm from 'client/components/Forms/Vm/ResizeDiskForm' import SaveAsDiskForm from 'client/components/Forms/Vm/SaveAsDiskForm' +import SaveAsTemplateForm from 'client/components/Forms/Vm/SaveAsTemplateForm' export * from 'client/components/Forms/Vm/AttachDiskForm' export * from 'client/components/Forms/Vm/CreateSchedActionForm' @@ -36,5 +37,6 @@ export { RecoverForm, ResizeCapacityForm, ResizeDiskForm, - SaveAsDiskForm + SaveAsDiskForm, + SaveAsTemplateForm } diff --git a/src/fireedge/src/client/components/Tables/VmTemplates/actions.js b/src/fireedge/src/client/components/Tables/VmTemplates/actions.js index b19406696e..49a4292384 100644 --- a/src/fireedge/src/client/components/Tables/VmTemplates/actions.js +++ b/src/fireedge/src/client/components/Tables/VmTemplates/actions.js @@ -245,7 +245,7 @@ const Actions = () => { onSubmit: async (_, rows) => { const ids = rows?.map?.(({ original }) => original?.ID) await Promise.all(ids.map(id => remove(id))) - await Promise.all(ids.map(id => getVmTemplate(id))) + await getVmTemplates() } }] } diff --git a/src/fireedge/src/client/components/Tables/Vms/actions.js b/src/fireedge/src/client/components/Tables/Vms/actions.js index 5e762a9301..eea644da43 100644 --- a/src/fireedge/src/client/components/Tables/Vms/actions.js +++ b/src/fireedge/src/client/components/Tables/Vms/actions.js @@ -31,10 +31,18 @@ import { } from 'iconoir-react' import { useAuth } from 'client/features/Auth' +import { useGeneralApi } from 'client/features/General' import { useDatastore, useVmApi } from 'client/features/One' import { Translate } from 'client/components/HOC' -import { RecoverForm, ChangeUserForm, ChangeGroupForm, MigrateForm } from 'client/components/Forms/Vm' +import { + RecoverForm, + ChangeUserForm, + ChangeGroupForm, + MigrateForm, + SaveAsTemplateForm +} from 'client/components/Forms/Vm' + import { createActions } from 'client/components/Tables/Enhanced/Utils' import { PATH } from 'client/apps/sunstone/routesOne' import { getLastHistory, isAvailableAction } from 'client/models/VirtualMachine' @@ -52,7 +60,12 @@ const ListVmNames = ({ rows = [] }) => { const DS_NAME = datastores?.find(ds => ds?.ID === DS_ID)?.NAME ?? '--' return ( - + ( const Actions = () => { const history = useHistory() const { view, getResourceView } = useAuth() + const { enqueueSuccess } = useGeneralApi() const { getVm, getVms, + saveAsTemplate, terminate, terminateHard, undeploy, @@ -144,7 +159,18 @@ const Actions = () => { tooltip: T.SaveAsTemplate, selected: { max: 1 }, icon: SaveFloppyDisk, - action: () => {} + options: [{ + dialogProps: { + title: T.SaveAsTemplate, + subheader: SubHeader + }, + form: SaveAsTemplateForm, + onSubmit: async (formData, rows) => { + const vmId = rows?.[0]?.original?.ID + const response = await saveAsTemplate(vmId, formData) + enqueueSuccess(response) + } + }] }, { tooltip: T.Manage, diff --git a/src/fireedge/src/client/components/Tabs/Common/Attribute/Actions.js b/src/fireedge/src/client/components/Tabs/Common/Attribute/Actions.js index 7174ab33e1..6fe3f91963 100644 --- a/src/fireedge/src/client/components/Tabs/Common/Attribute/Actions.js +++ b/src/fireedge/src/client/components/Tabs/Common/Attribute/Actions.js @@ -122,7 +122,7 @@ const Cancel = props => { attachDisk: (id, template) => unwrapDispatch(actions.attachDisk({ id, template })), detachDisk: (id, disk) => unwrapDispatch(actions.detachDisk({ id, disk })), saveAsDisk: (id, data) => unwrapDispatch(actions.saveAsDisk({ id, ...data })), + saveAsTemplate: (id, data) => unwrapDispatch(actions.saveAsTemplate({ id, ...data })), resizeDisk: (id, data) => unwrapDispatch(actions.resizeDisk({ id, ...data })), createDiskSnapshot: (id, data) => unwrapDispatch(actions.createDiskSnapshot({ id, ...data })), diff --git a/src/fireedge/src/client/features/One/vm/services.js b/src/fireedge/src/client/features/One/vm/services.js index 813c2f5754..cd6588752e 100644 --- a/src/fireedge/src/client/features/One/vm/services.js +++ b/src/fireedge/src/client/features/One/vm/services.js @@ -99,6 +99,29 @@ export const vmService = ({ return res?.data?.VM ?? {} }, + /** + * Clones the VM's source Template, replacing the disks with live snapshots + * of the current disks. The VM capacity and NICs are also preserved. + * + * @param {object} params - Request parameters + * @param {string|number} params.id - Virtual machine id + * @param {string} params.name - Template name + * @param {boolean} params.persistent - Make the new images persistent + * @returns {number} Virtual machine id + * @throws Fails when response isn't code 200 + */ + saveAsTemplate: async ({ id, name, persistent }) => { + const res = await RestClient.request({ + url: `/api/vm/save/${id}`, + method: 'POST', + data: { name, persistent } + }) + + if (!res?.id || res?.id !== httpCodes.ok.id) throw res?.data + + return res?.data + }, + /** * Renames a virtual machine. *