mirror of
https://github.com/ansible/awx.git
synced 2026-01-14 03:10:42 -03:30
remove unused code
This commit is contained in:
parent
01d9c8546e
commit
cf68df41d5
@ -281,3 +281,254 @@
|
||||
background-color: @default-err;
|
||||
|
||||
}
|
||||
|
||||
// Job Details ---------------------------------------------------------------------------------
|
||||
|
||||
@breakpoint-md: 1200px;
|
||||
|
||||
.JobResults {
|
||||
.OnePlusTwo-container(100%, @breakpoint-md);
|
||||
|
||||
&.fullscreen {
|
||||
.JobResults-rightSide {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.JobResults-leftSide {
|
||||
.OnePlusTwo-left--panel(100%, @breakpoint-md);
|
||||
max-width: 30%;
|
||||
height: ~"calc(100vh - 177px)";
|
||||
|
||||
@media screen and (max-width: @breakpoint-md) {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.JobResults-rightSide {
|
||||
.OnePlusTwo-right--panel(100%, @breakpoint-md);
|
||||
height: ~"calc(100vh - 177px)";
|
||||
|
||||
@media (max-width: @breakpoint-md - 1px) {
|
||||
padding-right: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.JobResults-detailsPanel{
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.JobResults-stdoutActionButton--active {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
flex:none;
|
||||
width:0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.JobResults-panelHeader {
|
||||
display: flex;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.JobResults-panelHeaderText {
|
||||
color: @default-interface-txt;
|
||||
flex: 1 0 auto;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.JobResults-panelHeaderButtonActions {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.JobResults-resultRow {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
padding-bottom: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.JobResults-resultRow--variables {
|
||||
flex-direction: column;
|
||||
|
||||
#cm-variables-container {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.JobResults-resultRowLabel {
|
||||
text-transform: uppercase;
|
||||
color: @default-interface-txt;
|
||||
font-size: 12px;
|
||||
font-weight: normal!important;
|
||||
width: 30%;
|
||||
margin-right: 20px;
|
||||
|
||||
@media screen and (max-width: @breakpoint-md) {
|
||||
flex: 2.5 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.JobResults-resultRowLabel--fullWidth {
|
||||
width: 100%;
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.JobResults-resultRowText {
|
||||
width: ~"calc(70% - 20px)";
|
||||
flex: 1 0 auto;
|
||||
text-transform: none;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.JobResults-resultRowText--fullWidth {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.JobResults-expandArrow {
|
||||
color: #D7D7D7;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
text-transform: uppercase;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.JobResults-resultRowText--instanceGroup {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.JobResults-isolatedBadge {
|
||||
align-items: center;
|
||||
background-color: @default-list-header-bg;
|
||||
border-radius: 5px;
|
||||
color: @default-stdout-txt;
|
||||
display: flex;
|
||||
font-size: 10px;
|
||||
height: 16px;
|
||||
margin: 3px 0 0 10px;
|
||||
padding: 0 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.JobResults-statusResultIcon {
|
||||
padding-left: 0px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.JobResults-badgeRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.JobResults-badgeTitle{
|
||||
color: @default-interface-txt;
|
||||
font-size: 14px;
|
||||
margin-right: 10px;
|
||||
font-weight: normal;
|
||||
text-transform: uppercase;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: @breakpoint-md) {
|
||||
.JobResults-detailsPanel {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.JobResults-rightSide {
|
||||
height: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.JobResults-timeBadge {
|
||||
float:right;
|
||||
font-size: 11px;
|
||||
font-weight: normal;
|
||||
padding: 1px 10px;
|
||||
height: 14px;
|
||||
margin: 3px 15px;
|
||||
width: 80px;
|
||||
background-color: @default-bg;
|
||||
border-radius: 5px;
|
||||
color: @default-interface-txt;
|
||||
margin-right: -5px;
|
||||
}
|
||||
|
||||
.JobResults-panelRight {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.JobResults-panelRight .SmartSearch-bar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.JobResults-panelRightTitle{
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.JobResults-panelRightTitleText{
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.JobResults-badgeAndActionRow{
|
||||
display:flex;
|
||||
flex: 1 0 auto;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.StandardOut-panelHeader {
|
||||
flex: initial;
|
||||
}
|
||||
|
||||
.StandardOut-panelHeader--jobIsRunning {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
host-status-bar {
|
||||
flex: initial;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
smart-search {
|
||||
flex: initial;
|
||||
}
|
||||
|
||||
job-results-standard-out {
|
||||
flex: 1;
|
||||
flex-basis: auto;
|
||||
height: ~"calc(100% - 800px)";
|
||||
display: flex;
|
||||
border: 1px solid @d7grey;
|
||||
border-radius: 5px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
@media screen and (max-width: @breakpoint-md) {
|
||||
job-results-standard-out {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.JobResults-extraVarsHelp {
|
||||
margin-left: 10px;
|
||||
color: @default-icon;
|
||||
}
|
||||
|
||||
.JobResults-seeMoreLess {
|
||||
color: #337AB7;
|
||||
margin: 4px 0px;
|
||||
text-transform: uppercase;
|
||||
padding: 2px 0px;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
font-size: 11px;
|
||||
}
|
||||
@ -81,9 +81,6 @@
|
||||
@import '../../src/inventories-hosts/inventories/inventories.block.less';
|
||||
@import '../../src/inventories-hosts/shared/associate-groups/associate-groups.block.less';
|
||||
@import '../../src/inventories-hosts/shared/associate-hosts/associate-hosts.block.less';
|
||||
@import '../../src/job-results/host-status-bar/host-status-bar.block.less';
|
||||
@import '../../src/job-results/job-results-stdout/job-results-stdout.block.less';
|
||||
@import '../../src/job-results/job-results.block.less';
|
||||
@import '../../src/job-submission/job-submission.block.less';
|
||||
@import '../../src/license/license.block.less';
|
||||
@import '../../src/login/loginModal/thirdPartySignOn/thirdPartySignOn.block.less';
|
||||
@ -116,7 +113,7 @@
|
||||
@import '../../src/shared/text-label';
|
||||
@import '../../src/shared/upgrade/upgrade.block.less';
|
||||
@import '../../src/smart-status/smart-status.block.less';
|
||||
@import '../../src/standard-out/standard-out.block.less';
|
||||
@import '../../src/workflow-results/standard-out.block.less';
|
||||
@import '../../src/system-tracking/date-picker/date-picker.block.less';
|
||||
@import '../../src/system-tracking/fact-data-table/fact-data-table.block.less';
|
||||
@import '../../src/system-tracking/fact-module-filter.block.less';
|
||||
|
||||
@ -19,7 +19,6 @@ import credentialTypes from './credential-types/main';
|
||||
import organizations from './organizations/main';
|
||||
import managementJobs from './management-jobs/main';
|
||||
import workflowResults from './workflow-results/main';
|
||||
import jobResults from './job-results/main';
|
||||
import jobSubmission from './job-submission/main';
|
||||
import notifications from './notifications/main';
|
||||
import about from './about/main';
|
||||
@ -30,7 +29,6 @@ import configuration from './configuration/main';
|
||||
import home from './home/main';
|
||||
import login from './login/main';
|
||||
import activityStream from './activity-stream/main';
|
||||
import standardOut from './standard-out/main';
|
||||
import Templates from './templates/main';
|
||||
import teams from './teams/main';
|
||||
import users from './users/main';
|
||||
@ -67,7 +65,6 @@ angular
|
||||
'gettext',
|
||||
'Timezones',
|
||||
'lrInfiniteScroll',
|
||||
|
||||
about.name,
|
||||
access.name,
|
||||
license.name,
|
||||
@ -86,10 +83,8 @@ angular
|
||||
login.name,
|
||||
activityStream.name,
|
||||
workflowResults.name,
|
||||
jobResults.name,
|
||||
jobSubmission.name,
|
||||
notifications.name,
|
||||
standardOut.name,
|
||||
Templates.name,
|
||||
portalMode.name,
|
||||
teams.name,
|
||||
@ -242,23 +237,6 @@ angular
|
||||
$rootScope.crumbCache = [];
|
||||
|
||||
$transitions.onStart({}, function(trans) {
|
||||
// Remove any lingering intervals
|
||||
// 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.jobResultInterval && !_.includes(jobResultStates, trans.to().name) ) {
|
||||
window.clearInterval($rootScope.jobResultInterval);
|
||||
}
|
||||
if ($rootScope.jobStdOutInterval && !_.includes(jobResultStates, trans.to().name) ) {
|
||||
window.clearInterval($rootScope.jobStdOutInterval);
|
||||
}
|
||||
|
||||
$rootScope.flashMessage = null;
|
||||
|
||||
$('#form-modal2 .modal-body').empty();
|
||||
|
||||
@ -1,77 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default ['jobResultsService', 'parseStdoutService', function(jobResultsService, parseStdoutService){
|
||||
var val = {};
|
||||
|
||||
val = {
|
||||
populateDefers: {},
|
||||
queue: {},
|
||||
// munge the raw event from the backend into the event_queue's format
|
||||
munge: function(event) {
|
||||
// basic data needed in the munged event
|
||||
var mungedEvent = {
|
||||
counter: event.counter,
|
||||
id: event.id,
|
||||
processed: false,
|
||||
name: event.event_name,
|
||||
changes: []
|
||||
};
|
||||
|
||||
// the interface for grabbing standard out is generalized and
|
||||
// present across many types of events, so go ahead and check for
|
||||
// updates to it
|
||||
if (event.stdout) {
|
||||
mungedEvent.stdout = parseStdoutService.parseStdout(event);
|
||||
mungedEvent.start_line = event.start_line + 1;
|
||||
mungedEvent.end_line = event.end_line + 1;
|
||||
mungedEvent.actual_end_line = parseStdoutService.actualEndLine(event) + 1;
|
||||
mungedEvent.changes.push('stdout');
|
||||
}
|
||||
|
||||
// for different types of events, you need different types of data
|
||||
if (event.event_name === 'playbook_on_start') {
|
||||
mungedEvent.startTime = event.modified;
|
||||
mungedEvent.changes.push('startTime');
|
||||
} if (event.event_name === 'playbook_on_stats') {
|
||||
// get the data for populating the host status bar
|
||||
mungedEvent.count = jobResultsService
|
||||
.getCountsFromStatsEvent(event.event_data);
|
||||
mungedEvent.finishedTime = event.modified;
|
||||
mungedEvent.changes.push('count');
|
||||
mungedEvent.changes.push('countFinished');
|
||||
mungedEvent.changes.push('finishedTime');
|
||||
}
|
||||
return mungedEvent;
|
||||
},
|
||||
// reinitializes the event queue value for the job results page
|
||||
initialize: function() {
|
||||
val.queue = {};
|
||||
val.populateDefers = {};
|
||||
},
|
||||
// populates the event queue
|
||||
populate: function(event) {
|
||||
if (event) {
|
||||
val.queue[event.counter] = val.munge(event);
|
||||
|
||||
if (!val.queue[event.counter].processed) {
|
||||
return val.munge(event);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
// the event has been processed in the view and should be marked as
|
||||
// completed in the queue
|
||||
markProcessed: function(event) {
|
||||
val.queue[event.counter].processed = true;
|
||||
}
|
||||
};
|
||||
|
||||
return val;
|
||||
}];
|
||||
@ -1,87 +0,0 @@
|
||||
.HostStatusBar {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.HostStatusBar-ok,
|
||||
.HostStatusBar-changed,
|
||||
.HostStatusBar-unreachable,
|
||||
.HostStatusBar-failures,
|
||||
.HostStatusBar-skipped,
|
||||
.HostStatusBar-noData {
|
||||
height: 15px;
|
||||
border-top: 5px solid @default-bg;
|
||||
border-bottom: 5px solid @default-bg;
|
||||
}
|
||||
|
||||
.HostStatusBar-ok {
|
||||
background-color: @default-succ;
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.HostStatusBar-changed {
|
||||
background-color: @default-warning;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.HostStatusBar-unreachable {
|
||||
background-color: @default-unreachable;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.HostStatusBar-dark {
|
||||
background-color: @default-unreachable;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.HostStatusBar-failures {
|
||||
background-color: @default-err;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.HostStatusBar-skipped {
|
||||
background-color: @default-link;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.HostStatusBar-noData {
|
||||
background-color: @default-icon-hov;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
.HostStatusBar-tooltipLabel {
|
||||
text-transform: uppercase;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.HostStatusBar-tooltipBadge {
|
||||
border-radius: 5px;
|
||||
border: 1px solid @default-bg;
|
||||
}
|
||||
|
||||
.HostStatusBar-tooltipBadge--ok {
|
||||
background-color: @default-succ;
|
||||
}
|
||||
|
||||
.HostStatusBar-tooltipBadge--unreachable {
|
||||
background-color: @default-unreachable;
|
||||
}
|
||||
|
||||
.HostStatusBar-tooltipBadge--dark {
|
||||
background-color: @default-unreachable;
|
||||
}
|
||||
|
||||
.HostStatusBar-tooltipBadge--skipped {
|
||||
background-color: @default-link;
|
||||
}
|
||||
|
||||
.HostStatusBar-tooltipBadge--changed {
|
||||
background-color: @default-warning;
|
||||
}
|
||||
|
||||
.HostStatusBar-tooltipBadge--failures {
|
||||
background-color: @default-err;
|
||||
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
// import hostStatusBarController from './host-status-bar.controller';
|
||||
export default [ 'templateUrl',
|
||||
function(templateUrl) {
|
||||
return {
|
||||
scope: true,
|
||||
templateUrl: templateUrl('job-results/host-status-bar/host-status-bar'),
|
||||
restrict: 'E',
|
||||
// controller: standardOutLogController,
|
||||
link: function(scope) {
|
||||
// as count is changed by event data coming in,
|
||||
// update the host status bar
|
||||
var toDestroy = scope.$watch('count', function(val) {
|
||||
if (val) {
|
||||
Object.keys(val).forEach(key => {
|
||||
// reposition the hosts status bar by setting
|
||||
// the various flex values to the count of
|
||||
// those hosts
|
||||
$(`.HostStatusBar-${key}`)
|
||||
.css('flex', `${val[key]} 0 auto`);
|
||||
|
||||
// set the tooltip to give how many hosts of
|
||||
// each type
|
||||
if (val[key] > 0) {
|
||||
scope[`${key}CountTip`] = `<span class='HostStatusBar-tooltipLabel'>${key}</span><span class='badge HostStatusBar-tooltipBadge HostStatusBar-tooltipBadge--${key}'>${val[key]}</span>`;
|
||||
}
|
||||
});
|
||||
|
||||
// if there are any hosts that have finished, don't
|
||||
// show default grey bar
|
||||
scope.hasCount = (Object
|
||||
.keys(val)
|
||||
.filter(key => (val[key] > 0)).length > 0);
|
||||
}
|
||||
});
|
||||
|
||||
scope.$on('$destroy', function(){
|
||||
toDestroy();
|
||||
});
|
||||
}
|
||||
};
|
||||
}];
|
||||
@ -1,30 +0,0 @@
|
||||
<div class="HostStatusBar">
|
||||
<div class="HostStatusBar-ok"
|
||||
data-placement="top"
|
||||
aw-tool-tip="{{okCountTip}}"
|
||||
data-tip-watch="okCountTip"></div>
|
||||
<div class="HostStatusBar-changed"
|
||||
data-placement="top"
|
||||
aw-tool-tip="{{changedCountTip}}"
|
||||
data-tip-watch="changedCountTip"></div>
|
||||
<div class="HostStatusBar-failures"
|
||||
data-placement="top"
|
||||
aw-tool-tip="{{failuresCountTip}}"
|
||||
data-tip-watch="failuresCountTip"></div>
|
||||
<div class="HostStatusBar-unreachable"
|
||||
data-placement="top"
|
||||
aw-tool-tip="{{unreachableCountTip}}"
|
||||
data-tip-watch="unreachableCountTip"></div>
|
||||
<div class="HostStatusBar-skipped"
|
||||
data-placement="top"
|
||||
aw-tool-tip="{{skippedCountTip}}"
|
||||
data-tip-watch="skippedCountTip"></div>
|
||||
<div class="HostStatusBar-noData"
|
||||
aw-tool-tip="The host status bar will update when the job is complete."
|
||||
ng-show="!hasCount && !jobFinished"
|
||||
data-placement="top"></div>
|
||||
<div class="HostStatusBar-noData"
|
||||
aw-tool-tip="No host status data was given for this job."
|
||||
ng-show="!hasCount && jobFinished"
|
||||
data-placement="top"></div>
|
||||
</div>
|
||||
@ -1,11 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2015 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import hostStatusBar from './host-status-bar.directive';
|
||||
|
||||
export default
|
||||
angular.module('hostStatusBarDirective', [])
|
||||
.directive('hostStatusBar', hostStatusBar);
|
||||
@ -1,271 +0,0 @@
|
||||
@breakpoint-md: 1200px;
|
||||
|
||||
.JobResultsStdOut {
|
||||
height: auto;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-toolbar {
|
||||
flex: initial;
|
||||
display: flex;
|
||||
border-bottom: 0px;
|
||||
border-radius: 5px;
|
||||
border-bottom-left-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-toolbarNumberColumn {
|
||||
background-color: @default-list-header-bg;
|
||||
color: @b7grey;
|
||||
flex: initial;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 70px;
|
||||
padding-bottom: 10px;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
padding-top: 10px;
|
||||
border-top-left-radius: 5px;
|
||||
z-index: 1;
|
||||
border-right: 1px solid @d7grey;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-expandAllButton {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
padding-left: 4px;
|
||||
padding-top: 1px;
|
||||
border-radius: 50%;
|
||||
background-color: @default-bg;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
color: #848992;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-expandAllButton:hover .JobResultsStdOut-expandAllIcon,
|
||||
.JobResultsStdOut-expandAllIcon:hover {
|
||||
color: @default-data-txt;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-toolbarStdoutColumn {
|
||||
white-space: normal;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-right: 10px;
|
||||
background-color: @default-secondary-bg;
|
||||
border-top-right-radius: 5px;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-followButton {
|
||||
cursor: pointer;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
padding: 1px 0 0 4px;
|
||||
border-radius: 50%;
|
||||
margin-top: 10px;
|
||||
font-size: 11px;
|
||||
background-color: @default-icon;
|
||||
color: @default-bg;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-followIcon {
|
||||
color: @default-bg;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-followButton:hover {
|
||||
background-color: @default-data-txt;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-followButton.is-engaged {
|
||||
background-color: @default-link;
|
||||
color: @default-bg;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-followButton.is-engaged .JobResultsStdOut-followIcon {
|
||||
color: @default-bg;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-followButton.is-engaged:hover {
|
||||
background-color: @default-icon;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-followButton.is-engaged:hover .JobResultsStdOut-followIcon,
|
||||
.JobResultsStdOut-followButton.is-engaged .JobResultsStdOut-followIcon:hover {
|
||||
color: @default-border;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-stdoutContainer {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
background-color: @default-secondary-bg;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-numberColumnPreload {
|
||||
background-color: @default-list-header-bg;
|
||||
border-right: 1px solid @d7grey;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-aLineOfStdOut {
|
||||
display: flex;
|
||||
font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-lineExpander {
|
||||
text-align: left;
|
||||
padding-left: 11px;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-lineExpanderIcon {
|
||||
font-size: 19px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-lineExpanderIcon:hover {
|
||||
color: @default-data-txt;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-lineNumberColumn {
|
||||
display: flex;
|
||||
background-color: @default-list-header-bg;
|
||||
text-align: right;
|
||||
padding-right: 10px;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
width: 75px;
|
||||
flex: 1 0 70px;
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
z-index: 1;
|
||||
border-right: 1px solid @d7grey;
|
||||
color: @default-icon;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-stdoutColumn {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
color: @default-interface-txt;
|
||||
display: inline-block;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
width:100%;
|
||||
background-color: @default-secondary-bg;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-stdoutColumn--tooMany {
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
color: @default-err;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-stdoutColumn--clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-aLineOfStdOut:hover,
|
||||
.JobResultsStdOut-aLineOfStdOut:hover .JobResultsStdOut-lineNumberColumn,
|
||||
.JobResultsStdOut-aLineOfStdOut:hover .JobResultsStdOut-stdoutColumn {
|
||||
background-color: @default-bg;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-aLineOfStdOut:hover .JobResultsStdOut-lineNumberColumn {
|
||||
border-right: 1px solid @default-bg;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-footer {
|
||||
height: 20px;
|
||||
border-bottom-right-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
background-color: @default-secondary-bg;
|
||||
border-top: 0px;
|
||||
border-radius: 5px;
|
||||
border-top-left-radius: 0px;
|
||||
border-top-right-radius: 0px;
|
||||
overflow: hidden;
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-footerNumberColumn {
|
||||
background-color: @default-list-header-bg;
|
||||
width: 70px;
|
||||
height: 100%;
|
||||
border-right: 1px solid @d7grey;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-followAnchor {
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-toTop {
|
||||
color: @default-icon;
|
||||
cursor: pointer;
|
||||
font-family: monaco;
|
||||
font-size: 10px;
|
||||
margin-right: 20px;
|
||||
text-align: right;
|
||||
display: flex;
|
||||
|
||||
span {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.JobResultsStdOut-toTop--numberColumn {
|
||||
background: @default-list-header-bg;
|
||||
height: 40px;
|
||||
width: 70px;
|
||||
border-right: 1px solid #D7D7D7;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-toTop:hover {
|
||||
color: @default-data-txt;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-cappedLine {
|
||||
color: @b7grey;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@media (max-width: @breakpoint-md) {
|
||||
.JobResultsStdOut-numberColumnPreload {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-topAnchor {
|
||||
position: static;
|
||||
width: 100%;
|
||||
top: -20px;
|
||||
margin-top: -250px;
|
||||
margin-bottom: 250px;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-followAnchor {
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-stdoutContainer {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.JobResultsStdOut-lineAnchor {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
@ -1,415 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
// import hostStatusBarController from './host-status-bar.controller';
|
||||
export default [ 'templateUrl', '$timeout', '$location', '$anchorScroll',
|
||||
function(templateUrl, $timeout, $location, $anchorScroll) {
|
||||
return {
|
||||
scope: false,
|
||||
templateUrl: templateUrl('job-results/job-results-stdout/job-results-stdout'),
|
||||
restrict: 'E',
|
||||
link: function(scope, element) {
|
||||
var toDestroy = [],
|
||||
resizer,
|
||||
scrollWatcher;
|
||||
|
||||
scope.$on('$destroy', function(){
|
||||
$(window).off("resize", resizer);
|
||||
$(window).off("scroll", scrollWatcher);
|
||||
$(".JobResultsStdOut-stdoutContainer").off('scroll',
|
||||
scrollWatcher);
|
||||
toDestroy.forEach(closureFunc => closureFunc());
|
||||
});
|
||||
|
||||
scope.stdoutContainerAvailable.resolve("container available");
|
||||
// utility function used to find the top visible line and
|
||||
// parent header in the pane
|
||||
//
|
||||
// note that while this function is called when in mobile width
|
||||
// the line anchor is not displayed in the css so calls
|
||||
// to lineAnchor do nothing
|
||||
var findTopLines = function() {
|
||||
var $container = $('.JobResultsStdOut-stdoutContainer');
|
||||
|
||||
// get the first visible line's head element
|
||||
var getHeadElement = function (line) {
|
||||
var lineHasHeaderClass = !!(line
|
||||
.hasClass("header_play") ||
|
||||
line.hasClass("header_task"));
|
||||
var lineClassList;
|
||||
var lineUUIDClass;
|
||||
|
||||
if (lineHasHeaderClass) {
|
||||
// find head element when the first visible
|
||||
// line is a header
|
||||
|
||||
lineClassList = line.attr("class")
|
||||
.split(" ");
|
||||
|
||||
// get the header class w task uuid...
|
||||
lineUUIDClass = lineClassList
|
||||
.filter(n => n
|
||||
.indexOf("header_task_") > -1)[0];
|
||||
|
||||
// ...if that doesn't exist get the one
|
||||
// w play uuid
|
||||
if (!lineUUIDClass) {
|
||||
lineUUIDClass = lineClassList
|
||||
.filter(n => n.
|
||||
indexOf("header_play_") > -1)[0];
|
||||
}
|
||||
|
||||
// get the header line (not their might
|
||||
// be more than one, so get the one with
|
||||
// the actual header class)
|
||||
//
|
||||
// TODO it might be better in this case to just
|
||||
// return `line` (less jumping with a cowsay
|
||||
// case)
|
||||
return $(".actual_header." +
|
||||
lineUUIDClass);
|
||||
} else {
|
||||
// find head element when the first visible
|
||||
// line is not a header
|
||||
|
||||
lineClassList = line.attr("class")
|
||||
.split(" ");
|
||||
|
||||
// get the class w task uuid...
|
||||
lineUUIDClass = lineClassList
|
||||
.filter(n => n
|
||||
.indexOf("task_") > -1)[0];
|
||||
|
||||
// ...if that doesn't exist get the one
|
||||
// w play uuid
|
||||
if (!lineUUIDClass) {
|
||||
lineUUIDClass = lineClassList
|
||||
.filter(n => n
|
||||
.indexOf("play_") > -1)[0];
|
||||
}
|
||||
|
||||
// get the header line (not their might
|
||||
// be more than one, so get the one with
|
||||
// the actual header class)
|
||||
return $(".actual_header.header_" +
|
||||
lineUUIDClass);
|
||||
}
|
||||
};
|
||||
|
||||
var visItem,
|
||||
parentItem;
|
||||
|
||||
// iterate through each line of standard out
|
||||
$container.find('.JobResultsStdOut-aLineOfStdOut:visible')
|
||||
.each( function () {
|
||||
var $this = $(this);
|
||||
|
||||
// check to see if the line is the first visible
|
||||
// line in the viewport...
|
||||
if ($this.position().top >= 0) {
|
||||
|
||||
// ...if it is, return the line number
|
||||
// for this line
|
||||
visItem = parseInt($($this
|
||||
.children()[0])
|
||||
.text());
|
||||
|
||||
// as well as the line number for it's
|
||||
// closest parent header line
|
||||
var $head = getHeadElement($this);
|
||||
parentItem = parseInt($($head
|
||||
.children()[0])
|
||||
.text());
|
||||
|
||||
// stop iterating over the standard out
|
||||
// lines once the first one has been
|
||||
// found
|
||||
|
||||
$this = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
$this = null;
|
||||
});
|
||||
|
||||
$container = null;
|
||||
|
||||
return {
|
||||
visLine: visItem,
|
||||
parentVisLine: parentItem
|
||||
};
|
||||
};
|
||||
|
||||
// find if window is initially mobile or desktop width
|
||||
if (window.innerWidth <= 1200) {
|
||||
scope.isMobile = true;
|
||||
} else {
|
||||
scope.isMobile = false;
|
||||
}
|
||||
|
||||
resizer = function() {
|
||||
// and update the isMobile var accordingly
|
||||
if (window.innerWidth <= 1200 && !scope.isMobile) {
|
||||
scope.isMobile = true;
|
||||
} else if (window.innerWidth > 1200 & scope.isMobile) {
|
||||
scope.isMobile = false;
|
||||
}
|
||||
};
|
||||
// watch changes to the window size
|
||||
$(window).resize(resizer);
|
||||
|
||||
var lastScrollTop;
|
||||
|
||||
var initScrollTop = function() {
|
||||
lastScrollTop = 0;
|
||||
};
|
||||
scrollWatcher = function() {
|
||||
var st = $(this).scrollTop();
|
||||
var netScroll = st + $(this).innerHeight();
|
||||
var fullHeight;
|
||||
|
||||
if (st < lastScrollTop){
|
||||
// user up scrolled, so disengage follow
|
||||
scope.followEngaged = false;
|
||||
}
|
||||
|
||||
if (scope.isMobile) {
|
||||
// for mobile the height is the body of the entire
|
||||
// page
|
||||
fullHeight = $("body").height();
|
||||
} else {
|
||||
// for desktop the height is the body of the
|
||||
// stdoutContainer, minus the "^ TOP" indicator
|
||||
fullHeight = $(this)[0].scrollHeight - 25;
|
||||
}
|
||||
|
||||
if(netScroll >= fullHeight) {
|
||||
// user scrolled all the way to bottom, so engage
|
||||
// follow
|
||||
scope.followEngaged = true;
|
||||
}
|
||||
|
||||
// pane is now overflowed, show top indicator.
|
||||
if (st > 0) {
|
||||
scope.stdoutOverflowed = true;
|
||||
}
|
||||
|
||||
lastScrollTop = st;
|
||||
|
||||
st = null;
|
||||
netScroll = null;
|
||||
fullHeight = null;
|
||||
};
|
||||
|
||||
// update scroll watchers when isMobile changes based on
|
||||
// window resize
|
||||
toDestroy.push(scope.$watch('isMobile', function(val) {
|
||||
if (val === true) {
|
||||
// make sure ^ TOP always shown for mobile
|
||||
scope.stdoutOverflowed = true;
|
||||
|
||||
// unbind scroll watcher on standard out container
|
||||
$(".JobResultsStdOut-stdoutContainer")
|
||||
.unbind('scroll');
|
||||
|
||||
// init scroll watcher on window
|
||||
initScrollTop();
|
||||
$(window).on('scroll', scrollWatcher);
|
||||
|
||||
} else if (val === false) {
|
||||
// unbind scroll watcher on window
|
||||
$(window).unbind('scroll');
|
||||
|
||||
// init scroll watcher on standard out container
|
||||
initScrollTop();
|
||||
$(".JobResultsStdOut-stdoutContainer").on('scroll',
|
||||
scrollWatcher);
|
||||
}
|
||||
}));
|
||||
|
||||
// called to scroll to follow anchor
|
||||
scope.followScroll = function() {
|
||||
// a double-check to make sure the follow anchor is at
|
||||
// the bottom of the standard out container
|
||||
$(".JobResultsStdOut-followAnchor")
|
||||
.appendTo(".JobResultsStdOut-stdoutContainer");
|
||||
|
||||
$location.hash('followAnchor');
|
||||
$anchorScroll();
|
||||
};
|
||||
|
||||
// called to scroll to top of standard out (when "^ TOP" is
|
||||
// clicked)
|
||||
scope.toTop = function() {
|
||||
$location.hash('topAnchor');
|
||||
$anchorScroll();
|
||||
};
|
||||
|
||||
// called to scroll to the current line anchor
|
||||
// when expand all/collapse all/filtering is engaged
|
||||
//
|
||||
// note that while this function can be called when in mobile
|
||||
// width the line anchor is not displayed in the css so those
|
||||
// calls do nothing
|
||||
scope.lineAnchor = function() {
|
||||
$location.hash('lineAnchor');
|
||||
$anchorScroll();
|
||||
};
|
||||
|
||||
// if following becomes active, go ahead and get to the bottom
|
||||
// of the standard out pane
|
||||
toDestroy.push(scope.$watch('followEngaged', function(val) {
|
||||
// scroll to follow point if followEngaged is true
|
||||
if (val) {
|
||||
scope.followScroll();
|
||||
}
|
||||
|
||||
// set up tooltip changes for not finsihed job
|
||||
if (!scope.jobFinished) {
|
||||
if (val) {
|
||||
scope.followTooltip = "Currently following standard out as it comes in. Click to unfollow.";
|
||||
} else {
|
||||
scope.followTooltip = "Click to follow standard out as it comes in.";
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// follow button ng-click function
|
||||
scope.followToggleClicked = function() {
|
||||
if (scope.jobFinished) {
|
||||
// when the job is finished engage followScroll
|
||||
scope.followScroll();
|
||||
} else {
|
||||
// when the job is not finished toggle followEngaged
|
||||
// which is watched above
|
||||
scope.followEngaged = !scope.followEngaged;
|
||||
}
|
||||
};
|
||||
|
||||
// expand all/collapse all ng-click function
|
||||
scope.toggleAllStdout = function(type) {
|
||||
// find the top visible line in the container currently,
|
||||
// as well as the header parent of that line
|
||||
var topLines = findTopLines();
|
||||
|
||||
if (type === 'expand') {
|
||||
// for expand prepend the lineAnchor to the visible
|
||||
// line
|
||||
$(".line_num_" + topLines.visLine)
|
||||
.prepend($("#lineAnchor"));
|
||||
} else {
|
||||
// for collapse prepent the lineAnchor to the
|
||||
// visible line's parent header
|
||||
$(".line_num_" + topLines.parentVisLine)
|
||||
.prepend($("#lineAnchor"));
|
||||
}
|
||||
|
||||
var expandClass;
|
||||
if (type === 'expand') {
|
||||
// for expand all, you'll need to find all the
|
||||
// collapsed headers to expand them
|
||||
expandClass = "fa-caret-right";
|
||||
} else {
|
||||
// for collapse all, you'll need to find all the
|
||||
// expanded headers to collapse them
|
||||
expandClass = "fa-caret-down";
|
||||
}
|
||||
|
||||
// find all applicable task headers that need to be
|
||||
// toggled
|
||||
element.find(".expanderizer--task."+expandClass)
|
||||
.each((i, val) => {
|
||||
// and trigger their expansion/collapsing
|
||||
$timeout(function(){
|
||||
// TODO change to a direct call of the
|
||||
// toggleLine function
|
||||
angular.element(val).trigger('click');
|
||||
// TODO only call lineAnchor for those
|
||||
// that are above the first visible line
|
||||
scope.lineAnchor();
|
||||
});
|
||||
});
|
||||
|
||||
// find all applicable play headers that need to be
|
||||
// toggled
|
||||
element.find(".expanderizer--play."+expandClass)
|
||||
.each((i, val) => {
|
||||
// for collapse all, only collapse play
|
||||
// headers that do not have children task
|
||||
// headers
|
||||
if(angular.element("." +
|
||||
angular.element(val).attr("data-uuid"))
|
||||
.find(".expanderizer--task")
|
||||
.length === 0 ||
|
||||
type !== 'collapse') {
|
||||
|
||||
// trigger their expansion/
|
||||
// collapsing
|
||||
$timeout(function(){
|
||||
// TODO change to a direct
|
||||
// call of the
|
||||
// toggleLine function
|
||||
angular.element(val)
|
||||
.trigger('click');
|
||||
// TODO only call lineAnchor
|
||||
// for those that are above
|
||||
// the first visible line
|
||||
scope.lineAnchor();
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// expand/collapse triangle ng-click function
|
||||
scope.toggleLine = function($event, id) {
|
||||
// if the section is currently expanded
|
||||
if ($($event.currentTarget).hasClass("fa-caret-down")) {
|
||||
// hide all the children lines
|
||||
$(id).hide();
|
||||
|
||||
// and change the triangle for the header to collapse
|
||||
$($event.currentTarget)
|
||||
.removeClass("fa-caret-down");
|
||||
$($event.currentTarget)
|
||||
.addClass("fa-caret-right");
|
||||
} else {
|
||||
// if the section is currently collapsed
|
||||
|
||||
// show all the children lines
|
||||
$(id).show();
|
||||
|
||||
// and change the triangle for the header to expanded
|
||||
$($event.currentTarget)
|
||||
.removeClass("fa-caret-right");
|
||||
$($event.currentTarget)
|
||||
.addClass("fa-caret-down");
|
||||
|
||||
// if the section you expanded is a play
|
||||
if ($($event.currentTarget)
|
||||
.hasClass("expanderizer--play")) {
|
||||
// find all children task headers and
|
||||
// expand them too
|
||||
$("." + $($event.currentTarget)
|
||||
.attr("data-uuid"))
|
||||
.find(".expanderizer--task")
|
||||
.each((i, val) => {
|
||||
if ($(val)
|
||||
.hasClass("fa-caret-right")) {
|
||||
$timeout(function(){
|
||||
// TODO change to a
|
||||
// direct call of the
|
||||
// toggleLine function
|
||||
angular.element(val)
|
||||
.trigger('click');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}];
|
||||
@ -1,65 +0,0 @@
|
||||
<div class="JobResultsStdOut">
|
||||
<div class="JobResultsStdOut-toolbar">
|
||||
<div class="JobResultsStdOut-toolbarNumberColumn">
|
||||
<div class="JobResultsStdOut-expandAllButton"
|
||||
ng-click="toggleAllStdout('expand')"
|
||||
aw-tool-tip="Expand all lines of standard out."
|
||||
data-placement="top">
|
||||
<i class ="JobResultsStdOut-expandAllIcon fa fa-plus">
|
||||
</i>
|
||||
</div>
|
||||
<div class="JobResultsStdOut-expandAllButton"
|
||||
ng-click="toggleAllStdout('collapse')"
|
||||
aw-tool-tip="Collapse all lines of standard out except play and task headers."
|
||||
data-placement="top">
|
||||
<i class ="JobResultsStdOut-expandAllIcon fa fa-minus">
|
||||
</i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="JobResultsStdOut-toolbarStdoutColumn">
|
||||
<div class="JobResultsStdOut-followButton"
|
||||
ng-class="{'is-engaged': followEngaged && !jobFinished}"
|
||||
aw-tool-tip="{{ followTooltip }}"
|
||||
data-tip-watch="followTooltip"
|
||||
data-placement="left"
|
||||
data-trigger="hover"
|
||||
data-container="body"
|
||||
ng-click="followToggleClicked()">
|
||||
<i class="JobResultsStdOut-followIcon fa fa-arrow-down">
|
||||
</i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="JobResultsStdOut-stdoutContainer">
|
||||
<div id="topAnchor" class="JobResultsStdOut-topAnchor"></div>
|
||||
<div class="JobResultsStdOut-numberColumnPreload"></div>
|
||||
<div id='lineAnchor' class="JobResultsStdOut-lineAnchor"></div>
|
||||
<div class="JobResultsStdOut-aLineOfStdOut"
|
||||
ng-show="tooManyEvents || tooManyPastEvents || showLegacyJobErrorMessage">
|
||||
<div class="JobResultsStdOut-lineNumberColumn">
|
||||
<span class="JobResultsStdOut-lineExpander"> </span>
|
||||
</div>
|
||||
<div class="JobResultsStdOut-stdoutColumn JobResultsStdOut-stdoutColumn--tooMany"
|
||||
ng-show="tooManyEvents" translate>The standard output is too large to display. Please specify additional filters to narrow the standard out.</div>
|
||||
<div class="JobResultsStdOut-stdoutColumn JobResultsStdOut-stdoutColumn--tooMany"
|
||||
ng-show="tooManyPastEvents" translate>Too much previous output to display. Showing running standard output.</div>
|
||||
<div class="JobResultsStdOut-stdoutColumn JobResultsStdOut-stdoutColumn--tooMany"
|
||||
ng-show="showLegacyJobErrorMessage" translate>Job details are not available for this job. Please download to view standard out.</div>
|
||||
</div>
|
||||
<!-- next is 1 is a hack to get the first line to be put in the pane in the
|
||||
right place -->
|
||||
<div class="next_is_1"></div>
|
||||
<div id="followAnchor"
|
||||
class="JobResultsStdOut-followAnchor">
|
||||
</div>
|
||||
</div>
|
||||
<div class="JobResultsStdOut-footer">
|
||||
<div class="JobResultsStdOut-toTop"
|
||||
ng-show="stdoutOverflowed">
|
||||
<div class="JobResultsStdOut-toTop--numberColumn">
|
||||
</div>
|
||||
<span ng-click="toTop()">^ <span translate>TOP</span></span>
|
||||
</div>
|
||||
<div class="JobResultsStdOut-footerNumberColumn"></div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,11 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2015 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import jobResultsStdOut from './job-results-stdout.directive';
|
||||
|
||||
export default
|
||||
angular.module('jobResultStdOutDirective', [])
|
||||
.directive('jobResultsStandardOut', jobResultsStdOut);
|
||||
@ -1,248 +0,0 @@
|
||||
@breakpoint-md: 1200px;
|
||||
|
||||
.JobResults {
|
||||
.OnePlusTwo-container(100%, @breakpoint-md);
|
||||
|
||||
&.fullscreen {
|
||||
.JobResults-rightSide {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.JobResults-leftSide {
|
||||
.OnePlusTwo-left--panel(100%, @breakpoint-md);
|
||||
max-width: 30%;
|
||||
height: ~"calc(100vh - 177px)";
|
||||
|
||||
@media screen and (max-width: @breakpoint-md) {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.JobResults-rightSide {
|
||||
.OnePlusTwo-right--panel(100%, @breakpoint-md);
|
||||
height: ~"calc(100vh - 177px)";
|
||||
|
||||
@media (max-width: @breakpoint-md - 1px) {
|
||||
padding-right: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.JobResults-detailsPanel{
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.JobResults-stdoutActionButton--active {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
flex:none;
|
||||
width:0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.JobResults-panelHeader {
|
||||
display: flex;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.JobResults-panelHeaderText {
|
||||
color: @default-interface-txt;
|
||||
flex: 1 0 auto;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.JobResults-panelHeaderButtonActions {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.JobResults-resultRow {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
padding-bottom: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.JobResults-resultRow--variables {
|
||||
flex-direction: column;
|
||||
|
||||
#cm-variables-container {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.JobResults-resultRowLabel {
|
||||
text-transform: uppercase;
|
||||
color: @default-interface-txt;
|
||||
font-size: 12px;
|
||||
font-weight: normal!important;
|
||||
width: 30%;
|
||||
margin-right: 20px;
|
||||
|
||||
@media screen and (max-width: @breakpoint-md) {
|
||||
flex: 2.5 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.JobResults-resultRowLabel--fullWidth {
|
||||
width: 100%;
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.JobResults-resultRowText {
|
||||
width: ~"calc(70% - 20px)";
|
||||
flex: 1 0 auto;
|
||||
text-transform: none;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.JobResults-resultRowText--fullWidth {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.JobResults-expandArrow {
|
||||
color: #D7D7D7;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
text-transform: uppercase;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.JobResults-resultRowText--instanceGroup {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.JobResults-isolatedBadge {
|
||||
align-items: center;
|
||||
background-color: @default-list-header-bg;
|
||||
border-radius: 5px;
|
||||
color: @default-stdout-txt;
|
||||
display: flex;
|
||||
font-size: 10px;
|
||||
height: 16px;
|
||||
margin: 3px 0 0 10px;
|
||||
padding: 0 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.JobResults-statusResultIcon {
|
||||
padding-left: 0px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.JobResults-badgeRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.JobResults-badgeTitle{
|
||||
color: @default-interface-txt;
|
||||
font-size: 14px;
|
||||
margin-right: 10px;
|
||||
font-weight: normal;
|
||||
text-transform: uppercase;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: @breakpoint-md) {
|
||||
.JobResults-detailsPanel {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.JobResults-rightSide {
|
||||
height: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.JobResults-timeBadge {
|
||||
float:right;
|
||||
font-size: 11px;
|
||||
font-weight: normal;
|
||||
padding: 1px 10px;
|
||||
height: 14px;
|
||||
margin: 3px 15px;
|
||||
width: 80px;
|
||||
background-color: @default-bg;
|
||||
border-radius: 5px;
|
||||
color: @default-interface-txt;
|
||||
margin-right: -5px;
|
||||
}
|
||||
|
||||
.JobResults-panelRight {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.JobResults-panelRight .SmartSearch-bar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.JobResults-panelRightTitle{
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.JobResults-panelRightTitleText{
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.JobResults-badgeAndActionRow{
|
||||
display:flex;
|
||||
flex: 1 0 auto;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.StandardOut-panelHeader {
|
||||
flex: initial;
|
||||
}
|
||||
|
||||
.StandardOut-panelHeader--jobIsRunning {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
host-status-bar {
|
||||
flex: initial;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
smart-search {
|
||||
flex: initial;
|
||||
}
|
||||
|
||||
job-results-standard-out {
|
||||
flex: 1;
|
||||
flex-basis: auto;
|
||||
height: ~"calc(100% - 800px)";
|
||||
display: flex;
|
||||
border: 1px solid @d7grey;
|
||||
border-radius: 5px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
@media screen and (max-width: @breakpoint-md) {
|
||||
job-results-standard-out {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.JobResults-extraVarsHelp {
|
||||
margin-left: 10px;
|
||||
color: @default-icon;
|
||||
}
|
||||
|
||||
.JobResults-seeMoreLess {
|
||||
color: #337AB7;
|
||||
margin: 4px 0px;
|
||||
text-transform: uppercase;
|
||||
padding: 2px 0px;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
font-size: 11px;
|
||||
}
|
||||
@ -1,784 +0,0 @@
|
||||
export default ['jobData', 'jobDataOptions', 'jobLabels', 'jobFinished', 'count', '$scope', 'ParseTypeChange',
|
||||
'ParseVariableString', 'jobResultsService', 'eventQueue', '$compile', '$log', 'Dataset', '$q',
|
||||
'QuerySet', '$rootScope', 'moment', '$stateParams', 'i18n', 'fieldChoices', 'fieldLabels',
|
||||
'workflowResultsService', 'statusSocket', 'GetBasePath', '$state', 'jobExtraCredentials',
|
||||
function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTypeChange,
|
||||
ParseVariableString, jobResultsService, eventQueue, $compile, $log, Dataset, $q,
|
||||
QuerySet, $rootScope, moment, $stateParams, i18n, fieldChoices, fieldLabels,
|
||||
workflowResultsService, statusSocket, GetBasePath, $state, jobExtraCredentials) {
|
||||
|
||||
var toDestroy = [];
|
||||
var cancelRequests = false;
|
||||
var runTimeElapsedTimer = null;
|
||||
|
||||
// download stdout tooltip text
|
||||
$scope.standardOutTooltip = i18n._('Download Output');
|
||||
|
||||
// stdout full screen toggle tooltip text
|
||||
$scope.toggleStdoutFullscreenTooltip = i18n._("Expand Output");
|
||||
|
||||
// this allows you to manage the timing of rest-call based events as
|
||||
// filters are updated. see processPage for more info
|
||||
var currentContext = 1;
|
||||
$scope.firstCounterFromSocket = -1;
|
||||
|
||||
$scope.explanationLimit = 150;
|
||||
|
||||
// if the user enters the page mid-run, reset the search to include a param
|
||||
// to only grab events less than the first counter from the websocket events
|
||||
toDestroy.push($scope.$watch('firstCounterFromSocket', function(counter) {
|
||||
if (counter > -1) {
|
||||
// make it so that the search include a counter less than the
|
||||
// first counter from the socket
|
||||
let params = _.cloneDeep($stateParams.job_event_search);
|
||||
params.counter__lte = "" + counter;
|
||||
|
||||
Dataset = QuerySet.search(jobData.related.job_events,
|
||||
params);
|
||||
|
||||
Dataset.then(function(actualDataset) {
|
||||
$scope.job_event_dataset = actualDataset.data;
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
// used for tag search
|
||||
$scope.job_event_dataset = Dataset.data;
|
||||
|
||||
// used for tag search
|
||||
$scope.list = {
|
||||
basePath: jobData.related.job_events,
|
||||
name: 'job_events'
|
||||
};
|
||||
|
||||
// used for tag search
|
||||
$scope.job_events = $scope.job_event_dataset.results;
|
||||
|
||||
$scope.jobExtraCredentials = jobExtraCredentials;
|
||||
|
||||
var getLinks = function() {
|
||||
var getLink = function(key) {
|
||||
if(key === 'schedule') {
|
||||
if($scope.job.related.schedule) {
|
||||
return '/#/templates/job_template/' + $scope.job.job_template + '/schedules' + $scope.job.related.schedule.split(/api\/v\d+\/schedules/)[1];
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else if(key === 'inventory') {
|
||||
if($scope.job.summary_fields.inventory && $scope.job.summary_fields.inventory.id) {
|
||||
if($scope.job.summary_fields.inventory.kind && $scope.job.summary_fields.inventory.kind === 'smart') {
|
||||
return '/#/inventories/smart/' + $scope.job.summary_fields.inventory.id;
|
||||
}
|
||||
else {
|
||||
return '/#/inventories/inventory/' + $scope.job.summary_fields.inventory.id;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ($scope.job.related[key]) {
|
||||
return '/#/' + $scope.job.related[key]
|
||||
.split(/api\/v\d+\//)[1];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$scope.created_by_link = getLink('created_by');
|
||||
$scope.scheduled_by_link = getLink('schedule');
|
||||
$scope.inventory_link = getLink('inventory');
|
||||
$scope.project_link = getLink('project');
|
||||
$scope.machine_credential_link = getLink('credential');
|
||||
$scope.cloud_credential_link = getLink('cloud_credential');
|
||||
$scope.network_credential_link = getLink('network_credential');
|
||||
$scope.vault_credential_link = getLink('vault_credential');
|
||||
$scope.schedule_link = getLink('schedule');
|
||||
};
|
||||
|
||||
// uses options to set scope variables to their readable string
|
||||
// value
|
||||
var getLabels = function() {
|
||||
var getLabel = function(key) {
|
||||
if ($scope.jobOptions && $scope.jobOptions[key]) {
|
||||
return $scope.jobOptions[key].choices
|
||||
.filter(val => val[0] === $scope.job[key])
|
||||
.map(val => val[1])[0];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.type_label = getLabel('job_type');
|
||||
$scope.verbosity_label = getLabel('verbosity');
|
||||
};
|
||||
|
||||
var getTotalHostCount = function(count) {
|
||||
return Object
|
||||
.keys(count).reduce((acc, i) => acc += count[i], 0);
|
||||
};
|
||||
|
||||
// put initially resolved request data on scope
|
||||
$scope.job = jobData;
|
||||
$scope.jobOptions = jobDataOptions.actions.GET;
|
||||
$scope.labels = jobLabels;
|
||||
$scope.jobFinished = jobFinished;
|
||||
|
||||
// update label in left pane and tooltip in right pane when the job_status
|
||||
// changes
|
||||
toDestroy.push($scope.$watch('job_status', function(status) {
|
||||
if (status) {
|
||||
$scope.status_label = $scope.jobOptions.status.choices
|
||||
.filter(val => val[0] === status)
|
||||
.map(val => val[1])[0];
|
||||
$scope.status_tooltip = "Job " + $scope.status_label;
|
||||
}
|
||||
}));
|
||||
|
||||
$scope.previousTaskFailed = false;
|
||||
|
||||
toDestroy.push($scope.$watch('job.job_explanation', function(explanation) {
|
||||
if (explanation && explanation.split(":")[0] === "Previous Task Failed") {
|
||||
$scope.previousTaskFailed = true;
|
||||
|
||||
var taskObj = JSON.parse(explanation.substring(explanation.split(":")[0].length + 1));
|
||||
// return a promise from the options request with the permission type choices (including adhoc) as a param
|
||||
var fieldChoice = fieldChoices({
|
||||
$scope: $scope,
|
||||
url: GetBasePath('unified_jobs'),
|
||||
field: 'type'
|
||||
});
|
||||
|
||||
// manipulate the choices from the options request to be set on
|
||||
// scope and be usable by the list form
|
||||
fieldChoice.then(function (choices) {
|
||||
choices =
|
||||
fieldLabels({
|
||||
choices: choices
|
||||
});
|
||||
$scope.explanation_fail_type = choices[taskObj.job_type];
|
||||
$scope.explanation_fail_name = taskObj.job_name;
|
||||
$scope.explanation_fail_id = taskObj.job_id;
|
||||
$scope.task_detail = $scope.explanation_fail_type + " failed for " + $scope.explanation_fail_name + " with ID " + $scope.explanation_fail_id + ".";
|
||||
});
|
||||
} else {
|
||||
$scope.previousTaskFailed = false;
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
// update the job_status value. Use the cached rootScope value if there
|
||||
// is one. This is a workaround when the rest call for the jobData is
|
||||
// made before some socket events come in for the job status
|
||||
if ($rootScope['lastSocketStatus' + jobData.id]) {
|
||||
$scope.job_status = $rootScope['lastSocketStatus' + jobData.id];
|
||||
delete $rootScope['lastSocketStatus' + jobData.id];
|
||||
} else {
|
||||
$scope.job_status = jobData.status;
|
||||
}
|
||||
|
||||
// turn related api browser routes into front end routes
|
||||
getLinks();
|
||||
|
||||
// the links below can't be set in getLinks because the
|
||||
// links on the UI don't directly match the corresponding URL
|
||||
// on the API browser
|
||||
if(jobData.summary_fields && jobData.summary_fields.job_template &&
|
||||
jobData.summary_fields.job_template.id){
|
||||
$scope.job_template_link = `/#/templates/job_template/${$scope.job.summary_fields.job_template.id}`;
|
||||
}
|
||||
if(jobData.summary_fields && jobData.summary_fields.project_update &&
|
||||
jobData.summary_fields.project_update.status){
|
||||
$scope.project_status = jobData.summary_fields.project_update.status;
|
||||
}
|
||||
if(jobData.summary_fields && jobData.summary_fields.project_update &&
|
||||
jobData.summary_fields.project_update.id){
|
||||
$scope.project_update_link = `/#/scm_update/${jobData.summary_fields.project_update.id}`;
|
||||
}
|
||||
if(jobData.summary_fields && jobData.summary_fields.source_workflow_job &&
|
||||
jobData.summary_fields.source_workflow_job.id){
|
||||
$scope.workflow_result_link = `/#/workflows/${jobData.summary_fields.source_workflow_job.id}`;
|
||||
}
|
||||
if(jobData.result_traceback) {
|
||||
$scope.job.result_traceback = jobData.result_traceback.trim().split('\n').join('<br />');
|
||||
}
|
||||
|
||||
// use options labels to manipulate display of details
|
||||
getLabels();
|
||||
|
||||
// set up a read only code mirror for extra vars
|
||||
$scope.variables = ParseVariableString($scope.job.extra_vars);
|
||||
$scope.parseType = 'yaml';
|
||||
ParseTypeChange({ scope: $scope,
|
||||
field_id: 'pre-formatted-variables',
|
||||
readOnly: true });
|
||||
|
||||
// Click binding for the expand/collapse button on the standard out log
|
||||
$scope.stdoutFullScreen = false;
|
||||
$scope.toggleStdoutFullscreen = function() {
|
||||
$scope.stdoutFullScreen = !$scope.stdoutFullScreen;
|
||||
|
||||
if ($scope.stdoutFullScreen === true) {
|
||||
$scope.toggleStdoutFullscreenTooltip = i18n._("Collapse Output");
|
||||
} else if ($scope.stdoutFullScreen === false) {
|
||||
$scope.toggleStdoutFullscreenTooltip = i18n._("Expand Output");
|
||||
}
|
||||
};
|
||||
|
||||
$scope.deleteJob = function() {
|
||||
jobResultsService.deleteJob($scope.job);
|
||||
};
|
||||
|
||||
$scope.cancelJob = function() {
|
||||
jobResultsService.cancelJob($scope.job);
|
||||
};
|
||||
|
||||
$scope.lessLabels = false;
|
||||
$scope.toggleLessLabels = function() {
|
||||
if (!$scope.lessLabels) {
|
||||
$('#job-results-labels').slideUp(200);
|
||||
$scope.lessLabels = true;
|
||||
}
|
||||
else {
|
||||
$('#job-results-labels').slideDown(200);
|
||||
$scope.lessLabels = false;
|
||||
}
|
||||
};
|
||||
|
||||
// get initial count from resolve
|
||||
$scope.count = count.val;
|
||||
$scope.hostCount = getTotalHostCount(count.val);
|
||||
$scope.countFinished = count.countFinished;
|
||||
|
||||
// if the job is still running engage following of the last line in the
|
||||
// standard out pane
|
||||
$scope.followEngaged = !$scope.jobFinished;
|
||||
|
||||
// follow button for completed job should specify that the
|
||||
// button will jump to the bottom of the standard out pane,
|
||||
// not follow lines as they come in
|
||||
if ($scope.jobFinished) {
|
||||
$scope.followTooltip = i18n._("Jump to last line of standard out.");
|
||||
} else {
|
||||
$scope.followTooltip = i18n._("Currently following standard out as it comes in. Click to unfollow.");
|
||||
}
|
||||
|
||||
$scope.events = {};
|
||||
|
||||
function updateJobElapsedTimer(time) {
|
||||
$scope.job.elapsed = time;
|
||||
}
|
||||
|
||||
// For elapsed time while a job is running, compute the differnce in seconds,
|
||||
// from the time the job started until now. Moment() returns the current
|
||||
// time as a moment object.
|
||||
if ($scope.job.started !== null && $scope.job.status === 'running') {
|
||||
runTimeElapsedTimer = workflowResultsService.createOneSecondTimer($scope.job.started, updateJobElapsedTimer);
|
||||
}
|
||||
|
||||
// EVENT STUFF BELOW
|
||||
var linesInPane = [];
|
||||
|
||||
function addToLinesInPane(event) {
|
||||
var arr = _.range(event.start_line, event.actual_end_line);
|
||||
linesInPane = linesInPane.concat(arr);
|
||||
linesInPane = linesInPane.sort(function(a, b) {
|
||||
return a - b;
|
||||
});
|
||||
}
|
||||
|
||||
function appendToBottom (event){
|
||||
// if we get here then the event type was either a
|
||||
// header line, recap line, or one of the additional
|
||||
// event types, so we append it to the bottom.
|
||||
// These are the event types for captured
|
||||
// stdout not directly related to playbook or runner
|
||||
// events:
|
||||
// (0, 'debug', _('Debug'), False),
|
||||
// (0, 'verbose', _('Verbose'), False),
|
||||
// (0, 'deprecated', _('Deprecated'), False),
|
||||
// (0, 'warning', _('Warning'), False),
|
||||
// (0, 'system_warning', _('System Warning'), False),
|
||||
// (0, 'error', _('Error'), True),
|
||||
angular
|
||||
.element(".JobResultsStdOut-stdoutContainer")
|
||||
.append($compile(event
|
||||
.stdout)($scope.events[event
|
||||
.counter]));
|
||||
}
|
||||
|
||||
function putInCorrectPlace(event) {
|
||||
if (linesInPane.length) {
|
||||
for (var i = linesInPane.length - 1; i >= 0; i--) {
|
||||
if (event.start_line > linesInPane[i]) {
|
||||
$(`.line_num_${linesInPane[i]}`)
|
||||
.after($compile(event
|
||||
.stdout)($scope.events[event
|
||||
.counter]));
|
||||
i = -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
appendToBottom(event);
|
||||
}
|
||||
}
|
||||
|
||||
// This is where the async updates to the UI actually happen.
|
||||
// Flow is event queue munging in the service -> $scope setting in here
|
||||
var processEvent = function(event, context) {
|
||||
// only care about filter context checking when the event comes
|
||||
// as a rest call
|
||||
if (context && context !== currentContext) {
|
||||
return;
|
||||
}
|
||||
// put the event in the queue
|
||||
var mungedEvent = eventQueue.populate(event);
|
||||
|
||||
// make changes to ui based on the event returned from the queue
|
||||
if (mungedEvent.changes) {
|
||||
mungedEvent.changes.forEach(change => {
|
||||
// we've got a change we need to make to the UI!
|
||||
// update the necessary scope and make the change
|
||||
if (change === 'startTime' && !$scope.job.start) {
|
||||
$scope.job.start = mungedEvent.startTime;
|
||||
}
|
||||
|
||||
if (change === 'count' && !$scope.countFinished) {
|
||||
// for all events that affect the host count,
|
||||
// update the status bar as well as the host
|
||||
// count badge
|
||||
$scope.count = mungedEvent.count;
|
||||
$scope.hostCount = getTotalHostCount(mungedEvent
|
||||
.count);
|
||||
}
|
||||
|
||||
if (change === 'finishedTime' && !$scope.job.finished) {
|
||||
$scope.job.finished = mungedEvent.finishedTime;
|
||||
$scope.jobFinished = true;
|
||||
$scope.followTooltip = i18n._("Jump to last line of standard out.");
|
||||
if ($scope.followEngaged) {
|
||||
if (!$scope.followScroll) {
|
||||
$scope.followScroll = function() {
|
||||
$log.error("follow scroll undefined, standard out directive not loaded yet?");
|
||||
};
|
||||
}
|
||||
$scope.followScroll();
|
||||
}
|
||||
}
|
||||
|
||||
if (change === 'countFinished') {
|
||||
// the playbook_on_stats event actually lets
|
||||
// us know that we don't need to iteratively
|
||||
// look at event to update the host counts
|
||||
// any more.
|
||||
$scope.countFinished = true;
|
||||
}
|
||||
|
||||
if(change === 'stdout'){
|
||||
if (!$scope.events[mungedEvent.counter]) {
|
||||
// line hasn't been put in the pane yet
|
||||
|
||||
// create new child scope
|
||||
$scope.events[mungedEvent.counter] = $scope.$new();
|
||||
$scope.events[mungedEvent.counter]
|
||||
.event = mungedEvent;
|
||||
|
||||
// let's see if we have a specific place to put it in
|
||||
// the pane
|
||||
let $prevElem = $(`.next_is_${mungedEvent.start_line}`);
|
||||
if ($prevElem && $prevElem.length) {
|
||||
// if so, put it there
|
||||
$(`.next_is_${mungedEvent.start_line}`)
|
||||
.after($compile(mungedEvent
|
||||
.stdout)($scope.events[mungedEvent
|
||||
.counter]));
|
||||
addToLinesInPane(mungedEvent);
|
||||
} else {
|
||||
var putIn;
|
||||
var classList = $("div",
|
||||
"<div>"+mungedEvent.stdout+"</div>")
|
||||
.attr("class").split(" ");
|
||||
if (classList
|
||||
.filter(v => v.indexOf("task_") > -1)
|
||||
.length) {
|
||||
putIn = classList
|
||||
.filter(v => v.indexOf("task_") > -1)[0];
|
||||
} else if(classList
|
||||
.filter(v => v.indexOf("play_") > -1)
|
||||
.length) {
|
||||
putIn = classList
|
||||
.filter(v => v.indexOf("play_") > -1)[0];
|
||||
}
|
||||
|
||||
var putAfter;
|
||||
var isDup = false;
|
||||
|
||||
if ($(".header_" + putIn + ",." + putIn).length === 0) {
|
||||
putInCorrectPlace(mungedEvent);
|
||||
addToLinesInPane(mungedEvent);
|
||||
} else {
|
||||
$(".header_" + putIn + ",." + putIn)
|
||||
.each((i, v) => {
|
||||
if (angular.element(v).scope()
|
||||
.event.start_line < mungedEvent
|
||||
.start_line) {
|
||||
putAfter = v;
|
||||
} else if (angular.element(v).scope()
|
||||
.event.start_line === mungedEvent
|
||||
.start_line) {
|
||||
isDup = true;
|
||||
return false;
|
||||
} else if (angular.element(v).scope()
|
||||
.event.start_line > mungedEvent
|
||||
.start_line) {
|
||||
return false;
|
||||
} else {
|
||||
appendToBottom(mungedEvent);
|
||||
addToLinesInPane(mungedEvent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!isDup && putAfter) {
|
||||
addToLinesInPane(mungedEvent);
|
||||
$(putAfter).after($compile(mungedEvent
|
||||
.stdout)($scope.events[mungedEvent
|
||||
.counter]));
|
||||
}
|
||||
|
||||
|
||||
classList = null;
|
||||
putIn = null;
|
||||
}
|
||||
|
||||
// delete ref to the elem because it might leak scope
|
||||
// if you don't
|
||||
$prevElem = null;
|
||||
}
|
||||
|
||||
// move the followAnchor to the bottom of the
|
||||
// container
|
||||
$(".JobResultsStdOut-followAnchor")
|
||||
.appendTo(".JobResultsStdOut-stdoutContainer");
|
||||
}
|
||||
});
|
||||
|
||||
// the changes have been processed in the ui, mark it in the
|
||||
// queue
|
||||
eventQueue.markProcessed(event);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.stdoutContainerAvailable = $q.defer();
|
||||
$scope.hasSkeleton = $q.defer();
|
||||
|
||||
eventQueue.initialize();
|
||||
|
||||
$scope.playCount = 0;
|
||||
$scope.taskCount = 0;
|
||||
|
||||
|
||||
// used to show a message to just download for old jobs
|
||||
// remove in 3.2.0
|
||||
$scope.isOld = 0;
|
||||
$scope.showLegacyJobErrorMessage = false;
|
||||
|
||||
toDestroy.push($scope.$watch('isOld', function (val) {
|
||||
if (val >= 2) {
|
||||
$scope.showLegacyJobErrorMessage = true;
|
||||
}
|
||||
}));
|
||||
|
||||
// get header and recap lines
|
||||
var skeletonPlayCount = 0;
|
||||
var skeletonTaskCount = 0;
|
||||
var getSkeleton = function(url) {
|
||||
jobResultsService.getEvents(url)
|
||||
.then(events => {
|
||||
events.results.forEach(event => {
|
||||
if (event.start_line === 0 && event.end_line === 0) {
|
||||
$scope.isOld++;
|
||||
}
|
||||
// get the name in the same format as the data
|
||||
// coming over the websocket
|
||||
event.event_name = event.event;
|
||||
delete event.event;
|
||||
|
||||
// increment play and task count
|
||||
if (event.event_name === "playbook_on_play_start") {
|
||||
skeletonPlayCount++;
|
||||
} else if (event.event_name === "playbook_on_task_start") {
|
||||
skeletonTaskCount++;
|
||||
}
|
||||
|
||||
processEvent(event);
|
||||
});
|
||||
if (events.next) {
|
||||
getSkeleton(events.next);
|
||||
} else {
|
||||
// after the skeleton requests have completed,
|
||||
// put the play and task count into the dom
|
||||
$scope.playCount = skeletonPlayCount;
|
||||
$scope.taskCount = skeletonTaskCount;
|
||||
$scope.hasSkeleton.resolve("skeleton resolved");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.stdoutContainerAvailable.promise.then(() => {
|
||||
getSkeleton(jobData.related.job_events + "?order_by=start_line&or__event__in=playbook_on_start,playbook_on_play_start,playbook_on_task_start,playbook_on_stats");
|
||||
});
|
||||
|
||||
var getEvents;
|
||||
|
||||
var processPage = function(events, context) {
|
||||
// currentContext is the context of the filter when this request
|
||||
// to processPage was made
|
||||
//
|
||||
// currentContext is the context of the filter currently
|
||||
//
|
||||
// if they are not the same, make sure to stop process events/
|
||||
// making rest calls for next pages/etc. (you can see context is
|
||||
// also passed into getEvents and processEvent and similar checks
|
||||
// exist in these functions)
|
||||
//
|
||||
// also, if the page doesn't contain results (i.e.: the response
|
||||
// returns an error), don't process the page
|
||||
if (context !== currentContext || events === undefined ||
|
||||
events.results === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
events.results.forEach(event => {
|
||||
// get the name in the same format as the data
|
||||
// coming over the websocket
|
||||
event.event_name = event.event;
|
||||
delete event.event;
|
||||
|
||||
processEvent(event, context);
|
||||
});
|
||||
if (events.next && !cancelRequests) {
|
||||
getEvents(events.next, context);
|
||||
} else {
|
||||
// put those paused events into the pane
|
||||
$scope.gotPreviouslyRanEvents.resolve("");
|
||||
}
|
||||
};
|
||||
|
||||
// grab non-header recap lines
|
||||
getEvents = function(url, context) {
|
||||
if (context !== currentContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
jobResultsService.getEvents(url)
|
||||
.then(events => {
|
||||
processPage(events, context);
|
||||
});
|
||||
};
|
||||
|
||||
// grab non-header recap lines
|
||||
toDestroy.push($scope.$watch('job_event_dataset', function(val) {
|
||||
if (val) {
|
||||
eventQueue.initialize();
|
||||
|
||||
Object.keys($scope.events)
|
||||
.forEach(v => {
|
||||
// dont destroy scope events for skeleton lines
|
||||
let name = $scope.events[v].event.name;
|
||||
|
||||
if (!(name === "playbook_on_play_start" ||
|
||||
name === "playbook_on_task_start" ||
|
||||
name === "playbook_on_stats")) {
|
||||
$scope.events[v].$destroy();
|
||||
$scope.events[v] = null;
|
||||
delete $scope.events[v];
|
||||
}
|
||||
});
|
||||
|
||||
// pause websocket events from coming in to the pane
|
||||
$scope.gotPreviouslyRanEvents = $q.defer();
|
||||
currentContext += 1;
|
||||
|
||||
let context = currentContext;
|
||||
|
||||
$( ".JobResultsStdOut-aLineOfStdOut.not_skeleton" ).remove();
|
||||
$scope.hasSkeleton.promise.then(() => {
|
||||
if (val.count > parseInt(val.maxEvents)) {
|
||||
$(".header_task").hide();
|
||||
$(".header_play").hide();
|
||||
$scope.standardOutTooltip = '<div class="JobResults-downloadTooLarge"><div>' +
|
||||
i18n._('The output is too large to display. Please download.') +
|
||||
'</div>' +
|
||||
'<div class="JobResults-downloadTooLarge--icon">' +
|
||||
'<span class="fa-stack fa-lg">' +
|
||||
'<i class="fa fa-circle fa-stack-1x"></i>' +
|
||||
'<i class="fa fa-stack-1x icon-job-stdout-download-tooltip"></i>' +
|
||||
'</span>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
|
||||
if ($scope.job_status === "successful" ||
|
||||
$scope.job_status === "failed" ||
|
||||
$scope.job_status === "error" ||
|
||||
$scope.job_status === "canceled") {
|
||||
$scope.tooManyEvents = true;
|
||||
$scope.tooManyPastEvents = false;
|
||||
} else {
|
||||
$scope.tooManyPastEvents = true;
|
||||
$scope.tooManyEvents = false;
|
||||
$scope.gotPreviouslyRanEvents.resolve("");
|
||||
}
|
||||
} else {
|
||||
$(".header_task").show();
|
||||
$(".header_play").show();
|
||||
$scope.tooManyEvents = false;
|
||||
$scope.tooManyPastEvents = false;
|
||||
processPage(val, context);
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
var buffer = [];
|
||||
|
||||
var processBuffer = function() {
|
||||
var follow = function() {
|
||||
// if follow is engaged,
|
||||
// scroll down to the followAnchor
|
||||
if ($scope.followEngaged) {
|
||||
if (!$scope.followScroll) {
|
||||
$scope.followScroll = function() {
|
||||
$log.error("follow scroll undefined, standard out directive not loaded yet?");
|
||||
};
|
||||
}
|
||||
$scope.followScroll();
|
||||
}
|
||||
};
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
processEvent(buffer[i]);
|
||||
buffer.splice(i, 1);
|
||||
}
|
||||
|
||||
follow();
|
||||
};
|
||||
|
||||
var bufferInterval;
|
||||
|
||||
// Processing of job_events messages from the websocket
|
||||
toDestroy.push($scope.$on(`ws-job_events-${$scope.job.id}`, function(e, data) {
|
||||
if (!bufferInterval) {
|
||||
bufferInterval = setInterval(function(){
|
||||
processBuffer();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// use the lowest counter coming over the socket to retrigger pull data
|
||||
// to only be for stuff lower than that id
|
||||
//
|
||||
// only do this for entering the jobs page mid-run (thus the
|
||||
// data.counter is 1 conditional
|
||||
if (data.counter === 1) {
|
||||
$scope.firstCounterFromSocket = -2;
|
||||
}
|
||||
|
||||
if ($scope.firstCounterFromSocket !== -2 &&
|
||||
$scope.firstCounterFromSocket === -1 ||
|
||||
data.counter < $scope.firstCounterFromSocket) {
|
||||
$scope.firstCounterFromSocket = data.counter;
|
||||
}
|
||||
|
||||
$q.all([$scope.gotPreviouslyRanEvents.promise,
|
||||
$scope.hasSkeleton.promise]).then(() => {
|
||||
// put the line in the
|
||||
// standard out pane (and increment play and task
|
||||
// count if applicable)
|
||||
if (data.event_name === "playbook_on_play_start") {
|
||||
$scope.playCount++;
|
||||
} else if (data.event_name === "playbook_on_task_start") {
|
||||
$scope.taskCount++;
|
||||
}
|
||||
buffer.push(data);
|
||||
});
|
||||
}));
|
||||
|
||||
// get previously set up socket messages from resolve
|
||||
if (statusSocket && statusSocket[0] && statusSocket[0].job_status) {
|
||||
$scope.job_status = statusSocket[0].job_status;
|
||||
}
|
||||
if ($scope.job_status === "running" && !$scope.job.elapsed) {
|
||||
runTimeElapsedTimer = workflowResultsService.createOneSecondTimer(moment(), updateJobElapsedTimer);
|
||||
}
|
||||
|
||||
// Processing of job-status messages from the websocket
|
||||
toDestroy.push($scope.$on(`ws-jobs`, function(e, data) {
|
||||
if (parseInt(data.unified_job_id, 10) ===
|
||||
parseInt($scope.job.id,10)) {
|
||||
// controller is defined, so set the job_status
|
||||
$scope.job_status = data.status;
|
||||
if(_.has(data, 'instance_group_name')){
|
||||
$scope.job.instance_group = true;
|
||||
$scope.job.summary_fields.instance_group = {
|
||||
"name": data.instance_group_name
|
||||
};
|
||||
}
|
||||
if (data.status === "running") {
|
||||
if (!runTimeElapsedTimer) {
|
||||
runTimeElapsedTimer = workflowResultsService.createOneSecondTimer(moment(), updateJobElapsedTimer);
|
||||
}
|
||||
} else if (data.status === "successful" ||
|
||||
data.status === "failed" ||
|
||||
data.status === "error" ||
|
||||
data.status === "canceled") {
|
||||
workflowResultsService.destroyTimer(runTimeElapsedTimer);
|
||||
|
||||
// When the fob is finished retrieve the job data to
|
||||
// correct anything that was out of sync from the job run
|
||||
jobResultsService.getJobData($scope.job.id).then(function(data){
|
||||
$scope.job = data;
|
||||
$scope.jobFinished = true;
|
||||
});
|
||||
}
|
||||
} else if (parseInt(data.project_id, 10) ===
|
||||
parseInt($scope.job.project,10)) {
|
||||
// this is a project status update message, so set the
|
||||
// project status in the left pane
|
||||
$scope.project_status = data.status;
|
||||
$scope.project_update_link = `/#/scm_update/${data
|
||||
.unified_job_id}`;
|
||||
} else {
|
||||
// controller was previously defined, but is not yet defined
|
||||
// for this job. cache the socket status on root scope
|
||||
$rootScope['lastSocketStatus' + data.unified_job_id] = data.status;
|
||||
}
|
||||
}));
|
||||
|
||||
if (statusSocket && statusSocket[1]) {
|
||||
statusSocket[1]();
|
||||
}
|
||||
|
||||
$scope.$on('$destroy', function(){
|
||||
if (statusSocket && statusSocket[1]) {
|
||||
statusSocket[1]();
|
||||
}
|
||||
$( ".JobResultsStdOut-aLineOfStdOut" ).remove();
|
||||
cancelRequests = true;
|
||||
eventQueue.initialize();
|
||||
Object.keys($scope.events)
|
||||
.forEach(v => {
|
||||
$scope.events[v].$destroy();
|
||||
$scope.events[v] = null;
|
||||
});
|
||||
$scope.events = {};
|
||||
workflowResultsService.destroyTimer(runTimeElapsedTimer);
|
||||
if (bufferInterval) {
|
||||
clearInterval(bufferInterval);
|
||||
}
|
||||
toDestroy.forEach(closureFunc => closureFunc());
|
||||
});
|
||||
}];
|
||||
@ -1,566 +0,0 @@
|
||||
<div class="tab-pane" id="job-results">
|
||||
<div ng-cloak
|
||||
id="htmlTemplate"
|
||||
class="JobResults"
|
||||
ng-class="{'fullscreen': stdoutFullScreen}">
|
||||
<div ui-view></div>
|
||||
|
||||
<!-- LEFT PANE -->
|
||||
<div class="JobResults-leftSide"
|
||||
ng-class="{'JobResults-stdoutActionButton--active': stdoutFullScreen}">
|
||||
<div class="JobResults-detailsPanel Panel"
|
||||
ng-show="!stdoutFullScreen">
|
||||
|
||||
<!-- LEFT PANE HEADER -->
|
||||
<div class="JobResults-panelHeader">
|
||||
<div
|
||||
class="JobResults-panelHeaderText" translate>
|
||||
DETAILS
|
||||
</div>
|
||||
|
||||
<!-- LEFT PANE HEADER ACTIONS -->
|
||||
<div class="JobResults-panelHeaderButtonActions">
|
||||
|
||||
<!-- RELAUNCH ACTION -->
|
||||
<at-relaunch job="job"></at-relaunch>
|
||||
|
||||
<!-- CANCEL ACTION -->
|
||||
<button class="List-actionButton
|
||||
List-actionButton--delete"
|
||||
data-placement="top"
|
||||
ng-click="cancelJob()"
|
||||
ng-show="job_status == 'running' ||
|
||||
job_status=='pending' "
|
||||
aw-tool-tip="{{'Cancel' | translate}}"
|
||||
data-original-title="" title="">
|
||||
<i class="fa fa-minus-circle"></i>
|
||||
</button>
|
||||
|
||||
<!-- DELETE ACTION -->
|
||||
<button class="List-actionButton
|
||||
List-actionButton--delete"
|
||||
data-placement="top"
|
||||
ng-click="deleteJob()"
|
||||
ng-hide="job_status == 'running' ||
|
||||
job_status == 'pending' || !job.summary_fields.user_capabilities.delete"
|
||||
aw-tool-tip="{{'Delete' | translate}}"
|
||||
data-original-title=""
|
||||
title="">
|
||||
<i class="fa fa-trash-o"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- LEFT PANE DETAILS GROUP -->
|
||||
<div>
|
||||
|
||||
<!-- STATUS DETAIL -->
|
||||
<div class="JobResults-resultRow">
|
||||
<label class="JobResults-resultRowLabel" translate>
|
||||
Status
|
||||
</label>
|
||||
<div class="JobResults-resultRowText">
|
||||
<i class="JobResults-statusResultIcon
|
||||
fa
|
||||
icon-job-{{ job_status }}">
|
||||
</i> {{ status_label | translate }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EXPLANATION DETAIL -->
|
||||
<div class="JobResults-resultRow"
|
||||
ng-show="job.job_explanation">
|
||||
<label class="JobResults-resultRowLabel" translate>
|
||||
Explanation
|
||||
</label>
|
||||
<div class="JobResults-resultRowText" ng-show="!previousTaskFailed">
|
||||
{{ job.job_explanation }}
|
||||
</div>
|
||||
<div class="JobResults-resultRowText">
|
||||
{{task_detail | limitTo:explanationLimit}}
|
||||
<span ng-show="previousTaskFailed && explanationLimit && task_detail.length > explanationLimit">
|
||||
<span>... </span>
|
||||
<span class="JobResults-seeMoreLess" ng-click="explanationLimit=undefined">Show More</span>
|
||||
</span>
|
||||
<span ng-show="explanationLimit === undefined" class="JobResults-seeMoreLess" ng-click="explanationLimit=150">Show Less</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- START TIME DETAIL -->
|
||||
<div class="JobResults-resultRow"
|
||||
ng-show="job.started">
|
||||
<label class="JobResults-resultRowLabel" translate>
|
||||
Started
|
||||
</label>
|
||||
<div class="JobResults-resultRowText">
|
||||
{{ job.started | longDate }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FINISHED TIME DETAIL -->
|
||||
<div class="JobResults-resultRow"
|
||||
ng-show="job.started">
|
||||
<label class="JobResults-resultRowLabel" translate>
|
||||
Finished
|
||||
</label>
|
||||
<div class="JobResults-resultRowText">
|
||||
{{ (job.finished |
|
||||
longDate) || "Not Finished" }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RESULTS TRACEBACK DETAIL -->
|
||||
<div class="JobResults-resultRow"
|
||||
ng-show="job.result_traceback && !previousTaskFailed">
|
||||
<label class="JobResults-resultRowLabel" translate>
|
||||
Results Traceback
|
||||
</label>
|
||||
<div class="JobResults-resultRowText"
|
||||
ng-bind-html="job.result_traceback">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- TEMPLATE DETAIL -->
|
||||
<div class="JobResults-resultRow"
|
||||
ng-show="job.summary_fields.job_template.name">
|
||||
<label class="JobResults-resultRowLabel" translate>
|
||||
Template
|
||||
</label>
|
||||
<div class="JobResults-resultRowText">
|
||||
<a href="{{ job_template_link }}"
|
||||
aw-tool-tip="{{'Edit the job template' | translate}}"
|
||||
data-placement="top">
|
||||
{{ job.summary_fields.job_template.name }}
|
||||
</a>
|
||||
<a href="{{ workflow_result_link }}"
|
||||
aw-tool-tip="{{'View workflow results' | translate}}"
|
||||
data-placement="top"
|
||||
data-original-title="" title="">
|
||||
<i class="WorkflowBadge"
|
||||
ng-show="job.launch_type === 'workflow' ">
|
||||
W
|
||||
</i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- JOB TYPE DETAIL -->
|
||||
<div class="JobResults-resultRow"
|
||||
ng-show="job.job_type">
|
||||
<label class="JobResults-resultRowLabel" translate>
|
||||
Job Type
|
||||
</label>
|
||||
<div class="JobResults-resultRowText">
|
||||
{{ type_label }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CREATED BY DETAIL -->
|
||||
<div class="JobResults-resultRow"
|
||||
ng-show="job.summary_fields.created_by.username">
|
||||
<label class="JobResults-resultRowLabel" translate>
|
||||
Launched By
|
||||
</label>
|
||||
<div class="JobResults-resultRowText">
|
||||
<a href="{{ created_by_link }}"
|
||||
aw-tool-tip="{{'Edit the User' | translate}}"
|
||||
data-placement="top">
|
||||
{{ job.summary_fields.created_by.username }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SCHEDULED BY DETAIL -->
|
||||
<div class="JobResults-resultRow toggle-show"
|
||||
ng-show="job.summary_fields.schedule.name">
|
||||
<label
|
||||
class="JobResults-resultRowLabel" translate>
|
||||
Launched By
|
||||
</label>
|
||||
<div class="JobResults-resultRowText">
|
||||
<a href="{{ scheduled_by_link }}"
|
||||
aw-tool-tip="{{'Edit the Schedule' | translate}}"
|
||||
data-placement="top">
|
||||
{{ job.summary_fields.schedule.name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- INVENTORY DETAIL -->
|
||||
<div class="JobResults-resultRow"
|
||||
ng-show="job.summary_fields.inventory.name">
|
||||
<label class="JobResults-resultRowLabel" translate>
|
||||
Inventory
|
||||
</label>
|
||||
<div class="JobResults-resultRowText">
|
||||
<a href="{{ inventory_link }}"
|
||||
aw-tool-tip="{{'Edit the inventory' | translate}}"
|
||||
data-placement="top">
|
||||
{{ job.summary_fields.inventory.name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PROJECT DETAIL -->
|
||||
<div class="JobResults-resultRow"
|
||||
ng-show="job.summary_fields.project.name">
|
||||
<label class="JobResults-resultRowLabel" translate>
|
||||
Project
|
||||
</label>
|
||||
<div class="JobResults-resultRowText">
|
||||
<a href="{{ project_update_link }}"
|
||||
ng-hide="job.summary_fields.project.scm_type==='' || !project_status"
|
||||
aw-tool-tip="{{'View project checkout results' | translate}}"
|
||||
data-placement="top">
|
||||
<i class="JobResults-statusResultIcon
|
||||
fa icon-job-{{ project_status }}">
|
||||
</i>
|
||||
</a>
|
||||
<a href="{{ project_link }}"
|
||||
aw-tool-tip="{{'Edit the project' | translate}}"
|
||||
data-placement="top">
|
||||
{{ job.summary_fields.project.name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- REVISION DETAIL -->
|
||||
<div class="JobResults-resultRow"
|
||||
ng-if="job.scm_revision">
|
||||
<label class="JobResults-resultRowLabel" translate>
|
||||
Revision
|
||||
</label>
|
||||
<at-truncate string="{{job.scm_revision}}" maxLength="7" class="JobResults-resultRowText">
|
||||
</at-truncate>
|
||||
</div>
|
||||
|
||||
<!-- PLAYBOOK DETAIL -->
|
||||
<div class="JobResults-resultRow"
|
||||
ng-show="job.playbook">
|
||||
<label class="JobResults-resultRowLabel" translate>
|
||||
Playbook
|
||||
</label>
|
||||
<div class="JobResults-resultRowText">
|
||||
{{ job.playbook }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MACHINE CREDENTIAL DETAIL -->
|
||||
<div class="JobResults-resultRow"
|
||||
ng-show="job.summary_fields.credential.name">
|
||||
<label class="JobResults-resultRowLabel" translate>
|
||||
Machine Credential
|
||||
</label>
|
||||
<div class="JobResults-resultRowText">
|
||||
<a href="{{ machine_credential_link }}"
|
||||
aw-tool-tip="{{'Edit the credential' | translate}}"
|
||||
data-placement="top">
|
||||
{{ job.summary_fields.credential.name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EXTRA CREDENTIALS DETAIL -->
|
||||
<div class="JobResults-resultRow"
|
||||
ng-show="jobExtraCredentials.length > 0">
|
||||
<label class="JobResults-resultRowLabel" translate>
|
||||
Extra Credentials
|
||||
</label>
|
||||
<div class="JobResults-resultRowText">
|
||||
<span ng-repeat="extraCredential in jobExtraCredentials">
|
||||
<a ui-sref="credentials.edit({credential_id: extraCredential.id})" aw-tool-tip="{{'Edit the credential' | translate}}" data-placement="top">
|
||||
{{ extraCredential.name }}
|
||||
</a>
|
||||
{{$last ? '' : ', '}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CLOUD CREDENTIAL DETAIL -->
|
||||
<div class="JobResults-resultRow"
|
||||
ng-show="job.summary_fields.cloud_credential.name">
|
||||
<label class="JobResults-resultRowLabel" translate>
|
||||
Cloud Credential
|
||||
</label>
|
||||
<div class="JobResults-resultRowText">
|
||||
<a href="{{ cloud_credential_link }}"
|
||||
aw-tool-tip="{{'Edit the credential' | translate}}"
|
||||
data-placement="top">
|
||||
{{ job.summary_fields.cloud_credential.name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- NETWORK CREDENTAIL DETAIL -->
|
||||
<div class="JobResults-resultRow"
|
||||
ng-show="job.summary_fields.network_credential.name">
|
||||
<label class="JobResults-resultRowLabel" translate>
|
||||
Network Credential
|
||||
</label>
|
||||
<div class="JobResults-resultRowText">
|
||||
<a href="{{ network_credential_link }}"
|
||||
aw-tool-tip="{{'Edit the credential' | translate}}"
|
||||
data-placement="top">
|
||||
{{ job.summary_fields.network_credential.name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- VAULT CREDENTAIL DETAIL -->
|
||||
<div class="JobResults-resultRow"
|
||||
ng-show="job.summary_fields.vault_credential.name">
|
||||
<label class="JobResults-resultRowLabel" translate>
|
||||
Vault Credential
|
||||
</label>
|
||||
<div class="JobResults-resultRowText">
|
||||
<a href="{{ vault_credential_link }}"
|
||||
aw-tool-tip="{{'Edit the credential' | translate}}"
|
||||
data-placement="top">
|
||||
{{ job.summary_fields.vault_credential.name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FORKS DETAIL -->
|
||||
<div class="JobResults-resultRow"
|
||||
ng-show="job.forks !== undefined">
|
||||
<label class="JobResults-resultRowLabel" translate>
|
||||
Forks
|
||||
</label>
|
||||
<div class="JobResults-resultRowText">
|
||||
{{ job.forks }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- LIMIT DETAIL -->
|
||||
<div class="JobResults-resultRow"
|
||||
ng-show="job.limit">
|
||||
<label class="JobResults-resultRowLabel" translate>
|
||||
Limit
|
||||
</label>
|
||||
<div class="JobResults-resultRowText">
|
||||
{{ job.limit }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- VERBOSITY DETAIL -->
|
||||
<div class="JobResults-resultRow"
|
||||
ng-show="job.verbosity !== undefined">
|
||||
<label class="JobResults-resultRowLabel" translate>
|
||||
Verbosity
|
||||
</label>
|
||||
<div class="JobResults-resultRowText">
|
||||
{{ verbosity_label }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- IG DETAIL -->
|
||||
<div class="JobResults-resultRow"
|
||||
ng-show="job.instance_group">
|
||||
<label class="JobResults-resultRowLabel" translate>
|
||||
Instance Group
|
||||
</label>
|
||||
<div class="JobResults-resultRowText JobResults-resultRowText--instanceGroup">
|
||||
{{ job.summary_fields.instance_group.name }}
|
||||
<span class="JobResults-isolatedBadge"
|
||||
ng-if="job.summary_fields.instance_group && job.summary_fields.instance_group.controller_id">
|
||||
Isolated
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TAGS DETAIL -->
|
||||
<div class="JobResults-resultRow"
|
||||
ng-show="job.job_tags">
|
||||
<label class="JobResults-resultRowLabel" translate>
|
||||
Job Tags
|
||||
</label>
|
||||
<div class="JobResults-resultRowText">
|
||||
{{ job.job_tags }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SKIP TAGS DETAIL -->
|
||||
<div class="JobResults-resultRow"
|
||||
ng-show="job.skip_tags">
|
||||
<label class="JobResults-resultRowLabel" translate>
|
||||
Skip Tags
|
||||
</label>
|
||||
<div class="JobResults-resultRowText">
|
||||
{{ job.skip_tags }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EXTRA VARIABLES DETAIL -->
|
||||
<div class="JobResults-resultRow
|
||||
JobResults-resultRow--variables"
|
||||
ng-show="variables">
|
||||
<label class="JobResults-resultRowLabel
|
||||
JobResults-resultRowLabel--fullWidth">
|
||||
<span translate> Extra Variables </span>
|
||||
<i class="JobResults-extraVarsHelp fa fa-question-circle"
|
||||
aw-tool-tip="{{'Read only view of extra variables added to the job template.' | translate}}"
|
||||
data-placement="top">
|
||||
</i>
|
||||
</label>
|
||||
<textarea
|
||||
rows="6"
|
||||
ng-model="variables"
|
||||
name="variables"
|
||||
class="form-control Form-textArea Form-textAreaLabel Form-formGroup--fullWidth"
|
||||
id="pre-formatted-variables"
|
||||
disabled="disabled">
|
||||
</textarea>
|
||||
</div>
|
||||
|
||||
<!-- LABELS DETAIL -->
|
||||
<div class="JobResults-resultRow"
|
||||
ng-show="labels && labels.length > 0">
|
||||
<div class="JobResults-resultRow">
|
||||
<a class="JobResults-resultRowLabel
|
||||
JobResults-resultRowLabel--fullWidth"
|
||||
ng-show="lessLabels"
|
||||
href=""
|
||||
ng-click="toggleLessLabels()">
|
||||
<span translate>Labels</span>
|
||||
<i class="JobResults-expandArrow
|
||||
fa fa-caret-right"></i>
|
||||
</a>
|
||||
<a class="JobResults-resultRowLabel
|
||||
JobResults-resultRowLabel--fullWidth"
|
||||
ng-show="!lessLabels"
|
||||
href=""
|
||||
ng-click="toggleLessLabels()">
|
||||
<span translate>Labels</span>
|
||||
<i class="JobResults-expandArrow
|
||||
fa fa-caret-down"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div id="job-results-labels" class="LabelList
|
||||
JobResults-resultRowText
|
||||
JobResults-resultRowText--fullWidth">
|
||||
<div ng-repeat="label in labels"
|
||||
class="LabelList-tagContainer">
|
||||
<div class="LabelList-tag">
|
||||
<div class="LabelList-name">
|
||||
{{ label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RIGHT PANE -->
|
||||
<div class="JobResults-rightSide">
|
||||
<div class="Panel JobResults-panelRight">
|
||||
|
||||
<!-- RIGHT PANE HEADER -->
|
||||
<div class="StandardOut-panelHeader JobResults-panelRightTitle">
|
||||
<div class="StandardOut-panelHeaderText JobResults-panelRightTitleText">
|
||||
<i class="JobResults-statusResultIcon
|
||||
fa icon-job-{{ job_status }}"
|
||||
ng-show="stdoutFullScreen"
|
||||
aw-tool-tip="Job {{status_label}}"
|
||||
data-tip-watch="status_tooltip"
|
||||
aw-tip-placement="top"
|
||||
data-original-title>
|
||||
</i>
|
||||
{{ job.name }}
|
||||
</div>
|
||||
<div class="JobResults-badgeAndActionRow">
|
||||
<!-- HEADER COUNTS -->
|
||||
<div class="JobResults-badgeRow">
|
||||
<!-- PLAYS COUNT -->
|
||||
<div class="JobResults-badgeTitle" translate>
|
||||
Plays
|
||||
</div>
|
||||
<span class="badge List-titleBadge">
|
||||
{{ playCount || 0}}
|
||||
</span>
|
||||
|
||||
<!-- TASKS COUNT -->
|
||||
<div class="JobResults-badgeTitle" translate>
|
||||
Tasks
|
||||
</div>
|
||||
<span class="badge List-titleBadge">
|
||||
{{ taskCount || 0}}
|
||||
</span>
|
||||
|
||||
<!-- HOSTS COUNT -->
|
||||
<div class="JobResults-badgeTitle" translate>
|
||||
Hosts
|
||||
</div>
|
||||
<span class="badge List-titleBadge"
|
||||
ng-if="jobFinished">
|
||||
{{ hostCount || 0}}
|
||||
</span>
|
||||
|
||||
<span class="badge List-titleBadge"
|
||||
aw-tool-tip="{{'The host count will update when the job is complete.' | translate}}"
|
||||
data-placement="top"
|
||||
ng-if="!jobFinished">
|
||||
<i class="fa fa-ellipsis-h"></i>
|
||||
</span>
|
||||
|
||||
<!-- ELAPSED TIME -->
|
||||
<div class="JobResults-badgeTitle" translate>
|
||||
Elapsed
|
||||
</div>
|
||||
<span class="badge List-titleBadge">
|
||||
{{ job.elapsed * 1000 | duration: "hh:mm:ss" }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- HEADER ACTIONS -->
|
||||
<div class="StandardOut-panelHeaderActions">
|
||||
|
||||
<!-- FULL-SCREEN TOGGLE ACTION -->
|
||||
<button class="StandardOut-actionButton"
|
||||
aw-tool-tip="{{ toggleStdoutFullscreenTooltip }}"
|
||||
data-tip-watch="toggleStdoutFullscreenTooltip"
|
||||
data-placement="top"
|
||||
ng-class="{'StandardOut-actionButton--active': stdoutFullScreen}"
|
||||
ng-click="toggleStdoutFullscreen()">
|
||||
<i class="fa fa-arrows-alt"></i>
|
||||
</button>
|
||||
|
||||
<!-- DOWNLOAD ACTION -->
|
||||
<a ng-show="job.status === 'failed' ||
|
||||
job.status === 'successful' ||
|
||||
job.status === 'canceled'"
|
||||
href="/api/v2/jobs/{{ job.id }}/stdout?format=txt_download">
|
||||
<button class="StandardOut-actionButton"
|
||||
aw-tool-tip="{{ standardOutTooltip }}"
|
||||
data-tip-watch="standardOutTooltip"
|
||||
data-placement="top">
|
||||
<i class="fa fa-download"></i>
|
||||
</button>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<host-status-bar></host-status-bar>
|
||||
<smart-search
|
||||
django-model="job_events"
|
||||
base-path="{{list.basePath}}"
|
||||
iterator="job_event"
|
||||
list="list"
|
||||
collection="job_events"
|
||||
dataset="job_event_dataset"
|
||||
search-tags="searchTags"
|
||||
disable-search="job_status == 'running' ||
|
||||
job_status=='pending'">
|
||||
</smart-search>
|
||||
<job-results-standard-out></job-results-standard-out>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,187 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import {templateUrl} from '../shared/template-url/template-url.factory';
|
||||
|
||||
const defaultParams = {
|
||||
page_size: "200",
|
||||
order_by: 'start_line',
|
||||
not__event__in: 'playbook_on_start,playbook_on_play_start,playbook_on_task_start,playbook_on_stats'
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'jobResult',
|
||||
url: '/jobs/{id: int}',
|
||||
searchPrefix: 'job_event',
|
||||
ncyBreadcrumb: {
|
||||
parent: 'jobs',
|
||||
label: '{{ job.id }} - {{ job.name }}'
|
||||
},
|
||||
data: {
|
||||
socket: {
|
||||
"groups":{
|
||||
"jobs": ["status_changed", "summary"],
|
||||
"job_events": []
|
||||
}
|
||||
}
|
||||
},
|
||||
params: {
|
||||
job_event_search: {
|
||||
value: defaultParams,
|
||||
dynamic: true,
|
||||
squash: ''
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
statusSocket: ['$rootScope', '$stateParams', function($rootScope, $stateParams) {
|
||||
var preScope = {};
|
||||
var eventOn = $rootScope.$on(`ws-jobs`, function(e, data) {
|
||||
if (parseInt(data.unified_job_id, 10) ===
|
||||
parseInt($stateParams.id,10)) {
|
||||
preScope.job_status = data.status;
|
||||
}
|
||||
});
|
||||
return [preScope, eventOn];
|
||||
}],
|
||||
// the GET for the particular job
|
||||
jobData: ['jobResultsService', '$stateParams', function(jobResultsService, $stateParams) {
|
||||
return jobResultsService.getJobData($stateParams.id);
|
||||
}],
|
||||
Dataset: ['QuerySet', '$stateParams', 'jobData',
|
||||
function(qs, $stateParams, jobData) {
|
||||
let path = jobData.related.job_events;
|
||||
return qs.search(path, $stateParams[`job_event_search`]);
|
||||
}
|
||||
],
|
||||
// used to signify if job is completed or still running
|
||||
jobFinished: ['jobData', function(jobData) {
|
||||
if (jobData.finished) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}],
|
||||
// after the GET for the job, this helps us keep the status bar from
|
||||
// flashing as rest data comes in. If the job is finished and
|
||||
// there's a playbook_on_stats event, go ahead and resolve the count
|
||||
// so you don't get that flashing!
|
||||
count: ['jobData', 'jobResultsService', 'Rest', '$q', '$stateParams', '$state', function(jobData, jobResultsService, Rest, $q, $stateParams, $state) {
|
||||
var defer = $q.defer();
|
||||
if (jobData.finished) {
|
||||
// if the job is finished, grab the playbook_on_stats
|
||||
// role to get the final count
|
||||
Rest.setUrl(jobData.related.job_events +
|
||||
"?event=playbook_on_stats");
|
||||
Rest.get()
|
||||
.then(({data}) => {
|
||||
if(!data.results[0]){
|
||||
defer.resolve({val: {
|
||||
ok: 0,
|
||||
skipped: 0,
|
||||
unreachable: 0,
|
||||
failures: 0,
|
||||
changed: 0
|
||||
}, countFinished: false});
|
||||
}
|
||||
else {
|
||||
defer.resolve({
|
||||
val: jobResultsService
|
||||
.getCountsFromStatsEvent(data
|
||||
.results[0].event_data),
|
||||
countFinished: true});
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
defer.resolve({val: {
|
||||
ok: 0,
|
||||
skipped: 0,
|
||||
unreachable: 0,
|
||||
failures: 0,
|
||||
changed: 0
|
||||
}, countFinished: false});
|
||||
});
|
||||
} else {
|
||||
// make sure to not include any extra
|
||||
// search params for a running job (because we can't filter
|
||||
// incoming job events)
|
||||
if (!_.isEqual($stateParams.job_event_search, defaultParams)) {
|
||||
let params = _.cloneDeep($stateParams);
|
||||
params.job_event_search = defaultParams;
|
||||
$state.go('.', params, { reload: true });
|
||||
}
|
||||
|
||||
// job isn't finished so just send an empty count and read
|
||||
// from events
|
||||
defer.resolve({val: {
|
||||
ok: 0,
|
||||
skipped: 0,
|
||||
unreachable: 0,
|
||||
failures: 0,
|
||||
changed: 0
|
||||
}, countFinished: false});
|
||||
}
|
||||
return defer.promise;
|
||||
}],
|
||||
// GET for the particular jobs labels to be displayed in the
|
||||
// left-hand pane
|
||||
jobLabels: ['Rest', 'GetBasePath', '$stateParams', '$q', function(Rest, GetBasePath, $stateParams, $q) {
|
||||
var getNext = function(data, arr, resolve) {
|
||||
Rest.setUrl(data.next);
|
||||
Rest.get()
|
||||
.then(({data}) => {
|
||||
if (data.next) {
|
||||
getNext(data, arr.concat(data.results), resolve);
|
||||
} else {
|
||||
resolve.resolve(arr.concat(data.results)
|
||||
.map(val => val.name));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var seeMoreResolve = $q.defer();
|
||||
|
||||
Rest.setUrl(GetBasePath('jobs') + $stateParams.id + '/labels/');
|
||||
Rest.get()
|
||||
.then(({data}) => {
|
||||
if (data.next) {
|
||||
getNext(data, data.results, seeMoreResolve);
|
||||
} else {
|
||||
seeMoreResolve.resolve(data.results
|
||||
.map(val => val.name));
|
||||
}
|
||||
});
|
||||
|
||||
return seeMoreResolve.promise;
|
||||
}],
|
||||
// OPTIONS request for the job. Used to make things like the
|
||||
// verbosity data in the left-hand pane prettier than just an
|
||||
// integer
|
||||
jobDataOptions: ['Rest', 'GetBasePath', '$stateParams', '$q', function(Rest, GetBasePath, $stateParams, $q) {
|
||||
Rest.setUrl(GetBasePath('jobs') + $stateParams.id);
|
||||
var val = $q.defer();
|
||||
Rest.options()
|
||||
.then(function(data) {
|
||||
val.resolve(data.data);
|
||||
}, function(data) {
|
||||
val.reject(data);
|
||||
});
|
||||
return val.promise;
|
||||
}],
|
||||
jobExtraCredentials: ['Rest', 'GetBasePath', '$stateParams', '$q', function(Rest, GetBasePath, $stateParams, $q) {
|
||||
Rest.setUrl(GetBasePath('jobs') + $stateParams.id + '/extra_credentials');
|
||||
var val = $q.defer();
|
||||
Rest.get()
|
||||
.then(function(res) {
|
||||
val.resolve(res.data.results);
|
||||
}, function(res) {
|
||||
val.reject(res);
|
||||
});
|
||||
return val.promise;
|
||||
}]
|
||||
},
|
||||
templateUrl: templateUrl('job-results/job-results'),
|
||||
controller: 'jobResultsController'
|
||||
};
|
||||
@ -1,269 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
|
||||
export default ['$q', 'Prompt', '$filter', 'Wait', 'Rest', '$state', 'ProcessErrors', 'GetBasePath', 'Alert', '$rootScope', 'i18n',
|
||||
function ($q, Prompt, $filter, Wait, Rest, $state, ProcessErrors, GetBasePath, Alert, $rootScope, i18n) {
|
||||
var val = {
|
||||
// the playbook_on_stats event returns the count data in a weird format.
|
||||
// format to what we need!
|
||||
getCountsFromStatsEvent: function(event_data) {
|
||||
var hosts = {},
|
||||
hostsArr;
|
||||
|
||||
// iterate over the event_data and populate an object with hosts
|
||||
// and their status data
|
||||
Object.keys(event_data).forEach(key => {
|
||||
// failed passes boolean not integer
|
||||
if (key === "changed" ||
|
||||
key === "dark" ||
|
||||
key === "failures" ||
|
||||
key === "ok" ||
|
||||
key === "skipped") {
|
||||
// array of hosts from each type ("changed", "dark", etc.)
|
||||
hostsArr = Object.keys(event_data[key]);
|
||||
hostsArr.forEach(host => {
|
||||
if (!hosts[host]) {
|
||||
// host has not been added to hosts object
|
||||
// add now
|
||||
hosts[host] = {};
|
||||
}
|
||||
|
||||
if (!hosts[host][key]) {
|
||||
// host doesn't have key
|
||||
hosts[host][key] = 0;
|
||||
}
|
||||
hosts[host][key] += event_data[key][host];
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var total_hosts_by_state = {
|
||||
ok: 0,
|
||||
skipped: 0,
|
||||
unreachable: 0,
|
||||
failures: 0,
|
||||
changed: 0
|
||||
};
|
||||
|
||||
// each host belongs in at most *one* of these states depending on
|
||||
// the state of its tasks
|
||||
_.each(hosts, function(host) {
|
||||
if (host.dark > 0){
|
||||
total_hosts_by_state.unreachable++;
|
||||
} else if (host.failures > 0){
|
||||
total_hosts_by_state.failures++;
|
||||
} else if (host.changed > 0){
|
||||
total_hosts_by_state.changed++;
|
||||
} else if (host.ok > 0){
|
||||
total_hosts_by_state.ok++;
|
||||
} else if (host.skipped > 0){
|
||||
total_hosts_by_state.skipped++;
|
||||
}
|
||||
});
|
||||
|
||||
return total_hosts_by_state;
|
||||
},
|
||||
// rest call to grab previously complete job_events
|
||||
getEvents: function(url) {
|
||||
var val = $q.defer();
|
||||
|
||||
Rest.setUrl(url);
|
||||
Rest.get()
|
||||
.then(({data}) => {
|
||||
val.resolve({results: data.results,
|
||||
next: data.next});
|
||||
})
|
||||
.catch(({obj, status}) => {
|
||||
ProcessErrors(null, obj, status, null, {
|
||||
hdr: 'Error!',
|
||||
msg: `Could not get job events.
|
||||
Returned status: ${status}`
|
||||
});
|
||||
val.reject(obj);
|
||||
});
|
||||
|
||||
return val.promise;
|
||||
},
|
||||
deleteJob: function(job) {
|
||||
Prompt({
|
||||
hdr: i18n._("Delete Job"),
|
||||
resourceName: `#${job.id} ` + $filter('sanitize')(job.name),
|
||||
body: `<div class='Prompt-bodyQuery'>
|
||||
${i18n._("Are you sure you want to delete this job?")}
|
||||
</div>`,
|
||||
action: function() {
|
||||
Wait('start');
|
||||
Rest.setUrl(job.url);
|
||||
Rest.destroy()
|
||||
.then(() => {
|
||||
Wait('stop');
|
||||
$('#prompt-modal').modal('hide');
|
||||
$state.go('jobs');
|
||||
})
|
||||
.catch(({obj, status}) => {
|
||||
Wait('stop');
|
||||
$('#prompt-modal').modal('hide');
|
||||
ProcessErrors(null, obj, status, null, {
|
||||
hdr: 'Error!',
|
||||
msg: `Could not delete job.
|
||||
Returned status: ${status}`
|
||||
});
|
||||
});
|
||||
},
|
||||
actionText: i18n._('DELETE')
|
||||
});
|
||||
},
|
||||
cancelJob: function(job) {
|
||||
var doCancel = function() {
|
||||
Rest.setUrl(job.url + 'cancel');
|
||||
Rest.post({})
|
||||
.then(() => {
|
||||
Wait('stop');
|
||||
$('#prompt-modal').modal('hide');
|
||||
})
|
||||
.catch(({obj, status}) => {
|
||||
Wait('stop');
|
||||
$('#prompt-modal').modal('hide');
|
||||
ProcessErrors(null, obj, status, null, {
|
||||
hdr: 'Error!',
|
||||
msg: `Could not cancel job.
|
||||
Returned status: ${status}`
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Prompt({
|
||||
hdr: i18n._('Cancel Job'),
|
||||
resourceName: `#${job.id} ` + $filter('sanitize')(job.name),
|
||||
body: `<div class='Prompt-bodyQuery' translate>
|
||||
${i18n._("Are you sure you want to cancel this job?")}
|
||||
</div>`,
|
||||
action: function() {
|
||||
Wait('start');
|
||||
Rest.setUrl(job.url + 'cancel');
|
||||
Rest.get()
|
||||
.then(({data}) => {
|
||||
if (data.can_cancel === true) {
|
||||
doCancel();
|
||||
} else {
|
||||
$('#prompt-modal').modal('hide');
|
||||
ProcessErrors(null, data, null, null, {
|
||||
hdr: 'Error!',
|
||||
msg: `Job has completed,
|
||||
unabled to be canceled.`
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
actionText: i18n._('PROCEED')
|
||||
});
|
||||
},
|
||||
getJobData: function(id){
|
||||
var val = $q.defer();
|
||||
|
||||
Rest.setUrl(GetBasePath('jobs') + id );
|
||||
Rest.get()
|
||||
.then(function(data) {
|
||||
val.resolve(data.data);
|
||||
}, function(data) {
|
||||
val.reject(data);
|
||||
|
||||
if (data.status === 404) {
|
||||
Alert('Job Not Found', 'Cannot find job.', 'alert-info');
|
||||
} else if (data.status === 403) {
|
||||
Alert('Insufficient Permissions', 'You do not have permission to view this job.', 'alert-info');
|
||||
}
|
||||
|
||||
$state.go('jobs');
|
||||
});
|
||||
|
||||
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()
|
||||
.then((response) => {
|
||||
return response;
|
||||
})
|
||||
.catch(({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;
|
||||
}];
|
||||
@ -1,26 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import hostStatusBar from './host-status-bar/main';
|
||||
import jobResultsStdOut from './job-results-stdout/main';
|
||||
|
||||
import route from './job-results.route.js';
|
||||
|
||||
import jobResultsController from './job-results.controller';
|
||||
|
||||
import jobResultsService from './job-results.service';
|
||||
import eventQueueService from './event-queue.service';
|
||||
import parseStdoutService from './parse-stdout.service';
|
||||
|
||||
export default
|
||||
angular.module('jobResults', [hostStatusBar.name, jobResultsStdOut.name, 'angularMoment'])
|
||||
.run(['$stateExtender', function($stateExtender) {
|
||||
$stateExtender.addState(route);
|
||||
}])
|
||||
.controller('jobResultsController', jobResultsController)
|
||||
.service('jobResultsService', jobResultsService)
|
||||
.service('eventQueue', eventQueueService)
|
||||
.service('parseStdoutService', parseStdoutService);
|
||||
@ -1,293 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default ['$log', 'moment', 'i18n', function($log, moment, i18n){
|
||||
var val = {
|
||||
// parses stdout string from api and formats various codes to the
|
||||
// correct dom structure
|
||||
prettify: function(line, unstyled){
|
||||
line = line
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
|
||||
// TODO: remove once Chris's fixes to the [K lines comes in
|
||||
if (line.indexOf("[K") > -1) {
|
||||
$log.error(line);
|
||||
}
|
||||
|
||||
if(!unstyled){
|
||||
// add span tags with color styling
|
||||
line = line.replace(/u001b/g, '');
|
||||
|
||||
// ansi classes
|
||||
/* jshint ignore:start */
|
||||
line = line.replace(/(|)\[1;im/g, '<span class="JobResultsStdOut-cappedLine">');
|
||||
line = line.replace(/(|)\[0;30m/g, '<span class="ansi30">');
|
||||
line = line.replace(/(|)\[1;30m/g, '<span class="ansi1 ansi30">');
|
||||
line = line.replace(/(|)\[[0,1];31m/g, '<span class="ansi1 ansi31">');
|
||||
line = line.replace(/(|)\[0;32m(=|)/g, '<span class="ansi32">');
|
||||
line = line.replace(/(|)\[0;32m1/g, '<span class="ansi36">');
|
||||
line = line.replace(/(|)\[0;33m/g, '<span class="ansi33">');
|
||||
line = line.replace(/(|)\[0;34m/g, '<span class="ansi34">');
|
||||
line = line.replace(/(|)\[[0,1];35m/g, '<span class="ansi35">');
|
||||
line = line.replace(/(|)\[0;36m/g, '<span class="ansi36">');
|
||||
line = line.replace(/(<host.*?>)\s/g, '$1');
|
||||
|
||||
//end span
|
||||
line = line.replace(/(|)\[0m/g, '</span>');
|
||||
/* jshint ignore:end */
|
||||
} else {
|
||||
// For the host event modal in the standard out tab,
|
||||
// the styling isn't necessary
|
||||
line = line.replace(/u001b/g, '');
|
||||
|
||||
// ansi classes
|
||||
/* jshint ignore:start */
|
||||
line = line.replace(/(|)\[[0,1];3[0-9]m(1|=|)/g, '');
|
||||
line = line.replace(/(<host.*?>)\s/g, '$1');
|
||||
|
||||
//end span
|
||||
line = line.replace(/(|)\[0m/g, '');
|
||||
/* jshint ignore:end */
|
||||
}
|
||||
|
||||
return line;
|
||||
},
|
||||
// adds anchor tags and tooltips to host status lines
|
||||
getAnchorTags: function(event){
|
||||
if(event.event_name.indexOf("runner_") === -1){
|
||||
return `"`;
|
||||
}
|
||||
else{
|
||||
return ` JobResultsStdOut-stdoutColumn--clickable" ui-sref="jobResult.host-event.json({eventId: ${event.id}, taskUuid: '${event.event_data.task_uuid}' })" aw-tool-tip="${i18n._("Event ID")}: ${event.id} <br>${i18n._("Status")}: ${event.event_display} <br>${i18n._("Click for details")}" data-placement="top"`;
|
||||
}
|
||||
|
||||
},
|
||||
// this adds classes based on event data to the
|
||||
// .JobResultsStdOut-aLineOfStdOut element
|
||||
getLineClasses: function(event, line, lineNum) {
|
||||
var string = "";
|
||||
|
||||
if (lineNum === event.end_line) {
|
||||
// used to tell you where to put stuff in the pane
|
||||
string += ` next_is_${event.end_line + 1}`;
|
||||
}
|
||||
|
||||
if (event.event_name === "playbook_on_play_start") {
|
||||
// play header classes
|
||||
string += " header_play";
|
||||
string += " header_play_" + event.event_data.play_uuid;
|
||||
|
||||
// give the actual header class to the line with the
|
||||
// actual header info (think cowsay)
|
||||
if (line.indexOf("PLAY") > -1) {
|
||||
string += " actual_header";
|
||||
}
|
||||
} else if (event.event_name === "playbook_on_task_start") {
|
||||
// task header classes
|
||||
string += " header_task";
|
||||
string += " header_task_" + event.event_data.task_uuid;
|
||||
|
||||
// give the actual header class to the line with the
|
||||
// actual header info (think cowsay)
|
||||
if (line.indexOf("TASK") > -1 ||
|
||||
line.indexOf("RUNNING HANDLER") > -1) {
|
||||
string += " actual_header";
|
||||
}
|
||||
|
||||
// task headers also get classed by their parent play
|
||||
// if applicable
|
||||
if (event.event_data.play_uuid) {
|
||||
string += " play_" + event.event_data.play_uuid;
|
||||
}
|
||||
} else if (event.event_name !== "playbook_on_stats"){
|
||||
string += " not_skeleton";
|
||||
// host status or debug line
|
||||
|
||||
// these get classed by their parent play if applicable
|
||||
if (event.event_data.play_uuid) {
|
||||
string += " play_" + event.event_data.play_uuid;
|
||||
}
|
||||
// as well as their parent task if applicable
|
||||
if (event.event_data.task_uuid) {
|
||||
string += " task_" + event.event_data.task_uuid;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: adding this line_num_XX class is hacky because the
|
||||
// line number is availabe in children of this dom element
|
||||
string += " line_num_" + lineNum;
|
||||
|
||||
return string;
|
||||
},
|
||||
getStartTimeBadge: function(event, line){
|
||||
// This will return a div with the badge class
|
||||
// for the start time to show at the right hand
|
||||
// side of each stdout header line.
|
||||
// returns an empty string if not a header line
|
||||
var emptySpan = "", time;
|
||||
if ((event.event_name === "playbook_on_play_start" ||
|
||||
event.event_name === "playbook_on_task_start") &&
|
||||
line !== "") {
|
||||
time = moment(event.created).format('HH:mm:ss');
|
||||
return `<div class="badge JobResults-timeBadge ng-binding">${time}</div>`;
|
||||
}
|
||||
else if(event.event_name === "playbook_on_stats" && line.indexOf("PLAY") > -1){
|
||||
time = moment(event.created).format('HH:mm:ss');
|
||||
return `<div class="badge JobResults-timeBadge ng-binding">${time}</div>`;
|
||||
}
|
||||
else {
|
||||
return emptySpan;
|
||||
}
|
||||
|
||||
},
|
||||
// used to add expand/collapse icon next to line numbers of headers
|
||||
getCollapseIcon: function(event, line) {
|
||||
var clickClass,
|
||||
expanderizerSpecifier;
|
||||
|
||||
var emptySpan = `
|
||||
<span class="JobResultsStdOut-lineExpander"></span>`;
|
||||
|
||||
if ((event.event_name === "playbook_on_play_start" ||
|
||||
event.event_name === "playbook_on_task_start") &&
|
||||
line !== "") {
|
||||
if (event.event_name === "playbook_on_play_start" &&
|
||||
line.indexOf("PLAY") > -1) {
|
||||
// play header specific attrs
|
||||
expanderizerSpecifier = "play";
|
||||
clickClass = "play_" +
|
||||
event.event_data.play_uuid;
|
||||
} else if (line.indexOf("TASK") > -1 ||
|
||||
line.indexOf("RUNNING HANDLER") > -1) {
|
||||
// task header specific attrs
|
||||
expanderizerSpecifier = "task";
|
||||
clickClass = "task_" +
|
||||
event.event_data.task_uuid;
|
||||
} else {
|
||||
// header lines that don't have PLAY, TASK,
|
||||
// or RUNNING HANDLER in them don't get
|
||||
// expand icon.
|
||||
// This provides cowsay support.
|
||||
return emptySpan;
|
||||
}
|
||||
|
||||
|
||||
var expandDom = `
|
||||
<span class="JobResultsStdOut-lineExpander">
|
||||
<i class="JobResultsStdOut-lineExpanderIcon fa fa-caret-down expanderizer
|
||||
expanderizer--${expanderizerSpecifier} expanded"
|
||||
ng-click="toggleLine($event, '.${clickClass}')"
|
||||
data-uuid="${clickClass}">
|
||||
</i>
|
||||
</span>`;
|
||||
return expandDom;
|
||||
} else {
|
||||
// non-header lines don't get an expander
|
||||
return emptySpan;
|
||||
}
|
||||
},
|
||||
distributeColors: function(lines) {
|
||||
var colorCode;
|
||||
return lines.map(line => {
|
||||
|
||||
if (colorCode) {
|
||||
line = colorCode + line;
|
||||
}
|
||||
|
||||
if (line.indexOf("[0m") === -1) {
|
||||
if (line.indexOf("[1;31m") > -1) {
|
||||
colorCode = "[1;31m";
|
||||
} else if (line.indexOf("[1;30m") > -1) {
|
||||
colorCode = "[1;30m";
|
||||
} else if (line.indexOf("[0;31m") > -1) {
|
||||
colorCode = "[0;31m";
|
||||
} else if (line.indexOf("[0;32m=") > -1) {
|
||||
colorCode = "[0;32m=";
|
||||
} else if (line.indexOf("[0;32m1") > -1) {
|
||||
colorCode = "[0;32m1";
|
||||
} else if (line.indexOf("[0;32m") > -1) {
|
||||
colorCode = "[0;32m";
|
||||
} else if (line.indexOf("[0;33m") > -1) {
|
||||
colorCode = "[0;33m";
|
||||
} else if (line.indexOf("[0;34m") > -1) {
|
||||
colorCode = "[0;34m";
|
||||
} else if (line.indexOf("[0;35m") > -1) {
|
||||
colorCode = "[0;35m";
|
||||
} else if (line.indexOf("[1;35m") > -1) {
|
||||
colorCode = "[1;35m";
|
||||
} else if (line.indexOf("[0;36m") > -1) {
|
||||
colorCode = "[0;36m";
|
||||
}
|
||||
} else {
|
||||
colorCode = null;
|
||||
}
|
||||
|
||||
return line;
|
||||
});
|
||||
},
|
||||
getLineArr: function(event) {
|
||||
let lineNums = _.range(event.start_line + 1,
|
||||
event.end_line + 1);
|
||||
|
||||
// hack around no-carriage return issues
|
||||
if (!lineNums.length) {
|
||||
lineNums = [event.start_line + 1];
|
||||
}
|
||||
|
||||
let lines = event.stdout
|
||||
.replace("\t", " ")
|
||||
.split("\r\n");
|
||||
|
||||
if (lineNums.length > lines.length) {
|
||||
lineNums = lineNums.slice(0, lines.length);
|
||||
}
|
||||
|
||||
lines = this.distributeColors(lines);
|
||||
|
||||
// hack around no-carriage return issues
|
||||
if (lineNums.length === lines.length) {
|
||||
return _.zip(lineNums, lines);
|
||||
}
|
||||
|
||||
return _.zip(lineNums, lines).slice(0, -1);
|
||||
},
|
||||
actualEndLine: function(event) {
|
||||
return event.start_line + this.getLineArr(event).length;
|
||||
},
|
||||
// public function that provides the parsed stdout line, given a
|
||||
// job_event
|
||||
parseStdout: function(event){
|
||||
// this utilizes the start/end lines and stdout blob
|
||||
// to create an array in the format:
|
||||
// [
|
||||
// [lineNum, lineText],
|
||||
// [lineNum, lineText],
|
||||
// ]
|
||||
var lineArr = this.getLineArr(event);
|
||||
|
||||
// this takes each `[lineNum: lineText]` element and calls the
|
||||
// relevant helper functions in this service to build the
|
||||
// parsed line of standard out
|
||||
lineArr = lineArr
|
||||
.map(lineArr => {
|
||||
return `
|
||||
<div class="JobResultsStdOut-aLineOfStdOut${this.getLineClasses(event, lineArr[1], lineArr[0])}">
|
||||
<div class="JobResultsStdOut-lineNumberColumn">${this.getCollapseIcon(event, lineArr[1])}${lineArr[0]}</div>
|
||||
<div class="JobResultsStdOut-stdoutColumn${this.getAnchorTags(event)}><span ng-non-bindable>${this.prettify(lineArr[1])}</span>${this.getStartTimeBadge(event, lineArr[1])}</div>
|
||||
</div>`;
|
||||
});
|
||||
|
||||
// this joins all the lines for this job_event together and
|
||||
// returns to the mungeEvent function
|
||||
return lineArr.join("");
|
||||
}
|
||||
};
|
||||
return val;
|
||||
}];
|
||||
@ -1,147 +0,0 @@
|
||||
<div class="tab-pane" id="jobs-stdout">
|
||||
<div ng-cloak id="htmlTemplate">
|
||||
<div class="StandardOut-container" ng-class="{'fullscreen': stdoutFullScreen}">
|
||||
<div class="StandardOut-leftPanel" ng-show="!stdoutFullScreen">
|
||||
<div class="Panel">
|
||||
<div class="StandardOut-panelHeader">
|
||||
<div class="StandardOut-panelHeaderText" translate>
|
||||
RESULTS
|
||||
</div>
|
||||
<div class="StandardOut-actions">
|
||||
<div>
|
||||
<at-relaunch job="job"></at-relaunch>
|
||||
</div>
|
||||
<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">
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.module_name">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>Name</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">{{ job.module_name }}</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>STATUS</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
<i class="fa icon-job-{{ job.status }}"></i>
|
||||
<span class="StandardOut-statusText StandardOut--capitalize">{{ job.status }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.started">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>STARTED</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
{{ job.started | longDate }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.finished">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>FINISHED</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
{{ job.finished | longDate }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.finished">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>ELAPSED</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
{{ job.elapsed }} seconds
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.module_args">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>Module Args</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">{{ job.module_args }}</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>Inventory</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
<a href="{{ inventory_url }}"
|
||||
aw-tool-tip="{{'The inventory this command ran on.'|translate}}"
|
||||
data-placement="top">{{ inventory_name }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="credential_name">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>Credential</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
<a href="{{ credential_url }}"
|
||||
aw-tool-tip="{{'The credential used to run this command.'|translate}}"
|
||||
data-placement="top">{{ credential_name }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="created_by">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>Launched By</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
<a href="/#/users/{{ created_by.id }}"
|
||||
aw-tool-tip="{{'The user who ran this command.'|translate}}"
|
||||
data-placement="top">{{ created_by.username }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- since zero is a falsy value, you need ng-show such that
|
||||
the number is >= 0 -->
|
||||
<div class="StandardOut-detailsRow" ng-show="forks >= 0">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>Forks</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">{{ forks }}</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="limit">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>Limit</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">{{ limit }}</div>
|
||||
</div>
|
||||
|
||||
<!-- since zero is a falsy value, you need ng-show such that
|
||||
the number is >= 0 -->
|
||||
<div class="StandardOut-detailsRow" ng-show="verbosity >= 0">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>Verbosity</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">{{ verbosity }}</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.extra_vars">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">
|
||||
{{ 'Extra Variables' | translate }}
|
||||
<i class="StandardOut-extraVarsHelp fa fa-question-circle"
|
||||
aw-tool-tip="Read only view of extra variables added to the ad-hoc command."
|
||||
data-placement="top">
|
||||
</i>
|
||||
</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
<textarea
|
||||
rows="6"
|
||||
ng-model="extra_vars"
|
||||
name="variables"
|
||||
class="StandardOut-extraVars"
|
||||
id="pre-formatted-variables">
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="StandardOut-rightPanel">
|
||||
<div class="Panel">
|
||||
<div class="StandardOut-panelHeader">
|
||||
<div class="StandardOut-panelHeaderText" translate>STANDARD OUT</div>
|
||||
<div class="StandardOut-panelHeaderActions">
|
||||
<button class="StandardOut-actionButton" aw-tool-tip="{{ toggleStdoutFullscreenTooltip }}" data-tip-watch="toggleStdoutFullscreenTooltip" data-placement="top" ng-class="{'StandardOut-actionButton--active': stdoutFullScreen}" ng-click="toggleStdoutFullscreen()">
|
||||
<i class="fa fa-arrows-alt"></i>
|
||||
</button>
|
||||
<a href="/api/v2/ad_hoc_commands/{{ job.id }}/stdout?format=txt_download">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,36 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import { templateUrl } from '../../shared/template-url/template-url.factory';
|
||||
|
||||
export default {
|
||||
name: 'adHocJobStdout',
|
||||
route: '/ad_hoc_commands/:id',
|
||||
templateUrl: templateUrl('standard-out/adhoc/standard-out-adhoc'),
|
||||
controller: 'JobStdoutController',
|
||||
ncyBreadcrumb: {
|
||||
parent: "jobs",
|
||||
label: "{{ job.module_name }}"
|
||||
},
|
||||
data: {
|
||||
jobType: 'ad_hoc_commands',
|
||||
socket: {
|
||||
"groups": {
|
||||
"jobs": ["status_changed", "summary"],
|
||||
"ad_hoc_command_events": []
|
||||
}
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
jobData: ['Rest', 'GetBasePath', '$stateParams', function(Rest, GetBasePath, $stateParams) {
|
||||
Rest.setUrl(GetBasePath('base') + 'ad_hoc_commands/' + $stateParams.id + '/');
|
||||
return Rest.get()
|
||||
.then(({data}) => {
|
||||
return data;
|
||||
});
|
||||
}]
|
||||
}
|
||||
};
|
||||
@ -1,152 +0,0 @@
|
||||
<div class="tab-pane" id="jobs-stdout">
|
||||
<div ng-cloak id="htmlTemplate">
|
||||
<div class="StandardOut-container" ng-class="{'fullscreen': stdoutFullScreen}">
|
||||
<div class="StandardOut-leftPanel" ng-show="!stdoutFullScreen">
|
||||
<div class="Panel">
|
||||
<div class="StandardOut-panelHeader">
|
||||
<div class="StandardOut-panelHeaderText" translate>
|
||||
RESULTS
|
||||
</div>
|
||||
<div class="StandardOut-actions">
|
||||
<at-relaunch job="job"></at-relaunch>
|
||||
<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">
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="inventory_source_name">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>NAME</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
<a href="{{inv_manage_group_link}}">
|
||||
{{ inventory_source_name }}
|
||||
</a>
|
||||
<a href="{{ workflow_result_link }}"
|
||||
aw-tool-tip="{{'View workflow results'|translate}}"
|
||||
data-placement="top"
|
||||
data-original-title="" title="">
|
||||
<i class="WorkflowBadge"
|
||||
ng-show="job.launch_type === 'workflow' ">
|
||||
W
|
||||
</i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>STATUS</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
<i class="fa icon-job-{{ job.status }}"></i>
|
||||
<span class="StandardOut-statusText StandardOut--capitalize">{{ job.status }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EXPLANATION DETAIL -->
|
||||
<div class="StandardOut-detailsRow">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>EXPLANATION</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
{{task_detail | limitTo:explanationLimit}}
|
||||
<span ng-show="explanationLimit && task_detail.length > explanationLimit">
|
||||
<span>... </span>
|
||||
<span class="StandardOut-seeMoreLess" ng-click="explanationLimit=undefined">Show More</span>
|
||||
</span>
|
||||
<span ng-show="explanationLimit === undefined" class="StandardOut-seeMoreLess" ng-click="explanationLimit=150">Show Less</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="{{job.license_error !== null}}">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>LICENSE ERROR</div>
|
||||
<div class="StandardOut-detailsContent StandardOut--capitalize">
|
||||
{{ job.license_error }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.started">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>STARTED</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
{{ job.started | longDate }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.finished">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>FINISHED</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
{{ job.finished | longDate }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.finished">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>ELAPSED</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
{{ job.elapsed }} seconds
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.launch_type">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>LAUNCH TYPE</div>
|
||||
<div class="StandardOut-detailsContent StandardOut--capitalize">
|
||||
{{ job.launch_type }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="credential_name">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>CREDENTIAL</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
<a ui-sref="credentials.edit({credential_id: credential})">
|
||||
{{ credential_name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="source">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>SOURCE</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
{{ source }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="source_regions">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>REGIONS</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
{{ source_regions }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="{{ job.overwrite !== null }}">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>OVERWRITE</div>
|
||||
<div class="StandardOut-detailsContent StandardOut--capitalize">
|
||||
{{ job.overwrite }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="{{ job.overwrite_vars !== null }}">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>OVERWRITE VARS</div>
|
||||
<div class="StandardOut-detailsContent StandardOut--capitalize">
|
||||
{{ job.overwrite_vars }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="StandardOut-rightPanel">
|
||||
<div class="Panel">
|
||||
<div class="StandardOut-panelHeader">
|
||||
<div class="StandardOut-panelHeaderText" translate>STANDARD OUT</div>
|
||||
<div class="StandardOut-panelHeaderActions">
|
||||
<button class="StandardOut-actionButton" aw-tool-tip="{{ toggleStdoutFullscreenTooltip }}" data-tip-watch="toggleStdoutFullscreenTooltip" data-placement="top" ng-class="{'StandardOut-actionButton--active': stdoutFullScreen}"ng-click="toggleStdoutFullscreen()">
|
||||
<i class="fa fa-arrows-alt"></i>
|
||||
</button>
|
||||
<a href="/api/v2/inventory_updates/{{ job.id }}/stdout?format=txt_download">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,38 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import {templateUrl} from '../../shared/template-url/template-url.factory';
|
||||
|
||||
// TODO: figure out what this route should be - should it be inventory_sync?
|
||||
|
||||
export default {
|
||||
name: 'inventorySyncStdout',
|
||||
route: '/inventory_sync/:id',
|
||||
templateUrl: templateUrl('standard-out/inventory-sync/standard-out-inventory-sync'),
|
||||
controller: 'JobStdoutController',
|
||||
ncyBreadcrumb: {
|
||||
parent: "jobs",
|
||||
label: "{{ inventory_source_name }}"
|
||||
},
|
||||
data: {
|
||||
socket: {
|
||||
"groups":{
|
||||
"jobs": ["status_changed", "summary"],
|
||||
"inventory_update_events": [],
|
||||
}
|
||||
},
|
||||
jobType: 'inventory_updates'
|
||||
},
|
||||
resolve: {
|
||||
jobData: ['Rest', 'GetBasePath', '$stateParams', function(Rest, GetBasePath, $stateParams) {
|
||||
Rest.setUrl(GetBasePath('base') + 'inventory_updates/' + $stateParams.id + '/');
|
||||
return Rest.get()
|
||||
.then(({data}) => {
|
||||
return data;
|
||||
});
|
||||
}]
|
||||
}
|
||||
};
|
||||
@ -1,10 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2015 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import standardOutLog from './standard-out-log.directive';
|
||||
export default
|
||||
angular.module('standardOutLogDirective', [])
|
||||
.directive('standardOutLog', standardOutLog);
|
||||
@ -1,202 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'ProcessErrors', 'Rest', 'Wait',
|
||||
function ($log, $rootScope, $scope, $state, $stateParams, ProcessErrors, Rest, Wait) {
|
||||
|
||||
var api_complete = false,
|
||||
current_range,
|
||||
loaded_sections = [],
|
||||
event_queue = 0,
|
||||
auto_scroll_down=true, // programmatic scroll to bottom
|
||||
live_event_processing = true,
|
||||
page_size = 500,
|
||||
job_id = $stateParams.id;
|
||||
|
||||
$scope.should_apply_live_events = true;
|
||||
|
||||
// Open up a socket for events depending on the type of job
|
||||
function openSockets() {
|
||||
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);
|
||||
if (api_complete) {
|
||||
event_queue++;
|
||||
}
|
||||
});
|
||||
}
|
||||
if ($state.current.name === 'adHocJobStdout') {
|
||||
$log.debug("socket watching on ad_hoc_command_events-" + job_id);
|
||||
$scope.$on(`ws-ad_hoc_command_events-${job_id}`, function() {
|
||||
$log.debug("socket fired on ad_hoc_command_events-" + job_id);
|
||||
if (api_complete) {
|
||||
event_queue++;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
openSockets();
|
||||
|
||||
// This is a trigger for loading up the standard out
|
||||
if ($scope.removeLoadStdout) {
|
||||
$scope.removeLoadStdout();
|
||||
}
|
||||
$scope.removeLoadStdout = $scope.$on('LoadStdout', function() {
|
||||
if (loaded_sections.length === 0) {
|
||||
loadStdout();
|
||||
}
|
||||
else if (live_event_processing) {
|
||||
getNextSection();
|
||||
}
|
||||
});
|
||||
|
||||
// This interval checks to see whether or not we've gotten a new
|
||||
// event via sockets. If so, go out and update the standard out
|
||||
// log.
|
||||
$rootScope.jobStdOutInterval = setInterval( function() {
|
||||
if (event_queue > 0) {
|
||||
// events happened since the last check
|
||||
$log.debug('checking for stdout...');
|
||||
if (loaded_sections.length === 0) { ////this if statement for refresh
|
||||
$log.debug('calling LoadStdout');
|
||||
loadStdout();
|
||||
}
|
||||
else if (live_event_processing) {
|
||||
$log.debug('calling getNextSection');
|
||||
getNextSection();
|
||||
}
|
||||
event_queue = 0;
|
||||
}
|
||||
}, 2000);
|
||||
|
||||
// stdoutEndpoint gets passed through in the directive declaration.
|
||||
// This watcher fires off loadStdout() when the endpoint becomes
|
||||
// available.
|
||||
$scope.$watch('stdoutEndpoint', function(newVal, oldVal) {
|
||||
if(newVal && newVal !== oldVal) {
|
||||
// Fire off the server call
|
||||
loadStdout();
|
||||
}
|
||||
});
|
||||
|
||||
// stdoutText optionall gets passed through in the directive declaration.
|
||||
$scope.$watch('stdoutText', function(newVal, oldVal) {
|
||||
if(newVal && newVal !== oldVal) {
|
||||
$('#pre-container-content').html(newVal);
|
||||
}
|
||||
});
|
||||
|
||||
function loadStdout() {
|
||||
if (!$scope.stdoutEndpoint) {
|
||||
return;
|
||||
}
|
||||
|
||||
Rest.setUrl($scope.stdoutEndpoint + '?format=json&start_line=0&end_line=' + page_size);
|
||||
Rest.get()
|
||||
.then(({data}) => {
|
||||
Wait('stop');
|
||||
if (data.content) {
|
||||
api_complete = true;
|
||||
$('#pre-container-content').html(data.content);
|
||||
current_range = data.range;
|
||||
if (data.content !== "Waiting for results...") {
|
||||
loaded_sections.push({
|
||||
start: (data.range.start < 0) ? 0 : data.range.start,
|
||||
end: data.range.end
|
||||
});
|
||||
}
|
||||
|
||||
$('#pre-container').scrollTop($('#pre-container').prop("scrollHeight"));
|
||||
}
|
||||
else {
|
||||
api_complete = true;
|
||||
}
|
||||
})
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'Failed to retrieve stdout for job: ' + job_id + '. GET returned: ' + status });
|
||||
});
|
||||
}
|
||||
|
||||
function getNextSection() {
|
||||
if (!$scope.stdoutEndpoint) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get the next range of data from the API
|
||||
var start = loaded_sections[loaded_sections.length - 1].end, url;
|
||||
url = $scope.stdoutEndpoint + '?format=json&start_line=' + start + '&end_line=' + (start + page_size);
|
||||
$('#stdoutMoreRowsBottom').fadeIn();
|
||||
Rest.setUrl(url);
|
||||
Rest.get()
|
||||
.then(({data}) => {
|
||||
if ($('#pre-container-content').html() === "Waiting for results...") {
|
||||
$('#pre-container-content').html(data.content);
|
||||
} else {
|
||||
$('#pre-container-content').append(data.content);
|
||||
}
|
||||
loaded_sections.push({
|
||||
start: (data.range.start < 0) ? 0 : data.range.start,
|
||||
end: data.range.end
|
||||
});
|
||||
if ($scope.should_apply_live_events) {
|
||||
// if user has not disabled live event view by scrolling upward, then scroll down to the new content
|
||||
current_range = data.range;
|
||||
auto_scroll_down = true; // prevent auto load from happening
|
||||
$('#pre-container').scrollTop($('#pre-container').prop("scrollHeight"));
|
||||
}
|
||||
$('#stdoutMoreRowsBottom').fadeOut(400);
|
||||
})
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'Failed to retrieve stdout for job: ' + job_id + '. GET returned: ' + status });
|
||||
});
|
||||
}
|
||||
|
||||
// lrInfiniteScroll handler
|
||||
// grabs the next stdout section
|
||||
$scope.stdOutGetNextSection = function(){
|
||||
if (current_range.absolute_end > current_range.end){
|
||||
var url = $scope.stdoutEndpoint + '?format=json&start_line=' + current_range.end +
|
||||
'&end_line=' + (current_range.end + page_size);
|
||||
Rest.setUrl(url);
|
||||
Rest.get()
|
||||
.then(({data}) => {
|
||||
$('#pre-container-content').append(data.content);
|
||||
current_range = data.range;
|
||||
})
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'Failed to retrieve stdout for job: ' + job_id + '. GET returned: ' + status });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// We watch for job status changes here. If the job completes we want to clear out the
|
||||
// stdout interval and kill the live_event_processing flag.
|
||||
$scope.$on(`ws-jobs`, function(e, data) {
|
||||
if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10)) {
|
||||
if (data.status === 'failed' || data.status === 'canceled' ||
|
||||
data.status === 'error' || data.status === 'successful') {
|
||||
if ($rootScope.jobStdOutInterval) {
|
||||
window.clearInterval($rootScope.jobStdOutInterval);
|
||||
}
|
||||
if (live_event_processing) {
|
||||
if (loaded_sections.length === 0) {
|
||||
loadStdout();
|
||||
}
|
||||
else {
|
||||
getNextSection();
|
||||
}
|
||||
}
|
||||
live_event_processing = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}];
|
||||
@ -1,47 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import standardOutLogController from './standard-out-log.controller';
|
||||
export default [ 'templateUrl',
|
||||
function(templateUrl) {
|
||||
return {
|
||||
scope: {
|
||||
stdoutEndpoint: '=',
|
||||
stdoutText: '=',
|
||||
jobId: '='
|
||||
},
|
||||
templateUrl: templateUrl('standard-out/log/standard-out-log'),
|
||||
restrict: 'E',
|
||||
controller: standardOutLogController,
|
||||
link: function(scope) {
|
||||
// All of our DOM related stuff will go in here
|
||||
|
||||
var lastScrollTop,
|
||||
direction;
|
||||
|
||||
function detectDirection() {
|
||||
var st = $('#pre-container').scrollTop();
|
||||
if (st > lastScrollTop) {
|
||||
direction = "down";
|
||||
} else {
|
||||
direction = "up";
|
||||
}
|
||||
lastScrollTop = st;
|
||||
return direction;
|
||||
}
|
||||
|
||||
$('#pre-container').bind('scroll', function() {
|
||||
if (detectDirection() === "up") {
|
||||
scope.should_apply_live_events = false;
|
||||
}
|
||||
|
||||
if ($(this).scrollTop() + $(this).height() === $(this).prop("scrollHeight")) {
|
||||
scope.should_apply_live_events = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}];
|
||||
@ -1,8 +0,0 @@
|
||||
<div class="StandardOut-consoleOutput" lr-infinite-scroll="stdOutGetNextSection" scroll-threshold="300" time-threshold="500">
|
||||
<div id="pre-container" class="body_background body_foreground pre mono-space StandardOut-preContainer">
|
||||
<div id="pre-container-content" class="StandardOut-preContent"></div>
|
||||
</div>
|
||||
<div class="scroll-spinner" id="stdoutMoreRowsBottom">
|
||||
<i class="fa fa-cog fa-spin"></i>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,22 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import stdoutAdhocRoute from './adhoc/standard-out-adhoc.route';
|
||||
import stdoutManagementJobsRoute from './management-jobs/standard-out-management-jobs.route';
|
||||
import stdoutInventorySyncRoute from './inventory-sync/standard-out-inventory-sync.route';
|
||||
import stdoutScmUpdateRoute from './scm-update/standard-out-scm-update.route';
|
||||
import {JobStdoutController} from './standard-out.controller';
|
||||
import StandardOutHelper from './standard-out-factories/main';
|
||||
import standardOutLogDirective from './log/main';
|
||||
|
||||
export default angular.module('standardOut', [StandardOutHelper.name, standardOutLogDirective.name])
|
||||
.controller('JobStdoutController', JobStdoutController)
|
||||
.run(['$stateExtender', function($stateExtender) {
|
||||
$stateExtender.addState(stdoutAdhocRoute);
|
||||
$stateExtender.addState(stdoutManagementJobsRoute);
|
||||
$stateExtender.addState(stdoutInventorySyncRoute);
|
||||
$stateExtender.addState(stdoutScmUpdateRoute);
|
||||
}]);
|
||||
@ -1,84 +0,0 @@
|
||||
<div class="tab-pane" id="jobs-stdout">
|
||||
<div ng-cloak id="htmlTemplate">
|
||||
<div class="StandardOut-container">
|
||||
<div class="StandardOut-leftPanel" ng-show="!stdoutFullScreen">
|
||||
<div class="Panel">
|
||||
<div class="StandardOut-panelHeader">
|
||||
<div class="StandardOut-panelHeaderText">
|
||||
RESULTS
|
||||
</div>
|
||||
<div class="StandardOut-actions">
|
||||
<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">
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.name">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">NAME</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">{{ job.name }}</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">STATUS</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
<i class="fa icon-job-{{ job.status }}"></i>
|
||||
<span class="StandardOut-statusText StandardOut--capitalize">{{ job.status }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.started">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">STARTED</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
{{ job.started | longDate }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.finished">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">FINISHED</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
{{ job.finished | longDate }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.finished">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">ELAPSED</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
{{ job.elapsed }} seconds
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.launch_type">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4">LAUNCH TYPE</div>
|
||||
<div class="StandardOut-detailsContent StandardOut--capitalize">
|
||||
{{ job.launch_type }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow--extraVars" ng-show="job.extra_vars">
|
||||
<div class="StandardOut-detailsLabel">EXTRA VARIABLES</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-extraVarsContainer" ng-show="job.extra_vars">
|
||||
<textarea rows="6" ng-model="variables" name="variables" class="StandardOut-extraVars" id="pre-formatted-variables"></textarea>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="StandardOut-rightPanel">
|
||||
<div class="Panel">
|
||||
<div class="StandardOut-panelHeader">
|
||||
<div class="StandardOut-panelHeaderText">STANDARD OUT</div>
|
||||
<div class="StandardOut-panelHeaderActions">
|
||||
<button class="StandardOut-actionButton" aw-tool-tip="{{ toggleStdoutFullscreenTooltip }}" data-tip-watch="toggleStdoutFullscreenTooltip" data-placement="top" ng-class="{'StandardOut-actionButton--active': stdoutFullScreen}"ng-click="toggleStdoutFullscreen()">
|
||||
<i class="fa fa-arrows-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<standard-out-log stdout-text="job.result_stdout"></standard-out-log>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,36 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import { templateUrl } from '../../shared/template-url/template-url.factory';
|
||||
|
||||
export default {
|
||||
name: 'managementJobStdout',
|
||||
route: '/management_jobs/:id',
|
||||
templateUrl: templateUrl('standard-out/management-jobs/standard-out-management-jobs'),
|
||||
controller: 'JobStdoutController',
|
||||
ncyBreadcrumb: {
|
||||
parent: "jobs",
|
||||
label: "{{ job.name }}"
|
||||
},
|
||||
data: {
|
||||
jobType: 'system_jobs',
|
||||
socket: {
|
||||
"groups": {
|
||||
"jobs": ["status_changed", "summary"],
|
||||
"system_job_events": [],
|
||||
}
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
jobData: ['Rest', 'GetBasePath', '$stateParams', function(Rest, GetBasePath, $stateParams) {
|
||||
Rest.setUrl(GetBasePath('base') + 'system_jobs/' + $stateParams.id + '/');
|
||||
return Rest.get()
|
||||
.then(({data}) => {
|
||||
return data;
|
||||
});
|
||||
}]
|
||||
}
|
||||
};
|
||||
@ -1,113 +0,0 @@
|
||||
<div class="tab-pane" id="jobs-stdout">
|
||||
<div ng-cloak id="htmlTemplate">
|
||||
<div class="StandardOut-container" ng-class="{'fullscreen': stdoutFullScreen}">
|
||||
<div class="StandardOut-leftPanel" ng-show="!stdoutFullScreen">
|
||||
<div class="Panel">
|
||||
<div class="StandardOut-panelHeader">
|
||||
<div class="StandardOut-panelHeaderText" translate>
|
||||
RESULTS
|
||||
</div>
|
||||
<div class="StandardOut-actions">
|
||||
<at-relaunch job="job"></at-relaunch>
|
||||
<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">
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="project_name">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>NAME</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
<a ui-sref="projects.edit({project_id: job.project})">
|
||||
{{ project_name }}
|
||||
</a>
|
||||
<a href="{{ workflow_result_link }}"
|
||||
aw-tool-tip="{{'View workflow results'|translate}}"
|
||||
data-placement="top"
|
||||
data-original-title="" title="">
|
||||
<i class="WorkflowBadge"
|
||||
ng-show="job.launch_type === 'workflow' ">
|
||||
W
|
||||
</i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>STATUS</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
<i class="fa icon-job-{{ job.status }}"></i>
|
||||
<span class="StandardOut-statusText StandardOut--capitalize">{{ job.status }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.started">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>STARTED</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
{{ job.started | longDate }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.finished">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>FINISHED</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
{{ job.finished | longDate }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.finished">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>ELAPSED</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
{{ job.elapsed }} seconds
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.launch_type">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>LAUNCH TYPE</div>
|
||||
<div class="StandardOut-detailsContent StandardOut--capitalize">
|
||||
{{ job.launch_type }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="project_name">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>PROJECT</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
<a ui-sref="projects.edit({project_id: job.project})">
|
||||
{{ project_name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="credential_name">
|
||||
<div class="StandardOut-detailsLabel col-lg-3 col-md-3 col-sm-3 col-xs-4" translate>CREDENTIAL</div>
|
||||
<div class="StandardOut-detailsContent col-lg-9 col-md-9 col-sm-9 col-xs-8">
|
||||
<a ui-sref="credentials.edit({credential_id: credential})">
|
||||
{{ credential_name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="StandardOut-rightPanel">
|
||||
<div class="Panel">
|
||||
<div class="StandardOut-panelHeader">
|
||||
<div class="StandardOut-panelHeaderText" translate>STANDARD OUT</div>
|
||||
<div class="StandardOut-panelHeaderActions">
|
||||
<button class="StandardOut-actionButton" aw-tool-tip="{{ toggleStdoutFullscreenTooltip }}" data-tip-watch="toggleStdoutFullscreenTooltip" data-placement="top" ng-class="{'StandardOut-actionButton--active': stdoutFullScreen}"ng-click="toggleStdoutFullscreen()">
|
||||
<i class="fa fa-arrows-alt"></i>
|
||||
</button>
|
||||
<a href="/api/v2/project_updates/{{ job.id }}/stdout?format=txt_download">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,38 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import { templateUrl } from '../../shared/template-url/template-url.factory';
|
||||
|
||||
// TODO: figure out what this route should be - should it be scm_update?
|
||||
|
||||
export default {
|
||||
name: 'scmUpdateStdout',
|
||||
route: '/scm_update/:id',
|
||||
templateUrl: templateUrl('standard-out/scm-update/standard-out-scm-update'),
|
||||
controller: 'JobStdoutController',
|
||||
ncyBreadcrumb: {
|
||||
parent: "jobs",
|
||||
label: "{{ project_name }}"
|
||||
},
|
||||
data: {
|
||||
jobType: 'project_updates',
|
||||
socket: {
|
||||
"groups": {
|
||||
"jobs": ["status_changed", "summary"],
|
||||
"project_update_events": [],
|
||||
}
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
jobData: ['Rest', 'GetBasePath', '$stateParams', function(Rest, GetBasePath, $stateParams) {
|
||||
Rest.setUrl(GetBasePath('base') + 'project_updates/' + $stateParams.id + '/');
|
||||
return Rest.get()
|
||||
.then(({data}) => {
|
||||
return data;
|
||||
});
|
||||
}]
|
||||
}
|
||||
};
|
||||
@ -1,145 +0,0 @@
|
||||
export default
|
||||
function DeleteJob($state, Find, Rest, Wait, ProcessErrors, Prompt, Alert,
|
||||
$filter, i18n) {
|
||||
return function(params) {
|
||||
var scope = params.scope,
|
||||
id = params.id,
|
||||
job = params.job,
|
||||
callback = params.callback,
|
||||
action, jobs, url, action_label, hdr;
|
||||
|
||||
if (!job) {
|
||||
if (scope.completed_jobs) {
|
||||
jobs = scope.completed_jobs;
|
||||
}
|
||||
else if (scope.running_jobs) {
|
||||
jobs = scope.running_jobs;
|
||||
}
|
||||
else if (scope.queued_jobs) {
|
||||
jobs = scope.queued_jobs;
|
||||
}
|
||||
else if (scope.all_jobs) {
|
||||
jobs = scope.all_jobs;
|
||||
}
|
||||
else if (scope.jobs) {
|
||||
jobs = scope.jobs;
|
||||
}
|
||||
job = Find({list: jobs, key: 'id', val: id });
|
||||
}
|
||||
|
||||
if (job.status === 'pending' || job.status === 'running' || job.status === 'waiting') {
|
||||
url = job.related.cancel;
|
||||
action_label = 'cancel';
|
||||
hdr = i18n._('Cancel');
|
||||
} else {
|
||||
url = job.url;
|
||||
action_label = 'delete';
|
||||
hdr = i18n._('Delete');
|
||||
}
|
||||
|
||||
action = function () {
|
||||
Wait('start');
|
||||
Rest.setUrl(url);
|
||||
if (action_label === 'cancel') {
|
||||
Rest.post()
|
||||
.then(() => {
|
||||
$('#prompt-modal').modal('hide');
|
||||
if (callback) {
|
||||
scope.$emit(callback, action_label);
|
||||
}
|
||||
else {
|
||||
$state.reload();
|
||||
Wait('stop');
|
||||
}
|
||||
})
|
||||
.catch(({obj, status}) => {
|
||||
Wait('stop');
|
||||
$('#prompt-modal').modal('hide');
|
||||
if (status === 403) {
|
||||
Alert('Error', obj.detail);
|
||||
}
|
||||
// Ignore the error. The job most likely already finished.
|
||||
// ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url +
|
||||
// ' failed. POST returned status: ' + status });
|
||||
});
|
||||
} else {
|
||||
Rest.destroy()
|
||||
.then(() => {
|
||||
$('#prompt-modal').modal('hide');
|
||||
if (callback) {
|
||||
scope.$emit(callback, action_label);
|
||||
}
|
||||
else {
|
||||
let reloadListStateParams = null;
|
||||
|
||||
if(scope.jobs.length === 1 && $state.params.job_search && !_.isEmpty($state.params.job_search.page) && $state.params.job_search.page !== '1') {
|
||||
reloadListStateParams = _.cloneDeep($state.params);
|
||||
reloadListStateParams.job_search.page = (parseInt(reloadListStateParams.job_search.page)-1).toString();
|
||||
}
|
||||
|
||||
$state.go('.', reloadListStateParams, {reload: true});
|
||||
Wait('stop');
|
||||
}
|
||||
})
|
||||
.catch(({obj, status}) => {
|
||||
Wait('stop');
|
||||
$('#prompt-modal').modal('hide');
|
||||
if (status === 403) {
|
||||
Alert('Error', obj.detail);
|
||||
}
|
||||
// Ignore the error. The job most likely already finished.
|
||||
//ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url +
|
||||
// ' failed. DELETE returned status: ' + status });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (scope.removeCancelNotAllowed) {
|
||||
scope.removeCancelNotAllowed();
|
||||
}
|
||||
scope.removeCancelNotAllowed = scope.$on('CancelNotAllowed', function() {
|
||||
Wait('stop');
|
||||
Alert('Job Completed', 'The request to cancel the job could not be submitted. The job already completed.', 'alert-info');
|
||||
});
|
||||
|
||||
if (scope.removeCancelJob) {
|
||||
scope.removeCancelJob();
|
||||
}
|
||||
scope.removeCancelJob = scope.$on('CancelJob', function() {
|
||||
var cancelBody = "<div class=\"Prompt-bodyQuery\">" + i18n._("Are you sure you want to submit the request to cancel this job?") + "</div>";
|
||||
var deleteBody = "<div class=\"Prompt-bodyQuery\">" + i18n._("Are you sure you want to delete this job?") + "</div>";
|
||||
Prompt({
|
||||
hdr: hdr,
|
||||
resourceName: `#${job.id} ` + $filter('sanitize')(job.name),
|
||||
body: (action_label === 'cancel' || job.status === 'new') ? cancelBody : deleteBody,
|
||||
action: action,
|
||||
actionText: (action_label === 'cancel' || job.status === 'new') ? i18n._("OK") : i18n._("DELETE")
|
||||
});
|
||||
});
|
||||
|
||||
if (action_label === 'cancel') {
|
||||
Rest.setUrl(url);
|
||||
Rest.get()
|
||||
.then(({data}) => {
|
||||
if (data.can_cancel) {
|
||||
scope.$emit('CancelJob');
|
||||
}
|
||||
else {
|
||||
scope.$emit('CancelNotAllowed');
|
||||
}
|
||||
})
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url +
|
||||
' failed. GET returned: ' + status });
|
||||
});
|
||||
}
|
||||
else {
|
||||
scope.$emit('CancelJob');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
DeleteJob.$inject =
|
||||
[ '$state', 'Find', 'Rest', 'Wait',
|
||||
'ProcessErrors', 'Prompt', 'Alert', '$filter', 'i18n'
|
||||
];
|
||||
@ -1,36 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default
|
||||
['Rest', 'ProcessErrors', 'Empty', function(Rest, ProcessErrors, Empty) {
|
||||
return function(params) {
|
||||
var url = params.url,
|
||||
scope_var = params.scope_var,
|
||||
scope = params.scope,
|
||||
callback = params.callback;
|
||||
Rest.setUrl(url);
|
||||
Rest.get()
|
||||
.then(({data}) => {
|
||||
if (scope_var === 'inventory_source') {
|
||||
scope.inventory = data.inventory;
|
||||
}
|
||||
if (!Empty(data.name)) {
|
||||
scope[scope_var + '_name'] = data.name;
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
scope.$emit(callback, data);
|
||||
}
|
||||
})
|
||||
.catch(({data, status}) => {
|
||||
if (status === 403 && params.ignore_403) {
|
||||
return;
|
||||
}
|
||||
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'Failed to retrieve ' + url + '. GET returned: ' + status });
|
||||
});
|
||||
};
|
||||
}];
|
||||
@ -1,13 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import lookUpName from './lookup-name.factory';
|
||||
import DeleteJob from './delete-job.factory';
|
||||
|
||||
export default
|
||||
angular.module('StandardOutHelper', [])
|
||||
.factory('LookUpName', lookUpName)
|
||||
.factory('DeleteJob', DeleteJob);
|
||||
@ -1,266 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2015 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name controllers.function:JobStdout
|
||||
* @description This controller's for the standard out page that can be displayed when a job runs
|
||||
*/
|
||||
|
||||
export function JobStdoutController ($rootScope, $scope, $state, $stateParams,
|
||||
GetBasePath, Rest, ProcessErrors, Empty, GetChoices, LookUpName,
|
||||
ParseTypeChange, ParseVariableString, DeleteJob, Wait, i18n,
|
||||
fieldChoices, fieldLabels, Project, Alert, InventorySource,
|
||||
jobData) {
|
||||
|
||||
var job_id = $stateParams.id,
|
||||
jobType = $state.current.data.jobType;
|
||||
|
||||
// This scope variable controls whether or not the left panel is shown and the right panel
|
||||
// is expanded to take up the full screen
|
||||
$scope.stdoutFullScreen = false;
|
||||
$scope.toggleStdoutFullscreenTooltip = i18n._("Expand Output");
|
||||
|
||||
$scope.explanationLimit = 150;
|
||||
|
||||
// Listen for job status updates that may come across via sockets. We need to check the payload
|
||||
// to see whethere the updated job is the one that we're currently looking at.
|
||||
$scope.$on(`ws-jobs`, function(e, data) {
|
||||
if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10) && $scope.job) {
|
||||
$scope.job.status = data.status;
|
||||
}
|
||||
|
||||
if (data.status === 'failed' || data.status === 'canceled' || data.status === 'error' || data.status === 'successful') {
|
||||
// Go out and refresh the job details
|
||||
|
||||
Rest.setUrl(GetBasePath('base') + jobType + '/' + job_id + '/');
|
||||
Rest.get()
|
||||
.then(({data}) => {
|
||||
updateJobObj(data);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$scope.previousTaskFailed = false;
|
||||
|
||||
$scope.$watch('job.job_explanation', function(explanation) {
|
||||
if (explanation && explanation.split(":")[0] === "Previous Task Failed") {
|
||||
$scope.previousTaskFailed = true;
|
||||
|
||||
var taskObj = JSON.parse(explanation.substring(explanation.split(":")[0].length + 1));
|
||||
// return a promise from the options request with the permission type choices (including adhoc) as a param
|
||||
var fieldChoice = fieldChoices({
|
||||
$scope: $scope,
|
||||
url: GetBasePath('unified_jobs'),
|
||||
field: 'type'
|
||||
});
|
||||
|
||||
// manipulate the choices from the options request to be set on
|
||||
// scope and be usable by the list form
|
||||
fieldChoice.then(function (choices) {
|
||||
choices =
|
||||
fieldLabels({
|
||||
choices: choices
|
||||
});
|
||||
$scope.explanation_fail_type = choices[taskObj.job_type];
|
||||
$scope.explanation_fail_name = taskObj.job_name;
|
||||
$scope.explanation_fail_id = taskObj.job_id;
|
||||
$scope.task_detail = $scope.explanation_fail_type + " failed for " + $scope.explanation_fail_name + " with ID " + $scope.explanation_fail_id + ".";
|
||||
});
|
||||
} else {
|
||||
$scope.previousTaskFailed = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Set the parse type so that CodeMirror knows how to display extra params YAML/JSON
|
||||
$scope.parseType = 'yaml';
|
||||
|
||||
function updateJobObj(updatedJobData) {
|
||||
|
||||
// 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
|
||||
// of stdout jobs.
|
||||
|
||||
$scope.job = updatedJobData;
|
||||
$scope.job_template_name = updatedJobData.name;
|
||||
$scope.created_by = updatedJobData.summary_fields.created_by;
|
||||
$scope.project_name = (updatedJobData.summary_fields.project) ? updatedJobData.summary_fields.project.name : '';
|
||||
$scope.inventory_name = (updatedJobData.summary_fields.inventory) ? updatedJobData.summary_fields.inventory.name : '';
|
||||
$scope.job_template_url = '/#/templates/' + updatedJobData.unified_job_template;
|
||||
if($scope.inventory_name && updatedJobData.inventory && updatedJobData.summary_fields.inventory && updatedJobData.summary_fields.inventory.kind) {
|
||||
if(updatedJobData.summary_fields.inventory.kind === '') {
|
||||
$scope.inventory_url = '/#/inventories/inventory' + updatedJobData.inventory;
|
||||
}
|
||||
else if(updatedJobData.summary_fields.inventory.kind === 'smart') {
|
||||
$scope.inventory_url = '/#/inventories/smart_inventory' + updatedJobData.inventory;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$scope.inventory_url = '';
|
||||
}
|
||||
$scope.project_url = ($scope.project_name && updatedJobData.project) ? '/#/projects/' + updatedJobData.project : '';
|
||||
$scope.credential_name = (updatedJobData.summary_fields.credential) ? updatedJobData.summary_fields.credential.name : '';
|
||||
$scope.credential_url = (updatedJobData.credential) ? '/#/credentials/' + updatedJobData.credential : '';
|
||||
$scope.cloud_credential_url = (updatedJobData.cloud_credential) ? '/#/credentials/' + updatedJobData.cloud_credential : '';
|
||||
if(updatedJobData.summary_fields && updatedJobData.summary_fields.source_workflow_job &&
|
||||
updatedJobData.summary_fields.source_workflow_job.id){
|
||||
$scope.workflow_result_link = `/#/workflows/${updatedJobData.summary_fields.source_workflow_job.id}`;
|
||||
}
|
||||
$scope.playbook = updatedJobData.playbook;
|
||||
$scope.credential = updatedJobData.credential;
|
||||
$scope.cloud_credential = updatedJobData.cloud_credential;
|
||||
$scope.forks = updatedJobData.forks;
|
||||
$scope.limit = updatedJobData.limit;
|
||||
$scope.verbosity = updatedJobData.verbosity;
|
||||
$scope.job_tags = updatedJobData.job_tags;
|
||||
$scope.job.module_name = updatedJobData.module_name;
|
||||
if (updatedJobData.extra_vars) {
|
||||
$scope.variables = ParseVariableString(updatedJobData.extra_vars);
|
||||
}
|
||||
|
||||
$scope.$on('getInventorySource', function(e, d) {
|
||||
$scope.inv_manage_group_link = '/#/inventories/inventory/' + d.inventory + '/inventory_sources/edit/' + d.id;
|
||||
});
|
||||
|
||||
// If we have a source then we have to go get the source choices from the server
|
||||
if (!Empty(updatedJobData.source)) {
|
||||
if ($scope.removeChoicesReady) {
|
||||
$scope.removeChoicesReady();
|
||||
}
|
||||
$scope.removeChoicesReady = $scope.$on('ChoicesReady', function() {
|
||||
$scope.source_choices.every(function(e) {
|
||||
if (e.value === updatedJobData.source) {
|
||||
$scope.source = e.label;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
// GetChoices can be found in the helper: Utilities.js
|
||||
// It attaches the source choices to $scope.source_choices.
|
||||
// Then, when the callback is fired, $scope.source is bound
|
||||
// to the corresponding label.
|
||||
GetChoices({
|
||||
scope: $scope,
|
||||
url: GetBasePath('inventory_sources'),
|
||||
field: 'source',
|
||||
variable: 'source_choices',
|
||||
choice_name: 'choices',
|
||||
callback: 'ChoicesReady'
|
||||
});
|
||||
}
|
||||
|
||||
// LookUpName can be found in the lookup-name.factory
|
||||
// It attaches the name that it gets (based on the url)
|
||||
// to the $scope variable defined by the attribute scope_var.
|
||||
if (!Empty(updatedJobData.credential)) {
|
||||
LookUpName({
|
||||
scope: $scope,
|
||||
scope_var: 'credential',
|
||||
url: GetBasePath('credentials') + updatedJobData.credential + '/',
|
||||
ignore_403: true
|
||||
});
|
||||
}
|
||||
|
||||
if (!Empty(updatedJobData.inventory)) {
|
||||
LookUpName({
|
||||
scope: $scope,
|
||||
scope_var: 'inventory',
|
||||
url: GetBasePath('inventory') + updatedJobData.inventory + '/'
|
||||
});
|
||||
}
|
||||
|
||||
if (!Empty(updatedJobData.project)) {
|
||||
LookUpName({
|
||||
scope: $scope,
|
||||
scope_var: 'project',
|
||||
url: GetBasePath('projects') + updatedJobData.project + '/'
|
||||
});
|
||||
}
|
||||
|
||||
if (!Empty(updatedJobData.cloud_credential)) {
|
||||
LookUpName({
|
||||
scope: $scope,
|
||||
scope_var: 'cloud_credential',
|
||||
url: GetBasePath('credentials') + updatedJobData.cloud_credential + '/',
|
||||
ignore_403: true
|
||||
});
|
||||
}
|
||||
|
||||
if (!Empty(updatedJobData.inventory_source)) {
|
||||
LookUpName({
|
||||
scope: $scope,
|
||||
scope_var: 'inventory_source',
|
||||
url: GetBasePath('inventory_sources') + updatedJobData.inventory_source + '/',
|
||||
callback: 'getInventorySource'
|
||||
});
|
||||
}
|
||||
|
||||
if (updatedJobData.extra_vars) {
|
||||
ParseTypeChange({
|
||||
scope: $scope,
|
||||
field_id: 'pre-formatted-variables',
|
||||
readOnly: true
|
||||
});
|
||||
}
|
||||
|
||||
// If the job isn't running we want to clear out the interval that goes out and checks for stdout updates.
|
||||
// This interval is defined in the standard out log directive controller.
|
||||
if (updatedJobData.status === 'successful' || updatedJobData.status === 'failed' || updatedJobData.status === 'error' || updatedJobData.status === 'canceled') {
|
||||
if ($rootScope.jobStdOutInterval) {
|
||||
window.clearInterval($rootScope.jobStdOutInterval);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ($scope.removeDeleteFinished) {
|
||||
$scope.removeDeleteFinished();
|
||||
}
|
||||
$scope.removeDeleteFinished = $scope.$on('DeleteFinished', function(e, action) {
|
||||
Wait('stop');
|
||||
if (action !== 'cancel') {
|
||||
Wait('stop');
|
||||
$state.go('jobs');
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: this is currently not used but is necessary for cases where sockets
|
||||
// are not available and a manual refresh trigger is needed.
|
||||
$scope.refresh = function(){
|
||||
$scope.$emit('LoadStdout');
|
||||
};
|
||||
|
||||
// Click binding for the expand/collapse button on the standard out log
|
||||
$scope.toggleStdoutFullscreen = function() {
|
||||
$scope.stdoutFullScreen = !$scope.stdoutFullScreen;
|
||||
|
||||
if ($scope.stdoutFullScreen === true) {
|
||||
$scope.toggleStdoutFullscreenTooltip = i18n._("Collapse Output");
|
||||
} else if ($scope.stdoutFullScreen === false) {
|
||||
$scope.toggleStdoutFullscreenTooltip = i18n._("Expand Output");
|
||||
}
|
||||
};
|
||||
|
||||
$scope.deleteJob = function() {
|
||||
DeleteJob({
|
||||
scope: $scope,
|
||||
id: $scope.job.id,
|
||||
job: $scope.job,
|
||||
callback: 'DeleteFinished'
|
||||
});
|
||||
};
|
||||
|
||||
updateJobObj(jobData);
|
||||
|
||||
}
|
||||
|
||||
JobStdoutController.$inject = [ '$rootScope', '$scope', '$state',
|
||||
'$stateParams', 'GetBasePath', 'Rest', 'ProcessErrors',
|
||||
'Empty', 'GetChoices', 'LookUpName', 'ParseTypeChange',
|
||||
'ParseVariableString', 'DeleteJob', 'Wait', 'i18n',
|
||||
'fieldChoices', 'fieldLabels', 'ProjectModel', 'Alert', 'InventorySourceModel',
|
||||
'jobData'];
|
||||
@ -169,4 +169,4 @@ standard-out-log {
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
@ -1,701 +0,0 @@
|
||||
'use strict';
|
||||
import moment from 'moment';
|
||||
|
||||
describe('Controller: jobResultsController', () => {
|
||||
// Setup
|
||||
let jobResultsController;
|
||||
|
||||
let jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTypeChange, ParseVariableString, jobResultsService, eventQueue, $compile, eventResolve, populateResolve, $rScope, q, $log, Dataset, Rest, $state, QuerySet, i18n,fieldChoices, fieldLabels, $interval, workflowResultsService, statusSocket, jobExtraCredentials;
|
||||
|
||||
statusSocket = function() {
|
||||
var fn = function() {};
|
||||
return fn;
|
||||
};
|
||||
jobData = {
|
||||
related: {},
|
||||
summary_fields: {
|
||||
inventory: {
|
||||
id: null,
|
||||
kind: ''
|
||||
}
|
||||
}
|
||||
};
|
||||
jobDataOptions = {
|
||||
actions: {
|
||||
get: {}
|
||||
}
|
||||
};
|
||||
jobLabels = {};
|
||||
jobFinished = true;
|
||||
count = {
|
||||
val: {},
|
||||
countFinished: false
|
||||
};
|
||||
eventResolve = {
|
||||
results: []
|
||||
};
|
||||
populateResolve = {};
|
||||
|
||||
Dataset = {
|
||||
data: {foo: "bar"}
|
||||
};
|
||||
|
||||
let provideVals = () => {
|
||||
angular.mock.module('jobResults', ($provide) => {
|
||||
ParseTypeChange = jasmine.createSpy('ParseTypeChange');
|
||||
ParseVariableString = jasmine.createSpy('ParseVariableString');
|
||||
jobResultsService = jasmine.createSpyObj('jobResultsService', [
|
||||
'deleteJob',
|
||||
'cancelJob',
|
||||
'relaunchJob',
|
||||
'getEvents',
|
||||
'getJobData',
|
||||
]);
|
||||
eventQueue = jasmine.createSpyObj('eventQueue', [
|
||||
'populate',
|
||||
'markProcessed',
|
||||
'initialize'
|
||||
]);
|
||||
|
||||
Rest = jasmine.createSpyObj('Rest', [
|
||||
'setUrl',
|
||||
'get'
|
||||
]);
|
||||
|
||||
$state = jasmine.createSpyObj('$state', [
|
||||
'reload'
|
||||
]);
|
||||
|
||||
QuerySet = jasmine.createSpyObj('QuerySet', [
|
||||
'encodeQueryset'
|
||||
]);
|
||||
|
||||
i18n = {
|
||||
_: function(txt) {
|
||||
return txt;
|
||||
}
|
||||
};
|
||||
|
||||
$provide.service('workflowResultsService', () => {
|
||||
return jasmine.createSpyObj('workflowResultsService', ['createOneSecondTimer', 'destroyTimer']);
|
||||
});
|
||||
|
||||
$provide.value('statusSocket', statusSocket);
|
||||
|
||||
$provide.value('jobData', jobData);
|
||||
$provide.value('jobDataOptions', jobDataOptions);
|
||||
$provide.value('jobLabels', jobLabels);
|
||||
$provide.value('jobFinished', jobFinished);
|
||||
$provide.value('count', count);
|
||||
$provide.value('ParseTypeChange', ParseTypeChange);
|
||||
$provide.value('ParseVariableString', ParseVariableString);
|
||||
$provide.value('jobResultsService', jobResultsService);
|
||||
$provide.value('eventQueue', eventQueue);
|
||||
$provide.value('Dataset', Dataset);
|
||||
$provide.value('Rest', Rest);
|
||||
$provide.value('$state', $state);
|
||||
$provide.value('QuerySet', QuerySet);
|
||||
$provide.value('i18n', i18n);
|
||||
$provide.value('fieldChoices', fieldChoices);
|
||||
$provide.value('fieldLabels', fieldLabels);
|
||||
$provide.value('jobExtraCredentials', jobExtraCredentials);
|
||||
});
|
||||
};
|
||||
|
||||
let injectVals = () => {
|
||||
angular.mock.inject((_jobData_, _jobDataOptions_, _jobLabels_, _jobFinished_, _count_, _ParseTypeChange_, _ParseVariableString_, _jobResultsService_, _eventQueue_, _$compile_, $rootScope, $controller, $q, $httpBackend, _$log_, _Dataset_, _Rest_, _$state_, _QuerySet_, _$interval_, _workflowResultsService_, _statusSocket_) => {
|
||||
// when you call $scope.$apply() (which you need to do to
|
||||
// to get inside of .then blocks to test), something is
|
||||
// causing a request for all static files.
|
||||
//
|
||||
// this is a hack to just pass those requests through
|
||||
//
|
||||
// from googling this is probably due to angular-router
|
||||
// weirdness
|
||||
$httpBackend.when("GET", (url) => (url
|
||||
.indexOf("/static/") !== -1))
|
||||
.respond('');
|
||||
|
||||
$httpBackend
|
||||
.whenGET('/api')
|
||||
.respond(200, '');
|
||||
|
||||
$scope = $rootScope.$new();
|
||||
$rScope = $rootScope;
|
||||
q = $q;
|
||||
jobData = _jobData_;
|
||||
jobDataOptions = _jobDataOptions_;
|
||||
jobLabels = _jobLabels_;
|
||||
jobFinished = _jobFinished_;
|
||||
count = _count_;
|
||||
ParseTypeChange = _ParseTypeChange_;
|
||||
ParseVariableString = _ParseVariableString_;
|
||||
ParseVariableString.and.returnValue(jobData.extra_vars);
|
||||
jobResultsService = _jobResultsService_;
|
||||
eventQueue = _eventQueue_;
|
||||
$log = _$log_;
|
||||
Dataset = _Dataset_;
|
||||
Rest = _Rest_;
|
||||
$state = _$state_;
|
||||
QuerySet = _QuerySet_;
|
||||
$interval = _$interval_;
|
||||
workflowResultsService = _workflowResultsService_;
|
||||
statusSocket = _statusSocket_;
|
||||
|
||||
jobResultsService.getEvents.and
|
||||
.returnValue(eventResolve);
|
||||
eventQueue.populate.and
|
||||
.returnValue(populateResolve);
|
||||
|
||||
jobResultsService.getJobData = function() {
|
||||
var deferred = $q.defer();
|
||||
deferred.resolve({});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
$compile = _$compile_;
|
||||
|
||||
jobResultsController = $controller('jobResultsController', {
|
||||
$scope: $scope,
|
||||
jobData: jobData,
|
||||
jobDataOptions: jobDataOptions,
|
||||
jobLabels: jobLabels,
|
||||
jobFinished: jobFinished,
|
||||
count: count,
|
||||
ParseTypeChange: ParseTypeChange,
|
||||
jobResultsService: jobResultsService,
|
||||
eventQueue: eventQueue,
|
||||
$compile: $compile,
|
||||
$log: $log,
|
||||
$q: q,
|
||||
Dataset: Dataset,
|
||||
Rest: Rest,
|
||||
$state: $state,
|
||||
QuerySet: QuerySet,
|
||||
statusSocket: statusSocket
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(angular.mock.module('shared'));
|
||||
|
||||
let bootstrapTest = () => {
|
||||
provideVals();
|
||||
injectVals();
|
||||
};
|
||||
|
||||
describe('bootstrap resolve values on scope', () => {
|
||||
beforeEach(() => {
|
||||
bootstrapTest();
|
||||
});
|
||||
|
||||
it('should set values to scope based on resolve', () => {
|
||||
expect($scope.job).toBe(jobData);
|
||||
expect($scope.jobOptions).toBe(jobDataOptions.actions.GET);
|
||||
expect($scope.labels).toBe(jobLabels);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLinks()', () => {
|
||||
beforeEach(() => {
|
||||
jobData.related = {
|
||||
"created_by": "api/v2/users/12",
|
||||
"inventory": "api/v2/inventories/12",
|
||||
"project": "api/v2/projects/12",
|
||||
"credential": "api/v2/credentials/12",
|
||||
"cloud_credential": "api/v2/credentials/13",
|
||||
"network_credential": "api/v2/credentials/14",
|
||||
};
|
||||
|
||||
jobData.summary_fields.inventory = {
|
||||
id: 12,
|
||||
kind: ''
|
||||
};
|
||||
|
||||
bootstrapTest();
|
||||
});
|
||||
|
||||
it('should transform related links and set to scope var', () => {
|
||||
expect($scope.created_by_link).toBe('/#/users/12');
|
||||
expect($scope.inventory_link).toBe('/#/inventories/inventory/12');
|
||||
expect($scope.project_link).toBe('/#/projects/12');
|
||||
expect($scope.machine_credential_link).toBe('/#/credentials/12');
|
||||
expect($scope.cloud_credential_link).toBe('/#/credentials/13');
|
||||
expect($scope.network_credential_link).toBe('/#/credentials/14');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLabels()', () => {
|
||||
beforeEach(() => {
|
||||
jobDataOptions.actions.GET = {
|
||||
status: {
|
||||
choices: [
|
||||
["new",
|
||||
"New"]
|
||||
]
|
||||
},
|
||||
job_type: {
|
||||
choices: [
|
||||
["job",
|
||||
"Playbook Run"]
|
||||
]
|
||||
},
|
||||
verbosity: {
|
||||
choices: [
|
||||
[0,
|
||||
"0 (Normal)"]
|
||||
]
|
||||
}
|
||||
};
|
||||
jobData.status = "new";
|
||||
jobData.job_type = "job";
|
||||
jobData.verbosity = 0;
|
||||
|
||||
bootstrapTest();
|
||||
});
|
||||
|
||||
it('should set scope variables based on options', () => {
|
||||
$scope.job_status = jobData.status;
|
||||
|
||||
$scope.$apply();
|
||||
expect($scope.status_label).toBe("New");
|
||||
expect($scope.type_label).toBe("Playbook Run");
|
||||
expect($scope.verbosity_label).toBe("0 (Normal)");
|
||||
});
|
||||
});
|
||||
|
||||
describe('elapsed timer', () => {
|
||||
describe('job running', () => {
|
||||
beforeEach(() => {
|
||||
jobData.started = moment();
|
||||
jobData.status = 'running';
|
||||
|
||||
bootstrapTest();
|
||||
});
|
||||
|
||||
it('should start timer', () => {
|
||||
expect(workflowResultsService.createOneSecondTimer).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('job waiting', () => {
|
||||
beforeEach(() => {
|
||||
jobData.started = null;
|
||||
jobData.status = 'waiting';
|
||||
|
||||
bootstrapTest();
|
||||
});
|
||||
|
||||
it('should not start timer', () => {
|
||||
expect(workflowResultsService.createOneSecondTimer).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('job transitions to running', () => {
|
||||
beforeEach(() => {
|
||||
jobData.started = null;
|
||||
jobData.status = 'waiting';
|
||||
jobData.id = 13;
|
||||
|
||||
bootstrapTest();
|
||||
|
||||
$rScope.$broadcast('ws-jobs', { unified_job_id: jobData.id, status: 'running' });
|
||||
});
|
||||
|
||||
it('should start timer', () => {
|
||||
expect(workflowResultsService.createOneSecondTimer).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('job transitions from running to finished', () => {
|
||||
it('should cleanup timer', () => {
|
||||
$rScope.$broadcast('ws-jobs', { unified_job_id: jobData.id, status: 'successful' });
|
||||
expect(workflowResultsService.destroyTimer).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('extra vars stuff', () => {
|
||||
let extraVars = "foo";
|
||||
|
||||
beforeEach(() => {
|
||||
jobData.extra_vars = extraVars;
|
||||
|
||||
bootstrapTest();
|
||||
});
|
||||
|
||||
it('should have extra vars on scope', () => {
|
||||
expect($scope.job.extra_vars).toBe(extraVars);
|
||||
});
|
||||
|
||||
it('should call ParseVariableString and set to scope', () => {
|
||||
expect(ParseVariableString)
|
||||
.toHaveBeenCalledWith(extraVars);
|
||||
expect($scope.variables).toBe(extraVars);
|
||||
});
|
||||
|
||||
it('should set the parse type to yaml', () => {
|
||||
expect($scope.parseType).toBe('yaml');
|
||||
});
|
||||
|
||||
it('should call ParseTypeChange with proper params', () => {
|
||||
let params = {
|
||||
scope: $scope,
|
||||
field_id: 'pre-formatted-variables',
|
||||
readOnly: true
|
||||
};
|
||||
|
||||
expect(ParseTypeChange)
|
||||
.toHaveBeenCalledWith(params);
|
||||
});
|
||||
});
|
||||
|
||||
describe('$scope.toggleStdoutFullscreen', () => {
|
||||
beforeEach(() => {
|
||||
bootstrapTest();
|
||||
});
|
||||
|
||||
it('should toggle $scope.stdoutFullScreen', () => {
|
||||
// essentially set to false
|
||||
expect($scope.stdoutFullScreen).toBe(false);
|
||||
|
||||
// toggle once to true
|
||||
$scope.toggleStdoutFullscreen();
|
||||
expect($scope.stdoutFullScreen).toBe(true);
|
||||
|
||||
// toggle again to false
|
||||
$scope.toggleStdoutFullscreen();
|
||||
expect($scope.stdoutFullScreen).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('$scope.deleteJob', () => {
|
||||
beforeEach(() => {
|
||||
bootstrapTest();
|
||||
});
|
||||
|
||||
it('should delete the job', () => {
|
||||
let job = $scope.job;
|
||||
$scope.deleteJob();
|
||||
expect(jobResultsService.deleteJob).toHaveBeenCalledWith(job);
|
||||
});
|
||||
});
|
||||
|
||||
describe('$scope.cancelJob', () => {
|
||||
beforeEach(() => {
|
||||
bootstrapTest();
|
||||
});
|
||||
|
||||
it('should cancel the job', () => {
|
||||
let job = $scope.job;
|
||||
$scope.cancelJob();
|
||||
expect(jobResultsService.cancelJob).toHaveBeenCalledWith(job);
|
||||
});
|
||||
});
|
||||
|
||||
describe('count stuff', () => {
|
||||
beforeEach(() => {
|
||||
count = {
|
||||
val: {
|
||||
ok: 1,
|
||||
skipped: 2,
|
||||
unreachable: 3,
|
||||
failures: 4,
|
||||
changed: 5
|
||||
},
|
||||
countFinished: true
|
||||
};
|
||||
|
||||
bootstrapTest();
|
||||
});
|
||||
|
||||
it('should set count values to scope', () => {
|
||||
expect($scope.count).toBe(count.val);
|
||||
expect($scope.countFinished).toBe(true);
|
||||
});
|
||||
|
||||
it('should find the hostCount based on the count', () => {
|
||||
expect($scope.hostCount).toBe(15);
|
||||
});
|
||||
});
|
||||
|
||||
describe('follow stuff - incomplete', () => {
|
||||
beforeEach(() => {
|
||||
jobFinished = false;
|
||||
|
||||
bootstrapTest();
|
||||
});
|
||||
|
||||
it('should set followEngaged based on jobFinished incomplete', () => {
|
||||
expect($scope.followEngaged).toBe(true);
|
||||
});
|
||||
|
||||
it('should set followTooltip based on jobFinished incomplete', () => {
|
||||
expect($scope.followTooltip).toBe("Currently following standard out as it comes in. Click to unfollow.");
|
||||
});
|
||||
});
|
||||
|
||||
describe('follow stuff - finished', () => {
|
||||
beforeEach(() => {
|
||||
jobFinished = true;
|
||||
|
||||
bootstrapTest();
|
||||
});
|
||||
|
||||
it('should set followEngaged based on jobFinished', () => {
|
||||
expect($scope.followEngaged).toBe(false);
|
||||
});
|
||||
|
||||
it('should set followTooltip based on jobFinished', () => {
|
||||
expect($scope.followTooltip).toBe("Jump to last line of standard out.");
|
||||
});
|
||||
});
|
||||
|
||||
describe('event stuff', () => {
|
||||
beforeEach(() => {
|
||||
jobData.id = 1;
|
||||
jobData.related.job_events = "url";
|
||||
|
||||
bootstrapTest();
|
||||
});
|
||||
|
||||
xit('should make a rest call to get already completed events', () => {
|
||||
expect(jobResultsService.getEvents).toHaveBeenCalledWith("url");
|
||||
});
|
||||
|
||||
xit('should call processEvent when receiving message', () => {
|
||||
let eventPayload = {"foo": "bar"};
|
||||
$rScope.$broadcast('ws-job_events-1', eventPayload);
|
||||
expect(eventQueue.populate).toHaveBeenCalledWith(eventPayload);
|
||||
});
|
||||
|
||||
it('should set the job status on scope when receiving message', () => {
|
||||
let eventPayload = {
|
||||
unified_job_id: 1,
|
||||
status: 'finished'
|
||||
};
|
||||
$rScope.$broadcast('ws-jobs', eventPayload);
|
||||
expect($scope.job_status).toBe(eventPayload.status);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getEvents and populate stuff', () => {
|
||||
describe('getEvents', () => {
|
||||
let event1 = {
|
||||
event: 'foo'
|
||||
};
|
||||
|
||||
let event2 = {
|
||||
event_name: 'bar'
|
||||
};
|
||||
|
||||
let event1Processed = {
|
||||
event_name: 'foo'
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
eventResolve = {
|
||||
results: [
|
||||
event1,
|
||||
event2
|
||||
]
|
||||
};
|
||||
|
||||
bootstrapTest();
|
||||
|
||||
$scope.$apply();
|
||||
});
|
||||
|
||||
xit('should change the event name to event_name', () => {
|
||||
expect(eventQueue.populate)
|
||||
.toHaveBeenCalledWith(event1Processed);
|
||||
});
|
||||
|
||||
xit('should pass through the event with event_name', () => {
|
||||
expect(eventQueue.populate)
|
||||
.toHaveBeenCalledWith(event2);
|
||||
});
|
||||
|
||||
xit('should have called populate twice', () => {
|
||||
expect(eventQueue.populate.calls.count()).toEqual(2);
|
||||
});
|
||||
|
||||
// TODO: can't figure out how to a test of events.next...
|
||||
// if you set events.next to true it causes the tests to
|
||||
// stop running
|
||||
});
|
||||
|
||||
describe('populate - start time', () => {
|
||||
beforeEach(() => {
|
||||
jobData.start = "";
|
||||
|
||||
populateResolve = {
|
||||
startTime: 'foo',
|
||||
changes: ['startTime']
|
||||
};
|
||||
|
||||
bootstrapTest();
|
||||
|
||||
$scope.$apply();
|
||||
});
|
||||
|
||||
xit('sets start time when passed as a change', () => {
|
||||
expect($scope.job.start).toBe('foo');
|
||||
});
|
||||
});
|
||||
|
||||
describe('populate - start time already set', () => {
|
||||
beforeEach(() => {
|
||||
jobData.start = "bar";
|
||||
|
||||
populateResolve = {
|
||||
startTime: 'foo',
|
||||
changes: ['startTime']
|
||||
};
|
||||
|
||||
bootstrapTest();
|
||||
|
||||
$scope.$apply();
|
||||
});
|
||||
|
||||
xit('does not set start time because already set', () => {
|
||||
expect($scope.job.start).toBe('bar');
|
||||
});
|
||||
});
|
||||
|
||||
describe('populate - count already received', () => {
|
||||
let receiveCount = {
|
||||
ok: 2,
|
||||
skipped: 2,
|
||||
unreachable: 2,
|
||||
failures: 2,
|
||||
changed: 2
|
||||
};
|
||||
|
||||
let alreadyCount = {
|
||||
ok: 3,
|
||||
skipped: 3,
|
||||
unreachable: 3,
|
||||
failures: 3,
|
||||
changed: 3
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
count.countFinished = true;
|
||||
count.val = alreadyCount;
|
||||
|
||||
populateResolve = {
|
||||
count: receiveCount,
|
||||
changes: ['count']
|
||||
};
|
||||
|
||||
bootstrapTest();
|
||||
|
||||
$scope.$apply();
|
||||
});
|
||||
|
||||
xit('count does not change', () => {
|
||||
expect($scope.count).toBe(alreadyCount);
|
||||
expect($scope.hostCount).toBe(15);
|
||||
});
|
||||
});
|
||||
|
||||
describe('populate - playCount, taskCount and countFinished', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
populateResolve = {
|
||||
playCount: 12,
|
||||
taskCount: 13,
|
||||
changes: ['playCount', 'taskCount', 'countFinished']
|
||||
};
|
||||
|
||||
bootstrapTest();
|
||||
|
||||
$scope.$apply();
|
||||
});
|
||||
|
||||
xit('sets playCount', () => {
|
||||
expect($scope.playCount).toBe(12);
|
||||
});
|
||||
|
||||
xit('sets taskCount', () => {
|
||||
expect($scope.taskCount).toBe(13);
|
||||
});
|
||||
|
||||
xit('sets countFinished', () => {
|
||||
expect($scope.countFinished).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('populate - finishedTime', () => {
|
||||
beforeEach(() => {
|
||||
jobData.finished = "";
|
||||
|
||||
populateResolve = {
|
||||
finishedTime: "finished_time",
|
||||
changes: ['finishedTime']
|
||||
};
|
||||
|
||||
bootstrapTest();
|
||||
|
||||
$scope.$apply();
|
||||
});
|
||||
|
||||
xit('sets finished time and changes follow tooltip', () => {
|
||||
expect($scope.job.finished).toBe('finished_time');
|
||||
expect($scope.jobFinished).toBe(true);
|
||||
expect($scope.followTooltip)
|
||||
.toBe("Jump to last line of standard out.");
|
||||
});
|
||||
});
|
||||
|
||||
describe('populate - finishedTime when already finished', () => {
|
||||
beforeEach(() => {
|
||||
jobData.finished = "already_set";
|
||||
|
||||
populateResolve = {
|
||||
finishedTime: "finished_time",
|
||||
changes: ['finishedTime']
|
||||
};
|
||||
|
||||
bootstrapTest();
|
||||
|
||||
$scope.$apply();
|
||||
});
|
||||
|
||||
xit('does not set finished time because already set', () => {
|
||||
expect($scope.job.finished).toBe('already_set');
|
||||
expect($scope.jobFinished).toBe(true);
|
||||
expect($scope.followTooltip)
|
||||
.toBe("Jump to last line of standard out.");
|
||||
});
|
||||
});
|
||||
|
||||
describe('populate - stdout', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
populateResolve = {
|
||||
counter: 12,
|
||||
stdout: "line",
|
||||
changes: ['stdout']
|
||||
};
|
||||
|
||||
bootstrapTest();
|
||||
|
||||
spyOn($log, 'error');
|
||||
|
||||
$scope.followEngaged = true;
|
||||
|
||||
$scope.$apply();
|
||||
});
|
||||
|
||||
xit('creates new child scope for the event', () => {
|
||||
expect($scope.events[12].event).toBe(populateResolve);
|
||||
|
||||
// in unit test, followScroll should not be defined as
|
||||
// directive has not been instantiated
|
||||
expect($log.error).toHaveBeenCalledWith("follow scroll undefined, standard out directive not loaded yet?");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,50 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
describe('jobResultsService', () => {
|
||||
let jobResultsService;
|
||||
|
||||
beforeEach(angular.mock.module('awApp'));
|
||||
|
||||
beforeEach(angular.mock.inject(( _jobResultsService_) => {
|
||||
jobResultsService = _jobResultsService_;
|
||||
}));
|
||||
|
||||
describe('getCountsFromStatsEvent()', () => {
|
||||
it('properly counts hosts based on task state', () => {
|
||||
let event_data = {
|
||||
"skipped": {
|
||||
"skipped-host": 5 // this host skipped all 5 tasks
|
||||
},
|
||||
"ok": {
|
||||
"ok-host": 5, // this host was ok on all 5 tasks
|
||||
"changed-host": 4 // this host had 4 ok tasks, had 1 changed task
|
||||
},
|
||||
"changed": {
|
||||
"changed-host": 1
|
||||
},
|
||||
"failures": {
|
||||
"failed-host": 1 // this host had a failed task
|
||||
},
|
||||
"dark": {
|
||||
"unreachable-host": 1 // this host was unreachable
|
||||
},
|
||||
"processed": {
|
||||
"ok-host": 1,
|
||||
"changed-host": 1,
|
||||
"skipped-host": 1,
|
||||
"failed-host": 1,
|
||||
"unreachable-host": 1
|
||||
},
|
||||
"playbook_uuid": "c23d8872-c92a-4e96-9f78-abe6fef38f33",
|
||||
"playbook": "some_playbook.yml",
|
||||
};
|
||||
expect(jobResultsService.getCountsFromStatsEvent(event_data)).toEqual({
|
||||
'ok': 1,
|
||||
'skipped': 1,
|
||||
'unreachable': 1,
|
||||
'failures': 1,
|
||||
'changed': 1
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,212 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
describe('parseStdoutService', () => {
|
||||
let parseStdoutService,
|
||||
log;
|
||||
|
||||
beforeEach(angular.mock.module('awApp'));
|
||||
|
||||
beforeEach(angular.mock.module('jobResults',($provide) => {
|
||||
log = jasmine.createSpyObj('$log', [
|
||||
'error'
|
||||
]);
|
||||
|
||||
$provide.value('$log', log);
|
||||
}));
|
||||
|
||||
beforeEach(angular.mock.inject((_$log_, _parseStdoutService_) => {
|
||||
parseStdoutService = _parseStdoutService_;
|
||||
}));
|
||||
|
||||
describe('prettify()', () => {
|
||||
it('returns lines of stdout with styling classes', () => {
|
||||
let line = "[0;32mok: [host-00][0m",
|
||||
styledLine = '<span class="ansi32">ok: [host-00]</span>';
|
||||
expect(parseStdoutService.prettify(line)).toBe(styledLine);
|
||||
});
|
||||
|
||||
it('can return lines of stdout without styling classes', () => {
|
||||
let line = "[0;32mok: [host-00][0m",
|
||||
unstyled = "unstyled",
|
||||
unstyledLine = 'ok: [host-00]';
|
||||
expect(parseStdoutService.prettify(line, unstyled)).toBe(unstyledLine);
|
||||
});
|
||||
|
||||
it('can return empty strings', () => {
|
||||
expect(parseStdoutService.prettify("")).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLineClasses()', () => {
|
||||
it('creates a string that is used as a class', () => {
|
||||
let headerEvent = {
|
||||
event_name: 'playbook_on_task_start',
|
||||
event_data: {
|
||||
play_uuid:"0f667a23-d9ab-4128-a735-80566bcdbca0",
|
||||
task_uuid: "80dd087c-268b-45e8-9aab-1083bcfd9364"
|
||||
}
|
||||
};
|
||||
let lineNum = 3;
|
||||
let line = "TASK [setup] *******************************************************************";
|
||||
let styledLine = " header_task header_task_80dd087c-268b-45e8-9aab-1083bcfd9364 actual_header play_0f667a23-d9ab-4128-a735-80566bcdbca0 line_num_3";
|
||||
expect(parseStdoutService.getLineClasses(headerEvent, line, lineNum)).toBe(styledLine);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getStartTime()', () => {
|
||||
// TODO: the problem is that the date here calls moment, and thus
|
||||
// the date will be timezone'd in the string (this could be
|
||||
// different based on where you are)
|
||||
xit('creates returns a badge with the start time of the event', () => {
|
||||
let headerEvent = {
|
||||
event_name: 'playbook_on_play_start',
|
||||
created: "2016-11-22T21:15:54.736Z"
|
||||
};
|
||||
|
||||
let line = "PLAY [add hosts to inventory] **************************************************";
|
||||
let badgeDiv = '<div class="badge JobResults-timeBadge ng-binding">13:15:54</div>';
|
||||
expect(parseStdoutService.getStartTimeBadge(headerEvent, line)).toBe(badgeDiv);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCollapseIcon()', () => {
|
||||
let emptySpan = `
|
||||
<span class="JobResultsStdOut-lineExpander"></span>`;
|
||||
|
||||
it('returns empty expander for non-header event', () => {
|
||||
let nonHeaderEvent = {
|
||||
event_name: 'not_header',
|
||||
start_line: 0,
|
||||
end_line: 1,
|
||||
stdout:"line1"
|
||||
};
|
||||
expect(parseStdoutService.getCollapseIcon(nonHeaderEvent))
|
||||
.toBe(emptySpan);
|
||||
});
|
||||
|
||||
it('returns collapse/decollapse icons for header events', () => {
|
||||
let headerEvent = {
|
||||
event_name: 'playbook_on_task_start',
|
||||
start_line: 0,
|
||||
end_line: 1,
|
||||
stdout:"line1",
|
||||
event_data: {
|
||||
task_uuid: '1da9012d-18e6-4562-85cd-83cf10a97f86'
|
||||
}
|
||||
};
|
||||
let line = "TASK [setup] *******************************************************************";
|
||||
let expandSpan = `
|
||||
<span class="JobResultsStdOut-lineExpander">
|
||||
<i class="JobResultsStdOut-lineExpanderIcon fa fa-caret-down expanderizer
|
||||
expanderizer--task expanded"
|
||||
ng-click="toggleLine($event, '.task_1da9012d-18e6-4562-85cd-83cf10a97f86')"
|
||||
data-uuid="task_1da9012d-18e6-4562-85cd-83cf10a97f86">
|
||||
</i>
|
||||
</span>`;
|
||||
|
||||
expect(parseStdoutService.getCollapseIcon(headerEvent, line))
|
||||
.toBe(expandSpan);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLineArr()', () => {
|
||||
it('returns stdout in array format', () => {
|
||||
let mockEvent = {
|
||||
start_line: 12,
|
||||
end_line: 14,
|
||||
stdout: "line1\r\nline2\r\n"
|
||||
};
|
||||
let expectedReturn = [[13, "line1"],[14, "line2"]];
|
||||
|
||||
let returnedEvent = parseStdoutService.getLineArr(mockEvent);
|
||||
|
||||
expect(returnedEvent).toEqual(expectedReturn);
|
||||
});
|
||||
|
||||
it('deals correctly with capped lines', () => {
|
||||
let mockEvent = {
|
||||
start_line: 7,
|
||||
end_line: 11,
|
||||
stdout: "a\r\nb\r\nc..."
|
||||
};
|
||||
let expectedReturn = [[8, "a"],[9, "b"], [10,"c..."]];
|
||||
|
||||
let returnedEvent = parseStdoutService.getLineArr(mockEvent);
|
||||
|
||||
expect(returnedEvent).toEqual(expectedReturn);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseStdout()', () => {
|
||||
let mockEvent = {"foo": "bar"};
|
||||
|
||||
it('calls functions', function() {
|
||||
spyOn(parseStdoutService, 'getLineArr').and
|
||||
.returnValue([[13, 'line1'], [14, 'line2']]);
|
||||
spyOn(parseStdoutService, 'getLineClasses').and
|
||||
.returnValue("");
|
||||
spyOn(parseStdoutService, 'getCollapseIcon').and
|
||||
.returnValue("");
|
||||
spyOn(parseStdoutService, 'getAnchorTags').and
|
||||
.returnValue("");
|
||||
spyOn(parseStdoutService, 'prettify').and
|
||||
.returnValue("prettified_line");
|
||||
spyOn(parseStdoutService, 'getStartTimeBadge').and
|
||||
.returnValue("");
|
||||
|
||||
parseStdoutService.parseStdout(mockEvent);
|
||||
|
||||
expect(parseStdoutService.getLineArr)
|
||||
.toHaveBeenCalledWith(mockEvent);
|
||||
expect(parseStdoutService.getLineClasses)
|
||||
.toHaveBeenCalledWith(mockEvent, 'line1', 13);
|
||||
expect(parseStdoutService.getCollapseIcon)
|
||||
.toHaveBeenCalledWith(mockEvent, 'line1');
|
||||
expect(parseStdoutService.getAnchorTags)
|
||||
.toHaveBeenCalledWith(mockEvent);
|
||||
expect(parseStdoutService.prettify)
|
||||
.toHaveBeenCalledWith('line1');
|
||||
expect(parseStdoutService.getStartTimeBadge)
|
||||
.toHaveBeenCalledWith(mockEvent, 'line1');
|
||||
|
||||
// get line arr should be called once for the event
|
||||
expect(parseStdoutService.getLineArr.calls.count())
|
||||
.toBe(1);
|
||||
|
||||
// other functions should be called twice (once for each
|
||||
// line)
|
||||
expect(parseStdoutService.getLineClasses.calls.count())
|
||||
.toBe(2);
|
||||
expect(parseStdoutService.getCollapseIcon.calls.count())
|
||||
.toBe(2);
|
||||
expect(parseStdoutService.getAnchorTags.calls.count())
|
||||
.toBe(2);
|
||||
expect(parseStdoutService.prettify.calls.count())
|
||||
.toBe(2);
|
||||
});
|
||||
|
||||
it('returns dom-ified lines', function() {
|
||||
spyOn(parseStdoutService, 'getLineArr').and
|
||||
.returnValue([[13, 'line1']]);
|
||||
spyOn(parseStdoutService, 'getLineClasses').and
|
||||
.returnValue("line_classes");
|
||||
spyOn(parseStdoutService, 'getCollapseIcon').and
|
||||
.returnValue("collapse_icon_dom");
|
||||
spyOn(parseStdoutService, 'getAnchorTags').and
|
||||
.returnValue(`" anchor_tag_dom`);
|
||||
spyOn(parseStdoutService, 'prettify').and
|
||||
.returnValue("prettified_line");
|
||||
spyOn(parseStdoutService, 'getStartTimeBadge').and
|
||||
.returnValue("");
|
||||
|
||||
var returnedString = parseStdoutService.parseStdout(mockEvent);
|
||||
|
||||
var expectedString = `
|
||||
<div class="JobResultsStdOut-aLineOfStdOutline_classes">
|
||||
<div class="JobResultsStdOut-lineNumberColumn">collapse_icon_dom13</div>
|
||||
<div class="JobResultsStdOut-stdoutColumn" anchor_tag_dom><span ng-non-bindable>prettified_line</span></div>
|
||||
</div>`;
|
||||
expect(returnedString).toBe(expectedString);
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user