remove unused code

This commit is contained in:
Jake McDermott 2018-04-05 00:51:45 -04:00
parent 01d9c8546e
commit cf68df41d5
No known key found for this signature in database
GPG Key ID: 3B02CAD476EECB35
40 changed files with 253 additions and 5770 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
// 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;
}];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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'
];

View File

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

View File

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

View File

@ -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'];

View File

@ -169,4 +169,4 @@ standard-out-log {
cursor: pointer;
border-radius: 5px;
font-size: 11px;
}
}

View File

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

View File

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

View File

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