From 13ee468b147fa585a772ed117bc61a5de3c2411e Mon Sep 17 00:00:00 2001 From: jaredevantabor Date: Mon, 14 Nov 2016 16:59:35 -0800 Subject: [PATCH] Adding tooltips and routes for host events will circle back with more changes for the host events modal --- awx/ui/client/src/job-detail/main.js | 4 +- .../host-event-codemirror.partial.html | 2 + .../host-event-details.partial.html | 46 ++++++ .../host-event/host-event-modal.partial.html | 36 +++++ .../host-event/host-event-timing.partial.html | 1 + .../host-event/host-event.block.less | 150 ++++++++++++++++++ .../host-event/host-event.controller.js | 110 +++++++++++++ .../host-event/host-event.route.js | 65 ++++++++ .../client/src/job-results/host-event/main.js | 21 +++ awx/ui/client/src/job-results/main.js | 3 +- .../src/job-results/parse-stdout.service.js | 14 +- 11 files changed, 447 insertions(+), 5 deletions(-) create mode 100644 awx/ui/client/src/job-results/host-event/host-event-codemirror.partial.html create mode 100644 awx/ui/client/src/job-results/host-event/host-event-details.partial.html create mode 100644 awx/ui/client/src/job-results/host-event/host-event-modal.partial.html create mode 100644 awx/ui/client/src/job-results/host-event/host-event-timing.partial.html create mode 100644 awx/ui/client/src/job-results/host-event/host-event.block.less create mode 100644 awx/ui/client/src/job-results/host-event/host-event.controller.js create mode 100644 awx/ui/client/src/job-results/host-event/host-event.route.js create mode 100644 awx/ui/client/src/job-results/host-event/main.js diff --git a/awx/ui/client/src/job-detail/main.js b/awx/ui/client/src/job-detail/main.js index 1307554b6e..28a73bea79 100644 --- a/awx/ui/client/src/job-detail/main.js +++ b/awx/ui/client/src/job-detail/main.js @@ -8,13 +8,13 @@ import controller from './job-detail.controller'; import service from './job-detail.service'; import hostEvents from './host-events/main'; -import hostEvent from './host-event/main'; +// import hostEvent from './host-event/main'; import hostSummary from './host-summary/main'; export default angular.module('jobDetail', [ hostEvents.name, - hostEvent.name, + // hostEvent.name, hostSummary.name ]) .controller('JobDetailController', controller) diff --git a/awx/ui/client/src/job-results/host-event/host-event-codemirror.partial.html b/awx/ui/client/src/job-results/host-event/host-event-codemirror.partial.html new file mode 100644 index 0000000000..7c744d2169 --- /dev/null +++ b/awx/ui/client/src/job-results/host-event/host-event-codemirror.partial.html @@ -0,0 +1,2 @@ + diff --git a/awx/ui/client/src/job-results/host-event/host-event-details.partial.html b/awx/ui/client/src/job-results/host-event/host-event-details.partial.html new file mode 100644 index 0000000000..60cd44a9b7 --- /dev/null +++ b/awx/ui/client/src/job-results/host-event/host-event-details.partial.html @@ -0,0 +1,46 @@ +
+
EVENT
+ +
+ STATUS + + + + + {{processEventStatus(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-results/host-event/host-event-modal.partial.html b/awx/ui/client/src/job-results/host-event/host-event-modal.partial.html new file mode 100644 index 0000000000..db236894e8 --- /dev/null +++ b/awx/ui/client/src/job-results/host-event/host-event-modal.partial.html @@ -0,0 +1,36 @@ + diff --git a/awx/ui/client/src/job-results/host-event/host-event-timing.partial.html b/awx/ui/client/src/job-results/host-event/host-event-timing.partial.html new file mode 100644 index 0000000000..06171bd1c5 --- /dev/null +++ b/awx/ui/client/src/job-results/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-results/host-event/host-event.block.less b/awx/ui/client/src/job-results/host-event/host-event.block.less new file mode 100644 index 0000000000..fc84890856 --- /dev/null +++ b/awx/ui/client/src/job-results/host-event/host-event.block.less @@ -0,0 +1,150 @@ +@import "./client/src/shared/branding/colors.less"; +@import "./client/src/shared/branding/colors.default.less"; +@import "./client/src/shared/layouts/one-plus-two.less"; + +.noselect { + -webkit-touch-callout: none; /* iOS Safari */ + -webkit-user-select: none; /* Chrome/Safari/Opera */ + -khtml-user-select: none; /* Konqueror */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* Internet Explorer/Edge */ + user-select: none; /* Non-prefixed version, currently + not supported by any browser */ +} + +@media screen and (min-width: 768px){ + .HostEvent .modal-dialog{ + width: 700px; + } +} +.HostEvent .CodeMirror{ + overflow-x: hidden; +} +.HostEvent-controls button.HostEvent-close{ + color: #FFFFFF; + text-transform: uppercase; + padding-left: 15px; + padding-right: 15px; + background-color: @default-link; + border-color: @default-link; + &:hover{ + background-color: @default-link-hov; + border-color: @default-link-hov; + } +} +.HostEvent-body{ + margin-bottom: 10px; +} +.HostEvent-tab { + color: @btn-txt; + background-color: @btn-bg; + font-size: 12px; + border: 1px solid @btn-bord; + height: 30px; + border-radius: 5px; + margin-right: 20px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 5px; + padding-top: 5px; + transition: background-color 0.2s; + text-transform: uppercase; + text-align: center; + white-space: nowrap; + .noselect; +} +.HostEvent-tab:hover { + color: @btn-txt; + background-color: @btn-bg-hov; + cursor: pointer; +} +.HostEvent-tab--selected{ + color: @btn-txt-sel!important; + background-color: @default-icon!important; + border-color: @default-icon!important; +} +.HostEvent-view--container{ + width: 100%; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: space-between; +} +.HostEvent .modal-footer{ + border: 0; + margin-top: 0px; + padding-top: 5px; +} +.HostEvent-controls{ + float: right; + button { + margin-left: 10px; + } +} +.HostEvent-status--ok{ + color: @green; +} +.HostEvent-status--unreachable{ + color: @unreachable; +} +.HostEvent-status--changed{ + color: @changed; +} +.HostEvent-status--failed{ + color: @default-err; +} +.HostEvent-status--skipped{ + color: @skipped; +} +.HostEvent-title{ + color: @default-interface-txt; + font-weight: 600; + margin-bottom: 8px; +} +.HostEvent .modal-body{ + max-height: 500px; + overflow-y: auto; + padding: 20px; +} +.HostEvent-nav{ + padding-top: 12px; + padding-bottom: 12px; +} +.HostEvent-field{ + margin-bottom: 8px; + flex: 0 1 12em; +} +.HostEvent-field--label{ + text-transform: uppercase; + flex: 0 1 80px; + max-width: 80px; + font-size: 12px; + word-wrap: break-word; +} +.HostEvent-field{ + .OnePlusTwo-left--detailsRow; +} +.HostEvent-field--content{ + word-wrap: break-word; + max-width: 13em; + flex: 0 1 13em; +} +.HostEvent-details--left, .HostEvent-details--right{ + flex: 1 1 47%; +} +.HostEvent-details--left{ + margin-right: 40px; +} +.HostEvent-details--right{ + .HostEvent-field--label{ + flex: 0 1 25em; + } + .HostEvent-field--content{ + max-width: 15em; + flex: 0 1 15em; + align-self: flex-end; + } +} +.HostEvent-button:disabled { + pointer-events: all!important; +} diff --git a/awx/ui/client/src/job-results/host-event/host-event.controller.js b/awx/ui/client/src/job-results/host-event/host-event.controller.js new file mode 100644 index 0000000000..d5377014e2 --- /dev/null +++ b/awx/ui/client/src/job-results/host-event/host-event.controller.js @@ -0,0 +1,110 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + + + export default + ['$stateParams', '$scope', '$state', 'Wait', 'JobDetailService', 'hostEvent', 'hostResults', + function($stateParams, $scope, $state, Wait, JobDetailService, hostEvent, hostResults){ + + $scope.processEventStatus = JobDetailService.processEventStatus; + $scope.hostResults = []; + // 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;} + }; + $scope.isStdOut = function(){ + if ($state.current.name === 'jobDetails.host-event.stdout' || $state.current.name === 'jobDetaisl.histe-event.stderr'){ + return 'StandardOut-preContainer StandardOut-preContent'; + } + }; + /*ignore jslint start*/ + var initCodeMirror = function(el, data, mode){ + var container = document.getElementById(el); + var editor = CodeMirror.fromTextArea(container, { // jshint ignore:line + lineNumbers: true, + mode: mode + }); + editor.setSize("100%", 300); + editor.getDoc().setValue(data); + }; + /*ignore jslint end*/ + $scope.isActiveState = function(name){ + return $state.current.name === name; + }; + + $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.goPrev = 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 = _.cloneDeep(hostEvent); + $scope.hostResults = hostResults; + $scope.json = JobDetailService.processJson(hostEvent); + + // grab standard out & standard error if present, and remove from the results displayed in the details panel + if (hostEvent.stdout){ + $scope.stdout = hostEvent.stdout; + delete $scope.event.stdout; + } + if (hostEvent.stderr){ + $scope.stderr = hostEvent.stderr; + delete $scope.event.stderr; + } + // instantiate Codemirror + // try/catch pattern prevents the abstract-state controller from complaining about element being null + if ($state.current.name === 'jobDetail.host-event.json'){ + try{ + initCodeMirror('HostEvent-codemirror', JSON.stringify($scope.json, null, 4), {name: "javascript", json: true}); + } + catch(err){ + // element with id HostEvent-codemirror is not the view controlled by this instance of HostEventController + } + } + else if ($state.current.name === 'jobDetail.host-event.stdout'){ + try{ + initCodeMirror('HostEvent-codemirror', $scope.stdout, 'shell'); + } + catch(err){ + // element with id HostEvent-codemirror is not the view controlled by this instance of HostEventController + } + } + else if ($state.current.name === 'jobDetail.host-event.stderr'){ + try{ + initCodeMirror('HostEvent-codemirror', $scope.stderr, 'shell'); + } + catch(err){ + // element with id HostEvent-codemirror is not the view controlled by this instance of HostEventController + } + } + $('#HostEvent').modal('show'); + }; + init(); + }]; diff --git a/awx/ui/client/src/job-results/host-event/host-event.route.js b/awx/ui/client/src/job-results/host-event/host-event.route.js new file mode 100644 index 0000000000..1be6f685ac --- /dev/null +++ b/awx/ui/client/src/job-results/host-event/host-event.route.js @@ -0,0 +1,65 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import { templateUrl } from '../../shared/template-url/template-url.factory'; + +var hostEventModal = { + name: 'jobDetail.host-event', + url: '/task/:taskId/host-event/:eventId', + controller: 'HostEventController', + templateUrl: templateUrl('job-results/host-event/host-event-modal'), + 'abstract': true, + resolve: { + hostEvent: ['JobDetailService', '$stateParams', function(JobDetailService, $stateParams) { + return JobDetailService.getRelatedJobEvents($stateParams.id, { + id: $stateParams.eventId + }).then(function(res) { + return res.data.results[0]; }); + }], + hostResults: ['JobDetailService', '$stateParams', function(JobDetailService, $stateParams) { + return JobDetailService.getJobEventChildren($stateParams.taskId).then(res => res.data.results); + }] + }, + onExit: function() { + // 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-results/host-event/host-event-details'), +}; + +var hostEventJson = { + name: 'jobDetail.host-event.json', + url: '/json', + controller: 'HostEventController', + templateUrl: templateUrl('job-results/host-event/host-event-codemirror') +}; + +var hostEventStdout = { + name: 'jobDetail.host-event.stdout', + url: '/stdout', + controller: 'HostEventController', + templateUrl: templateUrl('job-results/host-event/host-event-codemirror') +}; + +var hostEventStderr = { + name: 'jobDetail.host-event.stderr', + url: '/stderr', + controller: 'HostEventController', + templateUrl: templateUrl('job-results/host-event/host-event-codemirror') +}; + + +export { hostEventDetails, hostEventJson, hostEventModal, hostEventStdout, hostEventStderr }; diff --git a/awx/ui/client/src/job-results/host-event/main.js b/awx/ui/client/src/job-results/host-event/main.js new file mode 100644 index 0000000000..6f0be39b04 --- /dev/null +++ b/awx/ui/client/src/job-results/host-event/main.js @@ -0,0 +1,21 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + + import {hostEventModal, hostEventDetails, + hostEventJson, hostEventStdout, hostEventStderr} from './host-event.route'; + import controller from './host-event.controller'; + + export default + angular.module('jobResults.hostEvent', []) + .controller('HostEventController', controller) + + .run(['$stateExtender', function($stateExtender){ + $stateExtender.addState(hostEventModal); + $stateExtender.addState(hostEventDetails); + $stateExtender.addState(hostEventJson); + $stateExtender.addState(hostEventStdout); + $stateExtender.addState(hostEventStderr); + }]); diff --git a/awx/ui/client/src/job-results/main.js b/awx/ui/client/src/job-results/main.js index 319c88d985..c367b26918 100644 --- a/awx/ui/client/src/job-results/main.js +++ b/awx/ui/client/src/job-results/main.js @@ -6,6 +6,7 @@ import hostStatusBar from './host-status-bar/main'; import jobResultsStdOut from './job-results-stdout/main'; +import hostEvent from './host-event/main'; import route from './job-results.route.js'; @@ -18,7 +19,7 @@ import parseStdoutService from './parse-stdout.service'; import durationFilter from './duration.filter'; export default - angular.module('jobResults', [hostStatusBar.name, jobResultsStdOut.name]) + angular.module('jobResults', [hostStatusBar.name, jobResultsStdOut.name, hostEvent.name]) .run(['$stateExtender', function($stateExtender) { $stateExtender.addState(route); }]) diff --git a/awx/ui/client/src/job-results/parse-stdout.service.js b/awx/ui/client/src/job-results/parse-stdout.service.js index 998eee33b8..12620d3197 100644 --- a/awx/ui/client/src/job-results/parse-stdout.service.js +++ b/awx/ui/client/src/job-results/parse-stdout.service.js @@ -7,7 +7,6 @@ export default [function(){ var val = { prettify: function(line){ - // TODO: figure out from Jared what this is if (line.indexOf("[K") > -1) { console.log(line); @@ -31,6 +30,17 @@ export default [function(){ line = line.replace(/\[0m/g, ''); return line; }, + getAnchorTags: function(event, line){ + if(event.event.indexOf("runner_") === -1){ + return line; + } + else{ + var str = ``, + str2 = ''; + return str.concat(line).concat(str2); + } + + }, getCollapseClasses: function(event, line, lineNum) { var string = ""; if (event.event_name === "playbook_on_play_start") { @@ -104,7 +114,7 @@ export default [function(){ return `
${this.getCollapseIcon(event, lineArr[1])}${lineArr[0]}
-
${this.prettify(lineArr[1])}
+
${this.getAnchorTags(event, this.prettify(lineArr[1]))}
`; }).join(""); }