diff --git a/ansibleworks/ui/static/css/ansible-ui.css b/ansibleworks/ui/static/css/ansible-ui.css index e053b0c85e..097d5b3c61 100644 --- a/ansibleworks/ui/static/css/ansible-ui.css +++ b/ansibleworks/ui/static/css/ansible-ui.css @@ -274,7 +274,8 @@ color: #da4f49; } - .job-new, input[type="text"].job-new { + .job-new, input[type="text"].job-new, + .job-canceled, input[type="text"].job-canceled { color: #778899; } @@ -290,18 +291,6 @@ padding-left: 15px; } - #job_events label { - margin-right: 10px; - } - - #job_events { - text-align: center; - } - - #job_events_items_form { - margin-top: 15px; - } - .form-items .search-widget { margin-top: 15px; } diff --git a/ansibleworks/ui/static/js/app.js b/ansibleworks/ui/static/js/app.js index 7c19a8ab6e..1871b995d5 100644 --- a/ansibleworks/ui/static/js/app.js +++ b/ansibleworks/ui/static/js/app.js @@ -47,6 +47,7 @@ angular.module('ansible', [ 'ProjectsListDefinition', 'JobsListDefinition', 'JobFormDefinition', + 'JobEventsListDefinition', 'JobEventFormDefinition' ]) .config(['$routeProvider', function($routeProvider) { @@ -58,8 +59,11 @@ angular.module('ansible', [ { templateUrl: urlPrefix + 'partials/jobs.html', controller: JobsEdit }). when('/jobs/:id/job_events', - { templateUrl: urlPrefix + 'partials/jobs.html', controller: JobEvents }). + { templateUrl: urlPrefix + 'partials/jobs.html', controller: JobEventsList }). + when('/jobs/:job_id/job_events/:event_id', + { templateUrl: urlPrefix + 'partials/jobs.html', controller: JobEventsEdit }). + when('/job_templates', { templateUrl: urlPrefix + 'partials/job_templates.html', controller: JobTemplatesList }). diff --git a/ansibleworks/ui/static/js/controllers/JobEvents.js b/ansibleworks/ui/static/js/controllers/JobEvents.js new file mode 100644 index 0000000000..9f2fb9e972 --- /dev/null +++ b/ansibleworks/ui/static/js/controllers/JobEvents.js @@ -0,0 +1,93 @@ +/************************************ + * Copyright (c) 2013 AnsibleWorks, Inc. + * + * + * JobEvents.js + * + * Controller functions for the Job Events model. + * + */ + +'use strict'; + +function JobEventsList ($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, JobEventList, + GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller, + ClearScope, ProcessErrors, GetBasePath, LookUpInit) +{ + ClearScope('htmlTemplate'); + var list = JobEventList; + list.base = $location.path(); + var defaultUrl = GetBasePath('jobs') + $routeParams.id + '/job_events/'; + var view = GenerateList; + var base = $location.path().replace(/^\//,'').split('/')[0]; + var scope = view.inject(list, { mode: 'edit' }); + scope.selected = []; + + SearchInit({ scope: scope, set: 'jobevents', list: list, url: defaultUrl }); + PaginateInit({ scope: scope, list: list, url: defaultUrl }); + scope.search(list.iterator); + + LoadBreadCrumbs(); + + if (scope.PostRefreshRemove) { + scope.PostRefreshRemove(); + } + scope.PostRefreshRemove = scope.$on('PostRefresh', function() { + for (var i=0; i < scope.jobevents.length; i++) { + scope.jobevents[i].status = (scope.jobevents[i].failed) ? 'error' : 'success'; + } + }); + + scope.editJobEvent = function(id) { + $location.path($location.path() + '/' + id); + } +} + +JobEventsList.$inject = [ '$scope', '$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'JobEventList', + 'GenerateList', 'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', + 'ProcessErrors','GetBasePath', 'LookUpInit' + ]; + +function JobEventsEdit ($scope, $rootScope, $compile, $location, $log, $routeParams, JobEventForm, + GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ClearScope, GetBasePath) +{ + ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior + //scope. + + // Inject dynamic view + var form = JobEventForm; + var generator = GenerateForm; + var scope = GenerateForm.inject(form, {mode: 'edit', related: true}); + generator.reset(); + + var defaultUrl = GetBasePath('base') + 'job_events/' + $routeParams.event_id + '/'; + var base = $location.path().replace(/^\//,'').split('/')[0]; + + // Retrieve detail record and prepopulate the form + Rest.setUrl(defaultUrl); + Rest.get() + .success( function(data, status, headers, config) { + LoadBreadCrumbs({ path: '/job_events/' + $routeParams.event_id, title: data.event }); + for (var fld in form.fields) { + if (fld == 'status') { + scope['status'] = (data.failed) ? 'error' : 'success'; + } + else if (fld == 'event_data') { + scope['event_data'] = JSON.stringify(data['event_data'], undefined, '\t'); + } + else { + if (data[fld]) { + scope[fld] = data[fld]; + } + } + } + }) + .error( function(data, status, headers, config) { + ProcessErrors(scope, data, status, form, + { hdr: 'Error!', msg: 'Failed to retrieve event detail: ' + $routeParams.event_id + '. GET status: ' + status }); + }); + +} + +JobEventsEdit.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'JobEventForm', + 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ClearScope', 'GetBasePath']; diff --git a/ansibleworks/ui/static/js/controllers/JobEvents.old b/ansibleworks/ui/static/js/controllers/JobEvents.old new file mode 100644 index 0000000000..f6ed3dbf8a --- /dev/null +++ b/ansibleworks/ui/static/js/controllers/JobEvents.old @@ -0,0 +1,79 @@ +function JobEvents ($scope, $rootScope, $compile, $location, $log, $routeParams, JobEventForm, + GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ClearScope, SearchInit, + PaginateInit, GetBasePath) +{ + ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior + //scope. + + // Inject dynamic view + var form = JobEventForm; + var generator = GenerateForm; + var scope = GenerateForm.inject(form, {mode: 'edit', related: true}); + generator.reset(); + + var defaultUrl = GetBasePath('jobs') + $routeParams.id + '/job_events/'; + var base = $location.path().replace(/^\//,'').split('/')[0]; + var master = {}; + var id = $routeParams.id; + var relatedSets = {}; + + if (scope.PostRefreshRemove){ + scope.PostRefreshRemove(); + } + scope.PostRefreshRemove = scope.$on('PostRefresh', function() { + + // Disable Next/Prev buttons when we reach the end/beginning of array + scope[form.items.event.iterator + 'NextUrlDisable'] = (scope[form.items.event.iterator + 'NextUrl'] !== null) ? "" : "disabled"; + scope[form.items.event.iterator + 'PrevUrlDisable'] = (scope[form.items.event.iterator + 'PrevUrl'] !== null) ? "" : "disabled"; + + // Set the scope input field values + if (scope[form.items.event.set] && scope[form.items.event.set].length > 0) { + var results = scope[form.items.event.set][0]; + for (var fld in form.items.event.fields) { + if (fld == 'event_data') { + scope.event_data = JSON.stringify(results[fld]); + } + else { + if (results[fld]) { + scope[fld] = results[fld]; + } + } + } + scope['event_status'] = (results.failed) ? 'failed' : 'success'; + } + }); + + // Retrieve detail record and prepopulate the form + Rest.setUrl(defaultUrl); + Rest.get({ params: {page_size: 1} }) + .success( function(data, status, headers, config) { + var results = data.results[0]; + scope[form.items.event.iterator + 'NextUrl'] = data.next; + scope[form.items.event.iterator + 'PrevUrl'] = data.previous; + scope[form.items.event.iterator + 'Count'] = data.count; + LoadBreadCrumbs({ path: '/jobs/' + id, title: results["summary_fields"].job.name }); + for (var fld in form.fields) { + if (results[fld]) { + scope[fld] = results[fld]; + } + if (form.fields[fld].sourceModel && results.summary_fields[form.fields[fld].sourceModel]) { + scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] = + results.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField]; + } + } + scope[form.items.event.set] = data.results; + SearchInit({ scope: scope, set: form.items.event.set, list: form.items.event, iterator: form.items.event.iterator, url: defaultUrl }); + PaginateInit({ scope: scope, list: form.items.event, iterator: form.items.event.iterator, url: defaultUrl , pageSize: 1 }); + scope.$emit('PostRefresh'); + }) + .error( function(data, status, headers, config) { + ProcessErrors(scope, data, status, form, + { hdr: 'Error!', msg: 'Failed to retrieve job event data: ' + $routeParams.id + '. GET status: ' + status }); + }); + +} + +JobEvents.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'JobEventForm', + 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ClearScope', 'SearchInit', + 'PaginateInit', 'GetBasePath' ]; + diff --git a/ansibleworks/ui/static/js/controllers/Jobs.js b/ansibleworks/ui/static/js/controllers/Jobs.js index 80da531323..36f0f90d88 100644 --- a/ansibleworks/ui/static/js/controllers/Jobs.js +++ b/ansibleworks/ui/static/js/controllers/Jobs.js @@ -36,7 +36,8 @@ function JobsListCtrl ($scope, $rootScope, $location, $log, $routeParams, Rest, $location.path($location.path() + '/' + id); } - scope.viewEvents = function(id) { + scope.viewEvents = function(id, name) { + LoadBreadCrumbs({ path: '/jobs/' + id, title: name }); $location.path($location.path() + '/' + id + '/job_events'); } @@ -332,84 +333,3 @@ JobsEdit.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$ 'RelatedPaginateInit', 'ReturnToCaller', 'ClearScope', 'InventoryList', 'CredentialList', 'ProjectList', 'LookUpInit', 'PromptPasswords', 'GetBasePath' ]; - - -function JobEvents ($scope, $rootScope, $compile, $location, $log, $routeParams, JobEventForm, - GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ClearScope, SearchInit, - PaginateInit, GetBasePath) -{ - ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior - //scope. - - // Inject dynamic view - var form = JobEventForm; - var generator = GenerateForm; - var scope = GenerateForm.inject(form, {mode: 'edit', related: true}); - generator.reset(); - - var defaultUrl = GetBasePath('jobs') + $routeParams.id + '/job_events/'; - var base = $location.path().replace(/^\//,'').split('/')[0]; - var master = {}; - var id = $routeParams.id; - var relatedSets = {}; - - if (scope.PostRefreshRemove){ - scope.PostRefreshRemove(); - } - scope.PostRefreshRemove = scope.$on('PostRefresh', function() { - - // Disable Next/Prev buttons when we reach the end/beginning of array - scope[form.items.event.iterator + 'NextUrlDisable'] = (scope[form.items.event.iterator + 'NextUrl'] !== null) ? "" : "disabled"; - scope[form.items.event.iterator + 'PrevUrlDisable'] = (scope[form.items.event.iterator + 'PrevUrl'] !== null) ? "" : "disabled"; - - // Set the scope input field values - if (scope[form.items.event.set] && scope[form.items.event.set].length > 0) { - var results = scope[form.items.event.set][0]; - for (var fld in form.items.event.fields) { - if (fld == 'event_data') { - scope.event_data = JSON.stringify(results[fld]); - } - else { - if (results[fld]) { - scope[fld] = results[fld]; - } - } - } - scope['event_status'] = (results.failed) ? 'failed' : 'success'; - } - }); - - // Retrieve detail record and prepopulate the form - Rest.setUrl(defaultUrl); - Rest.get({ params: {page_size: 1} }) - .success( function(data, status, headers, config) { - var results = data.results[0]; - scope[form.items.event.iterator + 'NextUrl'] = data.next; - scope[form.items.event.iterator + 'PrevUrl'] = data.previous; - scope[form.items.event.iterator + 'Count'] = data.count; - LoadBreadCrumbs({ path: '/jobs/' + id, title: results["summary_fields"].job.name }); - for (var fld in form.fields) { - if (results[fld]) { - scope[fld] = results[fld]; - } - if (form.fields[fld].sourceModel && results.summary_fields[form.fields[fld].sourceModel]) { - scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] = - results.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField]; - } - } - scope[form.items.event.set] = data.results; - SearchInit({ scope: scope, set: form.items.event.set, list: form.items.event, iterator: form.items.event.iterator, url: defaultUrl }); - PaginateInit({ scope: scope, list: form.items.event, iterator: form.items.event.iterator, url: defaultUrl , pageSize: 1 }); - scope.$emit('PostRefresh'); - }) - .error( function(data, status, headers, config) { - ProcessErrors(scope, data, status, form, - { hdr: 'Error!', msg: 'Failed to retrieve job event data: ' + $routeParams.id + '. GET status: ' + status }); - }); - -} - -JobEvents.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'JobEventForm', - 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ClearScope', 'SearchInit', - 'PaginateInit', 'GetBasePath' ]; - diff --git a/ansibleworks/ui/static/js/forms/JobEvents.js b/ansibleworks/ui/static/js/forms/JobEvents.js index 05083146c6..776ffb9c49 100644 --- a/ansibleworks/ui/static/js/forms/JobEvents.js +++ b/ansibleworks/ui/static/js/forms/JobEvents.js @@ -10,88 +10,52 @@ angular.module('JobEventFormDefinition', []) .value( 'JobEventForm', { - editTitle: '{{ name }} Events', //Legend in edit mode + editTitle: '{{ id }} - {{ event }}', //Legend in edit mode name: 'job_events', well: true, - fieldsAsHeader: true, fields: { - job: { - label: 'Job', + id: { + label: 'Event ID', + type: 'text', + readonly: true, + class: 'span2' + }, + event: { + label: 'Event', type: 'text', - class: 'span1', readonly: true }, - job_name: { + created: { + label: 'Created', + type: 'text', + readonly: true, + class: 'span4' + }, + host: { + label: 'Host', type: 'text', - sourceModel: 'job', - sourceField: 'name', - class: 'span5', readonly: true }, - job_description: { + status: { + label: 'Status', type: 'text', - sourceModel: 'job', - sourceField: 'description', - class: 'span5', + class: 'job-\{\{ event_status \}\}', readonly: true - } + }, + event_data: { + label: 'Event Data', + type: 'textarea', + class: 'span12', + rows: 10, + readonly: true + } }, buttons: { }, - items: { - event: { - set: 'job_events', - iterator: 'job_event', - label: 'Event', - fields: { - id: { - label: 'Event ID', - type: 'text', - readonly: true, - class: 'span2', - key: true, - searchType: 'int' - }, - created: { - label: 'Event Timestamp', - type: 'text', - readonly: true, - class: 'span4' - }, - event: { - label: 'Event', - type: 'text', - readonly: true - }, - host: { - label: 'Host', - type: 'text', - readonly: true - }, - event_status: { - label: 'Event Status', - type: 'text', - class: 'job-\{\{ event_status \}\}', - readonly: true, - searchField: 'failed', - searchType: 'boolean', - searchOptions: [{ name: "success", value: 0 }, { name: "failed", value: 1 }], - }, - event_data: { - label: 'Event Data', - type: 'textarea', - class: 'span12', - rows: 10, - readonly: true - } - } - } - }, - related: { //related colletions (and maybe items?) } diff --git a/ansibleworks/ui/static/js/forms/JobEvents.js.old b/ansibleworks/ui/static/js/forms/JobEvents.js.old new file mode 100644 index 0000000000..05083146c6 --- /dev/null +++ b/ansibleworks/ui/static/js/forms/JobEvents.js.old @@ -0,0 +1,100 @@ +/********************************************* + * Copyright (c) 2013 AnsibleWorks, Inc. + * + * JobEvents.js + * Form definition for Job Events model + * + * + */ +angular.module('JobEventFormDefinition', []) + .value( + 'JobEventForm', { + + editTitle: '{{ name }} Events', //Legend in edit mode + name: 'job_events', + well: true, + fieldsAsHeader: true, + + fields: { + job: { + label: 'Job', + type: 'text', + class: 'span1', + readonly: true + }, + job_name: { + type: 'text', + sourceModel: 'job', + sourceField: 'name', + class: 'span5', + readonly: true + }, + job_description: { + type: 'text', + sourceModel: 'job', + sourceField: 'description', + class: 'span5', + readonly: true + } + }, + + buttons: { + + }, + + items: { + event: { + set: 'job_events', + iterator: 'job_event', + label: 'Event', + fields: { + id: { + label: 'Event ID', + type: 'text', + readonly: true, + class: 'span2', + key: true, + searchType: 'int' + }, + created: { + label: 'Event Timestamp', + type: 'text', + readonly: true, + class: 'span4' + }, + event: { + label: 'Event', + type: 'text', + readonly: true + }, + host: { + label: 'Host', + type: 'text', + readonly: true + }, + event_status: { + label: 'Event Status', + type: 'text', + class: 'job-\{\{ event_status \}\}', + readonly: true, + searchField: 'failed', + searchType: 'boolean', + searchOptions: [{ name: "success", value: 0 }, { name: "failed", value: 1 }], + }, + event_data: { + label: 'Event Data', + type: 'textarea', + class: 'span12', + rows: 10, + readonly: true + } + } + } + }, + + related: { //related colletions (and maybe items?) + + } + + }); //Form + diff --git a/ansibleworks/ui/static/js/helpers/search.js b/ansibleworks/ui/static/js/helpers/search.js index 736616f9c2..45f5000547 100644 --- a/ansibleworks/ui/static/js/helpers/search.js +++ b/ansibleworks/ui/static/js/helpers/search.js @@ -44,7 +44,8 @@ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper']) scope[iterator + 'HideSearchType'] = false; var f = scope[iterator + 'SearchField'] - if (list.fields[f].searchType && list.fields[f].searchType == 'boolean') { + if (list.fields[f].searchType && ( list.fields[f].searchType == 'boolean' + || list.fields[f].searchType == 'select')) { scope[iterator + 'SelectShow'] = true; scope[iterator + 'SearchSelectOpts'] = list.fields[fld].searchOptions; } @@ -60,9 +61,10 @@ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper']) scope[iterator + 'SelectShow'] = false; scope[iterator + 'HideSearchType'] = false; - if (list.fields[fld].searchType && list.fields[fld].searchType == 'boolean') { + if (list.fields[fld].searchType && (list.fields[fld].searchType == 'boolean' + || list.fields[fld].searchType == 'select')) { scope[iterator + 'SelectShow'] = true; - scope[iterator + 'SearchSelectOpts'] = list.fields[f].searchOptions; + scope[iterator + 'SearchSelectOpts'] = list.fields[fld].searchOptions; } if (list.fields[fld].searchType && list.fields[fld].searchType == 'int') { scope[iterator + 'HideSearchType'] = true; @@ -107,7 +109,8 @@ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper']) } if ( list.fields[scope[iterator + 'SearchField']].searchType && - list.fields[scope[iterator + 'SearchField']].searchType == 'boolean' ) { + (list.fields[scope[iterator + 'SearchField']].searchType == 'boolean' + || list.fields[scope[iterator + 'SearchField']].searchType == 'select') ) { scope[iterator + 'SearchParams'] += scope[iterator + 'SearchSelectValue'].value; } else { diff --git a/ansibleworks/ui/static/js/lists/JobEvents.js b/ansibleworks/ui/static/js/lists/JobEvents.js new file mode 100644 index 0000000000..199bebb923 --- /dev/null +++ b/ansibleworks/ui/static/js/lists/JobEvents.js @@ -0,0 +1,54 @@ +/********************************************* + * Copyright (c) 2013 AnsibleWorks, Inc. + * + * Jobs.js + * List view object for Team data model. + * + * + */ +angular.module('JobEventsListDefinition', []) + .value( + 'JobEventList', { + + name: 'jobevents', + iterator: 'jobevent', + editTitle: 'Job Events', + index: false, + hover: true, + + fields: { + id: { + label: 'Event ID', + key: true, + desc: true, + searchType: 'int' + }, + event: { + label: 'Event', + link: true + }, + created: { + label: 'Creation Date', + }, + status: { + label: 'Status', + icon: 'icon-circle', + class: 'job-\{\{ jobevent.status \}\}', + searchField: 'failed', + searchType: 'boolean', + searchOptions: [{ name: "success", value: 0 }, { name: "error", value: 1 }] + } + }, + + actions: { + }, + + fieldActions: { + edit: { + ngClick: "editJobEvent(\{\{ jobevent.id \}\})", + icon: 'icon-edit', + class: 'btn-mini', + awToolTip: 'View event detail', + }, + } + }); diff --git a/ansibleworks/ui/static/js/lists/Jobs.js b/ansibleworks/ui/static/js/lists/Jobs.js index bc435a8a5e..5e9973c16f 100644 --- a/ansibleworks/ui/static/js/lists/Jobs.js +++ b/ansibleworks/ui/static/js/lists/Jobs.js @@ -35,7 +35,16 @@ angular.module('JobsListDefinition', []) status: { label: 'Status', icon: 'icon-circle', - class: 'job-\{\{ job.status \}\}' + class: 'job-\{\{ job.status \}\}', + searchType: 'select', + searchOptions: [ + { name: "new", value: "new" }, + { name: "pending", value: "pending" }, + { name: "running", value: "running" }, + { name: "success", value: "success" }, + { name: "error", value: "error" }, + { name: "failed", value: "failed" }, + { name: "canceled", value: "canceled" } ] } }, @@ -67,7 +76,7 @@ angular.module('JobsListDefinition', []) title: 'Detail', icon: 'icon-list-ul', mode: 'all', - ngClick: 'viewEvents(\{{ job.id \}\})', + ngClick: "viewEvents(\{{ job.id \}\}, '\{\{ job.name \}\}')", class: 'btn-success btn-mini', awToolTip: 'View events', ngDisabled: "job.status == 'new'" diff --git a/ansibleworks/ui/static/lib/ansible/list-generator.js b/ansibleworks/ui/static/lib/ansible/list-generator.js index 5007c42570..7b5629742e 100644 --- a/ansibleworks/ui/static/lib/ansible/list-generator.js +++ b/ansibleworks/ui/static/lib/ansible/list-generator.js @@ -22,6 +22,9 @@ angular.module('ListGenerator', ['GeneratorHelpers',]) case 'ngClick': result = "ng-click=\"" + obj[key] + "\" "; break; + case 'ngClass': + result = "ng-class=\"" + obj[key] + "\" "; + break; case 'ngDisabled': result = "ng-disabled=\"" + obj[key] + "\" "; break; @@ -187,30 +190,27 @@ angular.module('ListGenerator', ['GeneratorHelpers',]) } var cnt = 2; var base = (list.base) ? list.base : list.name; + base = base.replace(/^\//,''); for (fld in list.fields) { - cnt++; - if (! list.fields[fld].ngBind) { - html += "