diff --git a/awx/ui/static/js/controllers/Home.js b/awx/ui/static/js/controllers/Home.js
index 66b97e22ab..09475cfe90 100644
--- a/awx/ui/static/js/controllers/Home.js
+++ b/awx/ui/static/js/controllers/Home.js
@@ -122,7 +122,7 @@ Home.$inject = ['$scope', '$compile', '$routeParams', '$rootScope', '$location',
];
-function HomeGroups($location, $routeParams, HomeGroupList, GenerateList, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope,
+function HomeGroups($filter, $compile, $location, $routeParams, LogViewer, HomeGroupList, GenerateList, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope,
GetBasePath, SearchInit, PaginateInit, FormatDate, GetHostsStatusMsg, GetSyncStatusMsg, ViewUpdateStatus, Stream, GroupsEdit, Wait,
Alert, Rest, Empty, InventoryUpdate, Find) {
@@ -135,6 +135,35 @@ function HomeGroups($location, $routeParams, HomeGroupList, GenerateList, Proces
scope = generator.inject(list, { mode: 'edit' }),
opt;
+ function ellipsis(a) {
+ if (a.length > 20) {
+ return a.substr(0,20) + '...';
+ }
+ return a;
+ }
+
+ function attachElem(event, html, title) {
+ var elem = $(event.target).parent();
+ try {
+ elem.tooltip('hide');
+ elem.popover('destroy');
+ }
+ catch(err) {
+ //ignore
+ }
+ elem.attr({ "aw-pop-over": html, "data-title": title, "data-placement": "right" });
+ $compile(elem)(scope);
+ elem.on('shown.bs.popover', function() {
+ $('.popover').each(function() {
+ $compile($(this))(scope); //make nested directives work!
+ });
+ $('.popover-content, .popover-title').click(function() {
+ elem.popover('hide');
+ });
+ });
+ elem.popover('show');
+ }
+
if (scope.removePostRefresh) {
scope.removePostRefresh();
}
@@ -145,7 +174,9 @@ function HomeGroups($location, $routeParams, HomeGroupList, GenerateList, Proces
scope.home_groups[i].inventory_name = scope.home_groups[i].summary_fields.inventory.name;
stat = GetSyncStatusMsg({
- status: scope.home_groups[i].summary_fields.inventory_source.status
+ status: scope.home_groups[i].summary_fields.inventory_source.status,
+ source: scope.home_groups[i].summary_fields.inventory_source.source,
+ has_inventory_sources: scope.home_groups[i].has_inventory_sources
}); // from helpers/Groups.js
hosts_status = GetHostsStatusMsg({
@@ -174,6 +205,7 @@ function HomeGroups($location, $routeParams, HomeGroupList, GenerateList, Proces
list: list,
url: defaultUrl
});
+
PaginateInit({
scope: scope,
list: list,
@@ -258,7 +290,8 @@ function HomeGroups($location, $routeParams, HomeGroupList, GenerateList, Proces
scope: scope,
group_id: group_id,
inventory_id: inventory_id,
- groups_reload: false
+ groups_reload: false,
+ mode: 'edit'
});
};
@@ -306,9 +339,136 @@ function HomeGroups($location, $routeParams, HomeGroupList, GenerateList, Proces
scope.search(list.iterator, null, false, true);
};
+
+ if (scope.removeHostSummaryReady) {
+ scope.removeHostSummaryReady();
+ }
+ scope.removeHostSummaryReady = scope.$on('HostSummaryReady', function(e, event, data) {
+ var html, title = "Recent Jobs", url = GetBasePath('jobs');
+ Wait('stop');
+ if (data.length > 0) {
+ html = "
\n";
+ html += "\n";
+ html += "";
+ html += "| Status | ";
+ html += "View | ";
+ html += "Name | ";
+ html += "
\n";
+ html += "\n";
+ html += "\n";
+ data.forEach(function(row) {
+ html += "\n";
+ html += " | \n";
+ //html += "" + ($filter('date')(row.finished,'MM/dd HH:mm:ss')).replace(/ /,' ') + " | ";
+ html += "Events " +
+ "Hosts | ";
+ html += "" + ellipsis(row.name) + " | ";
+ html += "
\n";
+ });
+ html += "\n";
+ html += "
\n";
+ html += "\n";
+ }
+ else {
+ html = "No recent job data available for this inventory.
\n" +
+ "\n";
+ }
+ attachElem(event, html, title);
+ });
+
+ if (scope.removeGroupSummaryReady) {
+ scope.removeGroupSummaryReady();
+ }
+ scope.removeGroupSummaryReady = scope.$on('GroupSummaryReady', function(e, event, inventory, data) {
+ var html, title;
+
+ Wait('stop');
+
+ // Build the html for our popover
+ html = "\n";
+ html += "\n";
+ html += "";
+ html += "| Status | ";
+ html += "Last Sync | ";
+ html += "Group | ";
+ html += "
";
+ html += "\n";
+ html += "\n";
+ data.results.forEach( function(row) {
+ html += "";
+ html += " | ";
+ html += "" + ($filter('date')(row.last_updated,'MM/dd HH:mm:ss')).replace(/ /,' ') + " | ";
+ html += "" + ellipsis(row.summary_fields.group.name) + " | ";
+ html += "
\n";
+ });
+ html += "\n";
+ html += "
\n";
+ html += "\n";
+ title = "Sync Status";
+ attachElem(event, html, title);
+ });
+
+ scope.showGroupSummary = function(event, id) {
+ var group, status;
+ if (!Empty(id)) {
+ group = Find({ list: scope.home_groups, key: 'id', val: id });
+ status = group.summary_fields.inventory_source.status;
+ if (status === 'failed' || status === 'error' || status === 'successful') {
+ Wait('start');
+ Rest.setUrl(group.related.inventory_sources + '?or__source=ec2&or__source=rax&order_by=-last_job_run&page_size=5');
+ Rest.get()
+ .success(function(data) {
+ scope.$emit('GroupSummaryReady', event, group, data);
+ })
+ .error(function(data, status) {
+ ProcessErrors( scope, data, status, null, { hdr: 'Error!',
+ msg: 'Call to ' + group.related.inventory_sources + ' failed. GET returned status: ' + status
+ });
+ });
+ }
+ }
+ };
+
+ scope.showHostSummary = function(event, id) {
+ var url, jobs = [];
+ if (!Empty(id)) {
+ Wait('start');
+ url = GetBasePath('hosts') + "?groups__id=" + id + "&last_job__isnull=false&order_by=-last_job&page_size=5";
+ Rest.setUrl(url);
+ Rest.get()
+ .success( function(data) {
+ data.results.forEach(function(host) {
+ host.summary_fields.recent_jobs.every(function(job) {
+ if (job.id === host.last_job) {
+ jobs.push(job);
+ return false;
+ }
+ return true;
+ });
+ });
+ scope.$emit('HostSummaryReady', event, jobs);
+ })
+ .error( function(data, status) {
+ ProcessErrors( scope, data, status, null, { hdr: 'Error!',
+ msg: 'Call to ' + url + ' failed. GET returned: ' + status
+ });
+ });
+ }
+ };
+
+ scope.viewJob = function(url) {
+ LogViewer({
+ scope: scope,
+ url: url
+ });
+ };
+
+
}
-HomeGroups.$inject = ['$location', '$routeParams', 'HomeGroupList', 'GenerateList', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller',
+HomeGroups.$inject = ['$filter', '$compile', '$location', '$routeParams', 'LogViewer', 'HomeGroupList', 'GenerateList', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller',
'ClearScope', 'GetBasePath', 'SearchInit', 'PaginateInit', 'FormatDate', 'GetHostsStatusMsg', 'GetSyncStatusMsg', 'ViewUpdateStatus',
'Stream', 'GroupsEdit', 'Wait', 'Alert', 'Rest', 'Empty', 'InventoryUpdate', 'Find'
];
diff --git a/awx/ui/static/js/helpers/Groups.js b/awx/ui/static/js/helpers/Groups.js
index 1400764b25..069e0386e3 100644
--- a/awx/ui/static/js/helpers/Groups.js
+++ b/awx/ui/static/js/helpers/Groups.js
@@ -175,6 +175,12 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
launch_tip = 'Can only be updated by running a sync on the parent group.';
}
+ if (has_inventory_sources === false && Empty(source)) {
+ launch_class = 'btn-disabled';
+ status_tip = 'Cloud source not configured. Click to update.';
+ launch_tip = 'Cloud source not configured.';
+ }
+
return {
"class": stat_class,
"tooltip": status_tip,
diff --git a/awx/ui/static/js/lists/CompletedJobs.js b/awx/ui/static/js/lists/CompletedJobs.js
index df2d6fca8a..c4689e7645 100644
--- a/awx/ui/static/js/lists/CompletedJobs.js
+++ b/awx/ui/static/js/lists/CompletedJobs.js
@@ -45,11 +45,6 @@ angular.module('CompletedJobsDefinition', [])
{ name: "Canceled", value: "canceled" }
]
},
- inventory: {
- label: 'Inventory ID',
- searchType: 'int',
- searchOnly: true
- },
finished: {
label: 'Finished On',
noLink: true,
diff --git a/awx/ui/static/js/lists/HomeGroups.js b/awx/ui/static/js/lists/HomeGroups.js
index 8229c9840d..2dfe3c6e9d 100644
--- a/awx/ui/static/js/lists/HomeGroups.js
+++ b/awx/ui/static/js/lists/HomeGroups.js
@@ -21,6 +21,28 @@ angular.module('HomeGroupListDefinition', [])
well: true,
fields: {
+ status: {
+ label: 'Status',
+ columnClass: 'col-md-2 col-sm-2 col-xs-2',
+ searchable: false,
+ nosort: true,
+ ngClick: "null",
+ iconOnly: true,
+ icons: [{
+ icon: "{{ 'icon-cloud-' + group.status_class }}",
+ awToolTip: "{{ group.status_tooltip }}",
+ dataTipWatch: "group.launch_tooltip",
+ awTipPlacement: "top",
+ ngClick: "showGroupSummary($event, group.id)",
+ ngClass: "group.launch_class"
+ },{
+ icon: "{{ 'icon-job-' + group.hosts_status_class }}",
+ awToolTip: "{{ group.hosts_status_tip }}",
+ awTipPlacement: "top",
+ ngClick: "showHostSummary($event, group.id)",
+ ngClass: ""
+ }]
+ },
name: {
key: true,
label: 'Group',
@@ -82,6 +104,7 @@ angular.module('HomeGroupListDefinition', [])
},
fieldActions: {
+ /*
sync_status: {
mode: 'all',
ngClick: "viewUpdateStatus(group.id, group.group_id)",
@@ -96,6 +119,7 @@ angular.module('HomeGroupListDefinition', [])
ngHref: "/#/inventories/{{ group.inventory }}/",
iconClass: "{{ 'fa icon-failures-' + group.hosts_status_class }}"
},
+ */
group_update: {
//label: 'Sync',
mode: 'all',
diff --git a/awx/ui/static/js/lists/Inventories.js b/awx/ui/static/js/lists/Inventories.js
index 202d0253ba..1f6b18a411 100644
--- a/awx/ui/static/js/lists/Inventories.js
+++ b/awx/ui/static/js/lists/Inventories.js
@@ -27,7 +27,7 @@ angular.module('InventoriesListDefinition', [])
searchable: false,
nosort: true,
ngClick: "null",
- dataTitle: "Sync Status",
+ iconOnly: true,
icons: [{
icon: "{{ 'icon-cloud-' + inventory.syncStatus }}",
awToolTip: "{{ inventory.syncTip }}",
diff --git a/awx/ui/static/js/lists/QueuedJobs.js b/awx/ui/static/js/lists/QueuedJobs.js
index a4b2dbeb8f..ca7b6da22e 100644
--- a/awx/ui/static/js/lists/QueuedJobs.js
+++ b/awx/ui/static/js/lists/QueuedJobs.js
@@ -36,12 +36,8 @@ angular.module('QueuedJobsDefinition', [])
dataTitle: "{{ queued_job.status_popover_title }}",
icon: 'icon-job-{{ queued_job.status }}',
iconOnly: true,
- ngClick:"viewJobLog(queued_job.id)"
- },
- inventory: {
- label: 'Inventory ID',
- searchType: 'int',
- searchOnly: true
+ ngClick:"viewJobLog(queued_job.id)",
+ searchable: false
},
created: {
label: 'Created On',
diff --git a/awx/ui/static/js/lists/RunningJobs.js b/awx/ui/static/js/lists/RunningJobs.js
index a25bc71d6c..fd80aa9701 100644
--- a/awx/ui/static/js/lists/RunningJobs.js
+++ b/awx/ui/static/js/lists/RunningJobs.js
@@ -36,12 +36,8 @@ angular.module('RunningJobsDefinition', [])
dataTitle: "{{ running_job.status_popover_title }}",
icon: 'icon-job-{{ running_job.status }}',
iconOnly: true,
- ngClick:"viewJobLog(running_job.id)"
- },
- inventory: {
- label: 'Inventory ID',
- searchType: 'int',
- searchOnly: true
+ ngClick:"viewJobLog(running_job.id)",
+ searchable: false
},
started: {
label: 'Started On',
diff --git a/awx/ui/static/less/ansible-ui.less b/awx/ui/static/less/ansible-ui.less
index 23f04b5152..cf2c6829b5 100644
--- a/awx/ui/static/less/ansible-ui.less
+++ b/awx/ui/static/less/ansible-ui.less
@@ -1249,7 +1249,8 @@ input[type="checkbox"].checkbox-no-label {
/* Inventory Edit */
- #inventories_table i[class*="icon-job-"] {
+ #inventories_table i[class*="icon-job-"],
+ #home_groups_table i[class*="icon-job-"] {
margin-left: 8px;
}
diff --git a/awx/ui/static/lib/ansible/generator-helpers.js b/awx/ui/static/lib/ansible/generator-helpers.js
index 05fe0a3e7f..bec62420f2 100644
--- a/awx/ui/static/lib/ansible/generator-helpers.js
+++ b/awx/ui/static/lib/ansible/generator-helpers.js
@@ -483,6 +483,7 @@ angular.module('GeneratorHelpers', [])
html += "aw-pop-over=\"" + field.awPopOver + "\" ";
html += (field.dataPlacement) ? "data-placement=\"" + field.dataPlacement + "\" " : "";
}
+ html += (field.ngClass) ? Attr(field, 'ngClass') : '';
html += ">";
// Add icon:
diff --git a/awx/ui/static/partials/subhome.html b/awx/ui/static/partials/subhome.html
index 204e551564..61e0bc9f0f 100644
--- a/awx/ui/static/partials/subhome.html
+++ b/awx/ui/static/partials/subhome.html
@@ -1,4 +1,41 @@
\ No newline at end of file