diff --git a/awx/ui/static/js/controllers/JobDetail.js b/awx/ui/static/js/controllers/JobDetail.js index 8bd10b1c70..199f346c7b 100644 --- a/awx/ui/static/js/controllers/JobDetail.js +++ b/awx/ui/static/js/controllers/JobDetail.js @@ -23,6 +23,8 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc $scope.plays = []; $scope.tasks = []; + $scope.hosts = []; + $scope.hostResults = []; // Apply each event to the view if ($scope.removeEventsReady) { diff --git a/awx/ui/static/js/helpers/JobDetail.js b/awx/ui/static/js/helpers/JobDetail.js index 3de066a733..a93cc5a495 100644 --- a/awx/ui/static/js/helpers/JobDetail.js +++ b/awx/ui/static/js/helpers/JobDetail.js @@ -39,7 +39,8 @@ angular.module('JobDetailHelper', ['Utilities', 'RestServices']) -.factory('DigestEvents', ['UpdatePlayStatus', 'UpdatePlayNoHostsMatched', function(UpdatePlayStatus, UpdatePlayNoHostsMatched) { +.factory('DigestEvents', ['UpdatePlayStatus', 'UpdatePlayNoHostsMatched', 'UpdateHostStatus', 'UpdatePlayChild', 'AddHostResult', +function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePlayChild, AddHostResult) { return function(params) { var scope = params.scope, events = params.events; @@ -48,8 +49,22 @@ angular.module('JobDetailHelper', ['Utilities', 'RestServices']) scope.plays.push({ id: event.id, name: event.play, + status: (event.failed) ? 'failed' : 'successful', + children: [] + }); + } + if (event.event === 'playbook_on_setup') { + scope.tasks.push({ + id: event.id, + name: event.event_display, + play_id: event.parent, status: (event.failed) ? 'failed' : 'successful' }); + UpdatePlayStatus({ + scope: scope, + play_id: event.parent, + failed: event.failed + }); } if (event.event === 'playbook_on_task_start') { scope.tasks.push({ @@ -58,18 +73,68 @@ angular.module('JobDetailHelper', ['Utilities', 'RestServices']) play_id: event.parent, status: (event.failed) ? 'failed' : 'successful' }); - UpdatePlayStatus({ scope: scope, play_id: event.parent, status: event.status }); + UpdatePlayStatus({ + scope: scope, + play_id: event.parent, + failed: event.failed + }); } if (event.event === 'playbook_on_no_hosts_matched') { UpdatePlayNoHostsMatched({ scope: scope, play_id: event.parent }); } if (event.event === 'runner_on_failed') { + } + if (event.event === 'runner_on_ok') { + UpdateHostStatus({ + scope: scope, + name: event.event_data.host, + host_id: event.host_id, + task_id: event.parent, + status: (event.changed) ? 'changed' : 'ok', + results: (event.res && event.res.results) ? event.res.results : '' + }); + AddHostResult({ + scope: scope, + event: event + }); } if (event.event === 'playbook_on_stats') { } + }); + }; +}]) +.factory('UpdatePlayChild', [ function() { + return function(params) { + var scope = params.scope, + id = params.id, + play_id = params.play_id, + failed = params.failed, + name = params.name, + found_child = false; + scope.plays.every(function(play, i) { + if (play.id === play_id) { + scope.plays[i].children.every(function(child, j) { + if (child.id === id) { + scope.plays[i].children[j].status = (failed) ? 'failed' : 'successful'; + found_child = true; + return false; + } + return true; + }); + if (!found_child) { + scope.plays[i].children.push({ + id: id, + name: name, + status: (failed) ? 'failed' : 'successful' + }); + } + scope.plays[i].status = (failed) ? 'failed' : 'successful'; + return false; + } + return true; }); }; }]) @@ -78,11 +143,11 @@ angular.module('JobDetailHelper', ['Utilities', 'RestServices']) .factory('UpdatePlayStatus', [ function() { return function(params) { var scope = params.scope, - status = params.status, + failed = params.failed, id = params.play_id; scope.plays.every(function(play,idx) { if (play.id === id) { - scope.plays[idx].status = (status) ? 'failed' : 'successful'; + scope.plays[idx].status = (failed) ? 'failed' : 'successful'; return false; } return true; @@ -90,6 +155,19 @@ angular.module('JobDetailHelper', ['Utilities', 'RestServices']) }; }]) +.factory('UpdateTaskStatus', [ function() { + return function(params) { + var scope = params.scope, + task_id = params.task_id, + failed = params.failed; + scope.tasks.every(function (task, i) { + if (task.id === task_id) { + scope.tasks[i].status = (failed) ? 'failed' : 'successful'; + } + }); + }; +}]) + .factory('UpdatePlayNoHostsMatched', [ function() { return function(params) { var scope = params.scope, @@ -102,6 +180,90 @@ angular.module('JobDetailHelper', ['Utilities', 'RestServices']) return true; }); }; +}]) + +// Update or add a new host +.factory('UpdateHostStatus', ['UpdateTaskStatus', function(UpdateTaskStatus) { + return function(params) { + var scope = params.scope, + status = params.status, // ok, changed, unreachable, failed + name = params.name, + host_id = params.host_id, + task_id = params.task_id, + host_found = false; + scope.hosts.every(function(host, i) { + if (host.id === host_id) { + scope.hosts[i].ok += (status === 'ok' || status === 'changed') ? 1 : 0; + scope.hosts[i].changed += (status === 'changed') ? 1 : 0; + scope.hosts[i].unreachable += (status === 'unreachable') ? 1 : 0; + scope.hosts[i].failed += (status === 'failed') ? 1 : 0; + host_found = true; + return false; + } + return true; + }); + if (!host_found) { + scope.hosts.push({ + id: host_id, + name: name, + ok: (status === 'ok' || status === 'changed') ? 1 : 0, + changed: (status === 'changed') ? 1 : 0, + unreachable: (status === 'unreachable') ? 1 : 0, + failed: (status === 'failed') ? 1 : 0 + }); + } + + // Mark task failed + if (status === 'failed') { + UpdateTaskStatus({ + scope: scope, + task_id: task_id, + failed: true + }); + } + }; +}]) + +// Add a new host result +.factory('AddHostResult', ['Empty', function(Empty) { + return function(params) { + var scope = params.scope, + event = params.event, + id, status, host_id, play_name, task_name, module_name, module_args, + results, rc; + + id = event.id; + status = (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful'; + host_id = event.host; + play_name = event.play; + task_name = event.task; + if (event.event_data.res && event.event_data.res.invocation) { + module_name = event.event_data.res.invocation.module_name; + module_args = event.event_data.res.invocation.module_args; + } + else { + module_name = ''; + module_args = ''; + } + if (event.event_data.res && event.event_data.res.results) { + results = ''; + event.event_data.res.results.forEach(function(row) { + results += row; + }); + } + rc = (event.event_data.res && !Empty(event.event_data.res.rc)) ? event.event_data.res.rc : ''; + scope.hostResults.push({ + id: id, + status: status, + host_id: host_id, + play_name: play_name, + task_name : task_name, + module_name: module_name, + module_args: module_args, + results: results, + rc: rc + }); + }; }]); diff --git a/awx/ui/static/less/ansible-ui.less b/awx/ui/static/less/ansible-ui.less index 7007d6ae6b..ec5d6e9423 100644 --- a/awx/ui/static/less/ansible-ui.less +++ b/awx/ui/static/less/ansible-ui.less @@ -1,8 +1,8 @@ /********************************************* * Copyright (c) 2014 AnsibleWorks, Inc. - * + * * ansible-ui.css - * + * * custom styles for ansible-ui * */ @@ -120,7 +120,7 @@ a:focus { } .btn-disabled { - cursor: not-allowed; + cursor: not-allowed; } /* Bring primary (blue) buttons in line with link colors */ @@ -151,12 +151,12 @@ a:focus { #home_groups_table .actions .cancel { padding-right: 3px; } -.success-badge { +.success-badge { color: #ffffff; background-color: #5cb85c; } -.bold-text .checkbox-inline { +.bold-text .checkbox-inline { font-weight: bold; } @@ -176,9 +176,9 @@ textarea.allowresize { z-index: 2000; width: 75px; height: 75px; - text-align:center; + text-align:center; color: #eee; - background-color: @black; + background-color: @black; border: 1px solid @grey; border-radius: 6px; padding-top: 10px; @@ -199,7 +199,7 @@ textarea.allowresize { } .license-version { - font-size: 18px; + font-size: 18px; color: @grey-txt; } @@ -212,9 +212,9 @@ textarea.allowresize { position: absolute; top: 0; left: 0; - z-index: 1999; + z-index: 1999; background-color: @black; - opacity: 0; + opacity: 0; } /* TB tooltip overrides */ @@ -225,7 +225,7 @@ textarea.allowresize { font-size: 12px; } .flyout thead> tr> th, .flyout tbody> tr> td, .flyout tbody> tr> td> a { - font-size: 12px; + font-size: 12px; } .popover-title { padding-top: 5px; @@ -272,7 +272,7 @@ hr { .help-auto-off { margin-top: 15px; margin-bottom: 15px; - margin-left: 10px; + margin-left: 10px; label { font-weight: normal; } @@ -284,7 +284,7 @@ hr { } .panel-heading:hover { - cursor: pointer; + cursor: pointer; } .panel-default>.panel-heading .collapse-help-icon { @@ -296,10 +296,10 @@ hr { margin-bottom: 20px; dl { margin-left: 15px; - } + } dt { margin-top: 15px; - } + } } th.actions-column, @@ -341,7 +341,7 @@ td.actions { } .ssh-key-field, .mono-space { - font-family: Fixed, monospace; + font-family: Fixed, monospace; } dd { @@ -423,7 +423,7 @@ dd { } .navbar-collapse { - padding-right: 0; + padding-right: 0; } .main-menu .nav >li >a:last-child { @@ -437,7 +437,7 @@ dd { margin-top: 11px; } -/* Using inline-block rather than block keeps +/* Using inline-block rather than block keeps brand img from right aligning into the collapse button on mobile screens */ .main-menu .navbar-brand { @@ -480,7 +480,7 @@ dd { padding-top: 10px; padding-bottom: 10px; font-size: 12px; - + a, a:active, a:visited { @@ -501,7 +501,7 @@ dd { } .logo img { - max-width: 46px; + max-width: 46px; } .copyright { @@ -526,7 +526,7 @@ dd { width: 327px; } -.form-title { +.form-title { display: inline-block; width: 100%; vertical-align: middle; @@ -536,7 +536,7 @@ dd { } .form-group { - margin-bottom: 25px; + margin-bottom: 25px; } .form-cancel { @@ -549,7 +549,7 @@ dd { } .form-horizontal .buttons { - margin-top: 25px; + margin-top: 25px; } .label-text { @@ -615,7 +615,7 @@ legend { } /* Pagination */ - .page-label { + .page-label { font-size: 12px; margin-top: 0; text-align: right; @@ -630,7 +630,7 @@ legend { padding: 3px 6px; } - .modal-body { + .modal-body { .pagination { margin-top: 15px; margin-bottom: 0; @@ -658,9 +658,12 @@ legend { } -#lookup-modal-dialog .instructions { - margin-top: 0; - margin-bottom: 20px; +#lookup-modal-dialog + overflow-x: hidden; + .instructions { + margin-top: 0; + margin-bottom: 20px; + } } .related-footer { @@ -701,27 +704,27 @@ select.page-size { } /* Search Widget */ - + .search-widget label { display: inline-block; padding-right: 15px; - vertical-align: middle; + vertical-align: middle; } - - #search_value_input { + + #search_value_input { padding-right: 25px; } .search-reset-start { color: @grey; float: right; - position: relative; + position: relative; top: -25px; left: -10px; z-index: 10; } - .search-reset-start:hover { + .search-reset-start:hover { cursor: pointer; color: @black; } @@ -777,9 +780,9 @@ select.page-size { } /* Display drop-down menus on hover. Remove margin between toggle link - and menu, allowing smooth mouse movement between toggle and menu - - http://stackoverflow.com/questions/8878033/how-to-make-twitter-bootstrap-menu-dropdown-on-hover-rather-than-click + and menu, allowing smooth mouse movement between toggle and menu + + http://stackoverflow.com/questions/8878033/how-to-make-twitter-bootstrap-menu-dropdown-on-hover-rather-than-click */ .dropdown-toggle:hover .dropdown-menu, .dropdown:hover .dropdown-menu { display: block; @@ -787,7 +790,7 @@ select.page-size { .dropdown-menu li:hover { visibility: visible; - } + } .dropdown-menu { margin-top: 0; @@ -866,15 +869,15 @@ input[type="checkbox"].checkbox-no-label { } /*.list-wrapper { background-color: @well; - padding: 10px; + padding: 10px; border-radius: 4px; border: 1px solid @well-border; }*/ .ui-accordion-content { - padding-left: 15px; + padding-left: 15px; padding-right: 15px; } - .page-label { + .page-label { margin-top: 5px; } } @@ -891,7 +894,7 @@ input[type="checkbox"].checkbox-no-label { .table-hover tbody tr:hover > td, .table-hover tbody tr:hover > th { background-color: #fff; - border-top: 1px solid #ddd; + border-top: 1px solid #ddd; } .table-hover-inverse tbody tr:hover > td, @@ -947,7 +950,7 @@ input[type="checkbox"].checkbox-no-label { /* Table info rows */ .loading-info { - color: @grey-txt; + color: @grey-txt; font-weight: normal; padding: 15px 0; } @@ -978,7 +981,7 @@ input[type="checkbox"].checkbox-no-label { padding-top: 5px; } - + .job-new, .license-valid, .job-success, @@ -1014,11 +1017,11 @@ input[type="checkbox"].checkbox-no-label { .icon-job-successful { color: @green; } - + .icon-job-running { .pulsate(); } - + .icon-job-changed, .job-changed { color: @warning; @@ -1053,7 +1056,7 @@ input[type="checkbox"].checkbox-no-label { font-weight: normal; line-height: 1; } - + .job-list.ui-accordion-content { padding: 25px 15px 25px 15px; } @@ -1088,7 +1091,7 @@ input[type="checkbox"].checkbox-no-label { margin-top: 0; margin-bottom: 25px; } - + /* Inventory job status badge */ .failures-true { background-color: @red; @@ -1101,7 +1104,7 @@ input[type="checkbox"].checkbox-no-label { } /* Cloud inventory status. i.e. inventory_source.status values */ - + .icon-cloud-na:before, .icon-cloud-never:before, .icon-cloud-updating:before, @@ -1118,7 +1121,7 @@ input[type="checkbox"].checkbox-no-label { }*/ .icon-cloud-na, - .icon-cloud-never, + .icon-cloud-never, a.icon-cloud-na:hover, a.icon-cloud-never:hover { color: @grey; @@ -1160,14 +1163,14 @@ input[type="checkbox"].checkbox-no-label { color: @red; width: 14px; } - + /* Inventory cloud sourced? indicator */ .icon-cloud-true:before { content: "\f111"; } .icon-cloud-false:before { - content: "\f111"; + content: "\f111"; } .icon-cloud-true { @@ -1176,7 +1179,7 @@ input[type="checkbox"].checkbox-no-label { .icon-cloud-false { color: @grey; - } + } /* end */ .field-success { @@ -1255,13 +1258,13 @@ input[type="checkbox"].checkbox-no-label { overflow: hidden; } -/* Inventory nav links */ +/* Inventory nav links */ .navigation-links { - + padding: 0; margin-top: -10px; - a { + a { margin-right: 15px; } @@ -1280,12 +1283,12 @@ input[type="checkbox"].checkbox-no-label { .selected { font-weight: bold; - color: @blue-dark; + color: @blue-dark; } .inventory-title { - font-size: 16px; - font-weight: bold; + font-size: 16px; + font-weight: bold; } .active-row { @@ -1339,11 +1342,11 @@ input[type="checkbox"].checkbox-no-label { } a.disabled:hover { - color: @grey; - cursor: not-allowed; + color: @grey; + cursor: not-allowed; } a.btn-disabled:hover { - cursor: not-allowed; + cursor: not-allowed; } /* Variable Editing */ @@ -1366,7 +1369,7 @@ a.btn-disabled:hover { display: inline-block; margin-left: 20px; } - + .slider { display: inline-block; width: 100px; @@ -1497,7 +1500,7 @@ tr td button i { li { line-height: normal; margin-bottom: 10px; - } + } ul:last-child { margin-top: 10px; } @@ -1526,7 +1529,7 @@ tr td button i { overflow: hidden; padding: 10px; - img { + img { max-width: 450px; margin-top: 15px; margin-bottom: 15px; @@ -1550,7 +1553,7 @@ tr td button i { } -/* Activity Stream Widget */ +/* Activity Stream Widget */ #stream-container { display: none; @@ -1573,18 +1576,74 @@ tr td button i { margin-bottom: 20px; } } - + /* New job detail page */ -.job-detail-tables { +.relative-position { + position: relative; +} + +.job-detail-tables, .job_options { + .table { + margin-bottom: 0; + } .table>tbody>tr>td { border-top-color: #fff; + padding: 0; } .table>thead>tr>th { border-bottom-color: #fff; + padding: 0; + } + ul { + list-style-type: none; + margin: 0; + padding: 0; + } + li ul { + margin-left: 20px; } } +.job_well { + padding: 8px; + background-color: @well; + border: 1px solid @well-border; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); +} + +.job_options { + height: 100px; + overflow-y: auto; + overflow-x: none; +} + +.scroll-up-indicator { + position: absolute; + right: 18px; + font-size: 14px; + bottom: -2px; + padding: 0; +} + +.scroll-down-indicator { + position: absolute; + right: 18px; + bottom: -2px; + font-size: 14px; + padding: 0; +} + +.scroll-up-indicator, +.scroll-up-indicator:hover, +.scroll-up-indicator:visited, +.scroll-down-indicator, +.scroll-down-indicator:hover, +.scroll-down-indicator:visited { + color: @grey; +} /* ng-cloak directive */ @@ -1621,7 +1680,7 @@ tr td button i { }*/ @media (min-width: 768px) and (max-width: 1199px) { - + .level-1, .level-2, .level-3, @@ -1637,7 +1696,7 @@ tr td button i { } .list-actions button, .list-actions .checkbox-inline { - margin-top: 10px; + margin-top: 10px; } .label-text { @@ -1647,16 +1706,16 @@ tr td button i { .group-name { width: 80%; } - + } /* Landscape phone to portrait tablet */ @media (max-width: 767px) { - + /* Job events */ - + .level-1, .level-2, .level-3, @@ -1687,7 +1746,7 @@ tr td button i { } .list-actions button, .list-actions .checkbox-inline { - margin-top: 10px; + margin-top: 10px; } .group-name { diff --git a/awx/ui/static/lib/ansible/directives.js b/awx/ui/static/lib/ansible/directives.js index 11c0f610be..f9df896cd3 100644 --- a/awx/ui/static/lib/ansible/directives.js +++ b/awx/ui/static/lib/ansible/directives.js @@ -1,7 +1,7 @@ /********************************************* * Copyright (c) 2014 AnsibleWorks, Inc. * - * Custom directives for form validation + * Custom directives for form validation * */ @@ -10,7 +10,7 @@ /* global chkPass:false */ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'JobsHelper']) - + // awpassmatch: Add to password_confirm field. Will test if value // matches that of 'input[name="password"]' .directive('awpassmatch', function() { @@ -32,11 +32,11 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job } }; }) - + // caplitalize Add to any input field where the first letter of each // word should be capitalized. Use in place of css test-transform. // For some reason "text-transform: capitalize" in breadcrumbs - // causes a break at each blank space. And of course, + // causes a break at each blank space. And of course, // "autocapitalize='word'" only works in iOS. Use this as a fix. .directive('capitalize', function() { return { @@ -93,18 +93,18 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job } }; }) - + // // awRequiredWhen: { variable: "", init:"true|false" } // // Make a field required conditionally using a scope variable. If the scope variable is true, the - // field will be required. Otherwise, the required attribute will be removed. - // + // field will be required. Otherwise, the required attribute will be removed. + // .directive('awRequiredWhen', function() { return { require: 'ngModel', link: function(scope, elm, attrs, ctrl) { - + function checkIt () { var viewValue = elm.val(), label, validity = true; if ( scope[attrs.awRequiredWhen] && (elm.attr('required') === null || elm.attr('required') === undefined) ) { @@ -131,12 +131,12 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job } ctrl.$setValidity('required', validity); } - + if (attrs.awrequiredInit !== undefined && attrs.awrequiredInit !== null) { scope[attrs.awRequiredWhen] = attrs.awrequiredInit; checkIt(); } - + scope.$watch(attrs.awRequiredWhen, function() { // watch for the aw-required-when expression to change value checkIt(); @@ -149,7 +149,7 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job } }; }) - + // awPlaceholder: Dynamic placeholder set to a scope variable you want watched. // Value will be place in field placeholder attribute. .directive('awPlaceholder', [ function() { @@ -165,7 +165,7 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job }]) // lookup Validate lookup value against API - // + // .directive('awlookup', ['Rest', function(Rest) { return { require: 'ngModel', @@ -198,10 +198,10 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job } }; }]) - + // // awValidUrl - // + // .directive('awValidUrl', [ function() { return { require: 'ngModel', @@ -222,13 +222,13 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job }; }]) - /* + /* * Enable TB tooltips. To add a tooltip to an element, include the following directive in * the element's attributes: * * aw-tool-tip="<< tooltip text here >>" * - * Include the standard TB data-XXX attributes to controll a tooltip's appearance. We will + * Include the standard TB data-XXX attributes to controll a tooltip's appearance. We will * default placement to the left and delay to 2 seconds. */ .directive('awToolTip', function() { @@ -241,11 +241,11 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job else { placement = (attrs.placement !== undefined && attrs.placement !== null) ? attrs.placement : 'left'; } - + $(element).on('hidden.bs.tooltip', function( ) { // TB3RC1 is leaving behind tooltip
elements. This will remove them - // after a tooltip fades away. If not, they lay overtop of other elements and - // honk up the page. + // after a tooltip fades away. If not, they lay overtop of other elements and + // honk up the page. $('.tooltip').each(function() { $(this).remove(); }); @@ -272,7 +272,7 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job } }; }) - + /* * Enable TB pop-overs. To add a pop-over to an element, include the following directive in * the element's attributes: @@ -470,7 +470,7 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job } }; }]) - + // // awRefresh // @@ -507,8 +507,8 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job }; }]) - - /* + + /* awMultiSelect Relies on select2.js to create a multi-select with tags. */ @@ -543,8 +543,8 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job }; }]) - - /* + + /* * Make an element draggable. Used on inventory groups tree. * * awDraggable: boolean || {{ expression }} @@ -552,7 +552,7 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job */ .directive('awDraggable', [ function() { return function(scope, element, attrs) { - + if (attrs.awDraggable === "true") { var containment = attrs.containment; //provide dataContainment:"#id" $(element).draggable({ @@ -569,8 +569,8 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job } }; }]) - - /* + + /* * Make an element droppable- it can receive draggable elements * * awDroppable: boolean || {{ expression }} @@ -581,7 +581,7 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job var node; if (attrs.awDroppable === "true") { $(element).droppable({ - // the following is inventory specific accept checking and + // the following is inventory specific accept checking and // drop processing. accept: function(draggable) { if (draggable.attr('data-type') === 'group') { @@ -643,7 +643,7 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job var active, list = Store('accordions'), id, base; - + if (!Empty(attrs.openFirst)) { active = 0; } @@ -696,6 +696,51 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job } }); }; + }]) + + .directive('awScrollDown', [ function() { + return function(scope, element, attrs) { + var element_to_scroll = attrs.awScrollDown, elem; + $(element).on('click', function() { + var height = document.getElementById(element_to_scroll).scrollHeight; + $(element).hide(); + $('#' + element_to_scroll).animate({scrollTop:height}, 800); + }); + elem = document.getElementById(element_to_scroll); + $('#' + element_to_scroll).on('scroll', function() { + if (elem.scrollTop > 0 && $(element).is(':visible')) { + $(element).hide(); + } + else if (elem.scrollTop === 0 && !$(element).is(':visible')) { + $(element).fadeIn(2500); + } + }); + if (elem.scrollTop > 0 && $(element).is(':visible')) { + $(element).hide(); + } + }; + }]) + + .directive('awScrollUp', [ function() { + return function(scope, element, attrs) { + var element_to_scroll = attrs.awScrollUp, elem; + $(element).on('click', function() { + $(element).hide(); + $('#' + element_to_scroll).animate({scrollTop:0}, 'slow'); + }); + elem = document.getElementById(element_to_scroll); + $('#' + element_to_scroll).on('scroll', function() { + if (elem.scrollTop === 0 && $(element).is(':visible')) { + $(element).hide(); + } + else if (elem.scrollTop > 0 && !$(element).is(':visible')) { + $(element).fadeIn(2500); + } + }); + if (elem.scrollTop === 0) { + $(element).hide(); + } + }; }]); /* @@ -714,4 +759,4 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job } }; }]); - */ \ No newline at end of file + */ diff --git a/awx/ui/static/partials/job_detail.html b/awx/ui/static/partials/job_detail.html index f8710df11c..911eddfba6 100644 --- a/awx/ui/static/partials/job_detail.html +++ b/awx/ui/static/partials/job_detail.html @@ -2,13 +2,13 @@
-
+
-

Job Options

-
+
Options
+
- + @@ -16,11 +16,11 @@ - + - + @@ -52,29 +52,27 @@
Job Template {{ job_template_name }}
Project {{ project_name }}
Inventory {{ inventory_name }}
Playbook {{ playbook }}
+ more + more
- -

Plays

-
- - - - - - - - - - - - - - -
StatusName
{{ play.name }}{{ play.state }}
+ +
Plays
+
+
    +
  • + {{play.name }} +
      +
    • + {{child.name }} +
    • +
    +
  • +
+

Tasks

@@ -90,10 +88,9 @@ {{ task.name }} - {{ task.state }} - +

Hosts

@@ -101,22 +98,20 @@ - - - - - + + + + - - - - - + + + +
Status NameOKChangedUnreachableFailedOKChangedUnreachableFailed
{{ host.name }}{{ host.ok }}{{ host.changed }}{{ host.unreachable }}{{ host.failed }}{{ host.ok }}{{ host.changed }}{{ host.unreachable }}{{ host.failed }}
@@ -127,17 +122,3 @@
- - - - - - - - - - - - - -