From ee035cbb99d9b2b7942d40626de3115548f4835b Mon Sep 17 00:00:00 2001
From: Ken Hoes
Date: Thu, 27 Oct 2016 15:48:36 -0400
Subject: [PATCH] Configure Tower in Tower
---
awx/ui/client/src/app.js | 2 +
.../configuration-auth.controller.js | 235 ++++++++++
.../auth-form/configuration-auth.partial.html | 53 +++
.../sub-forms/auth-github-org.form.js | 43 ++
.../sub-forms/auth-github-team.form.js | 43 ++
.../auth-form/sub-forms/auth-github.form.js | 39 ++
.../sub-forms/auth-google-oauth2.form.js | 51 +++
.../auth-form/sub-forms/auth-ldap.form.js | 99 +++++
.../auth-form/sub-forms/auth-radius.form.js | 44 ++
.../auth-form/sub-forms/auth-saml.form.js | 71 +++
.../configuration/configuration.block.less | 27 ++
.../configuration/configuration.controller.js | 405 ++++++++++++++++++
.../configuration/configuration.partial.html | 22 +
.../src/configuration/configuration.route.js | 56 +++
.../configuration/configuration.service.js | 74 ++++
.../configurationUtils.service.js | 67 +++
.../configuration-jobs.controller.js | 94 ++++
.../jobs-form/configuration-jobs.form.js | 67 +++
.../jobs-form/configuration-jobs.partial.html | 10 +
awx/ui/client/src/configuration/main.js | 44 ++
.../configuration-system.controller.js | 68 +++
.../system-form/configuration-system.form.js | 55 +++
.../configuration-system.partial.html | 10 +
.../ui-form/configuration-ui.controller.js | 92 ++++
.../ui-form/configuration-ui.form.js | 37 ++
.../ui-form/configuration-ui.partial.html | 10 +
.../src/setup-menu/setup-menu.partial.html | 6 +
awx/ui/client/src/shared/Utilities.js | 4 +-
awx/ui/client/src/shared/form-generator.js | 16 +
29 files changed, 1842 insertions(+), 2 deletions(-)
create mode 100644 awx/ui/client/src/configuration/auth-form/configuration-auth.controller.js
create mode 100644 awx/ui/client/src/configuration/auth-form/configuration-auth.partial.html
create mode 100644 awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-org.form.js
create mode 100644 awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-team.form.js
create mode 100644 awx/ui/client/src/configuration/auth-form/sub-forms/auth-github.form.js
create mode 100644 awx/ui/client/src/configuration/auth-form/sub-forms/auth-google-oauth2.form.js
create mode 100644 awx/ui/client/src/configuration/auth-form/sub-forms/auth-ldap.form.js
create mode 100644 awx/ui/client/src/configuration/auth-form/sub-forms/auth-radius.form.js
create mode 100644 awx/ui/client/src/configuration/auth-form/sub-forms/auth-saml.form.js
create mode 100644 awx/ui/client/src/configuration/configuration.block.less
create mode 100644 awx/ui/client/src/configuration/configuration.controller.js
create mode 100644 awx/ui/client/src/configuration/configuration.partial.html
create mode 100644 awx/ui/client/src/configuration/configuration.route.js
create mode 100644 awx/ui/client/src/configuration/configuration.service.js
create mode 100644 awx/ui/client/src/configuration/configurationUtils.service.js
create mode 100644 awx/ui/client/src/configuration/jobs-form/configuration-jobs.controller.js
create mode 100644 awx/ui/client/src/configuration/jobs-form/configuration-jobs.form.js
create mode 100644 awx/ui/client/src/configuration/jobs-form/configuration-jobs.partial.html
create mode 100644 awx/ui/client/src/configuration/main.js
create mode 100644 awx/ui/client/src/configuration/system-form/configuration-system.controller.js
create mode 100644 awx/ui/client/src/configuration/system-form/configuration-system.form.js
create mode 100644 awx/ui/client/src/configuration/system-form/configuration-system.partial.html
create mode 100644 awx/ui/client/src/configuration/ui-form/configuration-ui.controller.js
create mode 100644 awx/ui/client/src/configuration/ui-form/configuration-ui.form.js
create mode 100644 awx/ui/client/src/configuration/ui-form/configuration-ui.partial.html
diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js
index cdc39200f3..d7c42b13fd 100644
--- a/awx/ui/client/src/app.js
+++ b/awx/ui/client/src/app.js
@@ -56,6 +56,7 @@ import setupMenu from './setup-menu/main';
import mainMenu from './main-menu/main';
import breadCrumb from './bread-crumb/main';
import browserData from './browser-data/main';
+import configuration from './configuration/main';
import dashboard from './dashboard/main';
import moment from './shared/moment/main';
import login from './login/main';
@@ -100,6 +101,7 @@ var tower = angular.module('Tower', [
license.name,
RestServices.name,
browserData.name,
+ configuration.name,
systemTracking.name,
inventories.name,
inventoryScripts.name,
diff --git a/awx/ui/client/src/configuration/auth-form/configuration-auth.controller.js b/awx/ui/client/src/configuration/auth-form/configuration-auth.controller.js
new file mode 100644
index 0000000000..3ba3d2fa72
--- /dev/null
+++ b/awx/ui/client/src/configuration/auth-form/configuration-auth.controller.js
@@ -0,0 +1,235 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+export default [
+ '$scope',
+ '$state',
+ '$stateParams',
+ '$timeout',
+ '$q',
+ 'configurationGithubForm',
+ 'configurationGithubOrgForm',
+ 'configurationGithubTeamForm',
+ 'configurationGoogleForm',
+ 'configurationLdapForm',
+ 'configurationRadiusForm',
+ 'configurationSamlForm',
+ 'ConfigurationService',
+ 'ConfigurationUtils',
+ 'CreateSelect2',
+ 'GenerateForm',
+ 'ParseTypeChange',
+ 'Wait',
+ function(
+ $scope,
+ $state,
+ $stateParams,
+ $timeout,
+ $q,
+ configurationGithubForm,
+ configurationGithubOrgForm,
+ configurationGithubTeamForm,
+ configurationGoogleForm,
+ configurationLdapForm,
+ configurationRadiusForm,
+ configurationSamlForm,
+ ConfigurationService,
+ ConfigurationUtils,
+ CreateSelect2,
+ GenerateForm,
+ ParseTypeChange,
+ Wait
+ ) {
+ var authVm = this;
+
+ var generator = GenerateForm;
+ var formTracker = $scope.$parent.vm.formTracker;
+ var dropdownValue = 'github';
+ var activeAuthForm = 'github';
+
+ // Default active form
+ if ($stateParams.currentTab === '' || $stateParams.currentTab === 'auth') {
+ formTracker.setCurrentAuth(activeAuthForm);
+ }
+
+ var activeForm = function() {
+ if(!$scope.$parent[formTracker.currentFormName()].$dirty) {
+ authVm.activeAuthForm = authVm.dropdownValue;
+ formTracker.setCurrentAuth(authVm.activeAuthForm);
+ } else {
+ var msg = 'You have unsaved changes. Would you like to proceed without saving?';
+ var title = 'Warning: Unsaved Changes';
+ var buttons = [{
+ label: "Discard changes",
+ "class": "btn Form-cancelButton",
+ "id": "formmodal-cancel-button",
+ onClick: function() {
+ $scope.$parent.vm.populateFromApi();
+ $scope.$parent[formTracker.currentFormName()].$setPristine();
+ authVm.activeAuthForm = authVm.dropdownValue;
+ formTracker.setCurrentAuth(authVm.activeAuthForm);
+ $('#FormModal-dialog').dialog('close');
+ }
+ }, {
+ label: "Save changes",
+ onClick: function() {
+ $scope.$parent.vm.formSave()
+ .then(function() {
+ $scope.$parent[formTracker.currentFormName()].$setPristine();
+ $scope.$parent.vm.populateFromApi();
+ authVm.activeAuthForm = authVm.dropdownValue;
+ formTracker.setCurrentAuth(authVm.activeAuthForm);
+ $('#FormModal-dialog').dialog('close');
+ });
+ },
+ "class": "btn btn-primary",
+ "id": "formmodal-save-button"
+ }];
+ $scope.$parent.vm.triggerModal(msg, title, buttons);
+ }
+ formTracker.setCurrentAuth(authVm.activeAuthForm);
+ };
+
+ var dropdownOptions = [
+ {label: 'Github', value: 'github'},
+ {label: 'Github Org', value: 'github_org'},
+ {label: 'Github Team', value: 'github_team'},
+ {label: 'Google OAuth2', value: 'google_oauth'},
+ {label: 'LDAP', value: 'ldap'},
+ {label: 'RADIUS', value: 'radius'},
+ {label: 'SAML', value: 'saml'}
+ ];
+
+ CreateSelect2({
+ element: '#configure-dropdown-nav',
+ multiple: false,
+ });
+
+ var authForms = [{
+ formDef: configurationGithubForm,
+ id: 'auth-github-form'
+ }, {
+ formDef: configurationGithubOrgForm,
+ id: 'auth-github-org-form'
+ }, {
+ formDef: configurationGithubTeamForm,
+ id: 'auth-github-team-form'
+ }, {
+ formDef: configurationGoogleForm,
+ id: 'auth-google-form'
+ }, {
+ formDef: configurationLdapForm,
+ id: 'auth-ldap-form'
+ }, {
+ formDef: configurationRadiusForm,
+ id: 'auth-radius-form'
+ }, {
+ formDef: configurationSamlForm,
+ id: 'auth-saml-form'
+ }, ];
+
+ var forms = _.pluck(authForms, 'formDef');
+
+ _.each(forms, function(form) {
+ var keys = _.keys(form.fields);
+ _.each(keys, function(key) {
+ if($scope.$parent.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({
+ name: choice[0],
+ label: choice[1],
+ value: choice[0]
+ });
+ });
+ }
+ addFieldInfo(form, key);
+ });
+ });
+
+ function addFieldInfo(form, key) {
+ _.extend(form.fields[key], {
+ awPopOver: $scope.$parent.configDataResolve[key].help_text,
+ label: $scope.$parent.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
+ });
+ }
+
+ $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
+ });
+ });
+
+ // Flag to avoid re-rendering and breaking Select2 dropdowns on tab switching
+ var dropdownRendered = false;
+
+ $scope.$on('populated', function() {
+ // Attach codemirror to fields that need it
+ _.each(authForms, function(form) {
+ _.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(field.codeMirror) {
+ ParseTypeChange({
+ scope: $scope.$parent,
+ variable: field.name,
+ parse_variable: 'parseType',
+ field_id: form.formDef.name + '_' + field.name
+ });
+ }
+ });
+ });
+
+ // Create Select2 fields
+ var opts = [];
+ if($scope.$parent.AUTH_LDAP_GROUP_TYPE !== null) {
+ opts.push({
+ id: $scope.$parent.AUTH_LDAP_GROUP_TYPE,
+ text: $scope.$parent.AUTH_LDAP_GROUP_TYPE
+ });
+ }
+
+ if(!dropdownRendered) {
+ dropdownRendered = true;
+ CreateSelect2({
+ element: '#configuration_ldap_template_AUTH_LDAP_GROUP_TYPE',
+ multiple: false,
+ placeholder: 'Select group types',
+ opts: opts
+ });
+ // 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_ldap_template_form.$setPristine();
+ }, 1000);
+ }
+
+ });
+
+ angular.extend(authVm, {
+ activeForm: activeForm,
+ activeAuthForm: activeAuthForm,
+ authForms: authForms,
+ dropdownOptions: dropdownOptions,
+ dropdownValue: dropdownValue
+ });
+ }
+];
diff --git a/awx/ui/client/src/configuration/auth-form/configuration-auth.partial.html b/awx/ui/client/src/configuration/auth-form/configuration-auth.partial.html
new file mode 100644
index 0000000000..d80652f124
--- /dev/null
+++ b/awx/ui/client/src/configuration/auth-form/configuration-auth.partial.html
@@ -0,0 +1,53 @@
+
+
diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-org.form.js b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-org.form.js
new file mode 100644
index 0000000000..6bc58773e3
--- /dev/null
+++ b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-org.form.js
@@ -0,0 +1,43 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+export default function() {
+ return {
+ name: 'configuration_github_org_template',
+ showActions: true,
+ showHeader: false,
+
+ fields: {
+ SOCIAL_AUTH_GITHUB_ORG_KEY: {
+ type: 'text',
+ reset: 'SOCIAL_AUTH_GITHUB_ORG_KEY'
+ },
+ SOCIAL_AUTH_GITHUB_ORG_SECRET: {
+ type: 'text',
+ reset: 'SOCIAL_AUTH_GITHUB_ORG_SECRET'
+ },
+ SOCIAL_AUTH_GITHUB_ORG_NAME: {
+ type: 'text',
+ reset: 'SOCIAL_AUTH_GITHUB_ORG_NAME'
+ }
+ },
+
+ buttons: {
+ reset: {
+ ngClick: 'vm.resetAllConfirm()',
+ label: 'Reset All',
+ class: 'Form-button--left Form-cancelButton'
+ },
+ cancel: {
+ ngClick: 'vm.formCancel()',
+ },
+ save: {
+ ngClick: 'vm.formSave()',
+ ngDisabled: true
+ }
+ }
+ };
+}
diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-team.form.js b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-team.form.js
new file mode 100644
index 0000000000..bad0c95627
--- /dev/null
+++ b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github-team.form.js
@@ -0,0 +1,43 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+export default function() {
+ return {
+ name: 'configuration_github_team_template',
+ showActions: true,
+ showHeader: false,
+
+ fields: {
+ SOCIAL_AUTH_GITHUB_TEAM_KEY: {
+ type: 'text',
+ reset: 'SOCIAL_AUTH_GITHUB_TEAM_KEY'
+ },
+ SOCIAL_AUTH_GITHUB_TEAM_SECRET: {
+ type: 'text',
+ reset: 'SOCIAL_AUTH_GITHUB_TEAM_SECRET'
+ },
+ SOCIAL_AUTH_GITHUB_TEAM_ID: {
+ type: 'text',
+ reset: 'SOCIAL_AUTH_GITHUB_TEAM_ID'
+ }
+ },
+
+ buttons: {
+ reset: {
+ ngClick: 'vm.resetAllConfirm()',
+ label: 'Reset All',
+ class: 'Form-button--left Form-cancelButton'
+ },
+ cancel: {
+ ngClick: 'vm.formCancel()',
+ },
+ save: {
+ ngClick: 'vm.formSave()',
+ ngDisabled: true
+ }
+ }
+ };
+}
diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github.form.js b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github.form.js
new file mode 100644
index 0000000000..ee46c53fb7
--- /dev/null
+++ b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-github.form.js
@@ -0,0 +1,39 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+export default function() {
+ return {
+ name: 'configuration_github_template',
+ showActions: true,
+ showHeader: false,
+
+ fields: {
+ SOCIAL_AUTH_GITHUB_KEY: {
+ type: 'text',
+ reset: 'SOCIAL_AUTH_GITHUB_KEY'
+ },
+ SOCIAL_AUTH_GITHUB_SECRET: {
+ type: 'text',
+ reset: 'SOCIAL_AUTH_GITHUB_SECRET'
+ }
+ },
+
+ buttons: {
+ reset: {
+ ngClick: 'vm.resetAllConfirm()',
+ label: 'Reset All',
+ class: 'Form-button--left Form-cancelButton'
+ },
+ cancel: {
+ ngClick: 'vm.formCancel()',
+ },
+ save: {
+ ngClick: 'vm.formSave()',
+ ngDisabled: true
+ }
+ }
+ };
+}
diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-google-oauth2.form.js b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-google-oauth2.form.js
new file mode 100644
index 0000000000..b00748da55
--- /dev/null
+++ b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-google-oauth2.form.js
@@ -0,0 +1,51 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+export default function() {
+ return {
+ name: 'configuration_google_oauth_template',
+ showActions: true,
+ showHeader: false,
+
+ fields: {
+ SOCIAL_AUTH_GOOGLE_OAUTH2_KEY: {
+ type: 'text',
+ reset: 'SOCIAL_AUTH_GOOGLE_OAUTH2_KEY'
+ },
+ SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET: {
+ type: 'text',
+ reset: 'SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET'
+ },
+ SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS: {
+ type: 'textarea',
+ reset: 'SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS',
+ rows: 6
+ },
+ SOCIAL_AUTH_GOOGLE_OAUTH2_AUTH_EXTRA_ARGUMENTS: {
+ type: 'textarea',
+ reset: 'SOCIAL_AUTH_GOOGLE_OAUTH2_AUTH_EXTRA_ARGUMENTS',
+ codeMirror: true,
+ rows: 6,
+ class: 'Form-textAreaLabel Form-formGroup--fullWidth',
+ }
+ },
+
+ buttons: {
+ reset: {
+ ngClick: 'vm.resetAllConfirm()',
+ label: 'Reset All',
+ class: 'Form-button--left Form-cancelButton'
+ },
+ cancel: {
+ ngClick: 'vm.formCancel()',
+ },
+ save: {
+ ngClick: 'vm.formSave()',
+ ngDisabled: true
+ }
+ }
+ };
+}
diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-ldap.form.js b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-ldap.form.js
new file mode 100644
index 0000000000..2ef6b8b5c4
--- /dev/null
+++ b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-ldap.form.js
@@ -0,0 +1,99 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+export default function() {
+ return {
+ // editTitle: 'Authorization Configuration',
+ name: 'configuration_ldap_template',
+ showActions: true,
+ showHeader: false,
+
+ fields: {
+ AUTH_LDAP_SERVER_URI: {
+ type: 'text',
+ reset: 'AUTH_LDAP_SERVER_URI'
+ },
+ AUTH_LDAP_BIND_DN: {
+ type: 'text',
+ reset: 'AUTH_LDAP_BIND_DN'
+ },
+ AUTH_LDAP_BIND_PASSWORD: {
+ type: 'password'
+ },
+ AUTH_LDAP_USER_SEARCH: {
+ type: 'textarea',
+ reset: 'AUTH_LDAP_USER_SEARCH'
+ },
+ AUTH_LDAP_USER_DN_TEMPLATE: {
+ type: 'text',
+ reset: 'AUTH_LDAP_USER_DN_TEMPLATE'
+ },
+ AUTH_LDAP_USER_ATTR_MAP: {
+ type: 'textarea',
+ reset: 'AUTH_LDAP_USER_ATTR_MAP',
+ rows: 6,
+ codeMirror: true,
+ class: 'Form-textAreaLabel Form-formGroup--fullWidth',
+ },
+ AUTH_LDAP_GROUP_SEARCH: {
+ type: 'textarea',
+ reset: 'AUTH_LDAP_GROUP_SEARCH'
+ },
+ AUTH_LDAP_GROUP_TYPE: {
+ type: 'select',
+ reset: 'AUTH_LDAP_GROUP_TYPE',
+ ngOptions: 'group.label for group in AUTH_LDAP_GROUP_TYPE_options track by group.value',
+ },
+ AUTH_LDAP_REQUIRE_GROUP: {
+ type: 'text',
+ reset: 'AUTH_LDAP_REQUIRE_GROUP'
+ },
+ AUTH_LDAP_DENY_GROUP: {
+ type: 'text',
+ reset: 'AUTH_LDAP_DENY_GROUP'
+ },
+ AUTH_LDAP_START_TLS: {
+ type: 'toggleSwitch'
+ },
+ AUTH_LDAP_USER_FLAGS_BY_GROUP: {
+ type: 'textarea',
+ reset: 'AUTH_LDAP_USER_FLAGS_BY_GROUP',
+ codeMirror: true,
+ rows: 6,
+ class: 'Form-textAreaLabel Form-formGroup--fullWidth',
+ },
+ AUTH_LDAP_ORGANIZATION_MAP: {
+ type: 'textarea',
+ reset: 'AUTH_LDAP_ORGANIZATION_MAP',
+ codeMirror: true,
+ rows: 6,
+ class: 'Form-textAreaLabel Form-formGroup--fullWidth',
+ },
+ AUTH_LDAP_TEAM_MAP: {
+ type: 'textarea',
+ reset: 'AUTH_LDAP_TEAM_MAP',
+ codeMirror: true,
+ rows: 6,
+ class: 'Form-textAreaLabel Form-formGroup--fullWidth',
+ }
+ },
+
+ buttons: {
+ reset: {
+ ngClick: 'vm.resetAllConfirm()',
+ label: 'Reset All',
+ class: 'Form-button--left Form-cancelButton'
+ },
+ cancel: {
+ ngClick: 'vm.formCancel()',
+ },
+ save: {
+ ngClick: 'vm.formSave()',
+ ngDisabled: true
+ }
+ }
+ };
+}
diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-radius.form.js b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-radius.form.js
new file mode 100644
index 0000000000..cd8dc68352
--- /dev/null
+++ b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-radius.form.js
@@ -0,0 +1,44 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+export default function() {
+ return {
+ // editTitle: 'Authorization Configuration',
+ name: 'configuration_radius_template',
+ showActions: true,
+ showHeader: false,
+
+ fields: {
+ RADIUS_SERVER: {
+ type: 'text',
+ reset: 'RADIUS_SERVER'
+ },
+ RADIUS_PORT: {
+ type: 'text',
+ reset: 'RADIUS_PORT'
+ },
+ RADIUS_SECRET: {
+ type: 'text',
+ reset: 'RADIUS_SECRET'
+ }
+ },
+
+ buttons: {
+ reset: {
+ ngClick: 'vm.resetAllConfirm()',
+ label: 'Reset All',
+ class: 'Form-button--left Form-cancelButton'
+ },
+ cancel: {
+ ngClick: 'vm.formCancel()',
+ },
+ save: {
+ ngClick: 'vm.formSave()',
+ ngDisabled: true
+ }
+ }
+ };
+}
diff --git a/awx/ui/client/src/configuration/auth-form/sub-forms/auth-saml.form.js b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-saml.form.js
new file mode 100644
index 0000000000..462e1373fd
--- /dev/null
+++ b/awx/ui/client/src/configuration/auth-form/sub-forms/auth-saml.form.js
@@ -0,0 +1,71 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+export default function() {
+ return {
+ name: 'configuration_saml_template',
+ showActions: true,
+ showHeader: false,
+
+ fields: {
+ SOCIAL_AUTH_SAML_SP_ENTITY_ID: {
+ type: 'text',
+ reset: 'SOCIAL_AUTH_SAML_SP_ENTITY_ID'
+ },
+ SOCIAL_AUTH_SAML_SP_PUBLIC_CERT: {
+ type: 'text',
+ reset: 'SOCIAL_AUTH_SAML_SP_PUBLIC_CERT'
+ },
+ SOCIAL_AUTH_SAML_SP_PRIVATE_KEY: {
+ type: 'text',
+ reset: 'SOCIAL_AUTH_SAML_SP_PRIVATE_KEY'
+ },
+ SOCIAL_AUTH_SAML_ORG_INFO: {
+ type: 'textarea',
+ reset: 'SOCIAL_AUTH_SAML_ORG_INFO',
+ rows: 6,
+ codeMirror: true,
+ class: 'Form-textAreaLabel Form-formGroup--fullWidth'
+ },
+ SOCIAL_AUTH_SAML_TECHNICAL_CONTACT: {
+ type: 'textarea',
+ reset: 'SOCIAL_AUTH_SAML_TECHNICAL_CONTACT',
+ rows: 6,
+ codeMirror: true,
+ class: 'Form-textAreaLabel Form-formGroup--fullWidth'
+ },
+ SOCIAL_AUTH_SAML_SUPPORT_CONTACT: {
+ type: 'textarea',
+ reset: 'SOCIAL_AUTH_SAML_SUPPORT_CONTACT',
+ rows: 6,
+ codeMirror: true,
+ class: 'Form-textAreaLabel Form-formGroup--fullWidth'
+ },
+ SOCIAL_AUTH_SAML_ENABLED_IDPS: {
+ type: 'textarea',
+ reset: 'SOCIAL_AUTH_SAML_ENABLED_IDPS',
+ rows: 6,
+ codeMirror: true,
+ class: 'Form-textAreaLabel Form-formGroup--fullWidth'
+ }
+ },
+
+ buttons: {
+ reset: {
+ ngClick: 'vm.resetAllConfirm()',
+ label: 'Reset All',
+ class: 'Form-button--left Form-cancelButton'
+ },
+ cancel: {
+ ngClick: 'vm.formCancel()',
+ },
+ save: {
+ ngClick: 'vm.formSave()',
+ ngDisabled: true
+ }
+ }
+ };
+}
diff --git a/awx/ui/client/src/configuration/configuration.block.less b/awx/ui/client/src/configuration/configuration.block.less
new file mode 100644
index 0000000000..9c6c855274
--- /dev/null
+++ b/awx/ui/client/src/configuration/configuration.block.less
@@ -0,0 +1,27 @@
+.Form-resetValue {
+ float: right;
+ text-transform: uppercase;
+ font-weight: normal;
+ cursor: pointer;
+ font-size: 12px;
+}
+
+.Form-tab {
+ min-width: 77px;
+}
+
+.Form-button--left {
+ margin-right: auto;
+ margin-left: 0;
+}
+
+.Form-nav--dropdown {
+ width: 175px;
+ margin-top: -52px;
+ margin-bottom: 22px;
+ margin-left: auto;
+}
+
+.Form-tabRow {
+ width: 80%;
+}
diff --git a/awx/ui/client/src/configuration/configuration.controller.js b/awx/ui/client/src/configuration/configuration.controller.js
new file mode 100644
index 0000000000..1214c994d3
--- /dev/null
+++ b/awx/ui/client/src/configuration/configuration.controller.js
@@ -0,0 +1,405 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+export default [
+ '$scope', '$state', '$stateParams', '$timeout', '$q', 'Alert', 'ClearScope',
+ 'ConfigurationService', 'ConfigurationUtils', 'CreateDialog', 'CreateSelect2', 'ParseTypeChange', 'ProcessErrors',
+ 'Wait', 'configDataResolve',
+ //Form definitions
+ 'configurationGithubForm',
+ 'configurationGithubOrgForm',
+ 'configurationGithubTeamForm',
+ 'configurationGoogleForm',
+ 'configurationLdapForm',
+ 'configurationRadiusForm',
+ 'configurationSamlForm',
+ 'ConfigurationJobsForm',
+ 'ConfigurationSystemForm',
+ 'ConfigurationUiForm',
+ function(
+ $scope, $state, $stateParams, $timeout, $q, Alert, ClearScope,
+ ConfigurationService, ConfigurationUtils, CreateDialog, CreateSelect2, ParseTypeChange, ProcessErrors,
+ Wait, configDataResolve,
+ //Form definitions
+ configurationGithubForm,
+ configurationGithubOrgForm,
+ configurationGithubTeamForm,
+ configurationGoogleForm,
+ configurationLdapForm,
+ configurationRadiusForm,
+ configurationSamlForm,
+ ConfigurationJobsForm,
+ ConfigurationSystemForm,
+ ConfigurationUiForm
+ ) {
+ var vm = this;
+
+ var formDefs = {
+ 'github': configurationGithubForm,
+ 'github_org': configurationGithubOrgForm,
+ 'github_team': configurationGithubTeamForm,
+ 'google_oauth': configurationGoogleForm,
+ 'ldap': configurationLdapForm,
+ 'radius': configurationRadiusForm,
+ 'saml': configurationSamlForm,
+ 'jobs': ConfigurationJobsForm,
+ 'system': ConfigurationSystemForm,
+ 'ui': ConfigurationUiForm
+ };
+
+ var populateFromApi = function() {
+ ConfigurationService.getCurrentValues()
+ .then(function(data) {
+ var currentKeys = _.keys(data);
+ _.each(currentKeys, function(key) {
+ if (data[key] !== null && typeof data[key] === 'object') {
+ if (Array.isArray(data[key])) {
+ //handle arrays
+ $scope[key] = ConfigurationUtils.arrayToList(data[key], key);
+ } else {
+ //handle nested objects
+ if(ConfigurationUtils.isEmpty(data[key])) {
+ $scope[key] = '{}';
+ } else {
+ $scope[key] = JSON.stringify(data[key]);
+ }
+ }
+ } else {
+ $scope[key] = data[key];
+ }
+ });
+ $scope.$broadcast('populated', data);
+ });
+ };
+
+ populateFromApi();
+
+ var formTracker = {
+ lastForm: '',
+ currentForm: '',
+ currentAuth: '',
+ setCurrent: function(form) {
+ this.lastForm = this.currentForm;
+ this.currentForm = form;
+ },
+ setCurrentAuth: function(form) {
+ this.currentAuth = form;
+ this.setCurrent(this.currentAuth);
+ },
+ getCurrent: function() {
+ return this.currentForm;
+ },
+ currentFormName: function() {
+ return 'configuration_' + this.currentForm + '_template_form';
+ }
+ };
+
+ // 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;
+
+ function activeTabCheck(setForm) {
+ if(!$scope[formTracker.currentFormName()].$dirty) {
+ active(setForm);
+ } else {
+ var msg = 'You have unsaved changes. Would you like to proceed without saving?';
+ var title = 'Warning: Unsaved Changes';
+ var buttons = [{
+ label: "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: "Save changes",
+ onClick: function() {
+ vm.formSave();
+ $scope[formTracker.currentFormName()].$setPristine();
+ $('#FormModal-dialog').dialog('close');
+ active(setForm);
+ },
+ "class": "btn btn-primary",
+ "id": "formmodal-save-button"
+ }];
+ triggerModal(msg, title, buttons);
+ }
+ }
+
+ function active(setForm) {
+ if (setForm === 'auth') {
+ // Default to 'github' on first load
+ if (formTracker.currentAuth === '') {
+ formTracker.setCurrentAuth('github');
+ } else {
+ // If returning to auth tab reset current form to previously viewed
+ formTracker.setCurrentAuth(formTracker.currentAuth);
+ }
+ } else {
+ formTracker.setCurrent(setForm);
+ }
+ vm.activeTab = setForm;
+ $state.go('configuration', {
+ currentTab: setForm
+ }, {
+ location: true,
+ inherit: false,
+ notify: false,
+ reload: false
+ });
+ }
+
+ var formCancel = function() {
+ if ($scope[formTracker.currentFormName()].$dirty === true) {
+ var msg = 'You have unsaved changes. Would you like to proceed without saving?';
+ var title = 'Warning: Unsaved Changes';
+ var buttons = [{
+ label: "Discard changes",
+ "class": "btn Form-cancelButton",
+ "id": "formmodal-cancel-button",
+ onClick: function() {
+ $('#FormModal-dialog').dialog('close');
+ $state.go('setup');
+ }
+ }, {
+ label: "Save changes",
+ onClick: function() {
+ $scope.formSave();
+ $('#FormModal-dialog').dialog('close');
+ $state.go('setup');
+ },
+ "class": "btn btn-primary",
+ "id": "formmodal-save-button"
+ }];
+ triggerModal(msg, title, buttons);
+ } else {
+ $state.go('setup');
+ }
+ };
+
+
+ $scope.resetValue = function(key) {
+ Wait('start');
+ var payload = {};
+ payload[key] = $scope.configDataResolve[key].default;
+
+ ConfigurationService.patchConfiguration(payload)
+ .then(function(data) {
+ $scope[key] = $scope.configDataResolve[key].default;
+ })
+ .catch(function(error) {
+ ProcessErrors($scope, error, status, formDefs[formTracker.getCurrent()],
+ {
+ hdr: 'Error!',
+ msg: 'There was an error resetting value. Returned status: ' + error.detail
+ });
+
+ })
+ .finally(function() {
+ Wait('stop');
+ });
+ };
+
+ var triggerModal = function(msg, title, buttons) {
+ if ($scope.removeModalReady) {
+ $scope.removeModalReady();
+ }
+ $scope.removeModalReady = $scope.$on('ModalReady', function() {
+ // $('#lookup-save-button').attr('disabled', 'disabled');
+ $('#FormModal-dialog').dialog('open');
+ });
+
+ $('#FormModal-dialog').html(msg);
+
+ CreateDialog({
+ scope: $scope,
+ buttons: buttons,
+ width: 600,
+ height: 200,
+ minWidth: 500,
+ title: title,
+ id: 'FormModal-dialog',
+ resizable: false,
+ callback: 'ModalReady'
+ });
+ };
+
+ function clearApiErrors() {
+ var currentForm = formDefs[formTracker.getCurrent()];
+ for (var fld in currentForm.fields) {
+ if (currentForm.fields[fld].sourceModel) {
+ $scope[currentForm.fields[fld].sourceModel + '_' + currentForm.fields[fld].sourceField + '_api_error'] = '';
+ $('[name="' + currentForm.fields[fld].sourceModel + '_' + currentForm.fields[fld].sourceField + '"]').removeClass('ng-invalid');
+ } else if (currentForm.fields[fld].realName) {
+ $scope[currentForm.fields[fld].realName + '_api_error'] = '';
+ $('[name="' + currentForm.fields[fld].realName + '"]').removeClass('ng-invalid');
+ } else {
+ $scope[fld + '_api_error'] = '';
+ $('[name="' + fld + '"]').removeClass('ng-invalid');
+ }
+ }
+ if (!$scope.$$phase) {
+ $scope.$digest();
+ }
+ }
+
+ // Some dropdowns are listed as "list" type in the API even though they're a dropdown:
+ var multiselectDropdowns = ['AD_HOC_COMMANDS'];
+ var formSave = function() {
+ var saveDeferred = $q.defer();
+ var keys = _.keys(formDefs[formTracker.getCurrent()].fields);
+ var payload = {};
+ clearApiErrors();
+ _.each(keys, function(key) {
+ if($scope.configDataResolve[key].type === 'choice' || multiselectDropdowns.indexOf(key) !== -1) {
+ //Parse dropdowns and dropdowns labeled as lists
+ if($scope[key] === null) {
+ payload[key] = null;
+ } else if($scope[key][0] && $scope[key][0].value !== undefined) {
+ payload[key] = _.map($scope[key], 'value').join(',');
+ } else {
+ payload[key] = $scope[key].value;
+ }
+ } else if($scope.configDataResolve[key].type === 'list' && $scope[key] !== null) {
+ // Parse lists
+ payload[key] = ConfigurationUtils.listToArray($scope[key], key);
+ }
+ else if($scope.configDataResolve[key].type === 'nested object') {
+ if($scope[key] === '') {
+ payload[key] = {};
+ } else {
+ payload[key] = JSON.parse($scope[key]);
+ }
+ }
+ else {
+ // Everything else
+ payload[key] = $scope[key];
+ }
+ });
+
+ Wait('start');
+ ConfigurationService.patchConfiguration(payload)
+ .then(function(data) {
+ saveDeferred.resolve(data);
+ $scope[formTracker.currentFormName()].$setPristine();
+ })
+ .catch(function(error, status) {
+ ProcessErrors($scope, error, status, formDefs[formTracker.getCurrent()],
+ {
+ hdr: 'Error!',
+ msg: 'Failed to save settings. Returned status: ' + status
+ });
+ saveDeferred.reject(error);
+ })
+ .finally(function() {
+ Wait('stop');
+ });
+
+ return saveDeferred.promise;
+ };
+
+ $scope.toggleForm = function(key) {
+ $scope[key] = !$scope[key];
+ Wait('start');
+ var payload = {};
+ payload[key] = $scope[key];
+ ConfigurationService.patchConfiguration(payload)
+ .then(function(results) {
+ //TODO consider updating form values with returned data here
+ })
+ .catch(function(error, status) {
+ //Change back on unsuccessful update
+ $scope[key] = !$scope[key];
+ ProcessErrors($scope, error, status, formDefs[formTracker.getCurrent()],
+ {
+ hdr: 'Error!',
+ msg: 'Failed to save toggle settings. Returned status: ' + error.detail
+ });
+ })
+ .finally(function() {
+ Wait('stop');
+ });
+ };
+
+ var resetAll = function() {
+ Wait('start');
+ ConfigurationService.resetAll()
+ .then(function(results) {
+ populateFromApi();
+ $scope[formTracker.currentFormName].$setPristine();
+ })
+ .catch(function(error) {
+ ProcessErrors($scope, error, status, formDefs[formTracker.getCurrent()],
+ {
+ hdr: 'Error!',
+ msg: 'There was an error resetting values. Returned status: ' + error.detail
+ });
+ })
+ .finally(function() {
+ Wait('stop');
+ });
+ };
+
+ var resetAllConfirm = function() {
+ var buttons = [{
+ label: "Cancel",
+ "class": "btn btn-default",
+ "id": "formmodal-cancel-button",
+ onClick: function() {
+ $('#FormModal-dialog').dialog('close');
+ }
+ }, {
+ label: "Confirm Reset",
+ onClick: function() {
+ resetAll();
+ $('#FormModal-dialog').dialog('close');
+ },
+ "class": "btn btn-primary",
+ "id": "formmodal-reset-button"
+ }];
+ var msg = 'This will reset all configuration values to their factory defaults. Are you sure you want to proceed?';
+ var title = 'Confirm factory reset';
+ triggerModal(msg, title, buttons);
+ };
+
+ angular.extend(vm, {
+ activeTab: activeTab,
+ activeTabCheck: activeTabCheck,
+ currentForm: currentForm,
+ formCancel: formCancel,
+ formTracker: formTracker,
+ formSave: formSave,
+ populateFromApi: populateFromApi,
+ resetAllConfirm: resetAllConfirm,
+ triggerModal: triggerModal
+ });
+ }
+];
diff --git a/awx/ui/client/src/configuration/configuration.partial.html b/awx/ui/client/src/configuration/configuration.partial.html
new file mode 100644
index 0000000000..4a35e4bf10
--- /dev/null
+++ b/awx/ui/client/src/configuration/configuration.partial.html
@@ -0,0 +1,22 @@
+
diff --git a/awx/ui/client/src/configuration/configuration.route.js b/awx/ui/client/src/configuration/configuration.route.js
new file mode 100644
index 0000000000..0026ec9e3c
--- /dev/null
+++ b/awx/ui/client/src/configuration/configuration.route.js
@@ -0,0 +1,56 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+ import {templateUrl} from '../shared/template-url/template-url.factory';
+ import ConfigurationController from './configuration.controller';
+
+// 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',
+
+ ncyBreadcrumb: {
+ label: "Edit Configuration"
+ },
+ controller: ConfigurationController,
+ 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/configuration.service.js b/awx/ui/client/src/configuration/configuration.service.js
new file mode 100644
index 0000000000..86bb4ecdf5
--- /dev/null
+++ b/awx/ui/client/src/configuration/configuration.service.js
@@ -0,0 +1,74 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+export default ['GetBasePath', 'ProcessErrors', '$q', '$http', 'Rest', '$rootScope', '$timeout', 'Wait',
+ function(GetBasePath, ProcessErrors, $q, $http, Rest, $rootScope, $timeout, Wait) {
+ var url = GetBasePath('settings');
+
+ return {
+ getConfigurationOptions: function() {
+ var deferred = $q.defer();
+ Rest.setUrl(url + '/all');
+ Rest.options()
+ .success(function(data) {
+ var returnData = data.actions.PUT;
+ //LICENSE is read only, returning here explicitly for display
+ returnData.LICENSE = data.actions.GET.LICENSE;
+ deferred.resolve(returnData);
+ })
+ .error(function(error) {
+ deferred.reject(error);
+ });
+
+ return deferred.promise;
+ },
+
+ patchConfiguration: function(body) {
+ var deferred = $q.defer();
+
+ Rest.setUrl(url + 'all');
+ Rest.patch(body)
+ .success(function(data) {
+ deferred.resolve(data);
+ })
+ .error(function(error) {
+ deferred.reject(error);
+ });
+
+ return deferred.promise;
+ },
+
+ getCurrentValues: function() {
+ var deferred = $q.defer();
+ Rest.setUrl(url + '/all');
+ Rest.get()
+ .success(function(data) {
+ deferred.resolve(data);
+ })
+ .error(function(error) {
+ deferred.reject(error);
+ });
+
+ return deferred.promise;
+ },
+
+ resetAll: function() {
+ var deferred = $q.defer();
+
+ Rest.setUrl(url + '/all');
+ Rest.delete()
+ .success(function(data) {
+ deferred.resolve(data);
+ })
+ .error(function(error) {
+ deferred.reject(error);
+ });
+
+ return deferred.promise;
+ }
+ };
+ }
+];
diff --git a/awx/ui/client/src/configuration/configurationUtils.service.js b/awx/ui/client/src/configuration/configurationUtils.service.js
new file mode 100644
index 0000000000..ba6069e3f3
--- /dev/null
+++ b/awx/ui/client/src/configuration/configurationUtils.service.js
@@ -0,0 +1,67 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+export default [
+ function() {
+
+ return {
+ listToArray: function(input, key) {
+ if (input.indexOf('\n') !== -1) {
+ //Parse multiline input
+ return input.replace(/^\s+|\s+$/g, "").split('\n');
+ } else {
+ return input.replace(/^\s+|\s+$/g, "").split(/\s*,\s*/);
+ }
+ },
+
+ arrayToList: function(input, key) {
+ var multiLineInput = false;
+ _.each(input, function(statement) {
+ if (statement.indexOf(',') !== -1) {
+ multiLineInput = true;
+ }
+ });
+ if (multiLineInput === false) {
+ return input.join(', ');
+ } else {
+ return input.join('\n');
+ }
+ },
+
+ isEmpty: function(map) {
+ for (var key in map) {
+ if (map.hasOwnProperty(key)) {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ formatPlaceholder: function(input, key) {
+ if(input !== null && typeof input === 'object') {
+ if(Array.isArray(input)) {
+ var multiLineInput = false;
+ _.each(input, function(statement) {
+ if(statement.indexOf(',') !== -1) {
+ multiLineInput = true;
+ }
+ });
+ if(multiLineInput === false) {
+ return input.join(', ');
+ } else {
+ return input.join('\n');
+ }
+ } else {
+ return JSON.stringify(input);
+ }
+ } else {
+ return input;
+ }
+ }
+
+ };
+ }
+];
diff --git a/awx/ui/client/src/configuration/jobs-form/configuration-jobs.controller.js b/awx/ui/client/src/configuration/jobs-form/configuration-jobs.controller.js
new file mode 100644
index 0000000000..b0b4631adf
--- /dev/null
+++ b/awx/ui/client/src/configuration/jobs-form/configuration-jobs.controller.js
@@ -0,0 +1,94 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+export default [
+ '$scope',
+ '$state',
+ '$timeout',
+ 'ConfigurationJobsForm',
+ 'ConfigurationService',
+ 'ConfigurationUtils',
+ 'CreateSelect2',
+ 'GenerateForm',
+ function(
+ $scope,
+ $state,
+ $timeout,
+ ConfigurationJobsForm,
+ ConfigurationService,
+ ConfigurationUtils,
+ CreateSelect2,
+ GenerateForm
+ ) {
+ var jobsVm = this;
+ var generator = GenerateForm;
+ var form = ConfigurationJobsForm;
+ $scope.$parent.AD_HOC_COMMANDS_options = [];
+ _.each($scope.$parent.configDataResolve.AD_HOC_COMMANDS.default, function(command) {
+ $scope.$parent.AD_HOC_COMMANDS_options.push({
+ name: command,
+ label: command,
+ value: command
+ });
+ });
+
+ var keys = _.keys(form.fields);
+ _.each(keys, function(key) {
+ addFieldInfo(form, key);
+ });
+
+ function addFieldInfo(form, key) {
+ _.extend(form.fields[key], {
+ awPopOver: $scope.$parent.configDataResolve[key].help_text,
+ label: $scope.$parent.configDataResolve[key].label,
+ name: key,
+ toggleSource: key,
+ dataPlacement: 'top',
+ dataTitle: $scope.$parent.configDataResolve[key].label
+ });
+ }
+
+ generator.inject(form, {
+ id: 'configure-jobs-form',
+ mode: 'edit',
+ scope: $scope.$parent,
+ related: false
+ });
+
+ // Flag to avoid re-rendering and breaking Select2 dropdowns on tab switching
+ var dropdownRendered = false;
+
+ $scope.$on('populated', function() {
+ var opts = [];
+ _.each(ConfigurationUtils.listToArray($scope.$parent.AD_HOC_COMMANDS), function(command) {
+ opts.push({
+ id: command,
+ text: command
+ });
+ });
+
+ if(!dropdownRendered) {
+ dropdownRendered = true;
+ CreateSelect2({
+ element: '#configuration_jobs_template_AD_HOC_COMMANDS',
+ multiple: true,
+ placeholder: 'Select commands',
+ opts: opts
+ });
+ }
+
+ });
+ // 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);
+
+ angular.extend(jobsVm, {
+ });
+
+ }
+];
diff --git a/awx/ui/client/src/configuration/jobs-form/configuration-jobs.form.js b/awx/ui/client/src/configuration/jobs-form/configuration-jobs.form.js
new file mode 100644
index 0000000000..d4a80ad056
--- /dev/null
+++ b/awx/ui/client/src/configuration/jobs-form/configuration-jobs.form.js
@@ -0,0 +1,67 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+ export default function() {
+ return {
+ showHeader: false,
+ name: 'configuration_jobs_template',
+ showActions: true,
+
+ fields: {
+ AD_HOC_COMMANDS: {
+ type: 'select',
+ ngOptions: 'command.label for command in AD_HOC_COMMANDS_options track by command.value',
+ reset: 'AD_HOC_COMMANDS',
+ multiSelect: true
+ },
+ STDOUT_MAX_BYTES_DISPLAY: {
+ type: 'number',
+ reset: 'STDOUT_MAX_BYTES_DISPLAY'
+ },
+ AWX_PROOT_BASE_PATH: {
+ type: 'text',
+ reset: 'AWX_PROOT_BASE_PATH',
+ },
+ SCHEDULE_MAX_JOBS: {
+ type: 'number',
+ reset: 'SCHEDULE_MAX_JOBS'
+ },
+ AWX_PROOT_SHOW_PATHS: {
+ type: 'textarea',
+ reset: 'AWX_PROOT_SHOW_PATHS',
+ rows: 6
+ },
+ AWX_ANSIBLE_CALLBACK_PLUGINS: {
+ type: 'textarea',
+ reset: 'AWX_ANSIBLE_CALLBACK_PLUGINS',
+ rows: 6
+ },
+ AWX_PROOT_HIDE_PATHS: {
+ type: 'textarea',
+ reset: 'AWX_PROOT_HIDE_PATHS',
+ rows: 6
+ },
+ AWX_PROOT_ENABLED: {
+ type: 'toggleSwitch',
+ }
+ },
+
+ buttons: {
+ reset: {
+ ngClick: 'vm.resetAllConfirm()',
+ label: 'Reset All',
+ class: 'Form-button--left Form-cancelButton'
+ },
+ cancel: {
+ ngClick: 'vm.formCancel()',
+ },
+ save: {
+ ngClick: 'vm.formSave()',
+ // ngDisabled: true
+ }
+ }
+ };
+ }
diff --git a/awx/ui/client/src/configuration/jobs-form/configuration-jobs.partial.html b/awx/ui/client/src/configuration/jobs-form/configuration-jobs.partial.html
new file mode 100644
index 0000000000..20b85355df
--- /dev/null
+++ b/awx/ui/client/src/configuration/jobs-form/configuration-jobs.partial.html
@@ -0,0 +1,10 @@
+
+
diff --git a/awx/ui/client/src/configuration/main.js b/awx/ui/client/src/configuration/main.js
new file mode 100644
index 0000000000..554690aa71
--- /dev/null
+++ b/awx/ui/client/src/configuration/main.js
@@ -0,0 +1,44 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import configurationService from './configuration.service';
+import ConfigurationUtils from './configurationUtils.service';
+import configurationRoute from './configuration.route';
+import configurationController from './configuration.controller.js';
+
+// Import forms
+//authorization sub-forms
+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 configurationRadiusForm from './auth-form/sub-forms/auth-radius.form.js';
+import configurationSamlForm from './auth-form/sub-forms/auth-saml.form';
+
+import configurationJobsForm from './jobs-form/configuration-jobs.form';
+import configurationSystemForm from './system-form/configuration-system.form';
+import configurationUiForm from './ui-form/configuration-ui.form';
+
+export default
+angular.module('configuration', [])
+ .controller('ConfigurationController', configurationController)
+ //auth forms
+ .factory('configurationGithubForm', configurationGithubForm)
+ .factory('configurationGithubOrgForm', configurationGithubOrgForm)
+ .factory('configurationGithubTeamForm', configurationGithubTeamForm)
+ .factory('configurationGoogleForm', configurationGoogleForm)
+ .factory('configurationLdapForm', configurationLdapForm)
+ .factory('configurationRadiusForm', configurationRadiusForm)
+ .factory('configurationSamlForm', configurationSamlForm)
+ .factory('ConfigurationJobsForm', configurationJobsForm)
+ .factory('ConfigurationSystemForm', configurationSystemForm)
+ .factory('ConfigurationUiForm', configurationUiForm)
+ .factory('ConfigurationUtils', ConfigurationUtils)
+ .service('ConfigurationService', configurationService)
+ .run(['$stateExtender', function($stateExtender) {
+ $stateExtender.addState(configurationRoute);
+ }]);
diff --git a/awx/ui/client/src/configuration/system-form/configuration-system.controller.js b/awx/ui/client/src/configuration/system-form/configuration-system.controller.js
new file mode 100644
index 0000000000..99091e7af1
--- /dev/null
+++ b/awx/ui/client/src/configuration/system-form/configuration-system.controller.js
@@ -0,0 +1,68 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+export default [
+ '$scope', '$state', 'AngularCodeMirror', 'ConfigurationSystemForm', 'ConfigurationService', 'ConfigurationUtils', 'GenerateForm', 'ParseTypeChange',
+ function(
+ $scope, $state, AngularCodeMirror, ConfigurationSystemForm, ConfigurationService, ConfigurationUtils, GenerateForm, ParseTypeChange
+ ) {
+ var systemVm = this;
+ var generator = GenerateForm;
+ var form = ConfigurationSystemForm;
+ var keys = _.keys(form.fields);
+
+ _.each(keys, function(key) {
+ addFieldInfo(form, key);
+ });
+
+ function addFieldInfo(form, key) {
+ _.extend(form.fields[key], {
+ awPopOver: $scope.$parent.configDataResolve[key].help_text,
+ label: $scope.$parent.configDataResolve[key].label,
+ name: key,
+ toggleSource: key,
+ dataPlacement: 'top',
+ dataTitle: $scope.$parent.configDataResolve[key].label
+ });
+ }
+
+ generator.inject(form, {
+ id: 'configure-system-form',
+ mode: 'edit',
+ scope: $scope.$parent,
+ related: true
+ });
+
+
+ $scope.$on('populated', function() {
+
+ // var fld = 'LICENSE';
+ // var readOnly = true;
+ // $scope.$parent[fld + 'codeMirror'] = AngularCodeMirror(readOnly);
+ // $scope.$parent[fld + 'codeMirror'].addModes($AnsibleConfig.variable_edit_modes);
+ // $scope.$parent[fld + 'codeMirror'].showTextArea({
+ // scope: $scope.$parent,
+ // model: fld,
+ // element: "configuration_system_template_LICENSE",
+ // lineNumbers: true,
+ // mode: 'json',
+ // });
+
+ $scope.$parent.parseType = 'json';
+ ParseTypeChange({
+ scope: $scope.$parent,
+ variable: 'LICENSE',
+ parse_variable: 'parseType',
+ field_id: 'configuration_system_template_LICENSE',
+ readOnly: true
+ });
+ });
+
+ angular.extend(systemVm, {
+
+ });
+ }
+];
diff --git a/awx/ui/client/src/configuration/system-form/configuration-system.form.js b/awx/ui/client/src/configuration/system-form/configuration-system.form.js
new file mode 100644
index 0000000000..c7ef1a2cd8
--- /dev/null
+++ b/awx/ui/client/src/configuration/system-form/configuration-system.form.js
@@ -0,0 +1,55 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+export default function() {
+ return {
+ showHeader: false,
+ name: 'configuration_system_template',
+ showActions: true,
+
+ fields: {
+ TOWER_URL_BASE: {
+ type: 'text',
+ reset: 'TOWER_URL_BASE',
+ addRequired: false,
+ editRequird: false,
+ },
+ TOWER_ADMIN_ALERTS: {
+ type: 'toggleSwitch',
+ },
+ ACTIVITY_STREAM_ENABLED: {
+ type: 'toggleSwitch',
+ },
+ ACTIVITY_STREAM_ENABLED_FOR_INVENTORY_SYNC: {
+ type: 'toggleSwitch'
+ },
+ ORG_ADMINS_CAN_SEE_ALL_USERS: {
+ type: 'toggleSwitch',
+ },
+ LICENSE: {
+ type: 'textarea',
+ rows: 6,
+ codeMirror: true,
+ class: 'Form-textAreaLabel Form-formGroup--fullWidth'
+ }
+ },
+
+ buttons: {
+ reset: {
+ ngClick: 'vm.resetAllConfirm()',
+ label: 'Reset All',
+ class: 'Form-button--left Form-cancelButton'
+ },
+ cancel: {
+ ngClick: 'vm.formCancel()',
+ },
+ save: {
+ ngClick: 'vm.formSave()',
+ ngDisabled: true
+ }
+ }
+ };
+}
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
new file mode 100644
index 0000000000..7ff91b4441
--- /dev/null
+++ b/awx/ui/client/src/configuration/system-form/configuration-system.partial.html
@@ -0,0 +1,10 @@
+
+
diff --git a/awx/ui/client/src/configuration/ui-form/configuration-ui.controller.js b/awx/ui/client/src/configuration/ui-form/configuration-ui.controller.js
new file mode 100644
index 0000000000..d50239fce5
--- /dev/null
+++ b/awx/ui/client/src/configuration/ui-form/configuration-ui.controller.js
@@ -0,0 +1,92 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+ export default [
+ '$scope',
+ '$state',
+ '$timeout',
+ 'ConfigurationUiForm',
+ 'ConfigurationService',
+ 'CreateSelect2',
+ 'GenerateForm',
+ function(
+ $scope,
+ $state,
+ $timeout,
+ ConfigurationUiForm,
+ ConfigurationService,
+ CreateSelect2,
+ GenerateForm
+ ) {
+ var uiVm = this;
+ var generator = GenerateForm;
+ var form = ConfigurationUiForm;
+
+
+ var keys = _.keys(form.fields);
+ _.each(keys, function(key) {
+ if($scope.$parent.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({
+ name: choice[0],
+ label: choice[1],
+ value: choice[0]
+ });
+ });
+ }
+ addFieldInfo(form, key);
+ });
+
+ function addFieldInfo(form, key) {
+ _.extend(form.fields[key], {
+ awPopOver: $scope.$parent.configDataResolve[key].help_text,
+ label: $scope.$parent.configDataResolve[key].label,
+ name: key,
+ toggleSource: key,
+ dataPlacement: 'top',
+ dataTitle: $scope.$parent.configDataResolve[key].label
+ });
+ }
+
+ generator.inject(form, {
+ id: 'configure-ui-form',
+ mode: 'edit',
+ scope: $scope.$parent,
+ related: true
+ });
+
+ // Flag to avoid re-rendering and breaking Select2 dropdowns on tab switching
+ var dropdownRendered = false;
+
+ $scope.$on('populated', function(){
+ if(!dropdownRendered) {
+ dropdownRendered = true;
+ CreateSelect2({
+ element: '#configuration_ui_template_PENDO_TRACKING_STATE',
+ multiple: false,
+ placeholder: 'Select commands',
+ opts: [{
+ id: $scope.$parent.PENDO_TRACKING_STATE,
+ text: $scope.$parent.PENDO_TRACKING_STATE
+ }]
+ });
+ // 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_ui_template_form.$setPristine();
+ }, 1000);
+ }
+ });
+
+ angular.extend(uiVm, {
+
+ });
+
+ }
+ ];
diff --git a/awx/ui/client/src/configuration/ui-form/configuration-ui.form.js b/awx/ui/client/src/configuration/ui-form/configuration-ui.form.js
new file mode 100644
index 0000000000..6b222f5805
--- /dev/null
+++ b/awx/ui/client/src/configuration/ui-form/configuration-ui.form.js
@@ -0,0 +1,37 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+ export default function() {
+ return {
+ showHeader: false,
+ name: 'configuration_ui_template',
+ showActions: true,
+
+ fields: {
+ PENDO_TRACKING_STATE: {
+ type: 'select',
+ ngChange: 'changedPendo()',
+ ngOptions: 'choice.label for choice in PENDO_TRACKING_STATE_options track by choice.value',
+ reset: 'PENDO_TRACKING_STATE'
+ },
+ },
+
+ buttons: {
+ reset: {
+ ngClick: 'vm.resetAllConfirm()',
+ label: 'Reset All',
+ class: 'Form-button--left Form-cancelButton'
+ },
+ cancel: {
+ ngClick: 'vm.formCancel()',
+ },
+ save: {
+ ngClick: 'vm.formSave()',
+ ngDisabled: true
+ }
+ }
+ };
+ }
diff --git a/awx/ui/client/src/configuration/ui-form/configuration-ui.partial.html b/awx/ui/client/src/configuration/ui-form/configuration-ui.partial.html
new file mode 100644
index 0000000000..d880be7025
--- /dev/null
+++ b/awx/ui/client/src/configuration/ui-form/configuration-ui.partial.html
@@ -0,0 +1,10 @@
+
+
diff --git a/awx/ui/client/src/setup-menu/setup-menu.partial.html b/awx/ui/client/src/setup-menu/setup-menu.partial.html
index f8f6276cec..8281a68a29 100644
--- a/awx/ui/client/src/setup-menu/setup-menu.partial.html
+++ b/awx/ui/client/src/setup-menu/setup-menu.partial.html
@@ -49,6 +49,12 @@
View and edit your license information.
+
+ Configure Tower
+
+ Edit Tower's configuration.
+
+
About Tower
diff --git a/awx/ui/client/src/shared/Utilities.js b/awx/ui/client/src/shared/Utilities.js
index 5a4d0ce4dc..9bc18dfd13 100644
--- a/awx/ui/client/src/shared/Utilities.js
+++ b/awx/ui/client/src/shared/Utilities.js
@@ -228,7 +228,7 @@ angular.module('Utilities', ['RestServices', 'Utilities', 'sanitizeFilter'])
scope[field + '_api_error'] = data[form.fields[field]][0];
//scope[form.name + '_form'][form.fields[field].realName].$setValidity('apiError', false);
$('[name="' + form.fields[field].realName + '"]').addClass('ng-invalid');
- $('[name="' + form.fields[field].realName + '"]').ScrollTo({ "onlyIfOutside": true, "offsetTop": 100 });
+ $('html, body').animate({scrollTop: $('[name="' + form.fields[field].realName + '"]').offset().top}, 0);
fieldErrors = true;
}
}
@@ -246,7 +246,7 @@ angular.module('Utilities', ['RestServices', 'Utilities', 'sanitizeFilter'])
scope[field + '_api_error'] = data[field][0];
//scope[form.name + '_form'][field].$setValidity('apiError', false);
$('[name="' + field + '"]').addClass('ng-invalid');
- $('[name="' + field + '"]').ScrollTo({ "onlyIfOutside": true, "offsetTop": 100 });
+ $('html, body').animate({scrollTop: $('[name="' + field + '"]').offset().top}, 0);
fieldErrors = true;
}
}
diff --git a/awx/ui/client/src/shared/form-generator.js b/awx/ui/client/src/shared/form-generator.js
index f3ea41ea07..6211a95ec0 100644
--- a/awx/ui/client/src/shared/form-generator.js
+++ b/awx/ui/client/src/shared/form-generator.js
@@ -478,6 +478,12 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
let cls = action["class"] || "";
html += `${action.label}`;
}
+
+ if(field.reset) {
+ var resetValue = "'" + field.reset+ "'";
+ html+= `Reset`;
+ }
+
html += "\n\t\n";
}
return html;
@@ -535,6 +541,16 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
html += (field.awFeature) ? "aw-feature=\"" + field.awFeature + "\" " : "";
html += ">\n";
+ // toggle switches
+ if(field.type === 'toggleSwitch') {
+ html += label();
+ html += `
`;
+ }
+
//text fields
if (field.type === 'text' || field.type === 'password' || field.type === 'email') {
html += label();