diff --git a/app/controllers/configuration_controller.rb b/app/controllers/configuration_controller.rb index a00d5fb190b6..a6e236e229e6 100644 --- a/app/controllers/configuration_controller.rb +++ b/app/controllers/configuration_controller.rb @@ -20,6 +20,7 @@ def title end def index + show_timeprofiles assert_privileges('my_settings_view') @breadcrumbs = [] active_tab = nil @@ -31,6 +32,7 @@ def index active_tab = 4 if active_tab.nil? end @tabform = params[:load_edit_err] ? @tabform : "ui_#{active_tab}" + set_tab_vars(active_tab) edit render :action => "show" end @@ -70,7 +72,10 @@ def edit # New tab was pressed def change_tab + # require 'byebug' + # byebug assert_privileges('my_settings_admin') + set_tab_vars(params['uib-tab']) @tabform = "ui_" + params['uib-tab'] if params['uib-tab'] != "5" edit render :action => "show" @@ -128,6 +133,7 @@ def theme_changed def update assert_privileges('my_settings_admin') + set_tab_vars(@tabform.split('_').last) if params["save"] get_form_vars if @tabform != "ui_3" case @tabform @@ -495,6 +501,12 @@ def set_session_data session[:zone_options] = @zone_options end + def set_tab_vars(current_tab) + @path = '/configuration/change_tab/' + @current_tab = current_tab.to_s + @check_for_changes = true + end + def merge_settings(user_settings, global_settings) prune_old_settings(user_settings ? user_settings.merge(global_settings) : global_settings) end diff --git a/app/helpers/configuration_helper.rb b/app/helpers/configuration_helper.rb index fb522ca86d13..e3974e1903c8 100644 --- a/app/helpers/configuration_helper.rb +++ b/app/helpers/configuration_helper.rb @@ -1,3 +1,26 @@ module ConfigurationHelper include_concern 'ConfigurationViewHelper' + + def settings_tab_configuration + [:Visual, :Default, :Time].map do |item| + {:name => item, :text => settings_tabs_types[item]} + end + end + + def settings_tab_content(key_name, &block) + if settings_tabs_types[key_name] + class_name = key_name == :summary ? 'tab_content active' : 'tab_content' + tag.div(:id => key_name, :class => class_name, &block) + end + end + + private + + def settings_tabs_types + { + :Visual => _('Visual'), + :Default => _('Default Filters'), + :Time => _('Time Profiles'), + } + end end diff --git a/app/javascript/components/custom-url-tabs/helper.js b/app/javascript/components/custom-url-tabs/helper.js new file mode 100644 index 000000000000..eb7a928c7b0c --- /dev/null +++ b/app/javascript/components/custom-url-tabs/helper.js @@ -0,0 +1,25 @@ +export const checkForFormChanges = () => { + let type = 'old'; // 'old' | 'angular' | 'tagging' | 'react' + let dirty = false; + const ignore = !!document.getElementById('ignore_form_changes'); + + if (ManageIQ.redux.store.getState().FormButtons && ManageIQ.redux.store.getState().FormButtons.in_a_form) { + type = 'react'; + } + + switch (type) { + case 'react': + dirty = !ManageIQ.redux.store.getState().FormButtons.pristine; + break; + + default: + dirty = $('#buttons_on').is(':visible'); + break; + } + + if (dirty && !ignore) { + return true; + } + + return false; +}; diff --git a/app/javascript/components/custom-url-tabs/index.jsx b/app/javascript/components/custom-url-tabs/index.jsx new file mode 100644 index 000000000000..d8201690c364 --- /dev/null +++ b/app/javascript/components/custom-url-tabs/index.jsx @@ -0,0 +1,99 @@ +import React, { useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; +import { Tab, Tabs, Modal } from 'carbon-components-react'; +import { checkForFormChanges } from './helper'; + +const CustomURLTabs = ({ + tabs, path, currentTab, checkForChanges, +}) => { + const [tabsList, setTabsList] = useState([]); + const [selectedTab, setSelectedTab] = useState(0); + const [showConfirm, setShowConfirm] = useState(false); + const [url, setUrl] = useState(); + + const onTabClick = (id) => { + if (currentTab !== id) { + if (checkForChanges && checkForFormChanges()) { + setUrl(`${path}${id}?uib-tab=${id}`); + setShowConfirm(true); + document.getElementById(id).parentElement.classList.add('bx--tabs--scrollable__nav-item--selected'); + } else { + window.location.replace(`${path}${id}?uib-tab=${id}`); + } + } + }; + + const fixTabStyling = () => { + const selectedTabs = []; + document.getElementsByClassName('bx--tabs--scrollable__nav-item--selected').forEach((element) => { + selectedTabs.push(element); + }); + selectedTabs.forEach((tab) => { + tab.classList.remove('bx--tabs--scrollable__nav-item--selected'); + }); + document.getElementsByClassName('selected')[0].classList.add('bx--tabs--scrollable__nav-item--selected'); + }; + + useEffect(() => { + const tempTabs = []; + tabs.forEach((tab, index) => { + if (tab[0] === currentTab) { + setSelectedTab(parseInt(index, 10)); + } + + tempTabs.push( + onTabClick(tab[0])} + /> + ); + }); + setTabsList(tempTabs); + }, []); + + useEffect(() => { + if (showConfirm) { + document.getElementsByClassName('selected')[0].classList.remove('bx--tabs--scrollable__nav-item--selected'); + } else if (document.getElementsByClassName('selected').length > 0) { + fixTabStyling(); + } + }, [showConfirm]); + + return ( +
+ setShowConfirm(false)} + onRequestSubmit={() => window.location.replace(url)} + onSecondarySubmit={() => { + setShowConfirm(false); + fixTabStyling(); + }} + > + {__('Abandon changes?')} + + + {tabsList} + +
+ ); +}; + +CustomURLTabs.propTypes = { + tabs: PropTypes.arrayOf(String).isRequired, + path: PropTypes.string.isRequired, + currentTab: PropTypes.string, + checkForChanges: PropTypes.bool, +}; + +CustomURLTabs.defaultProps = { + currentTab: '0', + checkForChanges: false, +}; + +export default CustomURLTabs; diff --git a/app/javascript/components/miq-custom-tab/index.jsx b/app/javascript/components/miq-custom-tab/index.jsx index 7971a9c2c1b9..bd83cb0020c1 100644 --- a/app/javascript/components/miq-custom-tab/index.jsx +++ b/app/javascript/components/miq-custom-tab/index.jsx @@ -9,6 +9,7 @@ const MiqCustomTab = ({ containerId, tabLabels, type }) => { { type: 'CATALOG_EDIT' }, { type: 'CATALOG_REQUEST_INFO', url: `/miq_request/prov_field_changed?tab_id=${name}&edit_mode=true` }, { type: 'UTILIZATION' }, + { type: 'SETTINGS' }, ]; const configuration = (name) => tabConfigurations(name).find((item) => item.type === type); diff --git a/app/javascript/packs/component-definitions-common.js b/app/javascript/packs/component-definitions-common.js index 7533fdc68366..2f6f97a8d114 100644 --- a/app/javascript/packs/component-definitions-common.js +++ b/app/javascript/packs/component-definitions-common.js @@ -37,6 +37,7 @@ import ContainerProjects from '../components/container-projects'; import CopyCatalogForm from '../components/copy-catalog-form/copy-catalog-form'; import CopyDashboardForm from '../components/copy-dashboard-form/copy-dashboard-form'; import CustomButtonForm from '../components/generic-objects-form/custom-button-form'; +import CustomURLTabs from '../components/custom-url-tabs'; import DashboardWidget from '../components/dashboard-widgets/dashboard-charts'; import Datastore from '../components/data-tables/datastore'; import DatastoreForm from '../components/data-store-form'; @@ -202,6 +203,7 @@ ManageIQ.component.addReact('ContainerProjects', ContainerProjects); ManageIQ.component.addReact('CopyCatalogForm', CopyCatalogForm); ManageIQ.component.addReact('CopyDashboardForm', CopyDashboardForm); ManageIQ.component.addReact('CustomButtonForm', CustomButtonForm); +ManageIQ.component.addReact('CustomURLTabs', CustomURLTabs); ManageIQ.component.addReact('DashboardWidget', DashboardWidget); ManageIQ.component.addReact('Datastore', Datastore); ManageIQ.component.addReact('DatastoreForm', DatastoreForm); diff --git a/app/views/layouts/_tabs.html.haml b/app/views/layouts/_tabs.html.haml index 3930ae90d44f..8bcb83d5962f 100644 --- a/app/views/layouts/_tabs.html.haml +++ b/app/views/layouts/_tabs.html.haml @@ -1,52 +1,57 @@ - if @tabs - %ul{'role' => 'tablist', :class => "nav nav-tabs #{@layout == 'dashboard' ? 'nav-tabs-pf' : nil}"} - - @tabs.each_with_index do |tab, tab_index| - - tab_id = tab[1].split(" ")[0] - - if tab[0] == @active_tab - %li.active{'role' => 'tab', 'tabindex' => tab_index, 'aria-selected' => "true", :id => "#{tab_id}_tab", 'aria-controls' => tab_id} - %a - = h(tab[1]) - - else - %li{'role' => 'tab', 'tabindex' => tab_index, 'aria-selected' => "false", :id => "#{tab_id}_tab", 'aria-controls' => tab_id} - - if tab[0] == "" - %a= h(tab[1]) + #custom-tabs-wrapper.miq_custom_tab_wrapper + - if @path.present? + = @current_tab + = react('CustomURLTabs', {:tabs => @tabs, :path => @path, :currentTab => @current_tab, :checkForChanges => @check_for_changes}) + - else + %ul{'role' => 'tablist', :class => "nav nav-tabs #{@layout == 'dashboard' ? 'nav-tabs-pf' : nil}"} + - @tabs.each_with_index do |tab, tab_index| + - tab_id = tab[1].split(" ")[0] + - if tab[0] == @active_tab + %li.active{'role' => 'tab', 'tabindex' => tab_index, 'aria-selected' => "true", :id => "#{tab_id}_tab", 'aria-controls' => tab_id} + %a + = h(tab[1]) - else - - if %w(new edit).include?(tab[0].split("_")[0]) - - if %w(report ops).include?(@layout) - = link_to(tab[1], - {:action => 'change_tab', - :tab => tab[0], - :id => tab[0]}, - "data-miq_sparkle_on" => true, - "data-miq_sparkle_off" => true, - :remote => true, - "data-method" => :post) + %li{'role' => 'tab', 'tabindex' => tab_index, 'aria-selected' => "false", :id => "#{tab_id}_tab", 'aria-controls' => tab_id} + - if tab[0] == "" + %a= h(tab[1]) - else - = link_to(tab[1], :action => 'change_tab', 'uib-tab' => tab[0], :id => tab[0]) - - else - - if %w(report ops).include?(@layout) - - if @layout == "ops" - = link_to(tab[1], - {:action => 'change_tab', - :tab => tab[0], - :id => tab[0]}, - "data-miq_sparkle_on" => true, - "data-miq_sparkle_off" => true, - :remote => true, - "data-method" => :post) + - if %w(new edit).include?(tab[0].split("_")[0]) + - if %w(report ops).include?(@layout) + = link_to(tab[1], + {:action => 'change_tab', + :tab => tab[0], + :id => tab[0]}, + "data-miq_sparkle_on" => true, + "data-miq_sparkle_off" => true, + :remote => true, + "data-method" => :post) + - else + = link_to(tab[1], :action => 'change_tab', 'uib-tab' => tab[0], :id => tab[0]) - else - = link_to(tab[1], - {:action => 'change_tab', - :tab => tab[0], - :id => tab[0]}, - "data-miq_check_for_changes" => true, - "data-miq_sparkle_on" => true, - "data-miq_sparkle_off" => true, - :remote => true, - "data-method" => :post) - - else - = link_to(tab[1], {:action => 'change_tab', 'uib-tab' => tab[0], :id => tab[0]}, :onclick => "return miqCheckForChanges()") - %br{:clear => "all"}/ + - if %w(report ops).include?(@layout) + - if @layout == "ops" + = link_to(tab[1], + {:action => 'change_tab', + :tab => tab[0], + :id => tab[0]}, + "data-miq_sparkle_on" => true, + "data-miq_sparkle_off" => true, + :remote => true, + "data-method" => :post) + - else + = link_to(tab[1], + {:action => 'change_tab', + :tab => tab[0], + :id => tab[0]}, + "data-miq_check_for_changes" => true, + "data-miq_sparkle_on" => true, + "data-miq_sparkle_off" => true, + :remote => true, + "data-method" => :post) + - else + = link_to(tab[1], {:action => 'change_tab', 'uib-tab' => tab[0], :id => tab[0]}, :onclick => "return miqCheckForChanges()") + %br{:clear => "all"}/ - if !@tabs && (@lastaction == "show_list" || !@edit && !session[:menu_click]) - if @edit && @edit[:adv_search_applied] != nil && !session[:menu_click] %h1 diff --git a/config/routes.rb b/config/routes.rb index 8febaf20f853..c06562ce80a6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -632,6 +632,7 @@ ], :post => %w[ button + change_tab filters_field_changed theme_changed timeprofile_delete