diff --git a/awx/ui/static/js/controllers/JobDetail.js b/awx/ui/static/js/controllers/JobDetail.js index 0120953661..132c3bd1ca 100644 --- a/awx/ui/static/js/controllers/JobDetail.js +++ b/awx/ui/static/js/controllers/JobDetail.js @@ -59,6 +59,39 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc } }); + if ($scope.removeGetCredentialNames) { + $scope.removeGetCredentialNames(); + } + $scope.removeGetCredentialNames = $scope.$on('GetCredentialNames', function(e, data) { + var url; + if (data.credential) { + url = GetBasePath('credentials') + data.credential + '/'; + Rest.setUrl(url); + Rest.get() + .success( function(data) { + $scope.credential_name = data.name; + }) + .error( function(data, status) { + $scope.credential_name = ''; + ProcessErrors($scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + '. GET returned: ' + status }); + }); + } + if (data.cloud_credential) { + url = GetBasePath('credentials') + data.credential + '/'; + Rest.setUrl(url); + Rest.get() + .success( function(data) { + $scope.cloud_credential_name = data.name; + }) + .error( function(data, status) { + $scope.credential_name = ''; + ProcessErrors($scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + '. GET returned: ' + status }); + }); + } + }); + // Load the job record Rest.setUrl(GetBasePath('jobs') + job_id + '/'); Rest.get() @@ -78,7 +111,12 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc $scope.limit = data.limit; $scope.verbosity = data.verbosity; $scope.job_tags = data.job_tags; + $scope.started = data.started; + $scope.finished = data.finished; + $scope.elapsed = data.elapsed; + $scope.job_status = data.status; $scope.$emit('JobReady', data.related.job_events + '?page_size=50&order_by=id'); + $scope.$emit('GetCredentialNames', data); }) .error(function(data, status) { ProcessErrors($scope, data, status, null, { hdr: 'Error!', diff --git a/awx/ui/static/js/helpers/JobDetail.js b/awx/ui/static/js/helpers/JobDetail.js index 011a46737d..79d9aa524c 100644 --- a/awx/ui/static/js/helpers/JobDetail.js +++ b/awx/ui/static/js/helpers/JobDetail.js @@ -39,11 +39,13 @@ angular.module('JobDetailHelper', ['Utilities', 'RestServices']) -.factory('DigestEvents', ['UpdatePlayStatus', 'UpdatePlayNoHostsMatched', 'UpdateHostStatus', 'UpdatePlayChild', 'AddHostResult', 'MakeLastRowActive', -function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePlayChild, AddHostResult, MakeLastRowActive) { +.factory('DigestEvents', ['UpdatePlayStatus', 'UpdatePlayNoHostsMatched', 'UpdateHostStatus', 'UpdatePlayChild', 'AddHostResult', 'SelectPlay', 'SelectTask', +function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePlayChild, AddHostResult, SelectPlay, SelectTask) { return function(params) { + var scope = params.scope, events = params.events; + events.forEach(function(event) { if (event.event === 'playbook_on_play_start') { scope.plays.push({ @@ -52,8 +54,10 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla status: (event.changed) ? 'changed' : (event.failed) ? 'failed' : 'none', children: [] }); - scope.activePlay = event.id; - MakeLastRowActive({ scope: scope, list: scope.plays, field: 'playActiveClass', set: 'activePlay' }); + SelectPlay({ + scope: scope, + id: event.id + }); } if (event.event === 'playbook_on_setup') { scope.tasks.push({ @@ -82,7 +86,10 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla failed: event.failed, changed: event.changed }); - MakeLastRowActive({ scope: scope, list: scope.tasks, field: 'taskActiveClass', set: 'activeTask' }); + SelectTask({ + scope: scope, + id: event.id + }); } /*if (event.event === 'playbook_on_no_hosts_matched') { UpdatePlayNoHostsMatched({ scope: scope, play_id: event.parent }); @@ -94,7 +101,7 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla UpdateHostStatus({ scope: scope, name: event.event_data.host, - host_id: event.host_id, + host_id: event.host, task_id: event.parent, status: (event.changed) ? 'changed' : 'ok', results: (event.res && event.res.results) ? event.res.results : '' @@ -169,7 +176,7 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla id = params.play_id; scope.plays.every(function(play,idx) { if (play.id === id) { - scope.plays[idx].status = (changed) ? 'changed' : (failed) ? 'failed' : 'none'; + scope.plays[idx].status = (changed) ? 'changed' : (failed) ? 'failed' : 'successful'; return false; } return true; @@ -190,7 +197,7 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla scope: scope, failed: failed, changed: changed, - play_id: task.parent + play_id: task.play_id }); } }); @@ -293,34 +300,80 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla }; }]) -.factory('SelectPlay', [ function() { +.factory('SelectPlay', ['SelectTask', function(SelectTask) { return function(params) { var scope = params.scope, - id = params.id; + id = params.id, + max_task_id = 0; scope.plays.forEach(function(play, idx) { if (play.id === id) { scope.plays[idx].playActiveClass = 'active'; + scope.activePlay = id; + scope.activePlayName = play.name; } else { scope.plays[idx].playActiveClass = ''; } }); - scope.activePlay = id; + + // Select the last task + scope.tasks.forEach(function(task) { + if (task.play_id === scope.activePlay && task.id > max_task_id) { + max_task_id = task.id; + } + }); + scope.activeTask = max_task_id; + SelectTask({ + scope: scope, + id: max_task_id, + callback: function() { + setTimeout(function() { + var inner_height = $('#job_tasks .job-detail-table').height(); + $('#job_tasks').scrollTop(inner_height); + }, 100); + } + }); }; }]) -.factory('SelectTask', [ function() { +.factory('SelectTask', ['SelectHost', function(SelectHost) { return function(params) { var scope = params.scope, - id = params.id; + id = params.id, + callback = params.callback; scope.tasks.forEach(function(task, idx) { if (task.id === id) { scope.tasks[idx].taskActiveClass = 'active'; + scope.activeTask = id; + scope.activeTaskName = task.name; } else { scope.tasks[idx].taskActiveClass = ''; } }); - scope.activeTask = id; + if (callback) { + callback(); + } + SelectHost(); + }; +}]) + +.factory('SelectHost', [ function() { + return function() { + setTimeout(function() { + var inner_height = $('#host_details .job-detail-table').height(); + $('#host_details').scrollTop(inner_height); + }, 100); }; }]); + + + + + + + + + + + diff --git a/awx/ui/static/less/ansible-ui.less b/awx/ui/static/less/ansible-ui.less index c4eeb1ecde..9bfcae9a89 100644 --- a/awx/ui/static/less/ansible-ui.less +++ b/awx/ui/static/less/ansible-ui.less @@ -34,6 +34,7 @@ @import "codemirror.less"; @import "angular-scheduler.less"; @import "log-viewer.less"; +@import "job-details.less"; html, body { height: 100%; } @@ -854,7 +855,7 @@ input[type="checkbox"].checkbox-no-label { /* Display list actions next to search widget */ .list-actions { text-align: right; - margin-bottom: 25px; + margin-bottom: 10px; button { margin-left: 4px; @@ -1278,9 +1279,14 @@ input[type="checkbox"].checkbox-no-label { /* Inventory Edit */ + #groups-container .well, + #hosts-container .well { + padding: 8px; + } + #inventories_table i[class*="icon-job-"], #home_groups_table i[class*="icon-job-"] { - margin-left: 8px; + margin-left: 10px; } .selected { @@ -1579,93 +1585,7 @@ tr td button i { } } -/* New job detail page */ -.relative-position { - position: relative; -} - -.job-detail-tables, .job_options { - .table { - margin-bottom: 0; - } - .table>tbody>tr>td { - border-top-color: @well; - padding: 0; - } - .table>thead>tr>th { - border-bottom-color: @well; - 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; -} - -.job-list li.active { - background-color: @active-color; -} - -.job_plays, .job_tasks, .job_hosts { - height: 150px; - overflow-y: auto; - overflow-x: none; -} - -.host_details { - pre { - border: none; - max-height: 100px; - overflow: auto; - margin: 0; - padding: 0; - } -} - -.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 */ diff --git a/awx/ui/static/less/job-details.less b/awx/ui/static/less/job-details.less new file mode 100644 index 0000000000..295f5e8863 --- /dev/null +++ b/awx/ui/static/less/job-details.less @@ -0,0 +1,99 @@ +/********************************************* + * Copyright (c) 2014 AnsibleWorks, Inc. + * + * job-details.less + * + * Styles for job details page + * + */ + +.job-detail-tables { + .table { + margin-bottom: 0; + } + .table>tbody>tr>td { + border-top-color: @well; + padding: 0; + } + .table>thead>tr>th { + border-bottom-color: @well; + padding: 0; + } + ul { + list-style-type: none; + margin: 0; + padding: 0; + } + li ul { + margin-left: 20px; + } + .status-column { + width: 25px; + font-size: 12px; + text-align: center; + i { + margin-top: 4px; + } + } + .table>tbody>tr.active>td { + background-color: @active-color; + } +} + +.section { + margin-top: 20px; + h5 { + margin-top: 0; + margin-bottom: 5px; + } + .small-title { + font-weight: normal; + } +} + +.job_summary, .job_status { + .table { + margin-bottom: 0; + } + .table>tbody>tr>td { + border-top-color: @well; + } + .table>thead>tr>th { + border-bottom-color: @well; + } +} + +.job_status { + margin-bottom: 25px; + + .label_column { + width: 80px; + } + .table>tbody>tr>td { + padding-bottom: 3px; + } + .status-column i { + font-size: 12px; + } +} + +.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; +} + +#job_plays, #job_tasks, #host_details { + height: 150px; + overflow-y: auto; + overflow-x: none; +} diff --git a/awx/ui/static/partials/job_detail.html b/awx/ui/static/partials/job_detail.html index d2bc500a11..ef6d56345c 100644 --- a/awx/ui/static/partials/job_detail.html +++ b/awx/ui/static/partials/job_detail.html @@ -5,132 +5,148 @@
-
-
Options
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Job Template{{ job_template_name }}
Project{{ project_name }}
Inventory{{ inventory_name }}
Playbook{{ playbook }}
Run Type{{ job_type }}
Machine Credential{{ credential }}
Forks{{ forks }}
Limit{{ limit }}
Verbosity{{ verbosity }}
Job Tags{{ job_tags }}
-
- - -
+
-
Plays
-
-
    -
  • - {{play.name }} -
      -
    • - {{child.name }} -
    • -
    -
  • -
