diff --git a/awx/ui/static/js/controllers/Inventories.js b/awx/ui/static/js/controllers/Inventories.js
index de2c723e2e..e94647e71c 100644
--- a/awx/ui/static/js/controllers/Inventories.js
+++ b/awx/ui/static/js/controllers/Inventories.js
@@ -36,23 +36,73 @@ function InventoriesList ($scope, $rootScope, $location, $log, $routeParams, Res
}
scope.projectsPostRefresh = scope.$on('PostRefresh', function() {
for (var i=0; i < scope.inventories.length; i++) {
+
+ // Set values for Failed Hosts column
+ scope.inventories[i].failed_hosts = scope.inventories[i].hosts_with_active_failures + ' / ' + scope.inventories[i].total_hosts;
if (scope.inventories[i].hosts_with_active_failures > 0) {
- scope.inventories[i].active_failures_params = "/?has_active_failures=true";
- }
- if (scope.inventories[i].has_inventory_sources) {
- //scope.inventories[i].inventory_source = 'external';
- scope.inventories[i].has_inv_sources_tip = 'Has one or more external sources. Click to view details.';
- scope.inventories[i].has_inv_sources_link = '/#/inventories/' + scope.inventories[i].id +
- '/groups?has_external_source=true';
- scope.inventories[i].inventory_sources = 'yes';
+ scope.inventories[i].failed_hosts_tip = "Contains " + scope.inventories[i].hosts_with_active_failures +
+ [ (scope.inventories[i].hosts_with_active_failures == 1) ? ' host' : ' hosts' ] + ' where the latest job failed. Click to view the ' +
+ [ (scope.inventories[i].hosts_with_active_failures == 1) ? ' offending host.' : ' hosts with failed jobs.' ];
+ scope.inventories[i].failed_hosts_link = '/#/inventories/' + scope.inventories[i].id + '/hosts?has_active_failures=true';
+ scope.inventories[i].failed_hosts_class = 'true';
}
else {
- //scope.inventories[i].inventory_source = 'manual';
- scope.inventories[i].has_inv_sources_tip = 'Has no external sources.';
- scope.inventories[i].has_inv_sources_link = '/#/inventories/' + scope.inventories[i].id +
- '/groups';
- scope.inventories[i].inventory_sources = 'no';
+ if (scope.inventories[i].total_hosts == 0) {
+ // no hosts
+ scope.inventories[i].failed_hosts_tip = "There are no hosts in this inventory. It's a sad empty shell. Click to view the hosts page and add a host.";
+ scope.inventories[i].failed_hosts_link = '/#/inventories/' + scope.inventories[i].id + '/hosts';
+ scope.inventories[i].failed_hosts_class = 'na';
+ }
+ else if (scope.inventories[i].total_hosts == 1) {
+ // on host with 0 failures
+ scope.inventories[i].failed_hosts_tip = "The 1 host contained in this inventory does not have a current job failure. It's happy!" +
+ " Click to view the host.";
+ scope.inventories[i].failed_hosts_link = '/#/inventories/' + scope.inventories[i].id + '/hosts';
+ scope.inventories[i].failed_hosts_class = 'false';
+ }
+ else {
+ // many hosts with 0 failures
+ scope.inventories[i].failed_hosts_tip = "All " + scope.inventories[i].total_hosts + " hosts are happy! None of them have " +
+ " a recent job failure. Click to view the hosts.";
+ scope.inventories[i].failed_hosts_link = '/#/inventories/' + scope.inventories[i].id + '/hosts';
+ scope.inventories[i].failed_hosts_class = 'false';
+ }
}
+
+ // Set values for Status column
+ scope.inventories[i].status = scope.inventories[i].inventory_sources_with_failures + ' / ' + scope.inventories[i].total_inventory_sources;
+ if (scope.inventories[i].inventory_sources_with_failures > 0) {
+ scope.inventories[i].status_tip = "Contains " + scope.inventories[i].inventory_sources_with_failures +
+ [ (scope.inventories[i].inventory_sources_with_failures == 1) ? ' group' : ' groups' ] + ' where the latest inventory update failed. ' +
+ 'Click to view the ' +
+ [ (scope.inventories[i].inventory_sources_with_failures == 1) ? ' offending group.' : ' groups with failures.' ];
+ scope.inventories[i].status_link = '/#/inventories/' + scope.inventories[i].id + '/groups?status=failed';
+ scope.inventories[i].status_class = 'failed';
+ }
+ else {
+ if (scope.inventories[i].total_inventory_sources == 0) {
+ // no groups are reporting a source
+ scope.inventories[i].status_tip = "There are no groups configured for an external inventory source. Click to view groups and " +
+ "and add an inventory source.";
+ scope.inventories[i].status_link = '/#/inventories/' + scope.inventories[i].id + '/groups';
+ scope.inventories[i].status_class = 'na';
+ }
+ else if (scope.inventories[i].total_inventory_sources == 1) {
+ // on host with 0 failures
+ scope.inventories[i].status_tip = "The 1 group configured with an inventory source was updated successfully. It's happy!" +
+ " Click to view the group.";
+ scope.inventories[i].status_link = '/#/inventories/' + scope.inventories[i].id + '/groups?has_external_source=true';
+ scope.inventories[i].status_class = 'successful';
+ }
+ else {
+ // many hosts with 0 failures
+ scope.inventories[i].status_tip = "The " + scope.inventories[i].total_inventory_sources + " groups with an inventory source are happy! " +
+ " The most recent update of each group was successful. Click to view the groups.";
+ scope.inventories[i].status_link = '/#/inventories/' + scope.inventories[i].id + '/groups?has_external_source=true';
+ scope.inventories[i].status_class = 'successful';
+ }
+ }
+
}
});
diff --git a/awx/ui/static/js/forms/InventoryHosts.js b/awx/ui/static/js/forms/InventoryHosts.js
index 62b89ad048..afc479486a 100644
--- a/awx/ui/static/js/forms/InventoryHosts.js
+++ b/awx/ui/static/js/forms/InventoryHosts.js
@@ -22,7 +22,7 @@ angular.module('InventoryHostsFormDefinition', [])
ngClick: "editHost(\{\{ host.id \}\}, '\{\{ host.name \}\}')"
},
active_failures: {
- label: 'Current
Job Status?',
+ label: 'Job Status?',
ngHref: "\{\{ host.activeFailuresLink \}\}",
awToolTip: "\{\{ host.badgeToolTip \}\}",
dataPlacement: 'top',
@@ -34,7 +34,7 @@ angular.module('InventoryHostsFormDefinition', [])
searchable: false,
nosort: true
},
- inventory_sources: {
+ /*inventory_sources: {
label: 'External
Source?',
ngHref: "\{\{ host.has_inv_source_link \}\}",
badgeNgHref: "\{\{ host.has_inv_source_link \}\}",
@@ -46,7 +46,7 @@ angular.module('InventoryHostsFormDefinition', [])
badgeTipPlacement: 'top',
searchable: false,
nosort: true
- },
+ },*/
groups: {
label: 'Groups',
searchable: true,
@@ -55,7 +55,7 @@ angular.module('InventoryHostsFormDefinition', [])
nosort: true
},
has_active_failures: {
- label: 'Current job failed?',
+ label: 'Has failed job?',
searchSingleValue: true,
searchType: 'boolean',
searchValue: 'true',
@@ -90,7 +90,7 @@ angular.module('InventoryHostsFormDefinition', [])
icon: 'icon-plus'
},
help: {
- dataPlacement: 'left',
+ dataPlacement: 'top',
icon: "icon-question-sign",
mode: 'all',
'class': 'btn-xs btn-info btn-help',
diff --git a/awx/ui/static/js/helpers/Groups.js b/awx/ui/static/js/helpers/Groups.js
index 1bbf847d27..5debd67972 100644
--- a/awx/ui/static/js/helpers/Groups.js
+++ b/awx/ui/static/js/helpers/Groups.js
@@ -232,6 +232,39 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
else {
scope.groups[i].active_failures_params = "/?has_active_failures=false";
}
+
+
+ // Set values for Failed Hosts column
+ scope.groups[i].failed_hosts = scope.groups[i].hosts_with_active_failures + ' / ' + scope.groups[i].total_hosts;
+ if (scope.groups[i].hosts_with_active_failures > 0) {
+ scope.groups[i].failed_hosts_tip = "Contains " + scope.groups[i].hosts_with_active_failures +
+ [ (scope.groups[i].hosts_with_active_failures == 1) ? ' host' : ' hosts' ] + ' where the latest job failed. Click to view the ' +
+ [ (scope.groups[i].hosts_with_active_failures == 1) ? ' offending host.' : ' hosts with failed jobs.' ];
+ scope.groups[i].failed_hosts_link = '/#/inventories/' + scope.groups[i].inventory + '/hosts?has_active_failures=true';
+ scope.groups[i].failed_hosts_class = 'true';
+ }
+ else {
+ if (scope.groups[i].total_hosts == 0) {
+ // no hosts
+ scope.groups[i].failed_hosts_tip = "There are no hosts in this inventory. It's a sad empty shell. Click to view the hosts page and add a host.";
+ scope.groups[i].failed_hosts_link = '/#/inventories/' + scope.groups[i].inventory + '/hosts';
+ scope.groups[i].failed_hosts_class = 'na';
+ }
+ else if (scope.groups[i].total_hosts == 1) {
+ // on host with 0 failures
+ scope.groups[i].failed_hosts_tip = "The 1 host contained in this inventory does not have a current job failure. It's happy!" +
+ " Click to view the host.";
+ scope.groups[i].failed_hosts_link = '/#/inventories/' + scope.groups[i].inventory + '/hosts';
+ scope.groups[i].failed_hosts_class = 'false';
+ }
+ else {
+ // many hosts with 0 failures
+ scope.groups[i].failed_hosts_tip = "All " + scope.groups[i].total_hosts + " hosts are happy! None of them have " +
+ " a recent job failure. Click to view the hosts.";
+ scope.groups[i].failed_hosts_link = '/#/inventories/' + scope.groups[i].inventory + '/hosts';
+ scope.groups[i].failed_hosts_class = 'false';
+ }
+ }
scope.groups[i].status = stat;
scope.groups[i].source = scope.groups[i].summary_fields.inventory_source.source;
@@ -263,7 +296,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
scope[list.iterator + 'SearchField'] = 'status';
scope[list.iterator + 'SelectShow'] = true;
scope[list.iterator + 'SearchSelectOpts'] = list.fields['status'].searchOptions;
- scope[list.iterator + 'SearchFieldLabel'] = list.fields['status'].label;
+ scope[list.iterator + 'SearchFieldLabel'] = list.fields['status'].label.replace(/\
/g,' ');
for (var opt in list.fields['status'].searchOptions) {
if (list.fields['status'].searchOptions[opt].value == $routeParams['status']) {
scope[list.iterator + 'SearchSelectValue'] = list.fields['status'].searchOptions[opt];
diff --git a/awx/ui/static/js/helpers/search.js b/awx/ui/static/js/helpers/search.js
index ec8afd46a6..ecd693fd6c 100644
--- a/awx/ui/static/js/helpers/search.js
+++ b/awx/ui/static/js/helpers/search.js
@@ -263,6 +263,9 @@ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper'])
if (list.fields[fld].searchField) {
sort_order = direction + list.fields[fld].searchField;
}
+ else if (list.fields[fld].sortField) {
+ sort_order = direction + list.fields[fld].sortField;
+ }
else {
if (list.fields[fld].sourceModel) {
sort_order = direction + list.fields[fld].sourceModel + '__' + list.fields[fld].sourceField;
diff --git a/awx/ui/static/js/lists/Inventories.js b/awx/ui/static/js/lists/Inventories.js
index e7282cf55a..a65130fdae 100644
--- a/awx/ui/static/js/lists/Inventories.js
+++ b/awx/ui/static/js/lists/Inventories.js
@@ -31,29 +31,33 @@ angular.module('InventoriesListDefinition', [])
sourceField: 'name',
excludeModal: true
},
- hosts_with_active_failures: {
- label: 'Hosts with
Failed Job?',
- ngHref: '/#/inventories/{{ inventory.id }}/hosts{{ inventory.active_failures_params }}',
- type: 'badgeCount',
- "class": "{{ 'failures-' + inventory.has_active_failures }}",
- awToolTip: '# of hosts with failed jobs. Click to view hosts.',
- dataPlacement: 'top',
- searchable: false,
- excludeModal: true
- },
- inventory_sources: {
- label: 'External
Sources?',
- ngHref: '\{\{ inventory.has_inv_sources_link \}\}',
- badgeNgHref: '\{\{ inventory.has_inv_sources_link \}\}',
- badgeIcon: "\{\{ 'icon-cloud-' + inventory.has_inventory_sources \}\}",
+ failed_hosts: {
+ label: 'Failed Hosts',
+ ngHref: "\{\{ inventory.failed_hosts_link \}\}",
+ badgeIcon: "\{\{ 'icon-failures-' + inventory.failed_hosts_class \}\}",
+ badgeNgHref: "\{\{ inventory.failed_hosts_link \}\}",
badgePlacement: 'left',
- badgeToolTip: "\{\{ inventory.has_inv_sources_tip \}\}",
- awToolTip: "\{\{ inventory.has_inv_sources_tip \}\}",
- dataPlacement: "top",
+ badgeToolTip: "\{\{ inventory.failed_hosts_tip \}\}",
badgeTipPlacement: 'top',
+ awToolTip: "\{\{ inventory.failed_hosts_tip \}\}",
+ dataPlacement: "top",
searchable: false,
excludeModal: true,
- nosort: true
+ sortField: "hosts_with_active_failures"
+ },
+ status: {
+ label: 'Status',
+ ngHref: "\{\{ inventory.status_link \}\}",
+ badgeIcon: "\{\{ 'icon-cloud-' + inventory.status_class \}\}",
+ badgeNgHref: "\{\{ inventory.status_link \}\}",
+ badgePlacement: 'left',
+ badgeTipPlacement: 'top',
+ badgeToolTip: "\{\{ inventory.status_tip \}\}",
+ awToolTip: "\{\{ inventory.status_tip \}\}",
+ dataPlacement: "top",
+ searchable: false,
+ excludeModal: true,
+ sortField: "inventory_sources_with_failures"
},
has_inventory_sources: {
label: 'Has external sources?',
@@ -63,7 +67,7 @@ angular.module('InventoriesListDefinition', [])
searchOnly: true
},
has_active_failures: {
- label: 'Has hosts with failed jobs?',
+ label: 'Has failed hosts?',
searchSingleValue: true,
searchType: 'boolean',
searchValue: 'true',
diff --git a/awx/ui/static/js/lists/InventorySummary.js b/awx/ui/static/js/lists/InventorySummary.js
index e6474d101d..f34f57d427 100644
--- a/awx/ui/static/js/lists/InventorySummary.js
+++ b/awx/ui/static/js/lists/InventorySummary.js
@@ -25,18 +25,22 @@ angular.module('InventorySummaryDefinition', [])
ngClick: "\{\{ 'GroupsEdit(' + group.id + ')' \}\}",
columnClass: 'col-lg-3 col-md3 col-sm-2'
},
- hosts_with_active_failures: {
- label: 'Hosts with
Job Failures?',
- ngHref: '/#/inventories/{{ inventory_id }}/hosts{{ group.active_failures_params }}',
- type: 'badgeCount',
- "class": "{{ 'failures-' + group.has_active_failures }}",
- awToolTip: '# of hosts with job failures. Click to view hosts.',
- dataPlacement: 'top',
+ failed_hosts: {
+ label: 'Failed Hosts',
+ ngHref: "\{\{ group.failed_hosts_link \}\}",
+ badgeIcon: "\{\{ 'icon-failures-' + group.failed_hosts_class \}\}",
+ badgeNgHref: "\{\{ group.failed_hosts_link \}\}",
+ badgePlacement: 'left',
+ badgeToolTip: "\{\{ group.failed_hosts_tip \}\}",
+ badgeTipPlacement: 'top',
+ awToolTip: "\{\{ group.failed_hosts_tip \}\}",
+ dataPlacement: "top",
searchable: false,
- nosort: false
+ excludeModal: true,
+ sortField: "hosts_with_active_failures"
},
status: {
- label: 'Update
Status',
+ label: 'Status',
ngClick: "viewUpdateStatus(\{\{ group.id \}\})",
searchType: 'select',
badgeIcon: "\{\{ 'icon-cloud-' + group.status_badge_class \}\}",
@@ -69,7 +73,8 @@ angular.module('InventorySummaryDefinition', [])
{ name: "none", value: "" },
{ name: "rackspace", value: "rackspace" }],
sourceModel: 'inventory_source',
- sourceField: 'source'
+ sourceField: 'source',
+ searchOnly: true
},
has_external_source: {
label: 'Has external source?',
@@ -80,7 +85,7 @@ angular.module('InventorySummaryDefinition', [])
sourceField: 'source'
},
has_active_failures: {
- label: 'Hosts have job failures?',
+ label: 'Has failed hosts?',
searchSingleValue: true,
searchType: 'boolean',
searchValue: 'true',
@@ -110,7 +115,7 @@ angular.module('InventorySummaryDefinition', [])
dataPlacement: 'top'
},
help: {
- dataPlacement: 'left',
+ dataPlacement: 'top',
icon: "icon-question-sign",
mode: 'all',
'class': 'btn-xs btn-info btn-help',
diff --git a/awx/ui/static/less/ansible-ui.less b/awx/ui/static/less/ansible-ui.less
index 802712be49..82aade7cac 100644
--- a/awx/ui/static/less/ansible-ui.less
+++ b/awx/ui/static/less/ansible-ui.less
@@ -689,7 +689,8 @@ select.field-mini-height {
color: @red;
}
- .icon-failures-none {
+ .icon-failures-none,
+ .icon-failures-na {
color: @grey;
}
@@ -698,6 +699,7 @@ select.field-mini-height {
}
.icon-failures-none:before,
+ .icon-failures-na:before,
.icon-failures-false:before {
content: "\f111";
}
@@ -733,17 +735,14 @@ select.field-mini-height {
content: "\f06a";
}
- .icon-cloud-na {
- color: #888;
- }
-
+ .icon-cloud-na,
.icon-cloud-never {
- color: #888;
+ color: @grey;
}
.icon-cloud-updating,
.icon-cloud-successful {
- color: #5bb75b;
+ color: @green;
}
.icon-cloud-failed {
@@ -1041,7 +1040,7 @@ select.field-mini-height {
.field-badge {
font-size: 10px;
line-height: normal;
- vertical-align: middle;
+ vertical-align: baseline;
}
}
diff --git a/awx/ui/static/lib/ansible/form-generator.js b/awx/ui/static/lib/ansible/form-generator.js
index 6a1321af36..3c8e9038ba 100644
--- a/awx/ui/static/lib/ansible/form-generator.js
+++ b/awx/ui/static/lib/ansible/form-generator.js
@@ -1280,7 +1280,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
html += SearchWidget({ iterator: form.iterator, template: form, mini: true, size: 'col-md-5 col-lg-5'});
- html += "