New job events list and detail pages. Fixed boolean search. Added new select search type -used on job status to provide a select list.

This commit is contained in:
chouseknecht 2013-05-22 01:37:47 -04:00
parent e137d17964
commit fc6545a059
12 changed files with 404 additions and 187 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 += "<td class=\"" + fld + "-column";
html += (list.fields[fld].class) ? " " + list.fields[fld].class : "";
html += "\">";
if ((list.fields[fld].key || list.fields[fld].link) && options.mode != 'lookup' && options.mode != 'select') {
html += "<a href=\"#/" + base + "/{{" + list.iterator + ".id }}\">";
}
html += (list.fields[fld].icon) ? this.icon(list.fields[fld].icon) : "";
html += "{{" + list.iterator + "." + fld + "}}";
html += ((list.fields[fld].key || list.fields[fld].link) && options.mode != 'lookup' && options.mode != 'select') ? "</a>" : "";
html += "</td>\n";
cnt++;
html += "<td ";
html += "<td class=\"" + fld + "-column";
html += (list.fields[fld].class) ? " " + list.fields[fld].class : "";
html += "\" ";
html += (list.fields[fld].ngClass) ? this.attr(list.fields[fld], 'ngClass') : "";
html += ">\n";
if ((list.fields[fld].key || list.fields[fld].link) && options.mode != 'lookup' && options.mode != 'select') {
html += "<a href=\"#/" + base + "/{{" + list.iterator + ".id }}\">";
}
html += (list.fields[fld].icon) ? this.icon(list.fields[fld].icon) : "";
if (list.fields[fld].ngBind) {
html += "{{ " + list.fields[fld].ngBind + " }}";
}
else {
html += "<td class=\"" + fld + "-column\">";
if ((list.fields[fld].key || list.fields[fld].link) && options.mode != 'lookup' && options.mode != 'select') {
html += "<a href=\"#/" + base + "/{{" + list.iterator + ".id }}\">";
}
html += (list.fields[fld].icon) ? this.icon(list.fields[fld].icon) : "";
html += "{{ " + list.fields[fld].ngBind + " }}";
html += ((list.fields[fld].key || list.fields[fld].link) && options.mode != 'lookup' && options.mode != 'select') ? "</a>" : "";
html += "</td>\n";
}
html += "{{" + list.iterator + "." + fld + "}}";
}
html += ((list.fields[fld].key || list.fields[fld].link) && options.mode != 'lookup' && options.mode != 'select') ? "</a>" : "";
html += "</td>\n";
}
if (options.mode == 'select' ) {

View File

@ -36,6 +36,7 @@
<script src="{{ STATIC_URL }}js/controllers/JobTemplates.js"></script>
<script src="{{ STATIC_URL }}js/controllers/Projects.js"></script>
<script src="{{ STATIC_URL }}js/controllers/Jobs.js"></script>
<script src="{{ STATIC_URL }}js/controllers/JobEvents.js"></script>
<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>
@ -57,6 +58,7 @@
<script src="{{ STATIC_URL }}js/lists/JobTemplates.js"></script>
<script src="{{ STATIC_URL }}js/lists/Projects.js"></script>
<script src="{{ STATIC_URL }}js/lists/Jobs.js"></script>
<script src="{{ STATIC_URL }}js/lists/JobEvents.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>