Fixed bugs from package upgrades. Updated all controllers to pass into form and list generators. Improved accordion styling. Refactored job status page. Tested using minified files and fixed build issues.

This commit is contained in:
Chris Houseknecht 2014-02-13 03:04:27 -05:00
parent 2fcb5a1ae4
commit 69b91afaf2
22 changed files with 455 additions and 765 deletions

View File

@ -8,7 +8,7 @@ module.exports = function(grunt) {
options: {
jshintrc: '.jshintrc'
},
uses_defaults: ['awx/ui/static/js/*','awx/ui/static/lib/ansible/*', '!awx/ui/static/js/awx-min.js']
uses_defaults: ['awx/ui/static/js/*','awx/ui/static/lib/ansible/*', '!awx/ui/static/js/awx.min.js']
},
uglify: {
@ -18,8 +18,8 @@ module.exports = function(grunt) {
},
my_target: {
files: {
'awx/ui/static/js/awx-min.js': ['awx/ui/static/js/**/*.js', 'awx/ui/static/lib/ansible/*.js',
'!awx/ui/static/js/awx.min.js']
'awx/ui/static/js/awx.min.js': ['awx/ui/static/js/**/*.js', 'awx/ui/static/lib/ansible/*.js',
'!awx/ui/static/js/awx.min.js', '!awx/ui/static/js/config.js']
}
}
},

File diff suppressed because one or more lines are too long

View File

