diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 000000000000..395b83ed650e
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+app/javascript/oldjs/locale/*.json -text -diff
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 9fb707c58676..597aac9aa2b4 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,8 +1 @@
-# trees
-app/presenters/tree_node/ @skateman
-app/presenters/tree_builder_* @skateman
-# styling and images
-app/assets/stylesheets/ @epwinchell
-app/assets/images/ @epwinchell
-# topology services
-app/services/*topology_service.rb @skateman
+* @ManageIQ/committers-ui
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index d8c34ad3d747..34aac6351c35 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -1,19 +1,18 @@
+---
name: CI
-
on:
push:
pull_request:
workflow_dispatch:
schedule:
- - cron: '0 0 * * *'
-
+ - cron: 0 0 * * *
jobs:
ci:
runs-on: ubuntu-latest
strategy:
matrix:
ruby-version:
- - '2.7'
+ - '3.0'
node-version:
- 14
test-suite:
@@ -24,7 +23,7 @@ jobs:
- spec:jest
- spec:routes
include:
- - ruby-version: '2.6'
+ - ruby-version: '2.7'
node-version: 14
test-suite: spec
services:
@@ -34,29 +33,31 @@ jobs:
POSTGRESQL_USER: root
POSTGRESQL_PASSWORD: smartvm
POSTGRESQL_DATABASE: vmdb_test
- options: --health-cmd pg_isready --health-interval 2s --health-timeout 5s --health-retries 5
+ options: "--health-cmd pg_isready --health-interval 2s --health-timeout 5s
+ --health-retries 5"
ports:
- 5432:5432
env:
- TEST_SUITE: ${{ matrix.test-suite }}
+ TEST_SUITE: "${{ matrix.test-suite }}"
PGHOST: localhost
PGPASSWORD: smartvm
- CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
+ CC_TEST_REPORTER_ID: "${{ secrets.CC_TEST_REPORTER_ID }}"
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Set up system
run: bin/before_install
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
- ruby-version: ${{ matrix.ruby-version }}
+ ruby-version: "${{ matrix.ruby-version }}"
bundler-cache: true
timeout-minutes: 30
- name: Set up Node
- if: ${{ matrix.test-suite == 'spec:compile' || matrix.test-suite == 'spec:javascript' || matrix.test-suite == 'spec:jest' }}
+ if: "${{ matrix.test-suite == 'spec:compile' || matrix.test-suite == 'spec:javascript'
+ || matrix.test-suite == 'spec:jest' }}"
uses: actions/setup-node@v2
with:
- node-version: ${{ matrix.node-version }}
+ node-version: "${{ matrix.node-version }}"
cache: yarn
registry-url: https://npm.manageiq.org/
- name: Prepare tests
@@ -64,6 +65,7 @@ jobs:
- name: Run tests
run: bundle exec rake
- name: Report code coverage
- if: ${{ github.ref == 'refs/heads/master' && matrix.ruby-version == '2.7' && matrix.test-suite == 'spec' }}
+ if: "${{ github.ref == 'refs/heads/master' && matrix.ruby-version == '2.7' &&
+ matrix.test-suite == 'spec' }}"
continue-on-error: true
uses: paambaati/codeclimate-action@v3.0.0
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 07f7b4514fc8..91d86ab05efa 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -1006,6 +1006,7 @@ def get_sort_col
def process_saved_reports(saved_reports, task)
success_count = 0
failure_count = 0
+ params[:miq_grid_checks] = params[:miq_grid_checks]&.split(",")
MiqReportResult.for_user(current_user).where(:id => saved_reports).order(MiqReportResult.arel_table[:name].lower).each do |rep|
rep.public_send(task) if rep.respond_to?(task) # Run the task
rescue StandardError
@@ -1019,6 +1020,7 @@ def process_saved_reports(saved_reports, task)
:target_class => "MiqReportResult",
:userid => current_userid
)
+ params[:miq_grid_checks]&.delete(rep[:id].to_s)
success_count += 1
else
add_flash(_("\"%{record}\": %{task} successfully initiated") % {:record => rep.name, :task => task})
@@ -1032,6 +1034,7 @@ def process_saved_reports(saved_reports, task)
add_flash(n_("Error during Saved Report delete from the %{product} Database",
"Error during Saved Reports delete from the %{product} Database", failure_count) % {:product => Vmdb::Appliance.PRODUCT_NAME})
end
+ params[:miq_grid_checks] || []
end
# Common timeprofiles button handler routines
diff --git a/app/controllers/application_controller/compare.rb b/app/controllers/application_controller/compare.rb
index ae6328f5f99a..a8c528a2bb2b 100644
--- a/app/controllers/application_controller/compare.rb
+++ b/app/controllers/application_controller/compare.rb
@@ -13,6 +13,8 @@ def create_compare_view
rpt = get_compare_report(@sb[:compare_db])
session[:miq_sections] = MiqCompare.sections(rpt)
+ selected_sections = session[:miq_sections]&.select { |_key, value| value[:checked] == true }
+ session[:selected_sections] = selected_sections ? selected_sections.keys.map(&:to_s) : []
ids = session[:miq_selected].collect(&:to_i)
@compare = MiqCompare.new({:ids => ids,
:include => session[:miq_sections]},
@@ -413,13 +415,11 @@ def sections_field_changed
end
def set_checked_sections
- session[:selected_sections] = []
- params[:all_checked]&.each do |a|
- s = a.split(':')
- if s.length > 1
- session[:selected_sections].push(s[1])
- end
+ selections = []
+ params[:all_checked]&.each do |item|
+ add_selections!(selection_names(item), selections)
end
+ session[:selected_sections] = update_selections(params[:id], selections, params[:check])
end
# Toggle exists/details view
@@ -1851,4 +1851,54 @@ def section_field_compare_values(view, section, field, base_val)
unset_same_flag unless fld.nil? || base_val == fld[:_value_]
end
end
+
+ def validate_name(name)
+ name&.starts_with?('xx-group')
+ end
+
+ # Method to get the selection names from :all_checked and :id params.
+ # individual selected item suggests that the variable name contains ':'
+ # else, the child_names are retrived from the selected_parent item.
+ def selection_names(name)
+ if validate_name(name)
+ count = name.scan(/xx-group/).count
+ count == 1 ? child_names(name) : [name.split(':')[1]]
+ end
+ end
+
+ # Method to get the child names of a selected parent item.
+ # session[:miq_sections] contains all tree names and the keys are filters using the selected group_name[1].
+ def child_names(name)
+ child_names = []
+ group_name = name.split('_')
+ if group_name[1]
+ child_sections = session[:miq_sections]&.select { |_key, value| value[:group] == group_name[1] }
+ if child_sections && child_sections.length > 1
+ child_names = child_sections.keys.map(&:to_s)
+ end
+ end
+ child_names
+ end
+
+ # Method to add or remove selections if an id is present.
+ def update_selections(param_id, selections, check)
+ if param_id
+ names = selection_names(CGI.unescape(param_id))
+ case check
+ when "true" then add_selections!(names, selections)
+ when "false" then remove_selections!(names, selections)
+ end
+ end
+ selections
+ end
+
+ # Method to add names to the selections.
+ def add_selections!(names, selections)
+ selections.replace(selections | names)
+ end
+
+ # Method to remove names to the selections.
+ def remove_selections!(names, selections)
+ selections.replace(selections - names)
+ end
end
diff --git a/app/controllers/application_controller/timelines/options.rb b/app/controllers/application_controller/timelines/options.rb
index 5220cbf0410f..7c5ad88d9883 100644
--- a/app/controllers/application_controller/timelines/options.rb
+++ b/app/controllers/application_controller/timelines/options.rb
@@ -34,6 +34,8 @@ def update_from_params(params)
self.levels = params[:tl_levels]&.map(&:to_sym) || group_levels
self.categories = {}
params.fetch(:tl_categories, []).each do |category_display_name|
+ next if category_display_name == "Other"
+
group_data = event_groups[events[category_display_name]]
category = {
:display_name => category_display_name,
diff --git a/app/controllers/catalog_controller.rb b/app/controllers/catalog_controller.rb
index 3697f8544eee..3590f0b81095 100644
--- a/app/controllers/catalog_controller.rb
+++ b/app/controllers/catalog_controller.rb
@@ -710,6 +710,9 @@ def svc_catalog_provision
ra, st, svc_catalog_provision_finish_submit_endpoint
)
@in_a_form = true
+ @dialog_locals = options[:dialog_locals]
+ # require 'byebug'
+ # byebug
replace_right_cell(:action => "dialog_provision", :dialog_locals => options[:dialog_locals])
else
# if catalog item has no dialog and provision button was pressed from list view
diff --git a/app/controllers/cloud_network_controller.rb b/app/controllers/cloud_network_controller.rb
index 8f23ecafdd8d..6f0d0e300a0c 100644
--- a/app/controllers/cloud_network_controller.rb
+++ b/app/controllers/cloud_network_controller.rb
@@ -45,7 +45,7 @@ def create
when "add"
options = form_params
ems = ExtManagementSystem.find(options[:ems_id])
- if CloudNetwork.class_by_ems(ems).supports?(:create)
+ if CloudNetwork.class_by_ems(ems)&.supports?(:create)
options.delete(:ems_id)
task_id = ems.create_cloud_network_queue(session[:userid], options)
unless task_id.kind_of?(Integer)
diff --git a/app/controllers/container_dashboard_controller.rb b/app/controllers/container_dashboard_controller.rb
index 72aa98bfb840..1efe00133e21 100644
--- a/app/controllers/container_dashboard_controller.rb
+++ b/app/controllers/container_dashboard_controller.rb
@@ -112,7 +112,7 @@ def breadcrumbs_options
:breadcrumbs => [
{:title => _("Compute")},
{:title => _("Containers")},
- {:title => _("Overview"), :url => controller_url},
+ {:title => _("Overview"), :url => "#{controller_url}/show"},
],
}
end
diff --git a/app/controllers/ems_storage_dashboard_controller.rb b/app/controllers/ems_storage_dashboard_controller.rb
index 937f7097b79e..862d98ff57b0 100644
--- a/app/controllers/ems_storage_dashboard_controller.rb
+++ b/app/controllers/ems_storage_dashboard_controller.rb
@@ -23,6 +23,11 @@ def aggregate_status_data
render :json => {:data => aggregate_status(params[:id])}
end
+ def aggregate_event_data
+ assert_privileges('ems_storage_show') # TODO: might be ems_event_show
+ render :json => {:data => aggregate_event(params[:id])}
+ end
+
private
def block_storage_heatmap_data(ems_id)
@@ -33,6 +38,10 @@ def aggregate_status(ems_id)
EmsStorageDashboardService.new(ems_id, self, EmsStorage).aggregate_status_data
end
+ def aggregate_event(ems_id)
+ EmsStorageDashboardService.new(ems_id, self, EmsStorage).aggregate_event_data
+ end
+
def get_session_data
@layout = "ems_storage_dashboard"
end
diff --git a/app/controllers/floating_ip_controller.rb b/app/controllers/floating_ip_controller.rb
index 12fc0d9ca7a6..2793b62532cb 100644
--- a/app/controllers/floating_ip_controller.rb
+++ b/app/controllers/floating_ip_controller.rb
@@ -44,7 +44,7 @@ def create
options = form_params
ems = ExtManagementSystem.find(options[:ems_id])
- if FloatingIp.class_by_ems(ems).supports?(:create)
+ if FloatingIp.class_by_ems(ems)&.supports?(:create)
options.delete(:ems_id)
task_id = ems.create_floating_ip_queue(session[:userid], options)
diff --git a/app/controllers/generic_object_definition_controller.rb b/app/controllers/generic_object_definition_controller.rb
index 5df73885b7ff..d295fd66bbb2 100644
--- a/app/controllers/generic_object_definition_controller.rb
+++ b/app/controllers/generic_object_definition_controller.rb
@@ -276,6 +276,7 @@ def replace_right_cell
@explorer = false
node = x_node || params[:id]
+ @node_type = node.split('-').first
v_tb, c_tb = case node_type(node)
when :root then process_root_node(presenter)
diff --git a/app/controllers/mixins/actions/vm_actions/associate_floating_ip.rb b/app/controllers/mixins/actions/vm_actions/associate_floating_ip.rb
index 1d2a0f7285ca..b88fb7a116f0 100644
--- a/app/controllers/mixins/actions/vm_actions/associate_floating_ip.rb
+++ b/app/controllers/mixins/actions/vm_actions/associate_floating_ip.rb
@@ -57,10 +57,6 @@ def associate_floating_ip_form_fields
def associate_floating_ip_vm
assert_privileges("instance_associate_floating_ip")
@record = find_record_with_rbac(VmCloud, params[:id])
- case params[:button]
- when "cancel" then associate_handle_cancel_button
- when "submit" then associate_handle_submit_button
- end
if @sb[:explorer]
replace_right_cell
@@ -72,38 +68,6 @@ def associate_floating_ip_vm
end
end
end
-
- def associate_handle_cancel_button
- add_flash(_("Association of Floating IP with Instance \"%{name}\" was cancelled by the user") % {:name => @record.name})
- @record = @sb[:action] = nil
- end
-
- def associate_handle_submit_button
- if @record.supports?(:associate_floating_ip)
- floating_ip = params[:floating_ip][:address]
- begin
- @record.associate_floating_ip_queue(session[:userid], floating_ip)
- add_flash(_("Associating Floating IP %{address} with Instance \"%{name}\"") % {
- :address => floating_ip,
- :name => @record.name
- })
- rescue StandardError => ex
- add_flash(_("Unable to associate Floating IP %{address} with Instance \"%{name}\": %{details}") % {
- :address => floating_ip,
- :name => @record.name,
- :details => get_error_message_from_fog(ex.to_s)
- }, :error)
- end
- else
- add_flash(_("Unable to associate Floating IP with Instance \"%{name}\": %{details}") % {
- :name => @record.name,
- :details => @record.unsupported_reason(:associate_floating_ip)
- }, :error)
- end
- params[:id] = @record.id.to_s # reset id in params for show
- @record = nil
- @sb[:action] = nil
- end
end
end
end
diff --git a/app/controllers/mixins/actions/vm_actions/disassociate_floating_ip.rb b/app/controllers/mixins/actions/vm_actions/disassociate_floating_ip.rb
index 5b15ad0d2030..0b68130d86d8 100644
--- a/app/controllers/mixins/actions/vm_actions/disassociate_floating_ip.rb
+++ b/app/controllers/mixins/actions/vm_actions/disassociate_floating_ip.rb
@@ -61,63 +61,6 @@ def disassociate_floating_ip_form_fields
def disassociate_floating_ip_vm
assert_privileges("instance_disassociate_floating_ip")
@record = find_record_with_rbac(VmCloud, params[:id])
-
- case params[:button]
- when "cancel" then disassociate_handle_cancel_button
- when "submit" then disassociate_handle_submit_button
- end
- end
-
- private
-
- def disassociate_handle_cancel_button
- add_flash(_("Disassociation of Floating IP from Instance \"%{name}\" was cancelled by the user") % {:name => @record.name})
- @record = @sb[:action] = nil
- if @sb[:explorer]
- replace_right_cell
- else
- flash_to_session
- render :update do |page|
- page << javascript_prologue
- page.redirect_to(previous_breadcrumb_url)
- end
- end
- end
-
- def disassociate_handle_submit_button
- if @record.supports?(:disassociate_floating_ip)
- floating_ip = params[:floating_ip][:address]
- begin
- @record.disassociate_floating_ip_queue(session[:userid], floating_ip)
- add_flash(_("Disassociating Floating IP %{address} from Instance \"%{name}\"") % {
- :address => floating_ip,
- :name => @record.name
- })
- rescue StandardError => ex
- add_flash(_("Unable to disassociate Floating IP %{address} from Instance \"%{name}\": %{details}") % {
- :address => floating_ip,
- :name => @record.name,
- :details => get_error_message_from_fog(ex.to_s)
- }, :error)
- end
- else
- add_flash(_("Unable to disassociate Floating IP from Instance \"%{name}\": %{details}") % {
- :name => @record.name,
- :details => @record.unsupported_reason(:disassociate_floating_ip)
- }, :error)
- end
- params[:id] = @record.id.to_s # reset id in params for show
- @record = nil
- @sb[:action] = nil
- if @sb[:explorer]
- replace_right_cell
- else
- flash_to_session
- render :update do |page|
- page << javascript_prologue
- page.redirect_to(previous_breadcrumb_url)
- end
- end
end
end
end
diff --git a/app/controllers/mixins/actions/vm_actions/resize.rb b/app/controllers/mixins/actions/vm_actions/resize.rb
index acd2a300eead..45cf9e446610 100644
--- a/app/controllers/mixins/actions/vm_actions/resize.rb
+++ b/app/controllers/mixins/actions/vm_actions/resize.rb
@@ -56,79 +56,6 @@ def resize
@resize = true
render :action => "show" unless @explorer
end
-
- def resize_vm
- assert_privileges("instance_resize")
- @record = find_record_with_rbac(VmOrTemplate, params[:objectId])
- if params[:id] && params[:id] != 'new'
- @request_id = params[:id]
- end
-
- case params[:button]
- when "cancel"
- add_flash(_("Reconfigure of Instance \"%{name}\" was cancelled by the user") % {:name => @record.name})
- @record = @sb[:action] = nil
- when "submit"
- if @record.supports?(:resize)
- begin
- flavor_id = params['flavor_id']
- flavor = find_record_with_rbac(Flavor, flavor_id)
- old_flavor_name = @record.flavor.try(:name) || _("unknown")
- options = {:src_ids => [@record.id],
- :instance_type => flavor_id}
- VmCloudReconfigureRequest.make_request(@request_id, options, current_user)
- add_flash(_("Reconfiguring Instance \"%{name}\" from %{old_flavor} to %{new_flavor}") % {
- :name => @record.name,
- :old_flavor => old_flavor_name,
- :new_flavor => flavor.name
- })
- rescue StandardError => ex
- add_flash(_("Unable to reconfigure Instance \"%{name}\": %{details}") % {
- :name => @record.name,
- :details => get_error_message_from_fog(ex.to_s)
- }, :error)
- end
- else
- add_flash(_("Unable to reconfigure Instance \"%{name}\": %{details}") % {
- :name => @record.name,
- :details => @record.unsupported_reason(:resize)
- }, :error)
- end
- params[:id] = @record.id.to_s # reset id in params for show
- @record = nil
- @sb[:action] = nil
- end
- if @sb[:explorer] && !(@breadcrumbs.length >= 2 && previous_breadcrumb_url.include?('miq_request'))
- @sb[:explorer] = nil
- replace_right_cell
- else
- flash_to_session
- javascript_redirect(previous_breadcrumb_url)
- end
- end
-
- def resize_form_fields
- assert_privileges("instance_resize")
-
- @request_id = params[:id]
- @record = find_record_with_rbac(VmOrTemplate, params[:objectId])
- flavors = []
- # include only flavors with root disks at least as big as the instance's current root disk.
- @record.ext_management_system&.flavors&.each do |ems_flavor|
- # include only flavors with root disks at least as big as the instance's current root disk.
- if @record.flavor.nil? || ((ems_flavor != @record.flavor) && (ems_flavor.root_disk_size >= @record.flavor.root_disk_size))
- flavors << {:name => ems_flavor.name_with_details, :id => ems_flavor.id}
- end
- end
- resize_values = {
- :flavors => flavors
- }
- unless @request_id == 'new'
- @req = MiqRequest.find_by(:id => @request_id)
- resize_values[:flavor_id] = @req.options[:instance_type].to_i
- end
- render :json => resize_values
- end
end
end
end
diff --git a/app/controllers/mixins/ems_common.rb b/app/controllers/mixins/ems_common.rb
index 433b0dcaf2b7..6f99bda4e5c6 100644
--- a/app/controllers/mixins/ems_common.rb
+++ b/app/controllers/mixins/ems_common.rb
@@ -103,6 +103,7 @@ def display_methods
physical_servers_with_host
physical_switches
physical_storages
+ placement_groups
security_groups
security_policies
security_policy_rules
diff --git a/app/controllers/ops_controller/ops_rbac.rb b/app/controllers/ops_controller/ops_rbac.rb
index 4ba08e28a8ad..1627b6d1dcd3 100644
--- a/app/controllers/ops_controller/ops_rbac.rb
+++ b/app/controllers/ops_controller/ops_rbac.rb
@@ -140,35 +140,13 @@ def rbac_tenant_edit
end
def rbac_tenant_manage_quotas_cancel
- @tenant = Tenant.find(params[:id])
- add_flash(_("Manage quotas for %{model}\ \"%{name}\" was cancelled by the user") %
- {:model => tenant_type_title_string(@tenant.divisible), :name => @tenant.name})
get_node_info(x_node)
replace_right_cell(:nodetype => x_node)
end
def rbac_tenant_manage_quotas_save_add
- tenant = Tenant.find(params[:id])
- begin
- tenant.set_quotas(rbac_tenant_manage_quotas_params.to_h.deep_symbolize_keys)
- rescue => bang
- add_flash(_("Error when saving tenant quota: %{message}") % {:message => bang.message}, :error)
- javascript_flash
- else
- add_flash(_("Quotas for %{model} \"%{name}\" were saved") %
- {:model => tenant_type_title_string(tenant.divisible), :name => tenant.name})
- get_node_info(x_node)
- replace_right_cell(:nodetype => "root", :replace_trees => [:rbac])
- end
- end
-
- private def rbac_tenant_manage_quotas_params
- if params[:quotas]
- permitted_attrs = TenantQuota::NAMES.index_with { %i[unit value warn_value] }
- params.require(:quotas).permit(permitted_attrs)
- else
- {}
- end
+ get_node_info(x_node)
+ replace_right_cell(:nodetype => "root", :replace_trees => [:rbac])
end
def rbac_tenant_manage_quotas_reset
@@ -177,7 +155,6 @@ def rbac_tenant_manage_quotas_reset
@edit = {:tenant_id => @tenant.id}
session[:edit] = {:key => "tenant_manage_quotas__#{@tenant.id}"}
session[:changed] = false
- add_flash(_("All changes have been reset"), :warning) if params[:button] == 'reset'
replace_right_cell(:nodetype => "tenant_manage_quotas")
end
@@ -193,17 +170,6 @@ def rbac_tenant_manage_quotas
end
end
- def tenant_quotas_form_fields
- assert_privileges("rbac_tenant_manage_quotas")
-
- tenant = Tenant.find(params[:id])
- tenant_quotas = tenant.get_quotas
- render :json => {
- :name => tenant.name,
- :quotas => tenant_quotas
- }
- end
-
# Edit user or group tags
def rbac_tenant_tags_edit
case params[:button]
diff --git a/app/controllers/placement_group_controller.rb b/app/controllers/placement_group_controller.rb
new file mode 100644
index 000000000000..7fccc6a813a8
--- /dev/null
+++ b/app/controllers/placement_group_controller.rb
@@ -0,0 +1,55 @@
+class PlacementGroupController < 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::MoreShowActions
+ include Mixins::GenericShowMixin
+ include Mixins::EmsCommon
+ include Mixins::BreadcrumbsMixin
+
+ def self.display_methods
+ %w[instances]
+ end
+
+ def breadcrumb_name(_model)
+ _("Placement Groups")
+ end
+
+ def self.table_name
+ @table_name ||= "placement_group"
+ end
+
+ def download_data
+ assert_privileges('placement_group_view')
+ super
+ end
+
+ def download_summary_pdf
+ assert_privileges('placement_group_view')
+ super
+ end
+
+ private
+
+ def textual_group_list
+ [%i[relationships properties], %i[tags]]
+ end
+ helper_method :textual_group_list
+
+ def breadcrumbs_options
+ {
+ :breadcrumbs => [
+ {:title => _("Compute")},
+ {:title => _("Clouds")},
+ {:title => _("Placement Groups"), :url => controller_url},
+ ],
+ }
+ end
+
+ menu_section :clo
+ feature_for_actions "#{controller_name}_show_list", *ADV_SEARCH_ACTIONS
+end
diff --git a/app/controllers/report_controller/saved_reports.rb b/app/controllers/report_controller/saved_reports.rb
index 293656a19be5..3463d3cd84e0 100644
--- a/app/controllers/report_controller/saved_reports.rb
+++ b/app/controllers/report_controller/saved_reports.rb
@@ -125,7 +125,7 @@ def saved_report_delete
@report = nil
r = MiqReportResult.for_user(current_user).find(savedreports[0])
@sb[:miq_report_id] = r.miq_report_id
- process_saved_reports(savedreports, "destroy") unless savedreports.empty?
+ params[:miq_grid_checks] = process_saved_reports(savedreports, "destroy") unless savedreports.empty?
add_flash(_("The selected Saved Report was deleted")) if @flash_array.nil?
@report_deleted = true
end
diff --git a/app/controllers/security_group_controller.rb b/app/controllers/security_group_controller.rb
index b0734b4863a8..41b65ff4f2a4 100644
--- a/app/controllers/security_group_controller.rb
+++ b/app/controllers/security_group_controller.rb
@@ -55,7 +55,7 @@ def create
@security_group = SecurityGroup.new
options = form_params
ems = ExtManagementSystem.find(options[:ems_id])
- if SecurityGroup.class_by_ems(ems).supports?(:create)
+ if SecurityGroup.class_by_ems(ems)&.supports?(:create)
options.delete(:ems_id)
task_id = ems.create_security_group_queue(session[:userid], options)
diff --git a/app/controllers/vm_common.rb b/app/controllers/vm_common.rb
index 1c783a21b36b..abab8be2368e 100644
--- a/app/controllers/vm_common.rb
+++ b/app/controllers/vm_common.rb
@@ -358,6 +358,10 @@ def floating_ips
show_association('floating_ips', _('Floating IPs'), :floating_ips, FloatingIp)
end
+ def placement_group
+ show_association('placement_groups', _('Placement Groups'), :placement_groups, PlacementGroup)
+ end
+
def cloud_subnets
show_association('cloud_subnets', _('Subnets'), :cloud_subnets, CloudSubnet)
end
@@ -1273,7 +1277,6 @@ def set_right_cell_vars(options = {})
when "resize"
partial = "vm_common/resize"
header = _("Reconfiguring %{vm_or_template} \"%{name}\"") % {:vm_or_template => ui_lookup(:table => table), :name => name}
- action = "resize_vm"
when "retire"
partial = "shared/views/retire"
header = _("Set/Remove retirement date for %{vm_or_template}") % {:vm_or_template => ui_lookup(:table => table)}
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index a7a7a83dd624..1da8be8d44a2 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -353,7 +353,7 @@ def db_to_controller(db, action = "show")
when "ActionSet"
controller = "miq_action"
action = "show_set"
- when "AutomationRequest"
+ when "AutomationRequest", "MiqProvision"
controller = "miq_request"
action = "show"
when "ConditionSet"
@@ -391,6 +391,9 @@ def db_to_controller(db, action = "show")
when "MiqAeInstance"
controller = "miq_ae_class"
action = "show_details"
+ when "PlacementGroup"
+ controller = "placement_group"
+ action = "show"
when "SecurityGroup"
controller = "security_group"
action = "show"
@@ -777,6 +780,7 @@ def display_adv_search?
orchestration_stack
persistent_volume
physical_server
+ placement_group
provider_foreman
resource_pool
retired
@@ -1092,6 +1096,7 @@ def pdf_page_size_style
physical_storage
physical_server
persistent_volume
+ placement_group
policy
policy_group
security_policy
@@ -1335,9 +1340,10 @@ def miq_toolbar(toolbars)
end
def miq_structured_list(data)
- react('MiqStructuredList', {:title => data[:title],
- :headers => data[:headers],
- :rows => data[:rows],
- :message => data[:message], :mode => ["miq_summary", data[:mode]].join(' ')})
+ react('MiqStructuredList', {:title => data[:title],
+ :headers => data[:headers],
+ :rows => data[:rows],
+ :message => data[:message],
+ :mode => "miq_summary #{data[:mode]}"})
end
end
diff --git a/app/helpers/application_helper/button/cloud_network_new.rb b/app/helpers/application_helper/button/cloud_network_new.rb
index 9e297d906794..71a89d0b4955 100644
--- a/app/helpers/application_helper/button/cloud_network_new.rb
+++ b/app/helpers/application_helper/button/cloud_network_new.rb
@@ -1,6 +1,6 @@
class ApplicationHelper::Button::CloudNetworkNew < ApplicationHelper::Button::ButtonNewDiscover
def supports_button_action?
- ::EmsNetwork.all.any? { |ems| CloudNetwork.class_by_ems(ems).supports?(:create) }
+ ::EmsNetwork.all.any? { |ems| CloudNetwork.class_by_ems(ems)&.supports?(:create) }
end
def role_allows_feature?
diff --git a/app/helpers/application_helper/button/cloud_subnet_new.rb b/app/helpers/application_helper/button/cloud_subnet_new.rb
index ed0275f37b2a..a0322cc92865 100644
--- a/app/helpers/application_helper/button/cloud_subnet_new.rb
+++ b/app/helpers/application_helper/button/cloud_subnet_new.rb
@@ -12,6 +12,6 @@ def role_allows_feature?
# disable button if no active providers support create action
def disabled?
- ::EmsNetwork.all.none? { |ems| CloudSubnet.class_by_ems(ems).supports?(:create) }
+ ::EmsNetwork.all.none? { |ems| CloudSubnet.class_by_ems(ems)&.supports?(:create) }
end
end
diff --git a/app/helpers/application_helper/button/floating_ip_new.rb b/app/helpers/application_helper/button/floating_ip_new.rb
index c37835c74283..dd59e0b7e1df 100644
--- a/app/helpers/application_helper/button/floating_ip_new.rb
+++ b/app/helpers/application_helper/button/floating_ip_new.rb
@@ -8,6 +8,6 @@ def calculate_properties
# disable button if no active providers support create action
def disabled?
- ::EmsNetwork.all.none? { |ems| ::FloatingIp.class_by_ems(ems).supports?(:create) }
+ ::EmsNetwork.all.none? { |ems| ::FloatingIp.class_by_ems(ems)&.supports?(:create) }
end
end
diff --git a/app/helpers/application_helper/button/network_router_new.rb b/app/helpers/application_helper/button/network_router_new.rb
index b8cd83e643b2..2d7410a27d21 100644
--- a/app/helpers/application_helper/button/network_router_new.rb
+++ b/app/helpers/application_helper/button/network_router_new.rb
@@ -12,6 +12,6 @@ def role_allows_feature?
# disable button if no active providers support create action
def disabled?
- ::EmsNetwork.all.none? { |ems| NetworkRouter.class_by_ems(ems).supports?(:create) }
+ ::EmsNetwork.all.none? { |ems| NetworkRouter.class_by_ems(ems)&.supports?(:create) }
end
end
diff --git a/app/helpers/application_helper/button/network_service_entry_new.rb b/app/helpers/application_helper/button/network_service_entry_new.rb
index 79547275a1c0..f3acf38b7c6a 100644
--- a/app/helpers/application_helper/button/network_service_entry_new.rb
+++ b/app/helpers/application_helper/button/network_service_entry_new.rb
@@ -1,6 +1,6 @@
class ApplicationHelper::Button::NetworkServiceEntryNew < ApplicationHelper::Button::ButtonNewDiscover
def supports_button_action?
- ::EmsNetwork.all.any? { |ems| NetworkServiceEntry.class_by_ems(ems).supports?(:create) }
+ ::EmsNetwork.all.any? { |ems| NetworkServiceEntry.class_by_ems(ems)&.supports?(:create) }
end
def role_allows_feature?
diff --git a/app/helpers/application_helper/button/network_service_new.rb b/app/helpers/application_helper/button/network_service_new.rb
index 9b5a2726b552..db2cecfdff86 100644
--- a/app/helpers/application_helper/button/network_service_new.rb
+++ b/app/helpers/application_helper/button/network_service_new.rb
@@ -1,6 +1,6 @@
class ApplicationHelper::Button::NetworkServiceNew < ApplicationHelper::Button::ButtonNewDiscover
def supports_button_action?
- ::EmsNetwork.all.any? { |ems| NetworkService.class_by_ems(ems).supports?(:create) }
+ ::EmsNetwork.all.any? { |ems| NetworkService.class_by_ems(ems)&.supports?(:create) }
end
def role_allows_feature?
diff --git a/app/helpers/application_helper/button/security_group_new.rb b/app/helpers/application_helper/button/security_group_new.rb
index 34c208963015..656d70f940c6 100644
--- a/app/helpers/application_helper/button/security_group_new.rb
+++ b/app/helpers/application_helper/button/security_group_new.rb
@@ -8,6 +8,6 @@ def calculate_properties
# disable button if no active providers support create action
def disabled?
- ::EmsNetwork.all.none? { |ems| SecurityGroup.class_by_ems(ems).supports?(:create) }
+ ::EmsNetwork.all.none? { |ems| SecurityGroup.class_by_ems(ems)&.supports?(:create) }
end
end
diff --git a/app/helpers/application_helper/button/security_policy_new.rb b/app/helpers/application_helper/button/security_policy_new.rb
index 583ce24e728a..ef07036f7334 100644
--- a/app/helpers/application_helper/button/security_policy_new.rb
+++ b/app/helpers/application_helper/button/security_policy_new.rb
@@ -1,6 +1,6 @@
class ApplicationHelper::Button::SecurityPolicyNew < ApplicationHelper::Button::ButtonNewDiscover
def supports_button_action?
- ::EmsNetwork.all.any? { |ems| SecurityPolicy.class_by_ems(ems).supports?(:create) }
+ ::EmsNetwork.all.any? { |ems| SecurityPolicy.class_by_ems(ems)&.supports?(:create) }
end
def role_allows_feature?
diff --git a/app/helpers/application_helper/button/security_policy_rule_new.rb b/app/helpers/application_helper/button/security_policy_rule_new.rb
index 5b419a7dd3e8..9310c168f2bb 100644
--- a/app/helpers/application_helper/button/security_policy_rule_new.rb
+++ b/app/helpers/application_helper/button/security_policy_rule_new.rb
@@ -1,6 +1,6 @@
class ApplicationHelper::Button::SecurityPolicyRuleNew < ApplicationHelper::Button::ButtonNewDiscover
def supports_button_action?
- ::EmsNetwork.all.any? { |ems| SecurityPolicyRule.class_by_ems(ems).supports?(:create) }
+ ::EmsNetwork.all.any? { |ems| SecurityPolicyRule.class_by_ems(ems)&.supports?(:create) }
end
def role_allows_feature?
diff --git a/app/helpers/application_helper/listnav.rb b/app/helpers/application_helper/listnav.rb
index e667f2020f2e..6248a9373edb 100644
--- a/app/helpers/application_helper/listnav.rb
+++ b/app/helpers/application_helper/listnav.rb
@@ -4,6 +4,7 @@ def render_listnav_filename
common_layouts = %w[
physical_storage
auth_key_pair_cloud
+ placement_group
automation_manager_configured_system
availability_zone
cloud_database
diff --git a/app/helpers/application_helper/page_layouts.rb b/app/helpers/application_helper/page_layouts.rb
index 5b2d0124e5bb..54732de4225c 100644
--- a/app/helpers/application_helper/page_layouts.rb
+++ b/app/helpers/application_helper/page_layouts.rb
@@ -221,6 +221,7 @@ def show_adv_search?
persistent_volume
physical_server
physical_storage
+ placement_group
resource_pool
retired
security_group
diff --git a/app/helpers/application_helper/toolbar/ems_infra_center.rb b/app/helpers/application_helper/toolbar/ems_infra_center.rb
index cb0de4a20913..837c3926c97e 100644
--- a/app/helpers/application_helper/toolbar/ems_infra_center.rb
+++ b/app/helpers/application_helper/toolbar/ems_infra_center.rb
@@ -143,7 +143,7 @@ class ApplicationHelper::Toolbar::EmsInfraCenter < ApplicationHelper::Toolbar::B
button(
:ems_native_console,
'pficon pficon-screen fa-lg',
- N_('Open a native console for this cloud provider'),
+ N_('Open a native console for this infrastructure provider'),
N_('Native Console'),
:keepSpinner => true,
:url => "launch_console",
diff --git a/app/helpers/application_helper/toolbar/hosts_center.rb b/app/helpers/application_helper/toolbar/hosts_center.rb
index 3536a65a863e..a30ebcef9c7c 100644
--- a/app/helpers/application_helper/toolbar/hosts_center.rb
+++ b/app/helpers/application_helper/toolbar/hosts_center.rb
@@ -204,7 +204,9 @@ class ApplicationHelper::Toolbar::HostsCenter < ApplicationHelper::Toolbar::Basi
:send_checked => true,
:confirm => N_("Power On the selected items?"),
:klass => ApplicationHelper::Button::HostFeatureButton,
- :options => {:feature => :start}),
+ :options => {:feature => :start},
+ :enabled => false,
+ :onwhen => "1+"),
button(
:host_stop,
nil,
@@ -215,7 +217,9 @@ class ApplicationHelper::Toolbar::HostsCenter < ApplicationHelper::Toolbar::Basi
:send_checked => true,
:confirm => N_("Power Off the selected items?"),
:klass => ApplicationHelper::Button::HostFeatureButton,
- :options => {:feature => :stop}),
+ :options => {:feature => :stop},
+ :enabled => false,
+ :onwhen => "1+"),
button(
:host_reset,
nil,
@@ -226,7 +230,9 @@ class ApplicationHelper::Toolbar::HostsCenter < ApplicationHelper::Toolbar::Basi
:send_checked => true,
:confirm => N_("Reset the selected items?"),
:klass => ApplicationHelper::Button::HostFeatureButtonWithDisable,
- :options => {:feature => :reset}),
+ :options => {:feature => :reset},
+ :enabled => false,
+ :onwhen => "1+"),
]
),
])
diff --git a/app/helpers/application_helper/toolbar/placement_group_center.rb b/app/helpers/application_helper/toolbar/placement_group_center.rb
new file mode 100644
index 000000000000..53a97d76cd0d
--- /dev/null
+++ b/app/helpers/application_helper/toolbar/placement_group_center.rb
@@ -0,0 +1,2 @@
+class ApplicationHelper::Toolbar::PlacementGroupCenter < ApplicationHelper::Toolbar::Basic
+end
diff --git a/app/helpers/application_helper/toolbar/placement_groups_center.rb b/app/helpers/application_helper/toolbar/placement_groups_center.rb
new file mode 100644
index 000000000000..e87920bba8e3
--- /dev/null
+++ b/app/helpers/application_helper/toolbar/placement_groups_center.rb
@@ -0,0 +1,2 @@
+class ApplicationHelper::Toolbar::PlacementGroupsCenter < ApplicationHelper::Toolbar::Basic
+end
diff --git a/app/helpers/application_helper/toolbar/x_vm_center.rb b/app/helpers/application_helper/toolbar/x_vm_center.rb
index 7ff8e249bc35..4da6ce85a95e 100644
--- a/app/helpers/application_helper/toolbar/x_vm_center.rb
+++ b/app/helpers/application_helper/toolbar/x_vm_center.rb
@@ -290,6 +290,16 @@ class ApplicationHelper::Toolbar::XVmCenter < ApplicationHelper::Toolbar::Basic
:url => "native_console",
:klass => ApplicationHelper::Button::VmNativeConsole
),
+ button(
+ :vm_native_console,
+ 'pficon pficon-screen fa-lg',
+ N_('Open a management console for this VM'),
+ N_('Management Console'),
+ :keepSpinner => true,
+ :url => "management_console",
+ :klass => ApplicationHelper::Button::GenericFeatureButton,
+ :options => {:feature => :native_console}
+ ),
]
),
])
diff --git a/app/helpers/application_helper/toolbar_chooser.rb b/app/helpers/application_helper/toolbar_chooser.rb
index 4d83c8fd4bba..d6356a196dea 100644
--- a/app/helpers/application_helper/toolbar_chooser.rb
+++ b/app/helpers/application_helper/toolbar_chooser.rb
@@ -437,6 +437,7 @@ def center_toolbar_filename_classic
physical_server
physical_switch
physical_storage
+ placement_group
container_template
resource_pool
timeline
diff --git a/app/helpers/catalog_helper.rb b/app/helpers/catalog_helper.rb
index c83871080586..62dbaa47f3b7 100644
--- a/app/helpers/catalog_helper.rb
+++ b/app/helpers/catalog_helper.rb
@@ -1,5 +1,6 @@
module CatalogHelper
include_concern 'TextualSummary'
+ include RequestInfoHelper
def miq_catalog_resource(resources)
headers = ["", _("Name"), _("Description"), _("Action Order"), _("Provision Order"), _("Action Start"), _("Action Stop"), _("Delay (mins) Start"), _("Delay (mins) Stop")]
@@ -55,9 +56,246 @@ def service_catalog_summary(record, sb_data)
miq_structured_list(data)
end
+ def catalog_tab_configuration(record)
+ condition = catalog_tab_conditions(record)
+ tab_labels = [tab_label(:basic)]
+ tab_labels.push(tab_label(:detail)) if condition[:detail]
+
+ if condition[:resource]
+ tab_labels.push(tab_label(:resource))
+ elsif condition[:request]
+ tab_labels.push(tab_label(:request))
+ end
+
+ if condition[:provision]
+ tab_labels.push(tab_label(:provision))
+ tab_labels.push(tab_label(:retirement)) if condition[:retirement]
+ end
+
+ return tab_labels, condition
+ end
+
+ def catalog_tab_edit_configuration(record)
+ condition = catalog_tab_edit_conditions(record)
+ tab_labels = [tab_label(:basic)]
+ tab_labels.push(tab_label(:detail)) if condition[:detail]
+ tab_labels.push(tab_label(:resource)) if condition[:resource]
+ tab_labels.push(tab_label(:request)) if condition[:request]
+ return tab_labels, condition
+ end
+
+ def catalog_tab_edit_generic_configuration
+ [tab_label(:basic), tab_label(:provision), tab_label(:retirement)]
+ end
+
+ def catalog_tab_content(key_name, &block)
+ if catalog_tabs_types[key_name]
+ class_name = key_name == :basic ? 'tab_content active' : 'tab_content'
+ content_tag(:div, :id => key_name, :class => class_name, &block)
+ end
+ end
+
+ def catalog_basic_information(record, sb_params, tenants_tree)
+ prov_types = catalog_provision_types
+ prov_data = [prov_types[:template], prov_types[:ovf]].include?(record.prov_type) && catalog_provision?(record, :playbook) ? provisioning : nil
+ data = {:title => _('Basic Information'), :mode => "miq_catalog_basic_information"}
+ rows = []
+ rows.push(row_data(_('Name / Description'), "#{record.name} / #{record.description}"))
+ rows.push(row_data(_('Display in Catalog'), {:input => "checkbox", :name => "display", :checked => record.display, :disabled => true, :label => ''}))
+ rows.push(row_data(_('Catalog'), h(record.service_template_catalog ? record.service_template_catalog.name : _('Unassigned'))))
+ rows.push(row_data(_('Zone'), record.zone ? record.zone.name : '')) unless record.composite?
+ rows.push(row_data(_('Dialog'), h(sb_params[:dialog_label]))) unless catalog_provision?(record, :playbook)
+ rows.push(row_data(_("Price / Month (in %{currency})") % {:currency => record.currency.code}, record.price)) if record.currency
+ rows.push(row_data(_('Item Type'), h(_(ServiceTemplate.all_catalog_item_types[record.prov_type])))) if record.prov_type
+ rows.push(row_data(_('Subtype'), h(_(ServiceTemplate::GENERIC_ITEM_SUBTYPES[record[:generic_subtype]]) || _("Custom")))) if catalog_provision?(record, :generic)
+
+ if catalog_provision?(record, :orchestration)
+ rows.push(row_data(_('Orchestration Template'), h(record.try(:orchestration_template).try(:name))))
+ rows.push(row_data(_('Provider'), h(record.orchestration_manager.name))) if record.orchestration_manager
+ elsif catalog_provision?(record, :tower)
+ rows.push(row_data(_('Ansible Tower Template'), h(record.try(:job_template).try(:name))))
+ elsif catalog_provision?(record, :template)
+ rows.push(row_data(_('Provider'), provision_data(prov_data, :provider_name)))
+ rows.push(row_data(_('Container Template'), provision_data(prov_data, :template_name)))
+ end
+
+ unless catalog_provision?(record, :playbook)
+ entry_points = [[_("Provisioning"), :fqname]]
+ unless record.prov_type.try(:start_with?, "generic_")
+ entry_points.push([_("Reconfigure"), :reconfigure_fqname], [_("Retirement"), :retire_fqname])
+ end
+ entry_points.each do |entry_points_op|
+ rows.push(row_data("#{entry_points_op[0]} %s" % _('Entry Point'), h(sb_params[entry_points_op[1]])))
+ end
+ end
+
+ rows.push(row_data(_('Tenant'), h(record.tenant.name))) if User.current_user.super_admin_user?
+ rows.push(row_data(_('Owner'), h(record.try(:evm_owner).try(:name))))
+ rows.push(row_data(_('Ownership Group'), h(record.try(:miq_group).try(:name))))
+ rows.push(row_data(_('Additional Tenants'), {:input => 'component', :component => 'TREE_VIEW_REDUX', :props => tenants_tree.locals_for_render})) if role_allows?(:feature => 'rbac_tenant_view')
+
+ if catalog_provision?(record, :ovf)
+ options = record.config_info[:provision]
+ rows.push(row_data(_('OVF Template'), provision_data(prov_data, :ovf_template_name)))
+ rows.push(row_data(_('VM Name'), options[:vm_name]))
+ rows.push(row_data(_('Accept EULA'), {:input => "checkbox", :name => "accept_ecula", :checked => options[:accept_all_eula], :disabled => true, :label => ''}))
+ rows.push(row_data(_('Datacenter'), provision_data(prov_data, :datacenter_name)))
+ rows.push(row_data(_('Resource Pool'), provision_data(prov_data, :resource_pool_name)))
+ rows.push(row_data(_('Folder'), provision_data(prov_data, :ems_folder_name)))
+ rows.push(row_data(_('Host'), provision_data(prov_data, :host_name)))
+ rows.push(row_data(_('Storage'), provision_data(prov_data, :storage_name)))
+ rows.push(row_data(_('Disk Format'), provision_data(prov_data, :disk_format)))
+ rows.push(row_data(_('Virtual Network'), provision_data(prov_data, :network_name)))
+ end
+
+ data[:rows] = rows
+ miq_structured_list(data)
+ end
+
+ def catalog_smart_management(record)
+ smart_mgnt = textual_tags_render_data(record)
+ data = {:title => smart_mgnt[:title], :mode => "miq_catalog-smart_management"}
+ rows = []
+ smart_mgnt[:items].each do |item|
+ row = row_data(item[:label], item[:value])
+ row[:cells][:icon] = item[:icon] if item[:icon]
+ rows.push(row)
+ end
+ data[:rows] = rows
+ miq_structured_list(data)
+ end
+
+ def catalog_custom_image(record)
+ picture = record.picture ? "#{record.picture.url_path}?#{rand(99_999_999)}" : nil
+ data = {:title => _('Custom Image'), :mode => "miq_catalog_custom_image"}
+ data[:rows] = [row_data('', {:input => 'component', :component => 'CATALOG_CUSTOM_IMAGE', :props => {:recordId => record.id, :image => picture}})]
+ miq_structured_list(data)
+ end
+
+ def catalog_details(record)
+ data = {:title => _('Details'), :mode => "miq_catalog_details"}
+ data[:rows] = [row_data(_('Long Description'), record.long_description)]
+ miq_structured_list(data)
+ end
+
+ def catalog_resources(record)
+ resources = record.service_resources
+ data = {:title => _('Resources'), :mode => "miq_catalog_resources"}
+ data[:rows] = [row_data('', {:input => 'component', :component => 'CATALOG_RESOURCE', :props => {:initialData => miq_catalog_resource(resources)}})]
+ miq_structured_list(data)
+ end
+
+ def catalog_generic_ansible_playbook_info(type, record, info)
+ list_type = type == :provision ? 'provisioning' : 'retirement'
+ data = {:title => "#{list_type.camelize} %s" % _('Info'), :mode => "miq_catalog_playbook_info"}
+ rows = []
+ rows.push(row_data(_('Repository'), h(info[:repository])))
+ rows.push(row_data(_('Playbook'), h(info[:playbook])))
+ rows.push(row_data(_('Machine Credential'), h(info[:machine_credential])))
+ rows.push(row_data(_('Vault Credential'), h(info[:vault_credential])))
+ rows.push(row_data(_('Vault Credential'), h(info[:vault_credential])))
+ rows.push(row_data(_('Cloud Credential'), h(info[:cloud_credential])))
+ rows.push(row_data(_('Max TTL (mins)'), h(record.config_info[type][:execution_ttl])))
+ rows.push(row_data(_('Hosts'), h(record.config_info[type][:hosts])))
+ rows.push(row_data(_('Logging Output'), h(ViewHelper::LOG_OUTPUT_LEVELS[info[:log_output]])))
+ rows.push(row_data(_('Escalate Privilege'), h(info[:become_enabled])))
+ rows.push(row_data(_('Verbosity'), _(ViewHelper::VERBOSITY_LEVELS[info[:verbosity]])))
+ data[:rows] = rows
+ miq_structured_list(data)
+ end
+
+ def catalog_variables_default_data(type, record)
+ data = {:title => _("Variables & Default Values"), :mode => "miq_catalog_variable_data"}
+ data[:headers] = [_("Variable"), _("Default value")]
+ rows = []
+ extra_vars = record.config_info[type][:extra_vars]
+ if extra_vars
+ extra_vars.each do |key, value|
+ rows.push({:cells => [{:value => h(key)}, {:value => h(value[:default])}]})
+ end
+ else
+ data[:message] = _("No variables & default values available")
+ end
+ data[:rows] = rows
+ miq_structured_list(data)
+ end
+
+ def catalog_dialog(provisioning)
+ rows = []
+ data = {:title => _("Dialog"), :mode => "miq_catalog_dialog"}
+ if provisioning[:dialog_id]
+ if role_allows?(:feature => "dialog_accord", :any => true)
+ rows.push({
+ :cells => [{:value => provisioning[:dialog]}],
+ :title => provisioning[:dialog],
+ :onclick => "DoNav('/miq_ae_customization/show/dg-#{provisioning[:dialog_id]}');",
+ })
+ else
+ rows.push(row_data('', provisioning[:dialog]))
+ end
+ end
+ data[:rows] = rows
+ miq_structured_list(data)
+ end
+
+ def catalog_provision_types
+ {:generic => "generic",
+ :orchestration => "generic_orchestration",
+ :ovf => "generic_ovf_template",
+ :playbook => "generic_ansible_playbook",
+ :tower => "generic_ansible_tower",
+ :template => "generic_container_template"}.freeze
+ end
+
private
+ def catalog_tabs_types
+ {
+ :basic => _('Basic Information'),
+ :detail => _('Details'),
+ :resource => _('Selected Resources'),
+ :request => _('Request Info'),
+ :provision => _('Provisioning'),
+ :retirement => _('Retirement')
+ }
+ end
+
+ def catalog_tab_conditions(record)
+ {
+ :detail => record.display && !record.prov_type.try(:start_with?, "generic_"),
+ :resource => record.composite?,
+ :request => !record.prov_type || (record.prov_type && need_prov_dialogs?(record.prov_type)),
+ :provision => record.prov_type == catalog_provision_types[:playbook],
+ :retirement => record.config_info.fetch_path(:retirement)
+ }
+ end
+
+ def catalog_tab_edit_conditions(record)
+ resource = request = false
+ detail = !!record[:display]
+ unless record[:st_prov_type].try(:start_with?, "generic_")
+ if record[:service_type] == "composite"
+ resource = true
+ elsif record[:service_type] == "atomic" && need_prov_dialogs?(record[:st_prov_type])
+ request = true
+ end
+ end
+ {:detail => detail, :resource => resource, :request => request}
+ end
+
+ def provision_data(data, type)
+ data && data[type]
+ end
+
def row_data(label, value)
{:cells => {:label => label, :value => value}}
end
+
+ def catalog_provision?(record, type)
+ record.prov_type == catalog_provision_types[type]
+ end
+
+ def tab_label(item)
+ {:name => item, :text => catalog_tabs_types[item]}
+ end
end
diff --git a/app/helpers/ems_cloud_helper/textual_summary.rb b/app/helpers/ems_cloud_helper/textual_summary.rb
index dc0949473ca9..b468b72fa0db 100644
--- a/app/helpers/ems_cloud_helper/textual_summary.rb
+++ b/app/helpers/ems_cloud_helper/textual_summary.rb
@@ -19,7 +19,7 @@ def textual_group_relationships
_("Relationships"),
%i[
ems_infra network_manager availability_zones host_aggregates cloud_tenants flavors
- security_groups instances images cloud_volumes orchestration_stacks storage_managers cloud_databases
+ security_groups placement_groups instances images cloud_volumes orchestration_stacks storage_managers cloud_databases
custom_button_events tenant
]
)
@@ -121,6 +121,16 @@ def textual_storage_managers
h
end
+ def textual_placement_groups
+ num = @record.try(:placement_groups) ? @record.number_of(:placement_groups) : 0
+ h = {:label => _('Placement Groups'), :icon => "fa fa-database", :value => num}
+ if num.positive?
+ h[:title] = _("Show all Placement Groups")
+ h[:link] = ems_cloud_path(@record.id, :display => 'placement_groups')
+ end
+ h
+ end
+
def textual_cloud_databases
num = @record.try(:cloud_databases) ? @record.number_of(:cloud_databases) : 0
h = {:label => _('Cloud Databases'), :icon => "fa fa-database", :value => num}
diff --git a/app/helpers/generic_object_definition_helper.rb b/app/helpers/generic_object_definition_helper.rb
index b713621620fd..2a69fbc35196 100644
--- a/app/helpers/generic_object_definition_helper.rb
+++ b/app/helpers/generic_object_definition_helper.rb
@@ -1,3 +1,57 @@
module GenericObjectDefinitionHelper
include_concern 'TextualSummary'
+
+ def generic_object_definition_button_summary(button)
+ style = button.options.key?(:button_color) ? button.options[:button_color].to_s : nil
+ data = {:title => _('Basic Information')}
+ data[:rows] = [
+ row_data(_('Name'), button.name),
+ row_data(_('Display in Catalog'), {:input => "checkbox", :name => "display", :checked => button.options[:display], :disabled => true, :label => ''}),
+ row_data(_('Descrption'), button.description),
+ row_data(_('Image'), button.options[:button_icon], style, :icon => true),
+ ]
+ miq_structured_list(data)
+ end
+
+ def generic_object_definition_button_group_summary(button_group)
+ @custom_buttons_table_data = custom_buttons_table_data(button_group.set_data[:button_order].map { |id| CustomButton.find_by(:id => id) })
+ style = button_group.set_data[:button_color] ? button_group.set_data[:button_color].to_s : nil
+ icon = button_group.set_data[:button_icon].to_s == "" ? "pficon-folder-close" : button_group.set_data[:button_icon].to_s
+ data = {:title => _('Basic Information')}
+ data[:rows] = [
+ row_data(_('Name'), button_group.name),
+ row_data(_('Display in Catalog'), {:input => "checkbox", :name => "display", :checked => button_group.set_data[:display], :disabled => true, :label => ''}),
+ row_data(_('Descrption'), button_group.description),
+ row_data(_('Image'), icon, style, :icon => true),
+ ]
+ miq_structured_list(data)
+ end
+
+ private
+
+ def row_data(label, value, style = "", icon: false)
+ if icon
+ data = {:cells => {:label => label, :icon => value, :color => style}}
+ else
+ data = {:cells => {:label => label, :value => value}}
+ data[:style] = style if style.present?
+ end
+ data
+ end
+
+ def custom_buttons_table_data(button_group)
+ rows = []
+ button_group.each do |button|
+ rows.push(
+ {
+ :name => button.name,
+ :id => button.id,
+ :description => button.description,
+ :button_icon => button.options[:button_icon],
+ :button_color => button.options[:button_color]
+ }
+ )
+ end
+ rows
+ end
end
diff --git a/app/helpers/generic_object_definition_helper/textual_summary.rb b/app/helpers/generic_object_definition_helper/textual_summary.rb
index f40147032c5f..1cacb5fb000e 100644
--- a/app/helpers/generic_object_definition_helper/textual_summary.rb
+++ b/app/helpers/generic_object_definition_helper/textual_summary.rb
@@ -33,7 +33,7 @@ def textual_generic_objects
def textual_group_attribute_details_list
if @record.property_attributes.count.zero?
- TextualEmpty.new(_('Attributes'), _('No Attributes defined'))
+ empty_record_properties_list(_('Attributes'))
else
record_properties_list(
_('Attributes (%{count})') % {:count => @record.property_attributes.count},
@@ -44,7 +44,7 @@ def textual_group_attribute_details_list
def textual_group_association_details_list
if @record.property_associations.count.zero?
- TextualEmpty.new(_('Associations'), _('No Associations defined'))
+ empty_record_properties_list(_('Associations'))
else
record_properties_list(
_('Associations (%{count})') % {:count => @record.property_associations.count },
@@ -62,9 +62,18 @@ def record_properties_list(type_and_count, type, labels)
)
end
+ def empty_record_properties_list(title)
+ TextualMultilabel.new(
+ title,
+ :additional_table_class => "table-fixed",
+ :labels => nil,
+ :values => nil
+ )
+ end
+
def textual_group_method_details_list
if @record.property_methods.count.zero?
- TextualEmpty.new(_('Methods'), _('No Methods defined'))
+ empty_record_properties_list(_('Methods'))
else
TextualMultilabel.new(
_('Methods (%{count})') % {:count => @record.property_methods.count},
diff --git a/app/helpers/miq_ae_class_helper.rb b/app/helpers/miq_ae_class_helper.rb
index 3c350a508c63..39ae8e12001b 100644
--- a/app/helpers/miq_ae_class_helper.rb
+++ b/app/helpers/miq_ae_class_helper.rb
@@ -314,4 +314,14 @@ def datastore_data(type, data)
class_field_data(data)
end
end
+
+ def datastore_form(ae_ns, sb_data, type)
+ domain = ae_ns.domain?
+ react('DatastoreForm', {:type => DATASTORE_TYPES[type],
+ :domain => domain,
+ :namespacePath => domain ? "" : sb_data[:namespace_path],
+ :namespaceId => ae_ns.id || 'new',
+ :nameReadOnly => domain && !ae_ns.editable_property?(:name),
+ :descReadOnly => domain && !ae_ns.editable_property?(:description)})
+ end
end
diff --git a/app/helpers/miq_request_helper.rb b/app/helpers/miq_request_helper.rb
new file mode 100644
index 000000000000..556a80f3c4b7
--- /dev/null
+++ b/app/helpers/miq_request_helper.rb
@@ -0,0 +1,3 @@
+module MiqRequestHelper
+ include RequestInfoHelper
+end
diff --git a/app/helpers/placement_group.rb b/app/helpers/placement_group.rb
new file mode 100644
index 000000000000..b14aee7d87c9
--- /dev/null
+++ b/app/helpers/placement_group.rb
@@ -0,0 +1,3 @@
+module PlacementGroupHelper
+ include_concern 'TextualSummary'
+end
diff --git a/app/helpers/placement_group_helper.rb b/app/helpers/placement_group_helper.rb
new file mode 100644
index 000000000000..b14aee7d87c9
--- /dev/null
+++ b/app/helpers/placement_group_helper.rb
@@ -0,0 +1,3 @@
+module PlacementGroupHelper
+ include_concern 'TextualSummary'
+end
diff --git a/app/helpers/placement_group_helper/textual_summary.rb b/app/helpers/placement_group_helper/textual_summary.rb
new file mode 100644
index 000000000000..b54472f93d1a
--- /dev/null
+++ b/app/helpers/placement_group_helper/textual_summary.rb
@@ -0,0 +1,38 @@
+module PlacementGroupHelper::TextualSummary
+ include TextualMixins::TextualEmsCloud
+ include TextualMixins::TextualGroupTags
+ include TextualMixins::TextualName
+ include TextualMixins::TextualCustomButtonEvents
+ #
+ # Groups
+ #
+
+ def textual_group_relationships
+ TextualGroup.new(_("Relationships"), %i[ems_cloud instances])
+ end
+
+ def textual_group_properties
+ TextualGroup.new(_("Properties"), %i[name policy])
+ end
+
+ #
+ # Items
+ #
+ def textual_type
+ ui_lookup(:model => @record.type)
+ end
+
+ def textual_policy
+ ui_lookup(:model => @record.policy)
+ end
+
+ def textual_instances
+ num = @record.number_of(:vms)
+ h = {:label => _('Instances'), :icon => "pficon pficon-virtual-machine", :value => num}
+ if num.positive? && role_allows?(:feature => "vm_show_list")
+ h[:link] = url_for_only_path(:action => 'show', :id => @record, :display => 'instances')
+ h[:title] = _("Show all Instances")
+ end
+ h
+ end
+end
diff --git a/app/helpers/request_info_helper.rb b/app/helpers/request_info_helper.rb
new file mode 100644
index 000000000000..73695415fe60
--- /dev/null
+++ b/app/helpers/request_info_helper.rb
@@ -0,0 +1,122 @@
+module RequestInfoHelper
+ private
+
+ def provision_tab_configuration(workflow)
+ prov_tab_labels = workflow.provisioning_tab_list.map do |dialog|
+ {:name => dialog[:name], :text => dialog[:description]}
+ end
+ return prov_tab_labels, workflow.get_dialog_order
+ end
+
+ def prov_vm_grid_data(edit, vms, vm)
+ none_index = '__VM__NONE__'
+ rows = []
+ clones = [:clone_to_template, :clone_to_vm]
+ if vms
+ unless clones.include?(edit[:wf].request_type)
+ rows.push({:id => none_index, :clickable => true, :cells => none_cells(edit[:vm_headers].length - 1)})
+ vms.each do |data|
+ rows.push({:id => data.id.to_s, :clickable => true, :cells => prov_vm_grid_cells(data, edit)})
+ end
+ end
+ else
+ rows.push({:id => vm.id.to_s, :clickable => true, :cells => prov_vm_grid_cells(vm, edit)})
+ end
+ {
+ :headers => prov_grid_vm_header(edit, clones, vms),
+ :rows => rows,
+ :selected => selected_vm(edit).presence || none_index,
+ :recordId => edit[:req_id] || "new",
+ }
+ end
+
+ def prov_host_grid_data(edit, options_data, hosts)
+ none_index = '__HOST__NONE__'
+ rows = [{:id => none_index, :clickable => true, :cells => none_cells(5)}]
+ options = edit || options_data
+ rows += hosts.map do |h|
+ {:id => h.id.to_s, :clickable => true, :cells => prov_host_grid_cells(h, options)}
+ end
+ {
+ :headers => prov_grid_host_header(edit, options),
+ :rows => rows,
+ :selected => selected_host(edit).presence || none_index,
+ :recordId => (edit && edit[:req_id]) || "new",
+ }
+ end
+
+ def prov_grid_vm_header(edit, clones, vms)
+ headers = []
+ edit[:vm_columns].each_with_index do |h, index|
+ item = {:text => edit[:vm_headers][h], :header_text => edit[:vm_headers][h]}
+ if vms && clones.exclude?(edit[:wf].request_type)
+ item[:sort_choice] = h
+ item[:sort_data] = sort_data(edit, index, 'vm')
+ end
+ headers.push(item)
+ end
+ headers
+ end
+
+ def prov_grid_host_header(edit, options)
+ headers = []
+ options && options[:host_columns].each_with_index do |h, index|
+ item = {:text => options[:host_headers][h], :header_text => options[:host_headers][h]}
+ if edit
+ item[:sort_choice] = h
+ item[:sort_data] = sort_data(edit, index, 'host')
+ end
+ headers.push(item)
+ end
+ headers
+ end
+
+ def cell_data(data)
+ {:text => data}
+ end
+
+ def selected_vm(edit)
+ edit[:new][:src_vm_id] && edit[:new][:src_vm_id][0].to_s
+ end
+
+ def selected_host(edit)
+ edit[:new][:placement_host_name] && edit[:new][:placement_host_name][0].to_s
+ end
+
+ def sort_data(edit, index, type)
+ sort = {:isFilteredBy => false}
+ if edit["#{type}_columns".to_sym][index] == edit["#{type}_sortcol".to_sym]
+ sort = {:isFilteredBy => true, :sortDirection => edit["#{type}_sortdir".to_sym] == 'ASC' ? 'ASC' : 'DESC'}
+ end
+ sort
+ end
+
+ def none_cells(count)
+ Array.new(count) { cell_data(" ") }.unshift(cell_data("<#{_('None')}>"))
+ end
+
+ def prov_vm_grid_cells(data, edit)
+ cells = [
+ cell_data(data.name),
+ cell_data(h(data.operating_system.try(:product_name))),
+ cell_data(h(data.platform)),
+ cell_data(h(data.cpu_total_cores)),
+ cell_data(h(number_to_human_size(data.mem_cpu.to_i * 1024 * 1024))),
+ cell_data(h(number_to_human_size(data.allocated_disk_storage))),
+ ]
+ cells.push(cell_data(h(data.deprecated ? _("true") : _("false")))) if edit[:vm_headers].key?('deprecated')
+ ext_name = data.ext_management_system ? h(data.ext_management_system.name) : ""
+ cells.push(cell_data(ext_name))
+ cells.push(cell_data(h(data.v_total_snapshots)))
+ if edit[:vm_headers].key?('cloud_tenant')
+ cells.push(cell_data(h(data.cloud_tenant ? data.cloud_tenant.name : _('None'))))
+ end
+ cells
+ end
+
+ def prov_host_grid_cells(data, options)
+ options[:host_columns].map do |col|
+ cell_data(h(data.send(col)))
+ end
+ end
+end
diff --git a/app/helpers/vm_cloud_helper.rb b/app/helpers/vm_cloud_helper.rb
index bc18bfecaa61..8674c39bca88 100644
--- a/app/helpers/vm_cloud_helper.rb
+++ b/app/helpers/vm_cloud_helper.rb
@@ -1,4 +1,5 @@
module VmCloudHelper
include VmHelper
+ include RequestInfoHelper
include_concern 'TextualSummary'
end
diff --git a/app/helpers/vm_helper/textual_summary.rb b/app/helpers/vm_helper/textual_summary.rb
index 625a3c6f32f2..136a865754bd 100644
--- a/app/helpers/vm_helper/textual_summary.rb
+++ b/app/helpers/vm_helper/textual_summary.rb
@@ -70,8 +70,8 @@ def textual_group_vm_cloud_relationships
_("Relationships"),
%i[
ems ems_infra cluster host availability_zone cloud_tenant flavor vm_template drift scan_history service genealogy
- cloud_network cloud_subnet orchestration_stack cloud_networks cloud_subnets network_routers security_groups
- floating_ips network_ports cloud_volumes custom_button_events
+ cloud_network cloud_subnet placement_group orchestration_stack cloud_networks cloud_subnets network_routers
+ security_groups floating_ips network_ports cloud_volumes custom_button_events
]
)
end
@@ -401,6 +401,19 @@ def textual_cloud_subnets
h
end
+ def textual_placement_group
+ my_placement_group = @record.placement_group
+ h = {:label => _('Placement Group'),
+ :icon => "pficon-flavor",
+ :value => (my_placement_group.nil? ? _("None") : my_placement_group.name)}
+ if !my_placement_group.nil? && role_allows?(:feature => "placement_group_show")
+ h[:title] = _("Show Placement Group")
+ textual_link(@record.placement_group, :label => _('Placement Group'))
+ h[:link] = url_for_only_path(:controller => 'placement_group', :action => 'show', :id => my_placement_group.id)
+ end
+ h
+ end
+
def textual_network_ports
num = @record.number_of(:network_ports)
h = {:label => _('Network Ports'), :icon => "ff ff-network-port", :value => num}
diff --git a/app/javascript/components/async-credentials/password-field.jsx b/app/javascript/components/async-credentials/password-field.jsx
index c270287a3663..9e9cdc4efc55 100644
--- a/app/javascript/components/async-credentials/password-field.jsx
+++ b/app/javascript/components/async-credentials/password-field.jsx
@@ -12,7 +12,7 @@ const PasswordField = ({
helperText,
edit,
parent,
- componentclass,
+ componentClass,
...rest
}) => {
const formOptions = useFormApi();
@@ -24,8 +24,8 @@ const PasswordField = ({
validateOnMount: rest.validateOnMount,
helperText,
...rest,
- component: edit ? 'edit-password-field' : componentclass,
- componentclass,
+ component: edit ? 'edit-password-field' : componentClass,
+ ...(edit) && { componentClass },
};
const newProps = { ...secretField };
@@ -88,7 +88,7 @@ PasswordField.propTypes = {
helperText: PropTypes.string,
edit: PropTypes.bool,
parent: PropTypes.string,
- componentclass: PropTypes.string,
+ componentClass: PropTypes.string,
};
PasswordField.defaultProps = {
@@ -97,7 +97,7 @@ PasswordField.defaultProps = {
helperText: undefined,
edit: false,
parent: undefined,
- componentclass: componentTypes.TEXT_FIELD,
+ componentClass: componentTypes.TEXT_FIELD,
};
export default PasswordField;
diff --git a/app/javascript/components/carbon-charts/stackBarChart.js b/app/javascript/components/carbon-charts/stackBarChart.js
index 02a02a4c5784..dd4738f67548 100644
--- a/app/javascript/components/carbon-charts/stackBarChart.js
+++ b/app/javascript/components/carbon-charts/stackBarChart.js
@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { StackedBarChart } from '@carbon/charts-react';
-const StackBarChartGraph = ({ data, title }) => {
+const StackBarChartGraph = ({ data, title, chart_options=null }) => {
const options = {
title,
axes: {
@@ -24,7 +24,7 @@ const StackBarChartGraph = ({ data, title }) => {
};
return (
-
{error}
} + // {customProp &&This is a custom prop and has nothing to do with form schema
} + // + //