-
- -
Tasks
-
-
    -
  • {{ task.name }}
  • -
-
- - -
Host Details
-
-

{{ activeTaskName }}

+
+
+
Job
+
- - - - - - - - - - - - -
HostResults
{{ result.host_name }}
{{ result.results }}
+ + + Job Template + {{ job_template_name }} + + + Project + {{ project_name }} + + + Inventory + {{ inventory_name }} + + + Playbook + {{ playbook }} + + + Run Type + {{ job_type }} + + + Machine Credential + {{ credential_name }} + + + Cloud Credential + {{ cloud_credential_name }} + + + Forks + {{ forks }} + + + Limit + {{ limit }} + + + Verbosity + {{ verbosity }} + + + Job Tags + {{ job_tags }} + + + +
-
+ +
+
Plays
+
+ + + + + + + +
{{ play.name }}
+
+
+ +
+
Tasks for play: {{ activePlayName }}
+
+ + + + + + + +
{{ task.name }}
+
+
+ +
+
Hosts in task: {{ activeTaskName }}
+
+ + + + + + + +
{{ result.host_name }}
+
+
+
-
-
Hosts Summary
-
- - - - - - - - - - - - - - - - - - - -
NameOKChangedUnreachableFailed
{{ host.name }}{{ host.ok }}{{ host.changed }}{{ host.unreachable }}{{ host.failed }}
-
- +
+
+
Summary
+
+ +
+ + + + + + + +
Status {{ job_status }}
Started{{ started | date:'MM/dd/yy HH:mm:ss' }}
Finished{{ finished | date:'MM/dd/yy HH:mm:ss' }}
Elapsed{{ elapsed }} seconds
+
+ +
+ + + + + + + + + + + + + + + + + + + +
HostOKChangedUnreachableFailed
{{ host.name }}{{ host.ok }}{{ host.changed }}{{ host.unreachable }}{{ host.failed }}
+
+ +
+