diff --git a/src/fireedge/etc/sunstone/admin/service-tab.yaml b/src/fireedge/etc/sunstone/admin/service-tab.yaml index 5b249a1788..f690e1c419 100644 --- a/src/fireedge/etc/sunstone/admin/service-tab.yaml +++ b/src/fireedge/etc/sunstone/admin/service-tab.yaml @@ -53,13 +53,14 @@ info-tabs: log: enabled: true - scheduler_actions: + sched_actions: enabled: true actions: - sched_action_create: true - sched_action_update: true - sched_action_delete: true + sched-add: true + sched-update: false + sched-delete: false charter_create: true + perform_action: true # Dialogs diff --git a/src/fireedge/etc/sunstone/cloud/service-tab.yaml b/src/fireedge/etc/sunstone/cloud/service-tab.yaml index 8345ba0796..66e828cc55 100644 --- a/src/fireedge/etc/sunstone/cloud/service-tab.yaml +++ b/src/fireedge/etc/sunstone/cloud/service-tab.yaml @@ -54,13 +54,14 @@ info-tabs: log: enabled: true - scheduler_actions: - enabled: false + sched_actions: + enabled: true actions: - sched_action_create: false - sched_action_update: false - sched_action_delete: false - charter_create: false + sched-add: true + sched-update: false + sched-delete: false + charter_create: true + perform_action: true # Dialogs diff --git a/src/fireedge/etc/sunstone/groupadmin/service-tab.yaml b/src/fireedge/etc/sunstone/groupadmin/service-tab.yaml index 69a6c4f5fb..c9bc0fbb5c 100644 --- a/src/fireedge/etc/sunstone/groupadmin/service-tab.yaml +++ b/src/fireedge/etc/sunstone/groupadmin/service-tab.yaml @@ -54,13 +54,14 @@ info-tabs: log: enabled: true - scheduler_actions: + sched_actions: enabled: true actions: - sched_action_create: true - sched_action_update: true - sched_action_delete: true + sched-add: true + sched-update: false + sched-delete: false charter_create: true + perform_action: true # Dialogs diff --git a/src/fireedge/src/client/components/Buttons/ScheduleAction.js b/src/fireedge/src/client/components/Buttons/ScheduleAction.js index 440cce6f57..80801630fa 100644 --- a/src/fireedge/src/client/components/Buttons/ScheduleAction.js +++ b/src/fireedge/src/client/components/Buttons/ScheduleAction.js @@ -26,6 +26,8 @@ import { CreateSchedActionForm, } from 'client/components/Forms/Vm' +import { CreatePerformAction } from 'client/components/Forms/Service' + import { Tr, Translate } from 'client/components/HOC' import { SERVER_CONFIG, @@ -226,6 +228,45 @@ const CharterButton = memo(({ relative, onSubmit }) => { ) }) +/** + * Returns a button to trigger form to perform an action. + * + * @param {object} props - Props + * @param {object} props.service - Service resource + * @param {boolean} [props.relative] - Applies to the form relative format + * @param {function():Promise} props.onSubmit - Submit function + * @returns {ReactElement} Button + */ +const PerformActionButton = memo( + ({ service, onSubmit, oneConfig, adminGroup, roles }) => { + const formConfig = { + stepProps: { service, oneConfig, adminGroup, roles }, + } + + return ( + CreatePerformAction(formConfig), + onSubmit, + }, + ]} + /> + ) + } +) + const ButtonPropTypes = { vm: PropTypes.object, relative: PropTypes.bool, @@ -234,6 +275,8 @@ const ButtonPropTypes = { oneConfig: PropTypes.object, adminGroup: PropTypes.bool, backupjobs: PropTypes.bool, + service: PropTypes.object, + roles: PropTypes.object, } CreateSchedButton.propTypes = ButtonPropTypes @@ -244,10 +287,13 @@ DeleteSchedButton.propTypes = ButtonPropTypes DeleteSchedButton.displayName = 'DeleteSchedButton' CharterButton.propTypes = ButtonPropTypes CharterButton.displayName = 'CharterButton' +PerformActionButton.propTypes = ButtonPropTypes +PerformActionButton.displayName = 'PerformActionButton' export { CharterButton, CreateSchedButton, DeleteSchedButton, UpdateSchedButton, + PerformActionButton, } diff --git a/src/fireedge/src/client/components/Forms/Service/PerformAction/index.js b/src/fireedge/src/client/components/Forms/Service/PerformAction/index.js new file mode 100644 index 0000000000..de7701aa46 --- /dev/null +++ b/src/fireedge/src/client/components/Forms/Service/PerformAction/index.js @@ -0,0 +1,38 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2024, 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 { + FIELDS, + SCHEMA, +} from 'client/components/Forms/Service/PerformAction/schema' + +const PerformActionForm = createForm(SCHEMA, FIELDS, { + transformBeforeSubmit: (formData) => { + // Transform args for an action that needs some arguments + if (formData?.ARGS) { + if (formData?.ARGS?.NAME) { + formData.ARGS = formData.ARGS.NAME + } else if (formData?.ARGS?.SNAPSHOT_ID) { + formData.ARGS = formData.ARGS.SNAPSHOT_ID + } + } + + return formData + }, +}) + +export default PerformActionForm diff --git a/src/fireedge/src/client/components/Forms/Service/PerformAction/schema.js b/src/fireedge/src/client/components/Forms/Service/PerformAction/schema.js new file mode 100644 index 0000000000..0b055fd31c --- /dev/null +++ b/src/fireedge/src/client/components/Forms/Service/PerformAction/schema.js @@ -0,0 +1,132 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2024, 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 { ObjectSchema, string } from 'yup' +import { getObjectSchemaFromFields, arrayToOptions, Field } from 'client/utils' +import { + INPUT_TYPES, + T, + VM_ACTIONS_WITH_SCHEDULE, + VM_ACTIONS, + ARGS_TYPES, +} from 'client/constants' + +import { getRequiredArgsByAction } from 'client/models/Scheduler' + +/** + * @returns {Field} Action name field + */ +const ACTION_FIELD = { + name: 'ACTION', + label: T.Action, + type: INPUT_TYPES.AUTOCOMPLETE, + optionsOnly: true, + values: () => { + const validActions = { + ...VM_ACTIONS_WITH_SCHEDULE, + } + + /** + * BACKUP: Not supported by oneflow api + */ + delete validActions[VM_ACTIONS.BACKUP] + + return arrayToOptions( + Object.entries({ + ...validActions, + }), + { + addEmpty: false, + getText: ([, text]) => text, + getValue: ([value]) => value, + } + ) + }, + validation: string().trim().required(), + grid: { xs: 12 }, +} + +export const ACTION_FIELD_NAME = 'ACTION' + +const createArgField = (argName, htmlType) => ({ + name: `ARGS.${argName}`, + dependOf: ACTION_FIELD_NAME, + htmlType: (action) => { + const prueba = getRequiredArgsByAction(action) + console.log(argName, prueba.includes(argName)) + + return !getRequiredArgsByAction(action)?.includes(argName) + ? INPUT_TYPES.HIDDEN + : htmlType + }, +}) + +/** @type {Field} Snapshot name field */ +const ARGS_NAME_FIELD = { + ...createArgField(ARGS_TYPES.NAME), + label: T.SnapshotName, + type: INPUT_TYPES.TEXT, +} + +/** + * @returns {Field} Snapshot id field + */ +const ARGS_SNAPSHOT_ID_FIELD = { + ...createArgField(ARGS_TYPES.SNAPSHOT_ID), + label: T.Snapshot + ' ' + T.ID, + type: INPUT_TYPES.TEXT, +} + +const ROLE_FIELD = (roles) => ({ + name: 'ROLE', + label: T.Role, + type: INPUT_TYPES.AUTOCOMPLETE, + optionsOnly: true, + values: () => { + const rolesWithAll = roles.map((role) => ({ + name: role.name, + value: role.name, + })) + + rolesWithAll.push({ + name: T.All, + value: 'ALL', + }) + + return arrayToOptions(rolesWithAll, { + addEmpty: false, + getText: (role) => role.name, + getValue: (role) => role.value, + }) + }, + validation: string().trim().required(), + grid: { xs: 12 }, +}) + +/** + * @param {object} props - Properties of the form + * @param {object} props.roles - Roles of the service + * @returns {Array} - List of fields + */ +export const FIELDS = ({ roles }) => [ + ACTION_FIELD, + ROLE_FIELD(roles), + ARGS_NAME_FIELD, + ARGS_SNAPSHOT_ID_FIELD, +] + +/** @type {ObjectSchema} Schema */ +export const SCHEMA = ({ roles }) => + getObjectSchemaFromFields(FIELDS({ roles })) diff --git a/src/fireedge/src/client/components/Tabs/Service/Actions.js b/src/fireedge/src/client/components/Forms/Service/index.js similarity index 57% rename from src/fireedge/src/client/components/Tabs/Service/Actions.js rename to src/fireedge/src/client/components/Forms/Service/index.js index 512319f147..a6975efeb2 100644 --- a/src/fireedge/src/client/components/Tabs/Service/Actions.js +++ b/src/fireedge/src/client/components/Forms/Service/index.js @@ -14,37 +14,14 @@ * limitations under the License. * * ------------------------------------------------------------------------- */ import { ReactElement } from 'react' -import PropTypes from 'prop-types' -import { Stack } from '@mui/material' - -import { useGetServiceQuery } from 'client/features/OneApi/service' -// import ScheduleActionCard from 'client/components/Cards/ScheduleActionCard' +import { AsyncLoadForm, ConfigurationProps } from 'client/components/HOC' +import { CreateStepsCallback } from 'client/utils/schema' /** - * Renders the list of schedule actions from a Service. - * - * @param {object} props - Props - * @param {string} props.id - Service id - * @param {object|boolean} props.tabProps - Tab properties - * @param {object} [props.tabProps.actions] - Actions from user view yaml - * @returns {ReactElement} Schedule actions tab + * @param {ConfigurationProps} configProps - Configuration + * @returns {ReactElement|CreateStepsCallback} Asynchronous loaded form */ -const SchedulingTab = ({ id, tabProps: { actions } = {} }) => { - const { data: service = {} } = useGetServiceQuery({ id }) +const CreatePerformAction = (configProps) => + AsyncLoadForm({ formPath: 'Service/PerformAction' }, configProps) - return ( - <> - - {service?.NAME} - {/* TODO: scheduler actions & form */} - - - ) -} - -SchedulingTab.propTypes = { - tabProps: PropTypes.object, - id: PropTypes.string, -} - -export default SchedulingTab +export { CreatePerformAction } diff --git a/src/fireedge/src/client/components/Forms/ServiceTemplate/InstantiateForm/Steps/Charters/index.js b/src/fireedge/src/client/components/Forms/ServiceTemplate/InstantiateForm/Steps/Charters/index.js index 8cb6a59faa..a8ff0cdf19 100644 --- a/src/fireedge/src/client/components/Forms/ServiceTemplate/InstantiateForm/Steps/Charters/index.js +++ b/src/fireedge/src/client/components/Forms/ServiceTemplate/InstantiateForm/Steps/Charters/index.js @@ -31,7 +31,6 @@ import { Tr } from 'client/components/HOC' import { Legend } from 'client/components/Forms' import { mapNameByIndex } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/schema' -import { STEP_ID as EXTRA_ID } from 'client/components/Forms/ServiceTemplate/CreateForm/Steps/Extra' import { Component, useMemo } from 'react' @@ -52,7 +51,7 @@ const ScheduleActionsSection = ({ oneConfig, adminGroup }) => { update, append, } = useFieldArray({ - name: `${EXTRA_ID}.${TAB_ID}`, + name: `charter.${TAB_ID}`, keyName: 'ID', }) diff --git a/src/fireedge/src/client/components/Forms/ServiceTemplate/InstantiateForm/Steps/index.js b/src/fireedge/src/client/components/Forms/ServiceTemplate/InstantiateForm/Steps/index.js index 98f0dd4f93..dfec7d01dd 100644 --- a/src/fireedge/src/client/components/Forms/ServiceTemplate/InstantiateForm/Steps/index.js +++ b/src/fireedge/src/client/components/Forms/ServiceTemplate/InstantiateForm/Steps/index.js @@ -48,14 +48,41 @@ const Steps = createSteps([General, UserInputs, Network, Charter], { } ) + // Get schedule actions from vm template contents + const schedActions = parseVmTemplateContents( + ServiceTemplate?.TEMPLATE?.BODY?.roles[0]?.vm_template_contents, + true + )?.schedActions + const knownTemplate = schema.cast({ [GENERAL_ID]: {}, [USERINPUTS_ID]: {}, [NETWORK_ID]: { NETWORKS: networks }, - [CHARTER_ID]: {}, + [CHARTER_ID]: { SCHED_ACTION: schedActions }, }) - return { ...knownTemplate, roles: roles } + const newRoles = roles.map((role) => { + // Parse vm template content + const roleTemplateContent = parseVmTemplateContents( + role.vm_template_contents, + true + ) + + // Delete schedule actions + delete roleTemplateContent.schedActions + + // Parse content without sched actions + const roleTemplateWithoutSchedActions = parseVmTemplateContents( + roleTemplateContent, + false + ) + role.vm_template_contents = roleTemplateWithoutSchedActions + + // Return content + return role + }) + + return { ...knownTemplate, roles: newRoles } }, transformBeforeSubmit: (formData) => { @@ -86,12 +113,12 @@ const Steps = createSteps([General, UserInputs, Network, Charter], { { vmTemplateContents: role?.vm_template_contents, customAttrsValues: userInputsData, + schedActions: charterData.SCHED_ACTION, }, false, true ), })), - ...(!!charterData?.SCHED_ACTION?.length && { ...charterData }), name: generalData?.NAME, } diff --git a/src/fireedge/src/client/components/Tabs/Service/SchedActions.js b/src/fireedge/src/client/components/Tabs/Service/SchedActions.js new file mode 100644 index 0000000000..2e8adf19d5 --- /dev/null +++ b/src/fireedge/src/client/components/Tabs/Service/SchedActions.js @@ -0,0 +1,148 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2024, 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, useMemo } from 'react' +import PropTypes from 'prop-types' +import { Stack } from '@mui/material' + +import { parseVmTemplateContents } from 'client/utils' + +import { + useGetServiceQuery, + useServiceRoleActionMutation, + useServiceAddActionMutation, +} from 'client/features/OneApi/service' +import {} from 'client/features/OneApi/vm' + +import ScheduleActionCard from 'client/components/Cards/ScheduleActionCard' +import { PerformActionButton } from 'client/components/Buttons/ScheduleAction' + +import { getScheduleActions } from 'client/models/VirtualMachine' + +import { VM_ACTIONS, T } from 'client/constants' + +import { useGeneralApi } from 'client/features/General' + +const { PERFORM_ACTION } = VM_ACTIONS + +/** + * Renders the list of schedule actions from a Service. + * + * @param {object} props - Props + * @param {string} props.id - Service id + * @param {object|boolean} props.tabProps - Tab properties + * @param {object} [props.tabProps.actions] - Actions from user view yaml + * @returns {ReactElement} Schedule actions tab + */ +const SchedulingTab = ({ id, tabProps: { actions } = {} }) => { + const { enqueueError, enqueueSuccess, enqueueInfo } = useGeneralApi() + + // Get service info + const { data: service = {} } = useGetServiceQuery({ id }) + + // Functions to manage sched actions + const [useServiceAddAction] = useServiceAddActionMutation() + const [serviceRoleAction] = useServiceRoleActionMutation() + + // Check actions and roles + const [scheduling, actionsAvailable, roles] = useMemo(() => { + const schedActions = { + TEMPLATE: { + SCHED_ACTION: parseVmTemplateContents( + service?.TEMPLATE?.BODY?.roles[0]?.vm_template_contents, + true + )?.schedActions, + }, + } + + const updatedRoles = service?.TEMPLATE?.BODY?.roles + + return [getScheduleActions(schedActions), actions, updatedRoles] + }, [service]) + + const isPerformActionEnabled = actionsAvailable[PERFORM_ACTION] + + /** + * Add new schedule action to VM. + * + * @param {object} formData - New schedule action + * @returns {Promise} - Add schedule action and refetch VM data + */ + const handlePerformAction = async (formData) => { + enqueueInfo(T.InfoServiceActionRole, [formData.ACTION, formData.ROLE]) + + try { + if (formData.ROLE === 'ALL') { + await useServiceAddAction({ + id, + perform: formData.ACTION, + params: { + args: formData.ARGS, + }, + }) + enqueueSuccess(T.SuccessRoleActionCompleted, [ + formData.ACTION, + formData.ROLE, + ]) + } else { + await serviceRoleAction({ + id, + role: formData.ROLE, + perform: formData.ACTION, + params: { + args: formData.ARGS, + }, + }) + enqueueSuccess(T.SuccessRoleActionCompleted, [ + formData.ACTION, + formData.ROLE, + ]) + } + } catch (error) { + enqueueError(T.ErrorServiceActionRole, [ + formData?.ACTION, + formData?.ROLE, + error, + ]) + } + } + + return ( + <> + {isPerformActionEnabled && ( + + {isPerformActionEnabled && ( + + )} + + )} + + + {scheduling.map((schedule) => { + const { ID, NAME } = schedule + + return + })} + + + ) +} + +SchedulingTab.propTypes = { + tabProps: PropTypes.object, + id: PropTypes.string, +} + +export default SchedulingTab diff --git a/src/fireedge/src/client/components/Tabs/Service/index.js b/src/fireedge/src/client/components/Tabs/Service/index.js index 7a0dec407e..28d54be436 100644 --- a/src/fireedge/src/client/components/Tabs/Service/index.js +++ b/src/fireedge/src/client/components/Tabs/Service/index.js @@ -23,7 +23,7 @@ import { useGetServiceQuery } from 'client/features/OneApi/service' import { getAvailableInfoTabs } from 'client/models/Helper' import Tabs from 'client/components/Tabs' -import Actions from 'client/components/Tabs/Service/Actions' +import Actions from 'client/components/Tabs/Service/SchedActions' import Info from 'client/components/Tabs/Service/Info' import Log from 'client/components/Tabs/Service/Log' import Roles from 'client/components/Tabs/Service/Roles' @@ -33,7 +33,7 @@ const getTabComponent = (tabName) => info: Info, roles: Roles, log: Log, - schedulerAction: Actions, + sched_actions: Actions, }[tabName]) const ServiceTabs = memo(({ id }) => { diff --git a/src/fireedge/src/client/constants/translates.js b/src/fireedge/src/client/constants/translates.js index 3bd6dcb0d0..985dae3672 100644 --- a/src/fireedge/src/client/constants/translates.js +++ b/src/fireedge/src/client/constants/translates.js @@ -151,6 +151,8 @@ module.exports = { Migrate: 'Migrate', MigrateLive: 'Migrate live', Offline: 'Offline', + PerformAction: 'Perform action', + AllRoles: 'All roles', Pin: 'Pin', Poweroff: 'Poweroff', PoweroffHard: 'Poweroff hard', @@ -1515,6 +1517,8 @@ module.exports = { RoleManageApps: 'Manage multi-VM applications efficiently.', /* Service Template - configuration */ RoleConfiguration: 'Role Configuration', + /* Service Template - schedule actions */ + ServiceSheduleActionCreated: 'Shedule action added to service', /* VMGroups - Role definition */ NewRole: 'New Role', diff --git a/src/fireedge/src/client/constants/vm.js b/src/fireedge/src/client/constants/vm.js index 7f50fc7cf3..bf10cd1da2 100644 --- a/src/fireedge/src/client/constants/vm.js +++ b/src/fireedge/src/client/constants/vm.js @@ -804,6 +804,7 @@ export const VM_ACTIONS = { SCHED_ACTION_UPDATE: 'sched-update', SCHED_ACTION_DELETE: 'sched-delete', CHARTER_CREATE: 'charter_create', + PERFORM_ACTION: 'perform_action', // CONFIGURATION UPDATE_CONF: 'update_configuration', diff --git a/src/fireedge/src/client/utils/parser/parseVmTemplateContents.js b/src/fireedge/src/client/utils/parser/parseVmTemplateContents.js index 01e9dda289..76f7e9b5a8 100644 --- a/src/fireedge/src/client/utils/parser/parseVmTemplateContents.js +++ b/src/fireedge/src/client/utils/parser/parseVmTemplateContents.js @@ -102,7 +102,7 @@ const extractPropertiesToArray = (content) => { } const formatInstantiate = (contents) => { - const { vmTemplateContents, customAttrsValues } = contents + const { vmTemplateContents, customAttrsValues, schedActions } = contents const sections = extractSections(vmTemplateContents) .map(parseSection) @@ -139,7 +139,12 @@ const formatInstantiate = (contents) => { ...filteredProperties, ] - const formattedTemplate = combinedContent.join('\n') + '\n' + const formattedActions = schedActions?.map((action, index) => + formatSchedActions({ ...action, ID: index }) + ) + + const formattedTemplate = + combinedContent.join('\n') + formattedActions.join('\n') + '\n' return formattedTemplate }