mirror of
https://github.com/ansible/awx.git
synced 2026-01-13 19:10:07 -03:30
Adding tooltips and routes for host events
will circle back with more changes for the host events modal
This commit is contained in:
parent
3c70171413
commit
13ee468b14
@ -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)
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
<textarea id="HostEvent-codemirror" class="HostEvent-codemirror">
|
||||
</textarea>
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
@ -0,0 +1 @@
|
||||
<div>timing</div>
|
||||
150
awx/ui/client/src/job-results/host-event/host-event.block.less
Normal file
150
awx/ui/client/src/job-results/host-event/host-event.block.less
Normal 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;
|
||||
}
|
||||
@ -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();
|
||||
}];
|
||||
65
awx/ui/client/src/job-results/host-event/host-event.route.js
Normal file
65
awx/ui/client/src/job-results/host-event/host-event.route.js
Normal 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 };
|
||||
21
awx/ui/client/src/job-results/host-event/main.js
Normal file
21
awx/ui/client/src/job-results/host-event/main.js
Normal 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);
|
||||
}]);
|
||||
@ -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);
|
||||
}])
|
||||
|
||||
@ -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("");
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user