diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index f06f387415..00e53a38c4 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -70,7 +70,7 @@ angular.module('ansible', [ 'LicenseFormDefinition', 'License', 'HostGroupsFormDefinition', - 'ObjectCountWidget', + 'JobStatusWidget', 'JobsHelper', 'InventoryStatusDefinition' ]) diff --git a/awx/ui/static/js/controllers/Authentication.js b/awx/ui/static/js/controllers/Authentication.js index 27614053fc..43562943ea 100644 --- a/awx/ui/static/js/controllers/Authentication.js +++ b/awx/ui/static/js/controllers/Authentication.js @@ -16,6 +16,9 @@ function Authenticate($cookieStore, $window, $scope, $rootScope, $location, Auth $('#login-username').focus(); }; + // Just in case, make sure the wait widget is not active + Wait('stop'); + // Display the login dialog $('#login-modal').modal({ show: true, keyboard: false, backdrop: 'static' }); diff --git a/awx/ui/static/js/controllers/Home.js b/awx/ui/static/js/controllers/Home.js index afaecfbb4a..3e897ec0b9 100644 --- a/awx/ui/static/js/controllers/Home.js +++ b/awx/ui/static/js/controllers/Home.js @@ -10,7 +10,7 @@ 'use strict'; -function Home ($routeParams, $scope, $rootScope, $location, Wait, ObjectCount, ClearScope) +function Home ($routeParams, $scope, $rootScope, $location, Wait, JobStatus, ClearScope) { ClearScope('home'); //Garbage collection. Don't leave behind any listeners/watchers from the prior //scope. @@ -23,10 +23,10 @@ function Home ($routeParams, $scope, $rootScope, $location, Wait, ObjectCount, C Wait('start'); } - ObjectCount({ target: 'container1' }); + JobStatus({ target: 'container1' }); $rootScope.$on('WidgetLoaded', function() { - // Once all the widget report back 'loaded', turn off Wait widget + // Once all the widgets report back 'loaded', turn off Wait widget loadedCount++; if ( loadedCount == waitCount ) { Wait('stop'); @@ -34,4 +34,4 @@ function Home ($routeParams, $scope, $rootScope, $location, Wait, ObjectCount, C }); } -Home.$inject=[ '$routeParams', '$scope', '$rootScope', '$location', 'Wait', 'ObjectCount', 'ClearScope']; \ No newline at end of file +Home.$inject=[ '$routeParams', '$scope', '$rootScope', '$location', 'Wait', 'JobStatus', 'ClearScope']; \ No newline at end of file diff --git a/awx/ui/static/js/controllers/JobHosts.js b/awx/ui/static/js/controllers/JobHosts.js index 22e232b6c1..41cca7ffc6 100644 --- a/awx/ui/static/js/controllers/JobHosts.js +++ b/awx/ui/static/js/controllers/JobHosts.js @@ -74,32 +74,28 @@ function JobHostSummaryList ($scope, $rootScope, $location, $log, $routeParams, Rest.setUrl(GetBasePath('jobs') + scope.job_id); Rest.get() .success( function(data, status, headers, config) { + LoadBreadCrumbs({ path: '/jobs/' + data.id, title: data.id + ' - ' + + data.summary_fields.job_template.name }); 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); } } + scope.$emit('setHostLink', data.inventory); }) .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 }); }); } - else { + else { + // Make the host name appear in breadcrumbs + LoadBreadCrumbs({ path: '/hosts/' + scope['host_id'], title: $routeParams['host_name'] }); if ($routeParams['inventory']) { scope.$emit('setHostLink', $routeParams['inventory']); } } - 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({ path: '/jobs/' + scope.job_id, title: scope.job_id + ' - ' + - scope.jobhosts[0].summary_fields.job.job_template_name }); - } }); SearchInit({ scope: scope, set: 'jobhosts', list: list, url: defaultUrl }); diff --git a/awx/ui/static/js/controllers/Projects.js b/awx/ui/static/js/controllers/Projects.js index dfa65db951..d7cfc83319 100644 --- a/awx/ui/static/js/controllers/Projects.js +++ b/awx/ui/static/js/controllers/Projects.js @@ -42,10 +42,12 @@ function ProjectsList ($scope, $rootScope, $location, $log, $routeParams, Rest, scope.projects[i].status = 'n/a'; } switch(scope.projects[i].status) { + case 'n/a': + scope.projects[i].badge = 'none'; + break; case 'updating': case 'successful': case 'ok': - case 'n/a': scope.projects[i].badge = 'false'; break; case 'never updated': diff --git a/awx/ui/static/js/lists/Jobs.js b/awx/ui/static/js/lists/Jobs.js index 0f2b64b382..83d2ec3bfb 100644 --- a/awx/ui/static/js/lists/Jobs.js +++ b/awx/ui/static/js/lists/Jobs.js @@ -31,13 +31,14 @@ angular.module('JobsListDefinition', []) }, created: { label: 'Date', - link: true, + link: false, searchable: false }, job_template: { label: 'Job Template', ngBind: 'job.summary_fields.job_template.name', - ngHref: "\{\{ '/#/job_templates/?name=' + job.summary_fields.job_template.name \}\}", + //ngHref: "\{\{ '/#/job_templates/?name=' + job.summary_fields.job_template.name \}\}", + ngHref:"\{\{ '/#/job_templates/' + job.job_template \}\}", sourceModel: 'job_template', sourceField: 'name' }, diff --git a/awx/ui/static/js/lists/Projects.js b/awx/ui/static/js/lists/Projects.js index dfd1309b80..da462fd430 100644 --- a/awx/ui/static/js/lists/Projects.js +++ b/awx/ui/static/js/lists/Projects.js @@ -33,7 +33,7 @@ angular.module('ProjectsListDefinition', []) label: 'Update Status', ngClick: 'showSCMStatus(\{\{ project.id \}\})', awToolTip: 'View details of last SCM Update', - dataPlacement: 'bottom', + dataPlacement: 'top', badgeIcon: "\{\{ 'icon-failures-' + project.badge \}\}", badgePlacement: 'left' }, diff --git a/awx/ui/static/js/widgets/JobStatus.js b/awx/ui/static/js/widgets/JobStatus.js new file mode 100644 index 0000000000..2f4a4fad73 --- /dev/null +++ b/awx/ui/static/js/widgets/JobStatus.js @@ -0,0 +1,159 @@ +/********************************************* + * Copyright (c) 2013 AnsibleWorks, Inc. + * + * JobStatus.js + * + * Dashboard widget showing object counts and license availability. + * + */ + +angular.module('JobStatusWidget', ['RestServices', 'Utilities']) + .factory('JobStatus', ['$rootScope', '$compile', 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait', + function($rootScope, $compile, Rest, GetBasePath, ProcessErrors, Wait) { + return function(params) { + + var scope = $rootScope.$new(); + var jobCount, jobFails, inventoryCount, inventoryFails, groupCount, groupFails, hostCount, hostFails; + var counts = 0; + var expectedCounts = 8; + var target = params.target; + + scope.$on('CountReceived', function() { + + function makeRow(label, count, fail) { + return "" + label + + "" + + fail + "" + + count + ""; + } + + counts++; + if (counts == expectedCounts) { + // all the counts came back, now generate the HTML + var html = "
\n"; + html += "
Job Status
\n"; + html += "
\n"; + html += "\n"; + html += "\n"; + html += "\n"; + html += "\n"; + html += "\n"; + html += "\n"; + html += "\n"; + html += "\n"; + html += "\n"; + html += makeRow('Jobs', jobCount, jobFails); + html += makeRow('Inventories', inventoryCount, inventoryFails); + html += makeRow('Groups', groupCount, groupFails); + html += makeRow('Hosts', hostCount, hostFails); + html += "\n"; + html += "
FailedTotal
\n"; + html += "
\n"; + html += "
\n"; + html += "\n"; + } + + var element = angular.element(document.getElementById(target)); + element.html(html); + $compile(element)(scope); + $rootScope.$emit('WidgetLoaded'); + }); + + var url = GetBasePath('jobs') + '?page=1'; + Rest.setUrl(url); + Rest.get() + .success( function(data, status, headers, config) { + jobCount=data.count; + scope.$emit('CountReceived'); + }) + .error( function(data, status, headers, config) { + ProcessErrors(scope, data, status, null, + { hdr: 'Error!', msg: 'Failed to get ' + url + '. GET status: ' + status }); + }); + + url = GetBasePath('jobs') + '?failed=true&page=1'; + Rest.setUrl(url); + Rest.get() + .success( function(data, status, headers, config) { + jobFails=data.count; + scope.$emit('CountReceived'); + }) + .error( function(data, status, headers, config) { + ProcessErrors(scope, data, status, null, + { hdr: 'Error!', msg: 'Failed to get ' + url + '. GET status: ' + status }); + }); + + url = GetBasePath('inventory') + '?page=1'; + Rest.setUrl(url); + Rest.get() + .success( function(data, status, headers, config) { + inventoryCount=data.count; + scope.$emit('CountReceived'); + }) + .error( function(data, status, headers, config) { + ProcessErrors(scope, data, status, null, + { hdr: 'Error!', msg: 'Failed to get ' + url + '. GET status: ' + status }); + }); + + url = GetBasePath('inventory') + '?has_active_failures=true&page=1'; + Rest.setUrl(url); + Rest.get() + .success( function(data, status, headers, config) { + inventoryFails=data.count; + scope.$emit('CountReceived'); + }) + .error( function(data, status, headers, config) { + ProcessErrors(scope, data, status, null, + { hdr: 'Error!', msg: 'Failed to get ' + url + '. GET status: ' + status }); + }); + + url = GetBasePath('groups') + '?page=1'; + Rest.setUrl(url); + Rest.get() + .success( function(data, status, headers, config) { + groupCount = data.count; + scope.$emit('CountReceived'); + }) + .error( function(data, status, headers, config) { + ProcessErrors(scope, data, status, null, + { hdr: 'Error!', msg: 'Failed to get ' + url + '. GET status: ' + status }); + }); + + url = GetBasePath('groups') + '?has_active_failures=true&page=1'; + Rest.setUrl(url); + Rest.get() + .success( function(data, status, headers, config) { + groupFails = data.count; + scope.$emit('CountReceived'); + }) + .error( function(data, status, headers, config) { + ProcessErrors(scope, data, status, null, + { hdr: 'Error!', msg: 'Failed to get ' + url + '. GET status: ' + status }); + }); + + url = GetBasePath('hosts') + '?page=1'; + Rest.setUrl(url); + Rest.get() + .success( function(data, status, headers, config) { + hostCount = data.count; + scope.$emit('CountReceived'); + }) + .error( function(data, status, headers, config) { + ProcessErrors(scope, data, status, null, + { hdr: 'Error!', msg: 'Failed to get ' + url + '. GET status: ' + status }); + }); + + url = GetBasePath('hosts') + '?has_active_failures=true&page=1'; + Rest.setUrl(url); + Rest.get() + .success( function(data, status, headers, config) { + hostFails = data.count; + scope.$emit('CountReceived'); + }) + .error( function(data, status, headers, config) { + ProcessErrors(scope, data, status, null, + { hdr: 'Error!', msg: 'Failed to get ' + url + '. GET status: ' + status }); + }); + + } + }]); diff --git a/awx/ui/static/less/ansible-ui.less b/awx/ui/static/less/ansible-ui.less index 473b4cff7a..aba0fe0a95 100644 --- a/awx/ui/static/less/ansible-ui.less +++ b/awx/ui/static/less/ansible-ui.less @@ -9,7 +9,8 @@ @black: #171717; @warning: #FF9900; -@red: #da4f49;; +@red: #da4f49; +@red-hover: #AE3F3A; @green: #5bb75b; @blue: #1778c3; /* logo blue */ @blue-link: #0088cc; @@ -1175,6 +1176,17 @@ tr td button i { } +/* Home page */ + +.failed-column { + a:link, a:visited { + color: @red; + } + a:hover { + color: @red-hover; + } +} + /* Large desktop */ diff --git a/awx/ui/templates/ui/index.html b/awx/ui/templates/ui/index.html index f5cfa4f9ae..69641a71da 100644 --- a/awx/ui/templates/ui/index.html +++ b/awx/ui/templates/ui/index.html @@ -108,7 +108,7 @@ - +