From b28fe1254ea5969314a399d3217c5a57859c8b75 Mon Sep 17 00:00:00 2001 From: chouseknecht Date: Mon, 7 Oct 2013 03:19:30 -0400 Subject: [PATCH] AC-414 Making red/green bubbles consistent throughout the application. Changes made to Inventory and jobs pages. Tooltip text now dynamic. Improved deep linking, especially on status fields. --- awx/ui/static/js/app.js | 3 +- awx/ui/static/js/controllers/Hosts.js | 18 +-- awx/ui/static/js/controllers/Inventories.js | 106 ++++++++++-------- awx/ui/static/js/controllers/JobEvents.js | 31 ++++- awx/ui/static/js/controllers/JobHosts.js | 44 ++++++-- awx/ui/static/js/controllers/JobTemplates.js | 8 ++ awx/ui/static/js/controllers/Jobs.js | 21 +++- awx/ui/static/js/forms/InventoryHosts.js | 26 ++++- awx/ui/static/js/forms/Jobs.js | 2 +- awx/ui/static/js/helpers/Events.js | 2 +- awx/ui/static/js/helpers/Groups.js | 65 +++++++++-- awx/ui/static/js/helpers/Hosts.js | 59 ++++++++-- awx/ui/static/js/helpers/Jobs.js | 40 +++++++ awx/ui/static/js/helpers/inventory.js | 10 +- awx/ui/static/js/helpers/search.js | 44 +++++++- awx/ui/static/js/lists/Inventories.js | 28 ++++- awx/ui/static/js/lists/InventorySummary.js | 71 ++++++------ awx/ui/static/js/lists/JobEvents.js | 12 +- awx/ui/static/js/lists/JobHosts.js | 14 ++- awx/ui/static/js/lists/Jobs.js | 13 ++- awx/ui/static/less/ansible-ui.less | 88 +++++++++++---- awx/ui/static/lib/ansible/form-generator.js | 52 +++++---- .../static/lib/ansible/generator-helpers.js | 76 +++++++++++-- awx/ui/static/lib/ansible/list-generator.js | 4 +- awx/ui/templates/ui/index.html | 1 + 25 files changed, 628 insertions(+), 210 deletions(-) create mode 100644 awx/ui/static/js/helpers/Jobs.js 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 += ""; + if (form.fields[fld].searchOnly == undefined || form.fields[fld].searchOnly == false) { + html += ""; + } + 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 += "\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 += "\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 += "
\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 += "\{\{ " + 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 += "\n"; - for ( var fld in form.fields) { if (form.fields[fld].searchable == undefined || form.fields[fld].searchable == true) { html += "
  • \n"; + "')\" placeholder=\"Search\" type=\"text\" ng-disabled=\"" + iterator + "InputDisable\">\n"; + /* html += "
  • \n"; html += "\n"; html += "
    \n"; + */ + + + // Reset button + html += "
    \n"; + html += "\n"; + html += "
    \n"; html += "
    \n"; + html += "\n"; html += "
    \n"; diff --git a/awx/ui/static/lib/ansible/list-generator.js b/awx/ui/static/lib/ansible/list-generator.js index cca55168bb..b8067b8248 100644 --- a/awx/ui/static/lib/ansible/list-generator.js +++ b/awx/ui/static/lib/ansible/list-generator.js @@ -224,10 +224,10 @@ angular.module('ListGenerator', ['GeneratorHelpers']) } } - if (list.name == 'inventories' && options.mode !== 'select') { + /*if (list.name == 'inventories' && options.mode !== 'select') { html += "\n"; - } + }*/ //select instructions if (options.mode == 'select' && list.selectInstructions) { diff --git a/awx/ui/templates/ui/index.html b/awx/ui/templates/ui/index.html index 93afec0205..a827d6f823 100644 --- a/awx/ui/templates/ui/index.html +++ b/awx/ui/templates/ui/index.html @@ -106,6 +106,7 @@ +