Adds the host event modal to the standard out feature

Removes old host modal code
This commit is contained in:
Jared Tabor 2018-04-02 11:21:11 -07:00 committed by Jake McDermott
parent 18dc0e9066
commit fe58b74d1e
18 changed files with 352 additions and 240 deletions

View File

@ -1,3 +1,4 @@
@import 'host-event/_index';
.at-Stdout {
&-menuTop {
color: @at-gray-848992;

View File

@ -1,4 +1,5 @@
<!-- todo: styling, css etc. - disposition according to project lib conventions -->
<div ui-view></div>
<div class="JobResults-panelHeader">
<div class="JobResults-panelHeaderText" translate> DETAILS</div>
<!-- LEFT PANE HEADER ACTIONS -->

View File

@ -15,6 +15,7 @@
}
.HostEvent .CodeMirror{
overflow-x: hidden;
max-height: none!important;
}
.HostEvent-close:hover{

View File

@ -40,19 +40,19 @@
<div class="HostEvent-nav">
<!-- view navigation buttons -->
<button ui-sref="jobResult.host-event.json" type="button"
<button ui-sref="jobz.host-event.json" type="button"
class="btn btn-sm btn-default HostEvent-tab"
ng-class="{'HostEvent-tab--selected' : isActiveState('jobResult.host-event.json')}">
ng-class="{'HostEvent-tab--selected' : isActiveState('jobz.host-event.json')}">
JSON
</button>
<button ng-if="stdout" ui-sref="jobResult.host-event.stdout"
<button ng-if="stdout" ui-sref="jobz.host-event.stdout"
type="button" class="btn btn-sm btn-default HostEvent-tab"
ng-class="{'HostEvent-tab--selected' : isActiveState('jobResult.host-event.stdout')}">
ng-class="{'HostEvent-tab--selected' : isActiveState('jobz.host-event.stdout')}">
Standard Out
</button>
<button ng-if="stderr" ui-sref="jobResult.host-event.stderr"
<button ng-if="stderr" ui-sref="jobz.host-event.stderr"
type="button" class="btn btn-sm btn-default HostEvent-tab"
ng-class="{'HostEvent-tab--selected' : isActiveState('jobResult.host-event.stderr')}">
ng-class="{'HostEvent-tab--selected' : isActiveState('jobz.host-event.stderr')}">
Standard Error
</button>

View File

@ -0,0 +1,170 @@
function HostEventsController (
$scope,
$state,
HostEventService,
hostEvent
) {
$scope.processEventStatus = HostEventService.processEventStatus;
$scope.processResults = processResults;
$scope.isActiveState = isActiveState;
$scope.getActiveHostIndex = getActiveHostIndex;
$scope.closeHostEvent = closeHostEvent;
function init () {
hostEvent.event_name = hostEvent.event;
$scope.event = _.cloneDeep(hostEvent);
// grab standard out & standard error if present from the host
// event's 'res' object, for things like Ansible modules. Small
// wrinkle in this implementation is that the stdout/stderr tabs
// should be shown if the `res` object has stdout/stderr keys, even
// if they're a blank string. The presence of these keys is
// potentially significant to a user.
if (_.has(hostEvent.event_data, 'task_action')) {
$scope.module_name = hostEvent.event_data.task_action;
} else if (!_.has(hostEvent.event_data, 'task_action')) {
$scope.module_name = 'No result found';
}
if (_.has(hostEvent.event_data, 'res.result.stdout')) {
if (hostEvent.event_data.res.stdout === '') {
$scope.stdout = ' ';
} else {
$scope.stdout = hostEvent.event_data.res.stdout;
}
}
if (_.has(hostEvent.event_data, 'res.result.stderr')) {
if (hostEvent.event_data.res.stderr === '') {
$scope.stderr = ' ';
} else {
$scope.stderr = hostEvent.event_data.res.stderr;
}
}
if (_.has(hostEvent.event_data, 'res')) {
$scope.json = hostEvent.event_data.res;
}
if ($scope.module_name === 'debug' &&
_.has(hostEvent.event_data, 'res.result.stdout')) {
$scope.stdout = hostEvent.event_data.res.result.stdout;
}
if ($scope.module_name === 'yum' &&
_.has(hostEvent.event_data, 'res.results') &&
_.isArray(hostEvent.event_data.res.results)) {
const event = hostEvent.event_data.res.results;
$scope.stdout = event[0];// eslint-disable-line prefer-destructuring
}
// instantiate Codemirror
if ($state.current.name === 'jobz.host-event.json') {
try {
if (_.has(hostEvent.event_data, 'res')) {
initCodeMirror(
'HostEvent-codemirror',
JSON.stringify($scope.json, null, 4),
{ name: 'javascript', json: true }
);
resize();
} else {
$scope.no_json = true;
}
} catch (err) {
// element with id HostEvent-codemirror is not the view
// controlled by this instance of HostEventController
}
} else if ($state.current.name === 'jobz.host-event.stdout') {
try {
resize();
} catch (err) {
// element with id HostEvent-codemirror is not the view
// controlled by this instance of HostEventController
}
} else if ($state.current.name === 'jobz.host-event.stderr') {
try {
resize();
} catch (err) {
// element with id HostEvent-codemirror is not the view
// controlled by this instance of HostEventController
}
}
$('#HostEvent').modal('show');
$('.modal-content').resizable({
minHeight: 523,
minWidth: 600
});
$('.modal-dialog').draggable({
cancel: '.HostEvent-view--container'
});
function resize () {
if ($state.current.name === 'jobz.host-event.json') {
const editor = $('.CodeMirror')[0].CodeMirror;
const height = $('.modal-dialog').height() - $('.HostEvent-header').height() - $('.HostEvent-details').height() - $('.HostEvent-nav').height() - $('.HostEvent-controls').height() - 120;
editor.setSize('100%', height);
} else if ($state.current.name === 'jobz.host-event.stdout' || $state.current.name === 'jobz.host-event.stderr') {
const height = $('.modal-dialog').height() - $('.HostEvent-header').height() - $('.HostEvent-details').height() - $('.HostEvent-nav').height() - $('.HostEvent-controls').height() - 120;
$('.HostEvent-stdout').width('100%');
$('.HostEvent-stdout').height(height);
$('.HostEvent-stdoutContainer').height(height);
$('.HostEvent-numberColumnPreload').height(height);
}
}
$('.modal-dialog').on('resize', resize);
$('#HostEvent').on('hidden.bs.modal', $scope.closeHostEvent);
}
function processResults (value) {
if (typeof value === 'object') {
return false;
}
return true;
}
function initCodeMirror (el, data, mode) {
const container = document.getElementById(el);
const options = {};
options.lineNumbers = true;
options.mode = mode;
options.readOnly = true;
options.scrollbarStyle = null;
const editor = CodeMirror.fromTextArea(// eslint-disable-line no-undef
container,
options
);
editor.setSize('100%', 200);
editor.getDoc().setValue(data);
}
function isActiveState (name) {
return $state.current.name === name;
}
function getActiveHostIndex () {
function hostResultfilter (obj) {
return obj.id === $scope.event.id;
}
const result = $scope.hostResults.filter(hostResultfilter);
return $scope.hostResults.indexOf(result[0]);
}
function closeHostEvent () {
// Unbind the listener so it doesn't fire when we close the modal via navigation
$('#HostEvent').off('hidden.bs.modal');
$('#HostEvent').modal('hide');
$state.go('jobz');
}
$scope.init = init;
$scope.init();
}
HostEventsController.$inject = [
'$scope',
'$state',
'HostEventService',
'hostEvent',
];
module.exports = HostEventsController;

View File

@ -0,0 +1,72 @@
const HostEventModalTemplate = require('~features/output/host-event/host-event-modal.partial.html');
const HostEventCodeMirrorTemplate = require('~features/output/host-event/host-event-codemirror.partial.html');
const HostEventStdoutTemplate = require('~features/output/host-event/host-event-stdout.partial.html');
const HostEventStderrTemplate = require('~features/output/host-event/host-event-stderr.partial.html');
function exit () {
// 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');
}
function HostEventResolve (HostEventService, $stateParams) {
return HostEventService.getRelatedJobEvents($stateParams.id, {
id: $stateParams.eventId
}).then((response) => response.data.results[0]);
}
HostEventResolve.$inject = [
'HostEventService',
'$stateParams',
];
const hostEventModal = {
name: 'jobz.host-event',
url: '/host-event/:eventId',
controller: 'HostEventsController',
templateUrl: HostEventModalTemplate,
abstract: false,
ncyBreadcrumb: {
skip: true
},
resolve: {
hostEvent: HostEventResolve
},
onExit: exit
};
const hostEventJson = {
name: 'jobz.host-event.json',
url: '/json',
controller: 'HostEventsController',
templateUrl: HostEventCodeMirrorTemplate,
ncyBreadcrumb: {
skip: true
},
};
const hostEventStdout = {
name: 'jobz.host-event.stdout',
url: '/stdout',
controller: 'HostEventsController',
templateUrl: HostEventStdoutTemplate,
ncyBreadcrumb: {
skip: true
},
};
const hostEventStderr = {
name: 'jobz.host-event.stderr',
url: '/stderr',
controller: 'HostEventsController',
templateUrl: HostEventStderrTemplate,
ncyBreadcrumb: {
skip: true
},
};
export { hostEventJson, hostEventModal, hostEventStdout, hostEventStderr };

View File

@ -0,0 +1,69 @@
function HostEventService (
Rest,
ProcessErrors,
GetBasePath,
$rootScope
) {
// GET events related to a job run
// e.g.
// ?event=playbook_on_stats
// ?parent=206&event__startswith=runner&page_size=200&order=host_name,counter
this.getRelatedJobEvents = (id, params) => {
let url = GetBasePath('jobs');
url = `${url}${id}/job_events/?${this.stringifyParams(params)}`;
Rest.setUrl(url);
return Rest.get()
.then(response => response)
.catch(({ data, status }) => {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: `Call to ${url}. GET returned: ${status}` });
});
};
this.stringifyParams = params => {
function reduceFunction (result, value, key) {
return `${result}${key}=${value}&`;
}
return _.reduce(params, reduceFunction, '');
};
// 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 for more filters to support
this.processEventStatus = event => {
const obj = {};
if (event.event === 'runner_on_unreachable') {
obj.class = 'HostEvent-status--unreachable';
obj.status = 'unreachable';
}
// equiv to 'runner_on_error' && 'runner on failed'
if (event.failed) {
obj.class = 'HostEvent-status--failed';
obj.status = 'failed';
}
// catch the changed case before ok, because both can be true
if (event.changed) {
obj.class = 'HostEvent-status--changed';
obj.status = 'changed';
}
if (event.event === 'runner_on_ok' || event.event === 'runner_on_async_ok') {
obj.class = 'HostEvent-status--ok';
obj.status = 'ok';
}
if (event.event === 'runner_on_skipped') {
obj.class = 'HostEvent-status--skipped';
obj.status = 'skipped';
}
return obj;
};
}
HostEventService.$inject = [
'Rest',
'ProcessErrors',
'GetBasePath',
'$rootScope'
];
export default HostEventService;

