diff --git a/app/controllers/catalog_controller.rb b/app/controllers/catalog_controller.rb index 3270b793b12d..fbc34b3c6bac 100644 --- a/app/controllers/catalog_controller.rb +++ b/app/controllers/catalog_controller.rb @@ -300,6 +300,18 @@ def atomic_form_field_changed if params[:display] page << "miq_tabs_show_hide('#details_tab', '#{(params[:display] == "1")}')" end + if params[:provisioning_entry_point_type] + @edit[:new][:fqname] = "" + page.replace_html("provision_entry_point", :partial => "provision_entry_points") + end + if params[:reconfigure_entry_point_type] + @edit[:new][:reconfigure_fqname] = "" + page.replace_html("reconfigure_entry_point", :partial => "reconfigure_entry_points") + end + if params[:retirement_entry_point_type] + @edit[:new][:retire_fqname] = "" + page.replace_html("retirement_entry_point", :partial => "retirement_entry_points") + end %w[fqname reconfigure_fqname retire_fqname].each do |name| if params[name.to_sym] && @edit[:new][name.to_sym].present? page << "$('##{name}_remove').attr('disabled', false);" @@ -652,6 +664,15 @@ def ae_tree_select_toggle session[:edit] = @edit end + def embededded_workflows_modal + render :update do |page| + page << javascript_prologue + page << javascript_show(params[:type]) + page << javascript_show("#{params[:type]}_modal") + page << "$('##{params[:type]}_modal').addClass('modal fade in');" + end + end + def ae_tree_select_discard assert_new_or_edit_by_service_type diff --git a/app/controllers/workflow_controller.rb b/app/controllers/workflow_controller.rb new file mode 100644 index 000000000000..1307d945013c --- /dev/null +++ b/app/controllers/workflow_controller.rb @@ -0,0 +1,38 @@ +class WorkflowController < ApplicationController + before_action :check_privileges + before_action :get_session_data + + after_action :cleanup_action + after_action :set_session_data + + include Mixins::GenericListMixin + include Mixins::GenericSessionMixin + include Mixins::GenericShowMixin + include Mixins::BreadcrumbsMixin + + def self.model + ManageIQ::Providers::EmbeddedAnsible::AutomationManager::Playbook + #ManageIQ::Providers::Workflows::AutomationManager::Workflow + end + + def show_list + assert_privileges('embedded_workflow_view') + super + end + + def show + assert_privileges('embedded_workflow_view') + end + + def breadcrumbs_options + { + :breadcrumbs => [ + {:title => _("Automation")}, + {:title => _("Embedded Workflow")}, + {:title => _("Workflows"), :url => controller_url}, + ], + } + end + + menu_section :embedded_workflow +end diff --git a/app/controllers/workflow_credential_controller.rb b/app/controllers/workflow_credential_controller.rb new file mode 100644 index 000000000000..11f28a4c5d5d --- /dev/null +++ b/app/controllers/workflow_credential_controller.rb @@ -0,0 +1,37 @@ +class WorkflowCredentialController < ApplicationController + before_action :check_privileges + before_action :get_session_data + + after_action :cleanup_action + after_action :set_session_data + + include Mixins::GenericListMixin + include Mixins::GenericSessionMixin + include Mixins::GenericShowMixin + include Mixins::BreadcrumbsMixin + + def self.model + ManageIQ::Providers::EmbeddedAnsible::AutomationManager::Playbook + end + + def show_list + assert_privileges('embedded_workflow_credential_view') + super + end + + def show + assert_privileges('embedded_workflow_credential_view') + end + + def breadcrumbs_options + { + :breadcrumbs => [ + {:title => _("Automation")}, + {:title => _("Embedded Workflow")}, + {:title => _("Credentials"), :url => controller_url}, + ], + } + end + + menu_section :embedded_workflow +end diff --git a/app/controllers/workflow_repository_controller.rb b/app/controllers/workflow_repository_controller.rb new file mode 100644 index 000000000000..a6b89687a820 --- /dev/null +++ b/app/controllers/workflow_repository_controller.rb @@ -0,0 +1,37 @@ +class WorkflowRepositoryController < ApplicationController + before_action :check_privileges + before_action :get_session_data + + after_action :cleanup_action + after_action :set_session_data + + include Mixins::GenericListMixin + include Mixins::GenericSessionMixin + include Mixins::GenericShowMixin + include Mixins::BreadcrumbsMixin + + def self.model + ManageIQ::Providers::EmbeddedAnsible::AutomationManager::Playbook + end + + def show_list + assert_privileges('embedded_workflow_repository_view') + super + end + + def show + assert_privileges('embedded_workflow_repository_view') + end + + def breadcrumbs_options + { + :breadcrumbs => [ + {:title => _("Automation")}, + {:title => _("Embedded Workflow")}, + {:title => _("Repositories"), :url => controller_url}, + ], + } + end + + menu_section :embedded_workflow +end diff --git a/app/javascript/components/code-editor/index.js b/app/javascript/components/code-editor/index.js index 71d96b57aad3..93491e8bb49f 100644 --- a/app/javascript/components/code-editor/index.js +++ b/app/javascript/components/code-editor/index.js @@ -51,6 +51,9 @@ const CodeEditor = (props) => { { modes.map((mode) => ) } )} + { + console.log(codeMode) + } { + const [data, setData] = useState({ isLoading: true, list: {} }); + + // TODO: Change the url when the GET automated worflow list api is available. + useEffect(() => { + API.get('/api/workflows/?expand=resources') + .then((response) => { + setData({ + isLoading: false, + list: workflowsList(response), + }); + }); + }, []); + + /** Function to handle a row's click event. */ + const onSelect = (selectedItemId) => { + miqSparkleOn(); + window.location.href = `/workflow/show/${selectedItemId}`; + }; + if (data.isLoading) { + return ( +
+ +
+ ); + } + + return ( + onSelect(selectedRow.id)} + showPagination={false} + truncateText={false} + mode="automated-workflow-list" + gridChecks={[]} + /> + ); +}; + +export default WorkflowList; diff --git a/app/javascript/components/workflows/summary.jsx b/app/javascript/components/workflows/summary.jsx new file mode 100644 index 000000000000..7eded87b6a2c --- /dev/null +++ b/app/javascript/components/workflows/summary.jsx @@ -0,0 +1,131 @@ +import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { + Loading, Tabs, Tab, +} from 'carbon-components-react'; +import AWSSfnGraph from '@tshepomgaga/aws-sfn-graph'; +import { Controlled as CodeMirror } from 'react-codemirror2'; +import MiqStructuredList from '../miq-structured-list'; +import { workflowData } from './workflows-dummy-data'; +import '@tshepomgaga/aws-sfn-graph/index.css'; + +const WorkflowSummary = ({ recordId }) => { + const tabLabels = [ + { name: 'text', text: __('Text') }, + { name: 'graph', text: __('Graph') }, + ]; + const [data, setData] = useState({ + isLoading: true, + summary: [], + jsonData: {}, + activeTab: tabLabels[0].name, + }); + + const onClickHandler = (data) => console.log('data=', data); + + // TODO: Change the url when the GET automated worflow summary for recordId api is available. + useEffect(() => { + API.get(`/api/workflows/${recordId}?expand=resources`) + .then((response) => { + const { summary, jsonData } = workflowData(response); + setData({ + isLoading: false, + summary, + jsonData, + }); + }); + }, [recordId]); + + if (data.isLoading) { + return ( +
+ +
+ ); + } + + /** Function to set the active tab name */ + const onTabSelect = (name) => { + setData({ + ...data, + activeTab: name, + }); + }; + + /** Function to render the code mirror component */ + const error = false; + const renderCodeSnippet = () => ( + + ); + + /** Function to render the graph. */ + const renderGraph = () => ( + console.log('error information', error)} + /> + ); + + /** Function to render various tab contents based on selected tab. */ + const renderTabContents = () => { + switch (data.activeTab) { + case tabLabels[0].name: return renderCodeSnippet(); + case tabLabels[1].name: return renderGraph(); + default: return renderCodeSnippet(); + } + }; + + /** Function to render the tab sections + * TODO: This can be moved to a new component. + */ + const renderTabsSection = () => ( + + { + tabLabels.map(({ name, text }) => ( + onTabSelect(name)}> + { renderTabContents()} + + )) + } + + ); + + /** Function to render the summary */ + const renderSummarySection = () => ( + onClickHandler(data)} + /> + ); + + return ( + <> + {renderSummarySection()} + {renderTabsSection()} + + ); +}; + +WorkflowSummary.propTypes = { + recordId: PropTypes.string.isRequired, +}; + +export default WorkflowSummary; diff --git a/app/javascript/components/workflows/workflow-entry-points.js b/app/javascript/components/workflows/workflow-entry-points.js new file mode 100644 index 000000000000..31af74d198d4 --- /dev/null +++ b/app/javascript/components/workflows/workflow-entry-points.js @@ -0,0 +1,81 @@ +import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { Button, Loading } from 'carbon-components-react'; +import MiqDataTable from '../miq-data-table'; +import { workflowsEntryPoints } from './workflows-dummy-data'; + +const WorkflowEntryPoints = ({ fieldName, entryPoint }) => { + const [data, setData] = useState({ isLoading: true, list: {}, selectedItemId: undefined }); + + // TODO: Change the url when the GET automated worflow list api is available. + useEffect(() => { + API.get('/api/workflows/?expand=resources') + .then((response) => { + setData({ + isLoading: false, + list: workflowsEntryPoints(response), + }); + }); + }, []); + + /** Function to handle a row's click event. */ + const onSelect = (selectedItemId) => { + setData({ + ...data, + selectedItemId: (data.selectedItemId === selectedItemId) ? undefined : selectedItemId, + }); + }; + if (data.isLoading) { + return ( +
+ +
+ ); + } + const onCloseModal = () => { + document.getElementById(entryPoint).style.display = 'none'; + document.getElementById(`${entryPoint}_modal`).style.display = 'none'; + }; + const onApply = () => { + const seletedItem = data.list.rows.find((item) => item.id === data.selectedItemId); + document.getElementById(fieldName).value = seletedItem.name.text; + onCloseModal(); + }; + return ( + <> + onSelect(selectedRow.id)} + showPagination={false} + truncateText={false} + mode="automated-workflow-entry-points" + gridChecks={[data.selectedItemId]} + /> +
+ + + +
+ + + + ); +}; +WorkflowEntryPoints.propTypes = { + entryPoint: PropTypes.string.isRequired, + fieldName: PropTypes.string.isRequired, +}; + +export default WorkflowEntryPoints; diff --git a/app/javascript/components/workflows/workflows-dummy-data.js b/app/javascript/components/workflows/workflows-dummy-data.js new file mode 100644 index 000000000000..f2b3f140ab9b --- /dev/null +++ b/app/javascript/components/workflows/workflows-dummy-data.js @@ -0,0 +1,159 @@ +/* ********************************************************** + * This file contains dummy data for List and Summary Page. + * This file can be removed when we have API's in place. + * ********************************************************** / + +/** Dummy data for List Page */ +import { rowData } from '../miq-data-table/helper'; + +/** Note: + * We are restructuing data like this because this is how we get the data from backend. + * We can always use a new component and simplify this. + */ + +/** Function to return the header information for the list */ +const headerInfo = () => [ + { header: __('Name'), key: 'name' }, +]; + +/** Function to return the header information for the service catalog item's entry points. */ +const entryPointsHeaderInfo = () => [ + { header: __('Workflows'), key: 'name' }, +]; + +/** Function to return the cell data for a row item. */ +const celInfo = (workflow) => [ + { text: workflow.name }, +]; + +/** Function to return the row information for the list */ +const rowInfo = (headers, response) => { + const headerKeys = headers.map((item) => item.key); + const rows = response.resources.map((workflow) => ({ + id: workflow.id.toString(), cells: celInfo(workflow), clickable: true, + })); + // const rows = [...Array(50)].map((_item, index) => ({ + // id: index.toString(), cells: celInfo(index), clickable: true, + // })); + const miqRows = rowData(headerKeys, rows, false); + return miqRows.rowItems; +}; + +/** Function to return the dummy data for automated workflows + * This data is used in data table list. +*/ +export const workflowsList = (response) => { + const headers = headerInfo(); + return { headers, rows: rowInfo(headers, response) }; +}; + +export const workflowsEntryPoints = (response) => { + const headers = entryPointsHeaderInfo(); + return { headers, rows: rowInfo(headers, response) }; +}; + +/** Dummy data for Summary Page */ + +const summary = (response) => ([ + { label: __('Name'), value: response.name }, + { label: __('Ems ID'), value: response.ems_id }, + { label: __('Created at'), value: response.created_at }, +]); + +/** Not being used and can be removed. */ +export const jsonData = ` { + "Comment": "An example of the Amazon States Language using a choice state.", + "StartAt": "FirstState", + "States": { + "FirstState": { + "Type": "Task", + "Resource": "docker://agrare/hello-world:latest", + "Credentials": { + "mysecret": "dont tell anyone" + }, + "Retry": [ + { + "ErrorEquals": [ "States.Timeout" ], + "IntervalSeconds": 3, + "MaxAttempts": 2, + "BackoffRate": 1.5 + } + ], + "Catch": [ + { + "ErrorEquals": [ "States.ALL" ], + "Next": "FailState" + } + ], + "Next": "ChoiceState" + }, + + "ChoiceState": { + "Type" : "Choice", + "Choices": [ + { + "Variable": "$.foo", + "NumericEquals": 1, + "Next": "FirstMatchState" + }, + { + "Variable": "$.foo", + "NumericEquals": 2, + "Next": "SecondMatchState" + }, + { + "Variable": "$.foo", + "NumericEquals": 3, + "Next": "SuccessState" + } + ], + "Default": "FailState" + }, + + "FirstMatchState": { + "Type" : "Task", + "Resource": "docker://agrare/hello-world:latest", + "Next": "PassState" + }, + + "SecondMatchState": { + "Type" : "Task", + "Resource": "docker://agrare/hello-world:latest", + "Next": "NextState" + }, + + "PassState": { + "Type": "Pass", + "Result": { + "foo": "bar", + "bar": "baz" + }, + "ResultPath": "$.result", + "Next": "NextState" + }, + + "FailState": { + "Type": "Fail", + "Error": "FailStateError", + "Cause": "No Matches!" + }, + + "SuccessState": { + "Type": "Succeed" + }, + + "NextState": { + "Type": "Task", + "Resource": "docker://agrare/hello-world:latest", + "Secrets": ["vmdb:aaa-bbb-ccc"], + "End": true + } + } +}`; + +export const workflowData = (response) => ( + { + summary: summary(response), + jsonData: response.workflow_content, + } +); diff --git a/app/javascript/oldjs/controllers/dialog_editor/dialog_editor_controller.js b/app/javascript/oldjs/controllers/dialog_editor/dialog_editor_controller.js index e62addaf7960..0df25e6ac063 100644 --- a/app/javascript/oldjs/controllers/dialog_editor/dialog_editor_controller.js +++ b/app/javascript/oldjs/controllers/dialog_editor/dialog_editor_controller.js @@ -8,6 +8,7 @@ ManageIQ.angular.app.controller('dialogEditorController', ['$window', 'miqServic // options for tree selector vm.treeOptions = { load: DialogEditorHttp.treeSelectorLoadData, + loadWorkflows: DialogEditorHttp.treeSelectorLoadWorkflows, lazyLoad: DialogEditorHttp.treeSelectorLazyLoadData, }; diff --git a/app/javascript/oldjs/miq_application.js b/app/javascript/oldjs/miq_application.js index d85e9572961e..109a02bc0624 100644 --- a/app/javascript/oldjs/miq_application.js +++ b/app/javascript/oldjs/miq_application.js @@ -954,6 +954,12 @@ window.miqShowAE_Tree = function(typ) { return true; }; +window.miqShowEmbededdedWorkflowsModal = function(type) { + const url = `/${ManageIQ.controller}/embededded_workflows_modal`; + miqJqueryRequest(miqPassFields(url, { type })); + return true; +}; + // Toggle the user options div in the page header (:onclick from layouts/user_options) window.miqChangeGroup = function(id) { miqSparkleOn(); diff --git a/app/javascript/oldjs/services/dialog_editor_http_service.js b/app/javascript/oldjs/services/dialog_editor_http_service.js index 6ccf6b15b700..2492cbd139b7 100644 --- a/app/javascript/oldjs/services/dialog_editor_http_service.js +++ b/app/javascript/oldjs/services/dialog_editor_http_service.js @@ -19,6 +19,14 @@ ManageIQ.angular.app.service('DialogEditorHttp', ['$http', 'API', function($http }); }; + this.treeSelectorLoadWorkflows = function(fqname) { + var url = '/api/workflows/?expand=resources' + (fqname ? '?fqname=' + encodeURIComponent(fqname) : ''); + return $http.get(url).then(function(response) { + return response.data; + }); + }; + + this.treeSelectorLazyLoadData = function(node) { return $http.get('/tree/automate_entrypoint?id=' + encodeURIComponent(node.key)).then(function(response) { return response.data; diff --git a/app/javascript/packs/component-definitions-common.js b/app/javascript/packs/component-definitions-common.js index 5460c5b83301..d1ac92dc8013 100644 --- a/app/javascript/packs/component-definitions-common.js +++ b/app/javascript/packs/component-definitions-common.js @@ -10,6 +10,9 @@ import AggregateStatusCard from '../components/aggregate_status_card'; import AnsibleCredentialsForm from '../components/ansible-credentials-form'; import AnsibleRepositoryForm from '../components/ansible-repository-form'; import AuthKeypairCloudForm from '../components/auth-key-pair-cloud'; +import WorkflowEntryPoints from '../components/workflows/workflow-entry-points'; +import WorkflowList from '../components/workflows'; +import WorkflowSummary from '../components/workflows/summary'; import { BreadcrumbsBar } from '../components/breadcrumbs'; import ButtonList from '../components/data-tables/button-list'; import ButtonGroupList from '../components/data-tables/button-group-list'; @@ -166,6 +169,9 @@ ManageIQ.component.addReact('AggregateStatusCard', AggregateStatusCard); ManageIQ.component.addReact('AnsibleCredentialsForm', AnsibleCredentialsForm); ManageIQ.component.addReact('AnsibleRepositoryForm', AnsibleRepositoryForm); ManageIQ.component.addReact('AuthKeypairCloudForm', AuthKeypairCloudForm); +ManageIQ.component.addReact('WorkflowEntryPoints', WorkflowEntryPoints); +ManageIQ.component.addReact('WorkflowList', WorkflowList); +ManageIQ.component.addReact('WorkflowSummary', WorkflowSummary); ManageIQ.component.addReact('BreadcrumbsBar', BreadcrumbsBar); ManageIQ.component.addReact('ButtonList', ButtonList); ManageIQ.component.addReact('ButtonGroupList', ButtonGroupList); diff --git a/app/presenters/menu/default_menu.rb b/app/presenters/menu/default_menu.rb index 3d93db7740db..3fc88e86f442 100644 --- a/app/presenters/menu/default_menu.rb +++ b/app/presenters/menu/default_menu.rb @@ -188,6 +188,7 @@ def automation_menu_section automation_manager_menu_section, configuration_menu_section, ansible_menu_section, + workflow_menu_section, automate_menu_section, ]) end @@ -209,6 +210,14 @@ def ansible_menu_section ]) end + def workflow_menu_section + Menu::Section.new(:embedded_workflow_automation_manager, N_("Embedded Workflow"), nil, [ + Menu::Item.new('embedded_workflow', N_('Workflows'), 'embedded_workflow', {:feature => 'embedded_workflow_view', :any => true}, '/workflow/show_list'), + Menu::Item.new('embedded_workflow_repository', N_('Repositories'), 'embedded_workflow_repository', {:feature => 'embedded_workflow_repository_view', :any => true}, '/workflow_repository/show_list'), + Menu::Item.new('embedded_workflow_credential', N_('Credentials'), 'embedded_workflowcredentials', {:feature => 'embedded_workflow_credential_view', :any => true}, '/workflow_credential/show_list'), + ]) + end + def automate_menu_section Menu::Section.new(:automate, N_("Embedded Automate"), nil, [ Menu::Item.new('miq_ae_class', N_('Explorer'), 'miq_ae_class_explorer', {:feature => 'miq_ae_domain_view'}, '/miq_ae_class/explorer'), diff --git a/app/stylesheet/legacy/miq_tree.scss b/app/stylesheet/legacy/miq_tree.scss index d52b819f770c..efe9cf752752 100644 --- a/app/stylesheet/legacy/miq_tree.scss +++ b/app/stylesheet/legacy/miq_tree.scss @@ -35,3 +35,46 @@ cursor: default !important; } } + +ul.nav.nav-list.workflows { + background-color: #f5f5f5; + border: 1px solid #e3e3e3; + + li { + border-bottom: 1px solid lightgray; + padding: 10px; + } +} + +.tree_selector_wrapper { + display: flex; + flex-direction: column; + border: 1px solid lightgray; + margin-top: 10px; + + .tree_selector_title_wrapper { + display: flex; + flex-direction: row; + background: lightgray; + padding: 5px; + + .tree_selector_dialog_title { + display: flex; + flex-grow: 1; + font-weight: bold; + padding: 5px 0; + font-size: 14px; + } + .tree_selector_action { + display: flex; + justify-content: center; + align-items: center; + min-width: 30px; + } + } + .tree_selector_content_wrapper { + display: flex; + flex-direction: column; + padding: 10px; + } +} diff --git a/app/views/catalog/_embedded_workflows_modal.html.haml b/app/views/catalog/_embedded_workflows_modal.html.haml new file mode 100644 index 000000000000..5d9a615d0dd5 --- /dev/null +++ b/app/views/catalog/_embedded_workflows_modal.html.haml @@ -0,0 +1,23 @@ +.modal.fade{"tabindex" => "-1", + "id" => "#{entry_point}_modal", + "role" => "dialog", + "aria-labelledby" => "ep_modal_label", + "aria-describedby" => "modal", + "aria-hidden" => "true", + "data-keyboard" => "false", + "data-backdrop" => "static", + :style => "display: none"} + .modal-dialog.modal-lg + .modal-content + .modal-header + %button.close{"data-dismiss" => "modal"} + %span{"aria-hidden" => "true"} + × + %span.sr-only + Close + %h4.modal-title + = _("Select an embededded workflow for provisioning entry point") + + .modal-body + = react('WorkflowEntryPoints', {:fieldName => field_name, :entryPoint => entry_point}) + .modal-footer diff --git a/app/views/catalog/_form_basic_info.html.haml b/app/views/catalog/_form_basic_info.html.haml index f19e9fcae47e..ab4e8156b955 100644 --- a/app/views/catalog/_form_basic_info.html.haml +++ b/app/views/catalog/_form_basic_info.html.haml @@ -1,5 +1,7 @@ - url = url_for(:id => "#{@edit[:rec_id] || "new"}", :action => @edit[:new][:service_type] == "composite" ? "st_form_field_changed" : "atomic_form_field_changed") +- entry_point_options = [["<#{_('No Entry Point')}>", nil], ["Embedded Automate", "embedded_automate"], ["Embedded Workflows", "embedded_workflows"]] + %h3= _('Basic Information') #basic_info_div @@ -163,67 +165,50 @@ .form-group %label.col-md-2.control-label{:title => _("Provisioning Entry Point (NameSpace/Class/Instance)")} = _('Provisioning Entry Point') - .col-md-8{:title => @edit[:new][:fqname]} - .input-group - = text_field_tag("fqname", - @edit[:new][:fqname], - :class => "form-control", - "data-miq_observe" => {:interval => '.5', :url => url}.to_json) - %span.input-group-btn - #fqname_div - %button.btn.btn-default{"onclick" => "miqShowAE_Tree('provision'); miqButtons('hide', 'automate');", - "title" => _('Click to select Provisioning Entry Point')} - %i.ff.ff-load-balancer - %button.btn.btn-default{"id" => "fqname_remove", - "onclick" => "miqAjax('#{url_for_only_path(:action => 'ae_tree_select_discard', :typ => 'provision')}');", - "title" => _('Remove this Provisioning Entry Point'), - "data-confirm" => _("Are you sure you want to remove this Provisioning Entry Point?"), - "disabled" => @edit[:new][:fqname].nil?} - %i.pficon.pficon-close - %span.input-group-addon{:style => "visibility:hidden"} + .col-md-8{:style => "padding: 0px;"} + .col-md-4{:title => @edit[:new][:fqname]} + %p.form-control-static + = select_tag('provisioning_entry_point_type', + options_for_select(entry_point_options, @edit[:new][:provisioning_entry_point_type]), + "data-miq_sparkle_on" => true, + :class => "selectpicker") + :javascript + miqInitSelectPicker(); + miqSelectPickerEvent('provisioning_entry_point_type', '#{url}') + .col-md-8#provision_entry_point{:style => "padding: 0px;"} + = render(:partial => "provision_entry_points") - unless %w[generic_container_template generic_ovf_template].include?(@edit[:new][:st_prov_type]) .form-group %label.col-md-2.control-label{:title => _("Reconfigure Entry Point (NameSpace/Class/Instance)")} = _('Reconfigure Entry Point') - .col-md-8{:title => @edit[:new][:reconfigure_fqname]} - .input-group - = text_field_tag("reconfigure_fqname", - @edit[:new][:reconfigure_fqname], - :class => "form-control", - "data-miq_observe" => {:interval => '.5', :url => url}.to_json) - %span.input-group-btn - #reconfigure_fqname_div - %button.btn.btn-default{"onclick" => "miqShowAE_Tree('reconfigure'); miqButtons('hide', 'automate');", - "title" => _('Click to select Reconfigure Entry Point')} - %i.ff.ff-load-balancer - %button.btn.btn-default{"id" => "reconfigure_fqname_remove", - "onclick" => "miqAjax('#{url_for_only_path(:action => 'ae_tree_select_discard', :typ => 'reconfigure')}');", - "title" => _('Remove this Reconfigure Entry Point'), - "data-confirm" => _("Are you sure you want to remove this Reconfigure Entry Point?"), - "disabled" => @edit[:new][:reconfigure_fqname].nil?} - %i.pficon.pficon-close - %span.input-group-addon{:style => "visibility:hidden"} + .col-md-8{:style => "padding: 0px;"} + .col-md-4{:title => @edit[:new][:reconfigure_fqname]} + %p.form-control-static + = select_tag('reconfigure_entry_point_type', + options_for_select(entry_point_options, @edit[:new][:reconfigure_entry_point_type]), + "data-miq_sparkle_on" => true, + :class => "selectpicker") + :javascript + miqInitSelectPicker(); + miqSelectPickerEvent('reconfigure_entry_point_type', '#{url}') + .col-md-8#reconfigure_entry_point{:style => "padding: 0px;"} + = render(:partial => "reconfigure_entry_points") .form-group %label.col-md-2.control-label{:title => _("Retirement Entry Point (NameSpace/Class/Instance)")} = _('Retirement Entry Point') - .col-md-8{:title => @edit[:new][:retire_fqname]} - .input-group - = text_field_tag("retire_fqname", - @edit[:new][:retire_fqname], - :class => "form-control", - "data-miq_observe" => {:interval => '.5', :url => url}.to_json) - %span.input-group-btn - #retire_fqname_div - %button.btn.btn-default{"onclick" => "miqShowAE_Tree('retire'); miqButtons('hide', 'automate');", - "title" => _('Click to select Retirement Entry Point')} - %i.ff.ff-load-balancer - %button.btn.btn-default{"id" => "retire_fqname_remove", - "onclick" => "miqAjax('#{url_for_only_path(:action => 'ae_tree_select_discard', :typ => 'retire')}');", - "title" => _('Remove this Retirement Entry Point'), - "data-confirm" => _("Are you sure you want to remove this Retirement Entry Point?"), - "disabled" => @edit[:new][:retire_fqname].nil?} - %i.pficon.pficon-close - %span.input-group-addon{:style => "visibility:hidden"} + .col-md-8{:style => "padding: 0px;"} + .col-md-4{:title => @edit[:new][:fqname]} + %p.form-control-static + = select_tag('retirement_entry_point_type', + options_for_select(entry_point_options, @edit[:new][:retire_fqname]), + "data-miq_sparkle_on" => true, + :class => "selectpicker") + :javascript + miqInitSelectPicker(); + miqSelectPickerEvent('retirement_entry_point_type', '#{url}') + .col-md-8#retirement_entry_point{:style => "padding: 0px;"} + = render(:partial => "retirement_entry_points") + - if role_allows?(:feature => 'rbac_tenant_view') = render(:partial => "tenants_tree_show") - if @edit[:new][:st_prov_type] == "generic_ovf_template" diff --git a/app/views/catalog/_provision_entry_points.html.haml b/app/views/catalog/_provision_entry_points.html.haml new file mode 100644 index 000000000000..94f88af5b731 --- /dev/null +++ b/app/views/catalog/_provision_entry_points.html.haml @@ -0,0 +1,29 @@ +- if params[:provisioning_entry_point_type] && params[:provisioning_entry_point_type] != "nil" + - url = url_for(:id => "#{@edit[:rec_id] || "new"}", + :action => @edit[:new][:service_type] == "composite" ? "st_form_field_changed" : "atomic_form_field_changed") + + - if params[:provisioning_entry_point_type] == "embedded_workflows" + = hidden_div_if(@edit, :id => "provision_entry_workflows") do + = render(:partial => 'embedded_workflows_modal', :locals => {:field_name => :fqname, :entry_point => "provision_entry_workflows"}) + - modal = {:open => "miqShowEmbededdedWorkflowsModal('provision_entry_workflows'); miqButtons('hide', 'automate');",:close => "miqAjax('#{url_for_only_path(:action => 'ae_tree_select_discard', :typ => 'provision')}');"} + - elsif params[:provisioning_entry_point_type] == "embedded_automate" + - modal = {:open => "miqShowAE_Tree('provision'); miqButtons('hide', 'automate');", :close => "miqAjax('#{url_for_only_path(:action => 'ae_tree_select_discard', :typ => 'provision')}');"} + + .col-md-12{:title => @edit[:new][:fqname], :style => "padding: 3px;"} + .input-group + = text_field_tag("fqname", + @edit[:new][:fqname], + :class => "form-control", + "data-miq_observe" => {:interval => '.5', :url => url}.to_json) + %span.input-group-btn + #fqname_div + %button.btn.btn-default{"onclick" => modal[:open], + "title" => _('Click to select Provisioning Entry Point')} + %i.ff.ff-load-balancer + %button.btn.btn-default{"id" => "fqname_remove", + "onclick" => modal[:close], + "title" => _('Remove this Provisioning Entry Point'), + "data-confirm" => _("Are you sure you want to remove this Provisioning Entry Point?"), + "disabled" => @edit[:new][:fqname].nil?} + %i.pficon.pficon-close + %span.input-group-addon{:style => "visibility:hidden"} diff --git a/app/views/catalog/_reconfigure_entry_points.html.haml b/app/views/catalog/_reconfigure_entry_points.html.haml new file mode 100644 index 000000000000..5d729e46f5bf --- /dev/null +++ b/app/views/catalog/_reconfigure_entry_points.html.haml @@ -0,0 +1,29 @@ +- if params[:reconfigure_entry_point_type] && params[:reconfigure_entry_point_type] != "nil" + - url = url_for(:id => "#{@edit[:rec_id] || "new"}", + :action => @edit[:new][:service_type] == "composite" ? "st_form_field_changed" : "atomic_form_field_changed") + + - if params[:reconfigure_entry_point_type] == "embedded_workflows" + = hidden_div_if(@edit, :id => "reconfigure_entry_workflows") do + = render(:partial => 'embedded_workflows_modal', :locals => {:field_name => :reconfigure_fqname, :entry_point => "reconfigure_entry_workflows" }) + - modal = {:open => "miqShowEmbededdedWorkflowsModal('reconfigure_entry_workflows'); miqButtons('hide', 'automate');",:close => "miqAjax('#{url_for_only_path(:action => 'ae_tree_select_discard', :typ => 'provision')}');"} + - elsif params[:reconfigure_entry_point_type] == "embedded_automate" + - modal = {:open => "miqShowAE_Tree('reconfigure'); miqButtons('hide', 'automate');", :close => "miqAjax('#{url_for_only_path(:action => 'ae_tree_select_discard', :typ => 'reconfigure')}');"} + + .col-md-12{:title => @edit[:new][:reconfigure_fqname], :style => "padding: 3px;"} + .input-group + = text_field_tag("reconfigure_fqname", + @edit[:new][:reconfigure_fqname], + :class => "form-control", + "data-miq_observe" => {:interval => '.5', :url => url}.to_json) + %span.input-group-btn + #reconfigure_fqname_div + %button.btn.btn-default{"onclick" => modal[:open], + "title" => _('Click to select Reconfigure Entry Point')} + %i.ff.ff-load-balancer + %button.btn.btn-default{"id" => "reconfigure_fqname_remove", + "onclick" => modal[:close], + "title" => _('Remove this Reconfigure Entry Point'), + "data-confirm" => _("Are you sure you want to remove this Reconfigure Entry Point?"), + "disabled" => @edit[:new][:reconfigure_fqname].nil?} + %i.pficon.pficon-close + %span.input-group-addon{:style => "visibility:hidden"} diff --git a/app/views/catalog/_retirement_entry_points.html.haml b/app/views/catalog/_retirement_entry_points.html.haml new file mode 100644 index 000000000000..aa66b7434f5f --- /dev/null +++ b/app/views/catalog/_retirement_entry_points.html.haml @@ -0,0 +1,29 @@ +- if params[:retirement_entry_point_type] && params[:retirement_entry_point_type] != "nil" + - url = url_for(:id => "#{@edit[:rec_id] || "new"}", + :action => @edit[:new][:service_type] == "composite" ? "st_form_field_changed" : "atomic_form_field_changed") + + - if params[:retirement_entry_point_type] == "embedded_workflows" + = hidden_div_if(@edit, :id => "retirement_entry_workflows") do + = render(:partial => 'embedded_workflows_modal', :locals => {:field_name => :retire_fqname, :entry_point => "retirement_entry_workflows" }) + - modal = {:open => "miqShowEmbededdedWorkflowsModal('retirement_entry_workflows'); miqButtons('hide', 'automate');",:close => "miqAjax('#{url_for_only_path(:action => 'ae_tree_select_discard', :typ => 'provision')}');"} + - elsif params[:retirement_entry_point_type] == "embedded_automate" + - modal = {:open => "miqShowAE_Tree('retire'); miqButtons('hide', 'automate');", :close => "miqAjax('#{url_for_only_path(:action => 'ae_tree_select_discard', :typ => 'retire')}');"} + + .col-md-12{:title => @edit[:new][:retire_fqname], :style => "padding: 3px;"} + .input-group + = text_field_tag("retire_fqname", + @edit[:new][:retire_fqname], + :class => "form-control", + "data-miq_observe" => {:interval => '.5', :url => url}.to_json) + %span.input-group-btn + #retire_fqname_div + %button.btn.btn-default{"onclick" => modal[:open], + "title" => _('Click to select Reconfigure Entry Point')} + %i.ff.ff-load-balancer + %button.btn.btn-default{"id" => "retire_fqname_remove", + "onclick" => modal[:close], + "title" => _('Remove this Reconfigure Entry Point'), + "data-confirm" => _("Are you sure you want to remove this Retirement Entry Point?"), + "disabled" => @edit[:new][:retire_fqname].nil?} + %i.pficon.pficon-close + %span.input-group-addon{:style => "visibility:hidden"} diff --git a/app/views/workflow/show.html.haml b/app/views/workflow/show.html.haml new file mode 100644 index 000000000000..cf47915a5b84 --- /dev/null +++ b/app/views/workflow/show.html.haml @@ -0,0 +1,2 @@ +#main_div + = react('WorkflowSummary', {:recordId => params[:id]}) diff --git a/app/views/workflow/show_list.html.haml b/app/views/workflow/show_list.html.haml new file mode 100644 index 000000000000..a9ceba83ceeb --- /dev/null +++ b/app/views/workflow/show_list.html.haml @@ -0,0 +1,2 @@ +#main_div + = react('WorkflowList') diff --git a/app/views/workflow_credential/show.html.haml b/app/views/workflow_credential/show.html.haml new file mode 100644 index 000000000000..6ed558ea9bf3 --- /dev/null +++ b/app/views/workflow_credential/show.html.haml @@ -0,0 +1 @@ += "Show credential" diff --git a/app/views/workflow_credential/show_list.html.haml b/app/views/workflow_credential/show_list.html.haml new file mode 100644 index 000000000000..9aa1226b1a14 --- /dev/null +++ b/app/views/workflow_credential/show_list.html.haml @@ -0,0 +1 @@ += "Show credential list" diff --git a/app/views/workflow_repository/show.html.haml b/app/views/workflow_repository/show.html.haml new file mode 100644 index 000000000000..94a81181eabf --- /dev/null +++ b/app/views/workflow_repository/show.html.haml @@ -0,0 +1 @@ += "Show repository" diff --git a/app/views/workflow_repository/show_list.html.haml b/app/views/workflow_repository/show_list.html.haml new file mode 100644 index 000000000000..d0483341947d --- /dev/null +++ b/app/views/workflow_repository/show_list.html.haml @@ -0,0 +1 @@ += "Show repository list" diff --git a/config/routes.rb b/config/routes.rb index 6122aa82b418..c9733b8a7f0a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -314,6 +314,7 @@ atomic_st_edit automate_button_field_changed playbook_options_field_changed + embededded_workflows_modal explorer group_create group_reorder_field_changed @@ -3182,6 +3183,27 @@ snap_post }, + :workflow => { + :get => %w( + show_list + show + ), + }, + + :workflow_credential => { + :get => %w( + show_list + show + ), + }, + + :workflow_repository => { + :get => %w( + show_list + show + ), + }, + :firmware_registry => { :get => %w[ download_data diff --git a/package.json b/package.json index d115b67cb9a0..8f1cad512b50 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@manageiq/ui-components": "1.4.4", "@novnc/novnc": "~1.2.0", "@pf3/select": "~1.12.6", + "@tshepomgaga/aws-sfn-graph": "0.0.6", "actioncable": "^5.2.4-2", "angular": "~1.6.6", "angular-animate": "~1.6.6", diff --git a/spec/config/routes.pending.yml b/spec/config/routes.pending.yml index a7901fdd1233..07a1461f749d 100644 --- a/spec/config/routes.pending.yml +++ b/spec/config/routes.pending.yml @@ -1460,3 +1460,12 @@ VmOrTemplateController: - x_show VolumeMappingController: - index +WorkflowController: +- report_data +- index +WorkflowCredentialController: +- report_data +- index +WorkflowRepositoryController: +- report_data +- index diff --git a/yarn.lock b/yarn.lock index 319ea3123c43..78e1ab7ae2d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2057,6 +2057,13 @@ __metadata: languageName: node linkType: hard +"@tshepomgaga/aws-sfn-graph@npm:0.0.6": + version: 0.0.6 + resolution: "@tshepomgaga/aws-sfn-graph@npm:0.0.6" + checksum: 1e8263472c2ec0579d459c04fcd9aa1e5cbc37c07dfaa594b1f1c295603db6d97741e5629b2a60da8e7ce04a1899b4cf0afe1e625ff43a27d877d1495e6df258 + languageName: node + linkType: hard + "@types/babel__core@npm:^7.1.0, @types/babel__core@npm:^7.1.7": version: 7.20.0 resolution: "@types/babel__core@npm:7.20.0" @@ -11377,6 +11384,7 @@ fsevents@^1.2.7: "@manageiq/ui-components": 1.4.4 "@novnc/novnc": ~1.2.0 "@pf3/select": ~1.12.6 + "@tshepomgaga/aws-sfn-graph": 0.0.6 actioncable: ^5.2.4-2 angular: ~1.6.6 angular-animate: ~1.6.6