Merge pull request #5594 from jaredevantabor/decouple-jobDetails-jobResults

RIP Old Job Details Page
This commit is contained in:
Jared Tabor 2017-03-01 13:14:21 -08:00 committed by GitHub
commit c839bb67d0
50 changed files with 177 additions and 4280 deletions

View File

@ -1671,7 +1671,7 @@ tr td button i {
.modal-body {
min-height: 120px;
padding: 20px 0;
.alert {
padding: 10px;
margin: 0;
@ -1984,10 +1984,6 @@ tr td button i {
width: 73px;
}
.JobDetails-status {
margin-bottom: 12px;
}
.red-text {
color: @red;
}

View File

@ -46,7 +46,6 @@ import inventories from './inventories/main';
import inventoryScripts from './inventory-scripts/main';
import organizations from './organizations/main';
import managementJobs from './management-jobs/main';
import jobDetail from './job-detail/main';
import workflowResults from './workflow-results/main';
import jobResults from './job-results/main';
import jobSubmission from './job-submission/main';
@ -119,7 +118,6 @@ var tower = angular.module('Tower', [
login.name,
activityStream.name,
footer.name,
jobDetail.name,
workflowResults.name,
jobResults.name,
jobSubmission.name,
@ -191,7 +189,6 @@ var tower = angular.module('Tower', [
'LogViewerStatusDefinition',
'StandardOutHelper',
'LogViewerOptionsDefinition',
'JobDetailHelper',
'lrInfiniteScroll',
'LoadConfigHelper',
'PortalJobsListDefinition',
@ -350,19 +347,19 @@ var tower = angular.module('Tower', [
$rootScope.$on("$stateChangeStart", function (event, next) {
// Remove any lingering intervals
// except on jobDetails.* states
var jobDetailStates = [
'jobDetail',
'jobDetail.host-summary',
'jobDetail.host-event.details',
'jobDetail.host-event.json',
'jobDetail.host-events',
'jobDetail.host-event.stdout'
// except on jobResults.* states
var jobResultStates = [
'jobResult',
'jobResult.host-summary',
'jobResult.host-event.details',
'jobResult.host-event.json',
'jobResult.host-events',
'jobResult.host-event.stdout'
];
if ($rootScope.jobDetailInterval && !_.includes(jobDetailStates, next.name) ) {
window.clearInterval($rootScope.jobDetailInterval);
if ($rootScope.jobResultInterval && !_.includes(jobResultStates, next.name) ) {
window.clearInterval($rootScope.jobResultInterval);
}
if ($rootScope.jobStdOutInterval && !_.includes(jobDetailStates, next.name) ) {
if ($rootScope.jobStdOutInterval && !_.includes(jobResultStates, next.name) ) {
window.clearInterval($rootScope.jobStdOutInterval);
}

View File

@ -12,7 +12,6 @@ import Credentials from "./helpers/Credentials";
import Events from "./helpers/Events";
import Groups from "./helpers/Groups";
import Hosts from "./helpers/Hosts";
import JobDetail from "./helpers/JobDetail";
import JobSubmission from "./helpers/JobSubmission";
import JobTemplates from "./helpers/JobTemplates";
import Jobs from "./helpers/Jobs";
@ -38,7 +37,6 @@ export
Events,
Groups,
Hosts,
JobDetail,
JobSubmission,
JobTemplates,
Jobs,

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,45 +0,0 @@
<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 | longDate) || "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

@ -1,36 +0,0 @@
<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

@ -1,150 +0,0 @@
// @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

@ -1,110 +0,0 @@
/*************************************************
* 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.event_data.res.stdout){
$scope.stdout = hostEvent.event_data.res.stdout;
delete $scope.event.event_data.res.stdout;
}
if (hostEvent.event_data.res.stderr){
$scope.stderr = hostEvent.event_data.res.stderr;
delete $scope.event.event_data.res.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

@ -1,65 +0,0 @@
/*************************************************
* 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-detail/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.taskUuid).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-detail/host-event/host-event-details'),
};
var hostEventJson = {
name: 'jobDetail.host-event.json',
url: '/json',
controller: 'HostEventController',
templateUrl: templateUrl('job-detail/host-event/host-event-codemirror')
};
var hostEventStdout = {
name: 'jobDetail.host-event.stdout',
url: '/stdout',
controller: 'HostEventController',
templateUrl: templateUrl('job-detail/host-event/host-event-codemirror')
};
var hostEventStderr = {
name: 'jobDetail.host-event.stderr',
url: '/stderr',
controller: 'HostEventController',
templateUrl: templateUrl('job-detail/host-event/host-event-codemirror')
};
export { hostEventDetails, hostEventJson, hostEventModal, hostEventStdout, hostEventStderr };

View File

@ -1,21 +0,0 @@
/*************************************************
* 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('jobDetail.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

@ -1,93 +0,0 @@
@import "./client/src/shared/branding/colors.less";
@import "./client/src/shared/branding/colors.default.less";
.HostEvents .CodeMirror{
border: none;
}
.HostEvents .modal-footer{
border: 0;
margin-top: 0px;
padding: 0px 20px 20px 20px;
}
button.HostEvents-close{
width: 70px;
color: #FFFFFF!important;
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;
}
}
.HostEvents-status--ok{
color: @green;
}
.HostEvents-status--unreachable{
color: @unreachable;
}
.HostEvents-status--changed{
color: @changed;
}
.HostEvents-status--failed{
color: @default-err;
}
.HostEvents-status--skipped{
color: @skipped;
}
.HostEvents-filter--form{
padding-top: 15px;
padding-bottom: 15px;
float: right;
display: inline-block;
}
.HostEvents .modal-body{
padding: 20px;
}
.HostEvents .select2-container{
text-transform: capitalize;
max-width: 220px;
float: right;
}
.HostEvents-form--container{
padding-top: 15px;
padding-bottom: 15px;
}
.HostEvents-title{
text-transform: uppercase;
color: @default-interface-txt;
font-weight: 600;
}
.HostEvents-status i {
padding-right: 10px;
}
.HostEvents-table--header {
height: 30px;
font-size: 14px;
font-weight: normal;
text-transform: uppercase;
color: @default-interface-txt;
background-color: @default-list-header-bg;
padding-left: 15px;
padding-right: 15px;
border-bottom-width: 0px;
}
.HostEvents-table--header:first-of-type{
border-top-left-radius: 5px;
}
.HostEvents-table--header:last-of-type{
border-top-right-radius: 5px;
}
.HostEvents-table--row{
color: @default-data-txt;
border: 0 !important;
}
.HostEvents-table--row:nth-child(odd){
background: @default-tertiary-bg;
}
.HostEvents-table--cell{
border: 0 !important;
}

View File

@ -1,48 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
['$stateParams', '$scope', '$rootScope', '$state', 'Wait',
'JobDetailService', 'CreateSelect2', 'hosts',
function($stateParams, $scope, $rootScope, $state, Wait,
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.filters = ['all', 'changed', 'failed', 'ok', 'unreachable', 'skipped'];
// watch select2 for changes
$('.HostEvents-select').on("select2:select", function () {
$scope.activeFilter = $('.HostEvents-select').val();
});
var init = function(){
$scope.hostName = $stateParams.hostName;
// create filter dropdown
CreateSelect2({
element: '.HostEvents-select',
multiple: false
});
// process the filter if one was passed
if ($stateParams.filter){
$scope.activeFilter = $stateParams.filter;
$('#HostEvents').modal('show');
}
else{
$scope.results = hosts.data.results;
$('#HostEvents').modal('show');
}
};
init();
}];

View File

@ -1,52 +0,0 @@
<div id="HostEvents" class="HostEvents modal fade" role="dialog">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-body">
<div class="HostEvents-header">
<span class="HostEvents-title">HOST EVENTS | {{hostName}}</span>
<!-- Close -->
<button ui-sref="jobDetail.host-summary" type="button" class="close">
<i class="fa fa fa-times-circle"></i>
</button>
</div>
<div class="HostEvents-form--container">
<select class="HostEvents-select">
<option ng-selected="filter == activeFilter" class="HostEvents-select--option" value="{{filter}}" ng-repeat="filter in filters">{{filter}}</option>
</select>
</div>
<!-- event results table -->
<div class="table-responsive">
<table class="table">
<!-- column labels -->
<th ng-hide="results.length == 0" class="HostEvents-table--header">STATUS</th>
<th ng-hide="results.length == 0" class="HostEvents-table--header">PLAY</th>
<th ng-hide="results.length == 0" class="HostEvents-table--header">TASK</th>
<!-- result rows -->
<tr class="HostEvents-table--row" ng-repeat="event in results track by $index" modal-paginate="event in results | page_size: page_size">
<td class=HostEvents-table--cell>
<!-- status circles -->
<a class="HostEvents-status">
<i class="fa fa-circle" ng-class="processEventStatus(event).class"></i>
</a>
{{processEventStatus(event).status}}
</td>
<td class=HostEvents-table--cell>{{event.play}}</td>
<td class=HostEvents-table--cell>{{event.task}}</td>
</tr>
<tr ng-show="results.length == 0" class="HostEvents-table--row">
<td class=HostEvents-table--cell>
No results were found.
</td>
</tr>
</table>
</div>
</div>
<div class="modal-footer">
<!-- pagination -->
<!-- close -->
<button ui-sref="jobDetail.host-summary" class="btn btn-default pull-right HostEvents-close">OK</button>
</div>
</div>
</div>
</div>

View File

@ -1,32 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import {templateUrl} from '../../shared/template-url/template-url.factory';
export default {
name: 'jobDetail.host-events',
url: '/host-events/{hostName:any}?:filter',
controller: 'HostEventsController',
params: {
page_size: 10
},
templateUrl: templateUrl('job-detail/host-events/host-events'),
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"
$('#HostEvents').modal('hide');
// hacky way to handle user browsing away via URL bar
$('.modal-backdrop').remove();
$('body').removeClass('modal-open');
},
resolve: {
hosts: ['JobDetailService','$stateParams', function(JobDetailService, $stateParams) {
return JobDetailService.getRelatedJobEvents($stateParams.id, {
host_name: $stateParams.hostName
}).success(function(res){ return res.results[0];});
}]
}
};

View File

@ -1,15 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import route from './host-events.route';
import controller from './host-events.controller';
export default
angular.module('jobDetail.hostEvents', [])
.controller('HostEventsController', controller)
.run(['$stateExtender', function($stateExtender){
$stateExtender.addState(route);
}]);

View File

@ -1,17 +0,0 @@
@import '../../shared/branding/colors.default.less';
.HostSummary-graph--successful{
text-anchor: start !important;
}
.HostSummary-graph--failed{
text-anchor: end !important;
}
.HostSummary-graph--changed{
text-anchor: start !important;
}
.HostSummary-loading{
border: none;
}
.HostSummary-loading{
padding-left: 0px !important;
color: @default-interface-txt;
}

View File

@ -1,131 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
['$scope', '$rootScope', '$stateParams', 'Wait', 'JobDetailService', 'DrawGraph', function($scope, $rootScope, $stateParams, Wait, JobDetailService, DrawGraph){
var page_size = 200;
$scope.loading = $scope.hosts.length > 0 ? false : true;
$scope.filter = 'all';
var buildGraph = function(hosts){
// status waterfall: unreachable > failed > changed > ok > skipped
var count;
count = {
ok : _.filter(hosts, function(o){
return o.failures === 0 && o.changed === 0 && o.ok > 0;
}),
skipped : _.filter(hosts, function(o){
return o.skipped > 0;
}),
unreachable : _.filter(hosts, function(o){
return o.dark > 0;
}),
failures : _.filter(hosts, function(o){
return o.failed === true;
}),
changed : _.filter(hosts, function(o){
return o.changed > 0;
})
};
return count;
};
var init = function(){
Wait('start');
JobDetailService.getJobHostSummaries($stateParams.id, {page_size: page_size, order_by: 'host_name'})
.success(function(res){
$scope.hosts = res.results;
$scope.next = res.next;
$scope.count = buildGraph(res.results);
Wait('stop');
DrawGraph({count: $scope.count, resize:true});
});
JobDetailService.getJob({id: $stateParams.id})
.success(function(res){
$scope.status = res.results[0].status;
});
};
if ($rootScope.removeJobSummaryComplete) {
$rootScope.removeJobSummaryComplete();
}
// emitted by the API in the same function used to persist host summary data
// JobEvent.update_host_summary_from_stats() from /awx/main.models.jobs.py
$scope.$on('ws-jobs-summary', function(e, data) {
// discard socket msgs we don't care about in this context
if (parseInt($stateParams.id) === data.unified_job_id){
init();
}
});
$scope.$on('ws-jobs', function(e, data) {
if (parseInt($stateParams.id) === data.unified_job_id){
$scope.status = data.status;
}
});
$scope.buildTooltip = function(n, status){
var grammar = function(n, status){
var dict = {
0: 'No host events were ',
1: ' host event was ',
2: ' host events were '
};
if (n >= 2){
return n + dict[2] + status;
}
else{
return n !== 0 ? n + dict[n] + status : dict[n] + status;
}
};
return grammar(n, status);
};
$scope.getNextPage = function(){
if ($scope.next){
JobDetailService.getNextPage($scope.next).success(function(res){
res.results.forEach(function(key, index){
$scope.hosts.push(res.results[index]);
});
$scope.hosts.push(res.results);
$scope.next = res.next;
});
}
};
$scope.setFilter = function(filter){
$scope.filter = filter;
var getAll = function(){
Wait('start');
JobDetailService.getJobHostSummaries($stateParams.id, {
page_size: page_size,
order_by: 'host_name'
}).success(function(res){
Wait('stop');
$scope.hosts = res.results;
$scope.next = res.next;
});
};
var getFailed = function(){
Wait('start');
JobDetailService.getJobHostSummaries($stateParams.id, {
page_size: page_size,
failed: true,
order_by: 'host_name'
}).success(function(res){
Wait('stop');
$scope.hosts = res.results;
$scope.next = res.next;
});
};
$scope.get = filter === 'all' ? getAll() : getFailed();
};
init();
// calling the init routine twice will size the d3 chart correctly - no idea why
// instantiating the graph inside a setTimeout() SHOULD have the same effect, but it doesn't
// instantiating the graph further down the promise chain e.g. .then() or .finally() also does not work
init();
}];

View File

@ -1,72 +0,0 @@
<div id="hosts-summary-section" class="section">
<div class="JobDetail-instructions" ng-hide="hosts.length == 0 && !searchTerm && !searchActive"><span class="badge">4</span> Please select a host below to view a summary of all associated tasks.</div>
<div class="JobDetail-searchHeaderRow" ng-hide="hosts.length == 0 && !searchTerm && !searchActive">
<div class="JobDetail-searchContainer form-group">
<div class="search-name">
<form ng-submit="search()">
<input type="text" class="JobDetail-searchInput form-control List-searchInput" id="search_host_summary_name" ng-model="searchTerm" placeholder="Host Name" />
<a class="List-searchInputIcon search-icon" ng-click="search()" ng-show="!searchActive"><i class="fa fa-search"></i></a>
<a class="List-searchInputIcon search-icon" ng-show="searchActive" ng-click="clearSearch()"><i class="fa fa-times"></i></a>
</form>
</div>
</div>
<div class="JobDetail-tableToggleContainer form-group">
<div class="btn-group" >
<button
ng-click="setFilter('all')"
class="JobDetail-tableToggle btn btn-xs" ng-class="{'btn-default': filter === 'failed', 'btn-primary': filter === 'all'}">All</button>
<button ng-click="setFilter('failed')"
ng-class="{'btn-default': filter === 'all', 'btn-primary': filter === 'failed'}" ng-disabled='count.failures == 0' class="JobDetail-tableToggle btn btn-xs">Failed</button>
</div>
</div>
</div>
<div class="table-header" ng-hide="hosts.length == 0 && !searchTerm && !searchActive">
<table class="table table-condensed">
<thead>
<tr>
<th class="List-tableHeader col-lg-6 col-md-6 col-sm-6 col-xs-6">Hosts</th>
<th class="List-tableHeader JobDetail-tableHeader col-lg-6 col-md-5 col-sm-5 col-xs-5">Completed Tasks</th>
</tr>
</thead>
</table>
</div>
<div id="hosts-summary-table" class="table-detail" lr-infinite-scroll="getNextPage" scroll-threshold="10" time-threshold="500">
<table class="table" ng-class="{'JobDetails-table--noResults': hosts.length === 0}">
<tbody>
<tr class="List-tableRow" ng-repeat="host in hosts track by $index" id="{{ host.id }}" ng-class-even="'List-tableRow--evenRow'" ng-class-odd="'List-tableRow--oddRow'">
<td class="List-tableCell name col-lg-6 col-md-6 col-sm-6 col-xs-6">
<a ui-sref="jobDetail.host-events({hostName: host.host_name})" aw-tool-tip="View events" data-placement="top">{{ host.host_name }}</a>
</td>
<td class="List-tableCell col-lg-6 col-md-5 col-sm-5 col-xs-5 badge-column">
<a ui-sref="jobDetail.host-events({hostName: host.host_name, filter: 'ok'})" aw-tool-tip="{{ buildTooltip(host.ok - host.changed, 'ok') }}" data-placement="top" ng-hide="host.ok == 0"><span class="badge successful-hosts">{{ host.ok - host.changed }}</span></a>
<a ui-sref="jobDetail.host-events({hostName: host.host_name, filter: 'changed'})" aw-tool-tip="{{buildTooltip(host.changed, 'changed')}}" data-placement="top" ng-hide="host.changed == 0"><span class="badge changed-hosts">{{ host.changed }}</span></a>
<a ui-sref="jobDetail.host-events({hostName: host.host_name, filter: 'skipped'})" aw-tool-tip="{{buildTooltip(host.skipped, 'skipped')}}" data-placement="top" ng-hide="host.skipped == 0"><span class="badge skipped-hosts">{{ host.skipped }}</span></a>
<a ui-sref="jobDetail.host-events({hostName: host.host_name, filter: 'unreachable'})" aw-tool-tip="{{buildTooltip(host.dark, 'unreachable')}}" data-placement="top" ng-hide="host.dark == 0"><span class="badge unreachable-hosts">{{ host.dark }}</span></a>
<a ui-sref="jobDetail.host-events({hostName: host.host_name, filter: 'failed'})" aw-tool-tip="{{ buildTooltip(host.failures, 'failed')}}" data-placement="top" ng-hide="host.failed == 0"><span class="badge failed-hosts">{{ host.failures }}</span></a>
</td>
</tr>
<tr ng-show="status == 'pending'">
<td colspan="5" class="col-lg-12 HostSummary-loading">Initiating job run.</td>
</tr>
<tr ng-show="status == 'running'">
<td colspan="5" class="col-lg-12 HostSummary-loading">Job is running. Summary will be available on completion.</td>
</tr>
<tr ng-show="(status === 'failed' || status === 'successful') && hosts.length === 0 ">
<td colspan="2" class="col-lg-12 HostSummary-loading">No matching hosts</td>
</tr>
</tbody>
</table>
</div>
<div class="scroll-spinner" id="hostSummariesMoreRows">
<i class="fa fa-cog fa-spin"></i>
</div>
</div><!-- section -->
<div class="JobDetail-panelHeaderText">Host Status Summary</div>
<div id="graph-section" class="JobDetail-graphSection">
<svg></svg>
</div>

View File

@ -1,21 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import {templateUrl} from '../../shared/template-url/template-url.factory';
export default {
name: 'jobDetail.host-summary',
url: '/event-summary',
views:{
'host-summary': {
controller: 'HostSummaryController',
templateUrl: templateUrl('job-detail/host-summary/host-summary'),
}
},
ncyBreadcrumb: {
skip: true // Never display this state in breadcrumb.
}
};

View File

@ -1,15 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import route from './host-summary.route';
import controller from './host-summary.controller';
export default
angular.module('jobDetail.hostSummary', [])
.controller('HostSummaryController', controller)
.run(['$stateExtender', function($stateExtender){
$stateExtender.addState(route);
}]);

View File

@ -1,240 +0,0 @@
/** @define SetupItem */
@import '../shared/branding/colors.less';
@import '../shared/branding/colors.default.less';
@import '../shared/layouts/one-plus-two.less';
@breakpoint-md: 1200px;
@breakpoint-sm: 623px;
.JobDetail-tasks.section{
margin-top:40px;
}
.JobDetail-instructions{
color: @default-interface-txt;
margin: 10px 0 10px 0;
.badge {
background-color: @default-list-header-bg;
color: @default-interface-txt;
padding: 5px 7px;
}
}
.JobDetail{
.OnePlusTwo-container(100%, @breakpoint-md);
&.fullscreen {
.JobDetail-rightSide {
max-width: 100%;
}
}
}
.JobDetail-leftSide{
.OnePlusTwo-left--panel(100%, @breakpoint-md);
}
.JobDetail-rightSide{
.OnePlusTwo-right--panel(100%, @breakpoint-md);
@media (max-width: @breakpoint-md - 1px) {
padding-right: 15px;
}
}
.JobDetail-panelHeader{
display: flex;
height: 30px;
}
.JobDetail-expandContainer{
flex: 1;
margin: 0px;
line-height: 30px;
white-space: nowrap;
}
.JobDetail-panelHeaderText{
color: @default-interface-txt;
flex: 1 0 auto;
font-size: 14px;
font-weight: bold;
margin-right: 10px;
text-transform: uppercase;
}
.JobDetail-panelHeaderText:hover{
color: @default-interface-txt;
font-size: 14px;
font-weight: bold;
margin-right: 10px;
text-transform: uppercase;
}
.JobDetail-expandArrow{
color: @default-icon-hov;
font-size: 14px;
font-weight: bold;
margin-right: 10px;
text-transform: uppercase;
margin-left: 10px;
}
.JobDetail-resultsDetails{
display: flex;
flex-wrap: wrap;
flex-direction: row;
padding-top: 25px;
@media screen and(max-width: @breakpoint-sm){
flex-direction: column;
}
}
.JobDetail-resultRow{
width: 100%;
display: flex;
padding-bottom: 10px;
flex-wrap: wrap;
}
.JobDetail-resultRow--variables {
flex-direction: column;
}
.JobDetail-resultRowLabel{
text-transform: uppercase;
color: @default-interface-txt;
font-size: 14px;
font-weight: normal!important;
width: 30%;
margin-right: 20px;
@media screen and(max-width: @breakpoint-md){
flex: 2.5 0 auto;
}
}
.JobDetail-resultRowLabel--fullWidth {
width: 100%;
margin-right: 0px;
}
.JobDetail-resultRowText{
width: ~"calc(70% - 20px)";
flex: 1 0 auto;
text-transform: none;
word-wrap: break-word;
}
.JobDetail-resultRowText--fullWidth {
width: 100%;
}
.JobDetail-searchHeaderRow{
display: flex;
flex-wrap: wrap;
flex-direction: row;
height: 50px;
margin-top: 20px;
@media screen and(max-width: @breakpoint-sm){
height: auto;
}
}
.JobDetail-searchContainer{
flex: 2 0 auto;
@media screen and(max-width: @breakpoint-sm){
margin-bottom: 0px;
}
}
.JobDetail-tableToggleContainer{
flex: 1 0 auto;
display: flex;
justify-content: flex-end;
}
.JobDetail-tableToggle{
padding-left:10px;
padding-right: 10px;
border: 1px solid @d7grey;
}
.JobDetail-tableToggle.active{
background-color: @default-link;
border: 1px solid @default-link;
color: @default-bg;
&:hover {
background-color: @default-link-hov;
}
}
.JobDetail .nvd3.nv-noData{
color: @default-interface-txt;
font-size: 12px;
text-transform: uppercase;
font-family: 'Open Sans', sans-serif;
}
.JobDetail .nv-series{
padding-right: 30px;
display: block;
}
.JobDetail-instructions .badge{
background-color: @default-list-header-bg;
color: @default-interface-txt;
}
.JobDetail-tableToggle--left{
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
.JobDetail-tableToggle--right{
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
.JobDetail-searchInput{
border-radius: 5px !important;
}
.JobDetail-tableHeader:last-of-type{
text-align:justify;
}
.JobDetail-statusIcon{
padding-right: 10px;
padding-left: 10px;
}
.JobDetail-tableRow--selected,
.JobDetail-tableRow--selected > :first-child{
border-left: 5px solid @list-row-select-bord;
}
.JobDetail-tableRow--selected > :first-child > .JobDetail-statusIcon{
margin-left: -5px;
}
.JobDetails-table--noResults {
tr > td {
border-top: none !important;
}
}
.JobDetail-statusIcon--results{
padding-left: 0px;
padding-right: 10px;
}
.JobDetail-graphSection{
height: 320px;
width:100%;
}
.JobDetail-stdoutActionButton--active{
display: none;
visibility: hidden;
flex:none;
width:0px;
padding-right: 0px;
}
.JobDetail-leftSide.JobDetail-stdoutActionButton--active {
margin-right: 0px;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,435 +0,0 @@
<div class="tab-pane" id="jobs-detail">
<div ng-cloak id="htmlTemplate" class="JobDetail" ng-class="{'fullscreen': stdoutFullScreen}">
<div ui-view></div>
<!--beginning of job-detail-container (left side) -->
<div id="job-detail-container" class="JobDetail-leftSide" ng-class="{'JobDetail-stdoutActionButton--active': stdoutFullScreen}">
<!--beginning of results-->
<div id="job-results-panel" class="JobDetail-resultsContainer Panel" ng-show="!stdoutFullScreen">
<div class="JobDetail-panelHeader">
<div class="JobDetail-expandContainer">
<a class="JobDetail-panelHeaderText" ng-show="lessStatus" href="" ng-click="toggleLessStatus()">
<translate>RESULTS</translate><i class="JobDetail-expandArrow fa fa-caret-right"></i>
</a>
<a class="JobDetail-panelHeaderText" ng-show="!lessStatus" href="" ng-click="toggleLessStatus()">
<translate>RESULTS</translate><i class="JobDetail-expandArrow fa fa-caret-down"></i>
</a>
</div>
<div class="JobDetail-actions">
<button id="relaunch-job-button" class="List-actionButton JobDetail-launchButton" data-placement="top" mode="all" ng-click="relaunchJob()" aw-tool-tip="Relaunch using the same parameters" data-original-title="" title=""><i class="icon-launch"></i> </button>
<button id="cancel-job-button" class="List-actionButton List-actionButton--delete JobDetail-launchButton" data-placement="top" ng-click="deleteJob()" ng-show="job_status.status == 'running' || job_status.status=='pending' " aw-tool-tip="Cancel" data-original-title="" title=""><i class="fa fa-minus-circle"></i> </button>
<button id="delete-job-button" class="List-actionButton List-actionButton--delete JobDetail-launchButton" data-placement="top" ng-click="deleteJob()" ng-hide="job_status.status == 'running' || job_status.status == 'pending' " aw-tool-tip="Delete" data-original-title="" title=""><i class="fa fa-trash-o"></i> </button>
</div>
</div>
<div class="form-horizontal JobDetail-resultsDetails" role="form" id="job-status-form">
<div class="form-group JobDetail-resultRow toggle-show">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Status</label>
<div class="JobDetail-resultRowText"><i class="JobDetail-statusIcon--results fa icon-job-{{ job_status.status }}"></i> {{ job_status.status_label }}</div>
</div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job_status.explanation">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 col-xs-12" translate>Explanation</label>
<div class="JobDetail-resultRowText col-lg-10 col-md-10 col-sm-10 col-xs-9 job_status_explanation"
ng-show="!previousTaskFailed" ng-bind-html="job_status.explanation"></div>
<div class="JobDetail-resultRowText col-lg-10 col-md-10 col-sm-10 col-xs-9 job_status_explanation"
ng-show="previousTaskFailed">Previous Task Failed
<a
href=""
id="explanation_help"
aw-pop-over="{{ task_detail }}"
aw-pop-over-watch="task_detail"
data-placement="bottom"
data-container="body" class="help-link" over-title="Failure Detail"
title=""
tabindex="-1">
<i class="fa fa-question-circle">
</i>
</a>
</div>
</div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job_status.traceback">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-12 col-sm-12 col-xs-12" translate>Results Traceback</label>
<div class="JobDetail-resultRowText col-lg-10 col-md-12 col-sm-12 col-xs-12 job_status_traceback" ng-bind-html="job_status.traceback"></div>
</div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job_template_name">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Template</label>
<div class="JobDetail-resultRowText">
<a href="{{ job_template_url }}" aw-tool-tip="Edit the job template" data-placement="top">{{ job_template_name }}</a>
</div>
</div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job_status.started">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Started</label>
<div class="JobDetail-resultRowText">{{ job_status.started | longDate }}</div>
</div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job_type">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Job Type</label>
<div class="JobDetail-resultRowText">{{ job_type }}</div>
</div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job_status.started">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Finished</label>
<div class="JobDetail-resultRowText">{{ job_status.finished | longDate }}</div>
</div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="created_by">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Launched By</label>
<div class="JobDetail-resultRowText">
<a href="{{ users_url }}" aw-tool-tip="Edit the User" data-placement="top">{{ created_by }}</a>
</div>
</div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job_status.started">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Elapsed</label>
<div class="JobDetail-resultRowText">{{ job_status.elapsed }}</div>
</div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="scheduled_by">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Launched By</label>
<div class="JobDetail-resultRowText">
<a href aw-tool-tip="Edit the Schedule" data-placement="top" ng-click="editSchedule()">{{scheduled_by}}</a>
</div>
</div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="inventory_name">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Inventory</label>
<div class="JobDetail-resultRowText">
<a href="{{ inventory_url }}" aw-tool-tip="Edit the inventory" data-placement="top">{{ inventory_name }}</a>
</div>
</div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="project_name">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Project</label>
<div class="JobDetail-resultRowText">
<a href="{{ project_url }}" aw-tool-tip="Edit the project" data-placement="top">{{ project_name }}</a>
</div>
</div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job.playbook">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Playbook</label>
<div class="JobDetail-resultRowText">{{ job.playbook }}</div>
</div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="credential_name">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Machine Credential</label>
<div class="JobDetail-resultRowText JobDetail-resultRowText">
<a href="{{ credential_url }}" aw-tool-tip="Edit the credential" data-placement="top">{{ credential_name }}</a>
</div>
</div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="cloud_credential_name">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Cloud Credential</label>
<div class="JobDetail-resultRowText">
<a href="{{ cloud_credential_url }}" aw-tool-tip="Edit the credential" data-placement="top">{{ cloud_credential_name }}</a>
</div>
</div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="network_credential_name">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Network Credential</label>
<div class="JobDetail-resultRowText">
<a href="{{ network_credential_url }}" aw-tool-tip="Edit the credential" data-placement="top">{{ network_credential_name }}</a>
</div>
</div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job.forks">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Forks</label>
<div class="JobDetail-resultRowText">{{ job.forks }}</div>
</div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job.limit">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Limit</label>
<div class="JobDetail-resultRowText">{{ job.limit }}</div>
</div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="verbosity">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Verbosity</label>
<div class="JobDetail-resultRowText">{{ verbosity }}</div>
</div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job.job_tags">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Job Tags</label>
<div class="JobDetail-resultRowText">{{ job.job_tags }}</div>
</div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job.skip_tags">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Skip Tags</label>
<div class="JobDetail-resultRowText">{{ job.skip_tags }}</div>
</div>
<div class="form-group JobDetail-resultRow JobDetail-resultRow--variables toggle-show" ng-show="variables">
<label class="JobDetail-resultRowLabel JobDetail-extraVarsLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label" translate>Extra Variables</label>
<textarea rows="6" ng-model="variables" name="variables" class="JobDetail-extraVars" id="pre-formatted-variables"></textarea>
</div>
</div>
</div>
<!--- end of results-->
<!--beginning of details-->
<div id="job-detail-panel" class="JobDetail-resultsContainer Panel" ng-show="!stdoutFullScreen">
<div class="JobDetail-panelHeader">
<div class="JobDetail-expandContainer">
<a class="JobDetail-panelHeaderText" ng-show="lessDetail" href="" ng-click="toggleLessDetail()">
<translate>DETAILS</translate><i class="JobDetail-expandArrow fa fa-caret-right"></i>
</a>
<a class="JobDetail-panelHeaderText" ng-show="!lessDetail" href="" ng-click="toggleLessDetail()">
<translate>DETAILS</translate><i class="JobDetail-expandArrow fa fa-caret-down"></i>
</a>
</div>
</div>
<div id="job-detail-details">
<div id="play-section">
<div class="JobDetail-instructions"><span class="badge">1</span> Please select from a play below to view its associated tasks.</div>
<div class="JobDetail-searchHeaderRow">
<div class="JobDetail-searchContainer form-group">
<div class="search-name">
<input type="text" class="JobDetail-searchInput form-control List-searchInput" id="search_play_name" ng-model="search_play_name" placeholder="Play Name" ng-keypress="searchPlaysKeyPress($event)" >
<a class="List-searchInputIcon search-icon" ng-show="searchPlaysEnabled" ng-click="searchPlays()"><i class="fa fa-search"></i></a>
<a class="List-searchInputIcon search-icon" ng-show="!searchPlaysEnabled" ng-click="search_play_name=''; searchPlays()"><i class="fa fa-times"></i></a>
</div>
</div>
<div class="JobDetail-tableToggleContainer form-group">
<div class="btn-group" aw-toggle-button data-after-toggle="filterPlayStatus">
<button class="JobDetail-tableToggle btn btn-xs btn-primary active" translate>All</button>
<button class="JobDetail-tableToggle btn btn-xs btn-default" translate>Failed</button>
</div>
</div>
</div>
<div id="plays-table-header" class="table-header" ng-show="plays.length !== 0">
<table class="table table-condensed">
<thead>
<tr>
<th class="List-tableHeader col-lg-7 col-md-6 col-sm-6 col-xs-4" translate>Plays</th>
<th class="List-tableHeader col-lg-2 col-md-2 col-sm-2 col-xs-3" translate>Started</th>
<th class="List-tableHeader JobDetail-tableHeader col-lg-2 col-md-2 col-sm-2 col-xs-3" translate>Elapsed</th>
</tr>
</thead>
</table>
</div>
<div id="plays-table-detail" class="table-detail" lr-infinite-scroll="playsScrollDown"
scroll-threshold="10" time-threshold="500">
<table class="table" ng-class="{'JobDetails-table--noResults': plays.length === 0}">
<tbody>
<tr class="List-tableRow cursor-pointer" ng-repeat="play in plays" ng-class-odd="'List-tableRow--oddRow'" ng-class-even="'List-tableRow--evenRow'" ng-class="play.playActiveClass" ng-click="selectPlay(play.id, $event)">
<td class="List-tableCell col-lg-7 col-md-6 col-sm-6 col-xs-4 status-column" aw-tool-tip="{{ play.status_tip }}" data-tip-watch="play.status_tip" data-placement="top"><i class="JobDetail-statusIcon fa icon-job-{{ play.status }}"></i>{{ play.name }}</td>
<td class="List-tableCell col-lg-2 col-md-2 col-sm-2 col-xs-3">{{ play.created | date: 'HH:mm:ss' }}</td>
<td class="List-tableCell col-lg-2 col-md-2 col-sm-2 col-xs-3" aw-tool-tip="{{ play.finishedTip }}" data-tip-watch="play.finishedTip"
data-placement="top">{{ play.elapsed }}</td>
</tr>
<tr ng-show="plays.length === 0 && waiting">
<td colspan="4" class="col-lg-12 loading-info">Waiting...</td>
</tr>
<tr ng-show="plays.length === 0 && playsLoading && !waiting">
<td colspan="4" class="col-lg-12 loading-info">Loading...</td>
</tr>
<tr ng-show="plays.length === 0 && !playsLoading && !waiting">
<td colspan="4" class="col-lg-12 loading-info">No matching plays</td>
</tr>
</tbody>
</table>
</div>
<div class="scroll-spinner" id="playsMoreRows">
<i class="fa fa-cog fa-spin"></i>
</div>
</div>
<!-- end of plays section of details-->
<div id="task-section" class="section JobDetail-tasks" >
<div class="JobDetail-instructions"><span class="badge">2</span> <translate>Please select a task below to view its associated hosts</translate></div>
<div class="JobDetail-searchHeaderRow">
<div class="JobDetail-searchContainer form-group">
<div class="search-name">
<input type="text" class="JobDetail-searchInput form-control List-searchInput" id="search_task_name" ng-model="search_task_name" placeholder="Task Name" ng-keypress="searchTasksKeyPress($event)" >
<a class="List-searchInputIcon search-icon" ng-show="searchTasksEnabled" ng-click="searchTasks()"><i class="fa fa-search"></i></a>
<a class="List-searchInputIcon search-icon" ng-show="!searchTasksEnabled" ng-click="search_task_name=''; searchTasks()"><i class="fa fa-times"></i></a>
</div>
</div>
<div class="JobDetail-tableToggleContainer form-group">
<div class="btn-group" aw-toggle-button data-after-toggle="filterTaskStatus">
<button class="JobDetail-tableToggle btn btn-xs btn-primary active" translate>All</button>
<button class="JobDetail-tableToggle btn btn-xs btn-default" translate>Failed</button>
</div>
</div>
</div>
<div class="table-header">
<table id="tasks-table-header" class="table table-condensed" ng-show="taskList.length !== 0">
<thead>
<tr>
<th class="List-tableHeader col-lg-3 col-md-3 col-sm-6 col-xs-4" translate>Tasks</th>
<th class="List-tableHeader col-lg-2 col-md-2 col-sm-2 col-xs-3" translate>Started</th>
<th class="List-tableHeader col-lg-2 col-md-2 col-sm-2 col-xs-3" translate>Elapsed</th>
<th class="List-tableHeader JobDetail-tableHeader col-lg-4 col-md-3 hidden-xs hidden-sm" translate>Host Status</th>
</tr>
</thead>
</table>
</div>
<div id="tasks-table-detail" class="table-detail" lr-infinite-scroll="tasksScrollDown"
scroll-threshold="10" time-threshold="500">
<table class="table" ng-class="{'JobDetails-table--noResults': taskList.length === 0}">
<tbody>
<tr class="List-tableRow cursor-pointer" ng-class-odd="'List-tableRow--oddRow'" ng-class-even="'List-tableRow--evenRow'" ng-repeat="task in taskList = (tasks) track by $index" ng-class="task.taskActiveClass" ng-click="selectTask(task.id)">
<td class="List-tableCell col-lg-3 col-md-3 col-sm-6 col-xs-4 status-column" aw-tool-tip="{{ task.status_tip }}"
data-tip-watch="task.status_tip" data-placement="top"><i class="JobDetail-statusIcon fa icon-job-{{ task.status }}"></i>{{ task.name }}</td>
<td class="List-tableCell col-lg-2 col-md-2 col-sm-2 col-xs-3">{{ task.created | date: 'HH:mm:ss' }}</td>
<td class="List-tableCell col-lg-2 col-md-2 col-sm-2 col-xs-3" aw-tool-tip="{{ task.finishedTip }}" data-tip-watch="task.finishedTip"
data-placement="top">{{ task.elapsed }}</td>
<td class="List-tableCell col-lg-4 col-md-3 hidden-sm hidden-xs">
<div>
<a href="" id="{{ task.id }}-successful-bar" aw-tool-tip="{{ task.successfulCountTip }}" data-tip-watch="task.successfulCountTip" data-placement="top" ng-style="task.successfulStyle">
<span class="badge successful-hosts">{{ task.successfulCount }}</span>
</a>
<a href="" id="{{ task.id }}-changed-bar" aw-tool-tip="{{ task.changedCountTip }}" data-tip-watch="task.changedCountTip" data-placement="top" ng-style="task.changedStyle">
<span class="badge changed-hosts">{{ task.changedCount }}</span>
</a>
<a href="" id="{{ task.id }}-skipped-bar" aw-tool-tip="{{ task.skippedCountTip }}" data-tip-watch="task.skippedCountTip" data-placement="top" ng-style="task.skippedStyle">
<span class="badge skipped-hosts">{{ task.skippedCount }}</span>
</a>
<a href="" id="{{ task.id }}-failed-bar" aw-tool-tip="{{ task.failedCountTip }}" data-tip-watch="task.failedCountTip" data-placement="top" ng-style="task.failedStyle">
<span class="badge failed-hosts">{{ task.failedCount }}</span>
</a>
<a href="" id="{{ task.id }}-unreachable-bar" aw-tool-tip="{{ task.unreachableCountTip }}" data-tip-watch="task.unreachableCountTip" data-placement="top" ng-style="task.unreachableStyle">
<span class="badge unreachable-hosts">{{ task.unreachableCount }}</span>
</a>
<a href="" id="{{ task.id }}-missing-bar" aw-tool-tip="{{ task.missingCountTip }}" data-tip-watch="task.missingCountTip" data-placement="top" ng-style="task.missingStyle">
<span class="badge missing-hosts">{{ task.missingCount }}</span>
</a>
<div class="no-matching-hosts inner-bar" id="{{ task.id }}-{{ task.play_id }}-no-matching-hosts-bar" aw-tool-tip="No matching hosts were found." data-placement="top" style="width: 100%;" ng-show="task.status === 'no-matching-hosts'">
<translate>No matching hosts.</translate>
</div>
</div>
</td>
</tr>
<tr ng-show="taskList.length === 0 && waiting">
<td colspan="5" class="col-lg-12 loading-info" translate>Waiting...</td>
</tr>
<tr ng-show="taskList.length === 0 && tasksLoading && !waiting">
<td colspan="5" class="col-lg-12 loading-info" translate>Loading...</td>
</tr>
<tr ng-show="taskList.length === 0 && !tasksLoading && !waiting">
<td colspan="5" class="col-lg-12 loading-info" translate>No matching tasks</td>
</tr>
</tbody>
</table>
</div>
<div class="scroll-spinner" id="tasksMoreRows"><i class="fa fa-cog fa-spin"></i></div>
</div><!-- section -->
<!--end of tasks section of details-->
<div id="task-hosts-section" class="section">
<div class="JobDetail-instructions"><span class="badge">3</span> Please select a host below to view associated task details.</div>
<div class="JobDetail-searchHeaderRow">
<div class="JobDetail-searchContainer form-group">
<div class="search-name">
<input type="text" class="JobDetail-searchInput form-control List-searchInput" id="search_host_name" ng-model="search_host_name" placeholder="Host Name" ng-keypress="searchHostsKeyPress($event)" >
<a class="List-searchInputIcon search-icon" ng-show="searchHostsEnabled" ng-click="searchHosts()"><i class="fa fa-search"></i></a>
<a class="List-searchInputIcon search-icon" ng-show="!searchHostsEnabled" ng-click="search_host_name=''; searchHosts()"><i class="fa fa-times"></i></a>
</div>
</div>
<div class="JobDetail-tableToggleContainer form-group">
<div class="btn-group" aw-toggle-button data-after-toggle="filterHostStatus">
<button class="JobDetail-tableToggle btn btn-xs btn-primary active" translate>All</button>
<button class="JobDetail-tableToggle btn btn-xs btn-default" translate>Failed</button>
</div>
</div>
</div>
<div class="table-header" id="hosts-table-header">
<table class="table table-condensed" ng-show="results.length !== 0">
<thead>
<tr>
<th class="List-tableHeader col-lg-4 col-md-3 col-sm-3 col-xs-3" translate>Hosts</th>
<th class="List-tableHeader col-lg-3 col-md-4 col-sm-3 col-xs-3" translate>Item</th>
<th class="List-tableHeader JobDetail-tableHeader col-lg-3 col-md-4 col-sm-3 col-xs-3" translate>Message</th>
</tr>
</thead>
</table>
</div>
<div id="hosts-table-detail" class="table-detail" lr-infinite-scroll="hostResultsScrollDown" scroll-threshold="10" time-threshold="500">
<table class="table" ng-class="{'JobDetails-table--noResults': results.length === 0}">
<tbody>
<tr class="List-tableRow cursor-pointer" ng-class-odd="'List-tableRow--oddRow'" ng-class-even="'List-tableRow--evenRow'" ng-repeat="result in results = (hostResults) track by $index">
<td class="List-tableCell col-lg-4 col-md-3 col-sm-3 col-xs-3 status-column">
<a ui-sref="jobDetail.host-event.details({eventId: result.id, taskId: selectedTask})" aw-tool-tip="Event ID: {{ result.id }}<br \>Status: {{ result.status_text }}. Click for details" data-tip-watch="result.tip" data-placement="top"><i ng-show="result.status_text != 'Unreachable'" class="JobDetail-statusIcon fa icon-job-{{ result.status }}"></i><span ng-show="result.status_text != 'Unreachable'">{{ result.name }}</span><i ng-show="result.status_text == 'Unreachable'" class="JobDetail-statusIcon fa icon-job-unreachable"></i><span ng-show="result.status_text == 'Unreachable'">{{ result.name }}</span></a>
</td>
<td class="List-tableCell col-lg-3 col-md-4 col-sm-3 col-xs-3 item-column">{{ result.item }}</td>
<td class="List-tableCell col-lg-3 col-md-4 col-sm-3 col-xs-3">{{ result.msg }}</td>
</tr>
<tr ng-show="results.length === 0 && waiting">
<td colspan="5" class="col-lg-12 loading-info" translate>Waiting...</td>
</tr>
<tr ng-show="results.length === 0 && hostResultsLoading && !waiting">
<td colspan="5" class="col-lg-12 loading-info" translate>Loading...</td>
</tr>
<tr ng-show="results.length === 0 && !hostResultsLoading && !waiting">
<td colspan="5" class="col-lg-12 loading-info" translate>No matching host events</td>
</tr>
</tbody>
</table>
</div>
<div class="scroll-spinner" id="hostResultsMoreRows"><i class="fa fa-cog fa-spin"></i></div>
</div>
<!--end of hosts section of details-->
</div>
</div>
<!--end of details-->
<!--beginning of events summary-->
<div id="events-summary-panel" class="JobDetail-resultsContainer Panel" ng-show="!stdoutFullScreen">
<div class="JobDetail-panelHeader">
<div class="JobDetail-expandContainer">
<a class="JobDetail-panelHeaderText" ng-show="lessEvents" ui-sref="jobDetail.host-summary" ng-click="toggleLessEvents()">
<translate>EVENT SUMMARY</translate><i class="JobDetail-expandArrow fa fa-caret-right"></i>
</a>
<a class="JobDetail-panelHeaderText" ng-show="!lessEvents" ui-sref="jobDetail" ng-click="toggleLessEvents()">
<translate>EVENT SUMMARY</translate><i class="JobDetail-expandArrow fa fa-caret-down"></i>
</a>
</div>
</div>
<!-- Host Summary view -->
<div id="events-summary" ng-hide="lessEvents">
<div ui-view="host-summary"></div>
</div>
</div>
<!-- end of events summary-->
</div>
<!--end of job-detail-container (left side)-->
<!--beginning of stdout-->
<div class="JobDetail-rightSide">
<div class="JobDetail-stdoutPanel Panel">
<div class="StandardOut-panelHeader">
<div class="StandardOut-panelHeaderText" translate>STANDARD OUT</div>
<div class="StandardOut-panelHeaderActions">
<button class="StandardOut-actionButton" aw-tool-tip="{{'Toggle Output'|translate}}" data-placement="top" ng-class="{'StandardOut-actionButton--active': stdoutFullScreen}" ng-click="toggleStdoutFullscreen()">
<i class="fa fa-arrows-alt"></i>
</button>
<a ng-show="job_status.status === 'failed' || job_status.status === 'successful' || job_status.status === 'canceled'" href="/api/v1/jobs/{{ job.id }}/stdout?format=txt_download&token={{ token }}">
<button class="StandardOut-actionButton" aw-tool-tip="{{'Download Output'|translate}}" data-placement="top">
<i class="fa fa-download"></i>
</button>
</a>
</div>
</div>
<standard-out-log stdout-endpoint="job.related.stdout"></standard-out-log>
</div>
</div>
<!--end of stdout-->
</div>
</div>
<div id="host-modal-dialog" style="display: none;" class="dialog-content"></div>
</div>

View File

@ -1,91 +0,0 @@
// <<<<<<< 4cf6a946a1aa14b7d64a8e1e8dabecfd3d056f27
// //<<<<<<< bc59236851902d7c768aa26abdb7dc9c9dc27a5a
// /*************************************************
// * Copyright (c) 2016 Ansible, Inc.
// *
// * All Rights Reserved
// *************************************************/
//
// // <<<<<<< a3d9eea2c9ddb4e16deec9ec38dea16bf37c559d
// // import { templateUrl } from '../shared/template-url/template-url.factory';
// //
// // export default {
// // name: 'jobDetail',
// // url: '/jobs/{id: int}',
// // ncyBreadcrumb: {
// // parent: 'jobs',
// // label: "{{ job.id }} - {{ job.name }}"
// // },
// // data: {
// // socket: {
// // "groups": {
// // "jobs": ["status_changed", "summary"],
// // "job_events": []
// // }
// // }
// // },
// // templateUrl: templateUrl('job-detail/job-detail'),
// // controller: 'JobDetailController'
// // };
// // =======
// // import {templateUrl} from '../shared/template-url/template-url.factory';
// //
// // export default {
// // name: 'jobDetail',
// // url: '/jobs/:id',
// // ncyBreadcrumb: {
// // parent: 'jobs',
// // label: "{{ job.id }} - {{ job.name }}"
// // },
// // socket: {
// // "groups":{
// // "jobs": ["status_changed", "summary"],
// // "job_events": []
// // }
// // },
// // templateUrl: templateUrl('job-detail/job-detail'),
// // controller: 'JobDetailController'
// // };
// //=======
// =======
// >>>>>>> Rebase of devel (w/ channels) + socket rework for new job details
// // /*************************************************
// // * Copyright (c) 2016 Ansible, Inc.
// // *
// // * All Rights Reserved
// // *************************************************/
// //
// // import {templateUrl} from '../shared/template-url/template-url.factory';
// //
// // export default {
// // name: 'jobDetail',
// // url: '/jobs/:id',
// // ncyBreadcrumb: {
// // parent: 'jobs',
// // label: "{{ job.id }} - {{ job.name }}"
// // },
// // socket: {
// // "groups":{
// // "jobs": ["status_changed", "summary"],
// // "job_events": []
// // }
// // },
// // resolve: {
// // jobEventsSocket: ['Socket', '$rootScope', function(Socket, $rootScope) {
// // if (!$rootScope.event_socket) {
// // $rootScope.event_socket = Socket({
// // scope: $rootScope,
// // endpoint: "job_events"
// // });
// // $rootScope.event_socket.init();
// // // returns should really be providing $rootScope.event_socket
// // // otherwise, we have to inject the entire $rootScope into the controller
// // return true;
// // } else {
// // return true;
// // }
// // }]
// // },
// // templateUrl: templateUrl('job-detail/job-detail'),
// // controller: 'JobDetailController'
// // };

View File

@ -1,215 +0,0 @@
export default
['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', function($rootScope, Rest, GetBasePath, ProcessErrors){
return {
stringifyParams: function(params){
return _.reduce(params, (result, value, key) => {
return result + key + '=' + value + '&';
}, '');
},
// the the API passes through Ansible's event_data response
// we need to massage away the verbose & redundant stdout/stderr properties
processJson: function(data){
// configure fields to ignore
var ignored = [
'type',
'event_data',
'related',
'summary_fields',
'url',
'ansible_facts',
];
// remove ignored properties
var result = _.chain(data).cloneDeep().forEach(function(value, key, collection){
if (ignored.indexOf(key) > -1){
delete collection[key];
}
}).value();
return result;
},
// Return Ansible's passed-through response msg on a job_event
processEventMsg: function(event){
return typeof event.event_data.res === 'object' ? event.event_data.res.msg : event.event_data.res;
},
// Return only Ansible's passed-through response item on a job_event
processEventItem: function(event){
try{
var item = event.event_data.res.item;
return typeof item === 'object' ? JSON.stringify(item) : item;
}
catch(err){return;}
},
processsEventTip: function(event, status){
try{
var string = `Event ID: ${ event.id }<br>Status: ${ _.capitalize(status.status)}. Click for details`;
return typeof item === 'object' ? JSON.stringify(string) : string;
}
catch(err){return;}
},
// 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
processEventStatus: function(event){
if (event.event === 'runner_on_unreachable'){
return {
class: 'HostEvents-status--unreachable',
status: 'unreachable'
};
}
// equiv to 'runner_on_error' && 'runner on failed'
if (event.failed){
return {
class: 'HostEvents-status--failed',
status: 'failed'
};
}
// catch the changed case before ok, because both can be true
if (event.changed){
return {
class: 'HostEvents-status--changed',
status: 'changed'
};
}
if (event.event === 'runner_on_ok' || event.event === 'runner_on_async_ok'){
return {
class: 'HostEvents-status--ok',
status: 'ok'
};
}
if (event.event === 'runner_on_skipped'){
return {
class: 'HostEvents-status--skipped',
status: 'skipped'
};
}
},
// Consumes a response from this.getRelatedJobEvents(id, params)
// returns an array for view logic to iterate over to build host result rows
processHostEvents: function(data){
var self = this;
var results = [];
data.forEach(function(event){
if (event.event !== 'runner_on_no_hosts'){
var status = self.processEventStatus(event);
var msg = self.processEventMsg(event);
var item = self.processEventItem(event);
var tip = self.processsEventTip(event, status);
results.push({
id: event.id,
status: status.status,
status_text: _.capitalize(status.status),
host_id: event.host,
task_id: event.parent,
name: event.event_data.host,
created: event.created,
tip: typeof tip === 'undefined' ? undefined : tip,
msg: typeof msg === 'undefined' ? undefined : msg,
item: typeof item === 'undefined' ? undefined : item
});
}
});
return results;
},
// 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
getRelatedJobEvents: function(id, params){
var url = GetBasePath('jobs');
url = url + id + '/job_events/?' + this.stringifyParams(params);
Rest.setUrl(url);
return Rest.get()
.success(function(data){
return data;
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
},
getJobEventChildren: function(uuid){
var url = GetBasePath('job_events');
url = `${url}?parent__uuid=${uuid}&order_by=host_name`;
Rest.setUrl(url);
return Rest.get()
.success(function(data){
return data;
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
},
// GET job host summaries related to a job run
// e.g. ?page_size=200&order=host_name
getJobHostSummaries: function(id, params){
var url = GetBasePath('jobs');
url = url + id + '/job_host_summaries/?' + this.stringifyParams(params);
Rest.setUrl(url);
return Rest.get()
.success(function(data){
return data;
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
},
// GET job plays related to a job run
// e.g. ?page_size=200
getJobPlays: function(id, params){
var url = GetBasePath('jobs');
url = url + id + '/job_plays/?' + this.stringifyParams(params);
Rest.setUrl(url);
return Rest.get()
.success(function(data){
return data;
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
},
getJobTasks: function(id, params){
var url = GetBasePath('jobs');
url = url + id + '/job_tasks/?' + this.stringifyParams(params);
Rest.setUrl(url);
return Rest.get()
.success(function(data){
return data;
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
},
getJob: function(params){
var url = GetBasePath('unified_jobs') + '?' + this.stringifyParams(params);
Rest.setUrl(url);
return Rest.get()
.success(function(data){
return data;
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
},
// GET next set of paginated results
// expects 'next' param returned by the API e.g.
// "/api/v1/jobs/51/job_plays/?order_by=id&page=2&page_size=1"
getNextPage: function(url){
Rest.setUrl(url);
return Rest.get()
.success(function(data){
return data;
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
}
};
}];

View File

@ -1,24 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
// 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';
import hostSummary from './host-summary/main';
export default
angular.module('jobDetail', [
hostEvents.name,
// hostEvent.name,
hostSummary.name
])
.controller('JobDetailController', controller)
.service('JobDetailService', service);
// .run(['$stateExtender', function($stateExtender) {
// $stateExtender.addState(route);
// }]);

View File

@ -9,7 +9,7 @@
</a>
<span class="HostEvent-title">{{event.host_name}}</span>
<!-- close -->
<button ui-sref="jobDetail" type="button" class="close">
<button ui-sref="jobResult" type="button" class="close">
<i class="fa fa-times-circle"></i>
</button>
</div>
@ -40,19 +40,19 @@
<div class="HostEvent-nav">
<!-- view navigation buttons -->
<button ui-sref="jobDetail.host-event.json" type="button"
<button ui-sref="jobResult.host-event.json" type="button"
class="btn btn-sm btn-default HostEvent-tab"
ng-class="{'HostEvent-tab--selected' : isActiveState('jobDetail.host-event.json')}">
ng-class="{'HostEvent-tab--selected' : isActiveState('jobResult.host-event.json')}">
JSON
</button>
<button ng-if="stdout" ui-sref="jobDetail.host-event.stdout"
<button ng-if="stdout" ui-sref="jobResult.host-event.stdout"
type="button" class="btn btn-sm btn-default HostEvent-tab"
ng-class="{'HostEvent-tab--selected' : isActiveState('jobDetail.host-event.stdout')}">
ng-class="{'HostEvent-tab--selected' : isActiveState('jobResult.host-event.stdout')}">
Standard Out
</button>
<button ng-if="stderr" ui-sref="jobDetail.host-event.stderr"
<button ng-if="stderr" ui-sref="jobResult.host-event.stderr"
type="button" class="btn btn-sm btn-default HostEvent-tab"
ng-class="{'HostEvent-tab--selected' : isActiveState('jobDetail.host-event.stderr')}">
ng-class="{'HostEvent-tab--selected' : isActiveState('jobResult.host-event.stderr')}">
Standard Error
</button>
@ -64,7 +64,7 @@
<!-- controls -->
<div class="HostEvent-controls">
<button ui-sref="jobDetail" class="btn btn-sm btn-default HostEvent-close">Close</button>
<button ui-sref="jobResult" class="btn btn-sm btn-default HostEvent-close">Close</button>
</div>
</div>
</div>

View File

@ -6,23 +6,15 @@
export default
['$stateParams', '$scope', '$state', 'Wait', 'JobDetailService', 'hostEvent', 'hostResults',
function($stateParams, $scope, $state, Wait, JobDetailService, hostEvent, hostResults){
['$stateParams', '$scope', '$state', 'Wait', 'jobResultsService', 'hostEvent',
function($stateParams, $scope, $state, Wait, jobResultsService, hostEvent){
$scope.processEventStatus = JobDetailService.processEventStatus;
$scope.hostResults = [];
// Avoid rendering objects in the details fieldset
// ng-if="processResults(value)" via host-event-details.partial.html
$scope.processEventStatus = jobResultsService.processEventStatus;
$scope.processResults = function(value){
if (typeof value === 'object'){return false;}
else {return true;}
};
$scope.isStdOut = function(){
if ($state.current.name === 'jobDetail.host-event.stdout' || $state.current.name === 'jobDetail.host-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
@ -37,17 +29,9 @@
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]);
};
var init = function(){
hostEvent.event_name = hostEvent.event;
$scope.event = _.cloneDeep(hostEvent);
$scope.hostResults = hostResults;
// grab standard out & standard error if present from the host
// event's "res" object, for things like Ansible modules
@ -72,7 +56,7 @@
}
// instantiate Codemirror
// try/catch pattern prevents the abstract-state controller from complaining about element being null
if ($state.current.name === 'jobDetail.host-event.json'){
if ($state.current.name === 'jobResult.host-event.json'){
try{
initCodeMirror('HostEvent-codemirror', JSON.stringify($scope.json, null, 4), {name: "javascript", json: true});
}
@ -80,7 +64,7 @@
// element with id HostEvent-codemirror is not the view controlled by this instance of HostEventController
}
}
else if ($state.current.name === 'jobDetail.host-event.stdout'){
else if ($state.current.name === 'jobResult.host-event.stdout'){
try{
initCodeMirror('HostEvent-codemirror', $scope.stdout, 'shell');
}
@ -88,7 +72,7 @@
// element with id HostEvent-codemirror is not the view controlled by this instance of HostEventController
}
}
else if ($state.current.name === 'jobDetail.host-event.stderr'){
else if ($state.current.name === 'jobResult.host-event.stderr'){
try{
initCodeMirror('HostEvent-codemirror', $scope.stderr, 'shell');
}

View File

@ -7,20 +7,17 @@
import { templateUrl } from '../../shared/template-url/template-url.factory';
var hostEventModal = {
name: 'jobDetail.host-event',
name: 'jobResult.host-event',
url: '/host-event/:eventId',
controller: 'HostEventController',
templateUrl: templateUrl('job-results/host-event/host-event-modal'),
'abstract': false,
resolve: {
hostEvent: ['JobDetailService', '$stateParams', function(JobDetailService, $stateParams) {
return JobDetailService.getRelatedJobEvents($stateParams.id, {
hostEvent: ['jobResultsService', '$stateParams', function(jobResultsService, $stateParams) {
return jobResultsService.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() {
@ -34,21 +31,21 @@ var hostEventModal = {
};
var hostEventJson = {
name: 'jobDetail.host-event.json',
name: 'jobResult.host-event.json',
url: '/json',
controller: 'HostEventController',
templateUrl: templateUrl('job-results/host-event/host-event-codemirror')
};
var hostEventStdout = {
name: 'jobDetail.host-event.stdout',
name: 'jobResult.host-event.stdout',
url: '/stdout',
controller: 'HostEventController',
templateUrl: templateUrl('job-results/host-event/host-event-stdout')
};
var hostEventStderr = {
name: 'jobDetail.host-event.stderr',
name: 'jobResult.host-event.stderr',
url: '/stderr',
controller: 'HostEventController',
templateUrl: templateUrl('job-results/host-event/host-event-stderr')

View File

@ -108,7 +108,7 @@
ng-show="!previousTaskFailed">
{{job.job_explanation}}
</div>
<div class="JobDetail-resultRowText "
<div class="jobResult-resultRowText "
ng-show="previousTaskFailed">Previous Task Failed
<a
href=""

View File

@ -13,7 +13,7 @@ const defaultParams = {
};
export default {
name: 'jobDetail',
name: 'jobResult',
url: '/jobs/{id: int}',
searchPrefix: 'job_event',
ncyBreadcrumb: {

View File

@ -5,8 +5,8 @@
*************************************************/
export default ['$q', 'Prompt', '$filter', 'Wait', 'Rest', '$state', 'ProcessErrors', 'InitiatePlaybookRun', 'GetBasePath', 'Alert',
function ($q, Prompt, $filter, Wait, Rest, $state, ProcessErrors, InitiatePlaybookRun, GetBasePath, Alert) {
export default ['$q', 'Prompt', '$filter', 'Wait', 'Rest', '$state', 'ProcessErrors', 'InitiatePlaybookRun', 'GetBasePath', 'Alert', '$rootScope',
function ($q, Prompt, $filter, Wait, Rest, $state, ProcessErrors, InitiatePlaybookRun, GetBasePath, Alert, $rootScope) {
var val = {
// the playbook_on_stats event returns the count data in a weird format.
// format to what we need!
@ -190,6 +190,87 @@ function ($q, Prompt, $filter, Wait, Rest, $state, ProcessErrors, InitiatePlaybo
});
return val.promise;
},
// 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
processEventStatus: function(event){
if (event.event === 'runner_on_unreachable'){
return {
class: 'HostEvent-status--unreachable',
status: 'unreachable'
};
}
// equiv to 'runner_on_error' && 'runner on failed'
if (event.failed){
return {
class: 'HostEvent-status--failed',
status: 'failed'
};
}
// catch the changed case before ok, because both can be true
if (event.changed){
return {
class: 'HostEvent-status--changed',
status: 'changed'
};
}
if (event.event === 'runner_on_ok' || event.event === 'runner_on_async_ok'){
return {
class: 'HostEvent-status--ok',
status: 'ok'
};
}
if (event.event === 'runner_on_skipped'){
return {
class: 'HostEvent-status--skipped',
status: 'skipped'
};
}
},
// 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
getRelatedJobEvents: function(id, params){
var url = GetBasePath('jobs');
url = url + id + '/job_events/?' + this.stringifyParams(params);
Rest.setUrl(url);
return Rest.get()
.success(function(data){
return data;
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
},
stringifyParams: function(params){
return _.reduce(params, (result, value, key) => {
return result + key + '=' + value + '&';
}, '');
},
// the the API passes through Ansible's event_data response
// we need to massage away the verbose & redundant stdout/stderr properties
processJson: function(data){
// configure fields to ignore
var ignored = [
'type',
'event_data',
'related',
'summary_fields',
'url',
'ansible_facts',
];
// remove ignored properties
var result = _.chain(data).cloneDeep().forEach(function(value, key, collection){
if (ignored.indexOf(key) > -1){
delete collection[key];
}
}).value();
return result;
}
};
return val;

View File

@ -77,7 +77,7 @@ export default ['$log', 'moment', function($log, moment){
return `"`;
}
else{
return ` JobResultsStdOut-stdoutColumn--clickable" ui-sref="jobDetail.host-event.json({eventId: ${event.id}, taskUuid: '${event.event_data.task_uuid}' })" aw-tool-tip="Event ID: ${event.id} <br>Status: ${event.event_display} <br>Click for details" data-placement="top"`;
return ` JobResultsStdOut-stdoutColumn--clickable" ui-sref="jobResult.host-event.json({eventId: ${event.id}, taskUuid: '${event.event_data.task_uuid}' })" aw-tool-tip="Event ID: ${event.id} <br>Status: ${event.event_display} <br>Click for details" data-placement="top"`;
}
},

View File

@ -124,12 +124,12 @@ export default
if($rootScope.portalMode===false && Empty(data.system_job) || (base === 'home')){
// use $state.go with reload: true option to re-instantiate sockets in
var goToJobDetails = function(state) {
var goTojobResults = function(state) {
$state.go(state, {id: job}, {reload:true});
};
if(_.has(data, 'job')) {
goToJobDetails('jobDetail');
goTojobResults('jobResult');
} else if(base === 'jobs'){
if(scope.clearDialog) {
scope.clearDialog();
@ -137,20 +137,20 @@ export default
return;
} else if(data.type && data.type === 'workflow_job') {
job = data.id;
goToJobDetails('workflowResults');
goTojobResults('workflowResults');
}
else if(_.has(data, 'ad_hoc_command')) {
goToJobDetails('adHocJobStdout');
goTojobResults('adHocJobStdout');
}
else if(_.has(data, 'system_job')) {
goToJobDetails('managementJobStdout');
goTojobResults('managementJobStdout');
}
else if(_.has(data, 'project_update')) {
// If we are on the projects list or any child state of that list
// then we want to stay on that page. Otherwise go to the stdout
// view.
if(!$state.includes('projects')) {
goToJobDetails('scmUpdateStdout');
goTojobResults('scmUpdateStdout');
}
}
else if(_.has(data, 'inventory_update')) {
@ -158,7 +158,7 @@ export default
// page then we want to stay on that page. Otherwise go to the stdout
// view.
if(!$state.includes('inventoryManage')) {
goToJobDetails('inventorySyncStdout');
goTojobResults('inventorySyncStdout');
}
}
}

View File

@ -136,7 +136,7 @@ export default
// As of 3.0, the only place the user can relaunch a
// playbook is on jobTemplates.edit (completed_jobs tab),
// jobs, and jobDetails $states.
// jobs, and jobResults $states.
if (!$scope.submitJobRelaunch) {
if($scope.submitJobType && $scope.submitJobType === 'job_template') {
@ -236,18 +236,18 @@ export default
// Go out and get some of the job details like inv, cred, name
Rest.setUrl(GetBasePath('jobs') + $scope.submitJobId);
Rest.get()
.success(function (jobDetailData) {
.success(function (jobResultData) {
$scope.job_template_data = {
name: jobDetailData.name
name: jobResultData.name
};
$scope.defaults = {};
if(jobDetailData.summary_fields.inventory) {
$scope.defaults.inventory = angular.copy(jobDetailData.summary_fields.inventory);
$scope.selected_inventory = angular.copy(jobDetailData.summary_fields.inventory);
if(jobResultData.summary_fields.inventory) {
$scope.defaults.inventory = angular.copy(jobResultData.summary_fields.inventory);
$scope.selected_inventory = angular.copy(jobResultData.summary_fields.inventory);
}
if(jobDetailData.summary_fields.credential) {
$scope.defaults.credential = angular.copy(jobDetailData.summary_fields.credential);
$scope.selected_credential = angular.copy(jobDetailData.summary_fields.credential);
if(jobResultData.summary_fields.credential) {
$scope.defaults.credential = angular.copy(jobResultData.summary_fields.credential);
$scope.selected_credential = angular.copy(jobResultData.summary_fields.credential);
updateRequiredPasswords();
}
initiateModal();

View File

@ -112,29 +112,29 @@
RelaunchJob({ scope: $scope, id: typeId, type: job.type, name: job.name });
};
$scope.viewJobDetails = function(job) {
$scope.viewjobResults = function(job) {
var goToJobDetails = function(state) {
var goTojobResults = function(state) {
$state.go(state, { id: job.id }, { reload: true });
};
switch (job.type) {
case 'job':
goToJobDetails('jobDetail');
goTojobResults('jobResult');
break;
case 'ad_hoc_command':
goToJobDetails('adHocJobStdout');
goTojobResults('adHocJobStdout');
break;
case 'system_job':
goToJobDetails('managementJobStdout');
goTojobResults('managementJobStdout');
break;
case 'project_update':
goToJobDetails('scmUpdateStdout');
goTojobResults('scmUpdateStdout');
break;
case 'inventory_update':
goToJobDetails('inventorySyncStdout');
goTojobResults('inventorySyncStdout');
break;
case 'workflow_job':
goToJobDetails('workflowResults');
goTojobResults('workflowResults');
break;
}

View File

@ -30,12 +30,12 @@ export default
dataTitle: "{{ job.status_popover_title }}",
icon: 'icon-job-{{ job.status }}',
iconOnly: true,
ngClick:"viewJobDetails(job)",
ngClick:"viewjobResults(job)",
nosort: true
},
id: {
label: 'ID',
ngClick:"viewJobDetails(job)",
ngClick:"viewjobResults(job)",
columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2 List-staticColumnAdjacent',
awToolTip: "{{ job.status_tip }}",
dataPlacement: 'top',
@ -44,7 +44,7 @@ export default
name: {
label: i18n._('Name'),
columnClass: 'col-lg-2 col-md-3 col-sm-4 col-xs-6',
ngClick: "viewJobDetails(job)",
ngClick: "viewjobResults(job)",
badgePlacement: 'right',
badgeCustom: true,
badgeIcon: `<a href="{{ job.workflow_result_link }}"
@ -90,7 +90,7 @@ export default
columnClass: 'col-lg-2 col-md-2 col-sm-3 col-xs-4',
"view": {
mode: "all",
ngClick: "viewJobDetails(job)",
ngClick: "viewjobResults(job)",
awToolTip: i18n._("View the job"),
dataPlacement: "top"
},

View File

@ -30,11 +30,11 @@ export default
dataTitle: "{{ completed_job.status_popover_title }}",
icon: 'icon-job-{{ completed_job.status }}',
iconOnly: true,
ngClick:"viewJobDetails(completed_job)",
ngClick:"viewjobResults(completed_job)",
},
id: {
label: 'ID',
ngClick:"viewJobDetails(completed_job)",
ngClick:"viewjobResults(completed_job)",
columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2 List-staticColumnAdjacent',
awToolTip: "{{ completed_job.status_tip }}",
dataPlacement: 'top'
@ -42,7 +42,7 @@ export default
name: {
label: i18n._('Name'),
columnClass: 'col-lg-4 col-md-4 col-sm-4 col-xs-6',
ngClick: "viewJobDetails(completed_job)",
ngClick: "viewjobResults(completed_job)",
awToolTip: "{{ completed_job.name | sanitize }}",
dataPlacement: 'top'
},

View File

@ -20,7 +20,7 @@ export default
fields: {
id: {
label: 'ID',
ngClick:"viewJobDetails(job)",
ngClick:"viewjobResults(job)",
key: true,
desc: true,
columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2',
@ -35,7 +35,7 @@ export default
dataTitle: "{{ job.status_popover_title }}",
icon: 'icon-job-{{ job.status }}',
iconOnly: true,
ngClick:"viewJobDetails(job)"
ngClick:"viewjobResults(job)"
},
started: {
label: 'Started',
@ -52,7 +52,7 @@ export default
name: {
label: 'Name',
columnClass: 'col-md-3 col-xs-5',
ngClick: "viewJobDetails(job)",
ngClick: "viewjobResults(job)",
}
},

View File

@ -177,10 +177,10 @@ export default ['$scope', '$rootScope', '$location', '$log',
$state.go('projects.edit', { project_id: id });
};
if ($scope.removeGoToJobDetails) {
$scope.removeGoToJobDetails();
if ($scope.removeGoTojobResults) {
$scope.removeGoTojobResults();
}
$scope.removeGoToJobDetails = $scope.$on('GoToJobDetails', function(e, data) {
$scope.removeGoTojobResults = $scope.$on('GoTojobResults', function(e, data) {
if (data.summary_fields.current_update || data.summary_fields.last_update) {
Wait('start');
@ -207,7 +207,7 @@ export default ['$scope', '$rootScope', '$location', '$log',
Rest.setUrl(project.url);
Rest.get()
.success(function(data) {
$scope.$emit('GoToJobDetails', data);
$scope.$emit('GoTojobResults', data);
})
.error(function(data, status) {
ProcessErrors($scope, data, status, null, {

View File

@ -132,10 +132,10 @@ export default ['$scope', '$rootScope', '$location', '$log', '$stateParams',
$state.go('projects.edit', { project_id: id });
};
if ($scope.removeGoToJobDetails) {
$scope.removeGoToJobDetails();
if ($scope.removeGoTojobResults) {
$scope.removeGoTojobResults();
}
$scope.removeGoToJobDetails = $scope.$on('GoToJobDetails', function(e, data) {
$scope.removeGoTojobResults = $scope.$on('GoTojobResults', function(e, data) {
if (data.summary_fields.current_update || data.summary_fields.last_update) {
Wait('start');
@ -162,7 +162,7 @@ export default ['$scope', '$rootScope', '$location', '$log', '$stateParams',
Rest.setUrl(project.url);
Rest.get()
.success(function(data) {
$scope.$emit('GoToJobDetails', data);
$scope.$emit('GoTojobResults', data);
})
.error(function(data, status) {
ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'),

View File

@ -8,9 +8,9 @@
RESULTS
</div>
<div class="StandardOut-actions">
<button id="relaunch-job-button" class="List-actionButton JobDetail-launchButton" data-placement="top" mode="all" ng-click="relaunchJob()" aw-tool-tip="Relaunch using the same parameters" data-original-title="" title=""><i class="icon-launch"></i> </button>
<button id="cancel-job-button" class="List-actionButton List-actionButton--delete JobDetail-launchButton" data-placement="top" ng-click="deleteJob()" ng-show="job.status === 'waiting' || job.status === 'running' || job.status === 'pending'" aw-tool-tip="Cancel" data-original-title="" title=""><i class="fa fa-minus-circle"></i> </button>
<button id="delete-job-button" class="List-actionButton List-actionButton--delete JobDetail-launchButton" data-placement="top" ng-click="deleteJob()" ng-hide="job.status === 'waiting' || job.status === 'running' || job.status === 'pending' " aw-tool-tip="Delete" data-original-title="" title=""><i class="fa fa-trash-o"></i> </button>
<button id="relaunch-job-button" class="List-actionButton jobResult-launchButton" data-placement="top" mode="all" ng-click="relaunchJob()" aw-tool-tip="Relaunch using the same parameters" data-original-title="" title=""><i class="icon-launch"></i> </button>
<button id="cancel-job-button" class="List-actionButton List-actionButton--delete jobResult-launchButton" data-placement="top" ng-click="deleteJob()" ng-show="job.status === 'waiting' || job.status === 'running' || job.status === 'pending'" aw-tool-tip="Cancel" data-original-title="" title=""><i class="fa fa-minus-circle"></i> </button>
<button id="delete-job-button" class="List-actionButton List-actionButton--delete jobResult-launchButton" data-placement="top" ng-click="deleteJob()" ng-hide="job.status === 'waiting' || job.status === 'running' || job.status === 'pending' " aw-tool-tip="Delete" data-original-title="" title=""><i class="fa fa-trash-o"></i> </button>
</div>
</div>
<div class="StandardOut-details">

View File

@ -8,9 +8,9 @@
RESULTS
</div>
<div class="StandardOut-actions">
<button id="relaunch-job-button" class="List-actionButton JobDetail-launchButton" data-placement="top" mode="all" ng-click="relaunchJob()" aw-tool-tip="Relaunch using the same parameters" data-original-title="" title=""><i class="icon-launch"></i> </button>
<button id="cancel-job-button" class="List-actionButton List-actionButton--delete JobDetail-launchButton" data-placement="top" ng-click="deleteJob()" ng-show="job.status === 'waiting' || job.status === 'running' || job.status === 'pending'" aw-tool-tip="Cancel" data-original-title="" title=""><i class="fa fa-minus-circle"></i> </button>
<button id="delete-job-button" class="List-actionButton List-actionButton--delete JobDetail-launchButton" data-placement="top" ng-click="deleteJob()" ng-hide="job.status === 'waiting' || job.status === 'running' || job.status === 'pending' " aw-tool-tip="Delete" data-original-title="" title=""><i class="fa fa-trash-o"></i> </button>
<button id="relaunch-job-button" class="List-actionButton jobResult-launchButton" data-placement="top" mode="all" ng-click="relaunchJob()" aw-tool-tip="Relaunch using the same parameters" data-original-title="" title=""><i class="icon-launch"></i> </button>
<button id="cancel-job-button" class="List-actionButton List-actionButton--delete jobResult-launchButton" data-placement="top" ng-click="deleteJob()" ng-show="job.status === 'waiting' || job.status === 'running' || job.status === 'pending'" aw-tool-tip="Cancel" data-original-title="" title=""><i class="fa fa-minus-circle"></i> </button>
<button id="delete-job-button" class="List-actionButton List-actionButton--delete jobResult-launchButton" data-placement="top" ng-click="deleteJob()" ng-hide="job.status === 'waiting' || job.status === 'running' || job.status === 'pending' " aw-tool-tip="Delete" data-original-title="" title=""><i class="fa fa-trash-o"></i> </button>
</div>
</div>
<div class="StandardOut-details">

View File

@ -20,7 +20,7 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce
// Open up a socket for events depending on the type of job
function openSockets() {
if ($state.current.name === 'jobDetail') {
if ($state.current.name === 'jobResult') {
$log.debug("socket watching on job_events-" + job_id);
$scope.$on(`ws-job_events-${job_id}`, function() {
$log.debug("socket fired on job_events-" + job_id);

View File

@ -8,8 +8,8 @@
RESULTS
</div>
<div class="StandardOut-actions">
<button id="cancel-job-button" class="List-actionButton List-actionButton--delete JobDetail-launchButton" data-placement="top" ng-click="deleteJob()" ng-show="job.status === 'waiting' || job.status === 'running' || job.status === 'pending'" aw-tool-tip="Cancel" data-original-title="" title=""><i class="fa fa-minus-circle"></i> </button>
<button id="delete-job-button" class="List-actionButton List-actionButton--delete JobDetail-launchButton" data-placement="top" ng-click="deleteJob()" ng-hide="job.status === 'waiting' || job.status === 'running' || job.status === 'pending' " aw-tool-tip="Delete" data-original-title="" title=""><i class="fa fa-trash-o"></i> </button>
<button id="cancel-job-button" class="List-actionButton List-actionButton--delete jobResult-launchButton" data-placement="top" ng-click="deleteJob()" ng-show="job.status === 'waiting' || job.status === 'running' || job.status === 'pending'" aw-tool-tip="Cancel" data-original-title="" title=""><i class="fa fa-minus-circle"></i> </button>
<button id="delete-job-button" class="List-actionButton List-actionButton--delete jobResult-launchButton" data-placement="top" ng-click="deleteJob()" ng-hide="job.status === 'waiting' || job.status === 'running' || job.status === 'pending' " aw-tool-tip="Delete" data-original-title="" title=""><i class="fa fa-trash-o"></i> </button>
</div>
</div>
<div class="StandardOut-details">

View File

@ -8,9 +8,9 @@
RESULTS
</div>
<div class="StandardOut-actions">
<button id="relaunch-job-button" class="List-actionButton JobDetail-launchButton" data-placement="top" mode="all" ng-click="relaunchJob()" aw-tool-tip="{{'Relaunch using the same parameters'|translate}}" data-original-title="" title=""><i class="icon-launch"></i> </button>
<button id="cancel-job-button" class="List-actionButton List-actionButton--delete JobDetail-launchButton" data-placement="top" ng-click="deleteJob()" ng-show="job.status === 'waiting' || job.status === 'running' || job.status === 'pending'" aw-tool-tip="{{'Cancel'|translate}}" data-original-title="" title=""><i class="fa fa-minus-circle"></i> </button>
<button id="delete-job-button" class="List-actionButton List-actionButton--delete JobDetail-launchButton" data-placement="top" ng-click="deleteJob()" ng-hide="job.status === 'waiting' || job.status === 'running' || job.status === 'pending' " aw-tool-tip="{{'Delete'|translate}}" data-original-title="" title=""><i class="fa fa-trash-o"></i> </button>
<button id="relaunch-job-button" class="List-actionButton jobResult-launchButton" data-placement="top" mode="all" ng-click="relaunchJob()" aw-tool-tip="{{'Relaunch using the same parameters'|translate}}" data-original-title="" title=""><i class="icon-launch"></i> </button>
<button id="cancel-job-button" class="List-actionButton List-actionButton--delete jobResult-launchButton" data-placement="top" ng-click="deleteJob()" ng-show="job.status === 'waiting' || job.status === 'running' || job.status === 'pending'" aw-tool-tip="{{'Cancel'|translate}}" data-original-title="" title=""><i class="fa fa-minus-circle"></i> </button>
<button id="delete-job-button" class="List-actionButton List-actionButton--delete jobResult-launchButton" data-placement="top" ng-click="deleteJob()" ng-hide="job.status === 'waiting' || job.status === 'running' || job.status === 'pending' " aw-tool-tip="{{'Delete'|translate}}" data-original-title="" title=""><i class="fa fa-trash-o"></i> </button>
</div>
</div>
<div class="StandardOut-details">

View File

@ -33,14 +33,14 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams,
if (data.status === 'failed' || data.status === 'canceled' || data.status === 'error' || data.status === 'successful') {
// Go out and refresh the job details
getJobDetails();
getjobResults();
}
});
// Set the parse type so that CodeMirror knows how to display extra params YAML/JSON
$scope.parseType = 'yaml';
function getJobDetails() {
function getjobResults() {
// Go out and get the job details based on the job type. jobType gets defined
// in the data block of the route declaration for each of the different types
@ -260,7 +260,7 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams,
RelaunchJob({ scope: $scope, id: typeId, type: job.type, name: job.name });
};
getJobDetails();
getjobResults();
}

View File

@ -830,7 +830,7 @@ export default [ '$state','moment', '$timeout', '$window',
this.on("click", function(d) {
if(d.job.id && d.unifiedJobTemplate) {
if(d.unifiedJobTemplate.unified_job_type === 'job') {
$state.go('jobDetail', {id: d.job.id});
$state.go('jobResult', {id: d.job.id});
}
else if(d.unifiedJobTemplate.unified_job_type === 'inventory_update') {
$state.go('inventorySyncStdout', {id: d.job.id});