From 363f677e1732c2b0b635e4569b7d4b986bf37778 Mon Sep 17 00:00:00 2001 From: David Resende Date: Tue, 29 Aug 2023 17:09:29 -0400 Subject: [PATCH] Add Credential Mapping to Workflows Page --- app/controllers/workflow_controller.rb | 14 +- .../toolbar/workflow_center.rb | 16 + .../toolbar/workflows_center.rb | 19 + .../miq-data-table/miq-table-cell.jsx | 1 + .../helper.js | 24 + .../index.jsx | 131 + ...workflow-credential-mapping-form.schema.js | 169 + .../packs/component-definitions-common.js | 2 + .../reconfigure-vm-form.spec.js.snap | 12 + .../settings-company-categories.spec.js.snap | 4 + .../settings-label-tag-mapping.spec.js.snap | 8 + ...kflow-credential-mapping-form.spec.js.snap | 4217 +++++++++++++++++ .../workflow-credential-mapping-form.spec.js | 75 + app/stylesheet/ddf_override.scss | 9 + app/stylesheet/miq-data-table.scss | 12 + app/views/workflow/map_credentials.html.haml | 2 + config/routes.rb | 1 + 17 files changed, 4715 insertions(+), 1 deletion(-) create mode 100644 app/javascript/components/workflow-credential-mapping-form/helper.js create mode 100644 app/javascript/components/workflow-credential-mapping-form/index.jsx create mode 100644 app/javascript/components/workflow-credential-mapping-form/workflow-credential-mapping-form.schema.js create mode 100644 app/javascript/spec/workflow-credential-mapping-form/__snapshots__/workflow-credential-mapping-form.spec.js.snap create mode 100644 app/javascript/spec/workflow-credential-mapping-form/workflow-credential-mapping-form.spec.js create mode 100644 app/views/workflow/map_credentials.html.haml diff --git a/app/controllers/workflow_controller.rb b/app/controllers/workflow_controller.rb index 2fc1e3043a2..41ab7213709 100644 --- a/app/controllers/workflow_controller.rb +++ b/app/controllers/workflow_controller.rb @@ -23,11 +23,23 @@ def show_searchbar? end def button - if params[:pressed] == "embedded_configuration_script_payload_tag" + case params[:pressed] + when 'embedded_configuration_script_payload_map_credentials' + javascript_redirect(:action => 'map_credentials', :id => params[:miq_grid_checks]) + when 'embedded_configuration_script_payload_tag' tag(self.class.model) end end + def map_credentials + assert_privileges('embedded_configuration_script_payload_map_credentials') + workflow = find_record_with_rbac(self.class.model, params[:id]) + drop_breadcrumb(:name => _("Map Credentials to \"%{name}\"") % {:name => workflow.name}, + :url => "/workflow/map_credentials/#{params[:id]}") + @in_a_form = true + @id = workflow.id + end + def toolbar %w[show_list].include?(@lastaction) ? 'workflows_center' : 'workflow_center' end diff --git a/app/helpers/application_helper/toolbar/workflow_center.rb b/app/helpers/application_helper/toolbar/workflow_center.rb index 1a92c8a0302..cc34c135631 100644 --- a/app/helpers/application_helper/toolbar/workflow_center.rb +++ b/app/helpers/application_helper/toolbar/workflow_center.rb @@ -1,5 +1,21 @@ class ApplicationHelper::Toolbar::WorkflowCenter < ApplicationHelper::Toolbar::Basic button_group('workflows_policy', [ + select( + :workflow_configuration, + nil, + t = N_('Configuration'), + t, + :items => [ + button( + :embedded_configuration_script_payload_map_credentials, + 'pficon pficon-edit fa-lg', + t = N_('Map Credentials to this Workflow'), + t, + :klass => ApplicationHelper::Button::EmbeddedWorkflow, + :url => "/map_credentials" + ), + ] + ), select( :workflows_policy_choice, nil, diff --git a/app/helpers/application_helper/toolbar/workflows_center.rb b/app/helpers/application_helper/toolbar/workflows_center.rb index fea8712fb93..ec4da72b4a1 100644 --- a/app/helpers/application_helper/toolbar/workflows_center.rb +++ b/app/helpers/application_helper/toolbar/workflows_center.rb @@ -1,5 +1,24 @@ class ApplicationHelper::Toolbar::WorkflowsCenter < ApplicationHelper::Toolbar::Basic button_group('workflows_policy', [ + select( + :workflows_configuration, + nil, + t = N_('Configuration'), + t, + :items => [ + button( + :embedded_configuration_script_payload_map_credentials, + 'pficon pficon-edit fa-lg', + t = N_('Map Credentials to this Workflow'), + t, + :klass => ApplicationHelper::Button::EmbeddedWorkflow, + :enabled => false, + :onwhen => "1", + :url_parms => "edit_div", + :send_checked => true + ), + ] + ), select( :workflows_policy_choice, nil, diff --git a/app/javascript/components/miq-data-table/miq-table-cell.jsx b/app/javascript/components/miq-data-table/miq-table-cell.jsx index fce5fea2a5b..e352d3439ff 100644 --- a/app/javascript/components/miq-data-table/miq-table-cell.jsx +++ b/app/javascript/components/miq-data-table/miq-table-cell.jsx @@ -121,6 +121,7 @@ const MiqTableCell = ({ disabled={item.disabled} onKeyPress={(e) => cellButtonEvent(item, e)} tabIndex={0} + size={item.size ? item.size : ''} title={item.title ? item.title : truncateText} kind={item.kind ? item.kind : 'primary'} className={classNames('miq-data-table-button', item.buttonClassName)} diff --git a/app/javascript/components/workflow-credential-mapping-form/helper.js b/app/javascript/components/workflow-credential-mapping-form/helper.js new file mode 100644 index 00000000000..29b4d65ece1 --- /dev/null +++ b/app/javascript/components/workflow-credential-mapping-form/helper.js @@ -0,0 +1,24 @@ +import React from 'react'; +import { useFieldApi, useFormApi } from '@@ddf'; +import MiqDataTable from '../miq-data-table'; + +export const CredentialMapperComponent = (props) => { + const { rows, onCellClick } = useFieldApi(props); + const formOptions = useFormApi(); + + return ( +
+ onCellClick(selectedRow, cellType, formOptions)} + /> +
+ ); +}; diff --git a/app/javascript/components/workflow-credential-mapping-form/index.jsx b/app/javascript/components/workflow-credential-mapping-form/index.jsx new file mode 100644 index 00000000000..88635dca74c --- /dev/null +++ b/app/javascript/components/workflow-credential-mapping-form/index.jsx @@ -0,0 +1,131 @@ +import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; + +import MiqFormRenderer from '@@ddf'; +import { CredentialMapperComponent } from './helper'; +import componentMapper from '../../forms/mappers/componentMapper'; +import createSchema from './workflow-credential-mapping-form.schema'; +import miqRedirectBack from '../../helpers/miq-redirect-back'; + +const WorkflowCredentialMappingForm = ({ recordId }) => { + const [{ + credentials, + credentialReferences, + payloadCredentials, + workflowAuthentications, + initialValues, + isLoading, + }, setState] = useState({ isLoading: !!recordId }); + const submitLabel = !!recordId ? __('Save') : __('Add'); + + // custom component mapper + const mapper = { + ...componentMapper, + 'credential-mapper': CredentialMapperComponent, + }; + + useEffect(() => { + // eslint-disable-next-line camelcase + API.get( + `/api/authentications?expand=resources&filter[]=type='ManageIQ::Providers::Workflows::AutomationManager::WorkflowCredential'` + ).then(({ resources }) => { + API.get(`/api/configuration_script_payloads/${recordId}`).then(({ name, payload, credentials }) => { + const initialCredentials = credentials != null ? credentials : {}; + + /* + Creates the list of credential references from the workflow payload by parsing each state and saving them to payloadCredentials. + Duplicate references get overridden. + */ + const jsonPayload = JSON.parse(payload).States; + let payloadCredentials = {}; + + Object.keys(jsonPayload).forEach((key1) => { + if (jsonPayload[key1].Credentials != null) { + Object.keys(jsonPayload[key1].Credentials).forEach((key2) => { + payloadCredentials = { + [key2]: jsonPayload[key1].Credentials[key2], + ...payloadCredentials, + }; + }); + } + }); + + // Returns the user to the show_list page if the workflow has no credential references in it's payload + if (Object.keys(payloadCredentials).length === 0) { + throw __('Workflow does not have any credentials to map.'); + } + + /* + payloadCredentials -> list of credential references for this workflow + workflowAuthentications -> list of available workflow credentials + credentials -> List of currently mapped reference-credential parings + */ + setState({ + payloadCredentials, + workflowAuthentications: Object.keys(resources).map((key) => ({ + value: resources[key].ems_ref, + label: resources[key].name, + })), + credentials: initialCredentials, + initialValues: { + name, + credentials: initialCredentials, + }, + isLoading: false, + }); + }).catch((error) => { + const message = __('Embedded Workflow service is not available.'); + miqRedirectBack(error || message, 'error', '/workflow/show_list'); + }); + }).catch(() => { + const message = __('Embedded Workflow service is not available.'); + miqRedirectBack(message, 'error', '/workflow/show_list'); + }); + }, []); + + const onSubmit = () => { + miqSparkleOn(); + + const submission = { credentials }; + + const request = API.patch(`/api/configuration_script_payloads/${recordId}`, submission); + request.then(() => { + const message = sprintf(__('Credential Mapping for "%s" was saved.'), initialValues.name); + miqRedirectBack(message, undefined, '/workflow/show_list'); + }).catch(miqSparkleOff); + }; + + const onReset = () => { + setState((state) => ({ + ...state, + credentials: state.initialValues.credentials, + })); + }; + + const onCancel = () => { + const message = sprintf(__('Credential Mapping for "%s" was canceled by the user.'), initialValues && initialValues.name); + miqRedirectBack(message, 'warning', '/workflow/show_list'); + }; + + return !isLoading && ( + + ); +}; + +WorkflowCredentialMappingForm.propTypes = { + recordId: PropTypes.string, +}; +WorkflowCredentialMappingForm.defaultProps = { + recordId: undefined, +}; + +export default WorkflowCredentialMappingForm; diff --git a/app/javascript/components/workflow-credential-mapping-form/workflow-credential-mapping-form.schema.js b/app/javascript/components/workflow-credential-mapping-form/workflow-credential-mapping-form.schema.js new file mode 100644 index 00000000000..9855d5fa885 --- /dev/null +++ b/app/javascript/components/workflow-credential-mapping-form/workflow-credential-mapping-form.schema.js @@ -0,0 +1,169 @@ +import { componentTypes } from '@@ddf'; + +const fieldLabels = { + userid: __('Username'), + password: __('Password'), + auth_key: __('Private key'), + auth_key_password: __('Private key passphrase'), +}; + +const createSchema = (credentials, credentialReferences, payloadCredentials, workflowAuthentications, setState) => { + // Saves a new credential mapping to 'credentials' + const mapCredentials = () => setState((state) => { + if (state.workflowCredentials && state.credentialField) { + return ({ + ...state, + credentials: { + ...credentials, + [state.credentialReferences]: { + credential_ref: state.workflowCredentials, + credential_field: state.credentialField, + }, + }, + }); + } + return ({ ...state }); + }); + + // Deletes an existing credential mapping from 'credentials' + const deleteMapping = (selectedRow, cellType, formOptions) => { + if (cellType === 'buttonCallback' && credentials[selectedRow.cells[0].value]) { + // This is creating a new credentials object without the mapping we're trying to delete + // eslint-disable-next-line no-unused-vars + const { [selectedRow.cells[0].value]: _, ...newCredentials } = credentials; + setState((state) => ({ + ...state, + credentials: newCredentials, + })); + + formOptions.change('credential_references', ''); + setState((state) => ({ + ...state, + credentialReferences: undefined, + })); + } + }; + + // Creates the rows for the 'credential-mapper' component + const createRows = (credentials, payloadCredentials, workflowAuthentications) => { + const rows = []; + + Object.keys(payloadCredentials).forEach((value, index) => { + rows.push({ + id: index.toString(), + CredentialsIdentifier: { text: value }, + CredentialRecord: { + text: credentials[value] ? workflowAuthentications.find((item) => item.value === credentials[value].credential_ref).label : '', + }, + CredentialField: { text: credentials[value] ? fieldLabels[credentials[value].credential_field] : '' }, + Delete: { + is_button: true, + text: __('Delete'), + kind: 'danger', + size: 'md', + callback: 'deleteMapping', + }, + }); + }); + + return rows; + }; + + return ({ + fields: [ + { + component: 'credential-mapper', + name: 'test', + label: __('test'), + rows: createRows(credentials, payloadCredentials, workflowAuthentications), + onCellClick: deleteMapping, + }, + { + component: componentTypes.SELECT, + name: 'credential_references', + label: __('Credential Reference'), + placeholder: __(''), + includeEmpty: true, + options: Object.keys(payloadCredentials).map((key) => ({ + value: key, + label: key, + })), + resolveProps: (props, { meta, input }, formOptions) => { + // This ensures credentialReferences is set back to undefined when credential_references is reset to default + if (meta.pristine && credentialReferences !== undefined) { + setState((state) => ({ + ...state, + credentialReferences: undefined, + })); + } + + /* + Anytime the user selects a credential reference to map check to see if it's already mapped. + If it is, then update 'workflow_credentials' and 'credential_field' to display this mapping. + Otherwise reset them to empty. + */ + if (credentials[input.value] && credentialReferences !== input.value) { + formOptions.change('workflow_credentials', credentials[input.value].credential_ref); + formOptions.change('credential_field', credentials[input.value].credential_field); + setState((state) => ({ + ...state, + workflowCredentials: credentials[input.value].credential_ref, + credentialField: credentials[input.value].credential_field, + })); + } else if (credentialReferences !== (input.value || undefined)) { + formOptions.change('workflow_credentials', ''); + formOptions.change('credential_field', ''); + setState((state) => ({ + ...state, + workflowCredentials: '', + credentialField: '', + })); + } + }, + onChange: (value) => setState((state) => ({ + ...state, + credentialReferences: value, + })), + }, + { + component: componentTypes.SUB_FORM, + name: 'workflow_credentials_section', + id: 'workflow_credentials_section', + condition: { when: 'credential_references', isNotEmpty: true }, + fields: [ + { + component: componentTypes.SELECT, + name: 'workflow_credentials', + label: __('Workflow Credential'), + placeholder: __(''), + includeEmpty: true, + options: workflowAuthentications, + onChange: (value) => { + setState((state) => ({ ...state, workflowCredentials: value })); + mapCredentials(); + }, + }, + { + component: componentTypes.SELECT, + name: 'credential_field', + label: __('Credential Field'), + placeholder: __(''), + includeEmpty: true, + options: [ + { label: __('Username'), value: 'userid' }, + { label: __('Password'), value: 'password' }, + { label: __('Private key'), value: 'auth_key' }, + { label: __('Private key passphrase'), value: 'auth_key_password' }, + ], + onChange: (value) => { + setState((state) => ({ ...state, credentialField: value })); + mapCredentials(); + }, + }, + ], + }, + ], + }); +}; + +export default createSchema; diff --git a/app/javascript/packs/component-definitions-common.js b/app/javascript/packs/component-definitions-common.js index b97e3c6f4d8..fd7dd81ee8d 100644 --- a/app/javascript/packs/component-definitions-common.js +++ b/app/javascript/packs/component-definitions-common.js @@ -145,6 +145,7 @@ import WidgetReport from '../components/dashboard-widgets/widget-report'; import WidgetWrapper from '../components/dashboard-widgets/widget-wrapper'; import WidgetZoom from '../components/dashboard-widgets/widget-zoom'; import WorkersForm from '../components/workers-form/workers-form'; +import WorkflowCredentialMappingForm from '../components/workflow-credential-mapping-form'; import WorkflowCredentialsForm from '../components/workflow-credentials-form'; import WorkflowPayload from '../components/workflows/workflow_payload'; import WorkflowRepositoryForm from '../components/workflow-repository-form'; @@ -317,6 +318,7 @@ ManageIQ.component.addReact('WidgetReport', WidgetReport); ManageIQ.component.addReact('WidgetWrapper', WidgetWrapper); ManageIQ.component.addReact('WidgetZoom', WidgetZoom); ManageIQ.component.addReact('WorkersForm', WorkersForm); +ManageIQ.component.addReact('WorkflowCredentialMappingForm', WorkflowCredentialMappingForm); ManageIQ.component.addReact('WorkflowCredentialsForm', WorkflowCredentialsForm); ManageIQ.component.addReact('WorkflowPayload', WorkflowPayload); ManageIQ.component.addReact('WorkflowRepositoryForm', WorkflowRepositoryForm); diff --git a/app/javascript/spec/reconfigure-vm-form/__snapshots__/reconfigure-vm-form.spec.js.snap b/app/javascript/spec/reconfigure-vm-form/__snapshots__/reconfigure-vm-form.spec.js.snap index 751d7713521..218cfe945ca 100644 --- a/app/javascript/spec/reconfigure-vm-form/__snapshots__/reconfigure-vm-form.spec.js.snap +++ b/app/javascript/spec/reconfigure-vm-form/__snapshots__/reconfigure-vm-form.spec.js.snap @@ -8005,6 +8005,7 @@ exports[`Reconfigure VM form component should render reconfigure form and click kind="ghost" onClick={[Function]} onKeyPress={[Function]} + size="" tabIndex={0} title="Resize" > @@ -8245,6 +8246,7 @@ exports[`Reconfigure VM form component should render reconfigure form and click kind="ghost" onClick={[Function]} onKeyPress={[Function]} + size="" tabIndex={0} title="Delete" > @@ -9462,6 +9464,7 @@ exports[`Reconfigure VM form component should render reconfigure form and click kind="ghost" onClick={[Function]} onKeyPress={[Function]} + size="" tabIndex={0} title="Cancel Disconnect" > @@ -9610,6 +9613,7 @@ exports[`Reconfigure VM form component should render reconfigure form and click kind="ghost" onClick={[Function]} onKeyPress={[Function]} + size="" tabIndex={0} title="Connect" > @@ -29758,6 +29762,7 @@ exports[`Reconfigure VM form component should render reconfigure form with datat kind="ghost" onClick={[Function]} onKeyPress={[Function]} + size="" tabIndex={0} title="Resize" > @@ -29998,6 +30003,7 @@ exports[`Reconfigure VM form component should render reconfigure form with datat kind="ghost" onClick={[Function]} onKeyPress={[Function]} + size="" tabIndex={0} title="Delete" > @@ -31075,6 +31081,7 @@ exports[`Reconfigure VM form component should render reconfigure form with datat kind="ghost" onClick={[Function]} onKeyPress={[Function]} + size="" tabIndex={0} title="Edit" > @@ -31235,6 +31242,7 @@ exports[`Reconfigure VM form component should render reconfigure form with datat kind="ghost" onClick={[Function]} onKeyPress={[Function]} + size="" tabIndex={0} title="Delete" > @@ -40460,6 +40468,7 @@ exports[`Reconfigure VM form component should render reconfigure sub form and cl kind="ghost" onClick={[Function]} onKeyPress={[Function]} + size="" tabIndex={0} title="Resize" > @@ -40697,6 +40706,7 @@ exports[`Reconfigure VM form component should render reconfigure sub form and cl kind="ghost" onClick={[Function]} onKeyPress={[Function]} + size="" tabIndex={0} title="Cancel Delete" > @@ -41916,6 +41926,7 @@ exports[`Reconfigure VM form component should render reconfigure sub form and cl kind="ghost" onClick={[Function]} onKeyPress={[Function]} + size="" tabIndex={0} title="Disconnect" > @@ -42064,6 +42075,7 @@ exports[`Reconfigure VM form component should render reconfigure sub form and cl kind="ghost" onClick={[Function]} onKeyPress={[Function]} + size="" tabIndex={0} title="Connect" > diff --git a/app/javascript/spec/settings-compan-categories/__snapshots__/settings-company-categories.spec.js.snap b/app/javascript/spec/settings-compan-categories/__snapshots__/settings-company-categories.spec.js.snap index d74dc3454be..7554277c7d5 100644 --- a/app/javascript/spec/settings-compan-categories/__snapshots__/settings-company-categories.spec.js.snap +++ b/app/javascript/spec/settings-compan-categories/__snapshots__/settings-company-categories.spec.js.snap @@ -1871,6 +1871,7 @@ exports[`SettingsCompanyCategories component should render a SettingsCompanyCate kind="danger" onClick={[Function]} onKeyPress={[Function]} + size="" tabIndex={0} title="Category cannot be deleted" > @@ -3029,6 +3030,7 @@ exports[`SettingsCompanyCategories component should render a SettingsCompanyCate kind="danger" onClick={[Function]} onKeyPress={[Function]} + size="" tabIndex={0} title="Click to delete this category" > @@ -4187,6 +4189,7 @@ exports[`SettingsCompanyCategories component should render a SettingsCompanyCate kind="danger" onClick={[Function]} onKeyPress={[Function]} + size="" tabIndex={0} title="Click to delete this category" > @@ -5345,6 +5348,7 @@ exports[`SettingsCompanyCategories component should render a SettingsCompanyCate kind="danger" onClick={[Function]} onKeyPress={[Function]} + size="" tabIndex={0} title="Click to delete this category" > diff --git a/app/javascript/spec/settings-label-tag-mapping/__snapshots__/settings-label-tag-mapping.spec.js.snap b/app/javascript/spec/settings-label-tag-mapping/__snapshots__/settings-label-tag-mapping.spec.js.snap index 29b52717ab2..6ca82696e3b 100644 --- a/app/javascript/spec/settings-label-tag-mapping/__snapshots__/settings-label-tag-mapping.spec.js.snap +++ b/app/javascript/spec/settings-label-tag-mapping/__snapshots__/settings-label-tag-mapping.spec.js.snap @@ -1026,6 +1026,7 @@ exports[`SettingsLabelTagMapping component should render a SettingsLabelTagMappi kind="danger" onClick={[Function]} onKeyPress={[Function]} + size="" tabIndex={0} title="Click to delete this mapping" > @@ -1539,6 +1540,7 @@ exports[`SettingsLabelTagMapping component should render a SettingsLabelTagMappi kind="danger" onClick={[Function]} onKeyPress={[Function]} + size="" tabIndex={0} title="Click to delete this mapping" > @@ -2052,6 +2054,7 @@ exports[`SettingsLabelTagMapping component should render a SettingsLabelTagMappi kind="danger" onClick={[Function]} onKeyPress={[Function]} + size="" tabIndex={0} title="Click to delete this mapping" > @@ -2565,6 +2568,7 @@ exports[`SettingsLabelTagMapping component should render a SettingsLabelTagMappi kind="danger" onClick={[Function]} onKeyPress={[Function]} + size="" tabIndex={0} title="Click to delete this mapping" > @@ -3621,6 +3625,7 @@ exports[`SettingsLabelTagMapping component should render a SettingsLabelTagMappi kind="danger" onClick={[Function]} onKeyPress={[Function]} + size="" tabIndex={0} title="Click to delete this mapping" > @@ -4134,6 +4139,7 @@ exports[`SettingsLabelTagMapping component should render a SettingsLabelTagMappi kind="danger" onClick={[Function]} onKeyPress={[Function]} + size="" tabIndex={0} title="Click to delete this mapping" > @@ -4647,6 +4653,7 @@ exports[`SettingsLabelTagMapping component should render a SettingsLabelTagMappi kind="danger" onClick={[Function]} onKeyPress={[Function]} + size="" tabIndex={0} title="Click to delete this mapping" > @@ -5160,6 +5167,7 @@ exports[`SettingsLabelTagMapping component should render a SettingsLabelTagMappi kind="danger" onClick={[Function]} onKeyPress={[Function]} + size="" tabIndex={0} title="Click to delete this mapping" > diff --git a/app/javascript/spec/workflow-credential-mapping-form/__snapshots__/workflow-credential-mapping-form.spec.js.snap b/app/javascript/spec/workflow-credential-mapping-form/__snapshots__/workflow-credential-mapping-form.spec.js.snap new file mode 100644 index 00000000000..3a97bb3b21d --- /dev/null +++ b/app/javascript/spec/workflow-credential-mapping-form/__snapshots__/workflow-credential-mapping-form.spec.js.snap @@ -0,0 +1,4217 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Workflow Credential Form Component should redirect back to show_list page if a workflow has no credentials to map 1`] = ` + + + +`; + +exports[`Workflow Credential Form Component should render mapping credentials to the workflow 1`] = ` + + + ", + "resolveProps": [Function], + }, + Object { + "component": "sub-form", + "condition": Object { + "isNotEmpty": true, + "when": "credential_references", + }, + "fields": Array [ + Object { + "component": "select", + "includeEmpty": true, + "label": "Workflow Credential", + "name": "workflow_credentials", + "onChange": [Function], + "options": Array [ + Object { + "label": "Test Credential", + "value": "test_credential", + }, + Object { + "label": "API Credential", + "value": "api_credential", + }, + ], + "placeholder": "", + }, + Object { + "component": "select", + "includeEmpty": true, + "label": "Credential Field", + "name": "credential_field", + "onChange": [Function], + "options": Array [ + Object { + "label": "Username", + "value": "userid", + }, + Object { + "label": "Password", + "value": "password", + }, + Object { + "label": "Private key", + "value": "auth_key", + }, + Object { + "label": "Private key passphrase", + "value": "auth_key_password", + }, + ], + "placeholder": "", + }, + ], + "id": "workflow_credentials_section", + "name": "workflow_credentials_section", + }, + ], + } + } + > + ", + "resolveProps": [Function], + }, + Object { + "component": "sub-form", + "condition": Object { + "isNotEmpty": true, + "when": "credential_references", + }, + "fields": Array [ + Object { + "component": "select", + "includeEmpty": true, + "label": "Workflow Credential", + "name": "workflow_credentials", + "onChange": [Function], + "options": Array [ + Object { + "label": "Test Credential", + "value": "test_credential", + }, + Object { + "label": "API Credential", + "value": "api_credential", + }, + ], + "placeholder": "", + }, + Object { + "component": "select", + "includeEmpty": true, + "label": "Credential Field", + "name": "credential_field", + "onChange": [Function], + "options": Array [ + Object { + "label": "Username", + "value": "userid", + }, + Object { + "label": "Password", + "value": "password", + }, + Object { + "label": "Private key", + "value": "auth_key", + }, + Object { + "label": "Private key passphrase", + "value": "auth_key_password", + }, + ], + "placeholder": "", + }, + ], + "id": "workflow_credentials_section", + "name": "workflow_credentials_section", + }, + ], + } + } + showFormControls={true} + > + ", + "resolveProps": [Function], + }, + Object { + "component": "sub-form", + "condition": Object { + "isNotEmpty": true, + "when": "credential_references", + }, + "fields": Array [ + Object { + "component": "select", + "includeEmpty": true, + "label": "Workflow Credential", + "name": "workflow_credentials", + "onChange": [Function], + "options": Array [ + Object { + "label": "Test Credential", + "value": "test_credential", + }, + Object { + "label": "API Credential", + "value": "api_credential", + }, + ], + "placeholder": "", + }, + Object { + "component": "select", + "includeEmpty": true, + "label": "Credential Field", + "name": "credential_field", + "onChange": [Function], + "options": Array [ + Object { + "label": "Username", + "value": "userid", + }, + Object { + "label": "Password", + "value": "password", + }, + Object { + "label": "Private key", + "value": "auth_key", + }, + Object { + "label": "Private key passphrase", + "value": "auth_key_password", + }, + ], + "placeholder": "", + }, + ], + "id": "workflow_credentials_section", + "name": "workflow_credentials_section", + }, + Object { + "component": "spy-field", + "initialize": undefined, + "name": "spy-field", + }, + ], + } + } + > + + , + , + ", + }, + Object { + "component": "select", + "includeEmpty": true, + "label": "Credential Field", + "name": "credential_field", + "onChange": [Function], + "options": Array [ + Object { + "label": "Username", + "value": "userid", + }, + Object { + "label": "Password", + "value": "password", + }, + Object { + "label": "Private key", + "value": "auth_key", + }, + Object { + "label": "Private key passphrase", + "value": "auth_key_password", + }, + ], + "placeholder": "", + }, + ] + } + id="workflow_credentials_section" + name="workflow_credentials_section" + />, + , + ] + } + schema={ + Object { + "fields": Array [ + Object { + "component": "credential-mapper", + "label": "test", + "name": "test", + "onCellClick": [Function], + "rows": Array [ + Object { + "CredentialField": Object { + "text": "Password", + }, + "CredentialRecord": Object { + "text": "Test Credential", + }, + "CredentialsIdentifier": Object { + "text": "api_password", + }, + "Delete": Object { + "callback": "deleteMapping", + "is_button": true, + "kind": "danger", + "size": "md", + "text": "Delete", + }, + "id": "0", + }, + Object { + "CredentialField": Object { + "text": "Username", + }, + "CredentialRecord": Object { + "text": "API Credential", + }, + "CredentialsIdentifier": Object { + "text": "api_user", + }, + "Delete": Object { + "callback": "deleteMapping", + "is_button": true, + "kind": "danger", + "size": "md", + "text": "Delete", + }, + "id": "1", + }, + ], + }, + Object { + "component": "select", + "includeEmpty": true, + "label": "Credential Reference", + "name": "credential_references", + "onChange": [Function], + "options": Array [ + Object { + "label": "api_password", + "value": "api_password", + }, + Object { + "label": "api_user", + "value": "api_user", + }, + ], + "placeholder": "", + "resolveProps": [Function], + }, + Object { + "component": "sub-form", + "condition": Object { + "isNotEmpty": true, + "when": "credential_references", + }, + "fields": Array [ + Object { + "component": "select", + "includeEmpty": true, + "label": "Workflow Credential", + "name": "workflow_credentials", + "onChange": [Function], + "options": Array [ + Object { + "label": "Test Credential", + "value": "test_credential", + }, + Object { + "label": "API Credential", + "value": "api_credential", + }, + ], + "placeholder": "", + }, + Object { + "component": "select", + "includeEmpty": true, + "label": "Credential Field", + "name": "credential_field", + "onChange": [Function], + "options": Array [ + Object { + "label": "Username", + "value": "userid", + }, + Object { + "label": "Password", + "value": "password", + }, + Object { + "label": "Private key", + "value": "auth_key", + }, + Object { + "label": "Private key passphrase", + "value": "auth_key_password", + }, + ], + "placeholder": "", + }, + ], + "id": "workflow_credentials_section", + "name": "workflow_credentials_section", + }, + Object { + "component": "spy-field", + "initialize": undefined, + "name": "spy-field", + }, + ], + } + } + > + , + , + ", + }, + Object { + "component": "select", + "includeEmpty": true, + "label": "Credential Field", + "name": "credential_field", + "onChange": [Function], + "options": Array [ + Object { + "label": "Username", + "value": "userid", + }, + Object { + "label": "Password", + "value": "password", + }, + Object { + "label": "Private key", + "value": "auth_key", + }, + Object { + "label": "Private key passphrase", + "value": "auth_key_password", + }, + ], + "placeholder": "", + }, + ] + } + id="workflow_credentials_section" + name="workflow_credentials_section" + />, + , + ] + } + formWrapperProps={ + Object { + "className": "form-react", + } + } + resetLabel="Reset" + schema={ + Object { + "fields": Array [ + Object { + "component": "credential-mapper", + "label": "test", + "name": "test", + "onCellClick": [Function], + "rows": Array [ + Object { + "CredentialField": Object { + "text": "Password", + }, + "CredentialRecord": Object { + "text": "Test Credential", + }, + "CredentialsIdentifier": Object { + "text": "api_password", + }, + "Delete": Object { + "callback": "deleteMapping", + "is_button": true, + "kind": "danger", + "size": "md", + "text": "Delete", + }, + "id": "0", + }, + Object { + "CredentialField": Object { + "text": "Username", + }, + "CredentialRecord": Object { + "text": "API Credential", + }, + "CredentialsIdentifier": Object { + "text": "api_user", + }, + "Delete": Object { + "callback": "deleteMapping", + "is_button": true, + "kind": "danger", + "size": "md", + "text": "Delete", + }, + "id": "1", + }, + ], + }, + Object { + "component": "select", + "includeEmpty": true, + "label": "Credential Reference", + "name": "credential_references", + "onChange": [Function], + "options": Array [ + Object { + "label": "api_password", + "value": "api_password", + }, + Object { + "label": "api_user", + "value": "api_user", + }, + ], + "placeholder": "", + "resolveProps": [Function], + }, + Object { + "component": "sub-form", + "condition": Object { + "isNotEmpty": true, + "when": "credential_references", + }, + "fields": Array [ + Object { + "component": "select", + "includeEmpty": true, + "label": "Workflow Credential", + "name": "workflow_credentials", + "onChange": [Function], + "options": Array [ + Object { + "label": "Test Credential", + "value": "test_credential", + }, + Object { + "label": "API Credential", + "value": "api_credential", + }, + ], + "placeholder": "", + }, + Object { + "component": "select", + "includeEmpty": true, + "label": "Credential Field", + "name": "credential_field", + "onChange": [Function], + "options": Array [ + Object { + "label": "Username", + "value": "userid", + }, + Object { + "label": "Password", + "value": "password", + }, + Object { + "label": "Private key", + "value": "auth_key", + }, + Object { + "label": "Private key passphrase", + "value": "auth_key_password", + }, + ], + "placeholder": "", + }, + ], + "id": "workflow_credentials_section", + "name": "workflow_credentials_section", + }, + Object { + "component": "spy-field", + "initialize": undefined, + "name": "spy-field", + }, + ], + } + } + showFormControls={true} + submitLabel="Save" + > + , + , + ", + }, + Object { + "component": "select", + "includeEmpty": true, + "label": "Credential Field", + "name": "credential_field", + "onChange": [Function], + "options": Array [ + Object { + "label": "Username", + "value": "userid", + }, + Object { + "label": "Password", + "value": "password", + }, + Object { + "label": "Private key", + "value": "auth_key", + }, + Object { + "label": "Private key passphrase", + "value": "auth_key_password", + }, + ], + "placeholder": "", + }, + ] + } + id="workflow_credentials_section" + name="workflow_credentials_section" + />, + , + ] + } + formWrapperProps={ + Object { + "className": "form-react", + } + } + resetLabel="Reset" + schema={ + Object { + "fields": Array [ + Object { + "component": "credential-mapper", + "label": "test", + "name": "test", + "onCellClick": [Function], + "rows": Array [ + Object { + "CredentialField": Object { + "text": "Password", + }, + "CredentialRecord": Object { + "text": "Test Credential", + }, + "CredentialsIdentifier": Object { + "text": "api_password", + }, + "Delete": Object { + "callback": "deleteMapping", + "is_button": true, + "kind": "danger", + "size": "md", + "text": "Delete", + }, + "id": "0", + }, + Object { + "CredentialField": Object { + "text": "Username", + }, + "CredentialRecord": Object { + "text": "API Credential", + }, + "CredentialsIdentifier": Object { + "text": "api_user", + }, + "Delete": Object { + "callback": "deleteMapping", + "is_button": true, + "kind": "danger", + "size": "md", + "text": "Delete", + }, + "id": "1", + }, + ], + }, + Object { + "component": "select", + "includeEmpty": true, + "label": "Credential Reference", + "name": "credential_references", + "onChange": [Function], + "options": Array [ + Object { + "label": "api_password", + "value": "api_password", + }, + Object { + "label": "api_user", + "value": "api_user", + }, + ], + "placeholder": "", + "resolveProps": [Function], + }, + Object { + "component": "sub-form", + "condition": Object { + "isNotEmpty": true, + "when": "credential_references", + }, + "fields": Array [ + Object { + "component": "select", + "includeEmpty": true, + "label": "Workflow Credential", + "name": "workflow_credentials", + "onChange": [Function], + "options": Array [ + Object { + "label": "Test Credential", + "value": "test_credential", + }, + Object { + "label": "API Credential", + "value": "api_credential", + }, + ], + "placeholder": "", + }, + Object { + "component": "select", + "includeEmpty": true, + "label": "Credential Field", + "name": "credential_field", + "onChange": [Function], + "options": Array [ + Object { + "label": "Username", + "value": "userid", + }, + Object { + "label": "Password", + "value": "password", + }, + Object { + "label": "Private key", + "value": "auth_key", + }, + Object { + "label": "Private key passphrase", + "value": "auth_key_password", + }, + ], + "placeholder": "", + }, + ], + "id": "workflow_credentials_section", + "name": "workflow_credentials_section", + }, + Object { + "component": "spy-field", + "initialize": undefined, + "name": "spy-field", + }, + ], + } + } + showFormControls={true} + submitLabel="Save" + > +
+ + + + + + + +
+ +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ Credentials Identifier +
+
+
+ Credential Record +
+
+
+ Credential Field +
+
+
+ Delete +
+
+
+ + api_password + +
+
+
+ + Test Credential + +
+
+
+ + Password + +
+
+
+ + +
+
+
+ + api_user + +
+
+
+ + API Credential + +
+
+
+ + Username + +
+
+
+ + +
+
+
+ + +
+ + +
+
+
+
+ + ", + "resolveProps": [Function], + } + } + > + + + ", + "value": undefined, + }, + Object { + "label": "api_password", + "value": "api_password", + }, + Object { + "label": "api_user", + "value": "api_user", + }, + ] + } + placeholder="" + pluckSingleValue={true} + simpleValue={false} + value="" + > + ", + "value": undefined, + }, + Object { + "label": "api_password", + "value": "api_password", + }, + Object { + "label": "api_user", + "value": "api_user", + }, + ] + } + placeholder="" + value="" + > + + + + + + + + + + + + + + + + + + + + + + + + ", + }, + Object { + "component": "select", + "includeEmpty": true, + "label": "Credential Field", + "name": "credential_field", + "onChange": [Function], + "options": Array [ + Object { + "label": "Username", + "value": "userid", + }, + Object { + "label": "Password", + "value": "password", + }, + Object { + "label": "Private key", + "value": "auth_key", + }, + Object { + "label": "Private key passphrase", + "value": "auth_key_password", + }, + ], + "placeholder": "", + }, + ] + } + id="workflow_credentials_section" + key="workflow_credentials_section" + name="workflow_credentials_section" + > + ", + }, + Object { + "component": "select", + "includeEmpty": true, + "label": "Credential Field", + "name": "credential_field", + "onChange": [Function], + "options": Array [ + Object { + "label": "Username", + "value": "userid", + }, + Object { + "label": "Password", + "value": "password", + }, + Object { + "label": "Private key", + "value": "auth_key", + }, + Object { + "label": "Private key passphrase", + "value": "auth_key_password", + }, + ], + "placeholder": "", + }, + ], + "id": "workflow_credentials_section", + "name": "workflow_credentials_section", + } + } + > + ", + }, + Object { + "component": "select", + "includeEmpty": true, + "label": "Credential Field", + "name": "credential_field", + "onChange": [Function], + "options": Array [ + Object { + "label": "Username", + "value": "userid", + }, + Object { + "label": "Password", + "value": "password", + }, + Object { + "label": "Private key", + "value": "auth_key", + }, + Object { + "label": "Private key passphrase", + "value": "auth_key_password", + }, + ], + "placeholder": "", + }, + ], + "id": "workflow_credentials_section", + "name": "workflow_credentials_section", + } + } + triggers={ + Array [ + "credential_references", + ] + } + > + + ", + }, + Object { + "component": "select", + "includeEmpty": true, + "label": "Credential Field", + "name": "credential_field", + "onChange": [Function], + "options": Array [ + Object { + "label": "Username", + "value": "userid", + }, + Object { + "label": "Password", + "value": "password", + }, + Object { + "label": "Private key", + "value": "auth_key", + }, + Object { + "label": "Private key passphrase", + "value": "auth_key_password", + }, + ], + "placeholder": "", + }, + ], + "id": "workflow_credentials_section", + "name": "workflow_credentials_section", + } + } + triggers={Array []} + values={ + Object { + "credential_references": "", + } + } + > + ", + }, + Object { + "component": "select", + "includeEmpty": true, + "label": "Credential Field", + "name": "credential_field", + "onChange": [Function], + "options": Array [ + Object { + "label": "Username", + "value": "userid", + }, + Object { + "label": "Password", + "value": "password", + }, + Object { + "label": "Private key", + "value": "auth_key", + }, + Object { + "label": "Private key passphrase", + "value": "auth_key_password", + }, + ], + "placeholder": "", + }, + ], + "id": "workflow_credentials_section", + "name": "workflow_credentials_section", + } + } + values={ + Object { + "credential_references": "", + } + } + > + ", + }, + Object { + "component": "select", + "includeEmpty": true, + "label": "Credential Field", + "name": "credential_field", + "onChange": [Function], + "options": Array [ + Object { + "label": "Username", + "value": "userid", + }, + Object { + "label": "Password", + "value": "password", + }, + Object { + "label": "Private key", + "value": "auth_key", + }, + Object { + "label": "Private key passphrase", + "value": "auth_key_password", + }, + ], + "placeholder": "", + }, + ], + "id": "workflow_credentials_section", + "name": "workflow_credentials_section", + } + } + values={ + Object { + "credential_references": "", + } + } + /> + + + + + + + + + + + + + + + + + , + , + ", + }, + Object { + "component": "select", + "includeEmpty": true, + "label": "Credential Field", + "name": "credential_field", + "onChange": [Function], + "options": Array [ + Object { + "label": "Username", + "value": "userid", + }, + Object { + "label": "Password", + "value": "password", + }, + Object { + "label": "Private key", + "value": "auth_key", + }, + Object { + "label": "Private key passphrase", + "value": "auth_key_password", + }, + ], + "placeholder": "", + }, + ] + } + id="workflow_credentials_section" + name="workflow_credentials_section" + />, + , + ] + } + formSpyProps={ + Object { + "active": undefined, + "dirty": false, + "dirtyFields": Object {}, + "dirtyFieldsSinceLastSubmit": Object {}, + "dirtySinceLastSubmit": false, + "error": undefined, + "errors": Object {}, + "form": Object { + "batch": [Function], + "blur": [Function], + "change": [Function], + "destroyOnUnregister": false, + "focus": [Function], + "getFieldState": [Function], + "getRegisteredFields": [Function], + "getState": [Function], + "initialize": [Function], + "isValidationPaused": [Function], + "mutators": Object { + "concat": [Function], + "insert": [Function], + "move": [Function], + "pop": [Function], + "push": [Function], + "remove": [Function], + "removeBatch": [Function], + "shift": [Function], + "swap": [Function], + "unshift": [Function], + "update": [Function], + }, + "pauseValidation": [Function], + "registerField": [Function], + "reset": [Function], + "resetFieldState": [Function], + "restart": [Function], + "resumeValidation": [Function], + "setConfig": [Function], + "submit": [Function], + "subscribe": [Function], + }, + "hasSubmitErrors": false, + "hasValidationErrors": false, + "initialValues": Object { + "credentials": Object { + "api_password": Object { + "credential_field": "password", + "credential_ref": "test_credential", + }, + "api_user": Object { + "credential_field": "userid", + "credential_ref": "api_credential", + }, + }, + "name": "test-workflow.asl", + }, + "invalid": false, + "modified": Object {}, + "modifiedSinceLastSubmit": false, + "pristine": true, + "submitError": undefined, + "submitErrors": undefined, + "submitFailed": false, + "submitSucceeded": false, + "submitting": false, + "touched": Object {}, + "valid": true, + "validating": false, + "values": Object { + "credentials": Object { + "api_password": Object { + "credential_field": "password", + "credential_ref": "test_credential", + }, + "api_user": Object { + "credential_field": "userid", + "credential_ref": "api_credential", + }, + }, + "name": "test-workflow.asl", + }, + "visited": Object {}, + } + } + onCancel={[Function]} + onReset={[Function]} + resetLabel="Reset" + schema={ + Object { + "fields": Array [ + Object { + "component": "credential-mapper", + "label": "test", + "name": "test", + "onCellClick": [Function], + "rows": Array [ + Object { + "CredentialField": Object { + "text": "Password", + }, + "CredentialRecord": Object { + "text": "Test Credential", + }, + "CredentialsIdentifier": Object { + "text": "api_password", + }, + "Delete": Object { + "callback": "deleteMapping", + "is_button": true, + "kind": "danger", + "size": "md", + "text": "Delete", + }, + "id": "0", + }, + Object { + "CredentialField": Object { + "text": "Username", + }, + "CredentialRecord": Object { + "text": "API Credential", + }, + "CredentialsIdentifier": Object { + "text": "api_user", + }, + "Delete": Object { + "callback": "deleteMapping", + "is_button": true, + "kind": "danger", + "size": "md", + "text": "Delete", + }, + "id": "1", + }, + ], + }, + Object { + "component": "select", + "includeEmpty": true, + "label": "Credential Reference", + "name": "credential_references", + "onChange": [Function], + "options": Array [ + Object { + "label": "api_password", + "value": "api_password", + }, + Object { + "label": "api_user", + "value": "api_user", + }, + ], + "placeholder": "", + "resolveProps": [Function], + }, + Object { + "component": "sub-form", + "condition": Object { + "isNotEmpty": true, + "when": "credential_references", + }, + "fields": Array [ + Object { + "component": "select", + "includeEmpty": true, + "label": "Workflow Credential", + "name": "workflow_credentials", + "onChange": [Function], + "options": Array [ + Object { + "label": "Test Credential", + "value": "test_credential", + }, + Object { + "label": "API Credential", + "value": "api_credential", + }, + ], + "placeholder": "", + }, + Object { + "component": "select", + "includeEmpty": true, + "label": "Credential Field", + "name": "credential_field", + "onChange": [Function], + "options": Array [ + Object { + "label": "Username", + "value": "userid", + }, + Object { + "label": "Password", + "value": "password", + }, + Object { + "label": "Private key", + "value": "auth_key", + }, + Object { + "label": "Private key passphrase", + "value": "auth_key_password", + }, + ], + "placeholder": "", + }, + ], + "id": "workflow_credentials_section", + "name": "workflow_credentials_section", + }, + Object { + "component": "spy-field", + "initialize": undefined, + "name": "spy-field", + }, + ], + } + } + submitLabel="Save" + > + + +
+ + + + + + + + + +
+
+
+
+
+ +
+ + +
+
+
+
+
+
+
+
+
+`; diff --git a/app/javascript/spec/workflow-credential-mapping-form/workflow-credential-mapping-form.spec.js b/app/javascript/spec/workflow-credential-mapping-form/workflow-credential-mapping-form.spec.js new file mode 100644 index 00000000000..f81e727bac5 --- /dev/null +++ b/app/javascript/spec/workflow-credential-mapping-form/workflow-credential-mapping-form.spec.js @@ -0,0 +1,75 @@ +import React from 'react'; +import toJson from 'enzyme-to-json'; +import fetchMock from 'fetch-mock'; + +import { act } from 'react-dom/test-utils'; +import { mount } from '../helpers/mountForm'; +import WorkflowCredentialMappingForm from '../../components/workflow-credential-mapping-form/index'; + +describe('Workflow Credential Form Component', () => { + const authenticationsApi = { + resources: [ + { name: 'Test Credential', ems_ref: 'test_credential' }, + { name: 'API Credential', ems_ref: 'api_credential' }, + ], + }; + + const configurationScriptPayloadsApi = { + name: 'test-workflow.asl', + payload: `{ + "States": { + "State1": { "Credentials": { "api_user": "$.api_user", "api_password": "$.api_password" } }, + "State2": { "Credentials": { "api_user": "$.api_user", "api_password": "$.api_password" } } + } + }`, + credentials: { + api_user: { credential_ref: 'api_credential', credential_field: 'userid' }, + api_password: { credential_ref: 'test_credential', credential_field: 'password' }, + }, + }; + + const configurationScriptPayloadsApi2 = { + name: 'test-workflow2.asl', + payload: `{ "States": { "State1": {}, "State2": {} } }`, + credentials: {}, + }; + + afterEach(() => { + fetchMock.reset(); + fetchMock.restore(); + }); + + it('should render mapping credentials to the workflow', async(done) => { + fetchMock.get( + `/api/authentications?expand=resources&filter[]=type='ManageIQ::Providers::Workflows::AutomationManager::WorkflowCredential'`, + authenticationsApi, + ); + fetchMock.get('/api/configuration_script_payloads/1', configurationScriptPayloadsApi); + let wrapper; + + await act(async() => { + wrapper = mount(); + }); + wrapper.update(); + expect(fetchMock.calls()).toHaveLength(2); + expect(toJson(wrapper)).toMatchSnapshot(); + done(); + }); + + it('should redirect back to show_list page if a workflow has no credentials to map', async(done) => { + fetchMock.get( + `/api/authentications?expand=resources&filter[]=type='ManageIQ::Providers::Workflows::AutomationManager::WorkflowCredential'`, + { resources: [] }, + ); + fetchMock.get('/api/configuration_script_payloads/2', configurationScriptPayloadsApi2); + let wrapper; + + await act(async() => { + wrapper = mount(); + }); + wrapper.update(); + expect(fetchMock.calls()).toHaveLength(2); + expect(toJson(wrapper)).toMatchSnapshot(); + done(); + }); +}); diff --git a/app/stylesheet/ddf_override.scss b/app/stylesheet/ddf_override.scss index 66d5e50a8cc..8c6beaf7e4f 100644 --- a/app/stylesheet/ddf_override.scss +++ b/app/stylesheet/ddf_override.scss @@ -375,6 +375,15 @@ padding-right: 30px; } +#workflow_credentials_section { + display: flex; + gap: 2%; + + .bx--form-item { + flex-basis: 0; + } +} + .bx--tabs--scrollable .bx--tabs--scrollable__nav-link { min-width: 10rem; width: fit-content; diff --git a/app/stylesheet/miq-data-table.scss b/app/stylesheet/miq-data-table.scss index bf994fcfd13..7a5c18d3573 100644 --- a/app/stylesheet/miq-data-table.scss +++ b/app/stylesheet/miq-data-table.scss @@ -170,6 +170,18 @@ } } +.credential-mapper-data-table { + tbody tr td { + .bx--front-line { + margin-top: 0%; + } + + vertical-align: middle; + padding-top: 0%; + padding-bottom: 0%; + } +} + #iso_datastore_details_div { tbody tr { .cell { diff --git a/app/views/workflow/map_credentials.html.haml b/app/views/workflow/map_credentials.html.haml new file mode 100644 index 00000000000..abed1f8c1dd --- /dev/null +++ b/app/views/workflow/map_credentials.html.haml @@ -0,0 +1,2 @@ +#main_div + = react 'WorkflowCredentialMappingForm', :recordId => @id.to_s diff --git a/config/routes.rb b/config/routes.rb index 981a74e87bd..3ea0c966987 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3188,6 +3188,7 @@ :workflow => { :get => %w[ + map_credentials download_data download_summary_pdf show