View File

@ -0,0 +1,26 @@
import {
hostEventModal,
hostEventJson,
hostEventStdout,
hostEventStderr
} from './host-event.route';
import controller from './host-event.controller';
import service from './host-event.service';
const MODULE_NAME = 'hostEvents';
function hostEventRun ($stateExtender) {
$stateExtender.addState(hostEventModal);
$stateExtender.addState(hostEventJson);
$stateExtender.addState(hostEventStdout);
$stateExtender.addState(hostEventStderr);
}
hostEventRun.$inject = [
'$stateExtender'
];
angular.module(MODULE_NAME, [])
.controller('HostEventsController', controller)
.service('HostEventService', service)
.run(hostEventRun);
export default MODULE_NAME;

View File

@ -12,6 +12,7 @@ import StatusService from '~features/output/status.service';
import DetailsDirective from '~features/output/details.directive';
import SearchDirective from '~features/output/search.directive';
import StatsDirective from '~features/output/stats.directive';
import HostEvent from './host-event/index';
const Template = require('~features/output/index.view.html');
@ -211,7 +212,8 @@ JobsRun.$inject = ['$stateRegistry'];
angular
.module(MODULE_NAME, [
atLibModels,
atLibComponents
atLibComponents,
HostEvent
])
.service('JobStrings', Strings)
.service('JobPageService', PageService)

