diff --git a/ansibleworks/ui/static/css/ansible-ui.css b/ansibleworks/ui/static/css/ansible-ui.css index c969aa602f..6dae61bfc4 100644 --- a/ansibleworks/ui/static/css/ansible-ui.css +++ b/ansibleworks/ui/static/css/ansible-ui.css @@ -436,4 +436,17 @@ .list-header .icon-sort-down, .list-header .icon-sort-up { color: #36454F; + } + + /* job_events syles */ + tr td i { + float: none; + margin-right: 10px; + padding-top: 3px; + padding-left: 0; + margin-left: 0; + } + + #event_display-header { + min-width: 250px; } \ No newline at end of file diff --git a/ansibleworks/ui/static/js/app.js b/ansibleworks/ui/static/js/app.js index 70a4d719f4..fd433c7b0a 100644 --- a/ansibleworks/ui/static/js/app.js +++ b/ansibleworks/ui/static/js/app.js @@ -52,7 +52,8 @@ angular.module('ansible', [ 'JobHostDefinition', 'GroupsHelper', 'HostsHelper', - 'ParseHelper' + 'ParseHelper', + 'ChildrenHelper' ]) .config(['$routeProvider', function($routeProvider) { $routeProvider. diff --git a/ansibleworks/ui/static/js/controllers/JobEvents.js b/ansibleworks/ui/static/js/controllers/JobEvents.js index 19c696bd09..c34621bb6e 100644 --- a/ansibleworks/ui/static/js/controllers/JobEvents.js +++ b/ansibleworks/ui/static/js/controllers/JobEvents.js @@ -12,21 +12,50 @@ function JobEventsList ($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, JobEventList, GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller, - ClearScope, ProcessErrors, GetBasePath, LookUpInit) + ClearScope, ProcessErrors, GetBasePath, LookUpInit, ToggleChildren) { ClearScope('htmlTemplate'); var list = JobEventList; list.base = $location.path(); - var defaultUrl = GetBasePath('jobs') + $routeParams.id + '/job_events/'; + + var defaultUrl = GetBasePath('jobs') + $routeParams.id + '/job_events/?parent__isnull=1'; + var view = GenerateList; var base = $location.path().replace(/^\//,'').split('/')[0]; var scope = view.inject(list, { mode: 'edit' }); scope.selected = []; + if (scope.RemovePostRefresh) { + scope.RemovePostRefresh(); + } + scope.RemovePostRefresh = scope.$on('PostRefresh', function() { + // Initialize the parent levels + var set = scope[list.name]; + for (var i=0; i < set.length; i++) { + set[i].event_display = set[i].event_display.replace(/^\u00a0*/g,''); + if (set[i].parent == null && set[i]['ngclick'] === undefined && set[i]['ngicon'] == undefined) { + set[i].parent = 0; + set[i]['ngclick'] = "toggleChildren(" + set[i].id + ", \"" + set[i].related.children + "\")"; + set[i]['ngicon'] = 'icon-expand-alt'; + set[i]['level'] = 0; + set[i]['spaces'] = 0; + } + } + }); + SearchInit({ scope: scope, set: 'jobevents', list: list, url: defaultUrl }); PaginateInit({ scope: scope, list: list, url: defaultUrl }); scope.search(list.iterator); + scope.toggleChildren = function(id, children) { + ToggleChildren({ + scope: scope, + list: list, + id: id, + children: children + }); + } + LoadBreadCrumbs(); if (scope.PostRefreshRemove) { @@ -76,7 +105,7 @@ function JobEventsList ($scope, $rootScope, $location, $log, $routeParams, Rest, JobEventsList.$inject = [ '$scope', '$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'JobEventList', 'GenerateList', 'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', - 'ProcessErrors','GetBasePath', 'LookUpInit' + 'ProcessErrors','GetBasePath', 'LookUpInit', 'ToggleChildren' ]; function JobEventsEdit ($scope, $rootScope, $compile, $location, $log, $routeParams, JobEventForm, diff --git a/ansibleworks/ui/static/js/helpers/Children.js b/ansibleworks/ui/static/js/helpers/Children.js new file mode 100644 index 0000000000..433585d1f6 --- /dev/null +++ b/ansibleworks/ui/static/js/helpers/Children.js @@ -0,0 +1,112 @@ +/********************************************* + * Copyright (c) 2013 AnsibleWorks, Inc. + * + * ChildrenHelper + * + * Used in job_events to expand/collapse children + * + */ + +angular.module('ChildrenHelper', ['RestServices', 'Utilities']) + .factory('ToggleChildren', ['Alert', 'Rest', 'GetBasePath','ProcessErrors', + function(Alert, Rest, GetBasePath, ProcessErrors) { + return function(params) { + var scope = params.scope; + var list = params.list; + var id = params.id; + var children = params.children; + var set = scope[list.name]; // set is now a pointer to scope[list.name] + + function calcSpaces(lvl) { + return lvl * 24; + } + + // Scan the array list and find the clicked element + var clicked; + var found = false; + for (var i = 0; i < set.length && found == false; i++){ + if (set[i].id == id) { + clicked = i; + found = true; + } + } + // Expand or collapse children based on clicked element's icon + if (set[clicked]['ngicon'] == 'icon-expand-alt') { + // Expand: lookup and display children + Rest.setUrl(children); + Rest.get() + .success( function(data, status, headers, config) { + var found = false; + var level = (set[clicked].level !== undefined) ? set[clicked].level + 1 : 1; + var spaces = calcSpaces(level); + set[clicked]['ngicon'] = 'icon-collapse-alt'; + for (var j=0; j < data.results.length; j++) { + data.results[j].level = level; + data.results[j].spaces = spaces; + //if (data.results[j].related.children === undefined) { + // data.results[j].spaces += 12 + //} + data.results[j].event_display = data.results[j].event_display.replace(/^\u00a0*/g,''); + if (data.results[j].related.children) { + data.results[j]['ngclick'] = "toggleChildren(" + data.results[j].id + ", \"" + data.results[j].related.children + "\")"; + data.results[j]['ngicon'] = 'icon-expand-alt'; + } + if (clicked == (set.length - 1)) { + set.push(data.results[j]); + } + else { + set.splice(clicked + 1, 0, data.results[j]); + } + clicked++; + } + }) + .error( function(data, status, headers, config) { + ProcessErrors(scope, data, status, null, + { hdr: 'Error!', msg: 'Call to ' + children + ' failed. GET returned status: ' + status }); + }); + } + else { + // Collapse: find and remove children + var parents = []; + function findChildren(parent, idx) { + // recursive look through the tree finding all + // parents including and related the clicked element + for (var i=idx; i < set.length; i++) { + if (set[i].parent == parent) { + parents.push(parent); + findChildren(set[i].id, i + 1); + } + } + } + findChildren(id, clicked + 1); + // Remove all the children of the clicked element + var count; + for (var i=0; i < parents.length; i++) { + count = 0; + for (var j=clicked + 1; j< set.length; j++) { + if (set[j].parent == parents[i]) { + set.splice(j,1); + j=clicked; // start back a the top of the list + } + } + } + set[clicked]['ngicon'] = 'icon-expand-alt'; + } + } + }]); + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ansibleworks/ui/static/js/helpers/search.js b/ansibleworks/ui/static/js/helpers/search.js index 4b2842627f..ce7df31216 100644 --- a/ansibleworks/ui/static/js/helpers/search.js +++ b/ansibleworks/ui/static/js/helpers/search.js @@ -102,15 +102,15 @@ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper']) (list.fields[scope[iterator + 'SearchField']].searchType && list.fields[scope[iterator + 'SearchField']].searchType == 'gtzero') ) { if (list.fields[scope[iterator + 'SearchField']].searchField) { - scope[iterator + 'SearchParams'] = '?' + list.fields[scope[iterator + 'SearchField']].searchField + '__'; + scope[iterator + 'SearchParams'] = list.fields[scope[iterator + 'SearchField']].searchField + '__'; } else if (list.fields[scope[iterator + 'SearchField']].sourceModel) { // handle fields whose source is a related model e.g. inventories.organization - scope[iterator + 'SearchParams'] = '?' + list.fields[scope[iterator + 'SearchField']].sourceModel + '__' + + scope[iterator + 'SearchParams'] = list.fields[scope[iterator + 'SearchField']].sourceModel + '__' + list.fields[scope[iterator + 'SearchField']].sourceField + '__'; } else { - scope[iterator + 'SearchParams'] = '?' + scope[iterator + 'SearchField'] + '__'; + scope[iterator + 'SearchParams'] = scope[iterator + 'SearchField'] + '__'; } if ( list.fields[scope[iterator + 'SearchField']].searchType && @@ -138,11 +138,16 @@ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper']) scope[iterator + 'SearchParams'] += (sort_order) ? '&order_by=' + escape(sort_order) : ''; } else { - scope[iterator + 'SearchParams'] = ''; - scope[iterator + 'SearchParams'] += (sort_order) ? '?order_by=' + escape(sort_order) : ''; + scope[iterator + 'SearchParams'] = (sort_order) ? 'order_by=' + escape(sort_order) : ""; } scope[iterator + 'Page'] = 0; - url += scope[iterator + 'SearchParams']; + if (/\/$/.test(url)) { + url += '?' + scope[iterator + 'SearchParams']; + } + else { + url += '&' + scope[iterator + 'SearchParams']; + } + url = url.replace(/\&\&/,'&'); url += (scope[iterator + 'PageSize']) ? '&page_size=' + scope[iterator + 'PageSize'] : ""; Refresh({ scope: scope, set: set, iterator: iterator, url: url }); } diff --git a/ansibleworks/ui/static/js/lists/JobEvents.js b/ansibleworks/ui/static/js/lists/JobEvents.js index 490bd1a137..c0b8c78a51 100644 --- a/ansibleworks/ui/static/js/lists/JobEvents.js +++ b/ansibleworks/ui/static/js/lists/JobEvents.js @@ -15,20 +15,19 @@ angular.module('JobEventsListDefinition', []) editTitle: 'Job Events', index: false, hover: true, + hasChildren: true, fields: { - id: { - label: 'Event ID', + created: { + label: 'Creation Date', key: true, - desc: true, - searchType: 'int' + nosort: true }, event_display: { label: 'Event', - link: true - }, - created: { - label: 'Creation Date' + hasChildren: true, + link: true, + nosort: true }, host: { label: 'Host', @@ -36,7 +35,8 @@ angular.module('JobEventsListDefinition', []) ngBind: 'jobevent.host_name', sourceModel: 'host', sourceField: 'name', - searchField: 'hosts__name' + searchField: 'hosts__name', + nosort: true }, status: { label: 'Status', @@ -44,7 +44,8 @@ angular.module('JobEventsListDefinition', []) "class": 'job-\{\{ jobevent.status \}\}', searchField: 'failed', searchType: 'boolean', - searchOptions: [{ name: "success", value: 0 }, { name: "error", value: 1 }] + searchOptions: [{ name: "success", value: 0 }, { name: "error", value: 1 }], + nosort: true } }, diff --git a/ansibleworks/ui/static/lib/ansible/generator-helpers.js b/ansibleworks/ui/static/lib/ansible/generator-helpers.js index f01fc6ca99..5719db0099 100644 --- a/ansibleworks/ui/static/lib/ansible/generator-helpers.js +++ b/ansibleworks/ui/static/lib/ansible/generator-helpers.js @@ -84,7 +84,7 @@ angular.module('GeneratorHelpers', ['GeneratorHelpers']) var html = ''; html += "" : ""; + + // Add collapse/expand icon --used on job_events page + if (list['hasChildren'] && field.hasChildren) { + html += " " + + " "; + } // Start the Link if ((field.key || field.link || field.linkTo || field.ngClick ) && options['mode'] != 'lookup' && options['mode'] != 'select') { @@ -111,7 +118,9 @@ angular.module('GeneratorHelpers', ['GeneratorHelpers']) html += " "; } else { - html += Icon(field.icon) + " "; + if (field.icon) { + html += Icon(field.icon) + " "; + } } // Add data binds @@ -120,15 +129,19 @@ angular.module('GeneratorHelpers', ['GeneratorHelpers']) html += "{{ " + field.ngBind + " }}"; } else { - html += "{{" + list.iterator + "." + fld + "}}"; + html += "{{" + list.iterator + "." + fld + "}}"; } } - + // Add additional text: if (field.text) { html += field.text; } + if (list['hasChildren'] && field.hasChildren) { + html += ""; + } + // close the link if ((field.key || field.link || field.linkTo || field.ngClick ) && options.mode != 'lookup' && options.mode != 'select') { diff --git a/ansibleworks/ui/static/lib/ansible/list-generator.js b/ansibleworks/ui/static/lib/ansible/list-generator.js index 4b8d524fb4..c1d8bef018 100644 --- a/ansibleworks/ui/static/lib/ansible/list-generator.js +++ b/ansibleworks/ui/static/lib/ansible/list-generator.js @@ -153,20 +153,25 @@ angular.module('ListGenerator', ['GeneratorHelpers']) html += "#\n"; } for (var fld in list.fields) { - html += "" + list.fields[fld].label; - html += " "; } - else { - html += "icon-sort"; - } - html += "\">\n"; + html += "\n"; } if (options.mode == 'select') { html += "Select"; diff --git a/ansibleworks/ui/templates/ui/index.html b/ansibleworks/ui/templates/ui/index.html index e9cf7feb96..054f39b1f6 100644 --- a/ansibleworks/ui/templates/ui/index.html +++ b/ansibleworks/ui/templates/ui/index.html @@ -74,6 +74,7 @@ +