Latest UI changes. Credential List and Edit working -need to add ask pw option and validation around team vs. user. Lookup dialog is now a generic helper that can be shared by all controllers. Credentials controller is first to use api-loader which reads the \/api and \/api\/v1 to determine base set of urls. And more...

This commit is contained in:
chouseknecht 2013-05-09 23:49:51 -04:00
parent 2cbed11034
commit fdceb46c12
25 changed files with 1218 additions and 65 deletions

View File

@ -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 <http://www.gnu.org/licenses/>.
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] '

View File

@ -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 <http://www.gnu.org/licenses/>.
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] '

View File

@ -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 <http://www.gnu.org/licenses/>.
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] '

View File

@ -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 {

View File

@ -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');
}
});
}]);

View File

@ -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' ];

View File

@ -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'];

View File

@ -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

View File

@ -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);
}
}

View File

@ -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 <button> tags
save: {
label: 'Save',
icon: 'icon-ok',
class: 'btn btn-success',
ngClick: 'formSave()', //$scope.function to call on click, optional
ngDisabled: true //Disable when $pristine or $invalid, optional
},
reset: {
ngClick: 'formReset()',
label: 'Reset',
icon: 'icon-remove',
class: 'btn',
ngDisabled: true //Disabled when $pristine
}
},
related: { //related colletions (and maybe items?)
}
}); //InventoryForm

View File

@ -58,6 +58,117 @@ angular.module('TeamFormDefinition', [])
},
related: { //related colletions (and maybe items?)
users: {
type: 'collection',
title: 'Users',
iterator: 'user',
open: false,
actions: {
add: {
ngClick: "add('users')",
icon: 'icon-plus'
},
},
fields: {
username: {
key: true,
label: 'Username'
},
first_name: {
label: 'First Name'
},
last_name: {
label: 'Last Name'
}
},
fieldActions: {
edit: {
ngClick: "edit('users', \{\{ user.id \}\}, '\{\{ user.username \}\}')",
icon: 'icon-edit'
},
delete: {
ngClick: "delete('users', \{\{ user.id \}\}, '\{\{ user.username \}\}', 'users')",
icon: 'icon-remove',
class: 'btn-danger'
}
}
},
credentials: {
type: 'collection',
title: 'Credentials',
iterator: 'credential',
open: false,
actions: {
add: {
ngClick: "add('credentials')",
icon: 'icon-plus'
},
},
fields: {
name: {
key: true,
label: 'Name'
},
description: {
label: 'Description'
}
},
fieldActions: {
edit: {
ngClick: "edit('credentials', \{\{ credential.id \}\}, '\{\{ credential.name \}\}')",
icon: 'icon-edit'
},
delete: {
ngClick: "delete('credentials', \{\{ credential.id \}\}, '\{\{ credential.name \}\}', 'credentials')",
icon: 'icon-remove',
class: 'btn-danger'
}
}
},
projects: {
type: 'collection',
title: 'Projects',
iterator: 'project',
open: false,
actions: {
add: {
ngClick: "add('projects')",
icon: 'icon-plus'
},
},
fields: {
name: {
key: true,
label: 'Name'
},
description: {
label: 'Description'
}
},
fieldActions: {
edit: {
ngClick: "edit('projects', \{\{ project.id \}\}, '\{\{ project.name \}\}')",
icon: 'icon-edit'
},
delete: {
ngClick: "delete('projects', \{\{ project.id \}\}, '\{\{ project.name \}\}', 'projects')",
icon: 'icon-remove',
class: 'btn-danger'
}
}
}
}

View File

