diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js
index 93e7668c5b..2d39e42b06 100644
--- a/awx/ui/static/js/app.js
+++ b/awx/ui/static/js/app.js
@@ -70,7 +70,8 @@ angular.module('ansible', [
'LicenseFormDefinition',
'License',
'HostGroupsFormDefinition',
- 'ObjectCountWidget'
+ 'ObjectCountWidget',
+ 'JobsHelper'
])
.config(['$routeProvider', function($routeProvider) {
$routeProvider.
diff --git a/awx/ui/static/js/controllers/Hosts.js b/awx/ui/static/js/controllers/Hosts.js
index 02aea5b41b..3c354fb151 100644
--- a/awx/ui/static/js/controllers/Hosts.js
+++ b/awx/ui/static/js/controllers/Hosts.js
@@ -43,15 +43,15 @@ function InventoryHosts ($scope, $rootScope, $compile, $location, $log, $routePa
LoadSearchTree({ scope: scope, inventory_id: scope['inventory_id'] });
// Add the selected flag to the hosts set.
- if (scope.relatedHostsRemove) {
- scope.relatedHostsRemove();
- }
- scope.relatedHostsRemove = scope.$on('relatedhosts', function() {
- scope.toggleAllFlag = false;
- for (var i=0; i < scope.hosts.length; i++) {
- scope.hosts[i].selected = 0;
- }
- });
+ //if (scope.relatedHostsRemove) {
+ // scope.relatedHostsRemove();
+ //}
+ //scope.relatedHostsRemove = scope.$on('relatedhosts', function() {
+ // scope.toggleAllFlag = false;
+ // for (var i=0; i < scope.hosts.length; i++) {
+ // scope.hosts[i].selected = 0;
+ // }
+ // });
scope.filterHosts = function() {
HostsReload({ scope: scope, inventory_id: scope['inventory_id'], group_id: scope['group_id'] });
diff --git a/awx/ui/static/js/controllers/Inventories.js b/awx/ui/static/js/controllers/Inventories.js
index 5a530f3a9a..91b5450d8c 100644
--- a/awx/ui/static/js/controllers/Inventories.js
+++ b/awx/ui/static/js/controllers/Inventories.js
@@ -30,65 +30,79 @@ function InventoriesList ($scope, $rootScope, $location, $log, $routeParams, Res
scope.search(list.iterator);
LoadBreadCrumbs();
-
+
+ if (scope.projectsPostRefresh) {
+ scope.projectsPostRefresh();
+ }
+ scope.projectsPostRefresh = scope.$on('PostRefresh', function() {
+ for (var i=0; i < scope.inventories.length; i++) {
+ if (scope.inventories[i].hosts_with_active_failures > 0) {
+ scope.inventories[i].active_failures_params = "/?has_active_failures=true";
+ }
+ //if (scope.inventories[i].hosts_with_active_failures < 99) {
+ // scope.inventories[i].hosts_with_active_failures = ('00' + scope.inventories[i].hosts_with_active_failures).substr(-2);
+ //}
+ }
+ });
+
scope.addInventory = function() {
- $location.path($location.path() + '/add');
- }
+ $location.path($location.path() + '/add');
+ }
scope.editInventory = function(id) {
- $location.path($location.path() + '/' + id);
- }
+ $location.path($location.path() + '/' + id);
+ }
scope.deleteInventory = function(id, name) {
- var action = function() {
- var url = defaultUrl + id + '/';
- $('#prompt-modal').modal('hide');
- Wait('start');
- Rest.setUrl(url);
- Rest.destroy()
- .success( function(data, status, headers, config) {
- scope.search(list.iterator);
- Wait('stop');
- })
- .error( function(data, status, headers, config) {
- Wait('stop');
- ProcessErrors(scope, data, status, null,
- { hdr: 'Error!', msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status });
- });
- };
+ var action = function() {
+ var url = defaultUrl + id + '/';
+ $('#prompt-modal').modal('hide');
+ Wait('start');
+ Rest.setUrl(url);
+ Rest.destroy()
+ .success( function(data, status, headers, config) {
+ scope.search(list.iterator);
+ Wait('stop');
+ })
+ .error( function(data, status, headers, config) {
+ Wait('stop');
+ ProcessErrors(scope, data, status, null,
+ { hdr: 'Error!', msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status });
+ });
+ };
- Prompt({ hdr: 'Delete',
- body: 'Are you sure you want to delete ' + name + '?',
- action: action
- });
- }
+ Prompt({ hdr: 'Delete',
+ body: 'Are you sure you want to delete ' + name + '?',
+ action: action
+ });
+ }
- scope.lookupOrganization = function(organization_id) {
- Rest.setUrl(GetBasePath('organizations') + organization_id + '/');
- Rest.get()
- .success( function(data, status, headers, config) {
- return data.name;
- });
- }
+ scope.lookupOrganization = function(organization_id) {
+ Rest.setUrl(GetBasePath('organizations') + organization_id + '/');
+ Rest.get()
+ .success( function(data, status, headers, config) {
+ return data.name;
+ });
+ }
- // Failed jobs link. Go to the jobs tabs, find all jobs for the inventory and sort by status
- scope.viewJobs = function(id) {
- $location.url('/jobs/?inventory__int=' + id);
- }
+ // Failed jobs link. Go to the jobs tabs, find all jobs for the inventory and sort by status
+ scope.viewJobs = function(id) {
+ $location.url('/jobs/?inventory__int=' + id);
+ }
- scope.viewFailedJobs = function(id) {
- $location.url('/jobs/?inventory__int=' + id + '&status=failed');
- }
+ scope.viewFailedJobs = function(id) {
+ $location.url('/jobs/?inventory__int=' + id + '&status=failed');
+ }
- scope.editHosts = function(id) {
- $location.url('/inventories/' + id + '/hosts');
- }
+ scope.editHosts = function(id) {
+ $location.url('/inventories/' + id + '/hosts');
+ }
- scope.editGroups = function(id) {
- $location.url('/inventories/' + id + '/groups');
- }
+ scope.editGroups = function(id) {
+ $location.url('/inventories/' + id + '/groups');
+ }
}
InventoriesList.$inject = [ '$scope', '$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'InventoryList', 'GenerateList',
diff --git a/awx/ui/static/js/controllers/JobEvents.js b/awx/ui/static/js/controllers/JobEvents.js
index c7f962ccab..4d8156c736 100644
--- a/awx/ui/static/js/controllers/JobEvents.js
+++ b/awx/ui/static/js/controllers/JobEvents.js
@@ -131,12 +131,39 @@ function JobEventsList ($scope, $rootScope, $location, $log, $routeParams, Rest,
set[i]['spaces'] = set[i].event_level * 24;
if (scope.jobevents[i].failed) {
scope.jobevents[i].status = 'error';
+ if (i == set.length - 1) {
+ scope.jobevents[i].statusBadgeToolTip = "A failure occurred durring one or more playbook tasks.";
+ }
+ else if (set[i].event_level < 3) {
+ scope.jobevents[i].statusBadgeToolTip = "A failure occurred within the children of this event.";
+ }
+ else {
+ scope.jobevents[i].statusBadgeToolTip = "A failure occurred. Click to view details";
+ }
}
else if (scope.jobevents[i].changed) {
scope.jobevents[i].status = 'changed';
+ if (i == set.length - 1) {
+ scope.jobevents[i].statusBadgeToolTip = "A change was completed durring one or more playbook tasks.";
+ }
+ else if (set[i].event_level < 3) {
+ scope.jobevents[i].statusBadgeToolTip = "A change was completed by one or more children of this event.";
+ }
+ else {
+ scope.jobevents[i].statusBadgeToolTip = "A change was completed. Click to view details";
+ }
}
else {
scope.jobevents[i].status = 'success';
+ if (i == set.length - 1) {
+ scope.jobevents[i].statusBadgeToolTip = "All playbook tasks completed successfully.";
+ }
+ else if (set[i].event_level < 3) {
+ scope.jobevents[i].statusBadgeToolTip = "All the children of this event completed successfully.";
+ }
+ else {
+ scope.jobevents[i].statusBadgeToolTip = "No errors occurred. Click to view details";
+ }
}
cDate = new Date(set[i].created);
set[i].created = FormatDate(cDate);
@@ -147,6 +174,8 @@ function JobEventsList ($scope, $rootScope, $location, $log, $routeParams, Rest,
Rest.get()
.success( function(data, status, headers, config) {
scope.job_status = data.status;
+ scope.job_name = data.summary_fields.job_template.name;
+ LoadBreadCrumbs({ path: '/jobs/' + scope.job_id, title: scope.job_name });
if (!(data.status == 'pending' || data.status == 'waiting' || data.status == 'running')) {
if ($rootScope.timer) {
clearInterval($rootScope.timer);
@@ -181,8 +210,6 @@ function JobEventsList ($scope, $rootScope, $location, $log, $routeParams, Rest,
children: children
});
}
-
- LoadBreadCrumbs();
scope.viewJobEvent = function(id) {
EventView({ event_id: id });
diff --git a/awx/ui/static/js/controllers/JobHosts.js b/awx/ui/static/js/controllers/JobHosts.js
index e0eef59d66..07eac5f8ee 100644
--- a/awx/ui/static/js/controllers/JobHosts.js
+++ b/awx/ui/static/js/controllers/JobHosts.js
@@ -12,13 +12,14 @@
function JobHostSummaryList ($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, JobHostList,
GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller,
- ClearScope, ProcessErrors, GetBasePath, Refresh)
+ ClearScope, ProcessErrors, GetBasePath, Refresh, JobStatusToolTip)
{
ClearScope('htmlTemplate');
var list = JobHostList;
var base = $location.path().replace(/^\//,'').split('/')[0];
var defaultUrl = GetBasePath(base) + $routeParams.id + '/job_host_summaries/';
-
+ var inventory_id;
+
// When viewing all summaries for a particular host, show job ID, otherwise row ID.
if (base == 'hosts') {
list.index = false;
@@ -42,46 +43,71 @@ function JobHostSummaryList ($scope, $rootScope, $location, $log, $routeParams,
scope.host_id = null;
}
+ if (scope.RemoveSetHostLink) {
+ scope.RemoveSetHostLink();
+ }
+ scope.RemoveSetHostLink = scope.$on('setHostLink', function(e, inventory_id) {
+ for (var i=0; i < scope.jobhosts.length; i++) {
+ scope.jobhosts[i].hostLinkTo = '/#/inventories/' + inventory_id + '/hosts/?name=' +
+ escape(scope.jobhosts[i].summary_fields.host.name);
+ }
+ });
+
// After a refresh, populate any needed summary field values on each row
if (scope.PostRefreshRemove) {
scope.PostRefreshRemove();
}
- scope.PostRefershRemove = scope.$on('PostRefresh', function() {
+ scope.PostRefreshRemove = scope.$on('PostRefresh', function() {
+
+ // Set status, tooltips, badget icons, etc.
for( var i=0; i < scope.jobhosts.length; i++) {
scope.jobhosts[i].host_name = scope.jobhosts[i].summary_fields.host.name;
- scope.jobhosts[i].status = (scope.jobhosts[i].failed) ? 'error' : 'success';
+ scope.jobhosts[i].status = (scope.jobhosts[i].failed) ? 'failed' : 'success';
+ scope.jobhosts[i].statusBadgeToolTip = JobStatusToolTip(scope.jobhosts[i].status) +
+ " Click to view details.";
+ scope.jobhosts[i].statusLinkTo = '/#/jobs/' + scope.jobhosts[i].job + '/job_events/?host=' +
+ escape(scope.jobhosts[i].summary_fields.host.name);
}
+
if (scope.job_id !== null && scope.job_id !== undefined && scope.job_id !== '') {
// need job_status so we can show/hide refresh button
Rest.setUrl(GetBasePath('jobs') + scope.job_id);
Rest.get()
.success( function(data, status, headers, config) {
scope.job_status = data.status;
+ scope.$emit('setHostLink', data.inventory);
if (!(data.status == 'pending' || data.status == 'waiting' || data.status == 'running')) {
if ($rootScope.timer) {
clearInterval($rootScope.timer);
}
}
})
- .error( function(data, status, headers, config) {
+ .error( function(data, status, headers, config) {
ProcessErrors(scope, data, status, null,
{ hdr: 'Error!', msg: 'Failed to get job status for job: ' + scope.job_id + '. GET status: ' + status });
});
}
+ if (base == 'hosts' && $routeParams['host_name']) {
+ // Make the host name appear in breadcrumbs
+ LoadBreadCrumbs({ path: '/hosts/' + scope['host_id'], title: $routeParams['host_name'] });
+ }
+ else {
+ LoadBreadCrumbs();
+ }
});
SearchInit({ scope: scope, set: 'jobhosts', list: list, url: defaultUrl });
PaginateInit({ scope: scope, list: list, url: defaultUrl });
// Called from Inventories tab, host failed events link:
- if ($routeParams.host) {
+ if ($routeParams['host_name']) {
scope[list.iterator + 'SearchField'] = 'host';
- scope[list.iterator + 'SearchValue'] = $routeParams.host;
+ scope[list.iterator + 'SearchValue'] = $routeParams['host_name'];
scope[list.iterator + 'SearchFieldLabel'] = list.fields['host'].label;
}
scope.search(list.iterator);
- LoadBreadCrumbs();
+
scope.showEvents = function(host_name, last_job) {
// When click on !Failed Events link, redirect to latest job/job_events for the host
@@ -113,5 +139,5 @@ function JobHostSummaryList ($scope, $rootScope, $location, $log, $routeParams,
JobHostSummaryList.$inject = [ '$scope', '$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'JobHostList',
'GenerateList', 'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope',
- 'ProcessErrors', 'GetBasePath', 'Refresh'
+ 'ProcessErrors', 'GetBasePath', 'Refresh', 'JobStatusToolTip'
];
diff --git a/awx/ui/static/js/controllers/JobTemplates.js b/awx/ui/static/js/controllers/JobTemplates.js
index 5dfe81e328..9ba63834b4 100644
--- a/awx/ui/static/js/controllers/JobTemplates.js
+++ b/awx/ui/static/js/controllers/JobTemplates.js
@@ -27,6 +27,14 @@ function JobTemplatesList ($scope, $rootScope, $location, $log, $routeParams, Re
SearchInit({ scope: scope, set: 'job_templates', list: list, url: defaultUrl });
PaginateInit({ scope: scope, list: list, url: defaultUrl });
+
+ // Called from Inventories tab, host failed events link:
+ if ($routeParams['name']) {
+ scope[list.iterator + 'SearchField'] = 'name';
+ scope[list.iterator + 'SearchValue'] = $routeParams['name'];
+ scope[list.iterator + 'SearchFieldLabel'] = list.fields['name'].label;
+ }
+
scope.search(list.iterator);
LoadBreadCrumbs();
diff --git a/awx/ui/static/js/controllers/Jobs.js b/awx/ui/static/js/controllers/Jobs.js
index 08a6639462..353768a980 100644
--- a/awx/ui/static/js/controllers/Jobs.js
+++ b/awx/ui/static/js/controllers/Jobs.js
@@ -12,7 +12,8 @@
function JobsListCtrl ($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, JobList,
GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller,
- ClearScope, ProcessErrors, GetBasePath, LookUpInit, SubmitJob, FormatDate, Refresh)
+ ClearScope, ProcessErrors, GetBasePath, LookUpInit, SubmitJob, FormatDate, Refresh,
+ JobStatusToolTip)
{
ClearScope('htmlTemplate');
var list = JobList;
@@ -41,6 +42,13 @@ function JobsListCtrl ($scope, $rootScope, $location, $log, $routeParams, Rest,
scope[list.name][i].created = FormatDate(cDate);
}
}
+
+ for (var i=0; i < scope.jobs.length; i++) {
+ scope.jobs[i].statusBadgeToolTip = JobStatusToolTip(scope.jobs[i].status) +
+ " Click to view status details.";
+ scope.jobs[i].statusLinkTo = '/#/jobs/' + scope.jobs[i].id;
+ }
+
});
@@ -157,14 +165,14 @@ function JobsListCtrl ($scope, $rootScope, $location, $log, $routeParams, Rest,
JobsListCtrl.$inject = [ '$scope', '$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'JobList',
'GenerateList', 'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope',
- 'ProcessErrors','GetBasePath', 'LookUpInit', 'SubmitJob', 'FormatDate', 'Refresh'
+ 'ProcessErrors','GetBasePath', 'LookUpInit', 'SubmitJob', 'FormatDate', 'Refresh', 'JobStatusToolTip'
];
function JobsEdit ($scope, $rootScope, $compile, $location, $log, $routeParams, JobForm,
GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit,
RelatedPaginateInit, ReturnToCaller, ClearScope, InventoryList, CredentialList,
- ProjectList, LookUpInit, PromptPasswords, GetBasePath, md5Setup, FormatDate)
+ ProjectList, LookUpInit, PromptPasswords, GetBasePath, md5Setup, FormatDate, JobStatusToolTip)
{
ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior
//scope.
@@ -334,7 +342,9 @@ function JobsEdit ($scope, $rootScope, $compile, $location, $log, $routeParams,
}
}
}
-
+
+ scope.statusToolTip = JobStatusToolTip(data.status);
+
$('form[name="jobs_form"] input[type="text"], form[name="jobs_form"] jobs_form textarea').attr('readonly','readonly');
$('form[name="jobs_form"] select').prop('disabled', 'disabled');
$('form[name="jobs_form"] .lookup-btn').prop('disabled', 'disabled');
@@ -503,5 +513,6 @@ function JobsEdit ($scope, $rootScope, $compile, $location, $log, $routeParams,
JobsEdit.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'JobForm',
'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'RelatedSearchInit',
'RelatedPaginateInit', 'ReturnToCaller', 'ClearScope', 'InventoryList', 'CredentialList',
- 'ProjectList', 'LookUpInit', 'PromptPasswords', 'GetBasePath', 'md5Setup', 'FormatDate'
+ 'ProjectList', 'LookUpInit', 'PromptPasswords', 'GetBasePath', 'md5Setup', 'FormatDate',
+ 'JobStatusToolTip'
];
diff --git a/awx/ui/static/js/forms/InventoryHosts.js b/awx/ui/static/js/forms/InventoryHosts.js
index b51e820e45..4191207e89 100644
--- a/awx/ui/static/js/forms/InventoryHosts.js
+++ b/awx/ui/static/js/forms/InventoryHosts.js
@@ -18,13 +18,29 @@ angular.module('InventoryHostsFormDefinition', [])
fields: {
name: {
key: true,
- label: 'Host Name',
- ngClick: "editHost(\{\{ host.id \}\}, '\{\{ host.name \}\}')",
+ label: 'Name',
+ ngClick: "editHost(\{\{ host.id \}\}, '\{\{ host.name \}\}')"
+ //columnClass: 'col-lg-3'
+ },
+ active_failures: {
+ label: 'Current Job Status?',
+ ngHref: "\{\{ host.activeFailuresLink \}\}",
+ awToolTip: "\{\{ host.badgeToolTip \}\}",
+ dataPlacement: 'bottom',
+ badgeNgHref: '\{\{ host.activeFailuresLink \}\}',
badgeIcon: "\{\{ 'icon-failures-' + host.has_active_failures \}\}",
- badgeToolTip: 'Indicates if host has active failures',
badgePlacement: 'left',
+ badgeToolTip: "\{\{ host.badgeToolTip \}\}",
badgeTipPlacement: 'bottom',
- columnClass: 'col-lg-3'
+ searchable: false,
+ nosort: true
+ },
+ has_active_failures: {
+ label: 'Current job failed?',
+ searchSingleValue: true,
+ searchType: 'boolean',
+ searchValue: 'true',
+ searchOnly: true
},
groups: {
label: 'Groups',
@@ -32,7 +48,7 @@ angular.module('InventoryHostsFormDefinition', [])
sourceModel: 'groups',
sourceField: 'name',
nosort: true
- }
+ },
},
actions: {
diff --git a/awx/ui/static/js/forms/Jobs.js b/awx/ui/static/js/forms/Jobs.js
index 94bcfcb8a8..bcae47986b 100644
--- a/awx/ui/static/js/forms/Jobs.js
+++ b/awx/ui/static/js/forms/Jobs.js
@@ -270,7 +270,7 @@ angular.module('JobFormDefinition', [])
status: {
label: 'Job Status',
type: 'custom',
- control: '
\{\{ status \}\}
',
+ control: ' \{\{ status \}\}
',
readonly: true
},
created: {
diff --git a/awx/ui/static/js/helpers/Events.js b/awx/ui/static/js/helpers/Events.js
index 3e70433653..73e326341b 100644
--- a/awx/ui/static/js/helpers/Events.js
+++ b/awx/ui/static/js/helpers/Events.js
@@ -27,7 +27,7 @@ angular.module('EventsHelper', ['RestServices', 'Utilities', 'JobEventDataDefini
fields: {
status: {
labelClass: 'job-\{\{ status \}\}',
- icon: 'icon-circle',
+ icon: 'icon-job-\{\{ status \}\}',
type: 'custom',
section: 'Event',
control: '\{\{ status \}\}
'
diff --git a/awx/ui/static/js/helpers/Groups.js b/awx/ui/static/js/helpers/Groups.js
index fe9778c143..ef52097e5b 100644
--- a/awx/ui/static/js/helpers/Groups.js
+++ b/awx/ui/static/js/helpers/Groups.js
@@ -147,26 +147,35 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
for (var i=0; i < scope.groups.length; i++) {
var last_update = (scope.groups[i].last_updated == null) ? '' : FormatDate(new Date(scope.groups[i].last_updated));
var source = 'Manual';
- var stat;
- var stat_class;
+ var stat, stat_class, status_tip;
+ stat = scope.groups[i].status;
+ stat_class = stat;
switch (scope.groups[i].status) {
case 'never updated':
stat = 'never';
stat_class = 'never';
+ status_tip = 'Inventory update has not been performed. Click Update button to start it now.';
break;
case 'none':
stat = 'n/a';
stat_class = 'na';
+ status_tip = 'Not configured for inventory update.';
+ break;
+ case 'failed':
+ status_tip = 'Inventory update completed with errors. Click to view process output.';
+ break;
+ case 'successful':
+ status_tip = 'Inventory update completed with no errors. Click to view process output.';
+ break;
+ case 'updating':
+ status_tip = 'Inventory update process running now.';
break;
- default:
- stat = scope.groups[i].status;
- stat_class = stat;
}
switch (scope.groups[i].source) {
case 'file':
- source = 'File';
+ source = 'Local Script';
break;
case 'ec2':
source = 'Amazon EC2';
@@ -175,10 +184,21 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
source = 'Rackspace';
break;
}
+
+ if (scope.groups[i].summary_fields.group.hosts_with_active_failures > 0) {
+ scope.groups[i].active_failures_params = "/?has_active_failures=true";
+ }
+ else {
+ scope.groups[i].active_failures_params = '';
+ }
+
+ scope.groups[i].hosts_with_active_failures = scope.groups[i].summary_fields.group.hosts_with_active_failures;
+ scope.groups[i].has_active_failures = scope.groups[i].summary_fields.group.has_active_failures;
scope.groups[i].status = stat;
scope.groups[i].source = source;
scope.groups[i].last_updated = last_update;
- scope.groups[i].status_class = stat_class;
+ scope.groups[i].status_badge_class = stat_class;
+ scope.groups[i].status_badge_tooltip = status_tip;
}
});
@@ -688,6 +708,36 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
});
}
}
+ // Start the update process
+ scope.updateGroup = function() {
+ if (scope[source] == "" || scope.groups[i].source == null) {
+ Alert('Missing Configuration', 'The group is not configured for updates. You must provide Source settings before running the update ' +
+ 'process.');
+ }
+ else if (scope[status] == 'updating') {
+ Alert('Update in Progress', 'The inventory update process is currently running for this group ' +
+ scope.groups[i].summary_fields.group.name + ' . Under the Groupmonitor the status.', 'alert-info');
+ }
+ else {
+ if (scope['source'] == 'Amazon EC2') {
+ scope['sourceUsernameLabel'] = 'Access Key ID';
+ scope['sourcePasswordLabel'] = 'Secret Access Key';
+ scope['sourcePasswordConfirmLabel'] = 'Confirm Secret Access Key';
+ }
+ else {
+ scope['sourceUsernameLabel'] = 'Username';
+ scope['sourcePasswordLabel'] = 'Password';
+ scope['sourcePasswordConfirmLabel'] = 'Confirm Password';
+ }
+ InventoryUpdate({
+ scope: scope,
+ group_id: id,
+ url: scope.groups[i].related.update,
+ group_name: scope.groups[i].summary_fields.group.name,
+ group_source: scope.groups[i].source
+ });
+ }
+ }
// Cancel
scope.formReset = function() {
@@ -697,6 +747,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
}
scope.parseType = 'yaml';
}
+
}
}])
diff --git a/awx/ui/static/js/helpers/Hosts.js b/awx/ui/static/js/helpers/Hosts.js
index b956bc2360..e54ab64688 100644
--- a/awx/ui/static/js/helpers/Hosts.js
+++ b/awx/ui/static/js/helpers/Hosts.js
@@ -408,8 +408,8 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
}])
- .factory('HostsReload', ['SearchInit', 'PaginateInit', 'InventoryHostsForm', 'GetBasePath', 'Wait',
- function(SearchInit, PaginateInit, InventoryHostsForm, GetBasePath, Wait) {
+ .factory('HostsReload', ['$routeParams', 'SearchInit', 'PaginateInit', 'InventoryHostsForm', 'GetBasePath', 'Wait',
+ function($routeParams, SearchInit, PaginateInit, InventoryHostsForm, GetBasePath, Wait) {
return function(params) {
// Rerfresh the Hosts view on right side of page
@@ -424,15 +424,13 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
var url = (group_id !== null && group_id !== undefined) ? GetBasePath('groups') + group_id + '/all_hosts/' :
GetBasePath('inventory') + params.inventory_id + '/hosts/';
- if (scope.hostFailureFilter) {
- url += '?has_active_failures=true';
- }
-
// Set the groups value in each element of hosts array
if (scope.removePostRefresh) {
scope.removePostRefresh();
}
scope.removePostRefresh = scope.$on('PostRefresh', function() {
+
+ // Add a list of groups to each host
var groups, descr, found, list;
for (var i=0; i < scope.hosts.length; i++) {
groups = scope.hosts[i].summary_fields.groups;
@@ -444,14 +442,54 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
}
scope.hosts[i].groups = scope.hosts[i].groups.replace(/\, $/,'');
}
+
+ // Add the value displayed in Job Status column
+ for (var i=0; i < scope.hosts.length; i++) {
+ scope.hosts[i].activeFailuresLink = '/#/hosts/' + scope.hosts[i].id + '/job_host_summaries/?inventory=' + scope['inventory_id'] +
+ '&host_name=' + escape(scope.hosts[i].name);
+ if (scope.hosts[i].has_active_failures == true) {
+ scope.hosts[i].badgeToolTip = 'Most recent job failed. Click to view jobs.';
+ scope.hosts[i].active_failures = 'failed';
+ }
+ else if (scope.hosts[i].has_active_failures == false && scope.hosts[i].last_job == null) {
+ scope.hosts[i].has_active_failures = 'none';
+ scope.hosts[i].badgeToolTip = "No job data available.";
+ scope.hosts[i].active_failures = 'n/a';
+ }
+ else if (scope.hosts[i].has_active_failures == false && scope.hosts[i].last_job !== null) {
+ scope.hosts[i].badgeToolTip = "Most recent job successful. Click to view jobs.";
+ scope.hosts[i].active_failures = 'success';
+ }
+ }
+
if (postAction) {
postAction();
}
- });
+ });
+
SearchInit({ scope: scope, set: 'hosts', list: InventoryHostsForm, url: url });
PaginateInit({ scope: scope, list: InventoryHostsForm, url: url });
- scope.search('host');
+
+ if ($routeParams['has_active_failures']) {
+ //scope.resetSearch(InventoryHostsForm.iterator);
+ scope[InventoryHostsForm.iterator + 'InputDisable'] = true;
+ scope[InventoryHostsForm.iterator + 'SearchValue'] = $routeParams['has_active_failures'];
+ scope[InventoryHostsForm.iterator + 'SearchField'] = 'has_active_failures';
+ scope[InventoryHostsForm.iterator + 'SearchFieldLabel'] = InventoryHostsForm.fields['has_active_failures'].label;
+ scope[InventoryHostsForm.iterator + 'SearchSelectValue'] = ($routeParams['has_active_failures'] == 'true') ? { value: 1 } : { value: 0 };
+ }
+
+ if ($routeParams['name']) {
+ console.log('here!');
+ scope[InventoryHostsForm.iterator + 'InputDisable'] = false;
+ scope[InventoryHostsForm.iterator + 'SearchValue'] = $routeParams['name'];
+ scope[InventoryHostsForm.iterator + 'SearchField'] = 'name';
+ scope[InventoryHostsForm.iterator + 'SearchFieldLabel'] = InventoryHostsForm.fields['name'].label;
+ scope[InventoryHostsForm.iterator + 'SearchSelectValue'] = null;
+ }
+
+ scope.search(InventoryHostsForm.iterator);
if (!params.scope.$$phase) {
params.scope.$digest();
@@ -467,6 +505,7 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
var scope = params.scope;
var inventory_id = params.inventory_id;
var html = '';
+ var toolTip = 'Hosts have failed jobs?';
function buildHTML(tree_data) {
var sorted = SortNodes(tree_data);
@@ -480,7 +519,7 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
"data-group-id=\"" + sorted[i].id + "\" " +
"> " +
" " +
+ "aw-tool-tip=\"" + toolTip + "\" data-placement=\"bottom\"> " +
"" + sorted[i].name + " ";
if (sorted[i].children.length > 0) {
buildHTML(sorted[i].children);
@@ -651,7 +690,7 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
"data-name=\"" + data.name + "\" " +
"> " +
" " +
+ "aw-tool-tip=\"" + toolTip + "\" data-placement=\"bottom\"> " +
"" + data.name + " ";
scope.$emit('buildAllGroups', data.name, data.related.tree, data.related.groups);
scope.$emit('refreshHost', null, 'All Hosts');
diff --git a/awx/ui/static/js/helpers/Jobs.js b/awx/ui/static/js/helpers/Jobs.js
new file mode 100644
index 0000000000..ada78d0bd3
--- /dev/null
+++ b/awx/ui/static/js/helpers/Jobs.js
@@ -0,0 +1,40 @@
+/*********************************************
+ * Copyright (c) 2013 AnsibleWorks, Inc.
+ *
+ * JobsHelper
+ *
+ * Routines shared by job related controllers
+ *
+ */
+
+angular.module('JobsHelper', [ ])
+ .factory('JobStatusToolTip', [ function() {
+ return function(status) {
+ var toolTip;
+ switch (status) {
+ case 'successful':
+ case 'success':
+ toolTip = 'There were no failed tasks.';
+ break;
+ case 'failed':
+ toolTip = 'Some tasks encountered errors.';
+ break;
+ case 'canceled':
+ toolTip = 'Stopped by user request.';
+ break;
+ case 'new':
+ toolTip = 'In queue, waiting on task manager.';
+ break;
+ case 'waiting':
+ toolTip = 'SCM Update or Inventory Update is executing.';
+ break;
+ case 'pending':
+ toolTip = 'Not in queue, waiting on task manager.';
+ break;
+ case 'running':
+ toolTip = 'Playbook tasks executing.';
+ break;
+ }
+ return toolTip;
+ }
+ }]);
\ No newline at end of file
diff --git a/awx/ui/static/js/helpers/inventory.js b/awx/ui/static/js/helpers/inventory.js
index 04ba32fcab..b751807168 100644
--- a/awx/ui/static/js/helpers/inventory.js
+++ b/awx/ui/static/js/helpers/inventory.js
@@ -166,7 +166,6 @@ angular.module('InventoryHelper', [ 'RestServices', 'Utilities', 'OrganizationLi
$(tree_id).bind('move_node.jstree', function(e, data) {
// When user drags-n-drops a node, update the API
-
Wait('start');
var node, target, url, parent, inv_id, variables;
@@ -273,12 +272,11 @@ angular.module('InventoryHelper', [ 'RestServices', 'Utilities', 'OrganizationLi
}
});
- // When user clicks on a group
- $(tree_id).bind("select_node.jstree", function(e, data){
- scope.$emit('NodeSelect', data.inst.get_json()[0]);
- });
+ // When user clicks on a group
+ $(tree_id).bind("select_node.jstree", function(e, data){
+ scope.$emit('NodeSelect', data.inst.get_json()[0]);
+ });
-
Wait('start');
LoadTreeData(params);
diff --git a/awx/ui/static/js/helpers/search.js b/awx/ui/static/js/helpers/search.js
index f599c1712e..2cbee9d943 100644
--- a/awx/ui/static/js/helpers/search.js
+++ b/awx/ui/static/js/helpers/search.js
@@ -83,19 +83,57 @@ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper'])
scope[iterator + 'SelectShow'] = false;
scope[iterator + 'HideSearchType'] = false;
scope[iterator + 'InputHide'] = false;
+ scope[iterator + 'InputDisable'] = false;
+ scope[iterator + 'SearchType'] = 'icontains';
if (list.fields[fld].searchType && list.fields[fld].searchType == 'gtzero') {
scope[iterator + "InputHide"] = true;
}
- if (list.fields[fld].searchType && (list.fields[fld].searchType == 'boolean'
+ else if (list.fields[fld].searchSingleValue){
+ // Query a specific attribute for one specific value
+ // searchSingleValue: true
+ // searchType: 'boolean|int|etc.'
+ // searchValue: < value to match for boolean use 'true'|'false' >
+ scope[iterator + "SearchType"] = list.fields[fld].searchType;
+ scope[iterator + 'InputDisable'] = true;
+ scope[iterator + "SearchValue"] = list.fields[fld].searchValue;
+ // For boolean type, SearchValue must be an object
+ if (list.fields[fld].searchType == 'boolean' && list.fields[fld].searchValue == 'true') {
+ scope[iterator + "SearchSelectValue"] = { value: 1 };
+ }
+ else if (list.fields[fld].searchType == 'boolean' && list.fields[fld].searchValue == 'false') {
+ scope[iterator + "SearchSelectValue"] = { value: 0 };
+ }
+ else if (list.fields[fld].searchType == 'boolean') {
+ scope[iterator + "SearchSelectValue"] = { value: list.fields[fld].searchValue };
+ }
+ }
+ else if (list.fields[fld].searchType && (list.fields[fld].searchType == 'boolean'
|| list.fields[fld].searchType == 'select')) {
scope[iterator + 'SelectShow'] = true;
scope[iterator + 'SearchSelectOpts'] = list.fields[fld].searchOptions;
}
- if (list.fields[fld].searchType && list.fields[fld].searchType == 'int') {
+ else if (list.fields[fld].searchType && list.fields[fld].searchType == 'int') {
scope[iterator + 'HideSearchType'] = true;
+ }
+ scope.search(iterator);
+ }
+
+ scope.resetSearch = function(iterator) {
+ // Respdond to click of reset button
+ scope[iterator + "SearchValue"] = '';
+ scope[iterator + "SearchSelectValue"] = '';
+ scope[iterator + 'SelectShow'] = false;
+ scope[iterator + 'HideSearchType'] = false;
+ scope[iterator + 'InputHide'] = false;
+ scope[iterator + 'InputDisable'] = false;
+ for (fld in list.fields) {
+ if (list.fields[fld].searchable == undefined || list.fields[fld].searchable == true) {
+ scope[iterator + 'SearchFieldLabel'] = list.fields[fld].label;
+ scope[iterator + 'SearchField'] = fld;
+ break;
+ }
}
-
scope.search(iterator);
}
diff --git a/awx/ui/static/js/lists/Inventories.js b/awx/ui/static/js/lists/Inventories.js
index 0f704c19ad..b217d1df8b 100644
--- a/awx/ui/static/js/lists/Inventories.js
+++ b/awx/ui/static/js/lists/Inventories.js
@@ -21,21 +21,37 @@ angular.module('InventoriesListDefinition', [])
fields: {
name: {
key: true,
- label: 'Name',
- badgeIcon: "\{\{ 'icon-failures-' + inventory.has_active_failures \}\}",
- badgePlacement: 'left',
- badgeToolTip: 'Indicates if inventory contains hosts with active failures',
- badgeTipPlacement: 'bottom'
+ label: 'Name'
},
description: {
- label: 'Description'
+ label: 'Description',
+ link: 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 }}",
+ //badgeIcon: "\{\{ 'icon-failures-' + inventory.has_active_failures \}\}",
+ //badgePlacement: 'left',
+ awToolTip: '# of hosts with failed jobs. Click to view hosts.',
+ dataPlacement: 'bottom',
+ searchable: false
},
organization: {
label: 'Organization',
ngBind: 'inventory.summary_fields.organization.name',
+ linkTo: '/organizations/{{ inventory.organization }}',
sourceModel: 'organization',
sourceField: 'name',
excludeModal: true
+ },
+ has_active_failures: {
+ label: 'Hosts with failed jobs?',
+ searchSingleValue: true,
+ searchType: 'boolean',
+ searchValue: 'true',
+ searchOnly: true
}
},
diff --git a/awx/ui/static/js/lists/InventorySummary.js b/awx/ui/static/js/lists/InventorySummary.js
index 782d74237a..4891151d8a 100644
--- a/awx/ui/static/js/lists/InventorySummary.js
+++ b/awx/ui/static/js/lists/InventorySummary.js
@@ -25,27 +25,35 @@ angular.module('InventorySummaryDefinition', [])
noLink: true,
ngBind: "group.summary_fields.group.name",
sourceModel: 'group',
- sourceField: 'name',
- badges: [
- { //Active Failures
- icon: "\{\{ 'icon-failures-' + group.summary_fields.group.has_active_failures \}\}",
- toolTip: 'Indicates if inventory contains hosts with active failures',
- toolTipPlacement: 'bottom'
- },
- { //Cloud Status
- icon: "\{\{ 'icon-cloud-' + group.status_class \}\}",
- toolTip: 'Indicates status of inventory update process',
- toolTipPlacement: 'bottom'
- }]
+ sourceField: 'name'
},
- failures: {
- label: 'Active Failures',
- ngBind: "group.summary_fields.group.hosts_with_active_failures",
- sourceModel: 'group',
- sourceField: 'hosts_with_active_failures',
- searchField: 'group__has_active_failures',
- searchType: 'boolean',
- searchOptions: [{ name: "yes", value: 1 }, { name: "no", value: 0 }]
+ 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: 'bottom',
+ searchable: false,
+ nosort: true
+ },
+ status: {
+ label: 'Update Status',
+ searchType: 'select',
+ badgeIcon: 'icon-cloud',
+ badgeToolTip: "\{\{ group.status_badge_tooltip \}\}",
+ badgePlacement: 'left',
+ badgeClass: "\{\{ 'icon-cloud-' + group.status_badge_class \}\}",
+ searchOptions: [
+ { name: "failed", value: "failed" },
+ { name: "never", value: "never updated" },
+ { name: "n/a", value: "none" },
+ { name: "successful", value: "successful" },
+ { name: "updating", value: "updating" }]
+ },
+ last_updated: {
+ label: 'Last Updated',
+ searchable: false
},
source: {
label: 'Source',
@@ -56,25 +64,20 @@ angular.module('InventorySummaryDefinition', [])
{ name: "Manual", value: "" },
{ name: "Rackspace", value: "rackspace" }]
},
- last_updated: {
- label: 'Last Updated',
- searchable: false
- },
- status: {
- label: 'Update Status',
- searchType: 'select',
- searchOptions: [
- { name: "failed", value: "failed" },
- { name: "never", value: "never updated" },
- { name: "n/a", value: "none" },
- { name: "successful", value: "successful" },
- { name: "updating", value: "updating" }]
+ has_active_failures: {
+ label: 'Hosts have job failures?',
+ searchSingleValue: true,
+ searchType: 'boolean',
+ searchValue: 'true',
+ searchOnly: true,
+ sourceModel: 'group',
+ sourceField: 'has_active_failures'
}
},
actions: {
refresh: {
- awRefresh: true,
+ awRefresh: false,
mode: 'all'
},
help: {
diff --git a/awx/ui/static/js/lists/JobEvents.js b/awx/ui/static/js/lists/JobEvents.js
index 63818dc798..b107bc970f 100644
--- a/awx/ui/static/js/lists/JobEvents.js
+++ b/awx/ui/static/js/lists/JobEvents.js
@@ -48,14 +48,20 @@ angular.module('JobEventsListDefinition', [])
},
status: {
label: 'Status',
- icon: 'icon-circle',
showValue: true,
- "class": 'job-\{\{ jobevent.status \}\}',
searchField: 'failed',
searchType: 'boolean',
searchOptions: [{ name: "success", value: 0 }, { name: "error", value: 1 }],
nosort: true,
- searchable: false
+ searchable: false,
+ ngClick: "viewJobEvent(\{\{ jobevent.id \}\})",
+ awToolTip: "\{\{ jobevent.statusBadgeToolTip \}\}",
+ dataPlacement: 'top',
+ badgeIcon: 'icon-job-\{\{ jobevent.status \}\}',
+ badgePlacement: 'left',
+ badgeToolTip: "\{\{ jobevent.statusBadgeToolTip \}\}",
+ badgeTipPlacement: 'top',
+ badgeNgClick: "viewJobEvent(\{\{ jobevent.id \}\})"
},
event_display: {
label: 'Event',
diff --git a/awx/ui/static/js/lists/JobHosts.js b/awx/ui/static/js/lists/JobHosts.js
index 67cc2eb40b..826cf93bd1 100644
--- a/awx/ui/static/js/lists/JobHosts.js
+++ b/awx/ui/static/js/lists/JobHosts.js
@@ -38,7 +38,7 @@ angular.module('JobHostDefinition', [])
},
fields: {
- id: {
+ job: {
label: 'Job ID',
ngClick: "showJob(\{\{ jobhost.job \}\})",
columnShow: 'host_id !== null',
@@ -51,12 +51,18 @@ angular.module('JobHostDefinition', [])
sourceModel: 'host',
sourceField: 'name',
ngBind: 'jobhost.host_name',
- ngClick:"showEvents('\{\{ jobhost.summary_fields.host.name \}\}','\{\{ jobhost.related.job \}\}')"
+ ngHref: "\{\{ jobhost.hostLinkTo \}\}"
},
status: {
label: 'Status',
- icon: 'icon-circle',
- "class": 'job-\{\{ jobhost.status \}\}',
+ badgeNgHref: "\{\{ jobhost.statusLinkTo \}\}",
+ badgeIcon: 'icon-job-\{\{ jobhost.status \}\}',
+ badgePlacement: 'left',
+ badgeToolTip: "\{\{ jobhost.statusBadgeToolTip \}\}",
+ badgeTipPlacement: 'top',
+ ngHref: "\{\{ jobhost.statusLinkTo \}\}",
+ awToolTip: "\{\{ jobhost.statusBadgeToolTip \}\}",
+ dataPlacement: 'top',
searchField: 'failed',
searchType: 'boolean',
searchOptions: [{ name: "success", value: 0 }, { name: "error", value: 1 }]
diff --git a/awx/ui/static/js/lists/Jobs.js b/awx/ui/static/js/lists/Jobs.js
index 63356e3c4d..1c3add4a01 100644
--- a/awx/ui/static/js/lists/Jobs.js
+++ b/awx/ui/static/js/lists/Jobs.js
@@ -37,15 +37,15 @@ angular.module('JobsListDefinition', [])
job_template: {
label: 'Job Template',
ngBind: 'job.summary_fields.job_template.name',
- link: true,
+ ngHref: "\{\{ '/#/job_templates/?name=' + job.summary_fields.job_template.name \}\}",
sourceModel: 'job_template',
sourceField: 'name'
},
status: {
label: 'Status',
- icon: 'icon-circle',
"class": 'job-\{\{ job.status \}\}',
searchType: 'select',
+ linkTo: "\{\{ job.statusLinkTo \}\}",
searchOptions: [
{ name: "new", value: "new" },
{ name: "waiting", value: "waiting" },
@@ -54,7 +54,14 @@ angular.module('JobsListDefinition', [])
{ name: "successful", value: "successful" },
{ name: "error", value: "error" },
{ name: "failed", value: "failed" },
- { name: "canceled", value: "canceled" } ]
+ { name: "canceled", value: "canceled" } ],
+ badgeIcon: 'icon-job-\{\{ job.status \}\}',
+ badgePlacement: 'left',
+ badgeToolTip: "\{\{ job.statusBadgeToolTip \}\}",
+ badgeTipPlacement: 'top',
+ badgeNgHref: "\{\{ job.statusLinkTo \}\}",
+ awToolTip: "\{\{ job.statusBadgeToolTip \}\}",
+ dataPlacement: 'top'
}
},
diff --git a/awx/ui/static/less/ansible-ui.less b/awx/ui/static/less/ansible-ui.less
index d0319614f8..7935f280c0 100644
--- a/awx/ui/static/less/ansible-ui.less
+++ b/awx/ui/static/less/ansible-ui.less
@@ -9,11 +9,12 @@
@black: #171717;
@warning: #FF9900;
-@red: #FF0000;
+@red: #da4f49;;
@green: #5bb75b;
@blue: #1778c3; /* logo blue */
@blue-link: #0088cc;
@grey: #A9A9A9;
+@green: #5bb75b;
html {
background-color: @black;
@@ -32,6 +33,7 @@ body {
.no-bullets { list-style: none; }
.capitalize { text-transform: capitalize; }
.grey-txt { color: @grey; }
+.text-center { text-align: center !important; }
.success-badge {
color: #ffffff;
@@ -92,10 +94,6 @@ body {
z-index: 1050;
}
-.text-center {
- text-align: center !important;
-}
-
hr {
border-color: #e3e3e3;
}
@@ -607,8 +605,6 @@ select.field-mini-height {
/* Jobs pages */
- .job-error,
- .job-failed,
.license-expired,
.license-invalid,
.icon-failures-true,
@@ -633,33 +629,87 @@ select.field-mini-height {
padding-top: 5px;
}
- .job-new,
+
input[type="text"].job-new,
- .job-canceled,
input[type="text"].job-canceled {
color: #778899;
}
- .job-pending,
- .job-running,
- .job-success,
- .job-successful,
- .job-waiting,
.icon-failures-false,
.license-valid,
input[type="text"].job-success,
input[type="text"].job-successful {
- color: #5bb75b;
+ color: @green;
+ }
+
+ .icon-job-pending:before,
+ .icon-job-running:before,
+ .icon-job-success:before,
+ .icon-job-successful:before,
+ .icon-job-waiting:before,
+ .icon-job-new:before,
+ .icon-job-canceled:before,
+ .icon-job-changed:before {
+ content: "\f111";
+ }
+
+ .icon-job-error:before,
+ .icon-job-failed:before {
+ content: "\f06a";
+ }
+
+ .icon-job-pending,
+ .icon-job-running,
+ .icon-job-success,
+ .icon-job-successful,
+ .icon-job-waiting,
+ .icon-job-new {
+ color: @green;
+ }
+
+ .icon-job-canceled {
+ color: @grey;
+ }
+
+ .icon-job-changed {
+ color: @warning;
+ }
+
+ .icon-job-error,
+ .icon-job-failed {
+ color: @red;
+ }
+
+ .icon-failures-none {
+ color: @grey;
}
.icon-failures-true:before {
content: "\f06a";
}
+ .icon-failures-none:before,
.icon-failures-false:before {
content: "\f111";
}
+ .badge {
+ padding: 3px 4px;
+ font-size: 11px;
+ font-weight: normal;
+ line-height: 1;
+ }
+
+ /* Inventory job status badge */
+ .failures-true {
+ background-color: @red;
+ color: #fff;
+ }
+
+ .failures-false {
+ background-color: @green;
+ color: #fff;
+ }
/* Cloud inventory status. i.e. inventory_source.status values */
@@ -671,7 +721,7 @@ select.field-mini-height {
content: "\f0c2";
}
.icon-cloud-na {
- color: #e3e3e3;
+ color: #888;
}
.icon-cloud-never {
color: #888;
@@ -684,7 +734,6 @@ select.field-mini-height {
color: @red;
}
-
.field-success {
color: #5bb75b;
}
@@ -702,11 +751,10 @@ select.field-mini-height {
}
.field-badge {
- font-size: 12px;
+ font-size: 14px;
}
- .job-changed,
- .license-warning,
+ .license-warning
.license-demo {
color: @warning;
}
diff --git a/awx/ui/static/lib/ansible/form-generator.js b/awx/ui/static/lib/ansible/form-generator.js
index 20ccb59ad0..0538f7151e 100644
--- a/awx/ui/static/lib/ansible/form-generator.js
+++ b/awx/ui/static/lib/ansible/form-generator.js
@@ -1272,7 +1272,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
html += "\n";
- html += SearchWidget({ iterator: form.iterator, template: form, mini: true, size: 'col-md-6 col-lg-6'});
+ html += SearchWidget({ iterator: form.iterator, template: form, mini: true, size: 'col-md-5 col-lg-5'});
html += "
\n"
html += "
\n";
@@ -1309,26 +1309,28 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
html += "
\n";
for (var fld in form.fields) {
- html += "\n";
}
- html += "\n";
}
html += " \n";
@@ -1349,11 +1351,11 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
rfield = form.fields[fld];
if (fld == 'groups' ) {
// generate group form control/button widget
- html += "";
+ html += " ";
html += "\n";
html += "\n";
html += "" +
+ "aw-tool-tip=\"Edit group associations\" data-placement=\"top\" >" +
" \n";
html += " \n";
html += " \n";
@@ -1361,7 +1363,9 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
html += "
\n";
}
else {
- html += Column({ list: form, fld: fld, options: options, base: null });
+ if (form.fields[fld].searchOnly == undefined || form.fields[fld].searchOnly == false) {
+ html += Column({ list: form, fld: fld, options: options, base: null });
+ }
}
}
@@ -1404,12 +1408,14 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
html += "\n";
html += "\n"; // close list
+ /*
html += "\n";
html += "
\n";
html += " Only show hosts with failed jobs" +
" \n";
html += "
\n";
html += "
\n";
+ */
html += "\n"; // close well
diff --git a/awx/ui/static/lib/ansible/generator-helpers.js b/awx/ui/static/lib/ansible/generator-helpers.js
index 24bc0fa461..fef745d5a1 100644
--- a/awx/ui/static/lib/ansible/generator-helpers.js
+++ b/awx/ui/static/lib/ansible/generator-helpers.js
@@ -186,7 +186,13 @@ angular.module('GeneratorHelpers', ['GeneratorHelpers'])
var name = field['label'].replace(/ /g,'_');
- html = (params.td == undefined || params.td !== false) ? "\n" : "";
+ if (params.td == undefined || params.td !== false) {
+ html = " \n";
+ }
+ else {
+ html = '';
+ }
+
/*
html += "\n";
html += "" + field.options[i].label + "\n";
}
+
html += "\n";
html += "
\n";
-
html += (params.td == undefined || params.td !== false) ? " \n" : "";
-
+
return html;
}
}])
+ .factory('BadgeCount', [ function() {
+ return function(params) {
+
+ // Adds a badge count with optional tooltip
+
+ var list = params['list'];
+ var fld = params['fld'];
+ var field = list.fields[fld];
+ var options = params['options'];
+ var base = params['base'];
+ var html = "\n";
+ html += "";
+ html += "";
+ html += "\{\{ " + list.iterator + '.' + fld + " \}\}";
+ html += " ";
+ html += (field.badgeLabel) ? " " + field.badgeLabel : "";
+ html += " \n";
+ html += " \n";
+ return html;
+ }
+ }])
+
.factory('Badge', [ function() {
return function(field) {
+ // Adds an icon(s) with optional tooltip
+
var html = '';
if (field.badges) {
@@ -254,7 +290,10 @@ angular.module('GeneratorHelpers', ['GeneratorHelpers'])
}
else {
if (field.badgeToolTip) {
- html += "";
@@ -275,7 +314,7 @@ angular.module('GeneratorHelpers', ['GeneratorHelpers'])
}
}])
- .factory('Column', ['Attr', 'Icon', 'DropDown', 'Badge', function(Attr, Icon, DropDown, Badge) {
+ .factory('Column', ['Attr', 'Icon', 'DropDown', 'Badge', 'BadgeCount', function(Attr, Icon, DropDown, Badge, BadgeCount) {
return function(params) {
var list = params['list'];
var fld = params['fld'];
@@ -288,6 +327,9 @@ angular.module('GeneratorHelpers', ['GeneratorHelpers'])
if (field.type !== undefined && field.type == 'DropDown') {
html = DropDown(params);
}
+ else if (field.type == 'badgeCount') {
+ html = BadgeCount(params);
+ }
else {
html += " ";
}
@@ -458,7 +504,6 @@ angular.module('GeneratorHelpers', ['GeneratorHelpers'])
//html += "\n";
html += "