@ -213,8 +213,8 @@ function InventoriesList($scope, $rootScope, $location, $log, $routeParams, Rest
};
}
InventoriesList.$inject = ['$scope', '$root $scope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'InventoryList', 'GenerateList',
'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'Clear $scope', 'ProcessErrors',
InventoriesList.$inject = ['$scope', '$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'InventoryList', 'GenerateList',
'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'ProcessErrors',
'GetBasePath', 'Wait', 'Stream', 'EditInventoryProperties'
];

View File

@ -129,7 +129,6 @@ function JobEventsList($filter, $scope, $rootScope, $location, $log, $routeParam
for (i = 0; i < set.length; i++) {
set[i].event_display = set[i].event_display.replace(/^\u00a0*/g, '');
if (set[i].event_level < 3) {
set[i].ngclick = "toggleChildren(" + set[i].id + ", \"" + set[i].related.children + "\")";
set[i].ngicon = 'fa fa-minus-square-o node-toggle';
set[i]['class'] = 'parentNode';
} else {
@ -216,12 +215,11 @@ function JobEventsList($filter, $scope, $rootScope, $location, $log, $routeParam
$scope.search(list.iterator, $routeParams.page);
$scope.toggleChildren = function (id, children) {
$scope.toggle = function (id) {
ToggleChildren({
scope: $scope,
list: list,
id: id,
children: children
id: id
});
};

View File

@ -187,37 +187,40 @@ JobsListCtrl.$inject = ['$scope', '$rootScope', '$location', '$log', '$routePara
];
function JobsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, JobForm, GenerateForm, Rest, Alert, ProcessErrors,
LoadBreadCrumbs, RelatedSearchInit, RelatedPaginateInit, ReturnToCaller, ClearScope, InventoryList, CredentialList,
ProjectList, LookUpInit, PromptPasswords, GetBasePath, md5Setup, FormatDate, JobStatusToolTip, Wait) {
function JobsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, JobForm, JobTemplateForm, GenerateForm, Rest,
Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit, RelatedPaginateInit, ReturnToCaller, ClearScope, InventoryList,
CredentialList, ProjectList, LookUpInit, PromptPasswords, GetBasePath, md5Setup, FormatDate, JobStatusToolTip, Wait, Empty) {
ClearScope();
var defaultUrl = GetBasePath('jobs'),
generator = GenerateForm,
form = JobForm,
master = {},
id = $routeParams.id,
relatedSets = {},
loadingFinishedCount = 0;
generator.inject(form, { mode: 'edit', related: true, scope: $scope });
generator.reset();
loadingFinishedCount = 0,
templateForm = {};
generator.inject(JobForm, { mode: 'edit', related: true, scope: $scope });
$scope.job_id = id;
$scope.parseType = 'yaml';
$scope.statusSearchSpin = false;
function getPlaybooks(project) {
if (project !== null && project !== '' && project !== undefined) {
function getPlaybooks(project, playbook) {
if (!Empty(project)) {
var url = GetBasePath('projects') + project + '/playbooks/';
Rest.setUrl(url);
Rest.get()
.success(function (data) {
var i;
$scope.playbook_options = [];
for (var i = 0; i < data.length; i++) {
for (i = 0; i < data.length; i++) {
$scope.playbook_options.push(data[i]);
}
for (i = 0; i < $scope.playbook_options.length; i++) {
if ($scope.playbook_options[i] === playbook) {
$scope.playbook = $scope.playbook_options[i];
}
}
$scope.$emit('jobTemplateLoadFinished');
})
.error(function () {
@ -233,22 +236,22 @@ function JobsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, J
if ($scope.jobLoadedRemove) {
$scope.jobLoadedRemove();
}
$scope.jobLoadedRemove = $scope.$on('jobLoaded', function (e, related_cloud_credential) {
$scope.jobLoadedRemove = $scope.$on('jobLoaded', function (e, related_cloud_credential, project, playbook) {
getPlaybooks($scope.project);
getPlaybooks(project, playbook);
$scope[form.name + 'ReadOnly'] = ($scope.status === 'new') ? false : true;
//$scope[form.name + 'ReadOnly'] = ($scope.status === 'new') ? false : true;
$('#forks-slider').slider("option", "value", $scope.forks);
$('#forks-slider').slider("disable");
$('input[type="checkbox"]').attr('disabled', 'disabled');
$('input[type="radio"]').attr('disabled', 'disabled');
$('#host_config_key-gen-btn').attr('disabled', 'disabled');
$('textarea').attr('readonly', 'readonly');
//$('#forks-slider').slider("option", "value", $scope.forks);
//$('#forks-slider').slider("disable");
//$('input[type="checkbox"]').attr('disabled', 'disabled');
//$('input[type="radio"]').attr('disabled', 'disabled');
//$('#host_config_key-gen-btn').attr('disabled', 'disabled');
//$('textarea').attr('readonly', 'readonly');
// Get job template and display/hide host callback fields
Rest.setUrl($scope.template_url);
Rest.get()
/*Rest.setUrl($scope.template_url);
Rest.get()
.success(function (data) {
var dft = (data.host_config_key) ? 'true' : 'false';
$scope.host_config_key = data.host_config_key;
@ -265,7 +268,7 @@ function JobsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, J
Wait('stop');
$scope.callback_url = '<< Job template not found >>';
});
*/
if (related_cloud_credential) {
//Get the name of the cloud credential
Rest.setUrl(related_cloud_credential);
@ -281,6 +284,7 @@ function JobsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, J
} else {
$scope.$emit('jobTemplateLoadFinished');
}
});
// Turn off 'Wait' after both cloud credential and playbook list come back
@ -289,7 +293,7 @@ function JobsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, J
}
$scope.removeJobTemplateLoadFinished = $scope.$on('jobTemplateLoadFinished', function () {
loadingFinishedCount++;
if (loadingFinishedCount >= 3) {
if (loadingFinishedCount >= 2) {
// The initial template load finished. Now load related jobs, which
// will turn off the 'working' spinner.
Wait('stop');
@ -305,13 +309,13 @@ function JobsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, J
label: 'Check'
}];
$scope.verbosity_options = [{
value: '0',
value: 0,
label: 'Default'
}, {
value: '1',
value: 1,
label: 'Verbose'
}, {
value: '3',
value: 3,
label: 'Debug'
}];
$scope.playbook_options = null;
@ -328,12 +332,34 @@ function JobsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, J
Rest.setUrl(defaultUrl + ':id/');
Rest.get({ params: { id: id } })
.success(function (data) {
//LoadBreadCrumbs({ path: '/jobs/' + id, title: data.id + ' - ' + data.summary_fields.job_template.name });
var i, cDate, fld, json_obj, related, set;
var i, fld, json_obj;
LoadBreadCrumbs();
for (fld in form.fields) {
$scope.status = data.status;
$scope.created = FormatDate(data.created);
$scope.result_stdout = data.result_stdout;
$scope.result_traceback = data.result_traceback;
$scope.stdout_rows = calcRows($scope.result_stdout);
$scope.traceback_rows = calcRows($scope.result_traceback);
// Now load the job template form
templateForm.addTitle = 'Create Job Templates';
templateForm.editTitle = '{{ name }}';
templateForm.name = 'job_templates';
templateForm.twoColumns = true;
templateForm.fields = angular.copy(JobTemplateForm.fields);
for (fld in templateForm.fields) {
templateForm.fields[fld].readonly = true;
}
$('#ui-accordion-jobs-collapse-0-panel-1').find('div').attr('id','job-template-container');
generator.inject(templateForm, { mode: 'edit', id: 'job-template-container', scope: $scope, breadCrumbs: false });
for (fld in templateForm.fields) {
if (fld !== 'variables' && data[fld] !== null && data[fld] !== undefined) {
if (form.fields[fld].type === 'select') {
if (JobTemplateForm.fields[fld].type === 'select') {
if ($scope[fld + '_options'] && $scope[fld + '_options'].length > 0) {
for (i = 0; i < $scope[fld + '_options'].length; i++) {
if (data[fld] === $scope[fld + '_options'][i].value) {
@ -343,107 +369,36 @@ function JobsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, J
} else {
$scope[fld] = data[fld];
}
}
} else {
$scope[fld] = data[fld];
}
master[fld] = $scope[fld];
}
$scope.id = data.id;
$scope.name = (data.summary_fields && data.summary_fields.job_template) ? data.summary_fields.job_template.name : '';
if (fld === 'variables') {
// Parse extra_vars, converting to YAML.
if ($.isEmptyObject(data.extra_vars) || data.extra_vars === "{}" || data.extra_vars === "null" ||
data.extra_vars === "" || data.extra_vars === null) {
$scope.variables = "---";
} else {
json_obj = JSON.parse(data.extra_vars);
$scope.variables = jsyaml.safeDump(json_obj);
}
master.variables = $scope.variables;
}
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];
}
for (fld in form.statusFields) {
if (data[fld] !== null && data[fld] !== undefined) {
if (fld === 'created') {
// Convert created date to local time zone
cDate = new Date(data.created);
$scope.created = FormatDate(cDate);
} else {
$scope[fld] = data[fld];
}
}
}
$scope.statusToolTip = JobStatusToolTip(data.status);
$('form[name="jobs_form"] input[type="text"], form[name="jobs_form"] jobs_form textarea').attr('readonly', 'readonly');
$('form[name="jobs_form"] select').prop('disabled', 'disabled');
$('form[name="jobs_form"] .lookup-btn').prop('disabled', 'disabled');
$('form[name="jobs_form"] .buttons, form[name="jobs_form"] hr').hide();
$scope.url = data.url;
related = data.related;
for (set in form.related) {
if (related[set]) {
relatedSets[set] = {
url: related[set],
iterator: form.related[set].iterator
};
if (fld === 'variables') {
// Parse extra_vars, converting to YAML.
if ($.isEmptyObject(data.extra_vars) || data.extra_vars === "{}" || data.extra_vars === "null" ||
data.extra_vars === "" || data.extra_vars === null) {
$scope.variables = "---";
} else {
json_obj = JSON.parse(data.extra_vars);
$scope.variables = jsyaml.safeDump(json_obj);
}
}
if (JobTemplateForm.fields[fld].type === 'lookup' && data.summary_fields[JobTemplateForm.fields[fld].sourceModel]) {
$scope[JobTemplateForm.fields[fld].sourceModel + '_' + JobTemplateForm.fields[fld].sourceField] =
data.summary_fields[JobTemplateForm.fields[fld].sourceModel][JobTemplateForm.fields[fld].sourceField];
}
}
$scope.stdout_rows = calcRows($scope.result_stdout);
$scope.traceback_rows = calcRows($scope.result_traceback);
LookUpInit({
scope: $scope,
form: form,
current_item: data.inventory,
list: InventoryList,
field: 'inventory'
});
LookUpInit({
scope: $scope,
form: form,
current_item: data.credential,
list: CredentialList,
field: 'credential'
});
LookUpInit({
scope: $scope,
form: form,
current_item: data.project,
list: ProjectList,
field: 'project'
});
// 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.template_url = data.related.job_template;
$scope.$emit('jobLoaded', data.related.cloud_credential);
$scope.id = data.id;
$scope.name = (data.summary_fields && data.summary_fields.job_template) ? data.summary_fields.job_template.name : '';
$scope.statusToolTip = JobStatusToolTip(data.status);
$scope.url = data.url;
$scope.project = data.project;
$scope.$emit('jobLoaded', data.related.cloud_credential, data.project, data.playbook);
})
.error(function (data, status) {
ProcessErrors($scope, data, status, form, { hdr: 'Error!',
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to retrieve job: ' + $routeParams.id + '. GET status: ' + status });
});
@ -460,11 +415,8 @@ function JobsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, J
Wait('stop');
})
.error(function (data, status) {
Wait('stop');
ProcessErrors($scope, data, status, null, {
hdr: 'Error!',
msg: 'Attempt to load job failed. GET returned status: ' + status
});
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
msg: 'Attempt to load job failed. GET returned status: ' + status });
});
};
@ -477,8 +429,8 @@ function JobsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, J
};
}
JobsEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'JobForm',
JobsEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'JobForm', 'JobTemplateForm',
'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'RelatedSearchInit', 'RelatedPaginateInit',
'ReturnToCaller', 'ClearScope', 'InventoryList', 'CredentialList', 'ProjectList', 'LookUpInit', 'PromptPasswords',
'GetBasePath', 'md5Setup', 'FormatDate', 'JobStatusToolTip', 'Wait'
'GetBasePath', 'md5Setup', 'FormatDate', 'JobStatusToolTip', 'Wait', 'Empty'
];

View File

@ -0,0 +1,56 @@
/*********************************************
* Copyright (c) 2014 AnsibleWorks, Inc.
*
* InventoryStatus.js
*
* Use to show inventory sync status
*
*/
angular.module('InventoryStatusDefinition', [])
.value('InventoryStatusForm', {
name: 'inventory_update',
editTitle: 'Inventory Status',
well: false,
'class': 'horizontal-narrow',
fields: {
license_error: {
type: 'alertblock',
'class': 'alert-info',
alertTxt: 'The invenvtory update process exceeded the available number of licensed hosts. ' +
'<strong><a ng-click=\"viewLicense()\" href=\"\">View your license</a></strong> ' +
'for more information.',
ngShow: 'license_error',
closeable: true
},
created: {
label: 'Created',
type: 'text',
readonly: true
},
status: {
label: 'Status',
type: 'text',
readonly: true,
'class': 'nowrap mono-space resizable',
rows: '{{ status_rows }}'
},
result_stdout: {
label: 'Std Out',
type: 'textarea',
ngShow: 'result_stdout',
'class': 'nowrap mono-space resizable',
readonly: true,
rows: '{{ stdout_rows }}'
},
result_traceback: {
label: 'Traceback',
type: 'textarea',
ngShow: 'result_traceback',
'class': 'nowrap mono-space resizable',
readonly: true,
rows: '{{ traceback_rows }}'
}
}
}); //Form

View File

@ -14,6 +14,11 @@ angular.module('JobTemplateFormDefinition', [])
name: 'job_templates',
twoColumns: true,
well: true,
base: 'job_templates',
collapse: true,
collapseTitle: "Properties",
collapseMode: 'edit',
collapseOpenFirst: true, //Always open first panel
actions: {
stream: {
@ -152,7 +157,7 @@ angular.module('JobTemplateFormDefinition', [])
column: 1,
awPopOver: "<p>Provide a host pattern to further constrain the list of hosts that will be managed or affected by the playbook. " +
"Multiple patterns can be separated by &#59; &#58; or &#44;</p><p>For more information and examples see " +
"<a href=\"http://docs.ansible.com/intro_patterns.html\" target=\"_blank\">the Patters top at docs.ansible.com</a>.</p>",
"<a href=\"http://docs.ansible.com/intro_patterns.html\" target=\"_blank\">the Patters topic at docs.ansible.com</a>.</p>",
dataTitle: 'Limit',
dataPlacement: 'right',
dataContainer: "body"

View File

@ -13,11 +13,12 @@ angular.module('JobFormDefinition', [])
editTitle: '{{ id }} - {{ name }}',
name: 'jobs',
well: true,
base: 'jobs',
collapse: true,
collapseMode: 'edit',
collapseTitle: 'Job Template',
twoColumns: true,
collapseTitle: 'Job Status',
collapseOpenFirst: true, //Always open first panel
navigationLinks: {
details: {
href: "/#/jobs/{{ job_id }}",
@ -39,267 +40,7 @@ angular.module('JobFormDefinition', [])
},
fields: {
name: {
label: 'Job Template',
type: 'text',
addRequired: false,
editRequired: false,
readonly: true,
column: 1
},
description: {
label: 'Description',
type: 'text',
addRequired: false,
editRequired: false,
column: 1
},
job_type: {
label: 'Job Type',
type: 'select',
ngOptions: 'type.label for type in job_type_options',
"default": 'run',
addRequired: true,
editRequired: true,
awPopOver: "<p>When this template is submitted as a job, setting the type to <em>run</em> will execute the playbook, running tasks " +
" on the selected hosts.</p> <p>Setting the type to <em>check</em> will not execute the playbook. Instead, ansible will check playbook " +
" syntax, test environment setup and report problems.</p>",
dataTitle: 'Job Type',
dataPlacement: 'right',
dataContainer: 'body',
column: 1
},
inventory: {
label: 'Inventory',
type: 'lookup',
sourceModel: 'inventory',
sourceField: 'name',
addRequired: true,
editRequired: true,
ngClick: 'lookUpInventory()',
column: 1,
awPopOver: "<p>Select the inventory containing the hosts you want this job to manage.</p>",
dataTitle: 'Inventory',
dataPlacement: 'right',
dataContainer: "body"
},
project: {
label: 'Project',
type: 'lookup',
sourceModel: 'project',
sourceField: 'name',
addRequired: true,
editRequired: true,
ngClick: 'lookUpProject()',
column: 1,
awPopOver: "<p>Select the project containing the playbook you want this job to execute.</p>",
dataTitle: 'Project',
dataPlacement: 'right',
dataContainer: "body"
},
playbook: {
label: 'Playbook',
type: 'select',
ngOptions: 'book for book in playbook_options',
id: 'playbook-select',
addRequired: true,
editRequired: true,
column: 1,
awPopOver: "<p>Select the playbook to be executed by this job.</p>",
dataTitle: 'Playbook',
dataPlacement: 'right',
dataContainer: "body"
},
credential: { // FIXME: Lookup only credentials with kind=ssh
label: 'Credential',
type: 'lookup',
sourceModel: 'credential',
sourceField: 'name',
ngClick: 'lookUpCredential()',
addRequired: false,
editRequired: false,
column: 1,
awPopOver: "<p>Select the credential you want the job to use when accessing the remote hosts. Choose the credential containing " +
" the username and SSH key or password that Ansbile will need to log into the remote hosts.</p>",
dataTitle: 'Credential',
dataPlacement: 'right',
dataContainer: "body"
},
cloud_credential: { // FIXME: Lookup only credentials with kind=aws/rax
label: 'Cloud Credential',
type: 'lookup',
sourceModel: 'cloud_credential',
sourceField: 'name',
ngClick: 'lookUpCredential()',
addRequired: false,
editRequired: false,
column: 1,
awPopOver: "<p>Selecting an optional cloud credential in the job template will pass along the access credentials to the " +
"running playbook, allowing provisioning into the cloud without manually passing parameters to the included modules.</p>",
dataTitle: 'Cloud Credential',
dataPlacement: 'right',
dataContainer: "body"
},
forks: {
label: 'Forks',
id: 'forks-number',
type: 'number',
integer: true,
min: 0,
spinner: true,
"class": 'input-small',
"default": '0',
addRequired: false,
editRequired: false,
column: 1,
disabled: true,
awPopOver: "<p>The number of parallel or simultaneous processes to use while executing the playbook.</p>",
dataContainer: 'body',
dataTitle: 'Forks',
dataPlacement: 'right'
},
limit: {
label: 'Limit',
type: 'text',
addRequired: false,
editRequired: false,
column: 1,
awPopOver: "<p>Provide a host pattern to further constrain the list of hosts that will be managed or affected by the playbook. " +
"Multiple patterns can be separated by &#59; &#58; or &#44;</p><p>For more information and examples see the " +
"<a href=\"http://ansible.cc/docs/patterns.html#selecting-targets\" target=\"_blank\">Selecting Targets section</a> under Inventory and Patterns " +
" in the Ansible documentation.</p>",
dataContainer: 'body',
dataTitle: 'Limit',
dataPlacement: 'right'
},
verbosity: {
label: 'Verbosity',
type: 'select',
ngOptions: 'v.label for v in verbosity_options',
"default": 0,
addRequired: true,
editRequired: true,
column: 1,
awPopOver: "<p>Control the level of output ansible will produce as the playbook executes.</p>",
dataTitle: 'Verbosity',
dataPlacement: 'right',
dataContainer: 'body'
},
variables: {
label: 'Extra Variables',
type: 'textarea',
rows: 6,
"class": 'span12',
addRequired: false,
editRequired: false,
column: 2,
awPopOver: "<p>Pass extra command line variables to the playbook. This is the -e or --extra-vars command line parameter " +
"for ansible-playbook. Provide key/value pairs using either YAML or JSON.</p>" +
"JSON:<br />\n" +
"<blockquote>{<br />\"somevar\": \"somevalue\",<br />\"password\": \"magic\"<br /> }</blockquote>\n" +
"YAML:<br />\n" +
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n",
dataTitle: 'Extra Variables',
dataContainer: 'body',
dataPlacement: 'right'
},
job_tags: {
label: 'Job Tags',
type: 'textarea',
rows: 1,
addRequired: false,
editRequired: false,
'class': 'span12',
column: 2,
awPopOver: "<p>Provide a comma separated list of tags.</p>\n" +
"<p>Tags are useful when you have a large playbook, and you want to run a specific part of a play or task.</p>" +
"<p>For example, you might have a task consisiting of a long list of actions. Tag values can be assigned to each action. " +
"Suppose the actions have been assigned tag values of &quot;configuration&quot;, &quot;packages&quot; and &quot;install&quot;.</p>" +
"<p>If you just want to run the &quot;configuration&quot; and &quot;packages&quot; actions, you would enter the following here " +
"in the Job Tags field:</p>\n" +
"<blockquote>configuration,packages</blockquote>\n",
dataTitle: "Job Tags",
dataContainer: 'body',
dataPlacement: "right"
},
allow_callbacks: {
label: 'Allow Callbacks',
type: 'checkbox',
addRequired: false,
editRequird: false,
trueValue: 'true',
falseValue: 'false',
ngChange: "toggleCallback('host_config_key')",
"class": "span12",
column: 2,
awPopOver: "<p>Create a callback URL a host can use to contact Tower and request a configuration update " +
"using the job template. The URL will look like the following:</p>\n" +
"<p class=\"code-breakable\">http://your.server.com:999/api/v1/job_templates/1/callback/</p>" +
"<p>The request from the host must be a POST. Here is an example using curl:</p>\n" +
"<p class=\"code-breakable\">curl --data \"host_config_key=5a8ec154832b780b9bdef1061764ae5a\" " +
"http://your.server.com:999/api/v1/job_templates/1/callback/</p>\n" +
"<p>Note the requesting host must be defined in your inventory. If ansible fails to locate the host either by name or IP address " +
"in one of your defined inventories, the request will be denied.</p>" +
"<p>Successful requests will result in an entry on the Jobs tab, where the results and history can be viewed.</p>",
dataPlacement: 'right',
dataContainer: 'body',
dataTitle: 'Callback URL'
},
callback_url: {
label: 'Callback URL',
type: 'text',
addRequired: false,
editRequired: false,
readonly: true,
column: 2,
required: false,
'class': 'span12',
awPopOver: "<p>Using this URL a host can contact Tower and request a configuration update using the job " +
"template. The request from the host must be a POST. Here is an example using curl:</p>\n" +
"<p class=\"code-breakable\">curl --data \"host_config_key=5a8ec154832b780b9bdef1061764ae5a\" " +
"http://your.server.com:999/api/v1/job_templates/1/callback/</p>\n" +
"<p>Note the requesting host must be defined in your inventory. If ansible fails to locate the host either by name or IP address " +
"in one of your defined inventories, the request will be denied.</p>" +
"<p>Successful requests will result in an entry on the Jobs tab, where the results and history can be viewed.</p>",
dataPlacement: 'right',
dataContainer: 'body',
dataTitle: 'Callback URL'
},
host_config_key: {
label: 'Host Config Key',
type: 'text',
ngShow: "allow_callbacks",
genMD5: true,
column: 2,
awPopOver: "<p>When contacting Tower using the callback URL, the calling host must authenticate by including " +
"this key in the POST data of the request. Here's an example using curl:</p>\n" +
"<p class=\"code-breakable\">curl --data \"host_config_key=5a8ec154832b780b9bdef1061764ae5a\" " +
"http://your.server.com:999/api/v1/job_templates/1/callback/</p>\n",
dataPlacement: 'right',
dataContainer: 'body'
}
},
buttons: {
save: {
label: 'Save',
icon: 'icon-ok',
"class": 'btn-success',
ngClick: 'formSave()',
ngDisabled: true
},
reset: {
ngClick: 'formReset()',
label: 'Reset',
icon: 'icon-undo',
'class': 'btn btn-default',
ngDisabled: true
}
},
statusFields: {
status: {
//label: 'Job Status',
type: 'custom',
control: "<div class=\"job-detail-status\"><span style=\"padding-right: 15px; font-weight: bold;\">Status</span> " +
"<i class=\"fa icon-job-{{ status }}\"></i> {{ status }}</div>",
@ -330,7 +71,7 @@ angular.module('JobFormDefinition', [])
}
},
statusActions: {
actions: {
refresh: {
dataPlacement: 'top',
icon: "icon-refresh",
@ -341,6 +82,17 @@ angular.module('JobFormDefinition', [])
awToolTip: "Refresh the page",
ngClick: "refresh()"
}
}
},
related: {
job_template: {
type: 'collection',
title: 'Job Tempate',
iterator: 'job',
index: false,
open: false,
fields: { }
}
}
});

View File

@ -11,7 +11,8 @@
angular.module('GroupsHelper', ['RestServices', 'Utilities', 'ListGenerator', 'GroupListDefinition', 'SearchHelper',
'PaginationHelpers', 'ListGenerator', 'AuthService', 'GroupsHelper', 'InventoryHelper', 'SelectionHelper',
'JobSubmissionHelper', 'RefreshHelper', 'PromptDialog', 'CredentialsListDefinition', 'InventoryTree'
'JobSubmissionHelper', 'RefreshHelper', 'PromptDialog', 'CredentialsListDefinition', 'InventoryTree',
'InventoryStatusDefinition'
])
.factory('GetSourceTypeOptions', ['Rest', 'ProcessErrors', 'GetBasePath',

View File

@ -92,7 +92,7 @@ angular.module('InventoriesListDefinition', [])
},
"delete": {
label: 'Delete',
ngClick: "deleteInventory(inventory.id, inventory.names')",
ngClick: "deleteInventory(inventory.id, inventory.names)",
awToolTip: 'Delete inventory',
dataPlacement: 'top'
}

View File

@ -99,7 +99,7 @@ angular.module('InventoryGroupsDefinition', [])
cancel: {
//label: 'Cancel',
mode: 'all',
ngClick: "cancelUpdate({{ group.id }})",
ngClick: "cancelUpdate(group.id)",
awToolTip: "Cancel sync process",
'class': 'red-txt',
ngShow: "group.id > 1 && (group.status == 'running' || group.status == 'pending' || group.status == 'updating')",
@ -108,7 +108,7 @@ angular.module('InventoryGroupsDefinition', [])
edit: {
//label: 'Edit',
mode: 'all',
ngClick: "editGroup({{ group.group_id + ',' + group.id }})",
ngClick: "editGroup(group.group_id, group.id)",
awToolTip: 'Edit group',
ngShow: "group.id > 1", // hide for all hosts
dataPlacement: "top"
@ -116,7 +116,7 @@ angular.module('InventoryGroupsDefinition', [])
"delete": {
//label: 'Delete',
mode: 'all',
ngClick: "deleteGroup({{ group.id + ',' + group.group_id }})",
ngClick: "deleteGroup(group.id, group.group_id)",
awToolTip: 'Delete group',
ngShow: "group.id != 1", // hide for all hosts
dataPlacement: "top"

View File

@ -64,14 +64,14 @@ angular.module('JobEventsListDefinition', [])
}],
nosort: true,
searchable: false,
ngClick: 'viewJobEvent({{ jobevent.id }})',
ngClick: 'viewJobEvent(jobevent.id)',
awToolTip: '{{ jobevent.statusBadgeToolTip }}',
dataPlacement: 'top',
badgeIcon: 'fa icon-job-{{ jobevent.status }}',
badgePlacement: 'left',
badgeToolTip: '{{ jobevent.statusBadgeToolTip }}',
badgeTipPlacement: 'top',
badgeNgClick: 'viewJobEvent({{ jobevent.id }})'
badgeNgClick: 'viewJobEvent(jobevent.id)'
},
event_display: {
label: 'Event',

View File

@ -407,12 +407,16 @@ dd {
.help-link,
.help-link:active,
.help-link:visited {
.help-link:visited,
.ui-widget-content a.help-link,
.ui-widget-content a.help-link:active,
.ui-widget-content a.help-link:visited {
color: @grey;
text-decoration: none;
}
.help-link:hover {
.help-link:hover,
.ui-widget-content a.help-link:hover {
color: @black;
text-decoration: none;
}

View File

@ -59,11 +59,11 @@ angular.module('AuthService', ['ngCookies', 'Utilities'])
var scope = angular.element(document.getElementById('main-view')).scope();
scope.$destroy();
$rootScope.$destroy();
$cookieStore.remove('accordions');
$cookieStore.remove('token');
$cookieStore.remove('token_expires');
$cookieStore.remove('current_user');
$cookieStore.remove('lastPath');
$cookieStore.remove('license');
$cookieStore.put('userLoggedIn', false);
$cookieStore.put('sessionExpired', false);
$cookieStore.remove('lastPath', '/home');

View File

@ -113,7 +113,6 @@ angular.module('InventoryTree', ['Utilities', 'RestServices', 'GroupsHelper', 'P
event_level: level,
children: children,
ngicon: (sorted[i].children.length > 0) ? 'fa fa-minus-square-o node-toggle' : 'fa fa-square-o node-no-toggle',
ngclick: 'toggle(' + id + ')',
related: {
children: (sorted[i].children.length > 0) ? sorted[i].related.children : '',
inventory_source: sorted[i].related.inventory_source

View File

@ -262,12 +262,12 @@ angular.module('Utilities', ['RestServices', 'Utilities'])
});
}
} else {
if (/_/.test(paths[i])) {
//if (/_/.test(paths[i])) {
// replace '_' with space and uppercase each word
paths[i] = paths[i].replace(/(?:^|_)\S/g, toUppercase)
.replace(/_/g, ' ');
}
title = paths[i].charAt(0).toUpperCase() + paths[i].slice(1);
//}
//title = paths[i].charAt(0).toUpperCase() + paths[i].slice(1);
title = paths[i].replace(/(?:^|_)\S/g, toUppercase).replace(/_/g, ' ');
$rootScope.breadcrumbs.push({
title: title,
path: ppath + '/' + paths[i]
@ -456,7 +456,7 @@ angular.module('Utilities', ['RestServices', 'Utilities'])
/*
* Wrapper for data filter- an attempt to insure all dates display in
* the same format. Pass in date object.
* the same format. Pass in date object or string. See: http://docs.angularjs.org/api/ng.filter:date
*/
.factory('FormatDate', ['$filter',
function ($filter) {

View File

@ -6,6 +6,7 @@
* to access the primary model objects.
*
*/
'use strict';
angular.module('ApiLoader', ['Utilities'])

View File

@ -321,9 +321,9 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
//
// Enable jqueryui slider widget on a numeric input field
//
// <input type="number" ng-slider name="myfield" min="0" max="100" />
// <input type="number" aw-slider name="myfield" min="0" max="100" />
//
.directive('ngSlider', [ function() {
.directive('awSlider', [ function() {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
@ -333,6 +333,7 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
step: 1,
min: elm.attr('min'),
max: elm.attr('max'),
disabled: (elm.attr('readonly')) ? true : false,
slide: function(e,u) {
ctrl.$setViewValue(u.value);
ctrl.$setValidity('required',true);
@ -384,9 +385,9 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
//
// Enable jqueryui spinner widget on a numeric input field
//
// <input type="number" ng-spinner name="myfield" min="0" max="100" />
// <input type="number" aw-spinner name="myfield" min="0" max="100" />
//
.directive('ngSpinner', [ function() {
.directive('awSpinner', [ function() {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
@ -398,6 +399,7 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job
min: elm.attr('min'),
max: elm.attr('max'),
numberFormat: "d",
disabled: (elm.attr('readonly')) ? true : false,
spin: function(e, u) {
ctrl.$setViewValue(u.value);
ctrl.$setValidity('required',true);

View File

@ -13,9 +13,9 @@
angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies', 'Utilities'])
.factory('GenerateForm', ['$rootScope', '$location', '$cookieStore', '$compile', 'SearchWidget', 'PaginateWidget', 'Attr',
'Icon', 'Column', 'NavigationLink', 'HelpCollapse', 'Button', 'DropDown', 'Empty', 'SelectIcon',
'Icon', 'Column', 'NavigationLink', 'HelpCollapse', 'Button', 'DropDown', 'Empty', 'SelectIcon', 'Store',
function ($rootScope, $location, $cookieStore, $compile, SearchWidget, PaginateWidget, Attr, Icon, Column, NavigationLink,
HelpCollapse, Button, DropDown, Empty, SelectIcon) {
HelpCollapse, Button, DropDown, Empty, SelectIcon, Store) {
return {
setForm: function (form) { this.form = form; },
@ -283,6 +283,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies', 'Utilities'])
active: 0
});
} else {
// For help collapse, toggle the plus/minus icon
this.scope.accordionToggle = function (selector) {
$(selector).collapse('toggle');
if ($(selector + '-icon').hasClass('fa-minus')) {
@ -293,25 +294,28 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies', 'Utilities'])
};
$('.jqui-accordion').each(function () {
var active = false,
list = $cookieStore.get('accordions'),
list = Store('accordions'),
found = false,
id, base, i;
if (list) {
id = $(this).attr('id');
base = ($location.path().replace(/^\//, '').split('/')[0]);
for (i = 0; i < list.length && found === false; i++) {
if (list[i].base === base && list[i].id === id) {
found = true;
active = list[i].active;
if ($(this).attr('data-open-first')) {
active = 0;
}
else {
if (list) {
id = $(this).attr('id');
base = ($location.path().replace(/^\//, '').split('/')[0]);
for (i = 0; i < list.length && found === false; i++) {
if (list[i].base === base && list[i].id === id) {
found = true;
active = list[i].active;
}
}
}
}
if (found === false && $(this).attr('data-open') === 'true') {
active = 0;
if (found === false && $(this).attr('data-open') === 'true') {
active = 0;
}
}
$(this).accordion({
@ -319,16 +323,19 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies', 'Utilities'])
heightStyle: 'content',
active: active,
activate: function () {
// Maintain in local storage of list of all accordions by page, recording
// the active panel for each. If user navigates away and comes back,
// we can activate the last panely viewed.
$('.jqui-accordion').each(function () {
var active = $(this).accordion('option', 'active'),
id = $(this).attr('id'),
base = ($location.path().replace(/^\//, '').split('/')[0]),
list = $cookieStore.get('accordions'),
i, found;
if (list === null || list === undefined) {
list = Store('accordions'),
found = false,
i;
if (!list) {
list = [];
}
found = false;
for (i = 0; i < list.length && found === false; i++) {
if (list[i].base === base && list[i].id === id) {
found = true;
@ -342,7 +349,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies', 'Utilities'])
active: active
});
}
$cookieStore.put('accordions', list);
Store('accordions', list);
});
}
});
@ -728,7 +735,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies', 'Utilities'])
html += (options.mode === 'edit' && field.editRequired) ? "required " : "";
html += (options.mode === 'add' && field.addRequired) ? "required " : "";
html += (field.multiSelect) ? "multiple " : "";
html += (field.readonly) ? "readonly " : "";
html += (field.readonly) ? "disabled " : "";
html += (field.awRequiredWhen) ? "data-awrequired-init=\"" + field.awRequiredWhen.init + "\" aw-required-when=\"" +
field.awRequiredWhen.variable + "\" " : "";
html += ">\n";
@ -762,11 +769,14 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies', 'Utilities'])
html += "<input ";
html += (field.spinner) ? "" : "type=\"text\" ";
html += "\" value=\"" + field['default'] + "\" ";
html += "class=\"form-control";
html += "class=\"";
if (!field.slider && !field.spinner) {
html += "form-control";
}
html += (field['class']) ? " " + field['class'] : "";
html += "\" ";
html += (field.slider) ? "ng-slider=\"" + fld + "\" " : "";
html += (field.spinner) ? "ng-spinner=\"" + fld + "\" " : "";
html += (field.slider) ? "aw-slider=\"" + fld + "\" " : "";
html += (field.spinner) ? "aw-spinner=\"" + fld + "\" " : "";
html += "ng-model=\"" + fld + '" ';
html += 'name="' + fld + '" ';
html += buildId(field, fld, this.form);
@ -937,7 +947,9 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies', 'Utilities'])
html += "<div class=\"input-group\">\n";
html += "<span class=\"input-group-btn\">\n";
html += "<button type=\"button\" class=\"lookup-btn btn btn-default\" " + this.attr(field, 'ngClick') + "><i class=\"fa fa-search\"></i></button>\n";
html += "<button type=\"button\" class=\"lookup-btn btn btn-default\" " + this.attr(field, 'ngClick');
html += (field.readonly || field.showonly) ? " disabled " : "";
html += "><i class=\"fa fa-search\"></i></button>\n";
html += "</span>\n";
html += "<input type=\"text\" class=\"form-control input-medium lookup\" ";
html += "ng-model=\"" + field.sourceModel + '_' + field.sourceField + "\" ";
@ -947,6 +959,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies', 'Utilities'])
html += (field.id) ? this.attr(field, 'id') : "";
html += (field.placeholder) ? this.attr(field, 'placeholder') : "";
html += (options.mode === 'edit' && field.editRequired) ? "required " : "";
html += (field.readonly || field.showonly) ? "readonly " : "";
html += (field.awRequiredWhen) ? "data-awrequired-init=\"" + field.awRequiredWhen.init + "\" aw-required-when=\"" +
field.awRequiredWhen.variable + "\" " : "";
html += " awlookup >\n";
@ -1063,7 +1076,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies', 'Utilities'])
// Generate HTML. Do NOT call this function directly. Called by inject(). Returns an HTML
// string to be injected into the current view.
//
var act, action, btn, button, fld, field, html = '', i, section, group,
var btn, button, fld, field, html = '', i, section, group,
tab, sectionShow, offset, width;
if (!this.modal && (options.breadCrumbs === undefined || options.breadCrumbs === true)) {
@ -1073,263 +1086,229 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies', 'Utilities'])
html += this.breadCrumbs(options);
}
}
if (this.form.collapse && this.form.collapseMode === options.mode) {
html += "<div id=\"" + this.form.name + "-collapse-0\" ";
html += (this.form.collapseOpen) ? "data-open=\"true\" " : "";
html += (this.form.collapseOpenFirst) ? "data-open-first=\"true\" " : "";
html += "class=\"jqui-accordion\">\n";
html += "<h3>" + this.form.collapseTitle + "</h3>\n";
html += "<div>\n";
options.collapseAlreadyStarted = true;
}
if ((!this.modal && this.form.statusFields)) {
// Add status fields section (used in Jobs form)
html += "<div class=\"well\">\n";
if (this.form.statusActions) {
html += "<div class=\"list-actions\">\n";
for (action in this.form.statusActions) {
act = this.form.statusActions[action];
// Start the well
if (!this.modal && this.has('well')) {
if ( !(this.form.collapse && this.form.collapseMode === options.mode)) {
html += "<div class=\"well\">\n";
}
}
if (!this.modal && this.form.actions) {
html += this.getActions(options);
}
// Add a title and optionally a close button (used on Inventory->Groups)
/*if ((!options.modal) && this.form.showTitle) {
html += "<div class=\"form-title\">";
html += (options.mode === 'edit') ? this.form.editTitle : this.form.addTitle;
if (this.has('titleActions')) {
html += "<div class=\"title-actions pull-right\">\n";
for (btn in this.form.titleActions) {
html += this.button({
btn: act,
action: action,
btn: this.form.titleActions[btn],
action: btn,
toolbar: true
});
}
html += "</div>\n";
}
html += "<div class=\"form status-fields\">\n";
for (fld in this.form.statusFields) {
field = this.form.statusFields[fld];
html += this.buildField(fld, field, options, this.form);
}
html += "</div><!-- status fields -->\n";
html += "</div><!-- well -->\n";
}
html += "</div>\n";
html += "<hr class=\"form-title-hr\">\n";
}*/
if (this.form.fieldsAsHeader) {
html += "<div class=\"well\">\n";
html += "<form class=\"form-inline\" name=\"" + this.form.name + "_form\" id=\"" + this.form.name + "_form\" novalidate >\n";
html += "<form class=\"";
html += (this.form.horizontal) ? "form-horizontal" : "";
html += (this.form['class']) ? ' ' + this.form['class'] : '';
html += "\" name=\"" + this.form.name + "_form\" id=\"" + this.form.name + "_form\" autocomplete=\"off\" novalidate>\n";
html += "<div ng-show=\"flashMessage != null && flashMessage != undefined\" class=\"alert alert-info\">{{ flashMessage }}</div>\n";
if (this.form.twoColumns) {
html += "<div class=\"row\">\n";
html += "<div class=\"col-lg-6\">\n";
for (fld in this.form.fields) {
field = this.form.fields[fld];
html += this.headerField(fld, field, options);
if (field.column === 1) {
html += this.buildField(fld, field, options, this.form);
}
}
html += "</div><!-- column 1 -->\n";
html += "<div class=\"col-lg-6\">\n";
for (fld in this.form.fields) {
field = this.form.fields[fld];
if (field.column === 2) {
html += this.buildField(fld, field, options, this.form);
}
}
html += "</div><!-- column 2 -->\n";
html += "</div>\n";
} else if (this.form.tabs) {
html += "<ul id=\"" + this.form.name + "_tabs\" class=\"nav nav-tabs\">\n";
for (i = 0; i < this.form.tabs.length; i++) {
tab = this.form.tabs[i];
html += "<li";
if (i === 0) {
html += " class=\"active\"";
}
html += "><a href=\"#" + tab.name + "\" data-toggle=\"tab\">" + tab.label + "</a></li>\n";
}
html += "</ul>\n";
html += "<div class=\"tab-content\">\n";
for (i = 0; i < this.form.tabs.length; i++) {
tab = this.form.tabs[i];
html += "<div class=\"tab-pane";
if (i === 0) {
html += " active";
}
html += "\" id=\"" + tab.name + "\">\n";
for (fld in this.form.fields) {
if (this.form.fields[fld].tab === tab.name) {
html += this.buildField(fld, this.form.fields[fld], options, this.form);
}
}
html += "</div>\n";
}
html += "</form>\n";
html += "</div>\n";
} else {
if (this.form.collapse && this.form.collapseMode === options.mode) {
html += "<div id=\"" + this.form.name + "-collapse-0\" ";
html += (this.form.collapseOpen) ? "data-open=\"true\" " : "";
html += "class=\"jqui-accordion\">\n";
html += "<h3>" + this.form.collapseTitle + "</h3>\n";
html += "<div>\n";
options.collapseAlreadyStarted = true;
}
// Start the well
if (!this.modal && this.has('well')) {
if ( !(this.form.collapse && this.form.collapseMode === options.mode)) {
html += "<div class=\"well\">\n";
}
}
if (!this.modal && this.form.actions) {
html += this.getActions(options);
}
// Add a title and optionally a close button (used on Inventory->Groups)
if ((!options.modal) && this.form.showTitle) {
html += "<div class=\"form-title\">";
html += (options.mode === 'edit') ? this.form.editTitle : this.form.addTitle;
if (this.has('titleActions')) {
html += "<div class=\"title-actions pull-right\">\n";
for (btn in this.form.titleActions) {
html += this.button({
btn: this.form.titleActions[btn],
action: btn,
toolbar: true
});
// original, single-column form
section = '';
group = '';
for (fld in this.form.fields) {
field = this.form.fields[fld];
if (!(options.modal && field.excludeModal)) {
if (field.group && field.group !== group) {
if (group !== '') {
html += "</div>\n";
}
html += "<div class=\"well\">\n";
html += "<h5>" + field.group + "</h5>\n";
group = field.group;
}
html += "</div>\n";
if (field.section && field.section !== section) {
if (section !== '') {
html += "</div>\n";
} else {
html += "</div>\n";
html += "<div id=\"" + this.form.name + "-collapse\" class=\"jqui-accordion-modal\">\n";
}
sectionShow = (this.form[field.section + 'Show']) ? " ng-show=\"" + this.form[field.section + 'Show'] + "\"" : "";
html += "<h3" + sectionShow + ">" + field.section + "</h3>\n";
html += "<div" + sectionShow + ">\n";
section = field.section;
}
html += this.buildField(fld, field, options, this.form);
}
}
if (section !== '') {
html += "</div>\n</div>\n";
}
if (group !== '') {
html += "</div>\n";
html += "<hr class=\"form-title-hr\">\n";
}
html += "<form class=\"";
html += (this.form.horizontal) ? "form-horizontal" : "";
html += (this.form['class']) ? ' ' + this.form['class'] : '';
html += "\" name=\"" + this.form.name + "_form\" id=\"" + this.form.name + "_form\" autocomplete=\"off\" novalidate>\n";
html += "<div ng-show=\"flashMessage != null && flashMessage != undefined\" class=\"alert alert-info\">{{ flashMessage }}</div>\n";
if (this.form.twoColumns) {
html += "<div class=\"row\">\n";
html += "<div class=\"col-lg-6\">\n";
for (fld in this.form.fields) {
field = this.form.fields[fld];
if (field.column === 1) {
html += this.buildField(fld, field, options, this.form);
}
}
html += "</div><!-- column 1 -->\n";
html += "<div class=\"col-lg-6\">\n";
for (fld in this.form.fields) {
field = this.form.fields[fld];
if (field.column === 2) {
html += this.buildField(fld, field, options, this.form);
}
}
html += "</div><!-- column 2 -->\n";
html += "</div>\n";
} else if (this.form.tabs) {
html += "<ul id=\"" + this.form.name + "_tabs\" class=\"nav nav-tabs\">\n";
for (i = 0; i < this.form.tabs.length; i++) {
tab = this.form.tabs[i];
html += "<li";
if (i === 0) {
html += " class=\"active\"";
}
html += "><a href=\"#" + tab.name + "\" data-toggle=\"tab\">" + tab.label + "</a></li>\n";
}
html += "</ul>\n";
html += "<div class=\"tab-content\">\n";
for (i = 0; i < this.form.tabs.length; i++) {
tab = this.form.tabs[i];
html += "<div class=\"tab-pane";
if (i === 0) {
html += " active";
}
html += "\" id=\"" + tab.name + "\">\n";
for (fld in this.form.fields) {
if (this.form.fields[fld].tab === tab.name) {
html += this.buildField(fld, this.form.fields[fld], options, this.form);
}
}
html += "</div>\n";
}
html += "</div>\n";
} else {
// original, single-column form
section = '';
group = '';
for (fld in this.form.fields) {
field = this.form.fields[fld];
if (!(options.modal && field.excludeModal)) {
if (field.group && field.group !== group) {
if (group !== '') {
html += "</div>\n";
}
html += "<div class=\"well\">\n";
html += "<h5>" + field.group + "</h5>\n";
group = field.group;
}
if (field.section && field.section !== section) {
if (section !== '') {
html += "</div>\n";
} else {
html += "</div>\n";
html += "<div id=\"" + this.form.name + "-collapse\" class=\"jqui-accordion-modal\">\n";
}
sectionShow = (this.form[field.section + 'Show']) ? " ng-show=\"" + this.form[field.section + 'Show'] + "\"" : "";
html += "<h3" + sectionShow + ">" + field.section + "</h3>\n";
html += "<div" + sectionShow + ">\n";
section = field.section;
}
html += this.buildField(fld, field, options, this.form);
}
}
if (section !== '') {
html += "</div>\n</div>\n";
}
if (group !== '') {
html += "</div>\n";
}
}
//buttons
if (!this.modal) {
if (this.has('buttons')) {
if (this.form.twoColumns) {
html += "<div class=\"row\">\n";
html += "<div class=\"col-lg-12\">\n";
html += "<hr />\n";
}
html += "<div class=\"buttons\" ";
html += "id=\"" + this.form.name + "_controls\" ";
html += ">\n";
if (this.form.horizontal) {
offset = 2;
if (this.form.buttons.labelClass) {
offset = parseInt(this.form.buttons.labelClass.replace(/[A-Z,a-z,-]/g, ''));
}
width = 12 - offset;
html += "<div class=\"col-lg-offset-" + offset + " col-lg-" + width + ">\n";
}
for (btn in this.form.buttons) {
if (typeof this.form.buttons[btn] === 'object') {
button = this.form.buttons[btn];
// Set default color and label for Save and Reset
if (btn === 'save') {
button.label = 'Save';
button['class'] = 'btn-success';
}
if (btn === 'reset') {
button.label = 'Reset';
button['class'] = 'btn-default';
}
// Build button HTML
html += "<button type=\"button\" ";
html += "class=\"btn btn-sm";
html += (button['class']) ? " " + button['class'] : "";
html += "\" ";
html += "id=\"" + this.form.name + "_" + btn + "_btn\" ";
if (button.ngClick) {
html += this.attr(button, 'ngClick');
}
if (button.ngDisabled) {
if (btn !== 'reset') {
html += "ng-disabled=\"" + this.form.name + "_form.$pristine || " + this.form.name + "_form.$invalid";
html += (this.form.allowReadonly) ? " || " + this.form.name + "ReadOnly == true" : "";
html += "\" ";
} else {
html += "ng-disabled=\"" + this.form.name + "_form.$pristine";
html += (this.form.allowReadonly) ? " || " + this.form.name + "ReadOnly == true" : "";
html += "\" ";
}
}
html += ">";
html += SelectIcon({
action: btn
});
html += " " + button.label + "</button>\n";
}
}
html += "</div><!-- buttons -->\n";
if (this.form.horizontal) {
html += "</div>\n";
}
if (this.form.twoColumns) {
html += "</div>\n";
html += "</div>\n";
}
}
}
html += "</form>\n";
if (!this.modal && this.has('well')) {
if ( !(this.form.collapse && this.form.collapseMode === options.mode)) {
html += "</div>\n";
}
}
if (this.form.collapse && this.form.collapseMode === options.mode) {
html += "</div>\n";
//html += "</div>\n";
}
}
//buttons
if (!this.modal) {
if (this.has('buttons')) {
if (this.form.twoColumns) {
html += "<div class=\"row\">\n";
html += "<div class=\"col-lg-12\">\n";
html += "<hr />\n";
}
html += "<div class=\"buttons\" ";
html += "id=\"" + this.form.name + "_controls\" ";
html += ">\n";
if (this.form.horizontal) {
offset = 2;
if (this.form.buttons.labelClass) {
offset = parseInt(this.form.buttons.labelClass.replace(/[A-Z,a-z,-]/g, ''));
}
width = 12 - offset;
html += "<div class=\"col-lg-offset-" + offset + " col-lg-" + width + ">\n";
}
for (btn in this.form.buttons) {
if (typeof this.form.buttons[btn] === 'object') {
button = this.form.buttons[btn];
// Set default color and label for Save and Reset
if (btn === 'save') {
button.label = 'Save';
button['class'] = 'btn-success';
}
if (btn === 'reset') {
button.label = 'Reset';
button['class'] = 'btn-default';
}
// Build button HTML
html += "<button type=\"button\" ";
html += "class=\"btn btn-sm";
html += (button['class']) ? " " + button['class'] : "";
html += "\" ";
html += "id=\"" + this.form.name + "_" + btn + "_btn\" ";
if (button.ngClick) {
html += this.attr(button, 'ngClick');
}
if (button.ngDisabled) {
if (btn !== 'reset') {
html += "ng-disabled=\"" + this.form.name + "_form.$pristine || " + this.form.name + "_form.$invalid";
html += (this.form.allowReadonly) ? " || " + this.form.name + "ReadOnly == true" : "";
html += "\" ";
} else {
html += "ng-disabled=\"" + this.form.name + "_form.$pristine";
html += (this.form.allowReadonly) ? " || " + this.form.name + "ReadOnly == true" : "";
html += "\" ";
}
}
html += ">";
html += SelectIcon({
action: btn
});
html += " " + button.label + "</button>\n";
}
}
html += "</div><!-- buttons -->\n";
if (this.form.horizontal) {
html += "</div>\n";
}
if (this.form.twoColumns) {
html += "</div>\n";
html += "</div>\n";
}
}
}
html += "</form>\n";
if (!this.modal && this.has('well')) {
if ( !(this.form.collapse && this.form.collapseMode === options.mode)) {
html += "</div>\n";
}
}
if (this.form.collapse && this.form.collapseMode === options.mode) {
html += "</div>\n";
//html += "</div>\n";
}
if ((!this.modal) && options.related && this.form.related) {
html += this.buildCollections(options);
}

View File

@ -470,8 +470,8 @@ angular.module('GeneratorHelpers', ['GeneratorHelpers'])
// Add collapse/expand icon --used on job_events page
if (list.hasChildren && field.hasChildren) {
html += "<div class=\"level level-{{ " + list.iterator + ".event_level }}\"><a href=\"\" ng-click=\"{{ " +
list.iterator + ".ngclick }}\"> " +
html += "<div class=\"level level-{{ " + list.iterator + ".event_level }}\"><a href=\"\" ng-click=\"toggle(" +
list.iterator + ".id)\"> " +
"<i class=\"{{ " + list.iterator + ".ngicon }}\"></i></a></div>";
//ng-show=\"'\{\{ " + list.iterator + ".related.children \}\}' !== ''\"
}

View File

@ -406,13 +406,13 @@ angular.module('ListGenerator', ['GeneratorHelpers'])
html += "</tr>\n";
// Message for when a collection is empty
html += "<tr class=\"info\" ng-show=\"" + list.iterator + "Loading == false && " + list.name + ".length == 0\">\n";
html += "<td colspan=\"" + cnt + "\">No records matched your search.</td>\n";
html += "<tr class=\"loading-info\" ng-show=\"" + list.iterator + "Loading == false && " + list.name + ".length == 0\">\n";
html += "<td colspan=\"" + cnt + "\"><div class=\"loading-info\">No records matched your search.</div></td>\n";
html += "</tr>\n";
// Message for loading
html += "<tr class=\"info\" ng-show=\"" + list.iterator + "Loading == true\">\n";
html += "<td colspan=\"" + cnt + "\">Loading...</td>\n";
html += "<tr class=\"loading-info\" ng-show=\"" + list.iterator + "Loading == true\">\n";
html += "<td colspan=\"" + cnt + "\"><div class=\"loading-info\">Loading...</div></td>\n";
html += "</tr>\n";
// End List

View File

@ -22,14 +22,14 @@
</script>
<script src="{{ STATIC_URL }}js/config.js"></script>
<script src="{{ STATIC_URL }}lib/jquery/jquery.min.js"></script>
<script src="{{ STATIC_URL }}lib/angular/angular.js"></script>
<script src="{{ STATIC_URL }}lib/angular/angular.min.js"></script>
<script src="{{ STATIC_URL }}lib/angular-route/angular-route.min.js"></script>
<script src="{{ STATIC_URL }}lib/angular-resource/angular-resource.min.js"></script>
<script src="{{ STATIC_URL }}lib/angular-cookies/angular-cookies.min.js"></script>
<script src="{{ STATIC_URL }}lib/angular-sanitize/angular-sanitize.min.js"></script>
<script src="{{ STATIC_URL }}lib/angular-md5/angular-md5.min.js"></script>
{% if settings.USE_MINIFIED_JS %}
<script src="{{ STATIC_URL }}js/awx-min.js"></script>
<script src="{{ STATIC_URL }}js/awx.min.js"></script>
{% else %}
<script src="{{ STATIC_URL }}lib/less.js/dist/less-1.6.2.min.js"></script>
<script src="{{ STATIC_URL }}js/app.js"></script>
@ -64,6 +64,7 @@
<script src="{{ STATIC_URL }}js/forms/Users.js"></script>
<script src="{{ STATIC_URL }}js/forms/Organizations.js"></script>
<script src="{{ STATIC_URL }}js/forms/Inventories.js"></script>
<script src="{{ STATIC_URL }}js/forms/InventoryStatus.js"></script>
<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>
@ -246,7 +247,7 @@
<div class="modal-header">
<button type="button" class="close" data-target="#form-modal"
data-dismiss="modal" aria-hidden="true" ng-click="cancelModal()">&times;</button>
<h3 ng-bind-html-unsafe="formModalHeader"></h3>
<h3 ng-bind-html="formModalHeader"></h3>
</div>
<div class="modal-body" id="form-modal-body"></div>
<div class="modal-footer">
@ -265,7 +266,7 @@
<div class="modal-header">
<button type="button" class="close" data-target="#form-modal2"
data-dismiss="modal" aria-hidden="true">&times;</button>
<h3 ng-bind-html-unsafe="formModal2Header"></h3>
<h3 ng-bind-html="formModal2Header"></h3>
</div>
<div class="modal-body" id="form-modal2-body"></div>
<div class="modal-footer">
@ -306,7 +307,7 @@
data-dismiss="modal" aria-hidden="true">&times;</button>
<h3 ng-bind="promptHeader" id="prompt-header"></h3>
</div>
<div class="modal-body" ng-bind-html-unsafe="promptBody" id="prompt-body">
<div class="modal-body" ng-bind-html="promptBody" id="prompt-body">
</div>
<div class="modal-footer">
<a href="#" data-target="#prompt-modal" data-dismiss="modal" id="prompt_cancel_btn" class="btn btn-default">No</a>
@ -326,7 +327,7 @@
<h3 ng-bind="alertHeader"></h3>
</div>
<div class="modal-body">
<div class="alert" ng-class="alertClass" ng-bind-html-unsafe="alertBody"></div>
<div class="alert" ng-class="alertClass" ng-bind-html="alertBody"></div>
</div>
<div class="modal-footer">
<a href="#" ng-hide="disableButtons" data-target="#form-modal" data-dismiss="modal" id="alert_ok_btn" class="btn btn-primary">OK</a>
@ -344,7 +345,7 @@
<h3 ng-bind="alertHeader2"></h3>
</div>
<div class="modal-body">
<div class="alert" ng-class="alertClass2" ng-bind-html-unsafe="alertBody2"></div>
<div class="alert" ng-class="alertClass2" ng-bind-html="alertBody2"></div>
</div>
<div class="modal-footer">
<a href="#" ng-hide="disableButtons2" data-target="#form-modal2" data-dismiss="modal" id="alert2_ok_btn" class="btn btn-primary">OK</a>