From 5a3977495a14b8fb5f51b1a089e433c1bd6d6f37 Mon Sep 17 00:00:00 2001 From: Chris Houseknecht Date: Fri, 8 Nov 2013 17:58:19 +0000 Subject: [PATCH] First iteration of Activity Stream. Added Home/Groups page. Increased icon size for icon-only buttons. Dashboard jobs widget- group and job links now work. Closed AC-621, AC-618. --- awx/ui/static/html/event_log.html | 618 ++++++++++++++++++ awx/ui/static/img/cow.png | Bin 54310 -> 0 bytes awx/ui/static/img/footsteps.png | Bin 0 -> 5526 bytes awx/ui/static/js/app.js | 29 +- awx/ui/static/js/controllers/Credentials.js | 12 +- awx/ui/static/js/controllers/Home.js | 84 ++- awx/ui/static/js/controllers/Jobs.js | 19 +- awx/ui/static/js/forms/Credentials.js | 20 + awx/ui/static/js/helpers/Groups.js | 89 +-- awx/ui/static/js/lists/HomeGroups.js | 119 ++++ awx/ui/static/js/lists/InventorySummary.js | 3 +- awx/ui/static/js/lists/JobEvents.js | 3 +- awx/ui/static/js/lists/JobHosts.js | 3 +- awx/ui/static/js/lists/Jobs.js | 3 +- awx/ui/static/js/lists/Projects.js | 3 +- awx/ui/static/js/lists/Streams.js | 62 ++ awx/ui/static/js/widgets/JobStatus.js | 48 +- awx/ui/static/js/widgets/Stream.js | 102 +++ awx/ui/static/less/ansible-ui.less | 35 +- awx/ui/static/lib/ansible/Utilities.js | 10 +- .../static/lib/ansible/generator-helpers.js | 3 +- awx/ui/static/lib/ansible/list-generator.js | 23 +- awx/ui/static/partials/home.html | 5 +- awx/ui/static/partials/subhome.html | 3 + awx/ui/templates/ui/index.html | 9 +- 25 files changed, 1204 insertions(+), 101 deletions(-) create mode 100644 awx/ui/static/html/event_log.html delete mode 100644 awx/ui/static/img/cow.png create mode 100644 awx/ui/static/img/footsteps.png create mode 100644 awx/ui/static/js/lists/HomeGroups.js create mode 100644 awx/ui/static/js/lists/Streams.js create mode 100644 awx/ui/static/js/widgets/Stream.js create mode 100644 awx/ui/static/partials/subhome.html diff --git a/awx/ui/static/html/event_log.html b/awx/ui/static/html/event_log.html new file mode 100644 index 0000000000..e55e3ae972 --- /dev/null +++ b/awx/ui/static/html/event_log.html @@ -0,0 +1,618 @@ +{ + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "id": 1, + "url": "/api/v1/event_log/1/", + "related": { + "user": "/users/N/", + "object1": "/organizations/N/", + "object2": "" + }, + + "summary_fields": { + "object1": { + "name": "Frito Lay", + "description": "Salty Snacks" + }, + "user": { + "username": "chouseknecht" + } + }, + + "created": "2013-11-06T15:18:58.391Z", + "modified": "2013-11-06T15:18:58.514Z", + "user": 1, + "event_time": "2013-11-06T15:18:58.514Z", + "operation": "change", + "changes": { + "before": { "description": "Healthy Snacks" }, + "after": { "description": "Salty Snacks" } + }, + "relationship": "" + }, + { + "id": 2, + "url": "/api/v1/event_log/2/", + "related": { + "user": "/users/N/", + "object1": "/groups/N/", + "object2": "" + }, + + "summary_fields": { + "inventory": { + "name": "Test Inventory", + "description": "Testing activity stream" + }, + "object1": { + "name": "Group A", + "description": "The A group" + }, + "user": { + "username": "chouseknecht" + }, + "object2": {} + }, + + "created": "2013-11-05T15:18:55.000Z", + "modified": "2013-11-05T15:18:55.000Z", + "user": 1, + "event_time": "2013-11-06T15:18:55.000Z", + "operation": "create", + "changes": { + "before": { "groups": [ "Group X", "Group Y", "Group Z" ] }, + "after": { "groups": [ "Group A", "Group X", "Group Y", "Group Z" ] } + }, + "relationship": "" + }, + { + "id": 3, + "url": "/api/v1/event_log/2/", + "related": { + "user": "/users/N/", + "object1": "/groups/N/children", + "object2": "/groups/N/" + }, + + "summary_fields": { + "inventory": { + "name": "Test Inventory", + "description": "Testing activity stream" + }, + "object1": { + "name": "Group A", + "description": "The A group" + }, + "user": { "username": "chouseknecht" }, + "object2": { + "name": "Group B", + "description": "The B group" + } + }, + + "created": "2013-11-05T15:18:58.391Z", + "modified": "2013-11-05T15:18:58.514Z", + "user": 1, + "event_time": "2013-11-06T15:18:58.514Z", + "operation": "associate", + "changes": { + "before": { "groups": [ "Group X", "Group Y", "Group Z" ] }, + "after": { "groups": [ "Group A", "Group X", "Group Y", "Group Z" ] } + }, + "relationship": "" + }, + { + "id": 1, + "url": "/api/v1/event_log/1/", + "related": { + "user": "/users/N/", + "object1": "/organizations/N/", + "object2": "" + }, + + "summary_fields": { + "object1": { + "name": "Frito Lay", + "description": "Salty Snacks" + }, + "user": { + "username": "chouseknecht" + } + }, + + "created": "2013-11-06T15:18:58.391Z", + "modified": "2013-11-06T15:18:58.514Z", + "user": 1, + "event_time": "2013-11-06T15:18:58.514Z", + "operation": "change", + "changes": { + "before": { "description": "Healthy Snacks" }, + "after": { "description": "Salty Snacks" } + }, + "relationship": "" + }, + { + "id": 1, + "url": "/api/v1/event_log/1/", + "related": { + "user": "/users/N/", + "object1": "/organizations/N/", + "object2": "" + }, + + "summary_fields": { + "object1": { + "name": "Frito Lay", + "description": "Salty Snacks" + }, + "user": { + "username": "chouseknecht" + } + }, + + "created": "2013-11-06T15:18:58.391Z", + "modified": "2013-11-06T15:18:58.514Z", + "user": 1, + "event_time": "2013-11-06T15:18:58.514Z", + "operation": "change", + "changes": { + "before": { "description": "Healthy Snacks" }, + "after": { "description": "Salty Snacks" } + }, + "relationship": "" + }, + { + "id": 1, + "url": "/api/v1/event_log/1/", + "related": { + "user": "/users/N/", + "object1": "/organizations/N/", + "object2": "" + }, + + "summary_fields": { + "object1": { + "name": "Frito Lay", + "description": "Salty Snacks" + }, + "user": { + "username": "chouseknecht" + } + }, + + "created": "2013-11-06T15:18:58.391Z", + "modified": "2013-11-06T15:18:58.514Z", + "user": 1, + "event_time": "2013-11-06T15:18:58.514Z", + "operation": "change", + "changes": { + "before": { "description": "Healthy Snacks" }, + "after": { "description": "Salty Snacks" } + }, + "relationship": "" + }, + { + "id": 1, + "url": "/api/v1/event_log/1/", + "related": { + "user": "/users/N/", + "object1": "/organizations/N/", + "object2": "" + }, + + "summary_fields": { + "object1": { + "name": "Frito Lay", + "description": "Salty Snacks" + }, + "user": { + "username": "chouseknecht" + } + }, + + "created": "2013-11-06T15:18:58.391Z", + "modified": "2013-11-06T15:18:58.514Z", + "user": 1, + "event_time": "2013-11-06T15:18:58.514Z", + "operation": "change", + "changes": { + "before": { "description": "Healthy Snacks" }, + "after": { "description": "Salty Snacks" } + }, + "relationship": "" + }, + { + "id": 1, + "url": "/api/v1/event_log/1/", + "related": { + "user": "/users/N/", + "object1": "/organizations/N/", + "object2": "" + }, + + "summary_fields": { + "object1": { + "name": "Frito Lay", + "description": "Salty Snacks" + }, + "user": { + "username": "chouseknecht" + } + }, + + "created": "2013-11-06T15:18:58.391Z", + "modified": "2013-11-06T15:18:58.514Z", + "user": 1, + "event_time": "2013-11-06T15:18:58.514Z", + "operation": "change", + "changes": { + "before": { "description": "Healthy Snacks" }, + "after": { "description": "Salty Snacks" } + }, + "relationship": "" + }, + { + "id": 1, + "url": "/api/v1/event_log/1/", + "related": { + "user": "/users/N/", + "object1": "/organizations/N/", + "object2": "" + }, + + "summary_fields": { + "object1": { + "name": "Frito Lay", + "description": "Salty Snacks" + }, + "user": { + "username": "chouseknecht" + } + }, + + "created": "2013-11-06T15:18:58.391Z", + "modified": "2013-11-06T15:18:58.514Z", + "user": 1, + "event_time": "2013-11-06T15:18:58.514Z", + "operation": "change", + "changes": { + "before": { "description": "Healthy Snacks" }, + "after": { "description": "Salty Snacks" } + }, + "relationship": "" + }, + { + "id": 1, + "url": "/api/v1/event_log/1/", + "related": { + "user": "/users/N/", + "object1": "/organizations/N/", + "object2": "" + }, + + "summary_fields": { + "object1": { + "name": "Frito Lay", + "description": "Salty Snacks" + }, + "user": { + "username": "chouseknecht" + } + }, + + "created": "2013-11-06T15:18:58.391Z", + "modified": "2013-11-06T15:18:58.514Z", + "user": 1, + "event_time": "2013-11-06T15:18:58.514Z", + "operation": "change", + "changes": { + "before": { "description": "Healthy Snacks" }, + "after": { "description": "Salty Snacks" } + }, + "relationship": "" + }, + { + "id": 1, + "url": "/api/v1/event_log/1/", + "related": { + "user": "/users/N/", + "object1": "/organizations/N/", + "object2": "" + }, + + "summary_fields": { + "object1": { + "name": "Frito Lay", + "description": "Salty Snacks" + }, + "user": { + "username": "chouseknecht" + } + }, + + "created": "2013-11-06T15:18:58.391Z", + "modified": "2013-11-06T15:18:58.514Z", + "user": 1, + "event_time": "2013-11-06T15:18:58.514Z", + "operation": "change", + "changes": { + "before": { "description": "Healthy Snacks" }, + "after": { "description": "Salty Snacks" } + }, + "relationship": "" + }, + { + "id": 1, + "url": "/api/v1/event_log/1/", + "related": { + "user": "/users/N/", + "object1": "/organizations/N/", + "object2": "" + }, + + "summary_fields": { + "object1": { + "name": "Frito Lay", + "description": "Salty Snacks" + }, + "user": { + "username": "chouseknecht" + } + }, + + "created": "2013-11-06T15:18:58.391Z", + "modified": "2013-11-06T15:18:58.514Z", + "user": 1, + "event_time": "2013-11-06T15:18:58.514Z", + "operation": "change", + "changes": { + "before": { "description": "Healthy Snacks" }, + "after": { "description": "Salty Snacks" } + }, + "relationship": "" + }, + { + "id": 1, + "url": "/api/v1/event_log/1/", + "related": { + "user": "/users/N/", + "object1": "/organizations/N/", + "object2": "" + }, + + "summary_fields": { + "object1": { + "name": "Frito Lay", + "description": "Salty Snacks" + }, + "user": { + "username": "chouseknecht" + } + }, + + "created": "2013-11-06T15:18:58.391Z", + "modified": "2013-11-06T15:18:58.514Z", + "user": 1, + "event_time": "2013-11-06T15:18:58.514Z", + "operation": "change", + "changes": { + "before": { "description": "Healthy Snacks" }, + "after": { "description": "Salty Snacks" } + }, + "relationship": "" + }, + { + "id": 1, + "url": "/api/v1/event_log/1/", + "related": { + "user": "/users/N/", + "object1": "/organizations/N/", + "object2": "" + }, + + "summary_fields": { + "object1": { + "name": "Frito Lay", + "description": "Salty Snacks" + }, + "user": { + "username": "chouseknecht" + } + }, + + "created": "2013-11-06T15:18:58.391Z", + "modified": "2013-11-06T15:18:58.514Z", + "user": 1, + "event_time": "2013-11-06T15:18:58.514Z", + "operation": "change", + "changes": { + "before": { "description": "Healthy Snacks" }, + "after": { "description": "Salty Snacks" } + }, + "relationship": "" + }, + { + "id": 1, + "url": "/api/v1/event_log/1/", + "related": { + "user": "/users/N/", + "object1": "/organizations/N/", + "object2": "" + }, + + "summary_fields": { + "object1": { + "name": "Frito Lay", + "description": "Salty Snacks" + }, + "user": { + "username": "chouseknecht" + } + }, + + "created": "2013-11-06T15:18:58.391Z", + "modified": "2013-11-06T15:18:58.514Z", + "user": 1, + "event_time": "2013-11-06T15:18:58.514Z", + "operation": "change", + "changes": { + "before": { "description": "Healthy Snacks" }, + "after": { "description": "Salty Snacks" } + }, + "relationship": "" + }, + { + "id": 1, + "url": "/api/v1/event_log/1/", + "related": { + "user": "/users/N/", + "object1": "/organizations/N/", + "object2": "" + }, + + "summary_fields": { + "object1": { + "name": "Frito Lay", + "description": "Salty Snacks" + }, + "user": { + "username": "chouseknecht" + } + }, + + "created": "2013-11-06T15:18:58.391Z", + "modified": "2013-11-06T15:18:58.514Z", + "user": 1, + "event_time": "2013-11-06T15:18:58.514Z", + "operation": "change", + "changes": { + "before": { "description": "Healthy Snacks" }, + "after": { "description": "Salty Snacks" } + }, + "relationship": "" + }, + { + "id": 1, + "url": "/api/v1/event_log/1/", + "related": { + "user": "/users/N/", + "object1": "/organizations/N/", + "object2": "" + }, + + "summary_fields": { + "object1": { + "name": "Frito Lay", + "description": "Salty Snacks" + }, + "user": { + "username": "chouseknecht" + } + }, + + "created": "2013-11-06T15:18:58.391Z", + "modified": "2013-11-06T15:18:58.514Z", + "user": 1, + "event_time": "2013-11-06T15:18:58.514Z", + "operation": "change", + "changes": { + "before": { "description": "Healthy Snacks" }, + "after": { "description": "Salty Snacks" } + }, + "relationship": "" + }, + { + "id": 1, + "url": "/api/v1/event_log/1/", + "related": { + "user": "/users/N/", + "object1": "/organizations/N/", + "object2": "" + }, + + "summary_fields": { + "object1": { + "name": "Frito Lay", + "description": "Salty Snacks" + }, + "user": { + "username": "chouseknecht" + } + }, + + "created": "2013-11-06T15:18:58.391Z", + "modified": "2013-11-06T15:18:58.514Z", + "user": 1, + "event_time": "2013-11-06T15:18:58.514Z", + "operation": "change", + "changes": { + "before": { "description": "Healthy Snacks" }, + "after": { "description": "Salty Snacks" } + }, + "relationship": "" + }, + { + "id": 1, + "url": "/api/v1/event_log/1/", + "related": { + "user": "/users/N/", + "object1": "/organizations/N/", + "object2": "" + }, + + "summary_fields": { + "object1": { + "name": "Frito Lay", + "description": "Salty Snacks" + }, + "user": { + "username": "chouseknecht" + } + }, + + "created": "2013-11-06T15:18:58.391Z", + "modified": "2013-11-06T15:18:58.514Z", + "user": 1, + "event_time": "2013-11-06T15:18:58.514Z", + "operation": "change", + "changes": { + "before": { "description": "Healthy Snacks" }, + "after": { "description": "Salty Snacks" } + }, + "relationship": "" + }, + { + "id": 1, + "url": "/api/v1/event_log/1/", + "related": { + "user": "/users/N/", + "object1": "/organizations/N/", + "object2": "" + }, + + "summary_fields": { + "object1": { + "name": "Frito Lay", + "description": "Salty Snacks" + }, + "user": { + "username": "chouseknecht" + } + }, + + "created": "2013-11-06T15:18:58.391Z", + "modified": "2013-11-06T15:18:58.514Z", + "user": 1, + "event_time": "2013-11-06T15:18:58.514Z", + "operation": "change", + "changes": { + "before": { "description": "Healthy Snacks" }, + "after": { "description": "Salty Snacks" } + }, + "relationship": "" + } + ] +} \ No newline at end of file diff --git a/awx/ui/static/img/cow.png b/awx/ui/static/img/cow.png deleted file mode 100644 index bd9ac11bf6a9b6c3b5aa8fd7568dcb3156828422..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54310 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 zⅈEW13NdL0qO%RdM8{jpM4o$Q_aC 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 be$W2{EnwO<3I9kH00000NkvXXu0mjfU~BQ? diff --git a/awx/ui/static/img/footsteps.png b/awx/ui/static/img/footsteps.png new file mode 100644 index 0000000000000000000000000000000000000000..42bdfd90da802d2fa47aedd765dc48e9f9da5e21 GIT binary patch literal 5526 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 Lb{3W9-Vgo{hmty^ literal 0 HcmV?d00001 diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index 75dabd23f7..c92697f086 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -5,7 +5,7 @@ * */ -var urlPrefix = '/static/'; +var urlPrefix = $basePath; angular.module('ansible', [ 'RestServices', @@ -74,13 +74,16 @@ angular.module('ansible', [ 'InventorySyncStatusWidget', 'SCMSyncStatusWidget', 'ObjectCountWidget', + 'StreamWidget', 'JobsHelper', 'InventoryStatusDefinition', 'InventorySummaryHelpDefinition', 'InventoryHostsHelpDefinition', 'TreeSelector', 'CredentialsHelper', - 'TimerService' + 'TimerService', + 'StreamListDefinition', + 'HomeGroupListDefinition' ]) .config(['$routeProvider', function($routeProvider) { $routeProvider. @@ -245,13 +248,15 @@ angular.module('ansible', [ when('/logout', { templateUrl: urlPrefix + 'partials/organizations.html', controller: Authenticate }). when('/home', { templateUrl: urlPrefix + 'partials/home.html', controller: Home }). + + when('/home/groups', { templateUrl: urlPrefix + 'partials/subhome.html', controller: HomeGroups }). otherwise({redirectTo: '/home'}); }]) .run(['$cookieStore', '$rootScope', 'CheckLicense', '$location', 'Authorization','LoadBasePaths', 'ViewLicense', - 'Timer', + 'Timer', 'ClearScope', 'HideStream', function($cookieStore, $rootScope, CheckLicense, $location, Authorization, LoadBasePaths, ViewLicense, - Timer) { + Timer, ClearScope, HideStream) { LoadBasePaths(); @@ -260,12 +265,17 @@ angular.module('ansible', [ $rootScope.sessionTimer = Timer.init(); $rootScope.$on("$routeChangeStart", function(event, next, current) { + + // Before navigating away from current tab, make sure the primary view is visible + if ($('#stream-container').is(':visible')) { + HideStream(); + } + // On each navigation request, check that the user is logged in - - var tst = /login/; + var tst = /(login|logout)/; var path = $location.path(); if ( !tst.test($location.path()) ) { - // capture most recent URL, excluding login + // capture most recent URL, excluding login/logout $rootScope.lastPath = path; $cookieStore.put('lastPath', path); } @@ -288,11 +298,6 @@ angular.module('ansible', [ CheckLicense(); } - if ($rootScope.timer) { - clearInterval($rootScope.timer); - $rootScope.timer = null; - } - // Make the correct tab active var base = $location.path().replace(/^\//,'').split('/')[0]; if (base == '') { diff --git a/awx/ui/static/js/controllers/Credentials.js b/awx/ui/static/js/controllers/Credentials.js index ae05097723..8ad5229ae3 100644 --- a/awx/ui/static/js/controllers/Credentials.js +++ b/awx/ui/static/js/controllers/Credentials.js @@ -227,6 +227,9 @@ function CredentialsAdd ($scope, $rootScope, $compile, $location, $log, $routePa data['username'] = scope['access_key']; data['password'] = scope['secret_key']; break; + case 'scm': + data['ssh_key_unlock'] = scope['scm_key_unlock']; + break; } if (Empty(data.team) && Empty(data.user)) { @@ -415,7 +418,10 @@ function CredentialsEdit ($scope, $rootScope, $compile, $location, $log, $routeP scope['ssh_password'] = data.password; master['ssh_username'] = scope['ssh_username']; master['ssh_password'] = scope['ssh_password']; - break; + break; + case 'scm': + scope['scm_key_unlock'] = data['ssh_key_unlock']; + break; } scope.$emit('credentialLoaded'); @@ -451,7 +457,6 @@ function CredentialsEdit ($scope, $rootScope, $compile, $location, $log, $routeP } } - if (!Empty(scope.team)) { data.team = scope.team; data.user = ""; @@ -472,6 +477,9 @@ function CredentialsEdit ($scope, $rootScope, $compile, $location, $log, $routeP data['username'] = scope['access_key']; data['password'] = scope['secret_key']; break; + case 'scm': + data['ssh_key_unlock'] = scope['scm_key_unlock']; + break; } if (Empty(data.team) && Empty(data.user)) { diff --git a/awx/ui/static/js/controllers/Home.js b/awx/ui/static/js/controllers/Home.js index b1e99a22c8..922f4e0fa5 100644 --- a/awx/ui/static/js/controllers/Home.js +++ b/awx/ui/static/js/controllers/Home.js @@ -11,7 +11,7 @@ 'use strict'; function Home ($routeParams, $scope, $rootScope, $location, Wait, ObjectCount, JobStatus, InventorySyncStatus, SCMSyncStatus, - ClearScope) { + ClearScope, Stream) { ClearScope('home'); //Garbage collection. Don't leave behind any listeners/watchers from the prior //scope. @@ -28,6 +28,8 @@ function Home ($routeParams, $scope, $rootScope, $location, Wait, ObjectCount, J InventorySyncStatus({ target: 'container2' }); SCMSyncStatus({ target: 'container4' }); ObjectCount({ target: 'container3' }); + + $rootScope.showActivity = function() { Stream(); } $rootScope.$on('WidgetLoaded', function() { // Once all the widgets report back 'loaded', turn off Wait widget @@ -39,4 +41,82 @@ function Home ($routeParams, $scope, $rootScope, $location, Wait, ObjectCount, J } Home.$inject=[ '$routeParams', '$scope', '$rootScope', '$location', 'Wait', 'ObjectCount', 'JobStatus', 'InventorySyncStatus', - 'SCMSyncStatus', 'ClearScope']; \ No newline at end of file + 'SCMSyncStatus', 'ClearScope', 'Stream']; + + +function HomeGroups ($location, $routeParams, HomeGroupList, GenerateList, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, + GetBasePath, SearchInit, PaginateInit, FormatDate, HostsStatusMsg, UpdateStatusMsg, ViewUpdateStatus) { + + ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior + //scope. + + var generator = GenerateList; + var list = HomeGroupList; + var defaultUrl=GetBasePath('groups'); + + var scope = generator.inject(list, { mode: 'edit' }); + var base = $location.path().replace(/^\//,'').split('/')[0]; + + if (scope.removePostRefresh) { + scope.removePostRefresh(); + } + scope.removePostRefresh = scope.$on('PostRefresh', function() { + var msg, update_status, last_update; + for (var i=0; i < scope.groups.length; i++) { + + scope['groups'][i]['inventory_name'] = scope['groups'][i]['summary_fields']['inventory']['name']; + + last_update = (scope.groups[i].summary_fields.inventory_source.last_updated == null) ? null : + FormatDate(new Date(scope.groups[i].summary_fields.inventory_source.last_updated)); + + // Set values for Failed Hosts column + scope.groups[i].failed_hosts = scope.groups[i].hosts_with_active_failures + ' / ' + scope.groups[i].total_hosts; + + msg = HostsStatusMsg({ + active_failures: scope.groups[i].hosts_with_active_failures, + total_hosts: scope.groups[i].total_hosts, + inventory_id: scope.groups[i].inventory + }); + + update_status = UpdateStatusMsg({ status: scope.groups[i].summary_fields.inventory_source.status }); + + scope.groups[i].failed_hosts_tip = msg['tooltip']; + scope.groups[i].failed_hosts_link = msg['url']; + scope.groups[i].failed_hosts_class = msg['class']; + scope.groups[i].status = update_status['status']; + scope.groups[i].source = scope.groups[i].summary_fields.inventory_source.source; + scope.groups[i].last_updated = last_update; + scope.groups[i].status_badge_class = update_status['class']; + scope.groups[i].status_badge_tooltip = update_status['tooltip']; + } + }); + + SearchInit({ scope: scope, set: 'groups', list: list, url: defaultUrl }); + PaginateInit({ scope: scope, list: list, url: defaultUrl }); + + if ($routeParams['status']) { + // with status param, called post update-submit + scope[list.iterator + 'SearchField'] = 'status'; + scope[list.iterator + 'SelectShow'] = true; + scope[list.iterator + 'SearchSelectOpts'] = list.fields['status'].searchOptions; + scope[list.iterator + 'SearchFieldLabel'] = list.fields['status'].label.replace(/\/g,' '); + for (var opt in list.fields['status'].searchOptions) { + if (list.fields['status'].searchOptions[opt].value == $routeParams['status']) { + scope[list.iterator + 'SearchSelectValue'] = list.fields['status'].searchOptions[opt]; + break; + } + } + } + + scope.search(list.iterator); + + LoadBreadCrumbs(); + + scope.viewUpdateStatus = function(id) { ViewUpdateStatus({ scope: scope, group_id: id }) }; + + } + +HomeGroups.$inject = [ '$location', '$routeParams', 'HomeGroupList', 'GenerateList', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', + 'ClearScope', 'GetBasePath', 'SearchInit', 'PaginateInit', 'FormatDate', 'HostsStatusMsg', 'UpdateStatusMsg', 'ViewUpdateStatus' + ]; + \ No newline at end of file diff --git a/awx/ui/static/js/controllers/Jobs.js b/awx/ui/static/js/controllers/Jobs.js index bfe9635ff9..724a007d02 100644 --- a/awx/ui/static/js/controllers/Jobs.js +++ b/awx/ui/static/js/controllers/Jobs.js @@ -13,7 +13,7 @@ function JobsListCtrl ($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, JobList, GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, LookUpInit, SubmitJob, FormatDate, Refresh, - JobStatusToolTip) + JobStatusToolTip, Empty) { ClearScope('htmlTemplate'); var list = JobList; @@ -52,7 +52,7 @@ function JobsListCtrl ($scope, $rootScope, $location, $log, $routeParams, Rest, if ($routeParams['job_host_summaries__host']) { defaultUrl += '?job_host_summaries__host=' + $routeParams['job_host_summaries__host']; } - if ($routeParams['inventory__int'] && $routeParams['status']) { + else if ($routeParams['inventory__int'] && $routeParams['status']) { defaultUrl += '?inventory__int=' + $routeParams['inventory__int'] + '&status=' + $routeParams['status']; } @@ -70,6 +70,18 @@ function JobsListCtrl ($scope, $rootScope, $location, $log, $routeParams, Rest, scope[list.iterator + 'SearchValue'] = $routeParams['id__int']; scope[list.iterator + 'SearchFieldLabel'] = 'Job ID'; } + if ($routeParams['status']) { + scope[list.iterator + 'SearchField'] = 'status'; + scope[list.iterator + 'SelectShow'] = true; + scope[list.iterator + 'SearchSelectOpts'] = list.fields['status'].searchOptions; + scope[list.iterator + 'SearchFieldLabel'] = list.fields['status'].label.replace(/\/g,' '); + for (var opt in list.fields['status'].searchOptions) { + if (list.fields['status'].searchOptions[opt].value == $routeParams['status']) { + scope[list.iterator + 'SearchSelectValue'] = list.fields['status'].searchOptions[opt]; + break; + } + } + } scope.search(list.iterator); @@ -162,7 +174,8 @@ 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', 'JobStatusToolTip' + 'ProcessErrors','GetBasePath', 'LookUpInit', 'SubmitJob', 'FormatDate', 'Refresh', 'JobStatusToolTip', + 'Empty' ]; diff --git a/awx/ui/static/js/forms/Credentials.js b/awx/ui/static/js/forms/Credentials.js index 6262ff3e78..75d13fdd64 100644 --- a/awx/ui/static/js/forms/Credentials.js +++ b/awx/ui/static/js/forms/Credentials.js @@ -186,6 +186,26 @@ angular.module('CredentialFormDefinition', []) awPassMatch: true, associated: 'ssh_key_unlock' }, + "scm_key_unlock": { + label: 'Key Password', + type: 'password', + ngShow: "kind.value == 'scm'", + addRequired: false, + editRequired: false, + ngChange: "clearPWConfirm('scm_key_unlock_confirm')", + associated: 'scm_key_unlock_confirm', + ask: false, + clear: true + }, + "scm_key_unlock_confirm": { + label: 'Confirm Key Password', + type: 'password', + ngShow: "kind.value == 'scm'", + addRequired: false, + editRequired: false, + awPassMatch: true, + associated: 'scm_key_unlock' + }, "sudo_username": { label: 'Sudo Username', type: 'text', diff --git a/awx/ui/static/js/helpers/Groups.js b/awx/ui/static/js/helpers/Groups.js index 040be96606..fe180554c5 100644 --- a/awx/ui/static/js/helpers/Groups.js +++ b/awx/ui/static/js/helpers/Groups.js @@ -79,6 +79,49 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' } }]) + .factory('ViewUpdateStatus', [ 'Rest', 'ProcessErrors', 'GetBasePath', 'ShowUpdateStatus', 'Alert', + function(Rest, ProcessErrors, GetBasePath, ShowUpdateStatus, Alert) { + return function(params) { + + var scope = params.scope; + var id = params.group_id; + var found = false; + var group; + for (var i=0; i < scope.groups.length; i++) { + if (scope.groups[i].id == id) { + found = true; + group = scope.groups[i]; + } + } + if (found) { + if (group.summary_fields.inventory_source.source == "" || group.summary_fields.inventory_source.source == null) { + Alert('Missing Configuration', 'The selected group is not configured for inventory updates. ' + + 'You must first edit the group, provide Source settings, and then run an update.', 'alert-info'); + } + else if (group.summary_fields.inventory_source.status == "" || group.summary_fields.inventory_source.status == null || + group.summary_fields.inventory_source.status == "never updated") { + Alert('No Status Available', 'The inventory update process has not run for the selected group. Start the process by ' + + 'clicking the Update button.', 'alert-info'); + } + else { + Rest.setUrl(group.related.inventory_source); + Rest.get() + .success( function(data, status, headers, config) { + var url = (data.related.current_update) ? data.related.current_update : data.related.last_update; + ShowUpdateStatus({ group_name: data.summary_fields.group.name, + last_update: url }); + }) + .error( function(data, status, headers, config) { + ProcessErrors(scope, data, status, form, + { hdr: 'Error!', msg: 'Failed to retrieve inventory source: ' + group.related.inventory_source + + ' POST returned status: ' + status }); + }); + } + } + + } + }]) + .factory('HostsStatusMsg', [ function() { return function(params) { var active_failures = params.active_failures; @@ -278,11 +321,11 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' }]) .factory('InventoryStatus', [ '$rootScope', '$routeParams', 'Rest', 'Alert', 'ProcessErrors', 'GetBasePath', 'FormatDate', 'InventorySummary', - 'GenerateList', 'ClearScope', 'SearchInit', 'PaginateInit', 'Refresh', 'InventoryUpdate', 'GroupsEdit', 'ShowUpdateStatus', 'HelpDialog', - 'InventorySummaryHelp', 'BuildTree', 'ClickNode', 'HostsStatusMsg', 'UpdateStatusMsg', - function($rootScope, $routeParams, Rest, Alert, ProcessErrors, GetBasePath, FormatDate, InventorySummary, GenerateList, ClearScope, SearchInit, - PaginateInit, Refresh, InventoryUpdate, GroupsEdit, ShowUpdateStatus, HelpDialog, InventorySummaryHelp, BuildTree, ClickNode, - HostsStatusMsg, UpdateStatusMsg) { + 'GenerateList', 'ClearScope', 'SearchInit', 'PaginateInit', 'Refresh', 'InventoryUpdate', 'GroupsEdit', 'HelpDialog', + 'InventorySummaryHelp', 'BuildTree', 'ClickNode', 'HostsStatusMsg', 'UpdateStatusMsg', 'ViewUpdateStatus', + function($rootScope, $routeParams, Rest, Alert, ProcessErrors, GetBasePath, FormatDate, InventorySummary, GenerateList, ClearScope, + SearchInit, PaginateInit, Refresh, InventoryUpdate, GroupsEdit, HelpDialog, InventorySummaryHelp, BuildTree, ClickNode, + HostsStatusMsg, UpdateStatusMsg, ViewUpdateStatus) { return function(params) { //Build a summary of a given inventory @@ -373,41 +416,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' HelpDialog({ defn: InventorySummaryHelp }); } - scope.viewUpdateStatus = function(id) { - var found = false; - var group; - for (var i=0; i < scope.groups.length; i++) { - if (scope.groups[i].id == id) { - found = true; - group = scope.groups[i]; - } - } - if (found) { - if (group.summary_fields.inventory_source.source == "" || group.summary_fields.inventory_source.source == null) { - Alert('Missing Configuration', 'The selected group is not configured for inventory updates. ' + - 'You must first edit the group, provide Source settings, and then run an update.', 'alert-info'); - } - else if (group.summary_fields.inventory_source.status == "" || group.summary_fields.inventory_source.status == null || - group.summary_fields.inventory_source.status == "never updated") { - Alert('No Status Available', 'The inventory update process has not run for the selected group. Start the process by ' + - 'clicking the Update button.', 'alert-info'); - } - else { - Rest.setUrl(group.related.inventory_source); - Rest.get() - .success( function(data, status, headers, config) { - var url = (data.related.current_update) ? data.related.current_update : data.related.last_update; - ShowUpdateStatus({ group_name: data.summary_fields.group.name, - last_update: url }); - }) - .error( function(data, status, headers, config) { - ProcessErrors(scope, data, status, form, - { hdr: 'Error!', msg: 'Failed to retrieve inventory source: ' + group.related.inventory_source + - ' POST returned status: ' + status }); - }); - } - } - } + scope.viewUpdateStatus = function(group_id) { ViewUpdateStatus({ scope: scope, group_id: group_id }) }; // Click on group name scope.GroupsEdit = function(group_id) { diff --git a/awx/ui/static/js/lists/HomeGroups.js b/awx/ui/static/js/lists/HomeGroups.js new file mode 100644 index 0000000000..18eb328cad --- /dev/null +++ b/awx/ui/static/js/lists/HomeGroups.js @@ -0,0 +1,119 @@ +/********************************************* + * Copyright (c) 2013 AnsibleWorks, Inc. + * + * HomeGroups.js + * + * List view object for Group data model. Used + * on the home tab. + * + */ +angular.module('HomeGroupListDefinition', []) + .value( + 'HomeGroupList', { + + name: 'groups', + iterator: 'group', + editTitle: 'Groups', + index: true, + hover: true, + + fields: { + name: { + key: true, + label: 'Group', + ngClick: "\{\{ 'GroupsEdit(' + group.id + ')' \}\}", + columnClass: 'col-lg-3 col-md3 col-sm-2', + linkTo: "\{\{ '/#/inventories/' + group.inventory + '/groups/?name=' + group.name \}\}" + }, + inventory_name: { + label: 'Inventory', + sourceModel: 'inventory', + sourceField: 'name', + columnClass: 'col-lg-3 col-md3 col-sm-2', + linkTo: "\{\{ '/#/inventories/' + group.inventory \}\}" + }, + failed_hosts: { + label: 'Failed Hosts', + ngHref: "\{\{ group.failed_hosts_link \}\}", + badgeIcon: "\{\{ 'icon-failures-' + group.failed_hosts_class \}\}", + badgeNgHref: "\{\{ group.failed_hosts_link \}\}", + badgePlacement: 'left', + badgeToolTip: "\{\{ group.failed_hosts_tip \}\}", + badgeTipPlacement: 'top', + awToolTip: "\{\{ group.failed_hosts_tip \}\}", + dataPlacement: "top", + searchable: false, + excludeModal: true, + sortField: "hosts_with_active_failures" + }, + status: { + label: 'Status', + ngClick: "viewUpdateStatus(\{\{ group.id \}\})", + searchType: 'select', + badgeIcon: "\{\{ 'icon-cloud-' + group.status_badge_class \}\}", + badgeToolTip: "\{\{ group.status_badge_tooltip \}\}", + awToolTip: "\{\{ group.status_badge_tooltip \}\}", + dataPlacement: 'top', + badgeTipPlacement: 'top', + badgePlacement: 'left', + searchOptions: [ + { name: "failed", value: "failed" }, + { name: "never", value: "never updated" }, + { name: "n/a", value: "none" }, + { name: "successful", value: "successful" }, + { name: "updating", value: "updating" }], + sourceModel: 'inventory_source', + sourceField: 'status' + }, + last_updated: { + label: 'Last
Updated', + sourceModel: 'inventory_source', + sourceField: 'last_updated', + searchable: false, + nosort: false + }, + source: { + label: 'Source', + searchType: 'select', + searchOptions: [ + { name: "ec2", value: "ec2" }, + { name: "none", value: "" }, + { name: "rackspace", value: "rackspace" }], + sourceModel: 'inventory_source', + sourceField: 'source', + searchOnly: true + }, + has_external_source: { + label: 'Has external source?', + searchType: 'in', + searchValue: 'ec2,rackspace', + searchOnly: true, + sourceModel: 'inventory_source', + sourceField: 'source' + }, + has_active_failures: { + label: 'Has failed hosts?', + searchSingleValue: true, + searchType: 'boolean', + searchValue: 'true', + searchOnly: true + }, + last_update_failed: { + label: 'Update failed?', + searchType: 'select', + searchSingleValue: true, + searchValue: 'failed', + searchOnly: true, + sourceModel: 'inventory_source', + sourceField: 'status' + } + }, + + actions: { + + }, + + fieldActions: { + + } + }); diff --git a/awx/ui/static/js/lists/InventorySummary.js b/awx/ui/static/js/lists/InventorySummary.js index 046b1dcad4..fc680356f1 100644 --- a/awx/ui/static/js/lists/InventorySummary.js +++ b/awx/ui/static/js/lists/InventorySummary.js @@ -133,7 +133,8 @@ angular.module('InventorySummaryDefinition', []) mode: 'all', 'class': 'btn-xs btn-primary', awToolTip: "Refresh the page", - ngClick: "refresh()" + ngClick: "refresh()", + iconSize: 'large' } }, diff --git a/awx/ui/static/js/lists/JobEvents.js b/awx/ui/static/js/lists/JobEvents.js index 9f8bdd8093..0e9acaffc0 100644 --- a/awx/ui/static/js/lists/JobEvents.js +++ b/awx/ui/static/js/lists/JobEvents.js @@ -92,7 +92,8 @@ angular.module('JobEventsListDefinition', []) ngShow: "job_status == 'pending' || job_status == 'waiting' || job_status == 'running'", 'class': 'btn-xs btn-primary', awToolTip: "Refresh the page", - ngClick: "refresh()" + ngClick: "refresh()", + iconSize: 'large' } }, diff --git a/awx/ui/static/js/lists/JobHosts.js b/awx/ui/static/js/lists/JobHosts.js index 8784c14c18..4cfd73c2b7 100644 --- a/awx/ui/static/js/lists/JobHosts.js +++ b/awx/ui/static/js/lists/JobHosts.js @@ -126,7 +126,8 @@ angular.module('JobHostDefinition', []) ngShow: "host_id == null && (job_status == 'pending' || job_status == 'waiting' || job_status == 'running')", 'class': 'btn-xs btn-primary', awToolTip: "Refresh the page", - ngClick: "refresh()" + ngClick: "refresh()", + iconSize: 'large' } }, diff --git a/awx/ui/static/js/lists/Jobs.js b/awx/ui/static/js/lists/Jobs.js index 5ebd0e1a61..80ef8b7245 100644 --- a/awx/ui/static/js/lists/Jobs.js +++ b/awx/ui/static/js/lists/Jobs.js @@ -81,7 +81,8 @@ angular.module('JobsListDefinition', []) mode: 'all', 'class': 'btn-xs btn-primary', awToolTip: "Refresh the page", - ngClick: "refresh()" + ngClick: "refresh()", + iconSize: 'large' } }, diff --git a/awx/ui/static/js/lists/Projects.js b/awx/ui/static/js/lists/Projects.js index 517e4936eb..64d37e6c91 100644 --- a/awx/ui/static/js/lists/Projects.js +++ b/awx/ui/static/js/lists/Projects.js @@ -78,7 +78,8 @@ angular.module('ProjectsListDefinition', []) mode: 'all', 'class': 'btn-xs btn-primary', awToolTip: "Refresh the page", - ngClick: "refresh()" + ngClick: "refresh()", + iconSize: 'large' } }, diff --git a/awx/ui/static/js/lists/Streams.js b/awx/ui/static/js/lists/Streams.js new file mode 100644 index 0000000000..ff17ad1cb4 --- /dev/null +++ b/awx/ui/static/js/lists/Streams.js @@ -0,0 +1,62 @@ +/********************************************* + * Copyright (c) 2013 AnsibleWorks, Inc. + * + * Streams.js + * List view object for activity stream data model. + * + * + */ +angular.module('StreamListDefinition', []) + .value( + 'StreamList', { + + name: 'activities', + iterator: 'activity', + editTitle: 'Activity Stream', + selectInstructions: '', + index: false, + hover: true, + "class": "table-condensed", + + fields: { + event_time: { + key: true, + label: 'When' + }, + user: { + label: 'Who', + sourceModel: 'user', + sourceField: 'username' + }, + operation: { + label: 'Operation' + }, + description: { + label: 'Description' + } + }, + + actions: { + refresh: { + dataPlacement: 'top', + icon: "icon-refresh", + mode: 'all', + 'class': 'btn-xs btn-primary', + awToolTip: "Refresh the page", + ngClick: "refreshStream()", + iconSize: 'large' + }, + close: { + dataPlacement: 'top', + icon: "icon-arrow-left", + mode: 'all', + 'class': 'btn-xs btn-primary', + awToolTip: "Close Activity Stream view", + ngClick: "closeStream()", + iconSize: 'large' + } + }, + + fieldActions: { + } + }); \ No newline at end of file diff --git a/awx/ui/static/js/widgets/JobStatus.js b/awx/ui/static/js/widgets/JobStatus.js index e8d015ba3d..574ada224d 100644 --- a/awx/ui/static/js/widgets/JobStatus.js +++ b/awx/ui/static/js/widgets/JobStatus.js @@ -15,7 +15,7 @@ angular.module('JobStatusWidget', ['RestServices', 'Utilities']) var scope = $rootScope.$new(); var jobCount, jobFails, inventoryCount, inventoryFails, groupCount, groupFails, hostCount, hostFails; var counts = 0; - var expectedCounts = 8; + var expectedCounts = 6; var target = params.target; if (scope.removeCountReceived) { @@ -25,17 +25,21 @@ angular.module('JobStatusWidget', ['RestServices', 'Utilities']) var rowcount = 0; - function makeRow(label, count, fail) { + function makeRow(params) { var html = ''; + var label = params.label; + var link = params.link; + var fail_link = params.fail_link; + var count = params.count; + var fail = params.fail; html += "\n"; - html += "\n"; html += ""; - html += (fail > 0) ? "" + fail + "" : ""; + html += "" + fail + ""; html += "\n"; html += "" - html += (count > 0) ? "" + count + "" : ""; + html += "" + count + ""; html += "\n"; return html; } @@ -57,19 +61,33 @@ angular.module('JobStatusWidget', ['RestServices', 'Utilities']) html += "\n"; if (jobCount > 0) { - html += makeRow('Jobs', jobCount, jobFails); - rowcount++; - } - if (inventoryCount > 0) { - html += makeRow('Inventories', inventoryCount, inventoryFails); + html += makeRow({ + label: 'Jobs', + link: '/#/jobs', + count: jobCount, + fail: jobFails, + fail_link: '/#/jobs/?status=failed' + }); rowcount++; } if (groupCount > 0) { - html += makeRow('Groups', groupCount, groupFails); + html += makeRow({ + label: 'Groups', + link: '/#/home/groups', + count: groupCount, + fail: groupFails, + fail_link: '/#/home/groups/?status=failed' + }); rowcount++; } if (hostCount > 0) { - html += makeRow('Hosts', hostCount, hostFails); + html += makeRow({ + label: 'Hosts', + link: '#/home/hosts', + count: hostCount, + fail: hostFails, + fail_link: '/#/home/hosts/?status=failed' + }); rowcount++; } @@ -114,7 +132,7 @@ angular.module('JobStatusWidget', ['RestServices', 'Utilities']) { hdr: 'Error!', msg: 'Failed to get ' + url + '. GET status: ' + status }); }); - url = GetBasePath('inventory') + '?page=1'; + /*url = GetBasePath('inventory') + '?page=1'; Rest.setUrl(url); Rest.get() .success( function(data, status, headers, config) { @@ -136,7 +154,7 @@ angular.module('JobStatusWidget', ['RestServices', 'Utilities']) .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); diff --git a/awx/ui/static/js/widgets/Stream.js b/awx/ui/static/js/widgets/Stream.js new file mode 100644 index 0000000000..3210f6a3ca --- /dev/null +++ b/awx/ui/static/js/widgets/Stream.js @@ -0,0 +1,102 @@ +/********************************************* + * Copyright (c) 2013 AnsibleWorks, Inc. + * + * Stream.js + * + * Activity stream widget that can be called from anywhere + * + */ + +angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefinition', 'SearchHelper', 'PaginateHelper', + 'RefreshHelper', 'ListGenerator', 'StreamWidget']) + + .factory('ShowStream', [ function() { + return function() { + // Slide in the Stream widget + var stream = $('#stream-container'); + stream.css({ + position: 'absolute', + left: 0, + top: 0, + width: '100%', + 'min-height': '100%', + 'background-color': '#FFF' + }); + stream.show('slide', {'direction': 'left'}, {'duration': 500, 'queue': false }); + } + }]) + + .factory('HideStream', [ 'ClearScope', function(ClearScope) { + return function() { + // Remove the stream widget + var stream = $('#stream-container'); + stream.hide('slide', {'direction': 'left'}, {'duration': 500, 'queue': false }); + + // Completely destroy the container so we don't experience random flashes of it later. + // There was some sort weirdness with the tab 'show' causing the stream to slide in when + // a tab was clicked, after the stream had been hidden. Seemed like timing- wait long enough + // before clicking a tab, and it would not happen. + setTimeout( function() { + stream.detach(); + stream.empty(); + stream.unbind(); + }, 500); + } + }]) + + .factory('Stream', ['$rootScope', '$location', 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait', 'StreamList', 'SearchInit', + 'PaginateInit', 'GenerateList', 'FormatDate', 'ShowStream', 'HideStream', + function($rootScope, $location, Rest, GetBasePath, ProcessErrors, Wait, StreamList, SearchInit, PaginateInit, GenerateList, + FormatDate, ShowStream, HideStream) { + return function(params) { + + var list = StreamList; + var defaultUrl = $basePath + 'html/event_log.html/'; + var view = GenerateList; + + // Push the current page onto browser histor. If user clicks back button, restore current page without + // stream widget + // window.history.pushState({}, "AnsibleWorks AWX", $location.path()); + + // Add a container for the stream widget + $('#tab-content-container').append('
'); + + // Generate the list + var scope = view.inject(list, { + mode: 'edit', + id: 'stream-content', + breadCrumbs: true, + searchSize: 'col-lg-4' + }); + + scope.closeStream = function() { + HideStream(); + } + + scope.refreshStream = function() { + scope['activities'].splice(10,10); + //scope.search(list.iterator); + } + + if (scope.removePostRefresh) { + scope.removePostRefresh(); + } + scope.removePostRefresh = scope.$on('PostRefresh', function() { + for (var i=0; i < scope['activities'].length; i++) { + // Convert event_time date to local time zone + cDate = new Date(scope['activities'][i].event_time); + scope['activities'][i].event_time = FormatDate(cDate); + // Display username + scope['activities'][i].user = scope.activities[i].summary_fields.user.username; + } + ShowStream(); + }); + + // Initialize search and paginate pieces and load data + SearchInit({ scope: scope, set: list.name, list: list, url: defaultUrl }); + PaginateInit({ scope: scope, list: list, url: defaultUrl }); + scope.search(list.iterator); + + } + }]); + \ No newline at end of file diff --git a/awx/ui/static/less/ansible-ui.less b/awx/ui/static/less/ansible-ui.less index 3cfa4f9ec0..2746804dd4 100644 --- a/awx/ui/static/less/ansible-ui.less +++ b/awx/ui/static/less/ansible-ui.less @@ -487,10 +487,10 @@ legend { margin: 10px 0 0 0; } -.page-size { - height: 25px; - font-size: 10.5px; - line-height: normal; +select.page-size { + width: 65px; + height: 24px; + font-size: 10px; } .page-size-label { @@ -642,7 +642,7 @@ input[type="checkbox"].checkbox-no-label { text-align: right; button { - margin-left: 8px; + margin-left: 4px; } } @@ -1374,6 +1374,31 @@ tr td button i { } +/* Activity Stream Widget */ + + #stream-container { + display: none; + } + + #stream-content { + border: 1px solid @grey; + border-radius: 8px; + padding: 8px; + } + + /* + .activity-btn { + padding-left: 2px; + padding-right: 2px; + padding-bottom: 2px; + img { + width: 16px; + height: 16px; + } + } + */ + + /* Large desktop */ @media (min-width: 1200px) { diff --git a/awx/ui/static/lib/ansible/Utilities.js b/awx/ui/static/lib/ansible/Utilities.js index bf39e22598..99d579d1f9 100644 --- a/awx/ui/static/lib/ansible/Utilities.js +++ b/awx/ui/static/lib/ansible/Utilities.js @@ -109,14 +109,10 @@ angular.module('Utilities',['RestServices', 'Utilities']) } Alert(defaultMsg.hdr, msg); } - else if (status == 401 && data.detail && data.detail == 'Token is expired') { + else if ( (status == 401 && data.detail && data.detail == 'Token is expired') || + (status == 401 && data.detail && data.detail == 'Invalid token') ) { $rootScope.sessionTimer.expireSession(); - window.location = '/#/login'; //resetting location so that we drop search params - } - else if (status == 401 && data.detail && data.detail == 'Invalid token') { - // should this condition be treated as an expired session?? Yes, for now. - $rootScope.sessionTimer.expireSession(); - window.location = '/#/login'; //resetting location so that we drop search params + $location.url('/login'); } else if (data.non_field_errors) { Alert('Error!', data.non_field_errors); diff --git a/awx/ui/static/lib/ansible/generator-helpers.js b/awx/ui/static/lib/ansible/generator-helpers.js index 49d323504e..2e6b037cae 100644 --- a/awx/ui/static/lib/ansible/generator-helpers.js +++ b/awx/ui/static/lib/ansible/generator-helpers.js @@ -138,6 +138,7 @@ angular.module('GeneratorHelpers', ['GeneratorHelpers']) html += (btn.ngShow) ? Attr(btn, 'ngShow') : ""; html += (btn.ngHide) ? Attr(btn, 'ngHide') : ""; html += " >"; + html += (btn['img']) ? "" : ""; html += (btn['icon']) ? Attr(btn,'icon') : ""; html += (btn['awRefresh'] && !btn['icon']) ? " " : ""; html += (btn.label) ? " " + btn.label : ""; @@ -595,7 +596,7 @@ angular.module('GeneratorHelpers', ['GeneratorHelpers']) html += "