View File

@ -169,7 +169,7 @@ function JobRenderService ($q, $sce, $window) {
}
if (current.isHost) {
tdEvent = `<td class="at-Stdout-event--host" ng-click="vm.showHostDetails('${current.id}')">${content}</td>`;
tdEvent = `<td class="at-Stdout-event--host" ui-sref="jobz.host-event.json({eventId: ${current.id}, taskUuid: '${current.uuid}' })">${content}</td>`;
}
if (current.time && current.line === ln) {
@ -251,6 +251,7 @@ function JobRenderService ($q, $sce, $window) {
});
this.compile = html => {
html = $(this.el);
this.hooks.compile(html);
return this.requestAnimationFrame();

View File

@ -81,7 +81,6 @@
@import '../../src/inventories-hosts/inventories/inventories.block.less';
@import '../../src/inventories-hosts/shared/associate-groups/associate-groups.block.less';
@import '../../src/inventories-hosts/shared/associate-hosts/associate-hosts.block.less';
@import '../../src/job-results/host-event/host-event.block.less';
@import '../../src/job-results/host-status-bar/host-status-bar.block.less';
@import '../../src/job-results/job-results-stdout/job-results-stdout.block.less';
@import '../../src/job-results/job-results.block.less';

View File

@ -1,143 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
['$scope', '$state', 'jobResultsService', 'hostEvent',
function($scope, $state, jobResultsService, hostEvent){
$scope.processEventStatus = jobResultsService.processEventStatus;
$scope.processResults = function(value){
if (typeof value === 'object'){return false;}
else {return true;}
};
var initCodeMirror = function(el, data, mode){
var container = document.getElementById(el);
var editor = CodeMirror.fromTextArea(container, { // jshint ignore:line
lineNumbers: true,
mode: mode,
readOnly: true,
scrollbarStyle: null
});
editor.setSize("100%", 200);
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.closeHostEvent = function() {
// Unbind the listener so it doesn't fire when we close the modal via navigation
$('#HostEvent').off('hidden.bs.modal');
$('#HostEvent').modal('hide');
$state.go('jobResult');
};
var init = function(){
hostEvent.event_name = hostEvent.event;
$scope.event = _.cloneDeep(hostEvent);
// grab standard out & standard error if present from the host
// event's "res" object, for things like Ansible modules. Small
// wrinkle in this implementation is that the stdout/stderr tabs
// should be shown if the `res` object has stdout/stderr keys, even
// if they're a blank string. The presence of these keys is
// potentially significant to a user.
try{
$scope.module_name = hostEvent.event_data.task_action || "No result found";
$scope.stdout = hostEvent.event_data.res.stdout ? hostEvent.event_data.res.stdout : hostEvent.event_data.res.stdout === "" ? " " : undefined;
$scope.stderr = hostEvent.event_data.res.stderr ? hostEvent.event_data.res.stderr : hostEvent.event_data.res.stderr === "" ? " " : undefined;
$scope.json = hostEvent.event_data.res;
}
catch(err){
// do nothing, no stdout/stderr for this module
}
if($scope.module_name === "debug" &&
_.has(hostEvent.event_data, "res.result.stdout")){
$scope.stdout = hostEvent.event_data.res.result.stdout;
}
if($scope.module_name === "yum" &&
_.has(hostEvent.event_data, "res.results") &&
_.isArray(hostEvent.event_data.res.results)){
$scope.stdout = hostEvent.event_data.res.results[0];
}
// instantiate Codemirror
// try/catch pattern prevents the abstract-state controller from complaining about element being null
if ($state.current.name === 'jobResult.host-event.json'){
try{
if(_.has(hostEvent.event_data, "res")){
initCodeMirror('HostEvent-codemirror', JSON.stringify($scope.json, null, 4), {name: "javascript", json: true});
resize();
}
else{
$scope.no_json = true;
}
}
catch(err){
// element with id HostEvent-codemirror is not the view controlled by this instance of HostEventController
}
}
else if ($state.current.name === 'jobResult.host-event.stdout'){
try{
resize();
}
catch(err){
// element with id HostEvent-codemirror is not the view controlled by this instance of HostEventController
}
}
else if ($state.current.name === 'jobResult.host-event.stderr'){
try{
resize();
}
catch(err){
// element with id HostEvent-codemirror is not the view controlled by this instance of HostEventController
}
}
$('#HostEvent').modal('show');
$('.modal-content').resizable({
minHeight: 523,
minWidth: 600
});
$('.modal-dialog').draggable({
cancel: '.HostEvent-view--container'
});
function resize(){
if ($state.current.name === 'jobResult.host-event.json'){
let editor = $('.CodeMirror')[0].CodeMirror;
let height = $('.modal-dialog').height() - $('.HostEvent-header').height() - $('.HostEvent-details').height() - $('.HostEvent-nav').height() - $('.HostEvent-controls').height() - 120;
editor.setSize("100%", height);
}
else if($state.current.name === 'jobResult.host-event.stdout' || $state.current.name === 'jobResult.host-event.stderr'){
let height = $('.modal-dialog').height() - $('.HostEvent-header').height() - $('.HostEvent-details').height() - $('.HostEvent-nav').height() - $('.HostEvent-controls').height() - 120;
$(".HostEvent-stdout").width("100%");
$(".HostEvent-stdout").height(height);
$(".HostEvent-stdoutContainer").height(height);
$(".HostEvent-numberColumnPreload").height(height);
}
}
$('.modal-dialog').on('resize', function(){
resize();
});
$('#HostEvent').on('hidden.bs.modal', function () {
$scope.closeHostEvent();
});
};
init();
}];

View File

@ -1,66 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import { templateUrl } from '../../shared/template-url/template-url.factory';
var hostEventModal = {
name: 'jobResult.host-event',
url: '/host-event/:eventId',
controller: 'HostEventController',
templateUrl: templateUrl('job-results/host-event/host-event-modal'),
'abstract': false,
ncyBreadcrumb: {
skip: true
},
resolve: {
hostEvent: ['jobResultsService', '$stateParams', function(jobResultsService, $stateParams) {
return jobResultsService.getRelatedJobEvents($stateParams.id, {
id: $stateParams.eventId
}).then((response) => response.data.results[0]);
}]
},
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 hostEventJson = {
name: 'jobResult.host-event.json',
url: '/json',
controller: 'HostEventController',
templateUrl: templateUrl('job-results/host-event/host-event-codemirror'),
ncyBreadcrumb: {
skip: true
},
};
var hostEventStdout = {
name: 'jobResult.host-event.stdout',
url: '/stdout',
controller: 'HostEventController',
templateUrl: templateUrl('job-results/host-event/host-event-stdout'),
ncyBreadcrumb: {
skip: true
},
};
var hostEventStderr = {
name: 'jobResult.host-event.stderr',
url: '/stderr',
controller: 'HostEventController',
templateUrl: templateUrl('job-results/host-event/host-event-stderr'),
ncyBreadcrumb: {
skip: true
},
};
export { hostEventJson, hostEventModal, hostEventStdout, hostEventStderr };

View File

@ -1,20 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import {hostEventModal,
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(hostEventJson);
$stateExtender.addState(hostEventStdout);
$stateExtender.addState(hostEventStderr);
}]);

View File

@ -6,7 +6,6 @@
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';
@ -17,7 +16,7 @@ import eventQueueService from './event-queue.service';
import parseStdoutService from './parse-stdout.service';
export default
angular.module('jobResults', [hostStatusBar.name, jobResultsStdOut.name, hostEvent.name, 'angularMoment'])
angular.module('jobResults', [hostStatusBar.name, jobResultsStdOut.name, 'angularMoment'])
.run(['$stateExtender', function($stateExtender) {
$stateExtender.addState(route);
}])