@ -47,14 +47,15 @@ angular.module('UserFormDefinition', [])
type: 'password',
addRequired: true,
editRequired: false,
ngChange: 'clearPWConfirm()'
ngChange: "clearPWConfirm('password_confirm')"
},
password_confirm: {
label: 'Confirm Password',
type: 'password',
addRequired: false,
editRequired: false,
awPassMatch: true
awPassMatch: true,
associated: 'password'
},
is_superuser: {
label: 'Superuser?',

View File

@ -0,0 +1,74 @@
/*********************************************
* Copyright (c) 2013 AnsibleWorks, Inc.
*
* LookupHelper
* Build a lookup dialog
*
* LookUpInit( {
* scope: <form scope>,
* form: <form object>,
* current_item: <id of item to select on open>,
* list: <list object>,
* field: <name of the form field with which the lookup is associated>
* })
*/
angular.module('LookUpHelper', [ 'RestServices', 'Utilities', 'SearchHelper', 'PaginateHelper', 'ListGenerator', 'ApiLoader' ])
.factory('LookUpInit', ['Alert', 'Rest', 'GenerateList', 'SearchInit', 'PaginateInit', 'GetBasePath',
function(Alert, Rest, GenerateList, SearchInit, PaginateInit, GetBasePath) {
return function(params) {
var scope = params.scope; // form scope
var form = params.form; // form object
var current_item = params.current_item; //id of the item that should be selected on open
var list = params.list; // list object
var field = params.field; // form field
// Show pop-up to select user
var name = list.iterator.charAt(0).toUpperCase() + list.iterator.substring(1);
scope['lookUp' + name] = function() {
var listGenerator = GenerateList;
var listScope = listGenerator.inject(list, { mode: 'lookup', hdr: 'Select ' + name });
var defaultUrl = GetBasePath(list.name);
listScope.selectAction = function() {
var found = false;
var name;
for (var i=0; i < listScope[list.name].length; i++) {
if (listScope[list.iterator + "_" + listScope[list.name][i].id + "_class"] == "success") {
found = true;
scope[field] = listScope[list.name][i].id;
scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField] =
listScope[list.name][i][form.fields[field].sourceField];
scope[form.name + '_form'].$setDirty();
listGenerator.hide();
}
}
if (found == false) {
Alert('Missing Selection', 'Oops, you failed to make a selection. Click on a row to make your selection, ' +
'and then click the Select button.');
}
}
listScope['toggle_' + list.iterator] = function(id) {
// when user clicks a row, remove 'success' class from all rows except clicked-on row
if (listScope[list.name]) {
for (var i=0; i < listScope[list.name].length; i++) {
listScope[list.iterator + "_" + listScope[list.name][i].id + "_class"] = "";
}
}
if (id != null && id != undefined) {
listScope[list.iterator + "_" + id + "_class"] = "success";
}
}
SearchInit({ scope: listScope, set: list.name, list: list, url: defaultUrl });
PaginateInit({ scope: listScope, list: list, url: defaultUrl, mode: 'lookup' });
scope.search(list.iterator);
listScope['toggle_' + list.iterator](current_item);
}
}
}]);

View File

@ -0,0 +1,66 @@
/*********************************************
* Copyright (c) 2013 AnsibleWorks, Inc.
*
* Credentials.js
* List view object for Credential data model.
*
*
*/
angular.module('CredentialListDefinition', [])
.value(
'CredentialList', {
name: 'credentials',
iterator: 'credential',
selectTitle: 'Add Credentials',
editTitle: 'Credentials',
selectInstructions: 'Check the Select checkbox next to each user to be added, and click Finished when done. Use the green <i class=\"icon-plus\"></i> button to create a new user.',
fields: {
name: {
key: true,
label: 'Name'
},
description: {
label: 'Description'
},
team: {
label: 'Team',
ngBind: 'credential.summary_fields.team.name',
sourceModel: 'team',
sourceField: 'name'
},
user: {
label: 'User',
ngBind: 'credential.summary_fields.user.usename',
sourceModel: 'user',
sourceField: 'username'
}
},
actions: {
add: {
icon: 'icon-plus',
mode: 'select', // One of: edit, select, all
ngClick: 'addCredential()',
basePaths: ['credentials'], // base path must be in list, or action not available
class: 'btn btn-mini btn-success',
awToolTip: 'Create a new credential'
}
},
fieldActions: {
edit: {
ngClick: "editCredential(\{\{ credential.id \}\})",
icon: 'icon-edit',
awToolTip: 'Edit credential'
},
delete: {
ngClick: "deleteCredential(\{\{ credential.id \}\},'\{\{ credential.name \}\}')",
icon: 'icon-remove',
class: 'btn-danger',
awToolTip: 'Delete credential'
}
}
});

