Merge branch 'release_3.0.0' of github.com:ansible/ansible-tower into JobDetailHostEvents

This commit is contained in:
Akita Noek
2016-06-01 14:09:51 -04:00
72 changed files with 1333 additions and 925 deletions

View File

@@ -15,13 +15,13 @@
multiSelectExtended: true,
index: false,
hover: true,
emptyListText : 'No Teams exist',
fields: {
name: {
key: true,
label: 'name'
},
},
}
};
}

View File

@@ -16,6 +16,7 @@
multiSelectExtended: true,
index: false,
hover: true,
emptyListText : 'No Users exist',
fields: {
first_name: {

View File

@@ -23,7 +23,7 @@ export default
return i.role;
}))
.filter((role) => {
return !!attrs.teamRoleList == !!role.team_id;
return Boolean(attrs.teamRoleList) === Boolean(role.team_id);
})
.sort((a, b) => {
if (a.name

View File

@@ -79,6 +79,7 @@ __deferLoadIfEnabled();
var tower = angular.module('Tower', [
//'ngAnimate',
'lrInfiniteScroll',
'ngSanitize',
'ngCookies',
about.name,
@@ -269,7 +270,7 @@ var tower = angular.module('Tower', [
}).
state('projects', {
url: '/projects',
url: '/projects?{status}',
templateUrl: urlPrefix + 'partials/projects.html',
controller: ProjectsList,
data: {
@@ -297,6 +298,10 @@ var tower = angular.module('Tower', [
controller: ProjectsEdit,
data: {
activityStreamId: 'id'
},
ncyBreadcrumb: {
parent: 'projects',
label: 'EDIT PROJECT'
}
}).
state('projectOrganizations', {
@@ -340,6 +345,10 @@ var tower = angular.module('Tower', [
controller: TeamsEdit,
data: {
activityStreamId: 'team_id'
},
ncyBreadcrumb: {
parent: "teams",
label: "EDIT TEAM"
}
}).

View File

@@ -74,7 +74,7 @@ export default
var licenseInfo = FeaturesService.getLicenseInfo();
scope.licenseType = licenseInfo ? licenseInfo.license_type : null;
if (!licenseInfo) {
console.warn("License info not loaded correctly");
console.warn("License info not loaded correctly"); // jshint ignore:line
}
})
.catch(function (response) {

View File

@@ -22,7 +22,7 @@ export function ProjectsList ($scope, $rootScope, $location, $log, $stateParams,
Wait('start');
var list = ProjectList,
defaultUrl = GetBasePath('projects'),
defaultUrl = GetBasePath('projects') + ($stateParams.status ? '?status=' + $stateParams.status : ''),
view = GenerateList,
base = $location.path().replace(/^\//, '').split('/')[0],
mode = (base === 'projects') ? 'edit' : 'select',
@@ -495,6 +495,35 @@ export function ProjectsAdd(Refresh, $scope, $rootScope, $compile, $location, $l
$scope.scmRequired = ($scope.scm_type.value !== 'manual') ? true : false;
$scope.scmBranchLabel = ($scope.scm_type.value === 'svn') ? 'Revision #' : 'SCM Branch';
}
// Dynamically update popover values
if($scope.scm_type.value) {
switch ($scope.scm_type.value) {
case 'git':
$scope.urlPopover = '<p>Example URLs for GIT SCM include:</p><ul class=\"no-bullets\"><li>https://github.com/ansible/ansible.git</li>' +
'<li>git@github.com:ansible/ansible.git</li><li>git://servername.example.com/ansible.git</li></ul>' +
'<p><strong>Note:</strong> When using SSH protocol for GitHub or Bitbucket, enter an SSH key only, ' +
'do not enter a username (other than git). Additionally, GitHub and Bitbucket do not support password authentication when using ' +
'SSH. GIT read only protocol (git://) does not use username or password information.';
break;
case 'svn':
$scope.urlPopover = '<p>Example URLs for Subversion SCM include:</p>' +
'<ul class=\"no-bullets\"><li>https://github.com/ansible/ansible</li><li>svn://servername.example.com/path</li>' +
'<li>svn+ssh://servername.example.com/path</li></ul>';
break;
case 'hg':
$scope.urlPopover = '<p>Example URLs for Mercurial SCM include:</p>' +
'<ul class=\"no-bullets\"><li>https://bitbucket.org/username/project</li><li>ssh://hg@bitbucket.org/username/project</li>' +
'<li>ssh://server.example.com/path</li></ul>' +
'<p><strong>Note:</strong> Mercurial does not support password authentication for SSH. ' +
'Do not put the username and key in the URL. ' +
'If using Bitbucket and SSH, do not supply your Bitbucket username.';
break;
default:
$scope.urlPopover = '<p> URL popover text';
}
}
};
$scope.formCancel = function () {

View File

@@ -162,7 +162,7 @@ export function UsersAdd($scope, $rootScope, $compile, $location, $log,
generator.reset();
$scope.user_type_options = user_type_options;
$scope.user_type = user_type_options[0]
$scope.user_type = user_type_options[0];
$scope.$watch('user_type', user_type_sync($scope));
CreateSelect2({
@@ -271,7 +271,7 @@ export function UsersEdit($scope, $rootScope, $location,
generator.reset();
$scope.user_type_options = user_type_options;
$scope.user_type = user_type_options[0]
$scope.user_type = user_type_options[0];
$scope.$watch('user_type', user_type_sync($scope));
var setScopeFields = function(data){

View File

@@ -49,7 +49,7 @@ export default
label: "Inventories",
},
{
url: "/#/inventories/?inventory_sources_with_failures",
url: "/#/inventories?status=sync-failed",
number: scope.data.inventories.inventory_failed,
label: "Inventory Sync Failures",
isFailureCount: true
@@ -60,7 +60,7 @@ export default
label: "Projects"
},
{
url: "/#/projects/?status=failed",
url: "/#/projects?status=failed",
number: scope.data.projects.failed,
label: "Project Sync Failures",
isFailureCount: true

View File

@@ -118,6 +118,7 @@
top: auto;
box-shadow: none;
text-transform: uppercase;
cursor: pointer;
}
.DashboardGraphs-periodDropdown,

View File

@@ -246,7 +246,7 @@ export default
rows: 10,
awPopOver: "SSH key description",
awPopOverWatch: "key_description",
dataTitle: 'Help',
dataTitle: 'Private Key',
dataPlacement: 'right',
dataContainer: "body",
subForm: "credentialSubForm"

View File

@@ -81,6 +81,11 @@ export default
},
project: {
label: 'Project',
labelAction: {
label: 'RESET',
ngClick: 'resetProjectToDefault()',
'class': "{{!(job_type.value === 'scan' && project_name !== 'Default') ? 'hidden' : ''}}",
},
type: 'lookup',
sourceModel: 'project',
sourceField: 'name',
@@ -99,6 +104,7 @@ export default
label: 'Playbook',
type:'select',
ngOptions: 'book for book in playbook_options track by book',
ngDisabled: "job_type.value === 'scan' && project_name === 'Default'",
id: 'playbook-select',
awRequiredWhen: {
reqExpression: "playbookrequired",
@@ -110,12 +116,6 @@ export default
dataPlacement: 'right',
dataContainer: "body",
},
default_scan: {
type: 'custom',
column: 1,
ngShow: 'job_type.value === "scan" && project_name !== "Default"',
control: '<a href="" ng-click="toggleScanInfo()">Reset to default project and playbook</a>'
},
credential: {
label: 'Machine Credential',
type: 'lookup',
@@ -204,7 +204,7 @@ export default
},
job_tags: {
label: 'Job Tags',
type: 'textarea',
type: 'text',
rows: 1,
addRequired: false,
editRequired: false,

View File

@@ -64,11 +64,10 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
ngChange: 'scmChange()',
addRequired: true,
editRequired: true,
hasSubForm: true
hasSubForm: true,
},
missing_path_alert: {
type: 'alertblock',
"class": 'alert-info',
ngShow: "showMissingPlaybooksAlert && scm_type.value == 'manual'",
alertTxt: '<p class=\"text-justify\"><strong>WARNING:</strong> There are no available playbook directories in {{ base_dir }}. ' +
'Either that directory is empty, or all of the contents are already assigned to other projects. ' +
@@ -79,7 +78,7 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
base_dir: {
label: 'Project Base Path',
type: 'text',
//"class": 'col-lg-6',
class: 'Form-textUneditable',
showonly: true,
ngShow: "scm_type.value == 'manual' " ,
awPopOver: '<p>Base path used for locating playbooks. Directories found inside this path will be listed in the playbook directory drop-down. ' +
@@ -115,30 +114,12 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
init: false
},
subForm: 'sourceSubForm',
helpCollapse: [{
hdr: 'GIT URLs',
content: '<p>Example URLs for GIT SCM include:</p><ul class=\"no-bullets\"><li>https://github.com/ansible/ansible.git</li>' +
'<li>git@github.com:ansible/ansible.git</li><li>git://servername.example.com/ansible.git</li></ul>' +
'<p><strong>Note:</strong> When using SSH protocol for GitHub or Bitbucket, enter an SSH key only, ' +
'do not enter a username (other than git). Additionally, GitHub and Bitbucket do not support password authentication when using ' +
'SSH. GIT read only protocol (git://) does not use username or password information.',
show: "scm_type.value == 'git'"
}, {
hdr: 'SVN URLs',
content: '<p>Example URLs for Subversion SCM include:</p>' +
'<ul class=\"no-bullets\"><li>https://github.com/ansible/ansible</li><li>svn://servername.example.com/path</li>' +
'<li>svn+ssh://servername.example.com/path</li></ul>',
show: "scm_type.value == 'svn'"
}, {
hdr: 'Mercurial URLs',
content: '<p>Example URLs for Mercurial SCM include:</p>' +
'<ul class=\"no-bullets\"><li>https://bitbucket.org/username/project</li><li>ssh://hg@bitbucket.org/username/project</li>' +
'<li>ssh://server.example.com/path</li></ul>' +
'<p><strong>Note:</strong> Mercurial does not support password authentication for SSH. ' +
'Do not put the username and key in the URL. ' +
'If using Bitbucket and SSH, do not supply your Bitbucket username.',
show: "scm_type.value == 'hg'"
}],
hideSubForm: "scm_type.value === 'manual'",
awPopOverWatch: "urlPopover",
awPopOver: "set in controllers/projects",
dataTitle: 'SCM URL',
dataContainer: 'body',
dataPlacement: 'right'
},
scm_branch: {
labelBind: "scmBranchLabel",
@@ -174,7 +155,7 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
dataTitle: 'SCM Clean',
dataContainer: 'body',
dataPlacement: 'right',
labelClass: 'checkbox-options'
labelClass: 'checkbox-options stack-inline'
}, {
name: 'scm_delete_on_update',
label: 'Delete on Update',
@@ -186,7 +167,7 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
dataTitle: 'SCM Delete',
dataContainer: 'body',
dataPlacement: 'right',
labelClass: 'checkbox-options'
labelClass: 'checkbox-options stack-inline'
}, {
name: 'scm_update_on_launch',
label: 'Update on Launch',
@@ -197,7 +178,7 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
dataTitle: 'SCM Update',
dataContainer: 'body',
dataPlacement: 'right',
labelClass: 'checkbox-options'
labelClass: 'checkbox-options stack-inline'
}]
},
scm_update_cache_timeout: {
@@ -273,7 +254,7 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
}
},
notifications: {
include: "NotificationsList"
include: "NotificationsList",
}
},

View File

@@ -132,9 +132,10 @@ export default
"delete": {
label: 'Remove',
ngClick: 'deletePermissionFromTeam(team_id, team_obj.name, role.name, role.summary_fields.resource_name, role.related.teams)',
class: "List-actionButton--delete",
'class': "List-actionButton--delete",
iconClass: 'fa fa-times',
awToolTip: 'Dissasociate permission from team'
awToolTip: 'Dissasociate permission from team',
dataPlacement: 'top'
}
},
hideOnSuperuser: true

View File

@@ -138,7 +138,6 @@ export default
iterator: 'team',
open: false,
index: false,
actions: {},
fields: {

View File

@@ -784,7 +784,6 @@ export default
url, play;
scope.tasks = [];
if (scope.selectedPlay) {
url = scope.job.url + 'job_tasks/?event_id=' + scope.selectedPlay;
url += (scope.search_task_name) ? '&task__icontains=' + scope.search_task_name : '';
@@ -912,16 +911,25 @@ export default
scope.tasks[idx].taskActiveClass = '';
}
});
params = {
parent: scope.selectedTask,
event__startswith: 'runner',
page_size: scope.hostResultsMaxRows,
order: 'host_name,counter',
};
JobDetailService.getRelatedJobEvents(scope.job.id, params).success(function(res){
scope.hostResults = JobDetailService.processHostEvents(res.results);
if (scope.selectedTask !== null){
params = {
parent: scope.selectedTask,
event__startswith: 'runner',
page_size: scope.hostResultsMaxRows,
order: 'host_name,counter',
};
if (scope.search_host_status === 'failed'){
params.failed = true;
}
JobDetailService.getRelatedJobEvents(scope.job.id, params).success(function(res){
scope.hostResults = JobDetailService.processHostEvents(res.results);
scope.hostResultsLoading = false;
});
}
else{
scope.hostResults = [];
scope.hostResultsLoading = false;
});
}
};
}])
@@ -934,7 +942,7 @@ export default
graph_data.push({
label: 'OK',
value: count.ok.length,
color: '#60D66F'
color: '#5CB85C'
});
}
if (count.changed.length > 0) {
@@ -979,18 +987,19 @@ export default
job_detail_chart = nv.models.pieChart()
.margin({bottom: 15})
.x(function(d) {
return d.label +': '+ Math.round((d.value/total)*100) + "%";
return d.label +': '+ Math.floor((d.value/total)*100) + "%";
})
.y(function(d) { return d.value; })
.showLabels(true)
.showLegend(false)
.showLabels(false)
.showLegend(true)
.growOnHover(false)
.labelThreshold(0.01)
.tooltipContent(function(x, y) {
return '<p>'+x+'</p>'+ '<p>' + Math.floor(y.replace(',','')) + ' HOSTS ' + '</p>';
})
.color(colors);
job_detail_chart.legend.rightAlign(false);
job_detail_chart.legend.margin({top: 5, right: 450, left:0, bottom: 0});
d3.select(element.find('svg')[0])
.datum(dataset)
.transition().duration(350)
@@ -1000,19 +1009,15 @@ export default
"font-style": "normal",
"font-weight":400,
"src": "url(/static/assets/OpenSans-Regular.ttf)",
"width": 500,
"width": 600,
"height": 300,
"color": '#848992'
});
d3.select(element.find(".nv-label text")[0])
.attr("class", "HostSummary-graph--successful")
d3.select(element.find(".nv-noData")[0])
.style({
"font-family": 'Open Sans',
"font-size": "16px",
"text-transform" : "uppercase",
"fill" : colors[0],
"src": "url(/static/assets/OpenSans-Regular.ttf)"
"text-anchor": 'start'
});
/*
d3.select(element.find(".nv-label text")[1])
.attr("class", "HostSummary-graph--changed")
.style({
@@ -1040,6 +1045,7 @@ export default
"fill" : colors[3],
"src": "url(/static/assets/OpenSans-Regular.ttf)"
});
*/
return job_detail_chart;
};
}])

