From 05b8538fde5993c19b3ff75e2b46b0aebc191618 Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Fri, 25 Mar 2016 14:39:45 -0400 Subject: [PATCH] Job Detail - host event modal, refactor EventViewer into job-details/host-event/ module #1131 first predemo pass --- awx/ui/client/src/app.js | 2 + awx/ui/client/src/helpers/EventViewer.js | 568 ------------------ .../host-event-details.partial.html | 49 ++ .../host-event/host-event-json.partial.html | 2 + .../host-event/host-event-modal.partial.html | 36 ++ .../host-event/host-event-stdout.partial.html | 13 + .../host-event/host-event-timing.partial.html | 1 + .../host-event/host-event.block.less | 66 ++ .../host-event/host-event.controller.js | 71 +++ .../job-detail/host-event/host-event.route.js | 86 +++ .../client/src/job-detail/host-event/main.js | 21 + .../host-events/host-events.controller.js | 58 +- .../host-events/host-events.partial.html | 6 +- .../host-events/host-events.route.js | 9 +- .../src/job-detail/job-detail.controller.js | 15 +- .../src/job-detail/job-detail.partial.html | 6 +- .../src/job-detail/job-detail.service.js | 74 ++- awx/ui/client/src/job-detail/main.js | 4 +- awx/ui/client/src/partials/eventviewer.html | 34 -- .../src/shared/layouts/one-plus-two.less | 3 +- 20 files changed, 447 insertions(+), 677 deletions(-) delete mode 100644 awx/ui/client/src/helpers/EventViewer.js create mode 100644 awx/ui/client/src/job-detail/host-event/host-event-details.partial.html create mode 100644 awx/ui/client/src/job-detail/host-event/host-event-json.partial.html create mode 100644 awx/ui/client/src/job-detail/host-event/host-event-modal.partial.html create mode 100644 awx/ui/client/src/job-detail/host-event/host-event-stdout.partial.html create mode 100644 awx/ui/client/src/job-detail/host-event/host-event-timing.partial.html create mode 100644 awx/ui/client/src/job-detail/host-event/host-event.block.less create mode 100644 awx/ui/client/src/job-detail/host-event/host-event.controller.js create mode 100644 awx/ui/client/src/job-detail/host-event/host-event.route.js create mode 100644 awx/ui/client/src/job-detail/host-event/main.js delete mode 100644 awx/ui/client/src/partials/eventviewer.html diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index e207eab59b..e045c0322d 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -211,6 +211,8 @@ var tower = angular.module('Tower', [ templateUrl: urlPrefix + 'partials/breadcrumb.html' }); + // route to the details pane of /job/:id/host-event/:eventId if no other child specified + $urlRouterProvider.when('/jobs/*/host-event/*', '/jobs/*/host-event/*/details') // $urlRouterProvider.otherwise("/home"); $urlRouterProvider.otherwise(function($injector){ var $state = $injector.get("$state"); diff --git a/awx/ui/client/src/helpers/EventViewer.js b/awx/ui/client/src/helpers/EventViewer.js deleted file mode 100644 index cb075fa5e9..0000000000 --- a/awx/ui/client/src/helpers/EventViewer.js +++ /dev/null @@ -1,568 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name helpers.function:EventViewer - * @description eventviewerhelper -*/ - -export default - angular.module('EventViewerHelper', ['ModalDialog', 'Utilities', 'EventsViewerFormDefinition', 'HostsHelper']) - - .factory('EventViewer', ['$compile', 'CreateDialog', 'GetEvent', 'Wait', 'EventAddTable', 'GetBasePath', 'Empty', 'EventAddPreFormattedText', - function($compile, CreateDialog, GetEvent, Wait, EventAddTable, GetBasePath, Empty, EventAddPreFormattedText) { - return function(params) { - var parent_scope = params.scope, - url = params.url, - event_id = params.event_id, - parent_id = params.parent_id, - title = params.title, //optional - scope = parent_scope.$new(true), - index = params.index, - page, - current_event; - - if (scope.removeShowNextEvent) { - scope.removeShowNextEvent(); - } - scope.removeShowNextEvent = scope.$on('ShowNextEvent', function(e, data, show_event) { - scope.events = data; - $('#event-next-spinner').slideUp(200); - if (show_event === 'prev') { - showEvent(scope.events.length - 1); - } - else if (show_event === 'next') { - showEvent(0); - } - }); - - // show scope.events[idx] - function showEvent(idx) { - var show_tabs = false, elem, data; - - if (idx > scope.events.length - 1) { - GetEvent({ - scope: scope, - url: scope.next_event_set, - show_event: 'next' - }); - return; - } - - if (idx < 0) { - GetEvent({ - scope: scope, - url: scope.prev_event_set, - show_event: 'prev' - }); - return; - } - - data = scope.events[idx]; - current_event = idx; - - $('#status-form-container').empty(); - $('#results-form-container').empty(); - $('#timing-form-container').empty(); - $('#stdout-form-container').empty(); - $('#stderr-form-container').empty(); - $('#traceback-form-container').empty(); - $('#json-form-container').empty(); - $('#eventview-tabs li:eq(1)').hide(); - $('#eventview-tabs li:eq(2)').hide(); - $('#eventview-tabs li:eq(3)').hide(); - $('#eventview-tabs li:eq(4)').hide(); - $('#eventview-tabs li:eq(5)').hide(); - $('#eventview-tabs li:eq(6)').hide(); - - EventAddTable({ scope: scope, id: 'status-form-container', event: data, section: 'Event' }); - - if (EventAddTable({ scope: scope, id: 'results-form-container', event: data, section: 'Results'})) { - show_tabs = true; - $('#eventview-tabs li:eq(1)').show(); - } - - if (EventAddTable({ scope: scope, id: 'timing-form-container', event: data, section: 'Timing' })) { - show_tabs = true; - $('#eventview-tabs li:eq(2)').show(); - } - - if (data.stdout) { - show_tabs = true; - $('#eventview-tabs li:eq(3)').show(); - EventAddPreFormattedText({ - id: 'stdout-form-container', - val: data.stdout - }); - } - - if (data.stderr) { - show_tabs = true; - $('#eventview-tabs li:eq(4)').show(); - EventAddPreFormattedText({ - id: 'stderr-form-container', - val: data.stderr - }); - } - - if (data.traceback) { - show_tabs = true; - $('#eventview-tabs li:eq(5)').show(); - EventAddPreFormattedText({ - id: 'traceback-form-container', - val: data.traceback - }); - } - - show_tabs = true; - $('#eventview-tabs li:eq(6)').show(); - EventAddPreFormattedText({ - id: 'json-form-container', - val: JSON.stringify(data, null, 2) - }); - - if (!show_tabs) { - $('#eventview-tabs').hide(); - } - - elem = angular.element(document.getElementById('eventviewer-modal-dialog')); - $compile(elem)(scope); - } - - function setButtonMargin() { - var width = ($('.ui-dialog[aria-describedby="eventviewer-modal-dialog"] .ui-dialog-buttonpane').innerWidth() / 2) - $('#events-next-button').outerWidth() - 73; - $('#events-next-button').css({'margin-right': width + 'px'}); - } - - function addSpinner() { - var position; - if ($('#event-next-spinner').length > 0) { - $('#event-next-spinner').remove(); - } - position = $('#events-next-button').position(); - $('#events-next-button').after(''); - } - - if (scope.removeModalReady) { - scope.removeModalReady(); - } - scope.removeModalReady = scope.$on('ModalReady', function() { - Wait('stop'); - $('#eventviewer-modal-dialog').dialog('open'); - }); - - if (scope.removeJobReady) { - scope.removeJobReady(); - } - scope.removeEventReady = scope.$on('EventReady', function(e, data) { - var btns; - scope.events = data; - if (event_id) { - // find and show the selected event - data.every(function(row, idx) { - if (parseInt(row.id,10) === parseInt(event_id,10)) { - current_event = idx; - return false; - } - return true; - }); - } - else { - current_event = 0; - } - showEvent(current_event); - - btns = []; - if (scope.events.length > 1) { - btns.push({ - label: "Prev", - onClick: function () { - if (current_event - 1 === 0 && !scope.prev_event_set) { - $('#events-prev-button').prop('disabled', true); - } - if (current_event - 1 < scope.events.length - 1) { - $('#events-next-button').prop('disabled', false); - } - showEvent(current_event - 1); - }, - icon: "fa-chevron-left", - "class": "btn btn-primary", - id: "events-prev-button" - }); - btns.push({ - label: "Next", - onClick: function() { - if (current_event + 1 > 0) { - $('#events-prev-button').prop('disabled', false); - } - if (current_event + 1 >= scope.events.length - 1 && !scope.next_event_set) { - $('#events-next-button').prop('disabled', true); - } - showEvent(current_event + 1); - }, - icon: "fa-chevron-right", - "class": "btn btn-primary", - id: "events-next-button" - }); - } - btns.push({ - label: "OK", - onClick: function() { - scope.modalOK(); - }, - icon: "", - "class": "btn btn-primary", - id: "dialog-ok-button" - }); - - CreateDialog({ - scope: scope, - width: 675, - height: 600, - minWidth: 450, - callback: 'ModalReady', - id: 'eventviewer-modal-dialog', - // onResizeStop: resizeText, - title: ( (title) ? title : 'Host Event' ), - buttons: btns, - closeOnEscape: true, - onResizeStop: function() { - setButtonMargin(); - addSpinner(); - }, - onClose: function() { - try { - scope.$destroy(); - } - catch(e) { - //ignore - } - }, - onOpen: function() { - $('#eventview-tabs a:first').tab('show'); - $('#dialog-ok-button').focus(); - if (scope.events.length > 1 && current_event === 0 && !scope.prev_event_set) { - $('#events-prev-button').prop('disabled', true); - } - if ((current_event === scope.events.length - 1) && !scope.next_event_set) { - $('#events-next-button').prop('disabled', true); - } - if (scope.events.length > 1) { - setButtonMargin(); - addSpinner(); - } - } - }); - }); - - page = (index) ? Math.ceil((index+1)/50) : 1; - url += (/\/$/.test(url)) ? '?' : '&'; - url += (parent_id) ? 'page='+page +'&parent=' + parent_id + '&page_size=50&order=host_name,counter' : 'page_size=50&order=host_name,counter'; - - GetEvent({ - url: url, - scope: scope - }); - - scope.modalOK = function() { - $('#eventviewer-modal-dialog').dialog('close'); - scope.$destroy(); - }; - - }; - }]) - - .factory('GetEvent', ['Wait', 'Rest', 'ProcessErrors', - function(Wait, Rest, ProcessErrors) { - return function(params) { - var url = params.url, - scope = params.scope, - show_event = params.show_event, - results= []; - - if (show_event) { - $('#event-next-spinner').show(); - } - else { - Wait('start'); - } - - function getStatus(e) { - return (e.event === "runner_on_unreachable") ? "unreachable" : (e.event === "runner_on_skipped") ? 'skipped' : (e.failed) ? 'failed' : - (e.changed) ? 'changed' : 'ok'; - } - - Rest.setUrl(url); - Rest.get() - .success( function(data) { - - if(jQuery.isEmptyObject(data)) { - Wait('stop'); - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get event ' + url + '. ' }); - - } - else { - scope.next_event_set = data.next; - scope.prev_event_set = data.previous; - data.results.forEach(function(event) { - var msg, key, event_data = {}; - if (event.event_data.res) { - if (typeof event.event_data.res !== 'object') { - // turn event_data.res into an object - msg = event.event_data.res; - event.event_data.res = {}; - event.event_data.res.msg = msg; - } - for (key in event.event_data) { - if (key !== "res") { - event.event_data.res[key] = event.event_data[key]; - } - } - if (event.event_data.res.ansible_facts) { - // don't show fact gathering results - event.event_data.res.task = "Gathering Facts"; - delete event.event_data.res.ansible_facts; - } - event.event_data.res.status = getStatus(event); - event_data = event.event_data.res; - } - else { - event.event_data.status = getStatus(event); - event_data = event.event_data; - } - // convert results to stdout - if (event_data.results && typeof event_data.results === "object" && Array.isArray(event_data.results)) { - event_data.stdout = ""; - event_data.results.forEach(function(row) { - event_data.stdout += row + "\n"; - }); - delete event_data.results; - } - if (event_data.invocation) { - for (key in event_data.invocation) { - event_data[key] = event_data.invocation[key]; - } - delete event_data.invocation; - } - event_data.play = event.play; - if (event.task) { - event_data.task = event.task; - } - event_data.created = event.created; - event_data.role = event.role; - event_data.host_id = event.host; - event_data.host_name = event.host_name; - if (event_data.host) { - delete event_data.host; - } - event_data.id = event.id; - event_data.parent = event.parent; - event_data.event = (event.event_display) ? event.event_display : event.event; - results.push(event_data); - }); - if (show_event) { - scope.$emit('ShowNextEvent', results, show_event); - } - else { - scope.$emit('EventReady', results); - } - } //else statement - }) - .error(function(data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get event ' + url + '. GET returned: ' + status }); - }); - }; - }]) - - .factory('EventAddTable', ['$compile', '$filter', 'Empty', 'EventsViewerForm', function($compile, $filter, Empty, EventsViewerForm) { - return function(params) { - var scope = params.scope, - id = params.id, - event = params.event, - section = params.section, - html = '', e; - - function parseObject(obj) { - // parse nested JSON objects. a mini version of parseJSON without references to the event form object. - var i, key, html = ''; - for (key in obj) { - if (typeof obj[key] === "boolean" || typeof obj[key] === "number" || typeof obj[key] === "string") { - html += "" + key + ":" + obj[key] + ""; - } - else if (typeof obj[key] === "object" && Array.isArray(obj[key])) { - html += "" + key + ":["; - for (i = 0; i < obj[key].length; i++) { - html += obj[key][i] + ","; - } - html = html.replace(/,$/,''); - html += "]\n"; - } - else if (typeof obj[key] === "object") { - html += "" + key + ":\n\n" + parseObject(obj[key]) + "\n
\n\n"; - } - } - return html; - } - - function parseItem(itm, key, label) { - var i, html = ''; - if (Empty(itm)) { - // exclude empty items - } - else if (typeof itm === "boolean" || typeof itm === "number" || typeof itm === "string") { - html += "" + label + ":"; - if (key === "status") { - html += " " + itm; - } - else if (key === "start" || key === "end" || key === "created") { - if (!/Z$/.test(itm)) { - itm = itm.replace(/\ /,'T') + 'Z'; - html += $filter('longDate')(itm); - } - else { - html += $filter('longDate')(itm); - } - } - else if (key === "host_name" && event.host_id) { - html += "" + itm + ""; - } - else { - if( typeof itm === "string"){ - if(itm.indexOf('<') > -1 || itm.indexOf('>') > -1){ - itm = $filter('sanitize')(itm); - } - } - html += "" + itm + ""; - } - - html += "\n"; - } - else if (typeof itm === "object" && Array.isArray(itm)) { - html += "" + label + ":["; - for (i = 0; i < itm.length; i++) { - html += itm[i] + ","; - } - html = html.replace(/,$/,''); - html += "]\n"; - } - else if (typeof itm === "object") { - html += "" + label + ":\n\n" + parseObject(itm) + "\n
\n\n"; - } - return html; - } - - function parseJSON(obj) { - var h, html = '', key, keys, found = false, string_warnings = "", string_cmd = ""; - if (typeof obj === "object") { - html += "\n"; - html += "\n"; - keys = []; - for (key in EventsViewerForm.fields) { - if (EventsViewerForm.fields[key].section === section) { - keys.push(key); - } - } - keys.forEach(function(key) { - var h, label; - label = EventsViewerForm.fields[key].label; - h = parseItem(obj[key], key, label); - if (h) { - html += h; - found = true; - } - }); - if (section === 'Results') { - // Add to result fields that might not be found in the form object. - for (key in obj) { - h = ''; - if (key !== 'host_id' && key !== 'parent' && key !== 'event' && key !== 'src' && key !== 'md5sum' && - key !== 'stdout' && key !== 'traceback' && key !== 'stderr' && key !== 'cmd' && key !=='changed' && key !== "verbose_override" && - key !== 'feature_result' && key !== 'warnings') { - if (!EventsViewerForm.fields[key]) { - h = parseItem(obj[key], key, key); - if (h) { - html += h; - found = true; - } - } - } else if (key === 'cmd') { - // only show cmd if it's a cmd that was run - if (!EventsViewerForm.fields[key] && obj[key].length > 0) { - // include the label head Shell Command instead of CMD in the modal - if(typeof(obj[key]) === 'string'){ - obj[key] = [obj[key]]; - } - string_cmd += obj[key].join(" "); - h = parseItem(string_cmd, key, "Shell Command"); - if (h) { - html += h; - found = true; - } - } - } else if (key === 'warnings') { - if (!EventsViewerForm.fields[key] && obj[key].length > 0) { - if(typeof(obj[key]) === 'string'){ - obj[key] = [obj[key]]; - } - string_warnings += obj[key].join(" "); - h = parseItem(string_warnings, key, "Warnings"); - if (h) { - html += h; - found = true; - } - } - } - } - } - html += "\n"; - html += "
\n"; - } - return (found) ? html : ''; - } - html = parseJSON(event); - - e = angular.element(document.getElementById(id)); - e.empty(); - if (html) { - e.html(html); - $compile(e)(scope); - } - return (html) ? true : false; - }; - }]) - - .factory('EventAddTextarea', [ function() { - return function(params) { - var container_id = params.container_id, - val = params.val, - fld_id = params.fld_id, - html; - html = "
\n" + - "" + - "
\n"; - $('#' + container_id).empty().html(html); - }; - }]) - - .factory('EventAddPreFormattedText', ['$filter', function($filter) { - return function(params) { - var id = params.id, - val = params.val, - html; - if( typeof val === "string"){ - if(val.indexOf('<') > -1 || val.indexOf('>') > -1){ - val = $filter('sanitize')(val); - } - } - html = "
" + val + "
\n"; - $('#' + id).empty().html(html); - }; - }]); diff --git a/awx/ui/client/src/job-detail/host-event/host-event-details.partial.html b/awx/ui/client/src/job-detail/host-event/host-event-details.partial.html new file mode 100644 index 0000000000..66a60fcec9 --- /dev/null +++ b/awx/ui/client/src/job-detail/host-event/host-event-details.partial.html @@ -0,0 +1,49 @@ +
+
+
EVENT
+ +
+ +
+ STATUS + + + + + {{event.status || "No result found"}} + +
+
+ ID + {{event.id || "No result found"}} +
+
+ CREATED + {{event.created || "No result found"}} +
+
+ PLAY + {{event.play || "No result found"}} +
+
+ TASK + {{event.task || "No result found"}} +
+
+ MODULE + {{event.event_data.res.invocation.module_name || "No result found"}} +
+
+
+
RESULTS
+ +
+ {{key}} + {{value}} +
+
+ diff --git a/awx/ui/client/src/job-detail/host-event/host-event-json.partial.html b/awx/ui/client/src/job-detail/host-event/host-event-json.partial.html new file mode 100644 index 0000000000..a574043dbd --- /dev/null +++ b/awx/ui/client/src/job-detail/host-event/host-event-json.partial.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/awx/ui/client/src/job-detail/host-event/host-event-modal.partial.html b/awx/ui/client/src/job-detail/host-event/host-event-modal.partial.html new file mode 100644 index 0000000000..334aa78261 --- /dev/null +++ b/awx/ui/client/src/job-detail/host-event/host-event-modal.partial.html @@ -0,0 +1,36 @@ + \ No newline at end of file diff --git a/awx/ui/client/src/job-detail/host-event/host-event-stdout.partial.html b/awx/ui/client/src/job-detail/host-event/host-event-stdout.partial.html new file mode 100644 index 0000000000..436c25262a --- /dev/null +++ b/awx/ui/client/src/job-detail/host-event/host-event-stdout.partial.html @@ -0,0 +1,13 @@ +
+
+
STANDARD OUT
+ +
+ +
\ No newline at end of file diff --git a/awx/ui/client/src/job-detail/host-event/host-event-timing.partial.html b/awx/ui/client/src/job-detail/host-event/host-event-timing.partial.html new file mode 100644 index 0000000000..06171bd1c5 --- /dev/null +++ b/awx/ui/client/src/job-detail/host-event/host-event-timing.partial.html @@ -0,0 +1 @@ +
timing
\ No newline at end of file diff --git a/awx/ui/client/src/job-detail/host-event/host-event.block.less b/awx/ui/client/src/job-detail/host-event/host-event.block.less new file mode 100644 index 0000000000..73761cbb86 --- /dev/null +++ b/awx/ui/client/src/job-detail/host-event/host-event.block.less @@ -0,0 +1,66 @@ +@import "awx/ui/client/src/shared/branding/colors.less"; +@import "awx/ui/client/src/shared/branding/colors.default.less"; +@import "awx/ui/client/src/shared/layouts/one-plus-two.less"; + +.HostEvent .modal-footer{ + border: 0; + margin-top: 0px; + padding-top: 5px; +} +.HostEvent-controls{ + float: right; +} +.HostEvent-status--ok{ + color: @green; +} +.HostEvent-status--unreachable{ + color: @unreachable; +} +.HostEvent-status--changed{ + color: @changed; +} +.HostEvent-status--failed{ + color: @warning; +} +.HostEvent-status--skipped{ + color: @skipped; +} +.HostEvent-title{ + color: @default-interface-txt; + font-weight: 600; +} +.HostEvent .modal-body{ + max-height: 500px; + overflow-y: auto; + padding: 20px; +} +.HostEvent-nav{ + padding-top: 12px; + padding-bottom: 12px; +} +.HostEvent-field{ + margin-bottom: 8px; +} +.HostEvent-field--label{ + .OnePlusTwo-left--detailsLabel; + width: 80px; + margin-right: 20px; + font-size: 12px; +} +.HostEvent-field{ + .OnePlusTwo-left--detailsRow; +} +.HostEvent-field--content{ + .OnePlusTwo-left--detailsContent; +} +.HostEvent-details--left, .HostEvent-details--right{ + vertical-align:top; + width:270px; + display: inline-block; + +} +.HostEvent-details--right{ + .HostEvent-field--label{ + width: 170px; + } +} diff --git a/awx/ui/client/src/job-detail/host-event/host-event.controller.js b/awx/ui/client/src/job-detail/host-event/host-event.controller.js new file mode 100644 index 0000000000..a1485f4714 --- /dev/null +++ b/awx/ui/client/src/job-detail/host-event/host-event.controller.js @@ -0,0 +1,71 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + + export default + ['$stateParams', '$scope', '$state', 'Wait', 'JobDetailService', 'moment', 'event', + function($stateParams, $scope, $state, Wait, JobDetailService, moment, event){ + // Avoid rendering objects in the details fieldset + // ng-if="processResults(value)" via host-event-details.partial.html + $scope.processResults = function(value){ + if (typeof value == 'object'){return false} + else {return true} + }; + + var codeMirror = function(){ + var el = $('#HostEvent-json')[0]; + var editor = CodeMirror.fromTextArea(el, { + lineNumbers: true, + mode: {name: "javascript", json: true} + }); + editor.getDoc().setValue(JSON.stringify($scope.json, null, 4)); + }; + + $scope.getActiveHostIndex = function(){ + var result = $scope.hostResults.filter(function( obj ) { + return obj.id == $scope.event.id; + }); + return $scope.hostResults.indexOf(result[0]) + }; + + $scope.showPrev = function(){ + return $scope.getActiveHostIndex() != 0 + }; + + $scope.showNext = function(){ + return $scope.getActiveHostIndex() < $scope.hostResults.indexOf($scope.hostResults[$scope.hostResults.length - 1]) + }; + + $scope.goNext = function(){ + var index = $scope.getActiveHostIndex() + 1; + var id = $scope.hostResults[index].id; + $state.go('jobDetail.host-event.details', {eventId: id}) + }; + + $scope.goPrevious = function(){ + var index = $scope.getActiveHostIndex() - 1; + var id = $scope.hostResults[index].id; + $state.go('jobDetail.host-event.details', {eventId: id}) + }; + + var init = function(){ + $scope.event = event.data.results[0]; + $scope.event.created = moment($scope.event.created).format(); + $scope.processEventStatus = JobDetailService.processEventStatus($scope.event); + $scope.hostResults = $stateParams.hostResults; + $scope.json = JobDetailService.processJson($scope.event); + if ($state.current.name == 'jobDetail.host-event.json'){ + codeMirror(); + } + try { + $scope.stdout = $scope.event.event_data.res.stdout + } + catch(err){ + $scope.sdout = null; + } + $('#HostEvent').modal('show'); + }; + init(); + }]; \ No newline at end of file diff --git a/awx/ui/client/src/job-detail/host-event/host-event.route.js b/awx/ui/client/src/job-detail/host-event/host-event.route.js new file mode 100644 index 0000000000..7d4f1cc011 --- /dev/null +++ b/awx/ui/client/src/job-detail/host-event/host-event.route.js @@ -0,0 +1,86 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + + import {templateUrl} from '../../shared/template-url/template-url.factory'; + +var hostEventModal = { + name: 'jobDetail.host-event', + url: '/host-event/:eventId', + controller: 'HostEventController', + params:{ + hostResults: { + value: null, + squash: false, + } + }, + templateUrl: templateUrl('job-detail/host-event/host-event-modal'), + resolve: { + features: ['FeaturesService', function(FeaturesService){ + return FeaturesService.get(); + }], + event: ['JobDetailService','$stateParams', function(JobDetailService, $stateParams) { + return JobDetailService.getRelatedJobEvents($stateParams.id, { + id: $stateParams.eventId + }).success(function(res){ return res.results[0]}) + }] + }, + onExit: function($state){ + // close the modal + // using an onExit event to handle cases where the user navs away using the url bar / back and not modal "X" + $('#HostEvent').modal('hide'); + // hacky way to handle user browsing away via URL bar + $('.modal-backdrop').remove(); + $('body').removeClass('modal-open'); + } + } + + var hostEventDetails = { + name: 'jobDetail.host-event.details', + url: '/details', + controller: 'HostEventController', + templateUrl: templateUrl('job-detail/host-event/host-event-details'), + resolve: { + features: ['FeaturesService', function(FeaturesService){ + return FeaturesService.get(); + }] + } + } + + var hostEventJson = { + name: 'jobDetail.host-event.json', + url: '/json', + controller: 'HostEventController', + templateUrl: templateUrl('job-detail/host-event/host-event-json'), + resolve: { + features: ['FeaturesService', function(FeaturesService){ + return FeaturesService.get(); + }] + } + }; + var hostEventTiming = { + name: 'jobDetail.host-event.timing', + url: '/timing', + controller: 'HostEventController', + templateUrl: templateUrl('job-detail/host-event/host-event-timing'), + resolve: { + features: ['FeaturesService', function(FeaturesService){ + return FeaturesService.get(); + }] + } + }; + var hostEventStdout = { + name: 'jobDetail.host-event.stdout', + url: '/stdout', + controller: 'HostEventController', + templateUrl: templateUrl('job-detail/host-event/host-event-stdout'), + resolve: { + features: ['FeaturesService', function(FeaturesService){ + return FeaturesService.get(); + }] + } + }; + + export {hostEventDetails, hostEventJson, hostEventTiming, hostEventStdout, hostEventModal} \ No newline at end of file diff --git a/awx/ui/client/src/job-detail/host-event/main.js b/awx/ui/client/src/job-detail/host-event/main.js new file mode 100644 index 0000000000..c2b82530a1 --- /dev/null +++ b/awx/ui/client/src/job-detail/host-event/main.js @@ -0,0 +1,21 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + + import {hostEventModal, hostEventDetails, hostEventTiming, + hostEventJson, hostEventStdout} from './host-event.route'; + import controller from './host-event.controller'; + + export default + angular.module('jobDetail.hostEvent', []) + .controller('HostEventController', controller) + + .run(['$stateExtender', function($stateExtender){ + $stateExtender.addState(hostEventModal); + $stateExtender.addState(hostEventDetails); + $stateExtender.addState(hostEventTiming); + $stateExtender.addState(hostEventJson); + $stateExtender.addState(hostEventStdout); + }]); \ No newline at end of file diff --git a/awx/ui/client/src/job-detail/host-events/host-events.controller.js b/awx/ui/client/src/job-detail/host-events/host-events.controller.js index a3a1c8faaf..732ceab45a 100644 --- a/awx/ui/client/src/job-detail/host-events/host-events.controller.js +++ b/awx/ui/client/src/job-detail/host-events/host-events.controller.js @@ -6,13 +6,14 @@ export default ['$stateParams', '$scope', '$rootScope', '$state', 'Wait', - 'JobDetailService', 'CreateSelect2', + 'JobDetailService', 'CreateSelect2', 'hosts', function($stateParams, $scope, $rootScope, $state, Wait, - JobDetailService, CreateSelect2){ + JobDetailService, CreateSelect2, hosts){ // pagination not implemented yet, but it'll depend on this $scope.page_size = $stateParams.page_size; + $scope.processEventStatus = JobDetailService.processEventStatus; $scope.activeFilter = $stateParams.filter || null; $scope.search = function(){ @@ -39,6 +40,7 @@ var filter = function(filter){ Wait('start'); + if (filter == 'all'){ return JobDetailService.getRelatedJobEvents($stateParams.id, { host_name: $stateParams.hostName, @@ -104,39 +106,6 @@ filter($('.HostEvents-select').val()); }); - $scope.processStatus = function(event, $index){ - // the stack for which status to display is - // unreachable > failed > changed > ok - // uses the API's runner events and convenience properties .failed .changed to determine status. - // see: job_event_callback.py - if (event.event == 'runner_on_unreachable'){ - $scope.results[$index].status = 'Unreachable'; - return 'HostEvents-status--unreachable' - } - // equiv to 'runner_on_error' && 'runner on failed' - if (event.failed){ - $scope.results[$index].status = 'Failed'; - return 'HostEvents-status--failed' - } - // catch the changed case before ok, because both can be true - if (event.changed){ - $scope.results[$index].status = 'Changed'; - return 'HostEvents-status--changed' - } - if (event.event == 'runner_on_ok'){ - $scope.results[$index].status = 'OK'; - return 'HostEvents-status--ok' - } - if (event.event == 'runner_on_skipped'){ - $scope.results[$index].status = 'Skipped'; - return 'HostEvents-status--skipped' - } - else{ - // study a case where none of these apply - } - }; - - var init = function(){ // create filter dropdown CreateSelect2({ @@ -145,6 +114,7 @@ }); // process the filter if one was passed if ($stateParams.filter){ + Wait('start'); filter($stateParams.filter).success(function(res){ $scope.results = res.results; Wait('stop'); @@ -152,25 +122,11 @@ });; } else{ - Wait('start'); - JobDetailService.getRelatedJobEvents($stateParams.id, { - host_name: $stateParams.hostName, - page_size: $stateParams.page_size}) - .success(function(res){ - $scope.pagination = res; - $scope.results = res.results; - Wait('stop'); - $('#HostEvents').modal('show'); - - }); + $scope.results = hosts.data.results; + $('#HostEvents').modal('show'); } }; - $scope.goBack = function(){ - // go back to the job details state - // we're leaning on $stateProvider's onExit to close the modal - $state.go('jobDetail'); - }; init(); diff --git a/awx/ui/client/src/job-detail/host-events/host-events.partial.html b/awx/ui/client/src/job-detail/host-events/host-events.partial.html index ff2d21714a..1b7702ab4a 100644 --- a/awx/ui/client/src/job-detail/host-events/host-events.partial.html +++ b/awx/ui/client/src/job-detail/host-events/host-events.partial.html @@ -5,7 +5,7 @@
HOST EVENTS -
@@ -37,7 +37,7 @@ - + {{event.status}} @@ -56,7 +56,7 @@ diff --git a/awx/ui/client/src/job-detail/host-events/host-events.route.js b/awx/ui/client/src/job-detail/host-events/host-events.route.js index ebb2bb7bdd..4e2c6d4e93 100644 --- a/awx/ui/client/src/job-detail/host-events/host-events.route.js +++ b/awx/ui/client/src/job-detail/host-events/host-events.route.js @@ -1,5 +1,5 @@ /************************************************* - * Copyright (c) 2015 Ansible, Inc. + * Copyright (c) 2016 Ansible, Inc. * * All Rights Reserved *************************************************/ @@ -25,6 +25,11 @@ export default { resolve: { features: ['FeaturesService', function(FeaturesService) { return FeaturesService.get(); - }] + }], + hosts: ['JobDetailService','$stateParams', function(JobDetailService, $stateParams) { + return JobDetailService.getRelatedJobEvents($stateParams.id, { + host_name: $stateParams.hostName + }).success(function(res){ return res.results[0]}) + }] } }; diff --git a/awx/ui/client/src/job-detail/job-detail.controller.js b/awx/ui/client/src/job-detail/job-detail.controller.js index 1383f04c35..9bc6daf2fc 100644 --- a/awx/ui/client/src/job-detail/job-detail.controller.js +++ b/awx/ui/client/src/job-detail/job-detail.controller.js @@ -18,7 +18,7 @@ export default 'JobIsFinished', 'SetTaskStyles', 'DigestEvent', 'UpdateDOM', 'DeleteJob', 'PlaybookRun', 'LoadPlays', 'LoadTasks', 'LoadHosts', 'HostsEdit', 'ParseVariableString', 'GetChoices', 'fieldChoices', 'fieldLabels', - 'EditSchedule', 'ParseTypeChange', 'JobDetailService', 'EventViewer', + 'EditSchedule', 'ParseTypeChange', 'JobDetailService', function( $location, $rootScope, $filter, $scope, $compile, $stateParams, $log, ClearScope, GetBasePath, Wait, ProcessErrors, @@ -27,7 +27,7 @@ export default SetTaskStyles, DigestEvent, UpdateDOM, DeleteJob, PlaybookRun, LoadPlays, LoadTasks, LoadHosts, HostsEdit, ParseVariableString, GetChoices, fieldChoices, - fieldLabels, EditSchedule, ParseTypeChange, JobDetailService, EventViewer + fieldLabels, EditSchedule, ParseTypeChange, JobDetailService, ) { ClearScope(); @@ -1119,17 +1119,6 @@ export default } }; - scope.viewHostResults = function(id) { - EventViewer({ - scope: scope, - url: scope.job.related.job_events, - parent_id: scope.selectedTask, - event_id: id, - index: this.$index, - title: 'Host Event' - }); - }; - if (scope.removeDeleteFinished) { scope.removeDeleteFinished(); } diff --git a/awx/ui/client/src/job-detail/job-detail.partial.html b/awx/ui/client/src/job-detail/job-detail.partial.html index 8daba354b5..c1bfd91fbc 100644 --- a/awx/ui/client/src/job-detail/job-detail.partial.html +++ b/awx/ui/client/src/job-detail/job-detail.partial.html @@ -343,7 +343,9 @@ - + @@ -422,7 +424,7 @@
{{ result.name }}{{ result.name }} + {{ result.name }}{{ result.name }} + {{ result.item }} {{ result.msg }}
- {{ host.name }} + {{ host.name }} {{ host.ok }} diff --git a/awx/ui/client/src/job-detail/job-detail.service.js b/awx/ui/client/src/job-detail/job-detail.service.js index 8597fff9f1..58249d9aa2 100644 --- a/awx/ui/client/src/job-detail/job-detail.service.js +++ b/awx/ui/client/src/job-detail/job-detail.service.js @@ -2,13 +2,83 @@ export default ['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', function($rootScope, Rest, GetBasePath, ProcessErrors){ return { - /* + /* For ES6 it might be useful to set some default params here, e.g. getJobHostSummaries: function(id, page_size=200, order='host_name'){} without ES6, we'd have to supply defaults like this: this.page_size = params.page_size ? params.page_size : 200; - */ + */ + + // the the API passes through Ansible's event_data response + // we need to massage away the verbose and redundant properties + + processJson: function(data){ + // a deep copy + var result = $.extend(true, {}, data); + // configure fields to ignore + var ignored = [ + 'event_data', + 'related', + 'summary_fields', + 'url', + 'ansible_facts', + ]; + + // remove ignored properties + Object.keys(result).forEach(function(key, index){ + if (ignored.indexOf(key) > -1) { + delete result[key] + } + }); + + // flatten Ansible's passed-through response + result.event_data = {}; + Object.keys(data.event_data.res).forEach(function(key, index){ + if (ignored.indexOf(key) > -1) { + return + } + else{ + //console.log(key, data.event_data.res[key]) + result.event_data[key] = data.event_data.res[key]; + } + }); + + return result + }, + + processEventStatus: function(event){ + // Generate a helper class for job_event statuses + // the stack for which status to display is + // unreachable > failed > changed > ok + // uses the API's runner events and convenience properties .failed .changed to determine status. + // see: job_event_callback.py + if (event.event == 'runner_on_unreachable'){ + event.status = 'Unreachable'; + return 'HostEvents-status--unreachable' + } + // equiv to 'runner_on_error' && 'runner on failed' + if (event.failed){ + event.status = 'Failed'; + return 'HostEvents-status--failed' + } + // catch the changed case before ok, because both can be true + if (event.changed){ + event.status = 'Changed'; + return 'HostEvents-status--changed' + } + if (event.event == 'runner_on_ok'){ + event.status = 'OK'; + return 'HostEvents-status--ok' + } + if (event.event == 'runner_on_skipped'){ + event.status = 'Skipped'; + return 'HostEvents-status--skipped' + } + else{ + // study a case where none of these apply + } + }, // GET events related to a job run // e.g. diff --git a/awx/ui/client/src/job-detail/main.js b/awx/ui/client/src/job-detail/main.js index 8a9fc30aff..f497b76677 100644 --- a/awx/ui/client/src/job-detail/main.js +++ b/awx/ui/client/src/job-detail/main.js @@ -8,10 +8,12 @@ import route from './job-detail.route'; import controller from './job-detail.controller'; import service from './job-detail.service'; import hostEvents from './host-events/main'; +import hostEvent from './host-event/main'; export default angular.module('jobDetail', [ - hostEvents.name + hostEvents.name, + hostEvent.name ]) .controller('JobDetailController', controller) .service('JobDetailService', service) diff --git a/awx/ui/client/src/partials/eventviewer.html b/awx/ui/client/src/partials/eventviewer.html deleted file mode 100644 index 941e9d80d6..0000000000 --- a/awx/ui/client/src/partials/eventviewer.html +++ /dev/null @@ -1,34 +0,0 @@ - diff --git a/awx/ui/client/src/shared/layouts/one-plus-two.less b/awx/ui/client/src/shared/layouts/one-plus-two.less index 87c44fe056..a296af7127 100644 --- a/awx/ui/client/src/shared/layouts/one-plus-two.less +++ b/awx/ui/client/src/shared/layouts/one-plus-two.less @@ -61,7 +61,8 @@ } .OnePlusTwo-left--detailsLabel { - width: 140px; + word-wrap: break-word; + width: 170px; display: inline-block; color: @default-interface-txt; text-transform: uppercase;