View File

@ -33,10 +33,11 @@ angular.module('UserListDefinition', [])
actions: {
add: {
icon: 'icon-plus',
mode: 'select', // One of: edit, select, all
mode: 'select', // One of: edit, select, all
ngClick: 'addUser()',
basePaths: ['organizations'], // base path must be in list, or action not available
class: 'btn btn-mini btn-success',
awToolTip: 'Create a new row'
awToolTip: 'Create a new user'
}
},
@ -44,14 +45,14 @@ angular.module('UserListDefinition', [])
edit: {
ngClick: "editUser(\{\{ user.id \}\})",
icon: 'icon-edit',
awToolTip: 'Edit'
awToolTip: 'Edit user'
},
delete: {
ngClick: "deleteUser(\{\{ user.id \}\},'\{\{ user.username \}\}')",
icon: 'icon-remove',
class: 'btn-danger',
awToolTip: 'Delete'
awToolTip: 'Delete user'
}
}
});

View File

@ -0,0 +1,56 @@
/*********************************************
* Copyright (c) 2013 AnsibleWorks, Inc.
*
*
* Read /api and /api/X to discover all the base paths needed
* to access the primary model objects.
*
*/
angular.module('ApiLoader', ['ngCookies'])
.factory('LoadBasePaths', ['$http', '$rootScope', '$cookieStore', 'ProcessErrors',
function($http, $rootScope, $cookieStore, ProcessErrors) {
return function() {
$http.get('/api/')
.success( function(data, status, headers, config) {
var base = data.current_version;
$http.get(base)
.success( function(data, status, headers, config) {
data['base'] = base;
$rootScope['defaultUrls'] = data;
$cookieStore.remove('api');
$cookieStore.put('api',data); //Preserve in cookie to prevent against
//loss during browser refresh
})
.error ( function(data, status, headers, config) {
$rootScope['defaultUrls'] = { status: 'error' };
ProcessErrors(null, data, status, null,
{ hdr: 'Error', msg: 'Failed to read ' + base + '. GET status: ' + status });
});
})
.error( function(data, status, headers, config) {
$rootScope['defaultUrls'] = { status: 'error' };
ProcessErrors(null, data, status, null,
{ hdr: 'Error', msg: 'Failed to read /api. GET status: ' + status });
});
}
}])
.factory('GetBasePath', ['$rootScope', '$cookieStore', 'LoadBasePaths',
function($rootScope, $cookieStore, LoadBasePaths) {
return function(set) {
var answer;
if ($rootScope['defaultUrls'] == null || $rootScope['defaultUrls'] == undefined) {
// browser refresh must have occurred. use what's in session cookie and refresh
answer = $cookieStore.get('api')[set];
LoadBasePaths();
}
else {
answer = $rootScope['defaultUrls'][set];
}
return answer;
}
}]);

View File

