diff --git a/awx/ui/client/lib/components/_index.less b/awx/ui/client/lib/components/_index.less index 7beb200a10..ba606cad79 100644 --- a/awx/ui/client/lib/components/_index.less +++ b/awx/ui/client/lib/components/_index.less @@ -13,3 +13,4 @@ @import 'truncate/_index'; @import 'utility/_index'; @import 'code-mirror/_index'; +@import 'cards/_index'; diff --git a/awx/ui/client/lib/components/cards/_index.less b/awx/ui/client/lib/components/cards/_index.less new file mode 100644 index 0000000000..b85e7ef50e --- /dev/null +++ b/awx/ui/client/lib/components/cards/_index.less @@ -0,0 +1,51 @@ +/* Cards Group */ +.at-CardContainer { + padding: 20px; +} + +.at-CardGroup { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + grid-gap: 20px; +} + +/* Card */ +.at-Card { + min-height: 103px; + display: flex; + flex-direction: column; + flex: 1; + background-color: @at-white; + text-decoration: none; + text-align: center; + padding: 20px; + border-left: 3px solid @at-white; + border-radius: 2px; + box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12); + transition: box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1); + cursor: pointer; +} + +.at-Card:hover { + background-color: @at-color-body-background; + border-left: 3px solid @at-blue; + box-shadow: 0px 2px 4px -1px rgba(0, 0, 0, 0.2), 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12); +} + +.at-Card-title { + font-size: 14px; + color: @at-gray-70; + font-weight: bold; + line-height: 14px; +} + +.at-Card-text { + font-size: 12px; + color: @at-gray-161b1f; +} + +/* Spacers */ + +.at-Card--spacer { + min-height: 15px; +} \ No newline at end of file diff --git a/awx/ui/client/lib/components/cards/card.directive.js b/awx/ui/client/lib/components/cards/card.directive.js new file mode 100644 index 0000000000..f81c712f8b --- /dev/null +++ b/awx/ui/client/lib/components/cards/card.directive.js @@ -0,0 +1,14 @@ +const templateUrl = require('~components/cards/card.partial.html'); + +function atCard () { + return { + restrict: 'E', + transclude: true, + templateUrl, + scope: { + title: '@', + }, + }; +} + +export default atCard; diff --git a/awx/ui/client/lib/components/cards/card.partial.html b/awx/ui/client/lib/components/cards/card.partial.html new file mode 100644 index 0000000000..4136e44fab --- /dev/null +++ b/awx/ui/client/lib/components/cards/card.partial.html @@ -0,0 +1,5 @@ + + {{ title }} + + + \ No newline at end of file diff --git a/awx/ui/client/lib/components/cards/group.directive.js b/awx/ui/client/lib/components/cards/group.directive.js new file mode 100644 index 0000000000..e2f61e4543 --- /dev/null +++ b/awx/ui/client/lib/components/cards/group.directive.js @@ -0,0 +1,11 @@ +const templateUrl = require('~components/cards/group.partial.html'); + +function atCardGroup () { + return { + restrict: 'E', + transclude: true, + templateUrl, + }; +} + +export default atCardGroup; diff --git a/awx/ui/client/lib/components/cards/group.partial.html b/awx/ui/client/lib/components/cards/group.partial.html new file mode 100644 index 0000000000..aa07696b12 --- /dev/null +++ b/awx/ui/client/lib/components/cards/group.partial.html @@ -0,0 +1,4 @@ +
+
+
+
\ No newline at end of file diff --git a/awx/ui/client/lib/components/index.js b/awx/ui/client/lib/components/index.js index aa18751d17..8dadf920b8 100644 --- a/awx/ui/client/lib/components/index.js +++ b/awx/ui/client/lib/components/index.js @@ -37,6 +37,8 @@ import toggleTag from '~components/toggle-tag/toggle-tag.directive'; import topNavItem from '~components/layout/top-nav-item.directive'; import truncate from '~components/truncate/truncate.directive'; import atCodeMirror from '~components/code-mirror'; +import card from '~components/cards/card.directive'; +import cardGroup from '~components/cards/group.directive'; import BaseInputController from '~components/input/base.controller'; import ComponentsStrings from '~components/components.strings'; @@ -84,6 +86,8 @@ angular .directive('atToggleTag', toggleTag) .directive('atTopNavItem', topNavItem) .directive('atTruncate', truncate) + .directive('atCard', card) + .directive('atCardGroup', cardGroup) .service('BaseInputController', BaseInputController) .service('ComponentsStrings', ComponentsStrings); diff --git a/awx/ui/client/lib/components/layout/_index.less b/awx/ui/client/lib/components/layout/_index.less index 6d5a7a7229..9718aee233 100644 --- a/awx/ui/client/lib/components/layout/_index.less +++ b/awx/ui/client/lib/components/layout/_index.less @@ -1,5 +1,5 @@ .at-Layout { - height: 100vh; + min-height: 100vh; width: 100vw; display: flex; @@ -82,17 +82,13 @@ } } - &-side { + &-sideContainer { background: @at-color-side-nav-background; - position: fixed; - bottom: 0; - top: @at-height-top-side-nav-makeup; - overflow-y: auto; - max-height: 100vh; - min-width: @at-width-collapsed-side-nav; + } + + &-side { + margin-top: @at-height-top-side-nav-makeup; width: @at-width-collapsed-side-nav; - overflow-x: hidden; - z-index: @at-z-index-side-nav; .at-Popover-container { margin-top: 2px; @@ -120,6 +116,10 @@ width: 50px; } + i.is-no-tooltip { + padding-left: @at-padding-left-side-nav-item-icon-no-tooltip; + } + &:hover, &.is-active { background: @at-color-side-nav-item-background-hover; @@ -201,7 +201,6 @@ height: 100%; width: 100%; flex-direction: column; - padding-left: @at-width-collapsed-side-nav; padding-bottom: @at-space-4x; overflow-x: hidden; } @@ -254,3 +253,44 @@ } } } + +/* Tower Sub Nav Dropdown */ +.at-SettingsSubPane { + position: relative; +} +.at-SettingsSubPane-content { + display: none; + position: absolute; + background-color: @at-gray-848992; + min-width: 140px; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + bottom: 0px; + left: @at-width-collapsed-side-nav; + z-index: 2000; +} + +.at-Layout-side--expanded + .at-SettingsSubPane { + .at-SettingsSubPane-content { + left: @at-width-expanded-side-nav; + } +} + +.at-SettingsSubPane-content a { + color: @at-white; + font-size: 13px; + padding: 10px 20px; + text-decoration: none; + display: block; + height: 39px; +} + +// hover stuff +.at-SettingsSubPane-content a:hover { + background-color: @at-gray-b7; +} +.at-SettingsSubPane.at-SettingsSubPane--visible .at-SettingsSubPane-content { + display: block; +} +.at-SettingsSubPane.at-SettingsSubPane--visible .nav-button { + background-color: @at-gray-b7; +} \ No newline at end of file diff --git a/awx/ui/client/lib/components/layout/layout.directive.js b/awx/ui/client/lib/components/layout/layout.directive.js index c8c70e04ef..df91ea37fb 100644 --- a/awx/ui/client/lib/components/layout/layout.directive.js +++ b/awx/ui/client/lib/components/layout/layout.directive.js @@ -1,8 +1,12 @@ +import defaultStrings from '~assets/default.strings.json'; + const templateUrl = require('~components/layout/layout.partial.html'); function AtLayoutController ($scope, $http, strings, ProcessErrors, $transitions) { const vm = this || {}; + vm.product = defaultStrings.BRAND_NAME; + $transitions.onSuccess({}, (transition) => { vm.currentState = transition.to().name; }); diff --git a/awx/ui/client/lib/components/layout/layout.partial.html b/awx/ui/client/lib/components/layout/layout.partial.html index de0f9ce272..d282ade9f1 100644 --- a/awx/ui/client/lib/components/layout/layout.partial.html +++ b/awx/ui/client/lib/components/layout/layout.partial.html @@ -95,8 +95,8 @@ - +
diff --git a/awx/ui/client/lib/components/layout/side-nav-item.directive.js b/awx/ui/client/lib/components/layout/side-nav-item.directive.js index 73074e91d1..a4af383182 100644 --- a/awx/ui/client/lib/components/layout/side-nav-item.directive.js +++ b/awx/ui/client/lib/components/layout/side-nav-item.directive.js @@ -2,6 +2,20 @@ const templateUrl = require('~components/layout/side-nav-item.partial.html'); function atSideNavItemLink (scope, element, attrs, ctrl) { [scope.navVm, scope.layoutVm] = ctrl; + + if (attrs.showSettingsSubMenu) { + element.hover(() => { + scope.navVm.onSettingsNavItem = true; + scope.navVm.showSettingsSubMenu = true; + }, () => { + scope.navVm.onSettingsNavItem = false; + setTimeout(() => { + if (!scope.navVm.onSettingsSubPane) { + scope.navVm.showSettingsSubMenu = false; + } + }, 100); + }); + } } function AtSideNavItemController ($scope, strings) { @@ -42,11 +56,13 @@ function atSideNavItem () { controller: AtSideNavItemController, controllerAs: 'vm', link: atSideNavItemLink, + transclude: true, scope: { iconClass: '@', name: '@', route: '@', - systemAdminOnly: '@' + systemAdminOnly: '@', + noTooltipOnCollapsed: '@' } }; } diff --git a/awx/ui/client/lib/components/layout/side-nav-item.partial.html b/awx/ui/client/lib/components/layout/side-nav-item.partial.html index ca8ff13812..380ed08f10 100644 --- a/awx/ui/client/lib/components/layout/side-nav-item.partial.html +++ b/awx/ui/client/lib/components/layout/side-nav-item.partial.html @@ -1,9 +1,10 @@ - + - + {{ layoutVm.getString(name) }} diff --git a/awx/ui/client/lib/components/layout/side-nav.directive.js b/awx/ui/client/lib/components/layout/side-nav.directive.js index 3fbf7cd86f..40950152d5 100644 --- a/awx/ui/client/lib/components/layout/side-nav.directive.js +++ b/awx/ui/client/lib/components/layout/side-nav.directive.js @@ -10,6 +10,17 @@ function atSideNavLink (scope, element, attrs, ctrl) { scope.$emit('clickOutsideSideNav'); } }); + + element.find('.at-SettingsSubPane').hover(() => { + scope.vm.onSettingsSubPane = true; + }, () => { + scope.vm.onSettingsSubPane = false; + setTimeout(() => { + if (!scope.vm.onSettingsNavItem) { + scope.vm.showSettingsSubMenu = false; + } + }, 100); + }); } function AtSideNavController ($scope, $window) { diff --git a/awx/ui/client/lib/components/layout/side-nav.partial.html b/awx/ui/client/lib/components/layout/side-nav.partial.html index 8996df899f..14bf34231e 100644 --- a/awx/ui/client/lib/components/layout/side-nav.partial.html +++ b/awx/ui/client/lib/components/layout/side-nav.partial.html @@ -1,8 +1,18 @@ -
-
- + diff --git a/awx/ui/client/lib/theme/_variables.less b/awx/ui/client/lib/theme/_variables.less index 284929253e..baab97f85b 100644 --- a/awx/ui/client/lib/theme/_variables.less +++ b/awx/ui/client/lib/theme/_variables.less @@ -238,6 +238,7 @@ @at-padding-left-side-nav-toggle-icon: 15px; @at-padding-left-side-nav-item-icon: 10px; @at-padding-left-side-nav-item-icon-expanded: 15px; +@at-padding-left-side-nav-item-icon-no-tooltip: 18px; @at-padding-between-side-nav-icon-text: @at-space-3x; @at-padding-list-empty: @at-space-2x; @at-padding-list-row-item-tag: 3px 9px; diff --git a/awx/ui/client/lib/theme/index.less b/awx/ui/client/lib/theme/index.less index a99f1b60c6..00061e28b4 100644 --- a/awx/ui/client/lib/theme/index.less +++ b/awx/ui/client/lib/theme/index.less @@ -65,7 +65,7 @@ @import '../../src/activity-stream/streamDetailModal/streamDetailModal.block.less'; @import '../../src/activity-stream/activitystream.block.less'; @import '../../src/bread-crumb/bread-crumb.block.less'; -@import '../../src/configuration/configuration.block.less'; +@import '../../src/configuration/settings.block.less'; @import '../../src/credentials/ownerList.block.less'; @import '../../src/home/dashboard/counts/dashboard-counts.block.less'; @import '../../src/home/dashboard/graphs/dashboard-graphs.block.less'; @@ -128,7 +128,6 @@ @import '../../src/workflow-results/workflow-status-bar/workflow-status-bar.block.less'; @import '../../src/workflow-results/workflow-results.block.less'; - /** * App-wide style * diff --git a/awx/ui/client/src/activity-stream/factories/build-anchor.factory.js b/awx/ui/client/src/activity-stream/factories/build-anchor.factory.js index bf93f35b1f..ab19ee12ae 100644 --- a/awx/ui/client/src/activity-stream/factories/build-anchor.factory.js +++ b/awx/ui/client/src/activity-stream/factories/build-anchor.factory.js @@ -54,14 +54,14 @@ export default function BuildAnchor($log, $filter) { case 'setting': if (activity.summary_fields.setting[0].category === 'jobs' || activity.summary_fields.setting[0].category === 'ui') { - url += `configuration/${activity.summary_fields.setting[0].category}`; + url += `settings/${activity.summary_fields.setting[0].category}`; } else if (activity.summary_fields.setting[0].category === 'system' || activity.summary_fields.setting[0].category === 'logging') { - url += `configuration/system`; + url += `settings/system`; } else { - url += `configuration/auth`; + url += `settings/auth`; } break; case 'notification_template': diff --git a/awx/ui/client/src/configuration/configuration.partial.html b/awx/ui/client/src/configuration/configuration.partial.html deleted file mode 100644 index 47f9abfe10..0000000000 --- a/awx/ui/client/src/configuration/configuration.partial.html +++ /dev/null @@ -1,61 +0,0 @@ -
- - System auditors have read-only permissions in this section. - -
- -
-
-
-
CONFIGURE {{ BRAND_NAME }}
-
-
-
-
-
- Authentication -
-
- Jobs -
-
- System -
-
- User Interface -
-
- License -
-
-
-
-
-
-
-
-
-
-
-
diff --git a/awx/ui/client/src/configuration/configuration.route.js b/awx/ui/client/src/configuration/configuration.route.js deleted file mode 100644 index 4b1febbb74..0000000000 --- a/awx/ui/client/src/configuration/configuration.route.js +++ /dev/null @@ -1,62 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ -import {templateUrl} from '../shared/template-url/template-url.factory'; -import ConfigurationController from './configuration.controller'; -import { N_ } from '../i18n'; - -// Import form controllers -import ConfigurationAuthController from './auth-form/configuration-auth.controller'; -import ConfigurationJobsController from './jobs-form/configuration-jobs.controller'; -import ConfigurationSystemController from './system-form/configuration-system.controller'; -import ConfigurationUiController from './ui-form/configuration-ui.controller'; - - export default { - name: 'configuration', - route: '/configuration/:currentTab', - params: { - currentTab: { - value: 'auth', - dynamic: true, - isOptional: true - } - - }, - ncyBreadcrumb: { - label: N_("EDIT CONFIGURATION") - }, - resolve: { - configDataResolve: ['ConfigurationService', function(ConfigurationService){ - return ConfigurationService.getConfigurationOptions(); - }] - }, - views: { - '': { - templateUrl: templateUrl('configuration/configuration'), - controller: ConfigurationController, - controllerAs: 'vm' - }, - 'auth@configuration': { - templateUrl: templateUrl('configuration/auth-form/configuration-auth'), - controller: ConfigurationAuthController, - controllerAs: 'authVm' - }, - 'jobs@configuration': { - templateUrl: templateUrl('configuration/jobs-form/configuration-jobs'), - controller: ConfigurationJobsController, - controllerAs: 'jobsVm' - }, - 'system@configuration': { - templateUrl: templateUrl('configuration/system-form/configuration-system'), - controller: ConfigurationSystemController, - controllerAs: 'systemVm' - }, - 'ui@configuration': { - templateUrl: templateUrl('configuration/ui-form/configuration-ui'), - controller: ConfigurationUiController, - controllerAs: 'uiVm' - }, - }, - }; diff --git a/awx/ui/client/src/configuration/auth-form/configuration-auth.controller.js b/awx/ui/client/src/configuration/forms/auth-form/configuration-auth.controller.js similarity index 69% rename from awx/ui/client/src/configuration/auth-form/configuration-auth.controller.js rename to awx/ui/client/src/configuration/forms/auth-form/configuration-auth.controller.js index 2741d829a0..251e9845f2 100644 --- a/awx/ui/client/src/configuration/auth-form/configuration-auth.controller.js +++ b/awx/ui/client/src/configuration/forms/auth-form/configuration-auth.controller.js @@ -7,84 +7,44 @@ export default [ '$scope', '$rootScope', - '$state', '$stateParams', - '$timeout', - '$q', - 'configurationAzureForm', - 'configurationGithubForm', - 'configurationGithubOrgForm', - 'configurationGithubTeamForm', - 'configurationGoogleForm', - 'configurationLdapForm', - 'configurationLdap1Form', - 'configurationLdap2Form', - 'configurationLdap3Form', - 'configurationLdap4Form', - 'configurationLdap5Form', - 'configurationRadiusForm', - 'configurationTacacsForm', - 'configurationSamlForm', - 'ConfigurationService', - 'ConfigurationUtils', + 'SettingsUtils', 'CreateSelect2', 'GenerateForm', 'i18n', 'ParseTypeChange', - function( + function ( $scope, $rootScope, - $state, $stateParams, - $timeout, - $q, - configurationAzureForm, - configurationGithubForm, - configurationGithubOrgForm, - configurationGithubTeamForm, - configurationGoogleForm, - configurationLdapForm, - configurationLdap1Form, - configurationLdap2Form, - configurationLdap3Form, - configurationLdap4Form, - configurationLdap5Form, - configurationRadiusForm, - configurationTacacsForm, - configurationSamlForm, - ConfigurationService, - ConfigurationUtils, + SettingsUtils, CreateSelect2, GenerateForm, i18n, ParseTypeChange ) { - var authVm = this; + const authVm = this; + const generator = GenerateForm; + const formTracker = $scope.$parent.vm.formTracker; // track the current active form - var generator = GenerateForm; - var formTracker = $scope.$parent.vm.formTracker; - var dropdownValue = 'azure'; - var activeAuthForm = 'azure'; - var ldapDropdownValue = ''; + authVm.activeAuthForm = 'azure'; + authVm.activeTab = 'azure'; + authVm.ldapDropdownValue = ''; + authVm.githubDropdownValue = 'github'; let codeInputInitialized = false; - // Default active form - if ($stateParams.currentTab === '' || $stateParams.currentTab === 'auth') { - formTracker.setCurrentAuth(activeAuthForm); + const formDefs = $scope.$parent.formDefs; + + // Default active authform + if ($stateParams.form === 'auth') { + formTracker.setCurrentAuth(authVm.activeAuthForm); } - const getActiveAuthForm = () => { - if (authVm.dropdownValue === 'ldap') { - return `ldap${authVm.ldapDropdownValue}`; - } - return authVm.dropdownValue; - }; - - - const activeForm = function(revertDropdown) { + authVm.activeForm = function(tab) { if(!_.get($scope.$parent, [formTracker.currentFormName(), '$dirty'])) { - authVm.activeAuthForm = getActiveAuthForm(); + authVm.activeTab = tab; + authVm.activeAuthForm = getActiveAuthForm(tab); formTracker.setCurrentAuth(authVm.activeAuthForm); startCodeMirrors(); } else { @@ -97,7 +57,8 @@ export default [ onClick: function() { $scope.$parent.vm.populateFromApi(); $scope.$parent[formTracker.currentFormName()].$setPristine(); - authVm.activeAuthForm = getActiveAuthForm(); + authVm.activeTab = tab; + authVm.activeAuthForm = getActiveAuthForm(tab); formTracker.setCurrentAuth(authVm.activeAuthForm); $('#FormModal-dialog').dialog('close'); } @@ -108,15 +69,13 @@ export default [ .then(function() { $scope.$parent[formTracker.currentFormName()].$setPristine(); $scope.$parent.vm.populateFromApi(); - authVm.activeAuthForm = getActiveAuthForm(); + authVm.activeTab = tab; + authVm.activeAuthForm = getActiveAuthForm(tab); formTracker.setCurrentAuth(authVm.activeAuthForm); $('#FormModal-dialog').dialog('close'); }).catch(() => { event.preventDefault(); $('#FormModal-dialog').dialog('close'); - if (revertDropdown) { - revertDropdown(); - } }); }, "class": "btn btn-primary", @@ -128,31 +87,9 @@ export default [ authVm.ldapSelected = (authVm.activeAuthForm.indexOf('ldap') !== -1); }; - const changeAuthDropdown = (previousVal) => { - activeForm(() => { - authVm.dropdownValue = previousVal; - CreateSelect2({ - element: '#configure-dropdown-nav', - multiple: false, - }); - }); - }; - - const changeLdapDropdown = (previousVal) => { - activeForm(() => { - authVm.ldapDropdownValue = previousVal; - CreateSelect2({ - element: '#configure-ldap-dropdown', - multiple: false, - }); - }); - }; - - var dropdownOptions = [ + authVm.dropdownOptions = [ {label: i18n._('Azure AD'), value: 'azure'}, {label: i18n._('GitHub'), value: 'github'}, - {label: i18n._('GitHub Org'), value: 'github_org'}, - {label: i18n._('GitHub Team'), value: 'github_team'}, {label: i18n._('Google OAuth2'), value: 'google_oauth'}, {label: i18n._('LDAP'), value: 'ldap'}, {label: i18n._('RADIUS'), value: 'radius'}, @@ -160,7 +97,7 @@ export default [ {label: i18n._('TACACS+'), value: 'tacacs'} ]; - var ldapDropdownOptions = [ + authVm.ldapDropdownOptions = [ {label: i18n._('Default'), value: ''}, {label: i18n._('LDAP 1 (Optional)'), value: '1'}, {label: i18n._('LDAP 2 (Optional)'), value: '2'}, @@ -169,6 +106,12 @@ export default [ {label: i18n._('LDAP 5 (Optional)'), value: '5'}, ]; + authVm.githubDropdownOptions = [ + {label: i18n._('GitHub (Default)'), value: 'github'}, + {label: i18n._('GitHub Org'), value: 'github_org'}, + {label: i18n._('GitHub Team'), value: 'github_team'}, + ]; + CreateSelect2({ element: '#configure-dropdown-nav', multiple: false, @@ -179,74 +122,79 @@ export default [ multiple: false, }); + CreateSelect2({ + element: '#configure-github-dropdown', + multiple: false, + }); + var authForms = [ { - formDef: configurationAzureForm, + formDef: formDefs.azure, id: 'auth-azure-form', name: 'azure' }, { - formDef: configurationGithubForm, + formDef: formDefs.github, id: 'auth-github-form', name: 'github' }, { - formDef: configurationGithubOrgForm, + formDef: formDefs.github_org, id: 'auth-github-org-form', name: 'github_org' }, { - formDef: configurationGithubTeamForm, + formDef: formDefs.github_team, id: 'auth-github-team-form', name: 'github_team' }, { - formDef: configurationGoogleForm, + formDef: formDefs.google_oauth, id: 'auth-google-form', name: 'google_oauth' }, { - formDef: configurationRadiusForm, + formDef: formDefs.radius, id: 'auth-radius-form', name: 'radius' }, { - formDef: configurationTacacsForm, + formDef: formDefs.tacacs, id: 'auth-tacacs-form', name: 'tacacs' }, { - formDef: configurationSamlForm, + formDef: formDefs.saml, id: 'auth-saml-form', name: 'saml' }, { - formDef: configurationLdapForm, + formDef: formDefs.ldap, id: 'auth-ldap-form', name: 'ldap' }, { - formDef: configurationLdap1Form, + formDef: formDefs.ldap1, id: 'auth-ldap1-form', name: 'ldap1' }, { - formDef: configurationLdap2Form, + formDef: formDefs.ldap2, id: 'auth-ldap2-form', name: 'ldap2' }, { - formDef: configurationLdap3Form, + formDef: formDefs.ldap3, id: 'auth-ldap3-form', name: 'ldap3' }, { - formDef: configurationLdap4Form, + formDef: formDefs.ldap4, id: 'auth-ldap4-form', name: 'ldap4' }, { - formDef: configurationLdap5Form, + formDef: formDefs.ldap5, id: 'auth-ldap5-form', name: 'ldap5' }, @@ -256,11 +204,11 @@ export default [ _.each(forms, function(form) { var keys = _.keys(form.fields); _.each(keys, function(key) { - if($scope.$parent.configDataResolve[key].type === 'choice') { + if($scope.configDataResolve[key].type === 'choice') { // Create options for dropdowns var optionsGroup = key + '_options'; $scope.$parent[optionsGroup] = []; - _.each($scope.$parent.configDataResolve[key].choices, function(choice){ + _.each($scope.configDataResolve[key].choices, function(choice){ $scope.$parent[optionsGroup].push({ name: choice[0], label: choice[1], @@ -274,15 +222,28 @@ export default [ form.buttons.save.disabled = $rootScope.user_is_system_auditor; }); - function startCodeMirrors(key) { + $scope.$parent.$parent.parseType = 'json'; + + _.each(authForms, function(form) { + // Generate the forms + generator.inject(form.formDef, { + id: form.id, + mode: 'edit', + scope: $scope.$parent.$parent, + related: true, + noPanel: true + }); + }); + + function startCodeMirrors (key) { var form = _.find(authForms, f => f.name === $scope.authVm.activeAuthForm); if(!key){ // Attach codemirror to fields that need it _.each(form.formDef.fields, function(field) { // Codemirror balks at empty values so give it one - if($scope.$parent[field.name] === null && field.codeMirror) { - $scope.$parent[field.name] = '{}'; + if($scope.$parent.$parent[field.name] === null && field.codeMirror) { + $scope.$parent.$parent[field.name] = '{}'; } if(field.codeMirror) { createIt(field.name); @@ -295,11 +256,11 @@ export default [ function createIt(name){ ParseTypeChange({ - scope: $scope.$parent, + scope: $scope.$parent.$parent, variable: name, parse_variable: 'parseType', field_id: form.formDef.name + '_' + name, - readOnly: $scope.$parent.configDataResolve[name] && $scope.$parent.configDataResolve[name].disabled ? true : false + readOnly: $scope.configDataResolve[name] && $scope.configDataResolve[name].disabled ? true : false }); $scope.parseTypeChange('parseType', name); } @@ -307,35 +268,22 @@ export default [ function addFieldInfo(form, key) { _.extend(form.fields[key], { - awPopOver: ($scope.$parent.configDataResolve[key].defined_in_file) ? - null: $scope.$parent.configDataResolve[key].help_text, - label: $scope.$parent.configDataResolve[key].label, + awPopOver: ($scope.configDataResolve[key].defined_in_file) ? + null: $scope.configDataResolve[key].help_text, + label: $scope.configDataResolve[key].label, name: key, toggleSource: key, dataPlacement: 'top', - placeholder: ConfigurationUtils.formatPlaceholder($scope.$parent.configDataResolve[key].placeholder, key) || null, - dataTitle: $scope.$parent.configDataResolve[key].label, - required: $scope.$parent.configDataResolve[key].required, + placeholder: SettingsUtils.formatPlaceholder($scope.configDataResolve[key].placeholder, key) || null, + dataTitle: $scope.configDataResolve[key].label, + required: $scope.configDataResolve[key].required, ngDisabled: $rootScope.user_is_system_auditor, - disabled: $scope.$parent.configDataResolve[key].disabled || null, - readonly: $scope.$parent.configDataResolve[key].readonly || null, - definedInFile: $scope.$parent.configDataResolve[key].defined_in_file || null + disabled: $scope.configDataResolve[key].disabled || null, + readonly: $scope.configDataResolve[key].readonly || null, + definedInFile: $scope.configDataResolve[key].defined_in_file || null }); } - $scope.$parent.parseType = 'json'; - - _.each(authForms, function(form) { - // Generate the forms - generator.inject(form.formDef, { - id: form.id, - mode: 'edit', - scope: $scope.$parent, - related: true, - noPanel: true - }); - }); - // Flag to avoid re-rendering and breaking Select2 dropdowns on tab switching var dropdownRendered = false; @@ -409,7 +357,7 @@ export default [ }); $scope.$on('populated', function() { - let tab = $stateParams.currentTab; + let tab = $stateParams.form; if (tab === 'auth') { startCodeMirrors(); @@ -427,23 +375,24 @@ export default [ }); $scope.$on('codeMirror_populated', function() { - let tab = $stateParams.currentTab; + let tab = $stateParams.form; if (tab === 'auth') { startCodeMirrors(); codeInputInitialized = true; } }); + function getActiveAuthForm (tab) { + if (tab === 'ldap') { + return `ldap${authVm.ldapDropdownValue}`; + } else if (tab === 'github') { + return authVm.githubDropdownValue; + } + return tab; + } angular.extend(authVm, { - changeAuthDropdown: changeAuthDropdown, - changeLdapDropdown: changeLdapDropdown, - activeAuthForm: activeAuthForm, authForms: authForms, - dropdownOptions: dropdownOptions, - dropdownValue: dropdownValue, - ldapDropdownValue: ldapDropdownValue, - ldapDropdownOptions: ldapDropdownOptions, }); } ]; diff --git a/awx/ui/client/src/configuration/auth-form/configuration-auth.partial.html b/awx/ui/client/src/configuration/forms/auth-form/configuration-auth.partial.html similarity index 72% rename from awx/ui/client/src/configuration/auth-form/configuration-auth.partial.html rename to awx/ui/client/src/configuration/forms/auth-form/configuration-auth.partial.html index dcbf4e258e..5154285c80 100644 --- a/awx/ui/client/src/configuration/auth-form/configuration-auth.partial.html +++ b/awx/ui/client/src/configuration/forms/auth-form/configuration-auth.partial.html @@ -1,19 +1,20 @@
-
-
Sub Category
-
- +
+
+
+
+ {{opt.label}} +
+
-
+
LDAP Server
-
+
+
GitHub Category
+
+ +
+
diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-azure.form.js b/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-azure.form.js similarity index 100% rename from awx/ui/client/src/configuration/auth-form/sub-forms/auth-azure.form.js rename to awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-azure.form.js diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-org.form.js b/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-github-org.form.js similarity index 100% rename from awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-org.form.js rename to awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-github-org.form.js diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-team.form.js b/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-github-team.form.js similarity index 100% rename from awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-team.form.js rename to awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-github-team.form.js diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github.form.js b/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-github.form.js similarity index 100% rename from awx/ui/client/src/configuration/auth-form/sub-forms/auth-github.form.js rename to awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-github.form.js diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-google-oauth2.form.js b/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-google-oauth2.form.js similarity index 100% rename from awx/ui/client/src/configuration/auth-form/sub-forms/auth-google-oauth2.form.js rename to awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-google-oauth2.form.js diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-ldap.form.js b/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-ldap.form.js similarity index 100% rename from awx/ui/client/src/configuration/auth-form/sub-forms/auth-ldap.form.js rename to awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-ldap.form.js diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-ldap1.form.js b/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-ldap1.form.js similarity index 100% rename from awx/ui/client/src/configuration/auth-form/sub-forms/auth-ldap1.form.js rename to awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-ldap1.form.js diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-ldap2.form.js b/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-ldap2.form.js similarity index 100% rename from awx/ui/client/src/configuration/auth-form/sub-forms/auth-ldap2.form.js rename to awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-ldap2.form.js diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-ldap3.form.js b/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-ldap3.form.js similarity index 100% rename from awx/ui/client/src/configuration/auth-form/sub-forms/auth-ldap3.form.js rename to awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-ldap3.form.js diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-ldap4.form.js b/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-ldap4.form.js similarity index 100% rename from awx/ui/client/src/configuration/auth-form/sub-forms/auth-ldap4.form.js rename to awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-ldap4.form.js diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-ldap5.form.js b/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-ldap5.form.js similarity index 100% rename from awx/ui/client/src/configuration/auth-form/sub-forms/auth-ldap5.form.js rename to awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-ldap5.form.js diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-radius.form.js b/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-radius.form.js similarity index 100% rename from awx/ui/client/src/configuration/auth-form/sub-forms/auth-radius.form.js rename to awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-radius.form.js diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-saml.form.js b/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-saml.form.js similarity index 100% rename from awx/ui/client/src/configuration/auth-form/sub-forms/auth-saml.form.js rename to awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-saml.form.js diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-tacacs.form.js b/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-tacacs.form.js similarity index 100% rename from awx/ui/client/src/configuration/auth-form/sub-forms/auth-tacacs.form.js rename to awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-tacacs.form.js diff --git a/awx/ui/client/src/configuration/jobs-form/configuration-jobs.controller.js b/awx/ui/client/src/configuration/forms/jobs-form/configuration-jobs.controller.js similarity index 67% rename from awx/ui/client/src/configuration/jobs-form/configuration-jobs.controller.js rename to awx/ui/client/src/configuration/forms/jobs-form/configuration-jobs.controller.js index 8955a2337b..256f11214b 100644 --- a/awx/ui/client/src/configuration/jobs-form/configuration-jobs.controller.js +++ b/awx/ui/client/src/configuration/forms/jobs-form/configuration-jobs.controller.js @@ -7,12 +7,8 @@ export default [ '$scope', '$rootScope', - '$state', '$stateParams', - '$timeout', 'ConfigurationJobsForm', - 'ConfigurationService', - 'ConfigurationUtils', 'CreateSelect2', 'GenerateForm', 'ParseTypeChange', @@ -20,26 +16,27 @@ export default [ function( $scope, $rootScope, - $state, $stateParams, - $timeout, ConfigurationJobsForm, - ConfigurationService, - ConfigurationUtils, CreateSelect2, GenerateForm, ParseTypeChange, i18n ) { - var generator = GenerateForm; + const generator = GenerateForm; var form = ConfigurationJobsForm; + const formTracker = $scope.$parent.vm.formTracker; + + if ($stateParams.form === 'jobs') { + formTracker.setCurrentAuth('jobs'); + } let tab; let codeInputInitialized = false; - $scope.$parent.AD_HOC_COMMANDS_options = []; - _.each($scope.$parent.configDataResolve.AD_HOC_COMMANDS.default, function(command) { - $scope.$parent.AD_HOC_COMMANDS_options.push({ + $scope.$parent.$parent.AD_HOC_COMMANDS_options = []; + _.each($scope.$parent.configDataResolve.AD_HOC_COMMANDS.default, function (command) { + $scope.$parent.$parent.AD_HOC_COMMANDS_options.push({ name: command, label: command, value: command @@ -74,18 +71,20 @@ export default [ generator.inject(form, { id: 'configure-jobs-form', mode: 'edit', - scope: $scope.$parent, + scope: $scope.$parent.$parent, related: false, noPanel: true }); + $scope.$parent.$parent.parseType = 'json'; + function initializeCodeInput () { let name = 'AWX_TASK_ENV'; ParseTypeChange({ - scope: $scope.$parent, + scope: $scope.$parent.$parent, variable: name, - parseType: 'application/json', + parse_variable: 'parseType', field_id: `configuration_jobs_template_${name}` }); @@ -93,15 +92,14 @@ export default [ } function loadAdHocCommands () { - $scope.$parent.AD_HOC_COMMANDS_values = $scope.$parent.AD_HOC_COMMANDS.map(value => value); - $scope.$parent.AD_HOC_COMMANDS = $scope.$parent.AD_HOC_COMMANDS.map(value => ({ + $scope.$parent.$parent.AD_HOC_COMMANDS_values = $scope.$parent.$parent.AD_HOC_COMMANDS.map(value => value); + $scope.$parent.$parent.AD_HOC_COMMANDS = $scope.$parent.$parent.AD_HOC_COMMANDS.map(value => ({ value, name: value, label: value })); - $scope.$parent.AD_HOC_COMMANDS_options = $scope.$parent.AD_HOC_COMMANDS.map(tag => tag); - + $scope.$parent.$parent.AD_HOC_COMMANDS_options = $scope.$parent.$parent.AD_HOC_COMMANDS.map(tag => tag); CreateSelect2({ element: '#configuration_jobs_template_AD_HOC_COMMANDS', multiple: true, @@ -112,7 +110,7 @@ export default [ } function revertAdHocCommands () { - $scope.$parent.AD_HOC_COMMANDS = $scope.$parent.configDataResolve.AD_HOC_COMMANDS.default.map(value => ({ + $scope.$parent.$parent.AD_HOC_COMMANDS = $scope.$parent.configDataResolve.AD_HOC_COMMANDS.default.map(value => ({ value, name: value, label: value @@ -125,60 +123,36 @@ export default [ } }); - $scope.$parent.AD_HOC_COMMANDS_options = $scope.$parent.AD_HOC_COMMANDS.map(tag => tag); - $scope.$parent.AD_HOC_COMMANDS_values = $scope.$parent.AD_HOC_COMMANDS.map(tag => tag.value); + $scope.$parent.$parent.AD_HOC_COMMANDS_options = $scope.$parent.$parent.AD_HOC_COMMANDS.map(tag => tag); + $scope.$parent.$parent.AD_HOC_COMMANDS_values = $scope.$parent.$parent.AD_HOC_COMMANDS.map(tag => tag.value); CreateSelect2({ element: '#configuration_jobs_template_AD_HOC_COMMANDS', multiple: true, addNew: true, placeholder: i18n._('Select commands'), - options: $scope.$parent.AD_HOC_COMMANDS_options + options: $scope.$parent.$parent.AD_HOC_COMMANDS_options }); - } - // Fix for bug where adding selected opts causes form to be $dirty and triggering modal - // TODO Find better solution for this bug - $timeout(function(){ - $scope.$parent.configuration_jobs_template_form.$setPristine(); - }, 1000); - - // Managing the state of select2's tags since the behavior is unpredictable otherwise. let commandsElement = $('#configuration_jobs_template_AD_HOC_COMMANDS'); commandsElement.on('select2:select', event => { let command = event.params.data.text; - let commands = $scope.$parent.AD_HOC_COMMANDS_values; + let commands = $scope.$parent.$parent.AD_HOC_COMMANDS_values; commands.push(command); }); commandsElement.on('select2:unselect', event => { let command = event.params.data.text; - let commands = $scope.$parent.AD_HOC_COMMANDS_values; + let commands = $scope.$parent.$parent.AD_HOC_COMMANDS_values; - $scope.$parent.AD_HOC_COMMANDS_values = commands.filter(value => value !== command); + $scope.$parent.$parent.AD_HOC_COMMANDS_values = commands.filter(value => value !== command); }); $scope.$on('AD_HOC_COMMANDS_reverted', () => revertAdHocCommands()); - /* - * Controllers for each tab are initialized when configuration is opened. A listener - * on the URL itself is necessary to determine which tab is active. If a non-active - * tab initializes a codemirror, it doesn't display properly until the user navigates - * to the tab and it's been clicked. - */ - $scope.$on('$locationChangeStart', (event, url) => { - let parts = url.split('/'); - tab = parts[parts.length - 1]; - - if (tab === 'jobs' && !codeInputInitialized) { - initializeCodeInput(); - codeInputInitialized = true; - } - }); - /* * Necessary to listen for revert clicks and relaunch the codemirror instance. */ @@ -194,8 +168,7 @@ export default [ * direct load of this tab or if the user comes from a different tab. */ $scope.$on('populated', () => { - tab = $stateParams.currentTab; - + tab = $stateParams.form; if (tab === 'jobs') { initializeCodeInput(); codeInputInitialized = true; diff --git a/awx/ui/client/src/configuration/jobs-form/configuration-jobs.form.js b/awx/ui/client/src/configuration/forms/jobs-form/configuration-jobs.form.js similarity index 100% rename from awx/ui/client/src/configuration/jobs-form/configuration-jobs.form.js rename to awx/ui/client/src/configuration/forms/jobs-form/configuration-jobs.form.js diff --git a/awx/ui/client/src/configuration/jobs-form/configuration-jobs.partial.html b/awx/ui/client/src/configuration/forms/jobs-form/configuration-jobs.partial.html similarity index 75% rename from awx/ui/client/src/configuration/jobs-form/configuration-jobs.partial.html rename to awx/ui/client/src/configuration/forms/jobs-form/configuration-jobs.partial.html index 20b85355df..165a551cbd 100644 --- a/awx/ui/client/src/configuration/jobs-form/configuration-jobs.partial.html +++ b/awx/ui/client/src/configuration/forms/jobs-form/configuration-jobs.partial.html @@ -1,6 +1,4 @@
-
diff --git a/awx/ui/client/src/configuration/configuration.controller.js b/awx/ui/client/src/configuration/forms/settings-form.controller.js similarity index 75% rename from awx/ui/client/src/configuration/configuration.controller.js rename to awx/ui/client/src/configuration/forms/settings-form.controller.js index a7a82495e4..57216d9b85 100644 --- a/awx/ui/client/src/configuration/configuration.controller.js +++ b/awx/ui/client/src/configuration/forms/settings-form.controller.js @@ -1,14 +1,9 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ import defaultStrings from '~assets/default.strings.json'; export default [ - '$scope', '$rootScope', '$state', '$stateParams', '$timeout', '$q', 'Alert', - 'ConfigurationService', 'ConfigurationUtils', 'CreateDialog', 'CreateSelect2', 'i18n', 'ParseTypeChange', 'ProcessErrors', 'Store', - 'Wait', 'configDataResolve', 'ToJSON', 'ConfigService', 'ngToast', + '$scope', '$rootScope', '$state', '$stateParams', '$q', + 'SettingsService', 'SettingsUtils', 'CreateDialog', 'i18n', 'ProcessErrors', 'Store', + 'Wait', 'configDataResolve', 'ToJSON', 'ConfigService', //Form definitions 'configurationAzureForm', 'configurationGithubForm', @@ -29,10 +24,11 @@ export default [ 'systemMiscForm', 'ConfigurationJobsForm', 'ConfigurationUiForm', + 'ngToast', function( - $scope, $rootScope, $state, $stateParams, $timeout, $q, Alert, - ConfigurationService, ConfigurationUtils, CreateDialog, CreateSelect2, i18n, ParseTypeChange, ProcessErrors, Store, - Wait, configDataResolve, ToJSON, ConfigService, ngToast, + $scope, $rootScope, $state, $stateParams, $q, + SettingsService, SettingsUtils, CreateDialog, i18n, ProcessErrors, Store, + Wait, configDataResolve, ToJSON, ConfigService, //Form definitions configurationAzureForm, configurationGithubForm, @@ -52,13 +48,15 @@ export default [ systemLoggingForm, systemMiscForm, ConfigurationJobsForm, - ConfigurationUiForm + ConfigurationUiForm, + ngToast ) { - var vm = this; + const vm = this; vm.product = defaultStrings.BRAND_NAME; + vm.activeTab = $stateParams.form; - var formDefs = { + const formDefs = { 'azure': configurationAzureForm, 'github': configurationGithubForm, 'github_org': configurationGithubOrgForm, @@ -80,8 +78,19 @@ export default [ 'ui': ConfigurationUiForm }; + $scope.configDataResolve = configDataResolve; + $scope.formDefs = formDefs; + + // check if it's auditor, show messageBar + $scope.show_auditor_bar = false; + if($rootScope.user_is_system_auditor && Store('show_auditor_bar') !== false) { + $scope.show_auditor_bar = true; + } else { + $scope.show_auditor_bar = false; + } + var populateFromApi = function() { - ConfigurationService.getCurrentValues() + SettingsService.getCurrentValues() .then(function(data) { var currentKeys = _.keys(data); $scope.requiredLogValues = {}; @@ -113,12 +122,12 @@ export default [ } else if (isLdapUserSearch || isLdapGroupSearch) { $scope[key] = JSON.stringify(data[key]); } else { - $scope[key] = ConfigurationUtils.arrayToList(data[key], key); + $scope[key] = SettingsUtils.arrayToList(data[key], key); } } else { //handle nested objects - if(ConfigurationUtils.isEmpty(data[key])) { + if(SettingsUtils.isEmpty(data[key])) { $scope[key] = '{}'; } else { $scope[key] = JSON.stringify(data[key]); @@ -172,31 +181,6 @@ export default [ }, }; - // Default to auth form and tab - if ($stateParams.currentTab === '') { - $state.go('configuration', { - currentTab: 'auth' - }, { - location: true, - inherit: false, - notify: false, - reload: false - }); - } - - var currentForm = ''; - var currentTab = function() { - if ($stateParams.currentTab === '' || $stateParams.currentTab === 'auth') { - return 'auth'; - } else if ($stateParams.currentTab !== '' && $stateParams.currentTab !== 'auth') { - formTracker.setCurrent($stateParams.currentTab); - return $stateParams.currentTab; - } - }; - var activeTab = currentTab(); - - $scope.configDataResolve = configDataResolve; - var triggerModal = function(msg, title, buttons) { if ($scope.removeModalReady) { $scope.removeModalReady(); @@ -221,127 +205,6 @@ export default [ }); }; - function activeTabCheck(setForm) { - if(!$scope[formTracker.currentFormName()] || !$scope[formTracker.currentFormName()].$dirty) { - active(setForm); - } else { - var msg = i18n._('You have unsaved changes. Would you like to proceed without saving?'); - var title = i18n._('Warning: Unsaved Changes'); - var buttons = [{ - label: i18n._("Discard changes"), - "class": "btn Form-cancelButton", - "id": "formmodal-cancel-button", - onClick: function() { - clearApiErrors(); - populateFromApi(); - $scope[formTracker.currentFormName()].$setPristine(); - $('#FormModal-dialog').dialog('close'); - active(setForm); - } - }, { - label: i18n._("Save changes"), - onClick: function() { - vm.formSave().then(() => { - $scope[formTracker.currentFormName()].$setPristine(); - $('#FormModal-dialog').dialog('close'); - active(setForm); - }).catch(()=> { - event.preventDefault(); - $('#FormModal-dialog').dialog('close'); - }); - - }, - "class": "btn btn-primary", - "id": "formmodal-save-button" - }]; - triggerModal(msg, title, buttons); - } - } - - function active(setForm) { - // Authentication and System's sub-module dropdowns handled first: - if (setForm === 'auth') { - // Default to 'azure' on first load - if (formTracker.currentAuth === '') { - formTracker.setCurrentAuth('azure'); - } else { - // If returning to auth tab reset current form to previously viewed - formTracker.setCurrentAuth(formTracker.currentAuth); - } - } else if (setForm === 'system') { - if (formTracker.currentSystem === '') { - formTracker.setCurrentSystem('misc'); - } else { - // If returning to system tab reset current form to previously viewed - formTracker.setCurrentSystem(formTracker.currentSystem); - } - } - - vm.activeTab = setForm; - - if (setForm !== 'license') { - if (setForm === 'auth') { - formTracker.setCurrentAuth(formTracker.currentAuth); - } else if (setForm === 'system') { - formTracker.setCurrentSystem(formTracker.currentSystem); - } else { - formTracker.setCurrent(setForm); - } - - $state.go('configuration', { - currentTab: setForm - }, { - location: true, - inherit: false, - notify: false, - reload: false - }); - } else { - $state.go('configuration.license', { - currentTab: setForm - }, { - location: true, - inherit: false, - notify: false, - reload: false - }); - } - } - - var formCancel = function() { - if ($scope[formTracker.currentFormName()].$dirty === true) { - var msg = i18n._('You have unsaved changes. Would you like to proceed without saving?'); - var title = i18n._('Warning: Unsaved Changes'); - var buttons = [{ - label: i18n._("Discard changes"), - "class": "btn Form-cancelButton", - "id": "formmodal-cancel-button", - onClick: function() { - clearApiErrors(); - populateFromApi(); - $scope[formTracker.currentFormName()].$setPristine(); - $('#FormModal-dialog').dialog('close'); - } - }, { - label: i18n._("Save changes"), - onClick: function() { - vm.formSave().then(() => { - $scope[formTracker.currentFormName()].$setPristine(); - $('#FormModal-dialog').dialog('close'); - }).catch(()=> { - event.preventDefault(); - $('#FormModal-dialog').dialog('close'); - }); - }, - "class": "btn btn-primary", - "id": "formmodal-save-button" - }]; - triggerModal(msg, title, buttons); - } else { - $state.go('setup'); - } - }; - function loginUpdate() { // Updates the logo and app config so that logos and info are properly shown // on logout after modifying. @@ -370,7 +233,7 @@ export default [ Wait('start'); var payload = {}; payload[key] = $scope.configDataResolve[key].default; - ConfigurationService.patchConfiguration(payload) + SettingsService.patchConfiguration(payload) .then(function() { $scope[key] = $scope.configDataResolve[key].default; @@ -446,7 +309,6 @@ export default [ var getFormPayload = function() { var keys = _.keys(formDefs[formTracker.getCurrent()].fields); var payload = {}; - _.each(keys, function(key) { if($scope.configDataResolve[key].type === 'choice' || multiselectDropdowns.indexOf(key) !== -1) { //Parse dropdowns and dropdowns labeled as lists @@ -471,10 +333,10 @@ export default [ } } else if($scope.configDataResolve[key].type === 'list' && $scope[key] !== null) { // Parse lists - payload[key] = ConfigurationUtils.listToArray($scope[key], key); + payload[key] = SettingsUtils.listToArray($scope[key], key); } else if($scope.configDataResolve[key].type === 'nested object') { - if($scope[key] === '') { + if(!$scope[key]) { payload[key] = {}; } else { // payload[key] = JSON.parse($scope[key]); @@ -494,11 +356,11 @@ export default [ return payload; }; - var formSave = function() { + vm.formSave = function() { var saveDeferred = $q.defer(); clearApiErrors(); Wait('start'); - ConfigurationService.patchConfiguration(getFormPayload()) + SettingsService.patchConfiguration(getFormPayload()) .then(function(data) { loginUpdate(); @@ -532,7 +394,94 @@ export default [ return saveDeferred.promise; }; + vm.formCancel = function() { + if ($scope[formTracker.currentFormName()].$dirty === true) { + var msg = i18n._('You have unsaved changes. Would you like to proceed without saving?'); + var title = i18n._('Warning: Unsaved Changes'); + var buttons = [{ + label: i18n._("Discard changes"), + "class": "btn Form-cancelButton", + "id": "formmodal-cancel-button", + onClick: function() { + $('#FormModal-dialog').dialog('close'); + $state.go('settings'); + } + }, { + label: i18n._("Save changes"), + onClick: function() { + vm.formSave() + .then(function() { + $('#FormModal-dialog').dialog('close'); + $state.go('settings'); + }); + }, + "class": "btn btn-primary", + "id": "formmodal-save-button" + }]; + triggerModal(msg, title, buttons); + } else { + $state.go('settings'); + } + }; + vm.resetAllConfirm = function() { + var buttons = [{ + label: i18n._("Cancel"), + "class": "btn btn-default", + "id": "formmodal-cancel-button", + onClick: function() { + $('#FormModal-dialog').dialog('close'); + } + }, { + label: i18n._("Confirm Reset"), + onClick: function() { + resetAll(); + $('#FormModal-dialog').dialog('close'); + }, + "class": "btn btn-primary", + "id": "formmodal-reset-button" + }]; + var msg = i18n._('This will reset all configuration values to their factory defaults. Are you sure you want to proceed?'); + var title = i18n._('Confirm factory reset'); + triggerModal(msg, title, buttons); + }; + + vm.closeMessageBar = function() { + var msg = 'Are you sure you want to hide the notification bar?'; + var title = 'Warning: Closing notification bar'; + var buttons = [{ + label: "Cancel", + "class": "btn Form-cancelButton", + "id": "formmodal-cancel-button", + onClick: function() { + $('#FormModal-dialog').dialog('close'); + } + }, { + label: "OK", + onClick: function() { + $('#FormModal-dialog').dialog('close'); + updateMessageBarPrefs(); + }, + "class": "btn btn-primary", + "id": "formmodal-save-button" + }]; + triggerModal(msg, title, buttons); + }; + + vm.getCurrentFormTitle = function() { + switch($stateParams.form) { + case 'auth': + return 'AUTHENTICATION'; + case 'jobs': + return 'JOBS'; + case 'system': + return 'SYSTEM'; + case 'ui': + return 'USER INTERFACE'; + case 'license': + return 'LICENSE'; + } + }; $scope.toggleForm = function(key) { if($rootScope.user_is_system_auditor) { @@ -545,7 +494,7 @@ export default [ Wait('start'); var payload = {}; payload[key] = $scope[key]; - ConfigurationService.patchConfiguration(payload) + SettingsService.patchConfiguration(payload) .then(function() { //TODO consider updating form values with returned data here }) @@ -564,7 +513,7 @@ export default [ }); }; - var resetAll = function() { + function resetAll () { var keys = _.keys(formDefs[formTracker.getCurrent()].fields); var payload = {}; clearApiErrors(); @@ -573,7 +522,7 @@ export default [ }); Wait('start'); - ConfigurationService.patchConfiguration(payload) + SettingsService.patchConfiguration(payload) .then(function() { populateFromApi(); $scope[formTracker.currentFormName()].$setPristine(); @@ -610,76 +559,17 @@ export default [ .finally(function() { Wait('stop'); }); - }; - - var resetAllConfirm = function() { - var buttons = [{ - label: i18n._("Cancel"), - "class": "btn btn-default", - "id": "formmodal-cancel-button", - onClick: function() { - $('#FormModal-dialog').dialog('close'); - } - }, { - label: i18n._("Confirm Reset"), - onClick: function() { - resetAll(); - $('#FormModal-dialog').dialog('close'); - }, - "class": "btn btn-primary", - "id": "formmodal-reset-button" - }]; - var msg = i18n._('This will reset all configuration values to their factory defaults. Are you sure you want to proceed?'); - var title = i18n._('Confirm factory reset'); - triggerModal(msg, title, buttons); - }; - - var show_auditor_bar; - if($rootScope.user_is_system_auditor && Store('show_auditor_bar') !== false) { - show_auditor_bar = true; - } else { - show_auditor_bar = false; } - var updateMessageBarPrefs = function() { - vm.show_auditor_bar = false; - Store('show_auditor_bar', vm.show_auditor_bar); - }; - - var closeMessageBar = function() { - var msg = 'Are you sure you want to hide the notification bar?'; - var title = 'Warning: Closing notification bar'; - var buttons = [{ - label: "Cancel", - "class": "btn Form-cancelButton", - "id": "formmodal-cancel-button", - onClick: function() { - $('#FormModal-dialog').dialog('close'); - } - }, { - label: "OK", - onClick: function() { - $('#FormModal-dialog').dialog('close'); - updateMessageBarPrefs(); - }, - "class": "btn btn-primary", - "id": "formmodal-save-button" - }]; - triggerModal(msg, title, buttons); - }; + function updateMessageBarPrefs () { + $scope.show_auditor_bar = false; + Store('show_auditor_bar', $scope.show_auditor_bar); + } angular.extend(vm, { - activeTab: activeTab, - activeTabCheck: activeTabCheck, - closeMessageBar: closeMessageBar, - currentForm: currentForm, - formCancel: formCancel, formTracker: formTracker, - formSave: formSave, getFormPayload: getFormPayload, populateFromApi: populateFromApi, - resetAllConfirm: resetAllConfirm, - show_auditor_bar: show_auditor_bar, triggerModal: triggerModal, }); } diff --git a/awx/ui/client/src/configuration/forms/settings-form.partial.html b/awx/ui/client/src/configuration/forms/settings-form.partial.html new file mode 100644 index 0000000000..23ba6f8fd1 --- /dev/null +++ b/awx/ui/client/src/configuration/forms/settings-form.partial.html @@ -0,0 +1,19 @@ +
+ + System auditors have read-only permissions in this section. + +
+ +
+
+
+
{{ vm.getCurrentFormTitle() }}
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/awx/ui/client/src/configuration/forms/settings-form.route.js b/awx/ui/client/src/configuration/forms/settings-form.route.js new file mode 100644 index 0000000000..a03b4cbfa5 --- /dev/null +++ b/awx/ui/client/src/configuration/forms/settings-form.route.js @@ -0,0 +1,56 @@ +import {templateUrl} from '../../shared/template-url/template-url.factory'; +import { N_ } from '../../i18n'; +import SettingsFormController from './settings-form.controller'; + +// Import form controllers +import SettingsAuthController from './auth-form/configuration-auth.controller'; +import SettingsJobsController from './jobs-form/configuration-jobs.controller'; +import SettingsSystemController from './system-form/configuration-system.controller'; +import SettingsUiController from './ui-form/configuration-ui.controller'; + +export default { + name: 'settings.form', + route: '/:form', + ncyBreadcrumb: { + label: N_("{{ vm.getCurrentFormTitle() }}") + }, + views: { + '@': { + templateUrl: templateUrl('configuration/forms/settings-form'), + controller: SettingsFormController, + controllerAs: 'vm' + }, + 'auth@settings.form': { + templateUrl: templateUrl('configuration/forms/auth-form/configuration-auth'), + controller: SettingsAuthController, + controllerAs: 'authVm' + }, + 'jobs@settings.form': { + templateUrl: templateUrl('configuration/forms/jobs-form/configuration-jobs'), + controller: SettingsJobsController, + controllerAs: 'jobsVm' + }, + 'system@settings.form': { + templateUrl: templateUrl('configuration/forms/system-form/configuration-system'), + controller: SettingsSystemController, + controllerAs: 'systemVm' + }, + 'ui@settings.form': { + templateUrl: templateUrl('configuration/forms/ui-form/configuration-ui'), + controller: SettingsUiController, + controllerAs: 'uiVm' + }, + 'license@settings.form': { + templateUrl: templateUrl('license/license'), + controller: 'licenseController' + }, + }, + onEnter: ['$state', 'ConfigService', '$stateParams', (state, configService, stateParams) => { + return configService.getConfig() + .then(config => { + if (_.get(config, 'license_info.license_type') === 'open' && stateParams.form === 'license') { + return state.go('settings'); + } + }); + }], +}; \ No newline at end of file diff --git a/awx/ui/client/src/configuration/system-form/configuration-system.controller.js b/awx/ui/client/src/configuration/forms/system-form/configuration-system.controller.js similarity index 67% rename from awx/ui/client/src/configuration/system-form/configuration-system.controller.js rename to awx/ui/client/src/configuration/forms/system-form/configuration-system.controller.js index d009f21008..1b78d85685 100644 --- a/awx/ui/client/src/configuration/system-form/configuration-system.controller.js +++ b/awx/ui/client/src/configuration/forms/system-form/configuration-system.controller.js @@ -5,13 +5,11 @@ *************************************************/ export default [ - '$rootScope', '$scope', '$state', '$stateParams', '$timeout', - 'AngularCodeMirror', + '$rootScope', '$scope', '$stateParams', 'systemActivityStreamForm', 'systemLoggingForm', 'systemMiscForm', - 'ConfigurationService', - 'ConfigurationUtils', + 'SettingsUtils', 'CreateSelect2', 'GenerateForm', 'i18n', @@ -20,13 +18,11 @@ export default [ 'ngToast', '$filter', function( - $rootScope, $scope, $state, $stateParams, $timeout, - AngularCodeMirror, + $rootScope, $scope, $stateParams, systemActivityStreamForm, systemLoggingForm, systemMiscForm, - ConfigurationService, - ConfigurationUtils, + SettingsUtils, CreateSelect2, GenerateForm, i18n, @@ -39,16 +35,15 @@ export default [ var generator = GenerateForm; var formTracker = $scope.$parent.vm.formTracker; - var dropdownValue = 'misc'; var activeSystemForm = 'misc'; - if ($stateParams.currentTab === 'system') { + if ($stateParams.form === 'system') { formTracker.setCurrentSystem(activeSystemForm); } - var activeForm = function() { + var activeForm = function(tab) { if(!_.get($scope.$parent, [formTracker.currentFormName(), '$dirty'])) { - systemVm.activeSystemForm = systemVm.dropdownValue; + systemVm.activeSystemForm = tab; formTracker.setCurrentSystem(systemVm.activeSystemForm); } else { var msg = i18n._('You have unsaved changes. Would you like to proceed without saving?'); @@ -60,7 +55,7 @@ export default [ onClick: function() { $scope.$parent.vm.populateFromApi(); $scope.$parent[formTracker.currentFormName()].$setPristine(); - systemVm.activeSystemForm = systemVm.dropdownValue; + systemVm.activeSystemForm = tab; formTracker.setCurrentSystem(systemVm.activeSystemForm); $('#FormModal-dialog').dialog('close'); } @@ -71,7 +66,7 @@ export default [ .then(function() { $scope.$parent[formTracker.currentFormName()].$setPristine(); $scope.$parent.vm.populateFromApi(); - systemVm.activeSystemForm = systemVm.dropdownValue; + systemVm.activeSystemForm = tab; formTracker.setCurrentSystem(systemVm.activeSystemForm); $('#FormModal-dialog').dialog('close'); }); @@ -85,16 +80,11 @@ export default [ }; var dropdownOptions = [ + {label: i18n._('Misc. System'), value: 'misc'}, {label: i18n._('Activity Stream'), value: 'activity_stream'}, {label: i18n._('Logging'), value: 'logging'}, - {label: i18n._('Misc. System'), value: 'misc'} ]; - CreateSelect2({ - element: '#system-configure-dropdown-nav', - multiple: false, - }); - var systemForms = [{ formDef: systemLoggingForm, id: 'system-logging-form' @@ -110,12 +100,12 @@ export default [ _.each(forms, function(form) { var keys = _.keys(form.fields); _.each(keys, function(key) { - if($scope.$parent.configDataResolve[key].type === 'choice') { + if($scope.configDataResolve[key].type === 'choice') { // Create options for dropdowns var optionsGroup = key + '_options'; - $scope.$parent[optionsGroup] = []; - _.each($scope.$parent.configDataResolve[key].choices, function(choice){ - $scope.$parent[optionsGroup].push({ + $scope.$parent.$parent[optionsGroup] = []; + _.each($scope.configDataResolve[key].choices, function(choice){ + $scope.$parent.$parent[optionsGroup].push({ name: choice[0], label: choice[1], value: choice[0] @@ -130,29 +120,29 @@ export default [ function addFieldInfo(form, key) { _.extend(form.fields[key], { - awPopOver: ($scope.$parent.configDataResolve[key].defined_in_file) ? - null: $scope.$parent.configDataResolve[key].help_text, - label: $scope.$parent.configDataResolve[key].label, + awPopOver: ($scope.configDataResolve[key].defined_in_file) ? + null: $scope.configDataResolve[key].help_text, + label: $scope.configDataResolve[key].label, name: key, toggleSource: key, dataPlacement: 'top', - placeholder: ConfigurationUtils.formatPlaceholder($scope.$parent.configDataResolve[key].placeholder, key) || null, - dataTitle: $scope.$parent.configDataResolve[key].label, - required: $scope.$parent.configDataResolve[key].required, + placeholder: SettingsUtils.formatPlaceholder($scope.configDataResolve[key].placeholder, key) || null, + dataTitle: $scope.configDataResolve[key].label, + required: $scope.configDataResolve[key].required, ngDisabled: $rootScope.user_is_system_auditor, - disabled: $scope.$parent.configDataResolve[key].disabled || null, - readonly: $scope.$parent.configDataResolve[key].readonly || null, - definedInFile: $scope.$parent.configDataResolve[key].defined_in_file || null + disabled: $scope.configDataResolve[key].disabled || null, + readonly: $scope.configDataResolve[key].readonly || null, + definedInFile: $scope.configDataResolve[key].defined_in_file || null }); } - $scope.$parent.parseType = 'json'; + $scope.$parent.$parent.parseType = 'json'; _.each(systemForms, function(form) { generator.inject(form.formDef, { id: form.id, mode: 'edit', - scope: $scope.$parent, + scope: $scope.$parent.$parent, related: true, noPanel: true }); @@ -173,16 +163,17 @@ export default [ }); function populateLogAggregator(flag){ - if($scope.$parent.LOG_AGGREGATOR_TYPE !== null) { - $scope.$parent.LOG_AGGREGATOR_TYPE = _.find($scope.$parent.LOG_AGGREGATOR_TYPE_options, { value: $scope.$parent.LOG_AGGREGATOR_TYPE }); + + if($scope.$parent.$parent.LOG_AGGREGATOR_TYPE !== null) { + $scope.$parent.$parent.LOG_AGGREGATOR_TYPE = _.find($scope.$parent.$parent.LOG_AGGREGATOR_TYPE_options, { value: $scope.$parent.$parent.LOG_AGGREGATOR_TYPE }); } - if($scope.$parent.LOG_AGGREGATOR_PROTOCOL !== null) { - $scope.$parent.LOG_AGGREGATOR_PROTOCOL = _.find($scope.$parent.LOG_AGGREGATOR_PROTOCOL_options, { value: $scope.$parent.LOG_AGGREGATOR_PROTOCOL }); + if($scope.$parent.$parent.LOG_AGGREGATOR_PROTOCOL !== null) { + $scope.$parent.$parent.LOG_AGGREGATOR_PROTOCOL = _.find($scope.$parent.$parent.LOG_AGGREGATOR_PROTOCOL_options, { value: $scope.$parent.$parent.LOG_AGGREGATOR_PROTOCOL }); } - if($scope.$parent.LOG_AGGREGATOR_LEVEL !== null) { - $scope.$parent.LOG_AGGREGATOR_LEVEL = _.find($scope.$parent.LOG_AGGREGATOR_LEVEL_options, { value: $scope.$parent.LOG_AGGREGATOR_LEVEL }); + if($scope.$parent.$parent.LOG_AGGREGATOR_LEVEL !== null) { + $scope.$parent.$parent.LOG_AGGREGATOR_LEVEL = _.find($scope.$parent.$parent.LOG_AGGREGATOR_LEVEL_options, { value: $scope.$parent.$parent.LOG_AGGREGATOR_LEVEL }); } if(flag !== undefined){ @@ -196,18 +187,12 @@ export default [ multiple: false, placeholder: i18n._('Select types'), }); - $scope.$parent.configuration_logging_template_form.LOG_AGGREGATOR_TYPE.$setPristine(); - $scope.$parent.configuration_logging_template_form.LOG_AGGREGATOR_PROTOCOL.$setPristine(); - $scope.$parent.configuration_logging_template_form.LOG_AGGREGATOR_LEVEL.$setPristine(); + $scope.$parent.$parent.configuration_logging_template_form.LOG_AGGREGATOR_TYPE.$setPristine(); + $scope.$parent.$parent.configuration_logging_template_form.LOG_AGGREGATOR_PROTOCOL.$setPristine(); + $scope.$parent.$parent.configuration_logging_template_form.LOG_AGGREGATOR_LEVEL.$setPristine(); } } - // Fix for bug where adding selected opts causes form to be $dirty and triggering modal - // TODO Find better solution for this bug - $timeout(function(){ - $scope.$parent.configuration_logging_template_form.$setPristine(); - }, 1000); - $scope.$parent.vm.testLogging = function() { Rest.setUrl("/api/v2/settings/logging/test/"); Rest.post($scope.$parent.vm.getFormPayload()) @@ -241,7 +226,6 @@ export default [ activeForm: activeForm, activeSystemForm: activeSystemForm, dropdownOptions: dropdownOptions, - dropdownValue: dropdownValue, systemForms: systemForms }); } diff --git a/awx/ui/client/src/configuration/forms/system-form/configuration-system.partial.html b/awx/ui/client/src/configuration/forms/system-form/configuration-system.partial.html new file mode 100644 index 0000000000..2d918a55d9 --- /dev/null +++ b/awx/ui/client/src/configuration/forms/system-form/configuration-system.partial.html @@ -0,0 +1,49 @@ +
+
+
+
+
+ {{opt.label}} +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+ + +
+
+
+ +
+
+
+
+
diff --git a/awx/ui/client/src/configuration/system-form/sub-forms/system-activity-stream.form.js b/awx/ui/client/src/configuration/forms/system-form/sub-forms/system-activity-stream.form.js similarity index 100% rename from awx/ui/client/src/configuration/system-form/sub-forms/system-activity-stream.form.js rename to awx/ui/client/src/configuration/forms/system-form/sub-forms/system-activity-stream.form.js diff --git a/awx/ui/client/src/configuration/system-form/sub-forms/system-logging.form.js b/awx/ui/client/src/configuration/forms/system-form/sub-forms/system-logging.form.js similarity index 100% rename from awx/ui/client/src/configuration/system-form/sub-forms/system-logging.form.js rename to awx/ui/client/src/configuration/forms/system-form/sub-forms/system-logging.form.js diff --git a/awx/ui/client/src/configuration/system-form/sub-forms/system-misc.form.js b/awx/ui/client/src/configuration/forms/system-form/sub-forms/system-misc.form.js similarity index 100% rename from awx/ui/client/src/configuration/system-form/sub-forms/system-misc.form.js rename to awx/ui/client/src/configuration/forms/system-form/sub-forms/system-misc.form.js diff --git a/awx/ui/client/src/configuration/ui-form/configuration-ui.controller.js b/awx/ui/client/src/configuration/forms/ui-form/configuration-ui.controller.js similarity index 63% rename from awx/ui/client/src/configuration/ui-form/configuration-ui.controller.js rename to awx/ui/client/src/configuration/forms/ui-form/configuration-ui.controller.js index 000b265b93..f35e8b6090 100644 --- a/awx/ui/client/src/configuration/ui-form/configuration-ui.controller.js +++ b/awx/ui/client/src/configuration/forms/ui-form/configuration-ui.controller.js @@ -7,37 +7,36 @@ export default [ '$scope', '$rootScope', - '$state', - '$timeout', 'ConfigurationUiForm', - 'ConfigurationService', 'CreateSelect2', 'GenerateForm', 'i18n', + '$stateParams', function( $scope, $rootScope, - $state, - $timeout, ConfigurationUiForm, - ConfigurationService, CreateSelect2, GenerateForm, - i18n + i18n, + $stateParams ) { - var uiVm = this; var generator = GenerateForm; var form = ConfigurationUiForm; + const formTracker = $scope.$parent.vm.formTracker; + if ($stateParams.form === 'ui') { + formTracker.setCurrentAuth('ui'); + } var keys = _.keys(form.fields); _.each(keys, function(key) { - if($scope.$parent.configDataResolve[key].type === 'choice') { + if($scope.configDataResolve[key].type === 'choice') { // Create options for dropdowns var optionsGroup = key + '_options'; - $scope.$parent[optionsGroup] = []; - _.each($scope.$parent.configDataResolve[key].choices, function(choice){ - $scope.$parent[optionsGroup].push({ + $scope.$parent.$parent[optionsGroup] = []; + _.each($scope.configDataResolve[key].choices, function(choice){ + $scope.$parent.$parent[optionsGroup].push({ name: choice[0], label: choice[1], value: choice[0] @@ -52,25 +51,25 @@ function addFieldInfo(form, key) { _.extend(form.fields[key], { - awPopOver: ($scope.$parent.configDataResolve[key].defined_in_file) ? - null: $scope.$parent.configDataResolve[key].help_text, - label: $scope.$parent.configDataResolve[key].label, + awPopOver: ($scope.configDataResolve[key].defined_in_file) ? + null: $scope.configDataResolve[key].help_text, + label: $scope.configDataResolve[key].label, name: key, toggleSource: key, dataPlacement: 'top', - dataTitle: $scope.$parent.configDataResolve[key].label, - required: $scope.$parent.configDataResolve[key].required, + dataTitle: $scope.configDataResolve[key].label, + required: $scope.configDataResolve[key].required, ngDisabled: $rootScope.user_is_system_auditor, - disabled: $scope.$parent.configDataResolve[key].disabled || null, - readonly: $scope.$parent.configDataResolve[key].readonly || null, - definedInFile: $scope.$parent.configDataResolve[key].defined_in_file || null + disabled: $scope.configDataResolve[key].disabled || null, + readonly: $scope.configDataResolve[key].readonly || null, + definedInFile: $scope.configDataResolve[key].defined_in_file || null }); } generator.inject(form, { id: 'configure-ui-form', mode: 'edit', - scope: $scope.$parent, + scope: $scope.$parent.$parent, related: true, noPanel: true }); @@ -79,8 +78,8 @@ var dropdownRendered = false; function populatePendoTrackingState(flag){ - if($scope.$parent.PENDO_TRACKING_STATE !== null) { - $scope.$parent.PENDO_TRACKING_STATE = _.find($scope.$parent.PENDO_TRACKING_STATE_options, { value: $scope.$parent.PENDO_TRACKING_STATE }); + if($scope.$parent.$parent.PENDO_TRACKING_STATE !== null) { + $scope.$parent.$parent.PENDO_TRACKING_STATE = _.find($scope.$parent.$parent.PENDO_TRACKING_STATE_options, { value: $scope.$parent.$parent.PENDO_TRACKING_STATE }); } if(flag !== undefined){ @@ -104,10 +103,5 @@ $scope.$on('populated', function(){ populatePendoTrackingState(false); }); - - angular.extend(uiVm, { - - }); - } ]; diff --git a/awx/ui/client/src/configuration/ui-form/configuration-ui.form.js b/awx/ui/client/src/configuration/forms/ui-form/configuration-ui.form.js similarity index 100% rename from awx/ui/client/src/configuration/ui-form/configuration-ui.form.js rename to awx/ui/client/src/configuration/forms/ui-form/configuration-ui.form.js diff --git a/awx/ui/client/src/configuration/ui-form/configuration-ui.partial.html b/awx/ui/client/src/configuration/forms/ui-form/configuration-ui.partial.html similarity index 100% rename from awx/ui/client/src/configuration/ui-form/configuration-ui.partial.html rename to awx/ui/client/src/configuration/forms/ui-form/configuration-ui.partial.html diff --git a/awx/ui/client/src/configuration/license.route.js b/awx/ui/client/src/configuration/license.route.js deleted file mode 100644 index 2efa23bcae..0000000000 --- a/awx/ui/client/src/configuration/license.route.js +++ /dev/null @@ -1,52 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import {templateUrl} from '../shared/template-url/template-url.factory'; -import { N_ } from '../i18n'; -import _ from 'lodash'; - -export default { - name: 'configuration.license', - route: '/license', - // templateUrl: templateUrl('license/license'), - // controller: 'licenseController', - data: {}, - ncyBreadcrumb: { - label: N_('LICENSE') - }, - onEnter: ['$state', 'ConfigService', (state, configService) => { - return configService.getConfig() - .then(config => { - if (_.get(config, 'license_info.license_type') === 'open') { - return state.go('setup'); - } - }); - }], - views: { - 'license@configuration': { - templateUrl: templateUrl('license/license'), - controller: 'licenseController' - }, - }, - resolve: { - features: ['CheckLicense', '$rootScope', - function(CheckLicense, $rootScope) { - if($rootScope.licenseMissing === undefined){ - return CheckLicense.notify(); - } - - }], - config: ['ConfigService', 'CheckLicense', '$rootScope', - function(ConfigService, CheckLicense, $rootScope) { - ConfigService.delete(); - return ConfigService.getConfig() - .then(function(config){ - $rootScope.licenseMissing = (CheckLicense.valid(config.license_info) === false) ? true : false; - return config; - }); - }] - }, -}; diff --git a/awx/ui/client/src/configuration/main.js b/awx/ui/client/src/configuration/main.js index 5e20fa830d..809f3312fd 100644 --- a/awx/ui/client/src/configuration/main.js +++ b/awx/ui/client/src/configuration/main.js @@ -4,40 +4,43 @@ * All Rights Reserved *************************************************/ -import configurationService from './configuration.service'; -import ConfigurationUtils from './configurationUtils.service'; -import configurationRoute from './configuration.route'; -import licenseRoute from './license.route'; -import configurationController from './configuration.controller.js'; +import settingsService from './settings.service'; +import settingsUtils from './settingsUtils.service'; // Import forms //authorization sub-forms -import configurationAzureForm from './auth-form/sub-forms/auth-azure.form.js'; -import configurationGithubForm from './auth-form/sub-forms/auth-github.form.js'; -import configurationGithubOrgForm from './auth-form/sub-forms/auth-github-org.form'; -import configurationGithubTeamForm from './auth-form/sub-forms/auth-github-team.form'; -import configurationGoogleForm from './auth-form/sub-forms/auth-google-oauth2.form'; -import configurationLdapForm from './auth-form/sub-forms/auth-ldap.form.js'; -import configurationLdap1Form from './auth-form/sub-forms/auth-ldap1.form.js'; -import configurationLdap2Form from './auth-form/sub-forms/auth-ldap2.form.js'; -import configurationLdap3Form from './auth-form/sub-forms/auth-ldap3.form.js'; -import configurationLdap4Form from './auth-form/sub-forms/auth-ldap4.form.js'; -import configurationLdap5Form from './auth-form/sub-forms/auth-ldap5.form.js'; -import configurationRadiusForm from './auth-form/sub-forms/auth-radius.form.js'; -import configurationTacacsForm from './auth-form/sub-forms/auth-tacacs.form.js'; -import configurationSamlForm from './auth-form/sub-forms/auth-saml.form'; +import configurationAzureForm from './forms/auth-form/sub-forms/auth-azure.form.js'; +import configurationGithubForm from './forms/auth-form/sub-forms/auth-github.form.js'; +import configurationGithubOrgForm from './forms/auth-form/sub-forms/auth-github-org.form'; +import configurationGithubTeamForm from './forms/auth-form/sub-forms/auth-github-team.form'; +import configurationGoogleForm from './forms/auth-form/sub-forms/auth-google-oauth2.form'; +import configurationLdapForm from './forms/auth-form/sub-forms/auth-ldap.form.js'; +import configurationLdap1Form from './forms/auth-form/sub-forms/auth-ldap1.form.js'; +import configurationLdap2Form from './forms/auth-form/sub-forms/auth-ldap2.form.js'; +import configurationLdap3Form from './forms/auth-form/sub-forms/auth-ldap3.form.js'; +import configurationLdap4Form from './forms/auth-form/sub-forms/auth-ldap4.form.js'; +import configurationLdap5Form from './forms/auth-form/sub-forms/auth-ldap5.form.js'; +import configurationRadiusForm from './forms/auth-form/sub-forms/auth-radius.form.js'; +import configurationTacacsForm from './forms/auth-form/sub-forms/auth-tacacs.form.js'; +import configurationSamlForm from './forms/auth-form/sub-forms/auth-saml.form'; //system sub-forms -import systemActivityStreamForm from './system-form/sub-forms/system-activity-stream.form.js'; -import systemLoggingForm from './system-form/sub-forms/system-logging.form.js'; -import systemMiscForm from './system-form/sub-forms/system-misc.form.js'; +import systemActivityStreamForm from './forms/system-form/sub-forms/system-activity-stream.form.js'; +import systemLoggingForm from './forms/system-form/sub-forms/system-logging.form.js'; +import systemMiscForm from './forms/system-form/sub-forms/system-misc.form.js'; -import configurationJobsForm from './jobs-form/configuration-jobs.form'; -import configurationUiForm from './ui-form/configuration-ui.form'; +import configurationJobsForm from './forms/jobs-form/configuration-jobs.form'; +import configurationUiForm from './forms/ui-form/configuration-ui.form'; + +// Wrapper form route +import settingsFormRoute from './forms/settings-form.route'; + +import settingsRoute from './settings.route'; +import settingsController from './settings.controller.js'; export default angular.module('configuration', []) - .controller('ConfigurationController', configurationController) + .controller('SettingsController', settingsController) //auth forms .factory('configurationAzureForm', configurationAzureForm) .factory('configurationGithubForm', configurationGithubForm) @@ -63,9 +66,9 @@ angular.module('configuration', []) .factory('ConfigurationUiForm', configurationUiForm) //helpers and services - .factory('ConfigurationUtils', ConfigurationUtils) - .service('ConfigurationService', configurationService) + .factory('SettingsUtils', settingsUtils) + .service('SettingsService', settingsService) .run(['$stateExtender', function($stateExtender) { - $stateExtender.addState(configurationRoute); - $stateExtender.addState(licenseRoute); + $stateExtender.addState(settingsFormRoute); + $stateExtender.addState(settingsRoute); }]); diff --git a/awx/ui/client/src/configuration/configuration.block.less b/awx/ui/client/src/configuration/settings.block.less similarity index 100% rename from awx/ui/client/src/configuration/configuration.block.less rename to awx/ui/client/src/configuration/settings.block.less diff --git a/awx/ui/client/src/configuration/settings.controller.js b/awx/ui/client/src/configuration/settings.controller.js new file mode 100644 index 0000000000..88ea0745cd --- /dev/null +++ b/awx/ui/client/src/configuration/settings.controller.js @@ -0,0 +1,13 @@ +import defaultStrings from '~assets/default.strings.json'; + +export default [ '$state', + function ($state) { + const vm = this; + + vm.product = defaultStrings.BRAND_NAME; + + vm.goToCard = (card) => { + $state.go('settings.form', { form: card }); + }; + } +]; diff --git a/awx/ui/client/src/configuration/settings.partial.html b/awx/ui/client/src/configuration/settings.partial.html new file mode 100644 index 0000000000..588c055d78 --- /dev/null +++ b/awx/ui/client/src/configuration/settings.partial.html @@ -0,0 +1,17 @@ + + + Enable simplified login for your Tower applications + + + Update settings pertaining to Jobs within Tower + + + Define system-level features and functions + + + Set preferences for data collection, logos, and logins + + + View and edit your license information + + \ No newline at end of file diff --git a/awx/ui/client/src/configuration/settings.route.js b/awx/ui/client/src/configuration/settings.route.js new file mode 100644 index 0000000000..e975a5f6ba --- /dev/null +++ b/awx/ui/client/src/configuration/settings.route.js @@ -0,0 +1,40 @@ +import { N_ } from '../i18n'; +import {templateUrl} from '../shared/template-url/template-url.factory'; +import SettingsController from './settings.controller'; +// Import form controllers + +export default { + name: 'settings', + route: '/settings', + ncyBreadcrumb: { + label: N_("SETTINGS") + }, + resolve: { + configDataResolve: ['SettingsService', function(SettingsService){ + return SettingsService.getConfigurationOptions(); + }], + features: ['CheckLicense', '$rootScope', + function(CheckLicense, $rootScope) { + if($rootScope.licenseMissing === undefined){ + return CheckLicense.notify(); + } + + }], + config: ['ConfigService', 'CheckLicense', '$rootScope', + function(ConfigService, CheckLicense, $rootScope) { + ConfigService.delete(); + return ConfigService.getConfig() + .then(function(config){ + $rootScope.licenseMissing = (CheckLicense.valid(config.license_info) === false) ? true : false; + return config; + }); + }], + }, + views: { + '': { + templateUrl: templateUrl('configuration/settings'), + controller: SettingsController, + controllerAs: 'vm' + } + } +}; \ No newline at end of file diff --git a/awx/ui/client/src/configuration/configuration.service.js b/awx/ui/client/src/configuration/settings.service.js similarity index 100% rename from awx/ui/client/src/configuration/configuration.service.js rename to awx/ui/client/src/configuration/settings.service.js diff --git a/awx/ui/client/src/configuration/configurationUtils.service.js b/awx/ui/client/src/configuration/settingsUtils.service.js similarity index 100% rename from awx/ui/client/src/configuration/configurationUtils.service.js rename to awx/ui/client/src/configuration/settingsUtils.service.js diff --git a/awx/ui/client/src/configuration/system-form/configuration-system.partial.html b/awx/ui/client/src/configuration/system-form/configuration-system.partial.html deleted file mode 100644 index 32337fc588..0000000000 --- a/awx/ui/client/src/configuration/system-form/configuration-system.partial.html +++ /dev/null @@ -1,49 +0,0 @@ -
-
-
Sub Category
-
- -
-
-
-
- -
-
- -
-
-
-
- -
-
-
-
- -
- - -
-
-
- -
-
-
-
-
-
diff --git a/awx/ui/client/src/shared/directives.js b/awx/ui/client/src/shared/directives.js index 5b1fc00d25..a3a2e0a4d8 100644 --- a/awx/ui/client/src/shared/directives.js +++ b/awx/ui/client/src/shared/directives.js @@ -86,8 +86,8 @@ angular.module('AWDirectives', ['RestServices', 'Utilities']) // Accepts image and returns base64 information with basic validation // Can eventually expand to handle all uploads with different endpoints and handlers // -.directive('imageUpload', ['ConfigurationUtils', 'i18n', '$rootScope', -function(ConfigurationUtils, i18n, $rootScope) { +.directive('imageUpload', ['SettingsUtils', 'i18n', '$rootScope', +function(SettingsUtils, i18n, $rootScope) { var browseText = i18n._('BROWSE'), placeholderText = i18n._('Choose file'), uploadedText = i18n._('Current Image: '), @@ -157,7 +157,7 @@ function(ConfigurationUtils, i18n, $rootScope) { scope.fileChange = function(file) { filePickerError.html(''); - ConfigurationUtils.imageProcess(file[0]) + SettingsUtils.imageProcess(file[0]) .then(function(result) { scope.$parent[fieldKey] = result; filePickerText.val(file[0].name); diff --git a/awx/ui/client/src/shared/parse/parse-type-change.factory.js b/awx/ui/client/src/shared/parse/parse-type-change.factory.js index 7728850046..d54af36dcf 100644 --- a/awx/ui/client/src/shared/parse/parse-type-change.factory.js +++ b/awx/ui/client/src/shared/parse/parse-type-change.factory.js @@ -48,7 +48,6 @@ export default scrollbarStyle: null } }; - scope[fld + 'codeMirror'] = AngularCodeMirror(readOnly); scope[fld + 'codeMirror'].addModes(variableEditModes); scope[fld + 'codeMirror'].showTextArea({ @@ -61,7 +60,6 @@ export default onChange: onChange }); } - // Hide the textarea and show a CodeMirror editor createField(onChange, onReady, fld); diff --git a/awx/ui/test/e2e/objects/configuration.js b/awx/ui/test/e2e/objects/configuration.js index fdf45677a9..d6e8c0d4f5 100644 --- a/awx/ui/test/e2e/objects/configuration.js +++ b/awx/ui/test/e2e/objects/configuration.js @@ -14,6 +14,17 @@ const commands = [{ return this.navigate(); }, selectSubcategory (name) { + const spinny = 'div.spinny'; + const categoryName = `//*[text() = '${name}']`; + + this.api.useXpath(); + this.api.waitForElementVisible(categoryName); + this.api.click(categoryName); + this.api.useCss(); + + return this; + }, + selectDropDownContainer (name) { const spinny = 'div.spinny'; const select = '#configure-dropdown-nav'; const arrow = `${select} + span span[class$="arrow"]`; diff --git a/awx/ui/test/e2e/objects/sections/navigation.js b/awx/ui/test/e2e/objects/sections/navigation.js index 2ac257f1dd..c0577a269c 100644 --- a/awx/ui/test/e2e/objects/sections/navigation.js +++ b/awx/ui/test/e2e/objects/sections/navigation.js @@ -18,7 +18,10 @@ const navigation = { notifications: 'i[class$="fa-bell"]', managementJobs: 'i[class$="fa-wrench"]', instanceGroups: 'i[class$="fa-server"]', - settings: 'i[class$="fa-cog"]', + settings: 'i[class*="fa-cog"]', + settingsSubPane: '.at-SettingsSubPane', + settingsSubPaneSystem: 'a[href="#/settings/system"]', + settingsSubPaneAuth: 'a[href="#/settings/auth"]' } }; diff --git a/awx/ui/test/e2e/tests/test-configuration-ldap-fields.js b/awx/ui/test/e2e/tests/test-configuration-ldap-fields.js index ab0db09d16..159b5fcc58 100644 --- a/awx/ui/test/e2e/tests/test-configuration-ldap-fields.js +++ b/awx/ui/test/e2e/tests/test-configuration-ldap-fields.js @@ -1,9 +1,7 @@ module.exports = { 'expected LDAP codemirror fields are rendered when returning from another tab': client => { - const authTab = '#auth_tab'; const authView = 'div[ui-view="auth"]'; const ldapForm = '#configuration_ldap_template_form'; - const systemTab = '#system_tab'; const systemView = 'div[ui-view="system"]'; const { navigation } = client.page.dashboard().section; @@ -14,20 +12,23 @@ module.exports = { navigation .waitForElementVisible('@settings') - .click('@settings'); + .moveToElement('@settings', 0, 0) + .waitForElementVisible('@settingsSubPaneSystem') + .click('@settingsSubPaneSystem'); - configuration.waitForElementVisible(authView); - - configuration.waitForElementVisible(systemTab); - configuration.click(systemTab); - configuration.waitForElementNotVisible(authView); configuration.waitForElementVisible(systemView); - configuration.waitForElementVisible(authTab); - configuration.click(authTab); - configuration.waitForElementNotVisible(systemView); + navigation + .waitForElementVisible('@settings') + .moveToElement('@settings', 0, 0) + .waitForElementVisible('@settingsSubPane') + .waitForElementVisible('@settingsSubPaneAuth') + .click('@settingsSubPaneAuth'); + configuration.waitForElementVisible(authView); + // works as xpath const categoryName = + // `//*[@id="configuration_edit"]/div[1]/div/div/div[4]`; configuration.selectSubcategory('LDAP'); configuration.waitForElementVisible(ldapForm); @@ -41,7 +42,7 @@ module.exports = { 'AUTH_LDAP_TEAM_MAP', ]; - const ldapCodeMirrors = `${ldapForm} div[class^="CodeMirror"] textarea`; + const ldapCodeMirrors = `${ldapForm} div[class^="CodeMirror"] textarea`; client.elements('css selector', ldapCodeMirrors, ({ value }) => { client.assert.equal(value.length, expectedCodemirrorFields.length);