View File

@@ -177,7 +177,7 @@ angular.module('JobTemplatesHelper', ['Utilities'])
});
if(scope.project === "" && scope.playbook === ""){
if (scope.project === "" && scope.playbook === "") {
scope.toggleScanInfo();
}

View File

@@ -20,7 +20,7 @@ export default
angular.module('SchedulesHelper', [ 'Utilities', 'RestServices', 'SchedulesHelper', 'SearchHelper', 'PaginationHelpers', listGenerator.name, 'ModalDialog',
'GeneratorHelpers'])
.factory('EditSchedule', ['SchedulerInit', '$rootScope', 'Wait', 'Rest',
.factory('EditSchedule', ['SchedulerInit', '$rootScope', 'Wait', 'Rest',
'ProcessErrors', 'GetBasePath', 'SchedulePost', '$state',
function(SchedulerInit, $rootScope, Wait, Rest, ProcessErrors,
GetBasePath, SchedulePost, $state) {
@@ -176,7 +176,7 @@ export default
};
}])
.factory('AddSchedule', ['$location', '$rootScope', '$stateParams',
.factory('AddSchedule', ['$location', '$rootScope', '$stateParams',
'SchedulerInit', 'Wait', 'GetBasePath', 'Empty', 'SchedulePost', '$state', 'Rest', 'ProcessErrors',
function($location, $rootScope, $stateParams, SchedulerInit,
Wait, GetBasePath, Empty, SchedulePost, $state, Rest,
@@ -295,8 +295,7 @@ export default
}])
.factory('SchedulePost', ['Rest', 'ProcessErrors', 'RRuleToAPI', 'Wait',
'ToJSON',
function(Rest, ProcessErrors, RRuleToAPI, Wait, ToJSON) {
function(Rest, ProcessErrors, RRuleToAPI, Wait) {
return function(params) {
var scope = params.scope,
url = params.url,
@@ -326,8 +325,8 @@ export default
schedule.extra_data = JSON.stringify(extra_vars);
}
else if(scope.extraVars){
schedule.extra_data = scope.parseType === 'yaml' ?
(scope.extraVars === '---' ? "" : jsyaml.safeLoad(scope.extraVars)) : scope.extraVars;
schedule.extra_data = scope.parseType === 'yaml' ?
(scope.extraVars === '---' ? "" : jsyaml.safeLoad(scope.extraVars)) : scope.extraVars;
}
Rest.setUrl(url);
if (mode === 'add') {

View File

@@ -17,7 +17,7 @@ function InventoriesList($scope, $rootScope, $location, $log,
Find, Empty, $state) {
var list = InventoryList,
defaultUrl = GetBasePath('inventory'),
defaultUrl = GetBasePath('inventory') + ($stateParams.status === 'sync-failed' ? '?not__inventory_sources_with_failures=0' : ''),
view = generateList,
paths = $location.path().replace(/^\//, '').split('/'),
mode = (paths[0] === 'inventories') ? 'edit' : 'select';

View File

@@ -9,7 +9,7 @@ import InventoriesList from './inventory-list.controller';
export default {
name: 'inventories',
route: '/inventories',
route: '/inventories?{status}',
templateUrl: templateUrl('inventories/inventories'),
controller: InventoriesList,
data: {

View File

@@ -80,7 +80,7 @@
group_id: id
});
};
$scope.showFailedHosts = function(x, y, z){
$scope.showFailedHosts = function() {
$state.go('inventoryManage', {failed: true}, {reload: true});
};
$scope.scheduleGroup = function(id) {
@@ -91,7 +91,7 @@
$scope.$parent.groupsSelected = selection.length > 0 ? true : false;
$scope.$parent.groupsSelectedItems = selection.selectedItems;
});
$scope.$on('PostRefresh', () =>{
$scope.$on('PostRefresh', () => {
$scope.groups.forEach( (group, index) => {
var group_status, hosts_status;
group_status = GetSyncStatusMsg({

View File

@@ -8,7 +8,7 @@ import {ManageHostsAdd, ManageHostsEdit} from './hosts.route';
export default
angular.module('manageHosts', [])
.run(['$stateExtender', '$state', function($stateExtender, $state){
.run(['$stateExtender', function($stateExtender){
$stateExtender.addState(ManageHostsAdd);
$stateExtender.addState(ManageHostsEdit);
}]);

View File

@@ -61,7 +61,7 @@ export default
data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
}
}
$scope.canEdit = data['script'] !== null;
$scope.canEdit = data.script !== null;
if (!$scope.canEdit) {
$scope.script = "Script contents hidden";
}

View File

@@ -1,6 +1,6 @@
<div id="hosts-summary-section" class="section">
<div class="JobDetail-instructions"><span class="badge">4</span> Please select a host below to view a summary of all associated tasks.</div>
<div class="JobDetail-instructions" ng-hide="hosts.length == 0"><span class="badge">4</span> Please select a host below to view a summary of all associated tasks.</div>
<div class="JobDetail-searchHeaderRow" ng-hide="hosts.length == 0">
<div class="JobDetail-searchContainer form-group">
<div class="search-name">
@@ -13,7 +13,7 @@
</div>
<div class="JobDetail-tableToggleContainer form-group">
<div class="btn-group" >
<button
<button
ng-click="setFilter('all')"
class="JobDetail-tableToggle btn btn-xs" ng-class="{'btn-default': filter === 'failed', 'btn-primary': filter === 'all'}">All</button>
<button ng-click="setFilter('failed')"
@@ -34,7 +34,7 @@
</div>
<div id="hosts-summary-table" class="table-detail" lr-infinite-scroll="getNextPage" scroll-threshold="10" time-threshold="500">
<table class="table">
<table class="table" ng-class="{'JobDetails-table--noResults': hosts.length === 0}">
<tbody>
<tr class="List-tableRow" ng-repeat="host in hosts track by $index" id="{{ host.id }}" ng-class-even="'List-tableRow--evenRow'" ng-class-odd="'List-tableRow--oddRow'">
<td class="List-tableCell name col-lg-6 col-md-6 col-sm-6 col-xs-6">

View File

@@ -13,9 +13,15 @@
.JobDetail-instructions{
color: @default-interface-txt;
margin: 10px 0 10px 0;
.badge {
background-color: @default-list-header-bg;
color: @default-interface-txt;
padding: 5px 7px;
}
}
.JobDetail{
.OnePlusOne-container(100%, @breakpoint-md);
.OnePlusOne-container(100%, @breakpoint-md);
}
.JobDetail-leftSide{
@@ -151,8 +157,25 @@
background-color: @default-link;
border: 1px solid @default-link;
color: @default-bg;
}
&:hover {
background-color: @default-link-hov;
}
}
.JobDetail .nvd3.nv-noData{
color: @default-interface-txt;
font-size: 12px;
text-transform: uppercase;
font-family: 'Open Sans', sans-serif;
}
.JobDetail .nv-series{
padding-right: 30px;
display: block;
}
.JobDetail-instructions .badge{
background-color: @default-list-header-bg;
color: @default-interface-txt;
}
.JobDetail-tableToggle--left{
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
@@ -185,6 +208,12 @@
margin-left: -5px;
}
.JobDetails-table--noResults {
tr > td {
border-top: none !important;
}
}
.JobDetail-statusIcon--results{
padding-left: 0px;
padding-right: 10px;
@@ -196,7 +225,13 @@
}
.JobDetail-stdoutActionButton--active{
display: none;
visibility: hidden;
flex:none;
width:0px;
padding-right: 0px;
}
.JobDetail-leftSide.JobDetail-stdoutActionButton--active {
margin-right: 0px;
}

View File

@@ -673,21 +673,9 @@ export default
scope.lessStatus = false; // close the view more status option
// Detail table height adjusting. First, put page height back to 'normal'.
$('#plays-table-detail').height(80);
//$('#plays-table-detail').mCustomScrollbar("update");
// $('#tasks-table-detail').height(120);
//$('#tasks-table-detail').mCustomScrollbar("update");
$('#hosts-table-detail').height(150);
//$('#hosts-table-detail').mCustomScrollbar("update");
height = $(window).height() - $('#main-menu-container .navbar').outerHeight() -
$('#job-detail-container').outerHeight() - 20;
if (height > 15) {
// there's a bunch of white space at the bottom, let's use it
$('#plays-table-detail').height(80 + (height * 0.10));
$('#tasks-table-detail').height(120 + (height * 0.20));
$('#hosts-table-detail').height(150 + (height * 0.10));
}
scope.$emit('RefreshCompleted');
};
@@ -776,6 +764,15 @@ export default
}
};
scope.filterTaskStatus = function() {
scope.search_task_status = (scope.search_task_status === 'all') ? 'failed' : 'all';
if (!scope.liveEventProcessing || scope.pauseLiveEvents) {
LoadTasks({
scope: scope
});
}
};
scope.filterPlayStatus = function() {
scope.search_play_status = (scope.search_play_status === 'all') ? 'failed' : 'all';
if (!scope.liveEventProcessing || scope.pauseLiveEvents) {
@@ -785,6 +782,26 @@ export default
}
};
scope.filterHostStatus = function(){
scope.search_host_status = (scope.search_host_status === 'all') ? 'failed' : 'all';
if (!scope.liveEventProcessing || scope.pauseLiveEvents){
if (scope.selectedTask !== null && scope.selectedPlay !== null){
var params = {
parent: scope.selectedTask,
page_size: scope.hostResultsMaxRows,
order: 'host_name,counter',
};
if (scope.search_host_status === 'failed'){
params.failed = true;
}
JobDetailService.getRelatedJobEvents(scope.job.id, params).success(function(res){
scope.hostResults = JobDetailService.processHostEvents(res.results);
scope.hostResultsLoading = false;
});
}
}
};
scope.searchPlays = function() {
if (scope.search_play_name) {
scope.searchPlaysEnabled = false;

View File

@@ -189,7 +189,7 @@
</div>
<div id="plays-table-header" class="table-header">
<div id="plays-table-header" class="table-header" ng-show="plays.length !== 0">
<table class="table table-condensed">
<thead>
<tr>
@@ -202,7 +202,7 @@
</div>
<div id="plays-table-detail" class="table-detail" lr-infinite-scroll="playsScrollDown"
scroll-threshold="10" time-threshold="500">
<table class="table">
<table class="table" ng-class="{'JobDetails-table--noResults': plays.length === 0}">
<tbody>
<tr class="List-tableRow cursor-pointer" ng-repeat="play in plays" ng-class-odd="'List-tableRow--oddRow'" ng-class-even="'List-tableRow--evenRow'" ng-class="play.playActiveClass" ng-click="selectPlay(play.id, $event)">
<td class="List-tableCell col-lg-7 col-md-6 col-sm-6 col-xs-4 status-column" aw-tool-tip="{{ play.status_tip }}" data-tip-watch="play.status_tip" data-placement="top"><i class="JobDetail-statusIcon fa icon-job-{{ play.status }}"></i>{{ play.name }}</td>
@@ -248,7 +248,7 @@
</div>
<div class="table-header">
<table id="tasks-table-header" class="table table-condensed">
<table id="tasks-table-header" class="table table-condensed" ng-show="taskList.length !== 0">
<thead>
<tr>
<th class="List-tableHeader col-lg-3 col-md-3 col-sm-6 col-xs-4">Tasks</th>
@@ -261,7 +261,7 @@
</div>
<div id="tasks-table-detail" class="table-detail" lr-infinite-scroll="tasksScrollDown"
scroll-threshold="10" time-threshold="500">
<table class="table">
<table class="table" ng-class="{'JobDetails-table--noResults': taskList.length === 0}">
<tbody>
<tr class="List-tableRow cursor-pointer" ng-class-odd="'List-tableRow--oddRow'" ng-class-even="'List-tableRow--evenRow'" ng-repeat="task in taskList = (tasks) track by $index" ng-class="task.taskActiveClass" ng-click="selectTask(task.id)">
<td class="List-tableCell col-lg-3 col-md-3 col-sm-6 col-xs-4 status-column" aw-tool-tip="{{ task.status_tip }}"
@@ -331,7 +331,7 @@
</div>
</div>
<div class="table-header" id="hosts-table-header">
<table class="table table-condensed">
<table class="table table-condensed" ng-show="results.length !== 0">
<thead>
<tr>
<th class="List-tableHeader col-lg-4 col-md-3 col-sm-3 col-xs-3">Hosts</th>
@@ -343,7 +343,7 @@
</div>
<div id="hosts-table-detail" class="table-detail" lr-infinite-scroll="hostResultsScrollDown" scroll-threshold="10" time-threshold="500">
<table class="table">
<table class="table" ng-class="{'JobDetails-table--noResults': results.length === 0}">
<tbody>
<tr class="List-tableRow cursor-pointer" ng-class-odd="'List-tableRow--oddRow'" ng-class-even="'List-tableRow--evenRow'" ng-repeat="result in results = (hostResults) track by $index">
<td class="List-tableCell col-lg-4 col-md-3 col-sm-3 col-xs-3 status-column">
@@ -404,7 +404,7 @@
<button class="StandardOut-actionButton" aw-tool-tip="Toggle Output" data-placement="top" ng-class="{'StandardOut-actionButton--active': stdoutFullScreen}" ng-click="toggleStdoutFullscreen()">
<i class="fa fa-arrows-alt"></i>
</button>
<a ng-show="job_status.status === ('failed' || 'successful')" href="/api/v1/jobs/{{ job.id }}/stdout?format=txt_download&token={{ token }}">
<a ng-show="job_status.status === 'failed' || job_status.status === 'successful' || job_status.status === 'canceled'" href="/api/v1/jobs/{{ job.id }}/stdout?format=txt_download&token={{ token }}">
<button class="StandardOut-actionButton" aw-tool-tip="Download Output" data-placement="top">
<i class="fa fa-download"></i>
</button>

View File

@@ -72,6 +72,7 @@
jQuery.extend(true, CloudCredentialList, CredentialList);
CloudCredentialList.name = 'cloudcredentials';
CloudCredentialList.iterator = 'cloudcredential';
CloudCredentialList.basePath = '/api/v1/credentials?cloud=true';
SurveyControllerInit({
scope: $scope,
@@ -196,12 +197,20 @@
});
});
function sync_playbook_select2() {
CreateSelect2({
element:'#playbook-select',
multiple: false
});
}
// Update playbook select whenever project value changes
selectPlaybook = function (oldValue, newValue) {
var url;
if($scope.job_type.value === 'scan' && $scope.project_name === "Default"){
$scope.playbook_options = ['Default'];
$scope.playbook = 'Default';
sync_playbook_select2();
Wait('stop');
}
else if (oldValue !== newValue) {
@@ -216,6 +225,7 @@
opts.push(data[i]);
}
$scope.playbook_options = opts;
sync_playbook_select2();
Wait('stop');
})
.error(function (data, status) {
@@ -226,32 +236,37 @@
}
};
$scope.jobTypeChange = function(){
if($scope.job_type){
if($scope.job_type.value === 'scan'){
// If the job_type is 'scan' then we don't want the user to be
// able to prompt for job type or inventory
$scope.ask_job_type_on_launch = false;
$scope.ask_inventory_on_launch = false;
$scope.toggleScanInfo();
}
else if($scope.project_name === "Default"){
$scope.project_name = null;
$scope.playbook_options = [];
// $scope.playbook = 'null';
$scope.job_templates_form.playbook.$setPristine();
}
}
let last_non_scan_project_name = null;
let last_non_scan_playbook = "";
let last_non_scan_playbook_options = [];
$scope.jobTypeChange = function() {
if ($scope.job_type) {
if ($scope.job_type.value === 'scan') {
if ($scope.project_name !== "Default") {
last_non_scan_project_name = $scope.project_name;
last_non_scan_playbook = $scope.playbook;
last_non_scan_playbook_options = $scope.playbook_options;
}
// If the job_type is 'scan' then we don't want the user to be
// able to prompt for job type or inventory
$scope.ask_job_type_on_launch = false;
$scope.ask_inventory_on_launch = false;
$scope.resetProjectToDefault();
}
else if ($scope.project_name === "Default") {
$scope.project_name = last_non_scan_project_name;
$scope.playbook_options = last_non_scan_playbook_options;
$scope.playbook = last_non_scan_playbook;
$scope.job_templates_form.playbook.$setPristine();
}
}
sync_playbook_select2();
};
$scope.toggleScanInfo = function() {
$scope.resetProjectToDefault = function() {
$scope.project_name = 'Default';
if($scope.project === null){
selectPlaybook();
}
else {
$scope.project = null;
}
$scope.project = null;
selectPlaybook('force_load');
};
// Detect and alert user to potential SCM status issues

View File

@@ -76,6 +76,13 @@ export default
$scope.playbook = null;
generator.reset();
function sync_playbook_select2() {
CreateSelect2({
element:'#playbook-select',
multiple: false
});
}
getPlaybooks = function (project) {
var url;
if ($scope.playbook) {
@@ -85,6 +92,7 @@ export default
if($scope.job_type.value === 'scan' && $scope.project_name === "Default"){
$scope.playbook_options = ['Default'];
$scope.playbook = 'Default';
sync_playbook_select2();
Wait('stop');
}
else if (!Empty(project)) {
@@ -93,14 +101,14 @@ export default
Rest.setUrl(url);
Rest.get()
.success(function (data) {
var i;
$scope.playbook_options = [];
for (i = 0; i < data.length; i++) {
for (var i = 0; i < data.length; i++) {
$scope.playbook_options.push(data[i]);
if (data[i] === $scope.playbook) {
$scope.job_templates_form.playbook.$setValidity('required', true);
}
}
sync_playbook_select2();
if ($scope.playbook) {
$scope.$emit('jobTemplateLoadFinished');
} else {
@@ -108,7 +116,7 @@ export default
}
})
.error(function (ret,status_code) {
if (status_code == 403) {
if (status_code === 403) {
/* user doesn't have access to see the project, no big deal. */
} else {
Alert('Missing Playbooks', 'Unable to retrieve the list of playbooks for this project. Choose a different ' +
@@ -122,23 +130,31 @@ export default
}
};
$scope.jobTypeChange = function(){
if($scope.job_type){
if($scope.job_type.value === 'scan'){
// If the job_type is 'scan' then we don't want the user to be
// able to prompt for job type or inventory
$scope.ask_job_type_on_launch = false;
$scope.ask_inventory_on_launch = false;
$scope.toggleScanInfo();
}
else if($scope.project_name === "Default"){
$scope.project_name = null;
$scope.playbook_options = [];
// $scope.playbook = 'null';
$scope.job_templates_form.playbook.$setPristine();
}
}
let last_non_scan_project_name = null;
let last_non_scan_playbook = "";
let last_non_scan_playbook_options = [];
$scope.jobTypeChange = function() {
if ($scope.job_type) {
if ($scope.job_type.value === 'scan') {
if ($scope.project_name !== "Default") {
last_non_scan_project_name = $scope.project_name;
last_non_scan_playbook = $scope.playbook;
last_non_scan_playbook_options = $scope.playbook_options;
}
// If the job_type is 'scan' then we don't want the user to be
// able to prompt for job type or inventory
$scope.ask_job_type_on_launch = false;
$scope.ask_inventory_on_launch = false;
$scope.resetProjectToDefault();
}
else if ($scope.project_name === "Default") {
$scope.project_name = last_non_scan_project_name;
$scope.playbook_options = last_non_scan_playbook_options;
$scope.playbook = last_non_scan_playbook;
$scope.job_templates_form.playbook.$setPristine();
}
}
sync_playbook_select2();
};
$scope.toggleNotification = function(event, notifier_id, column) {
@@ -159,14 +175,10 @@ export default
});
};
$scope.toggleScanInfo = function() {
$scope.resetProjectToDefault = function() {
$scope.project_name = 'Default';
if($scope.project === null){
getPlaybooks();
}
else {
$scope.project = null;
}
$scope.project = null;
getPlaybooks();
};
// Detect and alert user to potential SCM status issues
@@ -198,7 +210,7 @@ export default
}
})
.error(function (data, status) {
if (status == 403) {
if (status === 403) {
/* User doesn't have read access to the project, no problem. */
} else {
ProcessErrors($scope, data, status, form, { hdr: 'Error!', msg: 'Failed to get project ' + $scope.project +
@@ -288,6 +300,7 @@ export default
jQuery.extend(true, CloudCredentialList, CredentialList);
CloudCredentialList.name = 'cloudcredentials';
CloudCredentialList.iterator = 'cloudcredential';
CloudCredentialList.basePath = '/api/v1/credentials?cloud=true';
LookUpInit({
url: GetBasePath('credentials') + '?cloud=true',
scope: $scope,

View File

@@ -20,6 +20,7 @@ export default
index: false,
hover: true,
well: false,
emptyListText: 'No completed jobs',
fields: {
status: {

View File

@@ -15,6 +15,7 @@ export default
index: true,
hover: true,
well: false,
emptyListText: 'No schedules exist',
fields: {
status: {

View File

@@ -157,11 +157,11 @@
ng-href="/#/portal"
ng-if="!licenseMissing"
ng-class="{'is-currentRoute' : isCurrentState('portalMode'), 'is-loggedOut' : !$root.current_user.username}"
aw-tool-tip="Portal Mode"
aw-tool-tip="My View"
data-placement="bottom"
data-trigger="hover"
data-container="body">
<i class="MainMenu-itemImage MainMenu-itemImage--settings fa fa-columns"
<i class="MainMenu-itemImage MainMenu-itemImage--settings fa fa-tasks"
alt="Portal Mode">
</i>
</a>

View File

@@ -1,3 +1,4 @@
<div ui-view></div>
<div class="tab-pane Panel" id="management_jobs">
<div class="List-title">
<div class="List-titleText">
@@ -7,7 +8,6 @@
{{ mgmtCards.length }}
</span>
</div>
<div ui-view></div>
<div class="MgmtCards">
<div class="MgmtCards-card"
ng-repeat="card in mgmtCards track by card.id">

View File

@@ -3,7 +3,8 @@
.MgmtCards {
display: flex;
flex-wrap: wrap;
flex-flow: row wrap;
justify-content: space-between;
}
.MgmtCards-card {
@@ -11,15 +12,14 @@
padding: 20px;
border-radius: 5px;
border: 1px solid @default-border;
display: flex;
flex-wrap: wrap;
align-items: baseline;
margin-top: 20px;
width: 32%;
}
.MgmtCards-card--selected {
padding-left: 16px;
border-left: 5px solid #337AB7;
border-left: 5px solid @default-link;
}
.MgmtCards-card--promptElements{
@@ -44,7 +44,6 @@
font-weight: bold;
color: @default-interface-txt;
margin-bottom: 25px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
@@ -86,46 +85,19 @@
margin-right: 10px;
}
@media (min-width: 1179px) {
.MgmtCards-card {
width: ~"calc(25% - 15px)";
margin-right: 20px;
}
.MgmtCards-card:nth-child(4n+4) {
margin-right: 0px;
}
}
@media (min-width: 901px) and (max-width: 1178px) {
.MgmtCards-card {
width: ~"calc(33% - 11px)";
margin-right: 20px;
}
.MgmtCards-card:nth-child(3n+3) {
margin-right: 0px;
}
}
@media (min-width: 616px) and (max-width: 900px) {
.MgmtCards-card {
width: ~"calc(50% - 10px)";
margin-right: 20px;
}
.MgmtCards-card:nth-child(2n+2) {
margin-right: 0px;
}
}
@media (max-width: 615px) {
@media (max-width: 840px) {
.MgmtCards-card {
width: 100%;
margin-right: 0px;
}
}
@media (min-width: 840px) and (max-width: 1240px) {
.MgmtCards-card {
width: ~"calc(50% - 10px)";
}
}
#prompt-for-days-facts, #prompt-for-days {
overflow-x: hidden;
font-family: "Open Sans";

View File

@@ -141,7 +141,23 @@ export default function() {
reqExpression: "channel_required",
init: "false"
},
ngShow: "notification_type.value == 'slack' || notification_type.value == 'hipchat'",
ngShow: "notification_type.value == 'slack'",
subForm: 'typeSubForm'
},
rooms: {
label: 'Destination Channels',
type: 'textarea',
rows: 3,
awPopOver: '<p>Type an option on each line. The pound symbol (#) is not required.</p>'+
'<p>For example:<br>engineering<br>\n #support<br>\n',
dataTitle: 'Destination Channels',
dataPlacement: 'right',
dataContainer: "body",
awRequiredWhen: {
reqExpression: "room_required",
init: "false"
},
ngShow: "notification_type.value == 'hipchat'",
subForm: 'typeSubForm'
},
token: {
@@ -243,8 +259,9 @@ export default function() {
subForm: 'typeSubForm'
},
api_url: {
label: 'API URL (e.g: https://mycompany.hiptchat.com)',
label: 'API URL',
type: 'text',
placeholder: 'https://mycompany.hipchat.com',
awRequiredWhen: {
reqExpression: "hipchat_required",
init: "false"
@@ -264,11 +281,7 @@ export default function() {
},
notify: {
label: 'Notify Channel',
type: 'text',
awRequiredWhen: {
reqExpression: "hipchat_required",
init: "false"
},
type: 'checkbox',
ngShow: "notification_type.value == 'hipchat' ",
subForm: 'typeSubForm'
},

View File

@@ -13,6 +13,7 @@ export default function(){
iterator: 'notification_template',
index: false,
hover: false,
emptyListText: 'No notifications exist',
fields: {
status: {

View File

@@ -14,6 +14,7 @@ export default function(){
iterator: 'notification',
index: false,
hover: false,
emptyListText: 'No Notifications exist',
basePath: 'notifications',
fields: {
name: {

View File

@@ -39,7 +39,7 @@ function () {
case 'hipchat':
obj.tokenLabel = ' Token';
obj.hipchat_required = true;
obj.channel_required = true;
obj.room_required = true;
obj.token_required = true;
break;
case 'twilio':

View File

@@ -20,7 +20,7 @@
<div id="scheduled_jobs_link" class="Form-tab"
ng-class="{'is-selected': schedulesSelected }"
ng-click="toggleTab('scheduled')">
Schedule
Schedules
</div>
</div>
<div id="jobs-tab-content" class="Form-tabSection"

View File

@@ -8,7 +8,7 @@ export default {
name: 'portalMode',
url: '/portal',
ncyBreadcrumb: {
label: "PORTAL MODE"
label: "MY VIEW"
},
resolve: {
features: ['FeaturesService', function(FeaturesService) {
@@ -30,4 +30,4 @@ export default {
controller: PortalModeJobsController
}
}
};
};

View File

@@ -37,19 +37,28 @@ export default
name: 'projectSchedules',
route: '/projects/:id/schedules',
templateUrl: templateUrl("scheduler/scheduler"),
controller: 'schedulerController'
controller: 'schedulerController',
ncyBreadcrumb: {
label: 'PROJECT SCHEDULES'
}
});
$stateExtender.addState({
name: 'projectSchedules.add',
route: '/add',
templateUrl: templateUrl("scheduler/schedulerForm"),
controller: 'schedulerAddController'
controller: 'schedulerAddController',
ncyBreadcrumb: {
label: 'PROJECT SCHEDULES ADD'
}
});
$stateExtender.addState({
name: 'projectSchedules.edit',
route: '/:schedule_id',
templateUrl: templateUrl("scheduler/schedulerForm"),
controller: 'schedulerEditController'
controller: 'schedulerEditController',
ncyBreadcrumb: {
label: 'PROJECT SCHEDULES EDIT'
}
});
$stateExtender.addState({
name: 'inventoryManage.schedules',

View File

@@ -509,7 +509,7 @@
Please provide a valid date.
</div>
</div>
<div class="form-group SchedulerForm-formGroup"
<div class="form-group SchedulerForm-formGroup"
ng-if="schedulerEnd && schedulerEnd.value == 'on'">
<label class="Form-inputLabel">
<span class="red-text">*</span>
@@ -583,7 +583,7 @@
<div class="SchedulerFormDetail-container"
ng-show="schedulerIsValid">
<label class="SchedulerFormDetail-label">
Description
Schedule Description
</label>
<div class="SchedulerFormDetail-nlp">
{{ rrule_nlp_description }}
@@ -651,7 +651,7 @@
<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>"
data-placement="right" data-container="body" over-title="Extra Variables" class="help-link" data-original-title="" title="" tabindex="-1">
<i class="fa fa-question-circle"></i>
</a>
</a>
<div class="parse-selection">
<input type="radio" ng-model="parseType" ng-change="parseTypeChange()" value="yaml"><span class="parse-label">YAML</span>

View File

@@ -31,7 +31,6 @@
align-items: center;
max-height: 400px;
width: 120px;
overflow-y: scroll;
cursor: pointer;
text-transform: uppercase;
}

View File

@@ -4,7 +4,7 @@ export default {
name: 'setup',
route: '/setup',
ncyBreadcrumb: {
label: "SETUP"
label: "SETTINGS"
},
templateUrl: templateUrl('setup-menu/setup-menu')
};

View File

@@ -59,7 +59,7 @@ angular.module('ModalDialog', ['Utilities', 'ParseHelper'])
callback = params.callback,
beforeDestroy = params.beforeDestroy,
closeOnEscape = (params.closeOnEscape === undefined) ? false : params.closeOnEscape,
resizable = (params.resizable === undefined) ? true : params.resizable,
resizable = (params.resizable === undefined) ? false : params.resizable,
draggable = (params.draggable === undefined) ? true : params.draggable,
dialogClass = params.dialogClass,
forms = _.chain([params.form]).flatten().compact().value(),

View File

@@ -36,7 +36,7 @@
// buttons
@btn-bg: @default-bg;
@btn-bord: @default-border;
@btn-bord: @d7grey;
@btn-txt: @default-interface-txt;
@btn-bg-hov: @default-tertiary-bg;
@btn-bg-sel: @default-icon-hov;

View File

@@ -714,13 +714,10 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
function label() {
var html = '';
if (field.label || field.labelBind) {
html += "<label ";
if (horizontal || field.labelClass) {
html += "class=\"";
html += (field.labelClass) ? field.labelClass : "";
html += (horizontal) ? " " + getLabelWidth() : "";
html += "\" ";
}
html += "<label class=\"";
html += (field.labelClass) ? field.labelClass : "";
html += (horizontal) ? " " + getLabelWidth() : "Form-inputLabelContainer ";
html += "\" ";
html += (field.labelNGClass) ? "ng-class=\"" + field.labelNGClass + "\" " : "";
html += "for=\"" + fld + '">\n';
html += (field.icon) ? Icon(field.icon) : "";
@@ -742,6 +739,14 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
html += "\" value=\"json\" ng-change=\"parseTypeChange()\"> <span class=\"parse-label\">JSON</span>\n";
html += "</div>\n";
}
if (field.labelAction) {
let action = field.labelAction;
let href = action.href || "";
let ngClick = action.ngClick || "";
let cls = action["class"] || "";
html += `<a class="Form-labelAction ${cls}" href="${href}" ng-click="${ngClick}">${action.label}</a>`;
}
html += "\n\t</label>\n";
}
return html;
@@ -787,7 +792,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
if ((!field.readonly) || (field.readonly && options.mode === 'edit')) {
if((field.excludeMode === undefined || field.excludeMode !== options.mode)) {
if((field.excludeMode === undefined || field.excludeMode !== options.mode) && field.type !== 'alertblock') {
html += "<div class='form-group Form-formGroup ";
@@ -1612,7 +1617,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
currentSubForm = field.subForm;
var subFormTitle = this.form.subFormTitles[field.subForm];
html += '<div class="Form-subForm '+ currentSubForm + '" ng-hide="'+ hasSubFormField + '.value === undefined"> ';
html += '<div class="Form-subForm '+ currentSubForm + '" ng-hide="'+ hasSubFormField + '.value === undefined || ' + field.hideSubForm + '"> ';
html += '<span class="Form-subForm--title">'+ subFormTitle +'</span>';
}
else if (!field.subForm && currentSubForm !== undefined) {
@@ -1838,7 +1843,12 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
`;
// Show the "no items" box when loading is done and the user isn't actively searching and there are no results
html += "<div class=\"List-noItems\" ng-show=\"" + collection.iterator + "Loading == false && " + collection.iterator + "_active_search == false && " + collection.iterator + "_total_rows < 1\">PLEASE ADD ITEMS TO THIS LIST</div>";
// Allow for the suppression of the empty list text to avoid duplication between form generator and list generator
var emptyListText = (collection.emptyListText) ? collection.emptyListText : "PLEASE ADD ITEMS TO THIS LIST";
html += '<div ng-hide="is_superuser">';
html += "<div class=\"List-noItems\" ng-hide=\"is_superuser\" ng-show=\"" + collection.iterator + "Loading == false && " + collection.iterator + "_active_search == false && " + collection.iterator + "_total_rows < 1\">" + emptyListText + "</div>";
html += '</div>';
//}
html += `
<div class=\"List-noItems\" ng-show=\"is_superuser\">

View File

@@ -8,7 +8,6 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce
function ($log, $rootScope, $scope, $state, $stateParams, ProcessErrors, Rest, Wait) {
var api_complete = false,
stdout_url,
current_range,
loaded_sections = [],
event_queue = 0,
@@ -87,7 +86,7 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce
});
function loadStdout() {
Rest.setUrl($scope.stdoutEndpoint + '?format=json&start_line=-' + page_size);
Rest.setUrl($scope.stdoutEndpoint + '?format=json&start_line=0&end_line=' + page_size);
Rest.get()
.success(function(data) {
Wait('stop');
@@ -145,38 +144,17 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce
});
}
$scope.stdOutScrollToTop = function() {
// scroll up or back in time toward the beginning of the file
var start, end, url;
if (loaded_sections.length > 0 && loaded_sections[0].start > 0) {
start = (loaded_sections[0].start - page_size > 0) ? loaded_sections[0].start - page_size : 0;
end = loaded_sections[0].start - 1;
}
else if (loaded_sections.length === 0) {
start = 0;
end = page_size;
}
if (start !== undefined && end !== undefined) {
$('#stdoutMoreRowsTop').fadeIn();
url = stdout_url + '?format=json&start_line=' + start + '&end_line=' + end;
// 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()
.success( function(data) {
//var currentPos = $('#pre-container').scrollTop();
var newSH, oldSH = $('#pre-container').prop('scrollHeight'),
st = $('#pre-container').scrollTop();
$('#pre-container-content').prepend(data.content);
newSH = $('#pre-container').prop('scrollHeight');
$('#pre-container').scrollTop(newSH - oldSH + st);
loaded_sections.unshift({
start: (data.range.start < 0) ? 0 : data.range.start,
end: data.range.end
});
.success(function(data){
$('#pre-container-content').append(data.content);
current_range = data.range;
$('#stdoutMoreRowsTop').fadeOut(400);
})
.error(function(data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!',

View File

@@ -1,6 +1,5 @@
<div class="StandardOut-consoleOutput">
<div id="pre-container" class="body_background body_foreground pre mono-space StandardOut-preContainer"
lr-infinite-scroll="stdOutScrollToTop" scroll-threshold="300" data-direction="up" time-threshold="500">
<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">