Adding tooltips and routes for host events

will circle back with more changes for the host events modal
This commit is contained in:
jaredevantabor 2016-11-14 16:59:35 -08:00
parent 3c70171413
commit 13ee468b14
11 changed files with 447 additions and 5 deletions

View File

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

View File

@ -0,0 +1,2 @@
<textarea id="HostEvent-codemirror" class="HostEvent-codemirror">
</textarea>

View File

@ -0,0 +1,46 @@
<div class="HostEvent-details--left">
<div class="HostEvent-title">EVENT</div>
<div class="HostEvent-field">
<span class="HostEvent-field--label">HOST</span>
<span class="HostEvent-field--content">
<a ui-sref="jobDetail.host-events({hostName: event.host_name})">{{event.host_name || "No result found"}}</a></span>
</div>
<div class="HostEvent-field">
<span class="HostEvent-field--label">STATUS</span>
<span class="HostEvent-field--content">
<a class="HostEvents-status">
<i class="fa fa-circle" ng-class="processEventStatus(event).class"></i>
</a>
{{processEventStatus(event).status || "No result found"}}
</span>
</div>
<div class="HostEvent-field">
<span class="HostEvent-field--label">ID</span>
<span class="HostEvent-field--content">{{event.id || "No result found"}}</span>
</div>
<div class="HostEvent-field">
<span class="HostEvent-field--label">CREATED</span>
<span class="HostEvent-field--content">{{event.created || "No result found"}}</span>
</div>
<div class="HostEvent-field">
<span class="HostEvent-field--label">PLAY</span>
<span class="HostEvent-field--content">{{event.play || "No result found"}}</span>
</div>
<div class="HostEvent-field">
<span class="HostEvent-field--label">TASK</span>
<span class="HostEvent-field--content">{{event.task || "No result found"}}</span>
</div>
<div class="HostEvent-field">
<span class="HostEvent-field--label">MODULE</span>
<span class="HostEvent-field--content">{{event.event_data.res.invocation.module_name || "No result found"}}</span>
</div>
</div>
<div class="HostEvent-details--right" ng-show="event.event_data.res">
<div class="HostEvent-title">RESULTS</div>
<!-- discard any objects in the ansible response until we decide to flatten them -->
<div class="HostEvent-field" ng-repeat="(key, value) in results = event.event_data.res track by $index" ng-if="processResults(value)">
<span class="HostEvent-field--label">{{key}}</span>
<span class="HostEvent-field--content">{{value}}</span>
</div>
</div>

View File

@ -0,0 +1,36 @@
<div id="HostEvent" class="HostEvent modal" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<!-- modal body -->
<div class="modal-body">
<div class="HostEvent-header">
<span class="HostEvent-title">HOST EVENT</span>
<!-- close -->
<button ui-sref="jobDetail" type="button" class="close">
<i class="fa fa-times-circle"></i>
</button>
</div>
<div class="HostEvent-nav">
<!-- view navigation buttons -->
<button ui-sref="jobDetail.host-event.details" type="button" class="btn btn-sm btn-default HostEvent-tab" ng-class="{'HostEvent-tab--selected' : isActiveState('jobDetail.host-event.details')}">Details</button>
<button ui-sref="jobDetail.host-event.json" type="button" class="btn btn-sm btn-default HostEvent-tab" ng-class="{'HostEvent-tab--selected' : isActiveState('jobDetail.host-event.json')}">JSON</button>
<button ng-if="stdout" ui-sref="jobDetail.host-event.stdout" type="button" class="btn btn-sm btn-default HostEvent-tab" ng-class="{'HostEvent-tab--selected' : isActiveState('jobDetail.host-event.stdout')}">Standard Out</button>
<button ng-if="stderr" ui-sref="jobDetail.host-event.stderr" type="button" class="btn btn-sm btn-default HostEvent-tab" ng-class="{'HostEvent-tab--selected' : isActiveState('jobDetail.host-event.stderr')}">Standard Error</button>
</div>
<div class="HostEvent-body">
<!-- views -->
<div class="HostEvent-view--container" ui-view></div>
</div>
<!-- controls -->
<div class="HostEvent-controls">
<button ng-disabled="!showPrev()" ng-click="goPrev()"
class="btn btn-sm btn-default HostEvent-button">Prev Host</button>
<button ng-disabled="!showNext()"ng-click="goNext()" class="btn btn-sm btn-default HostEvent-button">Next Host</button>
<button ui-sref="jobDetail" class="btn btn-sm btn-default HostEvent-close HostEvent-button" ng-show="true" >Close</button>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1 @@
<div>timing</div>

View File

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

View File

@ -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();
}];

View File

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

View File

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

View File

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

View File

@ -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, '</span>');
return line;
},
getAnchorTags: function(event, line){
if(event.event.indexOf("runner_") === -1){
return line;
}
else{
var str = `<a ui-sref="jobDetail.host-event.details({eventId: ${event.parent}, taskId: ${event.id} })" aw-tool-tip="Event ID: ${event.id} <br> Status: ${event.event_display}. <br> Click for details" data-tip-watch="result.tip" data-placement="top">`,
str2 = '</a>';
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 `
<div class="JobResultsStdOut-aLineOfStdOut${this.getCollapseClasses(event, lineArr[1], lineArr[0])}">
<div class="JobResultsStdOut-lineNumberColumn">${this.getCollapseIcon(event, lineArr[1])}${lineArr[0]}</div>
<div class="JobResultsStdOut-stdoutColumn">${this.prettify(lineArr[1])}</div>
<div class="JobResultsStdOut-stdoutColumn">${this.getAnchorTags(event, this.prettify(lineArr[1]))}</div>
</div>`;
}).join("");
}