diff --git a/lib/settings/local_settings.py.25142.1368115546.78.tmp b/lib/settings/local_settings.py.25142.1368115546.78.tmp new file mode 100644 index 0000000000..0eb3e522ba --- /dev/null +++ b/lib/settings/local_settings.py.25142.1368115546.78.tmp @@ -0,0 +1,97 @@ +# Local Django settings for Ansible Commander project. + +# Copyright (c) 2013 AnsibleWorks, Inc. +# +# This file is part of Ansible Commander. +# +# Ansible Commander is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# Ansible Commander is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible Commander. If not, see . + + +from defaults import * + +ADMINS = ( + # ('Your Name', 'your_email@domain.com'), +) + +MANAGERS = ADMINS + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': 'acom', + 'USER': 'ansible_commander', + 'PASSWORD': 'gateIsDown', + 'HOST': '', + 'PORT': '', + } +} + +if 'test' in sys.argv or 'ACOM_TEST_DATABASE_NAME' in os.environ: + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'acom.sqlite3'), + # Test database cannot be :memory: for celery/inventory tests to work. + 'TEST_NAME': os.path.join(BASE_DIR, 'acom_test.sqlite3'), + } + } + +# Absolute filesystem path to the directory to host projects (with playbooks). +# This directory should not be web-accessible. +PROJECTS_ROOT = os.path.join(BASE_DIR, 'projects') + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# On Unix systems, a value of None will cause Django to use the same +# timezone as the operating system. +# If running in a Windows environment this must be set to the same as your +# system time zone. +TIME_ZONE = 'America/New_York' + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = 'en-us' + +# SECURITY WARNING: keep the secret key used in production secret! +# Hardcoded values can leak through source control. Consider loading +# the secret key from an environment variable or a file instead. +SECRET_KEY = 'p7z7g1ql4%6+(6nlebb6hdk7sd^&fnjpal308%n%+p^_e6vo1y' + +# Email address that error messages come from. +SERVER_EMAIL = 'root@localhost' + +# The email backend to use. For possible shortcuts see django.core.mail. +# The default is to use the SMTP backend. +# Third-party backends can be specified by providing a Python path +# to a module that defines an EmailBackend class. +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' + +# Host for sending email. +EMAIL_HOST = 'localhost' + +# Port for sending email. +EMAIL_PORT = 25 + +# Optional SMTP authentication information for EMAIL_HOST. +EMAIL_HOST_USER = '' +EMAIL_HOST_PASSWORD = '' +EMAIL_USE_TLS = False + +# Default email address to use for various automated correspondence from +# the site managers. +DEFAULT_FROM_EMAIL = 'webmaster@localhost' + +# Subject-line prefix for email messages send with django.core.mail.mail_admins +# or ...mail_managers. Make sure to include the trailing space. +EMAIL_SUBJECT_PREFIX = '[ACOM] ' diff --git a/lib/settings/local_settings.py.26911.1368115716.57.tmp b/lib/settings/local_settings.py.26911.1368115716.57.tmp new file mode 100644 index 0000000000..0eb3e522ba --- /dev/null +++ b/lib/settings/local_settings.py.26911.1368115716.57.tmp @@ -0,0 +1,97 @@ +# Local Django settings for Ansible Commander project. + +# Copyright (c) 2013 AnsibleWorks, Inc. +# +# This file is part of Ansible Commander. +# +# Ansible Commander is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# Ansible Commander is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible Commander. If not, see . + + +from defaults import * + +ADMINS = ( + # ('Your Name', 'your_email@domain.com'), +) + +MANAGERS = ADMINS + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': 'acom', + 'USER': 'ansible_commander', + 'PASSWORD': 'gateIsDown', + 'HOST': '', + 'PORT': '', + } +} + +if 'test' in sys.argv or 'ACOM_TEST_DATABASE_NAME' in os.environ: + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'acom.sqlite3'), + # Test database cannot be :memory: for celery/inventory tests to work. + 'TEST_NAME': os.path.join(BASE_DIR, 'acom_test.sqlite3'), + } + } + +# Absolute filesystem path to the directory to host projects (with playbooks). +# This directory should not be web-accessible. +PROJECTS_ROOT = os.path.join(BASE_DIR, 'projects') + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# On Unix systems, a value of None will cause Django to use the same +# timezone as the operating system. +# If running in a Windows environment this must be set to the same as your +# system time zone. +TIME_ZONE = 'America/New_York' + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = 'en-us' + +# SECURITY WARNING: keep the secret key used in production secret! +# Hardcoded values can leak through source control. Consider loading +# the secret key from an environment variable or a file instead. +SECRET_KEY = 'p7z7g1ql4%6+(6nlebb6hdk7sd^&fnjpal308%n%+p^_e6vo1y' + +# Email address that error messages come from. +SERVER_EMAIL = 'root@localhost' + +# The email backend to use. For possible shortcuts see django.core.mail. +# The default is to use the SMTP backend. +# Third-party backends can be specified by providing a Python path +# to a module that defines an EmailBackend class. +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' + +# Host for sending email. +EMAIL_HOST = 'localhost' + +# Port for sending email. +EMAIL_PORT = 25 + +# Optional SMTP authentication information for EMAIL_HOST. +EMAIL_HOST_USER = '' +EMAIL_HOST_PASSWORD = '' +EMAIL_USE_TLS = False + +# Default email address to use for various automated correspondence from +# the site managers. +DEFAULT_FROM_EMAIL = 'webmaster@localhost' + +# Subject-line prefix for email messages send with django.core.mail.mail_admins +# or ...mail_managers. Make sure to include the trailing space. +EMAIL_SUBJECT_PREFIX = '[ACOM] ' diff --git a/lib/settings/local_settings.py.28656.1368116066.38.tmp b/lib/settings/local_settings.py.28656.1368116066.38.tmp new file mode 100644 index 0000000000..0eb3e522ba --- /dev/null +++ b/lib/settings/local_settings.py.28656.1368116066.38.tmp @@ -0,0 +1,97 @@ +# Local Django settings for Ansible Commander project. + +# Copyright (c) 2013 AnsibleWorks, Inc. +# +# This file is part of Ansible Commander. +# +# Ansible Commander is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# Ansible Commander is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible Commander. If not, see . + + +from defaults import * + +ADMINS = ( + # ('Your Name', 'your_email@domain.com'), +) + +MANAGERS = ADMINS + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': 'acom', + 'USER': 'ansible_commander', + 'PASSWORD': 'gateIsDown', + 'HOST': '', + 'PORT': '', + } +} + +if 'test' in sys.argv or 'ACOM_TEST_DATABASE_NAME' in os.environ: + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'acom.sqlite3'), + # Test database cannot be :memory: for celery/inventory tests to work. + 'TEST_NAME': os.path.join(BASE_DIR, 'acom_test.sqlite3'), + } + } + +# Absolute filesystem path to the directory to host projects (with playbooks). +# This directory should not be web-accessible. +PROJECTS_ROOT = os.path.join(BASE_DIR, 'projects') + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# On Unix systems, a value of None will cause Django to use the same +# timezone as the operating system. +# If running in a Windows environment this must be set to the same as your +# system time zone. +TIME_ZONE = 'America/New_York' + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = 'en-us' + +# SECURITY WARNING: keep the secret key used in production secret! +# Hardcoded values can leak through source control. Consider loading +# the secret key from an environment variable or a file instead. +SECRET_KEY = 'p7z7g1ql4%6+(6nlebb6hdk7sd^&fnjpal308%n%+p^_e6vo1y' + +# Email address that error messages come from. +SERVER_EMAIL = 'root@localhost' + +# The email backend to use. For possible shortcuts see django.core.mail. +# The default is to use the SMTP backend. +# Third-party backends can be specified by providing a Python path +# to a module that defines an EmailBackend class. +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' + +# Host for sending email. +EMAIL_HOST = 'localhost' + +# Port for sending email. +EMAIL_PORT = 25 + +# Optional SMTP authentication information for EMAIL_HOST. +EMAIL_HOST_USER = '' +EMAIL_HOST_PASSWORD = '' +EMAIL_USE_TLS = False + +# Default email address to use for various automated correspondence from +# the site managers. +DEFAULT_FROM_EMAIL = 'webmaster@localhost' + +# Subject-line prefix for email messages send with django.core.mail.mail_admins +# or ...mail_managers. Make sure to include the trailing space. +EMAIL_SUBJECT_PREFIX = '[ACOM] ' diff --git a/lib/ui/static/css/ansible-ui.css b/lib/ui/static/css/ansible-ui.css index 39aa16206d..f13456035d 100644 --- a/lib/ui/static/css/ansible-ui.css +++ b/lib/ui/static/css/ansible-ui.css @@ -140,9 +140,7 @@ } .footer-navigation { - border-top: 1px solid #000; - margin: 20px 0 20px 0; - padding-top: 20px; + margin: 10px 0 10px 0; } .lookup-navigation { @@ -151,8 +149,7 @@ } .related-footer { - padding-bottom: 0; - margin: 20px 0 0 0; + margin: 10px 0 0 0; } .page-size { diff --git a/lib/ui/static/js/app.js b/lib/ui/static/js/app.js index 65fa82674b..58abf871bc 100644 --- a/lib/ui/static/js/app.js +++ b/lib/ui/static/js/app.js @@ -19,7 +19,7 @@ angular.module('ansible', [ 'ListGenerator', 'AWToolTip', 'PromptDialog', - 'APIDefaults', + 'ApiLoader', 'RelatedSearchHelper', 'RelatedPaginateHelper', 'SearchHelper', @@ -38,7 +38,10 @@ angular.module('ansible', [ 'GroupListDefinition', 'TeamsListDefinition', 'TeamFormDefinition', - 'TeamHelper' + 'TeamHelper', + 'CredentialListDefinition', + 'CredentialFormDefinition', + 'LookUpHelper' ]) .config(['$routeProvider', function($routeProvider) { $routeProvider. @@ -98,6 +101,21 @@ angular.module('ansible', [ when('/teams/:id', { templateUrl: urlPrefix + 'partials/teams.html', controller: TeamsEdit }). + + when('/teams/:id/users', { templateUrl: urlPrefix + 'partials/teams.html', + controller: UsersList }). + + when('/teams/:organization_id/users/:id', { templateUrl: urlPrefix + 'partials/teams.html', + controller: UsersEdit }). + + when('/credentials', { templateUrl: urlPrefix + 'partials/credentials.html', + controller: CredentialsList }). + + when('/credentials/add', { templateUrl: urlPrefix + 'partials/credentials.html', + controller: CredentialsAdd }). + + when('/credentials/:id', { templateUrl: urlPrefix + 'partials/credentials.html', + controller: CredentialsEdit }). when('/users', { templateUrl: urlPrefix + 'partials/users.html', controller: UsersList }). @@ -114,8 +132,11 @@ angular.module('ansible', [ otherwise({redirectTo: '/'}); }]) - .run(function($rootScope, $location, Authorization) { + .run(['$rootScope', '$location', 'Authorization','LoadBasePaths', + function($rootScope, $location, Authorization, LoadBasePaths) { + LoadBasePaths(); + $rootScope.breadcrumbs = new Array(); $rootScope.crumbCache = new Array(); @@ -141,11 +162,10 @@ angular.module('ansible', [ // If browser refresh, activate the correct tab var base = ($location.path().replace(/^\//,'').split('/')[0]); if (base == '') { - console.log(base); base = 'organizations'; $location.path('/organizations'); } else { $('.nav-tabs a[href="#' + base + '"]').tab('show'); } - }); + }]); diff --git a/lib/ui/static/js/controllers/Credentials.js b/lib/ui/static/js/controllers/Credentials.js new file mode 100644 index 0000000000..e7d71b3472 --- /dev/null +++ b/lib/ui/static/js/controllers/Credentials.js @@ -0,0 +1,358 @@ +/************************************ + * Copyright (c) 2013 AnsibleWorks, Inc. + * + * + * Credentials.js + * + * Controller functions for the Credential model. + * + */ + +'use strict'; + +function CredentialsList ($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, CredentialList, + GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller, + ClearScope, ProcessErrors, GetBasePath) +{ + ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior + //scope. + var list = CredentialList; + var defaultUrl = GetBasePath('credentials'); + var view = GenerateList; + var paths = $location.path().replace(/^\//,'').split('/'); + var mode = (paths[0] == 'credentials') ? 'edit' : 'select'; // if base path 'credentials', we're here to add/edit + var scope = view.inject(CredentialList, { mode: mode }); // Inject our view + scope.selected = []; + + SearchInit({ scope: scope, set: 'credentials', list: list, url: defaultUrl }); + PaginateInit({ scope: scope, list: list, url: defaultUrl }); + scope.search(list.iterator); + + LoadBreadCrumbs(); + + scope.addCredential = function() { + $location.path($location.path() + '/add'); + } + + scope.editCredential = function(id) { + $location.path($location.path() + '/' + id); + } + + scope.deleteCredential = function(id, name) { + + var action = function() { + var url = defaultUrl + id + '/'; + Rest.setUrl(url); + Rest.delete() + .success( function(data, status, headers, config) { + $('#prompt-modal').modal('hide'); + scope.search(list.iterator); + }) + .error( function(data, status, headers, config) { + $('#prompt-modal').modal('hide'); + ProcessErrors(scope, data, status, null, + { hdr: 'Error!', msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status }); + }); + }; + + Prompt({ hdr: 'Delete', + body: 'Are you sure you want to delete ' + name + '?', + action: action + }); + } + + scope.finishSelection = function() { + Rest.setUrl(GetBasePath('base') + $location.path() + '/'); // We're assuming the path matches the api path. + // Will this always be true?? + scope.queue = []; + + scope.$on('callFinished', function() { + // We call the API for each selected user. We need to hang out until all the api + // calls are finished. + if (scope.queue.length == scope.selected.length) { + // All the api calls finished + $('input[type="checkbox"]').prop("checked",false); + scope.selected = []; + var errors = 0; + for (var i=0; i < scope.queue.length; i++) { + if (scope.queue[i].result == 'error') { + errors++; + // there is no way to know which user raised the error. no data comes + // back from the api call. + // $('td.username-column').each(function(index) { + // if ($(this).text() == scope.queue[i].username) { + // $(this).addClass("error"); + // } + // }); + } + } + if (errors > 0) { + Alert('Error', 'There was an error while adding one or more of the selected Credentials.'); + } + else { + ReturnToCaller(1); + } + } + }); + + if (scope.selected.length > 0 ) { + var credential = null; + for (var i=0; i < scope.selected.length; i++) { + for (var j=0; j < scope.Credentials.length; j++) { + if (scope.credentials[j].id == scope.selected[i]) { + credential = scope.credentials[j]; + } + } + if (credential !== null) { + Rest.post(credential) + .success( function(data, status, headers, config) { + scope.queue.push({ result: 'success', data: data, status: status }); + scope.$emit('callFinished'); + }) + .error( function(data, status, headers, config) { + scope.queue.push({ result: 'error', data: data, status: status, headers: headers }); + scope.$emit('callFinished'); + }); + } + } + } + else { + ReturnToCaller(); + } + } + + scope.toggle_credential = function(idx) { + if (scope.selected.indexOf(idx) > -1) { + scope.selected.splice(scope.selected.indexOf(idx),1); + } + else { + scope.selected.push(idx); + } + } +} + +CredentialsList.$inject = [ '$scope', '$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'CredentialList', 'GenerateList', + 'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'ProcessErrors', + 'GetBasePath']; + + +function CredentialsAdd ($scope, $rootScope, $compile, $location, $log, $routeParams, CredentialForm, + GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, + GenerateList, SearchInit, PaginateInit, LookUpInit, UserList, TeamList, GetBasePath) +{ + ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior + //scope. + + // Inject dynamic view + var defaultUrl = GetBasePath('credentials'); + var form = CredentialForm; + var generator = GenerateForm; + var scope = generator.inject(form, {mode: 'add', related: false}); + generator.reset(); + LoadBreadCrumbs(); + + LookUpInit({ + scope: scope, + form: form, + current_item: ($routeParams.user_id) ? $routeParams.user_id : null, + list: UserList, + field: 'user' + }); + + LookUpInit({ + scope: scope, + form: form, + current_item: ($routeParams.team_id) ? $routeParams.team_id : null, + list: TeamList, + field: 'team' + }); + + // Save + scope.formSave = function() { + Rest.setUrl(defaultUrl); + var data = {} + for (var fld in form.fields) { + data[fld] = scope[fld]; + } + Rest.post(data) + .success( function(data, status, headers, config) { + ReturnToCaller(); + }) + .error( function(data, status, headers, config) { + ProcessErrors(scope, data, status, form, + { hdr: 'Error!', msg: 'Failed to add new Credential. Post returned status: ' + status }); + }); + }; + + // Reset + scope.formReset = function() { + // Defaults + generator.reset(); + }; + + // Password change + scope.clearPWConfirm = function(fld) { + // If password value changes, make sure password_confirm must be re-entered + scope[fld] = ''; + scope[form.name + '_form'][fld].$setValidity('awpassmatch', false); + } +} + +CredentialsAdd.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'CredentialForm', 'GenerateForm', + 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', 'ClearScope', 'GenerateList', + 'SearchInit', 'PaginateInit', 'LookUpInit', 'UserList', 'TeamList', 'GetBasePath' ]; + + +function CredentialsEdit ($scope, $rootScope, $compile, $location, $log, $routeParams, CredentialForm, + GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit, + RelatedPaginateInit, ReturnToCaller, ClearScope, LookUpInit, + UserList, TeamList, Prompt, GetBasePath) +{ + ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior + //scope. + + var defaultUrl=GetBasePath('credentials'); + var generator = GenerateForm; + var form = CredentialForm; + var scope = generator.inject(form, {mode: 'edit', related: true}); + generator.reset(); + var base = $location.path().replace(/^\//,'').split('/')[0]; + var master = {}; + var id = $routeParams.id; + var relatedSets = {}; + + LookUpInit({ + scope: scope, + form: form, + current_item: ($routeParams.user_id) ? $routeParams.user_id : null, + list: UserList, + field: 'user' + }); + + LookUpInit({ + scope: scope, + form: form, + current_item: ($routeParams.team_id) ? $routeParams.team_id : null, + list: TeamList, + field: 'team' + }); + + // After Credential is loaded, retrieve each related set and any lookups + scope.$on('credentialLoaded', function() { + for (var set in relatedSets) { + scope.search(relatedSets[set].iterator); + } + }); + + // Retrieve detail record and prepopulate the form + Rest.setUrl(defaultUrl + ':id/'); + Rest.get({ params: {id: id} }) + .success( function(data, status, headers, config) { + LoadBreadCrumbs({ path: '/Credentials/' + id, title: data.name }); + for (var fld in form.fields) { + if (data[fld]) { + scope[fld] = data[fld]; + master[fld] = scope[fld]; + } + if (form.fields[fld].type == 'lookup' && data.summary_fields[form.fields[fld].sourceModel]) { + scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] = + data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField]; + master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] = + scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField]; + } + } + var related = data.related; + for (var set in form.related) { + if (related[set]) { + relatedSets[set] = { url: related[set], iterator: form.related[set].iterator }; + } + } + + // Initialize related search functions. Doing it here to make sure relatedSets object is populated. + RelatedSearchInit({ scope: scope, form: form, relatedSets: relatedSets }); + RelatedPaginateInit({ scope: scope, relatedSets: relatedSets }); + scope.$emit('credentialLoaded'); + }) + .error( function(data, status, headers, config) { + ProcessErrors(scope, data, status, form, + { hdr: 'Error!', msg: 'Failed to retrieve Credential: ' + $routeParams.id + '. GET status: ' + status }); + }); + + // Save changes to the parent + scope.formSave = function() { + Rest.setUrl(defaultUrl + $routeParams.id + '/'); + var data = {} + for (var fld in form.fields) { + data[fld] = scope[fld]; + } + Rest.put(data) + .success( function(data, status, headers, config) { + var base = $location.path().replace(/^\//,'').split('/')[0]; + (base == 'Credentials') ? ReturnToCaller() : ReturnToCaller(1); + }) + .error( function(data, status, headers, config) { + ProcessErrors(scope, data, status, form, + { hdr: 'Error!', msg: 'Failed to update Credential: ' + $routeParams.id + '. PUT status: ' + status }); + }); + }; + + // Cancel + scope.formReset = function() { + generator.reset(); + for (var fld in master) { + scope[fld] = master[fld]; + } + }; + + // Related set: Add button + scope.add = function(set) { + $rootScope.flashMessage = null; + $location.path('/' + base + '/' + $routeParams.id + '/' + set + '/add'); + }; + + // Related set: Edit button + scope.edit = function(set, id, name) { + $rootScope.flashMessage = null; + $location.path('/' + base + '/' + $routeParams.id + '/' + set + '/' + id); + }; + + // Related set: Delete button + scope.delete = function(set, itm_id, name, title) { + $rootScope.flashMessage = null; + + var action = function() { + var url = defaultUrl + id + '/' + set + '/'; + Rest.setUrl(url); + Rest.post({ id: itm_id, disassociate: 1 }) + .success( function(data, status, headers, config) { + $('#prompt-modal').modal('hide'); + scope.search(form.related[set].iterator); + }) + .error( function(data, status, headers, config) { + $('#prompt-modal').modal('hide'); + ProcessErrors(scope, data, status, null, + { hdr: 'Error!', msg: 'Call to ' + url + ' failed. POST returned status: ' + status }); + }); + }; + + Prompt({ hdr: 'Delete', + body: 'Are you sure you want to remove ' + name + ' from ' + scope.name + ' ' + title + '?', + action: action + }); + + } + + // Password change + scope.clearPWConfirm = function(fld) { + // If password value changes, make sure password_confirm must be re-entered + scope[fld] = ''; + scope[form.name + '_form'][fld].$setValidity('awpassmatch', false); + } + +} + +CredentialsEdit.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'CredentialForm', + 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'RelatedSearchInit', + 'RelatedPaginateInit', 'ReturnToCaller', 'ClearScope', 'LookUpInit', 'UserList', 'TeamList', + 'Prompt', 'GetBasePath' ]; + \ No newline at end of file diff --git a/lib/ui/static/js/controllers/Organizations.js b/lib/ui/static/js/controllers/Organizations.js index d0cb1d32fc..96e6ab6df9 100644 --- a/lib/ui/static/js/controllers/Organizations.js +++ b/lib/ui/static/js/controllers/Organizations.js @@ -10,7 +10,7 @@ 'use strict'; -function OrganizationsList ($scope, $rootScope, $location, $log, Rest, Alert, LoadBreadCrumbs, Prompt, GetAPIDefaults, +function OrganizationsList ($scope, $rootScope, $location, $log, Rest, Alert, LoadBreadCrumbs, Prompt, GenerateList, OrganizationList, SearchInit, PaginateInit, ClearScope, ProcessErrors) { ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior @@ -66,7 +66,7 @@ function OrganizationsList ($scope, $rootScope, $location, $log, Rest, Alert, Lo } } -OrganizationsList.$inject=[ '$scope', '$rootScope', '$location', '$log', 'Rest', 'Alert', 'LoadBreadCrumbs', 'Prompt', 'GetAPIDefaults', +OrganizationsList.$inject=[ '$scope', '$rootScope', '$location', '$log', 'Rest', 'Alert', 'LoadBreadCrumbs', 'Prompt', 'GenerateList', 'OrganizationList', 'SearchInit', 'PaginateInit', 'ClearScope', 'ProcessErrors']; @@ -110,8 +110,8 @@ OrganizationsAdd.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$ function OrganizationsEdit ($scope, $rootScope, $compile, $location, $log, $routeParams, OrganizationForm, - GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, GetAPIDefaults, - RelatedSearchInit, RelatedPaginateInit, Prompt, ClearScope) + GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit, + RelatedPaginateInit, Prompt, ClearScope) { ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior //scope. @@ -230,5 +230,5 @@ function OrganizationsEdit ($scope, $rootScope, $compile, $location, $log, $rout } OrganizationsEdit.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'OrganizationForm', - 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'GetAPIDefaults', - 'RelatedSearchInit', 'RelatedPaginateInit', 'Prompt', 'ClearScope']; + 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'RelatedSearchInit', + 'RelatedPaginateInit', 'Prompt', 'ClearScope']; diff --git a/lib/ui/static/js/controllers/Teams.js b/lib/ui/static/js/controllers/Teams.js index c9eddba899..2bf231a1ec 100644 --- a/lib/ui/static/js/controllers/Teams.js +++ b/lib/ui/static/js/controllers/Teams.js @@ -284,7 +284,7 @@ function TeamsEdit ($scope, $rootScope, $compile, $location, $log, $routeParams, // Related set: Add button scope.add = function(set) { $rootScope.flashMessage = null; - $location.path('/' + base + '/' + $routeParams.id + '/' + set + '/add'); + $location.path('/' + base + '/' + $routeParams.id + '/' + set); }; // Related set: Edit button diff --git a/lib/ui/static/js/controllers/Users.js b/lib/ui/static/js/controllers/Users.js index ea944e55c6..549bc49bb8 100644 --- a/lib/ui/static/js/controllers/Users.js +++ b/lib/ui/static/js/controllers/Users.js @@ -177,10 +177,10 @@ function UsersAdd ($scope, $rootScope, $compile, $location, $log, $routeParams, }; // Password change - scope.clearPWConfirm = function() { + scope.clearPWConfirm = function(fld) { // If password value changes, make sure password_confirm must be re-entered - scope.password_confirm = ''; - scope[form.name]['password_confirm'].$setValidity('awpassmatch', false); + scope[fld] = ''; + scope[form.name][fld].$setValidity('awpassmatch', false); } } @@ -245,7 +245,7 @@ function UsersEdit ($scope, $rootScope, $compile, $location, $log, $routeParams, // Save changes to the parent scope.formSave = function() { - Rest.setUrl(defaultUrl + $routeParams.id); + Rest.setUrl(defaultUrl + $routeParams.id + '/'); var data = {} for (var fld in form.fields) { data[fld] = scope[fld]; @@ -270,10 +270,10 @@ function UsersEdit ($scope, $rootScope, $compile, $location, $log, $routeParams, }; // Password change - scope.clearPWConfirm = function() { + scope.clearPWConfirm = function(fld) { // If password value changes, make sure password_confirm must be re-entered - scope.password_confirm = ''; - scope[form.name]['password_confirm'].$setValidity('awpassmatch', false); + scope[fld] = ''; + scope[form.name + '_form'][fld].$setValidity('awpassmatch', false); } } diff --git a/lib/ui/static/js/forms/Credentials.js b/lib/ui/static/js/forms/Credentials.js new file mode 100644 index 0000000000..6adc05473b --- /dev/null +++ b/lib/ui/static/js/forms/Credentials.js @@ -0,0 +1,137 @@ +/********************************************* + * Copyright (c) 2013 AnsibleWorks, Inc. + * + * Credentials.js + * Form definition for Credential model + * + * + */ +angular.module('CredentialFormDefinition', []) + .value( + 'CredentialForm', { + + addTitle: 'Create Credential', //Legend in add mode + editTitle: '{{ name }}', //Legend in edit mode + name: 'team', + well: true, + + fields: { + name: { + label: 'Name', + type: 'text', + addRequired: true, + editRequired: true + }, + description: { + label: 'Description', + type: 'text', + addRequired: false, + editRequired: false + }, + "ssh_username": { + label: 'SSH Username', + type: 'text', + addRequired: false, + editRequired: false + }, + "ssh_password": { + label: 'SSH Password', + type: 'password', + addRequired: false, + editRequired: false, + ngChange: "clearPWConfirm('ssh_password_confirm')" + }, + "ssh_password_confirm": { + label: 'Confirm SSH Password', + type: 'password', + addRequired: false, + editRequired: false, + awPassMatch: true, + associated: 'ssh_password' + }, + "ssh_key_data": { + label: 'SSH Key', + type: 'textarea', + addRequired: false, + editRequired: false, + rows: 10 + }, + "ssh_key_unlock": { + label: 'Key Password', + type: 'password', + addRequired: false, + editRequired: false, + ngChange: "clearPWConfirm('ssh_key_unlock_confirm')" + }, + "ssh_key_unlock_confirm": { + label: 'Confirm Key Password', + type: 'password', + addRequired: false, + editRequired: false, + awPassMatch: true, + associated: 'ssh_key_unlock' + }, + "sudo_username": { + label: 'Sudo Username', + type: 'text', + addRequired: false, + editRequired: false + }, + "sudo_password": { + label: 'Sudo Password', + type: 'text', + addRequired: false, + editRequired: false, + ngChange: "clearPWConfirm('sudo_password_confirm')" + }, + "sudo_password_confirm": { + label: 'Confirm Sudo Password', + type: 'password', + addRequired: false, + editRequired: false, + awPassMatch: true, + associated: 'sudo_password' + }, + user: { + label: 'User', + type: 'lookup', + sourceModel: 'user', + sourceField: 'username', + addRequired: false, + editRequired: false, + ngClick: 'lookUpUser()' + }, + team: { + label: 'Team', + type: 'lookup', + sourceModel: 'team', + sourceField: 'name', + addRequired: false, + editRequired: false, + ngClick: 'lookUpTeam()' + } + }, + + buttons: { //for now always generates