diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index 21416adfed..24070160cb 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -230,8 +230,8 @@ angular.module('ansible', [ otherwise({redirectTo: '/'}); }]) - .run(['$rootScope', 'CheckLicense', '$location', 'Authorization','LoadBasePaths', 'ViewLicense', - function($rootScope, CheckLicense, $location, Authorization, LoadBasePaths, ViewLicense) { + .run(['$cookieStore', '$rootScope', 'CheckLicense', '$location', 'Authorization','LoadBasePaths', 'ViewLicense', + function($cookieStore, $rootScope, CheckLicense, $location, Authorization, LoadBasePaths, ViewLicense) { LoadBasePaths(); @@ -239,8 +239,8 @@ angular.module('ansible', [ $rootScope.crumbCache = new Array(); $rootScope.$on("$routeChangeStart", function(event, next, current) { - // Evaluate the token on each navigation request. Redirect to login page when not valid - if (Authorization.isTokenValid() == false) { + // On each navigation request, check that the user is logged in + if (Authorization.isUserLoggedIn() == false) { if ( next.templateUrl != (urlPrefix + 'partials/login.html') ) { $location.path('/login'); } @@ -262,8 +262,10 @@ angular.module('ansible', [ } }); - if (! Authorization.isTokenValid() ) { + if (!Authorization.getToken()) { // When the app first loads, redirect to login page + $rootScope.sessionExpired = false; + $cookieStore.put('sessionExpired', false); $location.path('/login'); } diff --git a/awx/ui/static/js/config.js b/awx/ui/static/js/config.js index b25166ecf3..d0e9f37dbd 100644 --- a/awx/ui/static/js/config.js +++ b/awx/ui/static/js/config.js @@ -9,10 +9,7 @@ */ var $AnsibleConfig = -{ - session_timeout: 3600, // cookie expiration in seconds. session will expire after this many - // seconds of inactivity. - +{ tooltip_delay: {show: 500, hide: 100}, // Default number of milliseconds to delay displaying/hiding tooltips debug_mode: true, // Enable console logging messages diff --git a/awx/ui/static/js/controllers/Authentication.js b/awx/ui/static/js/controllers/Authentication.js index dabfe32ec8..3f0851b790 100644 --- a/awx/ui/static/js/controllers/Authentication.js +++ b/awx/ui/static/js/controllers/Authentication.js @@ -10,7 +10,7 @@ 'use strict'; -function Authenticate($window, $scope, $rootScope, $location, Authorization, ToggleClass, Alert) +function Authenticate($cookieStore, $window, $scope, $rootScope, $location, Authorization, ToggleClass, Alert) { var setLoginFocus = function() { $('#login-username').focus(); @@ -18,6 +18,7 @@ function Authenticate($window, $scope, $rootScope, $location, Authorization, Tog // Display the login dialog $('#login-modal').modal({ show: true, keyboard: false, backdrop: 'static' }); + // Set focus to username field $('#login-modal').on('shown.bs.modal', function() { setLoginFocus(); @@ -36,18 +37,8 @@ function Authenticate($window, $scope, $rootScope, $location, Authorization, Tog Authorization.logout(); } - scope.sessionTimeout = ($AnsibleConfig.session_timeout / 60).toFixed(2); - - if ($rootScope.userLoggedIn) { - // If we're logged in, check for session timeout - scope.sessionExpired = Authorization.didSessionExpire(); - } - else { - scope.sessionExpired = false; - } - - $rootScope.userLoggedIn = false; //hide the logout link. if you got here, you're logged out. - //gets set back to true by Authorization.setToken(). + $rootScope.userLoggedIn = false; //hide the logout link. if you got here, you're logged out. + $cookieStore.put('userLoggedIn', false); //gets set back to true by Authorization.setToken(). $('#login-password').bind('keypress', function(e) { var code = (e.keyCode ? e.keyCode : e.which); @@ -73,16 +64,7 @@ function Authenticate($window, $scope, $rootScope, $location, Authorization, Tog .success( function(data, status, headers, config) { $('#login-modal').modal('hide'); token = data.token; - Authorization.setToken(data.token); - scope.reset(); - - // Force request to /organizations to query with the correct token -in the event a new user - // has logged in. - var today = new Date(); - today.setTime(today.getTime() + ($AnsibleConfig.session_timeout * 1000)); - $rootScope.token = token; - $rootScope.userLoggedIn = true; - $rootScope.token_expire = today.getTime(); + Authorization.setToken(data.token, data.expires); // Get all the profile/access info regarding the logged in user Authorization.getUser() @@ -126,5 +108,5 @@ function Authenticate($window, $scope, $rootScope, $location, Authorization, Tog } } -Authenticate.$inject = ['$window', '$scope', '$rootScope', '$location', 'Authorization', 'ToggleClass', 'Alert']; +Authenticate.$inject = ['$cookieStore', '$window', '$scope', '$rootScope', '$location', 'Authorization', 'ToggleClass', 'Alert']; diff --git a/awx/ui/static/js/controllers/Credentials.js b/awx/ui/static/js/controllers/Credentials.js index c9039bfbd0..91740aff4c 100644 --- a/awx/ui/static/js/controllers/Credentials.js +++ b/awx/ui/static/js/controllers/Credentials.js @@ -29,6 +29,9 @@ function CredentialsList ($scope, $rootScope, $location, $log, $routeParams, Res SelectionInit({ scope: scope, list: list, url: url, returnToCaller: 1 }); + if (scope.PostRefreshRemove) { + scope.PostRefreshRemove(); + } scope.PostRefershRemove = scope.$on('PostRefresh', function() { // After a refresh, populate the organization name on each row for(var i=0; i < scope.credentials.length; i++) { diff --git a/awx/ui/static/js/controllers/Organizations.js b/awx/ui/static/js/controllers/Organizations.js index 0d5ed2e48c..10e85a2bbd 100644 --- a/awx/ui/static/js/controllers/Organizations.js +++ b/awx/ui/static/js/controllers/Organizations.js @@ -176,7 +176,7 @@ function OrganizationsEdit ($scope, $rootScope, $compile, $location, $log, $rout }) .error( function(data, status, headers, config) { ProcessErrors(scope, data, status, form, - { hdr: 'Error!', msg: 'Failed to retrieve organization: ' + $routeParams.id + '. GET status: ' + status }); + { hdr: 'Error!', msg: 'Failed to retrieve organization: ' + $routeParams.id + '. GET status: ' + status }); }); @@ -194,7 +194,7 @@ function OrganizationsEdit ($scope, $rootScope, $compile, $location, $log, $rout }) .error( function(data, status, headers, config) { ProcessErrors(scope, data, status, OrganizationForm, - { hdr: 'Error!', msg: 'Failed to update organization: ' + id + '. PUT status: ' + status }); + { hdr: 'Error!', msg: 'Failed to update organization: ' + id + '. PUT status: ' + status }); }); }; diff --git a/awx/ui/static/js/forms/JobTemplates.js b/awx/ui/static/js/forms/JobTemplates.js index d94c04a956..eab5635b21 100644 --- a/awx/ui/static/js/forms/JobTemplates.js +++ b/awx/ui/static/js/forms/JobTemplates.js @@ -54,6 +54,7 @@ angular.module('JobTemplateFormDefinition', []) addRequired: true, editRequired: true, ngClick: 'lookUpInventory()', + awRequiredWhen: {variable: "inventoryrequired", init: "true" }, column: 1 }, project: { @@ -64,6 +65,7 @@ angular.module('JobTemplateFormDefinition', []) addRequired: true, editRequired: true, ngClick: 'lookUpProject()', + awRequiredWhen: {variable: "projectrequired", init: "true" }, column: 1 }, playbook: { @@ -73,6 +75,7 @@ angular.module('JobTemplateFormDefinition', []) id: 'playbook-select', addRequired: true, editRequired: true, + awRequiredWhen: {variable: "playbookrequired", init: "true" }, column: 1 }, credential: { diff --git a/awx/ui/static/js/helpers/refresh-related.js b/awx/ui/static/js/helpers/refresh-related.js index c6963cd17f..1b6122971e 100644 --- a/awx/ui/static/js/helpers/refresh-related.js +++ b/awx/ui/static/js/helpers/refresh-related.js @@ -15,7 +15,7 @@ */ angular.module('RefreshRelatedHelper', ['RestServices', 'Utilities']) - .factory('RefreshRelated', ['Alert', 'Rest', function(Alert, Rest) { + .factory('RefreshRelated', ['ProcessErrors', 'Rest', function(ProcessErrors, Rest) { return function(params) { var scope = params.scope; @@ -40,7 +40,8 @@ angular.module('RefreshRelatedHelper', ['RestServices', 'Utilities']) }) .error ( function(data, status, headers, config) { scope[iterator + 'SearchSpin'] = true; - Alert('Error!', 'Failed to retrieve related set: ' + set + '. GET returned status: ' + status); + ProcessErrors(scope, data, status, null, + { hdr: 'Error!', msg: 'Failed to retrieve ' + set + '. GET returned status: ' + status }); }); } }]); \ No newline at end of file diff --git a/awx/ui/static/js/helpers/refresh.js b/awx/ui/static/js/helpers/refresh.js index 8c6fc0e4ff..6983678a51 100644 --- a/awx/ui/static/js/helpers/refresh.js +++ b/awx/ui/static/js/helpers/refresh.js @@ -15,7 +15,7 @@ */ angular.module('RefreshHelper', ['RestServices', 'Utilities']) - .factory('Refresh', ['Alert', 'Rest', function(Alert, Rest) { + .factory('Refresh', ['ProcessErrors', 'Rest', function(ProcessErrors, Rest) { return function(params) { var scope = params.scope; @@ -37,7 +37,8 @@ angular.module('RefreshHelper', ['RestServices', 'Utilities']) }) .error ( function(data, status, headers, config) { scope[iterator + 'SearchSpin'] = false; - Alert('Error!', 'Failed to retrieve ' + set + '. GET returned status: ' + status); + ProcessErrors(scope, data, status, null, + { hdr: 'Error!', msg: 'Failed to retrieve ' + set + '. GET returned status: ' + status }); }); } }]); \ No newline at end of file diff --git a/awx/ui/static/js/lists/JobHosts.js b/awx/ui/static/js/lists/JobHosts.js index 99426aba5a..6db18dbc3c 100644 --- a/awx/ui/static/js/lists/JobHosts.js +++ b/awx/ui/static/js/lists/JobHosts.js @@ -13,11 +13,11 @@ angular.module('JobHostDefinition', []) name: 'jobhosts', iterator: 'jobhost', editTitle: 'Job Host Summary', - index: true, + indexShow: 'host_id == null', hover: true, fields: { - job: { + id: { label: 'Job ID', ngClick: "showJob(\{\{ jobhost.job \}\})", columnShow: 'host_id !== null', diff --git a/awx/ui/static/lib/ansible/authenticate.js b/awx/ui/static/lib/ansible/AuthService.js similarity index 60% rename from awx/ui/static/lib/ansible/authenticate.js rename to awx/ui/static/lib/ansible/AuthService.js index ea3d87c990..c973a3adff 100644 --- a/awx/ui/static/lib/ansible/authenticate.js +++ b/awx/ui/static/lib/ansible/AuthService.js @@ -1,67 +1,45 @@ /********************************************* * Copyright (c) 2013 AnsibleWorks, Inc. * - * User authentication functions + * AuthService.js * + * User authentication functions */ -angular.module('AuthService', ['ngCookies']) - .factory('Authorization', ['$http', '$rootScope', '$location', '$cookieStore', function($http, $rootScope, $location, $cookieStore) { +angular.module('AuthService', ['ngCookies', 'Utilities']) + .factory('Authorization', ['$http', '$rootScope', '$location', '$cookieStore', 'GetBasePath', + function($http, $rootScope, $location, $cookieStore, GetBasePath) { return { - setToken: function(token) { + setToken: function(token, expires) { // set the session cookie - var today = new Date(); - today.setTime(today.getTime() + ($AnsibleConfig.session_timeout * 1000)); $cookieStore.remove('token'); - $cookieStore.remove('token_expire'); + $cookieStore.remove('token_expires'); + $cookieStore.remove('userLoggedIn'); $cookieStore.put('token', token); - $cookieStore.put('token_expire', today.getTime()); + $cookieStore.put('token_expires', expires); + $cookieStore.put('userLoggedIn', true); + $cookieStore.put('sessionExpired', false); $rootScope.token = token; $rootScope.userLoggedIn = true; - $rootScope.token_expire = today.getTime(); + $rootScope.token_expires = expires; + $rootScope.sessionExpired = false; }, - isTokenValid: function() { - // check if token exists and is not expired - var response = false; - var token = ($rootScope.token) ? $rootScope.token : $cookieStore.get('token'); - var token_expire = ($rootScope.token_expire) ? $rootScope.token_expire : $cookieStore.get('token_expire'); - if (token && token_expire) { - var exp = new Date(token_expire); - var today = new Date(); - if (today < exp) { - this.setToken(token); //push expiration into the future while user is active - response = true; - } + isUserLoggedIn: function() { + if ($rootScope.userLoggedIn == undefined) { + // Browser refresh may have occurred + $rootScope.userLoggedIn = $cookieStore.get('userLoggedIn'); + $rootScope.sessionExpired = $cookieStore.get('sessionExpired'); } - return response; + return $rootScope.userLoggedIn; }, - didSessionExpire: function() { - // use only to test why user was sent to login page. - var response = false; - var token_expire = ($rootScope.token_expire) ? $rootScope.token_expire : $cookieStore.get('token_expire'); - if (token_expire) { - var exp = new Date(token_expire); - var today = new Date(); - if (exp < today) { - response = true; - } - } - return response; - }, - getToken: function() { - if ( this.isTokenValid() ) { - return ($rootScope.token) ? $rootScope.token : $cookieStore.get('token'); - } - else { - $location.path('/login'); - } - }, + return ($rootScope.token) ? $rootScope.token : $cookieStore.get('token'); + }, retrieveToken: function(username, password) { - return $http({ method: 'POST', url: '/api/v1/authtoken/', + return $http({ method: 'POST', url: GetBasePath('authtoken'), data: {"username": username, "password": password} }); }, @@ -70,15 +48,17 @@ angular.module('AuthService', ['ngCookies']) // should prevent content flash from the prior user. var scope = angular.element(document.getElementById('main-view')).scope(); scope.$destroy(); - $rootScope.$destroy(); - + $rootScope.$destroy(); $cookieStore.remove('accordions'); $cookieStore.remove('token'); $cookieStore.remove('token_expire'); $cookieStore.remove('current_user'); + $cookieStore.put('userLoggedIn', false); + $cookieStore.put('sessionExpired', false); $rootScope.current_user = {}; $rootScope.license_tested = undefined; $rootScope.userLoggedIn = false; + $rootScope.sessionExpired = false; $rootScope.token = null; $rootScope.token_expire = new Date(1970, 0, 1, 0, 0, 0, 0); }, @@ -86,7 +66,7 @@ angular.module('AuthService', ['ngCookies']) getLicense: function() { return $http({ method: 'GET', - url: '/api/v1/config/', + url: GetBasePath('config'), headers: { 'Authorization': 'Token ' + this.getToken() } }); }, diff --git a/awx/ui/static/lib/ansible/rest-services.js b/awx/ui/static/lib/ansible/RestServices.js similarity index 100% rename from awx/ui/static/lib/ansible/rest-services.js rename to awx/ui/static/lib/ansible/RestServices.js diff --git a/awx/ui/static/lib/ansible/utilities.js b/awx/ui/static/lib/ansible/Utilities.js similarity index 94% rename from awx/ui/static/lib/ansible/utilities.js rename to awx/ui/static/lib/ansible/Utilities.js index 494e291b7a..bf419b193e 100644 --- a/awx/ui/static/lib/ansible/utilities.js +++ b/awx/ui/static/lib/ansible/Utilities.js @@ -78,8 +78,14 @@ angular.module('Utilities',[]) } }]) - .factory('ProcessErrors', ['$log', 'Alert', function($log, Alert) { + .factory('ProcessErrors', ['$cookieStore', '$log', '$location', '$rootScope', 'Alert', + function($cookieStore, $log, $location, $rootScope, Alert) { return function(scope, data, status, form, defaultMsg) { + if ($AnsibleConfig.debug_mode && console) { + console.log('Debug status: ' + status); + console.log('Debug data: '); + console.log(data); + } if (status == 403) { var msg = 'The API responded with a 403 Access Denied error. '; if (data['detail']) { @@ -90,6 +96,11 @@ angular.module('Utilities',[]) } Alert(defaultMsg.hdr, msg); } + else if (status == 401 && data.detail && data.detail == 'Token is expired') { + $rootScope.sessionExpired = true; + $cookieStore.put('sessionExpired', true); + $location.path('/login'); + } else if (data.non_field_errors) { Alert('Error!', data.non_field_errors); } @@ -250,4 +261,3 @@ angular.module('Utilities',[]) } } }]); - \ No newline at end of file diff --git a/awx/ui/templates/ui/index.html b/awx/ui/templates/ui/index.html index b00f390717..a9e7123245 100644 --- a/awx/ui/templates/ui/index.html +++ b/awx/ui/templates/ui/index.html @@ -25,9 +25,9 @@ {% else %} - - - + + +