diff --git a/app/assets/javascripts/controllers/ems_common/ems_common_form_controller.js b/app/assets/javascripts/controllers/ems_common/ems_common_form_controller.js index 64dc4acadc9..b0b3321fae9 100644 --- a/app/assets/javascripts/controllers/ems_common/ems_common_form_controller.js +++ b/app/assets/javascripts/controllers/ems_common/ems_common_form_controller.js @@ -245,7 +245,7 @@ ManageIQ.angular.app.controller('emsCommonFormController', ['$http', '$scope', ' $scope.emsCommonModel.metrics_verify != '' && $scope.angularForm.metrics_verify.$valid)) { return true; } else if($scope.currentTab == "default" && - ["ems_container", "ems_middleware", "ems_datawarehouse" ].indexOf($scope.emsCommonModel.ems_controller) >= 0 && + ["ems_container", "ems_middleware", "ems_datawarehouse", "ems_physical_infra"].indexOf($scope.emsCommonModel.ems_controller) >= 0 && ($scope.emsCommonModel.emstype) && ($scope.emsCommonModel.default_hostname != '' && $scope.emsCommonModel.default_api_port) && ($scope.emsCommonModel.default_password != '' && $scope.angularForm.default_password.$valid) && diff --git a/app/assets/javascripts/controllers/physical_infra_topology/physical_infra_topology_controller.js b/app/assets/javascripts/controllers/physical_infra_topology/physical_infra_topology_controller.js new file mode 100644 index 00000000000..55d99087236 --- /dev/null +++ b/app/assets/javascripts/controllers/physical_infra_topology/physical_infra_topology_controller.js @@ -0,0 +1,284 @@ +/* global miqHttpInject */ + +miqHttpInject(angular.module('physicalInfraTopologyApp', ['kubernetesUI', 'ui.bootstrap', 'ManageIQ'])) + .controller('physicalInfraTopologyController', physicalInfraTopologyCtrl); + +physicalInfraTopologyCtrl.$inject = ['$scope', '$http', '$interval', '$location', 'topologyService', 'miqService']; + +function physicalInfraTopologyCtrl($scope, $http, $interval, $location, topologyService, miqService) { + var self = this; + $scope.vs = null; + var icons = null; + + var d3 = window.d3; + $scope.refresh = function() { + var id; + if ($location.absUrl().match("show/$") || $location.absUrl().match("show$")) { + id = ''; + } else { + id = '/' + (/physical_infra_topology\/show\/(\d+)/.exec($location.absUrl())[1]); + } + + var url = '/physical_infra_topology/data' + id; + + $http.get(url) + .then(getPhysicalInfraTopologyData) + .catch(miqService.handleFailure); + }; + + $scope.checkboxModel = { + value: false + }; + + $scope.legendTooltip = __("Click here to show/hide entities of this type"); + + $scope.show_hide_names = function() { + var vertices = $scope.vs; + + if ($scope.checkboxModel.value) { + vertices.selectAll("text.attached-label") + .classed("visible", true); + } else { + vertices.selectAll("text.attached-label") + .classed("visible", false); + } + }; + + $scope.refresh(); + var promise = $interval($scope.refresh, 1000 * 60 * 3); + + $scope.$on('$destroy', function() { + $interval.cancel(promise); + }); + + var contextMenuShowing = false; + + d3.select("body").on('click', function() { + if(contextMenuShowing) { + removeContextMenu(); + } + }); + + var removeContextMenu = function() { + d3.event.preventDefault(); + d3.select(".popup").remove(); + contextMenuShowing = false; + }; + + self.contextMenu = function contextMenu(_that, data) { + if(contextMenuShowing) { + removeContextMenu(); + } else { + d3.event.preventDefault(); + + var canvas = d3.select("kubernetes-topology-graph"); + var mousePosition = d3.mouse(canvas.node()); + + var popup = canvas.append("div") + .attr("class", "popup") + .style("left", mousePosition[0] + "px") + .style("top", mousePosition[1] + "px"); + popup.append("h5").text("Actions on " + data.item.display_kind); + + buildContextMenuOptions(popup, data); + + var canvasSize = [ + canvas.node().offsetWidth, + canvas.node().offsetHeight + ]; + + var popupSize = [ + popup.node().offsetWidth, + popup.node().offsetHeight + ]; + + if (popupSize[0] + mousePosition[0] > canvasSize[0]) { + popup.style("left", "auto"); + popup.style("right", 0); + } + + if (popupSize[1] + mousePosition[1] > canvasSize[1]) { + popup.style("top", "auto"); + popup.style("bottom", 0); + } + contextMenuShowing = !contextMenuShowing; + } + }; + + var buildContextMenuOptions = function(popup, data) { + if (data.item.kind == "Tag") { + return false; + } + + topologyService.addContextMenuOption(popup, __("Go to summary page"), data, self.dblclick); + }; + + $scope.$on("render", function(ev, vertices, added) { + /* + * We are passed two selections of elements: + * vertices: All the elements + * added: Just the ones that were added + */ + + added.attr("class", function(d) { + return d.item.kind; + }); + + added.append("circle") + .attr("r", function(d) { + return self.getDimensions(d).r; + }) + .attr('class', function(d) { + return topologyService.getItemStatusClass(d); + }) + .on("contextmenu", function(d) { + self.contextMenu(this, d); + }); + + added.append("title"); + + added.on("dblclick", function(d) { + return self.dblclick(d); + }); + + added.append("image") + .attr("xlink:href", function (d) { + var iconInfo = self.getIcon(d); + switch(iconInfo.type) { + case 'image': + return iconInfo.icon; + case "glyph": + return null; + } + }) + .attr("height", function(d) { + var iconInfo = self.getIcon(d); + if (iconInfo.type != 'image') { + return 0; + } + return 40; + }) + .attr("width", function(d) { + var iconInfo = self.getIcon(d); + if (iconInfo.type != 'image') { + return 0; + } + return 40; + }) + .attr("y", function(d) { + return self.getDimensions(d).y; + }) + .attr("x", function(d) { + return self.getDimensions(d).x; + }) + .on("contextmenu", function(d) { + self.contextMenu(this, d); + }); + + added.append("text") + .each(function(d) { + var iconInfo = self.getIcon(d); + if (iconInfo.type != 'glyph') + return; + + $(this).text(iconInfo.icon) + .attr("class", "glyph") + .attr('font-family', iconInfo.fontfamily); + }) + + .attr("y", function(d) { + return self.getDimensions(d).y; + }) + .attr("x", function(d) { + return self.getDimensions(d).x; + }) + .on("contextmenu", function(d) { + self.contextMenu(this, d); + }); + + added.append("text") + .attr("x", 26) + .attr("y", 24) + .text(function(d) { + return d.item.name; + }) + .attr('class', function() { + var class_name = "attached-label"; + if ($scope.checkboxModel.value) { + return class_name + ' visible'; + } else { + return class_name; + } + }); + + added.selectAll("title").text(function(d) { + return topologyService.tooltip(d).join("\n"); + }); + + $scope.vs = vertices; + + /* Don't do default rendering */ + ev.preventDefault(); + }); + + this.dblclick = function dblclick(d) { + if (d.item.kind == "Tag") { + return false; + } + window.location.assign(topologyService.geturl(d)); + }; + + this.getIcon = function getIcon(d) { + switch(d.item.kind) { + case 'PhysicalInfraManager': + return icons[d.item.display_kind]; + default: + return icons[d.item.kind]; + } + }; + + this.getDimensions = function getDimensions(d) { + var defaultDimensions = topologyService.defaultElementDimensions(); + switch (d.item.kind) { + case "PhysicalInfraManager": + return { x: -20, y: -20, r: 28 }; + case "EmsCluster": + return { x: defaultDimensions.x, y: defaultDimensions.y, r: defaultDimensions.r }; + case "Host": + return { x: defaultDimensions.x, y: defaultDimensions.y, r: defaultDimensions.r }; + case "Tag": + return { x: defaultDimensions.x, y: defaultDimensions.y, r: 13 }; + default: + return defaultDimensions; + } + }; + + $scope.searchNode = function() { + var svg = topologyService.getSVG(d3); + var query = $scope.search.query; + + topologyService.searchNode(svg, query); + }; + + $scope.resetSearch = function() { + topologyService.resetSearch(d3); + + // Reset the search term in search input + $scope.search.query = ""; + }; + + function getPhysicalInfraTopologyData(response) { + var data = response.data; + + var currentSelectedKinds = $scope.kinds; + + $scope.items = data.data.items; + $scope.relations = data.data.relations; + $scope.kinds = data.data.kinds; + icons = data.data.icons; + + if (currentSelectedKinds && (Object.keys(currentSelectedKinds).length !== Object.keys($scope.kinds).length)) { + $scope.kinds = currentSelectedKinds; + } + } +} diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 407f0cdc083..d4e87249336 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1683,6 +1683,8 @@ def render_or_redirect_partial(pfx) javascript_redirect edit_ems_datawarehouse_path(params[:id]) elsif params[:pressed] == "ems_network_edit" && params[:id] javascript_redirect edit_ems_network_path(params[:id]) + elsif params[:pressed] == "ems_physical_infra_edit" && params[:id] + javascript_redirect edit_ems_physical_infra_path(params[:id]) else javascript_redirect :action => @refresh_partial, :id => @redirect_id end diff --git a/app/controllers/ems_common.rb b/app/controllers/ems_common.rb index aa34e00ad0d..773c5f8e444 100644 --- a/app/controllers/ems_common.rb +++ b/app/controllers/ems_common.rb @@ -451,9 +451,10 @@ def button javascript_redirect :back return end - if params[:pressed] == "ems_cloud_recheck_auth_status" || - params[:pressed] == "ems_infra_recheck_auth_status" || - params[:pressed] == "ems_middleware_recheck_auth_status" || + if params[:pressed] == "ems_cloud_recheck_auth_status" || + params[:pressed] == "ems_infra_recheck_auth_status" || + params[:pressed] == "ems_physical_infra_recheck_auth_status" || + params[:pressed] == "ems_middleware_recheck_auth_status" || params[:pressed] == "ems_container_recheck_auth_status" if params[:id] table_key = :table diff --git a/app/controllers/ems_physical_infra_controller.rb b/app/controllers/ems_physical_infra_controller.rb new file mode 100644 index 00000000000..246e157a8e8 --- /dev/null +++ b/app/controllers/ems_physical_infra_controller.rb @@ -0,0 +1,67 @@ +class EmsPhysicalInfraController < ApplicationController + include Mixins::GenericListMixin + include Mixins::GenericShowMixin + include EmsCommon # common methods for EmsInfra/Cloud controllers + include Mixins::EmsCommonAngular + + before_action :check_privileges + before_action :get_session_data + after_action :cleanup_action + after_action :set_session_data + + def self.model + ManageIQ::Providers::PhysicalInfraManager + end + + def self.table_name + @table_name ||= "ems_physical_infra" + end + + def ems_path(*args) + ems_physical_infra_path(*args) + end + + def new_ems_path + new_ems_physical_infra_path + end + + def ems_physical_infra_form_fields + assert_privileges("#{permission_prefix}_edit") + @ems = model.new if params[:id] == 'new' + @ems = find_by_id_filtered(model, params[:id]) if params[:id] != 'new' + + render :json => { + :name => @ems.name, + :emstype => @ems.emstype, + :zone => zone, + :provider_id => @ems.provider_id ? @ems.provider_id : "", + :hostname => @ems.hostname, + :default_hostname => @ems.connection_configurations.default.endpoint.hostname, + :default_api_port => @ems.connection_configurations.default.endpoint.port, + :provider_region => @ems.provider_region, + :default_userid => @ems.authentication_userid ? @ems.authentication_userid : "", + :ems_controller => controller_name, + :default_auth_status => default_auth_status, + } + end + + private + + ############################ + # Special EmsCloud link builder for restful routes + def show_link(ems, options = {}) + ems_path(ems.id, options) + end + + def log_and_flash_message(message) + add_flash(message, :error) + $log.error(message) + end + + def restful? + true + end + public :restful? + + menu_section :phy +end diff --git a/app/controllers/mixins/ems_common_angular.rb b/app/controllers/mixins/ems_common_angular.rb index d8096a7b7e0..53308871ab4 100644 --- a/app/controllers/mixins/ems_common_angular.rb +++ b/app/controllers/mixins/ems_common_angular.rb @@ -122,11 +122,6 @@ def ems_form_fields default_tls_verify = default_endpoint.verify_ssl != 0 ? true : false default_tls_ca_certs = default_endpoint.certificate_authority - if @ems.zone.nil? || @ems.my_zone == "" - zone = "default" - else - zone = @ems.my_zone - end amqp_userid = "" amqp_hostname = "" amqp_port = "" @@ -208,8 +203,6 @@ def ems_form_fields service_account_auth_status = @ems.authentication_status_ok? end - default_auth_status = @ems.authentication_status_ok? unless @ems.kind_of?(ManageIQ::Providers::Google::CloudManager) - render :json => {:name => @ems.name, :emstype => @ems.emstype, :zone => zone, @@ -445,6 +438,10 @@ def set_ems_record_vars(ems, mode = nil) default_endpoint = {:role => :default, :hostname => hostname, :port => port, :security_protocol => ems.security_protocol} end + if ems.kind_of?(ManageIQ::Providers::Lenovo::PhysicalInfraManager) + default_endpoint = {:role => :default, :hostname => hostname, :port => port} + end + endpoints = {:default => default_endpoint, :ceilometer => ceilometer_endpoint, :amqp => amqp_endpoint, @@ -583,5 +580,17 @@ def construct_edit_for_audit(ems) @edit[:new][:tenant_mapping_enabled] = params[:tenant_mapping_enabled] if ems.class.supports_cloud_tenant_mapping? end + + def zone + if @ems.zone.nil? || @ems.my_zone == "" + "default" + else + @ems.my_zone + end + end + + def default_auth_status + @ems.authentication_status_ok? unless @ems.kind_of?(ManageIQ::Providers::Google::CloudManager) + end end end diff --git a/app/controllers/physical_infra_topology_controller.rb b/app/controllers/physical_infra_topology_controller.rb new file mode 100644 index 00000000000..b7e7e8f8dc7 --- /dev/null +++ b/app/controllers/physical_infra_topology_controller.rb @@ -0,0 +1,6 @@ +class PhysicalInfraTopologyController < TopologyController + @layout = "physical_infra_topology" + @service_class = PhysicalInfraTopologyService + + menu_section :inf +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 9125700bd9d..ca098cb5794 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -234,6 +234,9 @@ def view_to_url(view, parent = nil) if controller == "ems_infra" && action == "show" return ems_infras_path end + if controller == "ems_physical_infra" && action == "show" + return ems_physical_infras_path + end if controller == "ems_container" && action == "show" return ems_containers_path end @@ -575,6 +578,7 @@ def taskbar_in_header? miq_policy_rsop network_topology ops + physical_infra_topology pxe report rss @@ -640,7 +644,16 @@ def check_if_button_is_implemented ] # Return a blank tb if a placeholder is needed for AJAX explorer screens, return nil if no custom toolbar to be shown def custom_toolbar_filename - if %w(ems_cloud ems_cluster ems_infra host miq_template storage ems_storage ems_network cloud_tenant).include?(@layout) # Classic CIs + if %w(cloud_tenant + ems_cloud + ems_cluster + ems_infra + ems_network + ems_storage + ems_physical_infra + host + miq_template + storage).include?(@layout) # Classic CIs return "custom_buttons_tb" if @record && @lastaction == "show" && @display == "main" end @@ -762,6 +775,7 @@ def display_adv_search? ems_infra ems_middleware ems_network + ems_physical_infra ems_storage flavor floating_ip @@ -1173,6 +1187,7 @@ def pdf_page_size_style ems_infra_dashboard ems_middleware ems_network + ems_physical_infra ems_storage infra_topology event @@ -1197,6 +1212,7 @@ def pdf_page_size_style network_topology offline orchestration_stack + physical_infra_topology persistent_volume policy policy_group @@ -1271,6 +1287,7 @@ def render_listnav_filename ems_infra ems_middleware ems_network + ems_physical_infra ems_storage flavor floating_ip @@ -1336,6 +1353,7 @@ def render_listnav_filename ems_infra ems_middleware ems_network + ems_physical_infra ems_storage flavor floating_ip @@ -1394,6 +1412,7 @@ def show_adv_search? ems_infra ems_middleware ems_network + ems_physical_infra ems_storage flavor floating_ip diff --git a/app/helpers/application_helper/page_layouts.rb b/app/helpers/application_helper/page_layouts.rb index 33eac78fa81..456a86a9fa6 100644 --- a/app/helpers/application_helper/page_layouts.rb +++ b/app/helpers/application_helper/page_layouts.rb @@ -27,6 +27,7 @@ def layout_uses_listnav? my_tasks my_ui_tasks ops + physical_infra_topology pxe report rss diff --git a/app/helpers/application_helper/toolbar/ems_physical_infra_center.rb b/app/helpers/application_helper/toolbar/ems_physical_infra_center.rb new file mode 100644 index 00000000000..d94f28ee65d --- /dev/null +++ b/app/helpers/application_helper/toolbar/ems_physical_infra_center.rb @@ -0,0 +1,96 @@ +class ApplicationHelper::Toolbar::EmsPhysicalInfraCenter < ApplicationHelper::Toolbar::Basic + button_group('ems_physical_infra_vmdb', [ + button( + :refresh_server_summary, + 'fa fa-repeat fa-lg', + N_('Reload Current Display'), + nil), + select( + :ems_physical_infra_vmdb_choice, + 'fa fa-cog fa-lg', + t = N_('Configuration'), + t, + :items => [ + button( + :ems_physical_infra_refresh, + 'fa fa-refresh fa-lg', + N_('Refresh relationships and power states for all items related to this Infrastructure Provider'), + N_('Refresh Relationships and Power States'), + :confirm => N_("Refresh relationships and power states for all items related to this Infrastructure Provider?")), + separator, + button( + :ems_physical_infra_edit, + 'pficon pficon-edit fa-lg', + t = N_('Edit this Infrastructure Provider'), + t), + button( + :ems_physical_infra_delete, + 'pficon pficon-delete fa-lg', + t = N_('Remove this Infrastructure Provider'), + t, + :url_parms => "&refresh=y", + :confirm => N_("Warning: This Infrastructure Provider and ALL of its components will be permanently removed!")), + ] + ), + ]) + button_group('ems_physical_infra_policy', [ + select( + :ems_physical_infra_policy_choice, + 'fa fa-shield fa-lg', + t = N_('Policy'), + t, + :items => [ + button( + :ems_physical_infra_protect, + 'pficon pficon-edit fa-lg', + N_('Manage Policies for this Physical Infrastructure Provider'), + N_('Manage Policies')), + button( + :ems_physical_infra_tag, + 'pficon pficon-edit fa-lg', + N_('Edit Tags for this Physical Infrastructure Provider'), + N_('Edit Tags')), + button( + :ems_physical_infra_check_compliance, + 'fa fa-search fa-lg', + N_('Check Compliance of the last known configuration for this Physical Infra Manager'), + N_('Check Compliance of Last Known Configuration'), + :confirm => N_("Initiate Check Compliance of the last known configuration for this item?")), + ] + ), + ]) + button_group('ems_physical_infra_monitoring', [ + select( + :ems_physical_infra_monitoring_choice, + 'product product-monitoring fa-lg', + t = N_('Monitoring'), + t, + :items => [ + button( + :ems_physical_infra_timeline, + 'product product-timeline fa-lg', + N_('Show Timelines for this Physical Infrastructure Provider'), + N_('Timelines'), + :klass => ApplicationHelper::Button::EmsTimeline, + :url_parms => "?display=timeline"), + ] + ), + ]) + button_group('ems_physical_infra_authentication', [ + select( + :ems_physical_infra_authentication_choice, + 'fa fa-lock fa-lg', + t = N_('Authentication'), + t, + :items => [ + button( + :ems_physical_infra_recheck_auth_status, + 'fa fa-search fa-lg', + N_('Re-check Authentication Status for this Physical Infrastructure Provider'), + N_('Re-check Authentication Status'), + :klass => ApplicationHelper::Button::GenericFeatureButton, + :options => {:feature => :authentication_status}), + ] + ), + ]) +end diff --git a/app/helpers/application_helper/toolbar/ems_physical_infras_center.rb b/app/helpers/application_helper/toolbar/ems_physical_infras_center.rb new file mode 100644 index 00000000000..bd718e2ad9b --- /dev/null +++ b/app/helpers/application_helper/toolbar/ems_physical_infras_center.rb @@ -0,0 +1,109 @@ +class ApplicationHelper::Toolbar::EmsPhysicalInfrasCenter < ApplicationHelper::Toolbar::Basic + button_group('ems_physical_infra_vmdb', [ + select( + :ems_physical_infra_vmdb_choice, + 'fa fa-cog fa-lg', + t = N_('Configuration'), + t, + :items => [ + button( + :ems_physical_infra_refresh, + 'fa fa-refresh fa-lg', + N_('Refresh relationships and power states for all items related to the selected Infrastructure Providers'), + N_('Refresh Relationships and Power States'), + :url_parms => "main_div", + :confirm => N_("Refresh relationships and power states for all items related to the selected Infrastructure Providers?"), + :enabled => false, + :onwhen => "1+"), + button( + :ems_physical_infra_discover, + 'fa fa-search fa-lg', + t = N_('Discover Infrastructure Providers'), + t, + :url => "/discover", + :url_parms => "?discover_type=ems"), + separator, + button( + :ems_physical_infra_new, + 'pficon pficon-add-circle-o fa-lg', + t = N_('Add a New Infrastructure Provider'), + t, + :url => "/new"), + button( + :ems_physical_infra_edit, + 'pficon pficon-edit fa-lg', + N_('Select a single Infrastructure Provider to edit'), + N_('Edit Selected Infrastructure Providers'), + :url_parms => "main_div", + :enabled => false, + :onwhen => "1"), + button( + :ems_physical_infra_delete, + 'pficon pficon-delete fa-lg', + N_('Remove selected Infrastructure Providers'), + N_('Remove Infrastructure Providers'), + :url_parms => "main_div", + :confirm => N_("Warning: The selected Infrastructure Providers and ALL of their components will be permanently removed!"), + :enabled => false, + :onwhen => "1+"), + ] + ), + ]) + button_group('ems_physical_infra_policy', [ + select( + :ems_physical_infra_policy_choice, + 'fa fa-shield fa-lg', + t = N_('Policy'), + t, + :enabled => false, + :onwhen => "1+", + :items => [ + button( + :ems_physical_infra_protect, + 'pficon pficon-edit fa-lg', + N_('Manage Policies for the selected Physical Infrastructure Providers'), + N_('Manage Policies'), + :url_parms => "main_div", + :enabled => false, + :onwhen => "1+"), + button( + :ems_physical_infra_tag, + 'pficon pficon-edit fa-lg', + N_('Edit Tags for the selected Physical Infrastructure Providers'), + N_('Edit Tags'), + :url_parms => "main_div", + :enabled => false, + :onwhen => "1+"), + button( + :ems_physical_infra_check_compliance, + 'fa fa-search fa-lg', + N_('Check Compliance of the last known configuration for these Physical Infra Managers'), + N_('Check Compliance of Last Known Configuration'), + :url_parms => "main_div", + :confirm => N_("Initiate Check Compliance of the last known configuration for the selected items?"), + :enabled => "false", + :onwhen => "1+") + ] + ), + ]) + button_group('ems_physical_infra_authentication', [ + select( + :ems_physical_infra_authentication_choice, + 'fa fa-lock fa-lg', + t = N_('Authentication'), + t, + :enabled => false, + :onwhen => "1+", + :items => [ + button( + :ems_physical_infra_recheck_auth_status, + 'fa fa-search fa-lg', + N_('Re-check Authentication Status for the selected Physical Infrastructure Providers'), + N_('Re-check Authentication Status'), + :url_parms => "main_div", + :enabled => false, + :onwhen => "1+"), + ] + ), + ]) +end diff --git a/app/helpers/application_helper/toolbar_chooser.rb b/app/helpers/application_helper/toolbar_chooser.rb index 43aef36da9b..79113e7009b 100644 --- a/app/helpers/application_helper/toolbar_chooser.rb +++ b/app/helpers/application_helper/toolbar_chooser.rb @@ -473,15 +473,64 @@ def center_toolbar_filename_classic else # show_list and show screens unless @in_a_form - if %w(auth_key_pair_cloud availability_zone host_aggregate cloud_object_store_object cloud_object_store_container cloud_tenant - cloud_volume cloud_volume_backup cloud_volume_snapshot configuration_job container_group container_node container_service - ems_cloud ems_cluster ems_container ems_datawarehouse ems_middleware container_project container_route container_replicator container_image - ems_network security_group floating_ip cloud_subnet network_router network_topology network_port cloud_network load_balancer - container_image_registry ems_infra flavor host container_build infra_networking infra_topology ems_storage - container_topology middleware_topology cloud_topology middleware_server - middleware_deployment middleware_datasource middleware_domain middleware_server_group middleware_messaging - orchestration_stack resource_pool storage_manager container_template - ems_block_storage ems_object_storage timeline usage).include?(@layout) + if %w(auth_key_pair_cloud + availability_zone + host_aggregate + cloud_object_store_object + cloud_object_store_container + cloud_tenant + cloud_volume + cloud_volume_backup + cloud_volume_snapshot + configuration_job + container_group + container_node + container_service + ems_cloud + ems_cluster + ems_container + ems_datawarehouse + ems_middleware + container_project + container_route + container_replicator + container_image + ems_network + security_group + floating_ip + cloud_subnet + network_router + network_topology + network_port + cloud_network + load_balancer + container_image_registry + ems_infra + ems_physical_infra + flavor + host + container_build + infra_networking + infra_topology + ems_storage + container_topology + middleware_topology + cloud_topology + middleware_server + middleware_deployment + middleware_datasource + middleware_domain + middleware_server_group + middleware_messaging + orchestration_stack + physical_infra_topology + resource_pool + storage_manager + container_template + ems_block_storage + ems_object_storage + timeline + usage).include?(@layout) if ["show_list"].include?(@lastaction) return "#{@layout.pluralize}_center_tb" else diff --git a/app/helpers/ems_physical_infra_helper.rb b/app/helpers/ems_physical_infra_helper.rb new file mode 100644 index 00000000000..f69c66ee9e4 --- /dev/null +++ b/app/helpers/ems_physical_infra_helper.rb @@ -0,0 +1,3 @@ +module EmsPhysicalInfraHelper + include_concern 'TextualSummary' +end diff --git a/app/helpers/ems_physical_infra_helper/textual_summary.rb b/app/helpers/ems_physical_infra_helper/textual_summary.rb new file mode 100644 index 00000000000..dc4fc470148 --- /dev/null +++ b/app/helpers/ems_physical_infra_helper/textual_summary.rb @@ -0,0 +1,93 @@ +module EmsPhysicalInfraHelper::TextualSummary + include TextualMixins::TextualRefreshStatus + # + # Groups + # + + def textual_group_properties + TextualGroup.new( + _("Properties"), + %i(hostname ipaddress type port guid) + ) + end + + def textual_group_relationships + TextualGroup.new( + _("Relationships"), + %i(physical_servers datastores vms) + ) + end + + def textual_group_status + TextualGroup.new( + _("Status"), + textual_authentications(@record.authentication_userid_passwords) + %i(refresh_status) + ) + end + + def textual_group_smart_management + TextualTags.new(_("Smart Management"), %i(zone tags)) + end + + def textual_group_topology + TextualGroup.new(_("Overview"), %i(topology)) + end + + # + # Items + # + + def textual_hostname + @record.hostname + end + + def textual_ipaddress + {:label => _("Discovered IP Address"), :value => @record.ipaddress} + end + + def textual_type + {:label => _("Type"), :value => @record.emstype_description} + end + + def textual_port + @record.supports_port? ? {:label => _("API Port"), :value => @record.port} : nil + end + + def textual_physical_servers + available = @record.number_of(:physical_servers) > 0 + h = {:label => _("Physical Servers"), :icon => "pficon pficon-server", :value => @ems.number_of(:physical_servers)} + if available + h[:link] = "/ems_physical_infra/#{@ems.id}?display=physical_servers" + end + h + end + + def textual_guid + {:label => _("Management Engine GUID"), :value => @record.guid} + end + + def textual_datastores + return nil if @record.kind_of?(ManageIQ::Providers::PhysicalInfraManager) + + textual_link(@record.storages.sort_by { |s| s.name.downcase }, + :as => Storage, + :link => ems_physical_infra_path(@record.id, :display => 'storages')) + end + + def textual_vms + return nil if @record.kind_of?(ManageIQ::Providers::PhysicalInfraManager) + + textual_link(@record.vms, :label => _("Virtual Machines")) + end + + def textual_zone + {:label => _("Managed by Zone"), :icon => "pficon pficon-zone", :value => @record.zone.name} + end + + def textual_topology + {:label => _('Topology'), + :icon => "pficon pficon-topology", + :link => url_for(:controller => '/physical_infra_topology', :action => 'show', :id => @record.id), + :title => _("Show topology")} + end +end diff --git a/app/presenters/menu/default_menu.rb b/app/presenters/menu/default_menu.rb index 31a778890f2..3fe816a5fc4 100644 --- a/app/presenters/menu/default_menu.rb +++ b/app/presenters/menu/default_menu.rb @@ -5,6 +5,7 @@ def compute_menu_section Menu::Section.new(:compute, N_("Compute"), 'pficon pficon-cpu', [ clouds_menu_section, infrastructure_menu_section, + physical_infrastructure_menu_section, container_menu_section ]) end @@ -74,6 +75,13 @@ def infrastructure_menu_section ]) end + def physical_infrastructure_menu_section + Menu::Section.new(:phy, N_("Physical Infrastructure"), 'fa fa-plus fa-2x', [ + Menu::Item.new('ems_physical_infra', N_('Providers'), 'ems_physical_infra', {:feature => 'ems_physical_infra_show_list'}, '/ems_physical_infra'), + Menu::Item.new('physical_server', N_('Servers'), 'physical_server', {:feature => 'physical_server_show_list'}, '/physical_server'), + ]) + end + def hybrid_name(klass, name1, name2, name3) lambda do case klass.node_types diff --git a/app/services/physical_infra_topology_service.rb b/app/services/physical_infra_topology_service.rb new file mode 100644 index 00000000000..2464fb866b5 --- /dev/null +++ b/app/services/physical_infra_topology_service.rb @@ -0,0 +1,72 @@ +class PhysicalInfraTopologyService < TopologyService + include UiServiceMixin + + @provider_class = ManageIQ::Providers::PhysicalInfraManager + + def entity_type(entity) + entity.class.name.demodulize + end + + def build_topology + topo_items = {} + links = [] + + included_relations = [ + :tags, + :ems_clusters => [ + :tags, + :hosts, + ], + ] + + entity_relationships = {:PhysicalInfraManager => build_entity_relationships(included_relations)} + preloaded = @providers.includes(included_relations) + + preloaded.each do |entity| + topo_items, links = build_recursive_topology(entity, entity_relationships[:PhysicalInfraManager], topo_items, links) + end + + populate_topology(topo_items, links, build_kinds, icons) + end + + def entity_display_type(entity) + if entity.kind_of?(ManageIQ::Providers::PhysicalInfraManager) + entity.class.short_token + else + name = entity.class.name.demodulize + if entity.kind_of?(Vm) + name.upcase # turn Vm to VM because it's an abbreviation + else + name + end + end + end + + def build_entity_data(entity) + data = build_base_entity_data(entity) + data[:status] = entity_status(entity) + data[:display_kind] = entity_display_type(entity) + + if entity.try(:ems_id) + data[:provider] = entity.ext_management_system.name + end + + data + end + + def entity_status(entity) + case entity + when ManageIQ::Providers::PhysicalInfraManager + entity.authentications.blank? ? _('Unknown') : entity.authentications.first.status.try(:capitalize) + when Host + entity.state ? entity.state.downcase.capitalize : _('Unknown') + else + _('Unknown') + end + end + + def build_kinds + kinds = [:PhysicalInfraManager, :EmsCluster, :Host] + build_legend_kinds(kinds) + end +end diff --git a/app/views/ems_physical_infra/_form.html.haml b/app/views/ems_physical_infra/_form.html.haml new file mode 100644 index 00000000000..457a0e99bf1 --- /dev/null +++ b/app/views/ems_physical_infra/_form.html.haml @@ -0,0 +1,69 @@ +- @angular_form = true + +.form-horizontal{:id => "start_form_div", :style => "display:none"} + = render :partial => "layouts/flash_msg" + %div + .form-group{"ng-class" => "{'has-error': angularForm.name.$invalid}"} + %label.col-md-2.control-label{"for" => "ems_name"} + = _('Name') + .col-md-8 + %input.form-control{"type" => "text", + "id" => "ems_name", + "name" => "name", + "ng-model" => "emsCommonModel.name", + "maxlength" => "#{MAX_NAME_LEN}", + "required" => "", + "checkchange" => "", + "auto-focus" => "", + "start-form-div" => "start_form_div"} + %span.help-block{"ng-show" => "angularForm.name.$error.required"} + = _("Required") + + .form-group{"ng-class" => "{'has-error': angularForm.emstype.$invalid}"} + %label.col-md-2.control-label{"for" => "ems_type"} + = _('Type') + .col-md-8 + = select_tag('emstype', + options_for_select([["<#{_('Choose')}>", nil]] + @ems_types, disabled: ["<#{_('Choose')}>", nil]), + "ng-if" => "newRecord", + "ng-model" => "emsCommonModel.emstype", + "ng-change" => "providerTypeChanged()", + "required" => "", + "checkchange" => "", + "selectpicker-for-select-tag" => "") + %div{"ng-if" => "!newRecord"} + %label.form-control{"type" => "text", + "id" => "ems_type", + "name" => "emstype", + "ng-if" => "!newRecord", + "readonly" => true, + "style" => "color: black; font-weight: normal;"} + = @emstype_display + + .form-group{"ng-class" => "{'has-error': angularForm.zone.$invalid}"} + %label.col-md-2.control-label{"for" => "ems_zone"} + = _("Zone") + .col-md-8 + - if @server_zones.length <= 1 + %input.form-control{"type" => "text", + "id" => "ems_zone", + "name" => "zone", + "ng-model" => "emsCommonModel.zone", + "maxlength" => 15, + "required" => "", + "checkchange" => "", + "readonly" => true, + "style" => "color: black;"} + - else + = select_tag('zone', + options_for_select(@server_zones.sort_by { |name, _name| name }), + "ng-model" => "emsCommonModel.zone", + "checkchange" => "", + "required" => "", + "selectpicker-for-select-tag" => "") + + + %hr + = render :partial => "/layouts/angular/multi_auth_credentials", :locals => {:record => @ems, :ng_model => "emsCommonModel"} + + = render :partial => "layouts/angular/x_edit_buttons_angular", :locals => {:record => @ems, :restful => true} diff --git a/app/views/ems_physical_infra/edit.html.haml b/app/views/ems_physical_infra/edit.html.haml new file mode 100644 index 00000000000..ff12732ec7f --- /dev/null +++ b/app/views/ems_physical_infra/edit.html.haml @@ -0,0 +1,17 @@ += form_for(@ems, + :url => ems_physical_infra_path(@ems), + :method => :patch, + :html => {"ng-controller" => "emsCommonFormController", + "name" => "angularForm", + "ng-show" => "afterGet", + "update-url" => "#{ems_physical_infra_path(@ems)}", + "form-fields-url" => "/#{controller_name}/ems_physical_infra_form_fields/", + "novalidate" => true}) do |f| + %input{:type => 'hidden', :id => "form_id", :value => "##{f.options[:html][:id]}"} + %input{:type => 'hidden', :id => "button_name", :name => "button", :value => "save"} + %input{:type => 'hidden', :id => "cred_type", :name => "cred_type", :value => "default"} + = render :partial => "form" + +:javascript + ManageIQ.angular.app.value('emsCommonFormId', '#{@ems.id || "new"}'); + miq_bootstrap($('#form_id').val()); diff --git a/app/views/ems_physical_infra/new.html.haml b/app/views/ems_physical_infra/new.html.haml new file mode 100644 index 00000000000..7c6db3df1d3 --- /dev/null +++ b/app/views/ems_physical_infra/new.html.haml @@ -0,0 +1,19 @@ +- url = @ems.persisted? ? ems_physical_infras_path(@ems) : ems_physical_infras_path += form_for(@ems, + :url => url, + :method => :post, + :html => {"ng-controller" => "emsCommonFormController", + "name" => "angularForm", + "ng-show" => "afterGet", + "create-url" => "#{url}", + "form-fields-url" => "/#{controller_name}/ems_physical_infra_form_fields/", + "novalidate" => true}) do |f| + %input{:type => 'hidden', :id => "form_id", :value => "##{f.options[:html][:id]}"} + %input{:type => 'hidden', :id => "button_name", :name => "button", :value => "add"} + %input{:type => 'hidden', :id => "cred_type", :name => "cred_type", :value => "default"} + + = render :partial => "form" + +:javascript + ManageIQ.angular.app.value('emsCommonFormId', '#{@ems.id || "new"}'); + miq_bootstrap($('#form_id').val()); diff --git a/app/views/ems_physical_infra/show.html.haml b/app/views/ems_physical_infra/show.html.haml new file mode 100644 index 00000000000..9af1120a11e --- /dev/null +++ b/app/views/ems_physical_infra/show.html.haml @@ -0,0 +1,2 @@ +-# needed by render :action => "show" in application controller += render :partial => "shared/views/ems_common/show" diff --git a/app/views/ems_physical_infra/show_list.html.haml b/app/views/ems_physical_infra/show_list.html.haml new file mode 100644 index 00000000000..039604839f2 --- /dev/null +++ b/app/views/ems_physical_infra/show_list.html.haml @@ -0,0 +1,2 @@ +#main_div + = render :partial => 'layouts/gtl' diff --git a/app/views/ems_physical_infra/show_policy_timeline.html.haml b/app/views/ems_physical_infra/show_policy_timeline.html.haml new file mode 100644 index 00000000000..ddece093b5d --- /dev/null +++ b/app/views/ems_physical_infra/show_policy_timeline.html.haml @@ -0,0 +1,2 @@ +#main_div + = render :partial => "layouts/tl_show" diff --git a/app/views/layouts/_pagingcontrols.html.haml b/app/views/layouts/_pagingcontrols.html.haml index 17820ea1538..5fa2b64ef9a 100644 --- a/app/views/layouts/_pagingcontrols.html.haml +++ b/app/views/layouts/_pagingcontrols.html.haml @@ -35,7 +35,7 @@ = _(@view.headers[@sortcol]) - unless db.blank? - - if %w(EmsInfra EmsCloud EmsCluster ResourcePool StorageManager).include?(db) + - if %w(EmsInfra EmsPhysicalInfra EmsCloud EmsCluster ResourcePool StorageManager).include?(db) - @db = db.underscore .form-group diff --git a/app/views/layouts/angular-bootstrap/_endpoints_angular.html.haml b/app/views/layouts/angular-bootstrap/_endpoints_angular.html.haml index f5f5251e3fd..47fd9c627ea 100644 --- a/app/views/layouts/angular-bootstrap/_endpoints_angular.html.haml +++ b/app/views/layouts/angular-bootstrap/_endpoints_angular.html.haml @@ -1,4 +1,4 @@ -- return unless %w(ems_cloud ems_infra ems_container ems_middleware ems_network ems_datawarehouse).include?(controller_name) +- return unless %w(ems_cloud ems_infra ems_container ems_middleware ems_network ems_datawarehouse ems_physical_infra).include?(controller_name) - prefix ||= "default" - ng_reqd_hostname ||= true - ng_reqd_api_port ||= true @@ -26,14 +26,15 @@ %div{"ng-if" => defined?(apiport_hide) ? false : true} .form-group{"ng-class" => "{'has-error': angularForm.#{prefix}_api_port.$invalid}", - "ng-if" => "emsCommonModel.emstype == 'openstack' || " + | - "emsCommonModel.emstype == 'openstack_infra' || " + | - "emsCommonModel.emstype == 'nuage_network' || " + | - "emsCommonModel.emstype == 'rhevm' || " + | - "emsCommonModel.emstype == 'vmware_cloud' || " + | - "emsCommonModel.emstype == 'hawkular' || " + | - "emsCommonModel.emstype == 'hawkular_datawarehouse' || " + | - "emsCommonModel.ems_controller == 'ems_container'"} | + "ng-if" => "emsCommonModel.emstype == 'openstack' || " + | + "emsCommonModel.emstype == 'openstack_infra' || " + | + "emsCommonModel.emstype == 'nuage_network' || " + | + "emsCommonModel.emstype == 'rhevm' || " + | + "emsCommonModel.emstype == 'vmware_cloud' || " + | + "emsCommonModel.emstype == 'hawkular' || " + | + "emsCommonModel.emstype == 'hawkular_datawarehouse' || " + | + "emsCommonModel.emstype == 'lenovo_ph_infra' || " + | + "emsCommonModel.ems_controller == 'ems_container'"} | %label.col-md-2.control-label{"for" => "#{prefix}_api_port"} = _('API Port') .col-md-2 diff --git a/app/views/layouts/angular/_multi_auth_credentials.html.haml b/app/views/layouts/angular/_multi_auth_credentials.html.haml index 1b587600799..3b6510a8af3 100644 --- a/app/views/layouts/angular/_multi_auth_credentials.html.haml +++ b/app/views/layouts/angular/_multi_auth_credentials.html.haml @@ -36,6 +36,7 @@ .col-md-12{"ng-if" => "#{ng_model}.ems_controller == 'ems_container' || " + | "#{ng_model}.ems_controller == 'ems_middleware' || " + | "#{ng_model}.ems_controller == 'ems_datawarehouse' || " + | + "#{ng_model}.ems_controller == 'ems_physical_infra' || " + | "#{ng_model}.ems_controller == 'ems_network'"} | = render :partial => "layouts/angular-bootstrap/endpoints_angular", :locals => {:ng_show => true, @@ -327,12 +328,13 @@ %div{"ng-if" => "#{ng_model}.emstype == ''"} :javascript $('#auth_tabs').hide(); -%div{"ng-if" => "#{ng_model}.emstype == 'ec2' || " + | - "#{ng_model}.emstype == 'gce' || " + | - "#{ng_model}.emstype == 'scvmm' || " + | - "#{ng_model}.emstype == 'vmwarews' || " + | +%div{"ng-if" => "#{ng_model}.emstype == 'ec2' || " + | + "#{ng_model}.emstype == 'gce' || " + | + "#{ng_model}.emstype == 'scvmm' || " + | + "#{ng_model}.emstype == 'vmwarews' || " + | + "#{ng_model}.emstype == 'lenovo_ph_infra' || " + | "#{ng_model}.emstype == 'hawkular_datawarehouse' || " + | - "#{ng_model}.emstype == 'hawkular'"} | + "#{ng_model}.emstype == 'hawkular'"} | :javascript miq_tabs_show_hide("#amqp_tab", false); miq_tabs_show_hide("#metrics_tab", false); diff --git a/app/views/layouts/listnav/_ems_physical_infra.html.haml b/app/views/layouts/listnav/_ems_physical_infra.html.haml new file mode 100644 index 00000000000..572de278ff6 --- /dev/null +++ b/app/views/layouts/listnav/_ems_physical_infra.html.haml @@ -0,0 +1,21 @@ +- if @record.try(:name) + #accordion.panel-group + = miq_accordion_panel(truncate(@record.name, :length => truncate_length), true, "icon") do + = render_quadicon(@record, :mode => :icon, :size => 72, :typ => :listnav) + + = miq_accordion_panel(_("Properties"), false, "ems_prop") do + %ul.nav.nav-pills.nav-stacked + %li + = link_to_with_icon(_('Summary'), polymorphic_path(@record, :display => 'main'), :title => _("Show Summary")) + = li_link(:if => (@record.has_events? || @record.has_events?(:policy_events)), + :text => _('Timelines'), + :record => @record, + :display => 'timeline', + :title => _("Show Timelines")) + = miq_accordion_panel(_("Relationships"), false, "ems_rel") do + %ul.nav.nav-pills.nav-stacked + = li_link(:if => !(@record.number_of(:physical_servers) == 0), + :text => _("Physical Servers"), + :record => @record, + :display => 'physical_servers', + :title => _("Physical Servers")) diff --git a/app/views/physical_infra_topology/show.html.haml b/app/views/physical_infra_topology/show.html.haml new file mode 100644 index 00000000000..6478deb0f2a --- /dev/null +++ b/app/views/physical_infra_topology/show.html.haml @@ -0,0 +1,43 @@ +- tooltipOptions = {"tooltip-placement" => "bottom-right", "tooltip" => "{{legendTooltip}}"} +.physical_infra_topology{'ng-controller' => "physicalInfraTopologyController"} + .row.toolbar-pf + .col-md-12 + = render :partial => "shared/topology_header" + .legend + %label#selected + %div{'ng-if' => "kinds"} + %kubernetes-topology-icon{tooltipOptions, :kind => "Host"} + %svg.kube-topology + %g.EntityLegend.PhysicalInfra.Host + %circle{:r => "17"} + -# pficon-host + %text{:y => "8"}  + %label + = _("Nodes") + %kubernetes-topology-icon{tooltipOptions, :kind => "EmsCluster"} + %svg.kube-topology + %g.EntityLegend.PhysicalInfra + %circle{:r => "17"} + -# pficon-cluster + %text{:y => "9"}  + %label + = _("Roles") + %kubernetes-topology-icon{tooltipOptions, :kind => "Tag"} + %svg.kube-topology + %g.EntityLegend.Network.FloatingIp + %circle{:r => "17"} + -# pficon-cloud-tenant + %text{:y => "9"}  + %label + = _("Tags") + + .alert.alert-info.alert-dismissable + %button.close{"aria-hidden" => "true", "data-dismiss" => "alert", :type => "button"} + %span.pficon.pficon-close + %span.pficon.pficon-info + %strong + = _("Click on the legend to show/hide entities, and double click/right click the entities in the graph to navigate to their summary pages.") + %kubernetes-topology-graph{:items => "items", :relations => "relations", :kinds => "kinds"} + +:javascript + miq_bootstrap('.physical_infra_topology', 'physicalInfraTopologyApp'); diff --git a/config/routes.rb b/config/routes.rb index d8febdf24a5..95bde0c00e5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1020,6 +1020,13 @@ ) }, + :physical_infra_topology => { + :get => %w( + show + data + ) + }, + :container_dashboard => { :get => %w( show @@ -1163,8 +1170,6 @@ protect show_list tagging_edit - scaling - scaledown ) + compare_get, :post => %w( @@ -1185,9 +1190,9 @@ tree_autoload update wait_for_task + x_show scaling scaledown - x_show squash_toggle ) + adv_search_post + @@ -1205,6 +1210,49 @@ ) }, + :ems_physical_infra => { + :get => %w( + discover + download_data + download_summary_pdf + ems_physical_infra_form_fields + protect + show_list + tagging_edit + ) + + compare_get, + :post => %w( + button + create + listnav_search_selected + protect + quick_search + show + show_list + squash_toggle + tag_edit_form_field_changed + tagging_edit + tl_chooser + tree_autoload + update + wait_for_task + x_show + ) + + adv_search_post + + compare_post + + dialog_runner_post + + discover_get_post + + exp_post + + save_post + }, + + :ems_physical_infra_dashboard => { + :get => %w( + show + data + ) + }, + :ems_container => { :get => %w( download_data @@ -3196,7 +3244,7 @@ controller_routes.each do |controller_name, controller_actions| # Default route with no action to controller's index action unless [ - :ems_cloud, :ems_infra, :ems_container, :ems_middleware, :ems_datawarehouse, :ems_network + :ems_cloud, :ems_infra, :ems_physical_infra, :ems_container, :ems_middleware, :ems_datawarehouse, :ems_network ].include?(controller_name) match controller_name.to_s, :controller => controller_name, :action => :index, :via => :get end @@ -3224,10 +3272,11 @@ # pure-angular templates get '/static/*id' => 'static#show', :format => false - resources :ems_cloud, :as => :ems_clouds - resources :ems_infra, :as => :ems_infras - resources :ems_container, :as => :ems_containers - resources :ems_middleware, :as => :ems_middlewares - resources :ems_datawarehouse, :as => :ems_datawarehouses - resources :ems_network, :as => :ems_networks + resources :ems_cloud, :as => :ems_clouds + resources :ems_infra, :as => :ems_infras + resources :ems_physical_infra, :as => :ems_physical_infras + resources :ems_container, :as => :ems_containers + resources :ems_middleware, :as => :ems_middlewares + resources :ems_datawarehouse, :as => :ems_datawarehouses + resources :ems_network, :as => :ems_networks end diff --git a/spec/controllers/ems_physical_infra_controller_spec.rb b/spec/controllers/ems_physical_infra_controller_spec.rb new file mode 100644 index 00000000000..d3bc17ea260 --- /dev/null +++ b/spec/controllers/ems_physical_infra_controller_spec.rb @@ -0,0 +1,226 @@ +describe EmsPhysicalInfraController do + include CompressedIds + + let!(:server) { EvmSpecHelper.local_miq_server(:zone => zone) } + let(:zone) { FactoryGirl.build(:zone) } + + describe "#create" do + before do + user = FactoryGirl.create(:user, :features => "ems_physical_infra_new") + + allow(user).to receive(:server_timezone).and_return("UTC") + allow_any_instance_of(described_class).to receive(:set_user_time_zone) + allow(controller).to receive(:check_privileges).and_return(true) + login_as user + end + + it "adds a new provider" do + controller.instance_variable_set(:@breadcrumbs, []) + get :new + expect(response.status).to eq(200) + end + end + + describe "#show" do + render_views + before(:each) do + EvmSpecHelper.create_guid_miq_server_zone + login_as FactoryGirl.create(:user) + @ems = FactoryGirl.create(:ems_physical_infra) + end + + let(:url_params) { {} } + + subject { get :show, :params => {:id => @ems.id}.merge(url_params) } + + context "display=timeline" do + let(:url_params) { {:display => 'timeline'} } + it { is_expected.to have_http_status 200 } + end + + context "render listnav partial" do + render_views + + it do + is_expected.to have_http_status 200 + is_expected.to render_template(:partial => "layouts/listnav/_ems_physical_infra") + end + end + + it "shows associated datastores" do + @datastore = FactoryGirl.create(:storage, :name => 'storage_name') + @datastore.parent = @ems + controller.instance_variable_set(:@breadcrumbs, []) + get :show, :params => {:id => @ems.id, :display => 'storages'} + expect(response.status).to eq(200) + expect(response).to render_template('shared/views/ems_common/show') + expect(assigns(:breadcrumbs)).to eq([{:name => "#{@ems.name} (All Datastores)", + :url => "/ems_physical_infra/#{@ems.id}?display=storages"}]) + + # display needs to be saved to session for GTL pagination and such + expect(session[:ems_physical_infra_display]).to eq('storages') + end + + it " can tag associated datastores" do + stub_user(:features => :all) + datastore = FactoryGirl.create(:storage, :name => 'storage_name') + datastore.parent = @ems + controller.instance_variable_set(:@_orig_action, "x_history") + get :show, :params => {:id => @ems.id, :display => 'storages'} + post :button, :params => {:id => @ems.id, + :display => 'storages', + :miq_grid_checks => to_cid(datastore.id), + :pressed => "storage_tag", + :format => :js} + expect(response.status).to eq(200) + _breadcrumbs = controller.instance_variable_get(:@breadcrumbs) + expect(assigns(:breadcrumbs)).to eq([{:name => "#{@ems.name} (All Datastores)", + :url => "/ems_physical_infra/#{@ems.id}?display=storages"}, + {:name => "Tag Assignment", :url => "//tagging_edit"}]) + end + end + + describe "#show_list" do + before(:each) do + stub_user(:features => :all) + FactoryGirl.create(:ems_vmware) + get :show_list + end + it { expect(response.status).to eq(200) } + end + + describe "breadcrumbs path on a 'show' page of an Physical Infrastructure Provider accessed from Dashboard maintab" do + before do + stub_user(:features => :all) + EvmSpecHelper.create_guid_miq_server_zone + end + context "when previous breadcrumbs path contained 'Cloud Providers'" do + it "shows 'Physical Infrastructure Providers -> (Summary)' breadcrumb path" do + ems = FactoryGirl.create("ems_physical_infra") + get :show, :params => { :id => ems.id } + breadcrumbs = controller.instance_variable_get(:@breadcrumbs) + expect(breadcrumbs).to eq([{:name => "Physical Infrastructure Providers", + :url => "/ems_physical_infra/show_list?page=&refresh=y"}, + {:name => "#{ems.name} (Summary)", + :url => "/ems_physical_infra/#{ems.id}"}]) + end + end + end + + describe "#build_credentials" do + before(:each) do + @ems = FactoryGirl.create(:ems_physical_infra) + end + context "#build_credentials only contains credentials that it supports and has a username for in params" do + let(:default_creds) { {:userid => "default_userid", :password => "default_password"} } + + it "uses the passwords from params for validation if they exist" do + controller.instance_variable_set(:@_params, + :default_userid => default_creds[:userid], + :default_password => default_creds[:password]) + expect(controller.send(:build_credentials, @ems, :validate)).to eq(:default => default_creds.merge!(:save => false)) + end + + it "uses the stored passwords for validation if passwords dont exist in params" do + controller.instance_variable_set(:@_params, + :default_userid => default_creds[:userid]) + expect(@ems).to receive(:authentication_password).and_return(default_creds[:password]) + expect(controller.send(:build_credentials, @ems, :validate)).to eq(:default => default_creds.merge!(:save => false)) + end + end + end + + describe "Lenovo XClarity (lenovo_ph_infra) - create, update, validate, cancel" do + before do + allow(controller).to receive(:check_privileges).and_return(true) + allow(controller).to receive(:assert_privileges).and_return(true) + login_as FactoryGirl.create(:user, :features => "ems_physical_infra_new") + end + + render_views + + it 'creates on post' do + expect do + post :create, :params => { + "button" => "add", + "name" => "foo", + "emstype" => "lenovo_ph_infra", + "zone" => zone.name, + "cred_type" => "default", + "default_hostname" => "foo.com", + "default_userid" => "foo", + "default_password" => "[FILTERED]", + "default_verify" => "[FILTERED]" + } + end.to change { ManageIQ::Providers::PhysicalInfraManager.count }.by(1) + end + + it 'creates and updates an authentication record on post' do + expect do + post :create, :params => { + "button" => "add", + "name" => "foo_lenovo_ph_infra", + "emstype" => "lenovo_ph_infra", + "zone" => zone.name, + "cred_type" => "default", + "default_hostname" => "foo.com", + "default_userid" => "foo", + "default_password" => "[FILTERED]", + "default_verify" => "[FILTERED]" + } + end.to change { Authentication.count }.by(1) + + expect(response.status).to eq(200) + lenovo_ph_infra = ManageIQ::Providers::PhysicalInfraManager.where(:name => "foo_lenovo_ph_infra").first + expect(lenovo_ph_infra.authentications.size).to eq(1) + + expect do + post :update, :params => { + "id" => lenovo_ph_infra.id, + "button" => "save", + "default_hostname" => "host_lenovo_ph_infra_updated", + "name" => "foo_lenovo_ph_infra", + "emstype" => "lenovo_ph_infra", + "default_userid" => "bar", + "default_password" => "[FILTERED]", + "default_verify" => "[FILTERED]" + } + end.not_to change { Authentication.count } + + expect(response.status).to eq(200) + expect(lenovo_ph_infra.authentications.first).to have_attributes(:userid => "bar", :password => "[FILTERED]") + end + + it "validates credentials for a new record" do + post :create, :params => { + "button" => "validate", + "cred_type" => "default", + "name" => "foo_lenovo_ph_infra", + "emstype" => "lenovo_ph_infra", + "zone" => zone.name, + "default_userid" => "foo", + "default_password" => "[FILTERED]", + "default_verify" => "[FILTERED]" + } + + expect(response.status).to eq(200) + end + + it "cancels a new record" do + post :create, :params => { + "button" => "cancel", + "cred_type" => "default", + "name" => "foo_lenovo_ph_infra", + "emstype" => "lenovo_ph_infra", + "zone" => zone.name, + "default_userid" => "foo", + "default_password" => "[FILTERED]", + "default_verify" => "[FILTERED]" + } + + expect(response.status).to eq(200) + end + end + + include_examples '#download_summary_pdf', :ems_physical_infra +end diff --git a/spec/routing/ems_physical_infra_routing_spec.rb b/spec/routing/ems_physical_infra_routing_spec.rb new file mode 100644 index 00000000000..657ceb468a1 --- /dev/null +++ b/spec/routing/ems_physical_infra_routing_spec.rb @@ -0,0 +1,54 @@ +require "routing/shared_examples" + +describe EmsPhysicalInfraController do + let(:controller_name) { "ems_infra" } + + it_behaves_like "A controller that has advanced search routes", true + it_behaves_like "A controller that has compare routes" + it_behaves_like "A controller that has dialog runner routes" + it_behaves_like "A controller that has discovery routes" + it_behaves_like "A controller that has download_data routes" + it_behaves_like "A controller that has policy protect routes" + it_behaves_like "A controller that has tagging routes" + it_behaves_like "A controller that has timeline routes" + + %w( + dialog_load + show_list + ).each do |task| + describe "##{task}" do + it 'routes with GET' do + expect(get("/#{controller_name}/#{task}")).to route_to("#{controller_name}##{task}") + end + end + end + + %w( + button + create + form_field_changed + listnav_search_selected + protect + quick_search + sections_field_changed + show + show_list + tag_edit_form_field_changed + tagging_edit + tl_chooser + tree_autoload + update + wait_for_task + protect + squash_toggle + scaling + scaledown + x_show + ).each do |task| + describe "##{task}" do + it 'routes with POST' do + expect(post("/#{controller_name}/#{task}")).to route_to("#{controller_name}##{task}") + end + end + end +end