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
-
-
-
Tasks
-
-
-
-
Host Details
-
-
{{ activeTaskName }}
+
+
+
+
Plays
+
+
+
+
+ |
+ {{ play.name }} |
+
+
+
+
+
+
+
+
Tasks for play: {{ activePlayName }}
+
+
+
+
+ |
+ {{ task.name }} |
+
+
+
+
+
+
+
+
Hosts in task: {{ activeTaskName }}
+
+
+
-
-
Hosts Summary
-
-
-
-
- Name |
- OK |
- Changed |
- Unreachable |
- Failed |
-
-
-
-
- {{ 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 |
+
+
+
+
+
+
+
+
+ Host |
+ OK |
+ Changed |
+ Unreachable |
+ Failed |
+
+
+
+
+ {{ host.name }} |
+ {{ host.ok }} |
+ {{ host.changed }} |
+ {{ host.unreachable }} |
+ {{ host.failed }} |
+
+
+
+
+
+
+