@ -14,7 +14,8 @@ angular.module('AWDirectives', [])
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
ctrl.$parsers.unshift( function(viewValue) {
var password = $('input[name="password"]').val();
var associated = attrs.awpassmatch;
var password = $('input[name="' + associated + '"]').val();
if (viewValue == password) {
// it is valid
ctrl.$setValidity('awpassmatch', true);

View File

@ -96,6 +96,9 @@ angular.module('FormGenerator', ['GeneratorHelpers'])
if (this.scope[this.form.name + '_form'][fld]) {
this.scope[this.form.name + '_form'][fld].$setPristine();
}
if (this.form.fields[fld].awPassMatch) {
this.scope[this.form.name + '_form'][fld].$setValidity('awpassmatch', true);
}
}
if (this.mode == 'add') {
this.applyDefaults();
@ -171,7 +174,7 @@ angular.module('FormGenerator', ['GeneratorHelpers'])
html += (options.mode == 'edit' && field.editRequired) ? "required " : "";
html += (options.mode == 'add' && field.addRequired) ? "required " : "";
html += (field.readonly) ? "readonly " : "";
html += (field.awPassMatch) ? "awpassmatch " : "";
html += (field.awPassMatch) ? "awpassmatch=\"" + field.associated + "\" " : "";
html += (field.capitalize) ? "capitalize " : "";
html += "/><br />\n";
// Add error messages
@ -193,6 +196,36 @@ angular.module('FormGenerator', ['GeneratorHelpers'])
}
}
//text type fields
if (field.type == 'textarea') {
if ( (! field.readonly) || (field.readonly && options.mode == 'edit') ) {
html += "<div class=\"control-group\""
html += (field.ngShow) ? this.attr(field,'ngShow') : "";
html += ">\n";
html += "<label class=\"control-label\" for=\"" + fld + '">' + field.label + '</label>' + "\n";
html += "<div class=\"controls\">\n";
html += "<textarea ";
html += this.attr(field, 'rows');
html += "ng-model=\"" + fld + '" ';
html += 'name="' + fld + '" ';
html += (field.ngChange) ? this.attr(field,'ngChange') : "";
html += (field.id) ? this.attr(field,'id') : "";
html += (field.placeholder) ? this.attr(field,'placeholder') : "";
html += (options.mode == 'edit' && field.editRequired) ? "required " : "";
html += (options.mode == 'add' && field.addRequired) ? "required " : "";
html += (field.readonly) ? "readonly " : "";
html += "></textarea><br />\n";
// Add error messages
if ( (options.mode == 'add' && field.addRequired) || (options.mode == 'edit' && field.editRequired) ) {
html += "<span class=\"error\" ng-show=\"" + this.form.name + '_form.' + fld + ".$dirty && " +
this.form.name + '_form.' + fld + ".$error.required\">A value is required!</span>\n";
}
html += "<span class=\"error api-error\" ng-bind=\"" + fld + "_api_error\"></span>\n";
html += "</div>\n";
html += "</div>\n";
}
}
//checkbox
if (field.type == 'checkbox') {
if ( (! field.readonly) || (field.readonly && options.mode == 'edit') ) {
@ -238,7 +271,7 @@ angular.module('FormGenerator', ['GeneratorHelpers'])
html += (options.mode == 'edit' && field.editRequired) ? "required " : "";
html += (options.mode == 'add' && field.addRequired) ? "required " : "";
html += " readonly />\n";
html += "</div>\n";
html += "</div><br />\n";
// Add error messages
if ( (options.mode == 'add' && field.addRequired) || (options.mode == 'edit' && field.editRequired) ) {
html += "<span class=\"error\" ng-show=\"" + this.form.name + '_form.' + fld + ".$dirty && " +
@ -294,6 +327,8 @@ angular.module('FormGenerator', ['GeneratorHelpers'])
html += this.buildCollections();
}
console.log(html);
return html;
},

View File

@ -100,9 +100,8 @@ angular.module('GeneratorHelpers', [])
html += "</select>\n";
}
html += "<div class=\"";
html += (mode != "lookup") ? "page-number pull-right" : "page-number-small";
html += "\" >Page: {{ " + iterator + "Page + 1 }} of {{ " + iterator + "PageCount }}</div>\n";
html += "<div class=\"page-number-small\"";
html += ">Page: {{ " + iterator + "Page + 1 }} of {{ " + iterator + "PageCount }}</div>\n";
html += "</form>\n";
html += "</div>\n";

View File

@ -7,9 +7,9 @@
*
*/
angular.module('ListGenerator', ['GeneratorHelpers'])
.factory('GenerateList', [ '$compile', '$rootScope', 'SearchWidget', 'PaginateWidget',
function($compile, $rootScope, SearchWidget, PaginateWidget) {
angular.module('ListGenerator', ['GeneratorHelpers',])
.factory('GenerateList', [ '$location', '$compile', '$rootScope', 'SearchWidget', 'PaginateWidget',
function($location, $compile, $rootScope, SearchWidget, PaginateWidget) {
return {
setList: function(list) {
@ -102,21 +102,17 @@ angular.module('ListGenerator', ['GeneratorHelpers'])
}
//select instructions
if (options.mode == 'select') {
if (list.selectInstructions) {
html += "<div class=\"alert alert-info alert-block\">\n";
html += "<button type=\"button\" class=\"close\" data-dismiss=\"alert\">&times;</button>\n";
html += "<strong>Hint: </strong>" + list.selectInstructions + "\n";
html += "</div>\n"
}
if (options.mode == 'select' && list.selectInstructions) {
html += "<div class=\"alert alert-info alert-block\">\n";
html += "<button type=\"button\" class=\"close\" data-dismiss=\"alert\">&times;</button>\n";
html += "<strong>Hint: </strong>" + list.selectInstructions + "\n";
html += "</div>\n"
}
else {
if (options.mode == 'edit' && list.editInstructions) {
html += "<div class=\"alert alert-info alert-block\">\n";
html += "<button type=\"button\" class=\"close\" data-dismiss=\"alert\">&times;</button>\n";
html += "<strong>Hint: </strong>" + list.editInstructions + "\n";
html += "</div>\n";
}
else if (options.mode == 'edit' && list.editInstructions) {
html += "<div class=\"alert alert-info alert-block\">\n";
html += "<button type=\"button\" class=\"close\" data-dismiss=\"alert\">&times;</button>\n";
html += "<strong>Hint: </strong>" + list.editInstructions + "\n";
html += "</div>\n";
}
if (options.mode != 'lookup') {
@ -132,13 +128,16 @@ angular.module('ListGenerator', ['GeneratorHelpers'])
if (options.mode != 'lookup') {
//actions
base = $location.path().replace(/^\//,'').split('/')[0];
html += "<div class=\"text-right\">\n";
for (action in list.actions) {
if (list.actions[action].mode == 'all' || list.actions[action].mode == options.mode) {
html += "<button " + this.attr(list.actions[action], 'ngClick') +
this.attr(list.actions[action], 'class');
html += (list.actions[action].awToolTip) ? this.attr(list.actions[action],'awToolTip') : "";
html += " >" + this.icon(list.actions[action].icon) + "</button> ";
if (list.basePaths && list.basePaths.indexOf(base) > -1) {
html += "<button " + this.attr(list.actions[action], 'ngClick') +
this.attr(list.actions[action], 'class');
html += (list.actions[action].awToolTip) ? this.attr(list.actions[action],'awToolTip') : "";
html += " >" + this.icon(list.actions[action].icon) + "</button> ";
}
}
}
if (options.mode == 'select') {
@ -227,7 +226,7 @@ angular.module('ListGenerator', ['GeneratorHelpers'])
else {
html += PaginateWidget({ set: list.name, iterator: list.iterator, mini: true });
}
return html;
}

View File

@ -23,7 +23,9 @@ angular.module('PromptDialog', [])
var scope = dialog.scope();
scope.promptHeader = params.hdr;
scope.promptBody = params.body;
scope.promptBtnClass = (params.class == null || params.class == undefined) ? 'btn-danger' : params.class;
var cls = (params.class == null || params.class == undefined) ? 'btn-danger' : params.class;
$('#prompt-action-btn').addClass(cls); //Use jquery because django template engine conflicts with Angular's
// use of {{...}}
scope.id = params.id;
scope.url = params.url;
scope.promptAction = params.action;

View File

@ -25,7 +25,7 @@ angular.module('RestServices',['ngCookies','AuthService'])
this.url = this.url.replace(rgx,this.params[key]);
delete this.params[key];
}
}
}
},
get: function(args) {

View File

@ -0,0 +1,3 @@
<div class="tab-pane" id="credentials">
<div id="htmlTemplate"></div>
</div>

View File

@ -1,3 +0,0 @@
<div class="tab-pane" id="inventories">
<div id="htmlTemplate"></div>
</div>

View File

@ -30,6 +30,7 @@
<script src="{{ STATIC_URL }}js/controllers/Admins.js"></script>
<script src="{{ STATIC_URL }}js/controllers/Inventories.js"></script>
<script src="{{ STATIC_URL }}js/controllers/Teams.js"></script>
<script src="{{ STATIC_URL }}js/controllers/Credentials.js"></script>
<script src="{{ STATIC_URL }}js/controllers/Hosts.js"></script>
<script src="{{ STATIC_URL }}js/controllers/Groups.js"></script>
<script src="{{ STATIC_URL }}js/forms/Users.js"></script>
@ -38,6 +39,7 @@
<script src="{{ STATIC_URL }}js/forms/Teams.js"></script>
<script src="{{ STATIC_URL }}js/forms/Hosts.js"></script>
<script src="{{ STATIC_URL }}js/forms/Groups.js"></script>
<script src="{{ STATIC_URL }}js/forms/Credentials.js"></script>
<script src="{{ STATIC_URL }}js/lists/Users.js"></script>
<script src="{{ STATIC_URL }}js/lists/Organizations.js"></script>
<script src="{{ STATIC_URL }}js/lists/Admins.js"></script>
@ -45,7 +47,7 @@
<script src="{{ STATIC_URL }}js/lists/Teams.js"></script>
<script src="{{ STATIC_URL }}js/lists/Hosts.js"></script>
<script src="{{ STATIC_URL }}js/lists/Groups.js"></script>
<script src="{{ STATIC_URL }}js/helpers/api-defaults.js"></script>
<script src="{{ STATIC_URL }}js/lists/Credentials.js"></script>
<script src="{{ STATIC_URL }}js/helpers/refresh-related.js"></script>
<script src="{{ STATIC_URL }}js/helpers/related-paginate.js"></script>
<script src="{{ STATIC_URL }}js/helpers/related-search.js"></script>
@ -55,9 +57,12 @@
<script src="{{ STATIC_URL }}js/helpers/inventory.js"></script>
<script src="{{ STATIC_URL }}js/helpers/hosts.js"></script>
<script src="{{ STATIC_URL }}js/helpers/teams.js"></script>
<script src="{{ STATIC_URL }}js/helpers/teams.js"></script>
<script src="{{ STATIC_URL }}js/helpers/Lookup.js"></script>
<script src="{{ STATIC_URL }}lib/ansible/directives.js"></script>
<script src="{{ STATIC_URL }}lib/ansible/filters.js"></script>
<script src="{{ STATIC_URL }}lib/ansible/tooltip.js"></script>
<script src="{{ STATIC_URL }}lib/ansible/api-loader.js"></script>
{% endif %}
<!-- <script src="{{ STATIC_URL }}lib/angular/http-auth-interceptor.js"></script> -->
</head>
@ -114,7 +119,7 @@
</div>
<div class="modal-footer">
<a href="#" data-target="#prompt-modal" data-dismiss="modal" class="btn">No</a>
<a href="" ng-click="promptAction()" ng-bind="promptBtnClass" class="btn">Yes</a> <!-- FIXME: Make sure the ng-bind works. -->
<a href="" ng-click="promptAction()" id="prompt-action-btn" class="btn">Yes</a> <!-- FIXME: Make sure the ng-bind works. -->
</div>
</div>