Promise-ify the call to /config and reduce number

of calls to 1 per session. We'll also pull the config/license info from memory instead of local storage
This commit is contained in:
Jared Tabor 2016-05-16 09:24:49 -04:00
parent 05b3920837
commit 8b271c4caf
15 changed files with 388 additions and 100 deletions

View File

@ -68,6 +68,7 @@ import './shared/directives';
import './shared/filters';
import './shared/Socket';
import './shared/features/main';
import config from './shared/config/main';
import './login/authenticationServices/pendo/ng-pendo';
import footer from './footer/main';
import scheduler from './scheduler/main';
@ -109,6 +110,7 @@ var tower = angular.module('Tower', [
JobTemplates.name,
portalMode.name,
search.name,
config.name,
'ngToast',
'templates',
'Utilities',
@ -514,10 +516,15 @@ var tower = angular.module('Tower', [
}]);
}])
.run(['$q', '$compile', '$cookieStore', '$rootScope', '$log', 'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'Timer', 'ClearScope', 'Socket',
'LoadConfig', 'Store', 'ShowSocketHelp', 'pendoService', 'Prompt', 'Rest', 'Wait', 'ProcessErrors', '$state', 'GetBasePath',
function ($q, $compile, $cookieStore, $rootScope, $log, CheckLicense, $location, Authorization, LoadBasePaths, Timer, ClearScope, Socket,
LoadConfig, Store, ShowSocketHelp, pendoService, Prompt, Rest, Wait, ProcessErrors, $state, GetBasePath) {
.run(['$q', '$compile', '$cookieStore', '$rootScope', '$log',
'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'Timer',
'ClearScope', 'Socket', 'LoadConfig', 'Store',
'ShowSocketHelp', 'pendoService', 'Prompt', 'Rest', 'Wait',
'ProcessErrors', '$state', 'GetBasePath', 'ConfigService',
function ($q, $compile, $cookieStore, $rootScope, $log, CheckLicense,
$location, Authorization, LoadBasePaths, Timer, ClearScope, Socket,
LoadConfig, Store, ShowSocketHelp, pendoService, Prompt, Rest, Wait,
ProcessErrors, $state, GetBasePath, ConfigService) {
var sock;
$rootScope.addPermission = function (scope) {
$compile("<add-permissions class='AddPermissions'></add-permissions>")(scope);
@ -585,11 +592,11 @@ var tower = angular.module('Tower', [
Prompt({
hdr: `Remove role`,
body: `
<div class="Prompt-bodyQuery">
Confirm the removal of the ${roleType}
<span class="Prompt-emphasis"> ${roleName} </span>
role associated with ${userName}.
</div>
<div class="Prompt-bodyQuery">
Confirm the removal of the ${roleType}
<span class="Prompt-emphasis"> ${roleName} </span>
role associated with ${userName}.
</div>
`,
action: action,
actionText: 'REMOVE'
@ -615,11 +622,11 @@ var tower = angular.module('Tower', [
Prompt({
hdr: `Remove role`,
body: `
<div class="Prompt-bodyQuery">
Confirm the removal of the ${roleType}
<span class="Prompt-emphasis"> ${roleName} </span>
role associated with the ${teamName} team.
</div>
<div class="Prompt-bodyQuery">
Confirm the removal of the ${roleType}
<span class="Prompt-emphasis"> ${roleName} </span>
role associated with the ${teamName} team.
</div>
`,
action: action,
actionText: 'REMOVE'
@ -760,9 +767,7 @@ var tower = angular.module('Tower', [
$rootScope.$on("$stateChangeStart", function (event, next, nextParams, prev) {
if (next.name !== 'signOut'){
CheckLicense.notify();
}
$rootScope.$broadcast("closePermissionsModal");
$rootScope.$broadcast("closeUsersModal");
// this line removes the query params attached to a route
@ -813,15 +818,19 @@ var tower = angular.module('Tower', [
if ($rootScope.current_user === undefined || $rootScope.current_user === null) {
Authorization.restoreUserInfo(); //user must have hit browser refresh
}
if (next && (next.name !== "signIn" && next.name !== "signOut" && next.name !== "license")) {
// if not headed to /login or /logout, then check the license
CheckLicense.test(event);
}
}
activateTab();
});
$rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState) {
// catch license expiration notifications immediately after user logs in, redirect
if (fromState.name === 'signIn'){
CheckLicense.notify();
}
// if (fromState.name === 'signIn'){
// CheckLicense.notify();
// }
if(fromState.name === 'license' && toParams.hasOwnProperty('licenseMissing')){
$rootScope.licenseMissing = toParams.licenseMissing;
@ -868,8 +877,10 @@ var tower = angular.module('Tower', [
Timer.init().then(function(timer){
$rootScope.sessionTimer = timer;
$rootScope.$emit('OpenSocket');
pendoService.issuePendoIdentity();
CheckLicense.notify();
ConfigService.getConfig().then(function(){
pendoService.issuePendoIdentity();
CheckLicense.test(event);
});
});
}
}
@ -904,7 +915,7 @@ var tower = angular.module('Tower', [
// create a promise that will resolve state $AnsibleConfig is loaded
$rootScope.loginConfig = $q.defer();
}
$rootScope.licenseMissing = true;
//the authorization controller redirects to the home page automatcially if there is no last path defined. in order to override
// this, set the last path to /portal for instances where portal is visited for the first time.
$rootScope.lastPath = ($location.path() === "/portal") ? 'portal' : undefined;

View File

@ -1,5 +1,5 @@
<div id="bread_crumb" class="BreadCrumb" ng-class="{'is-loggedOut' : !$root.current_user.username}">
<div ng-if="!licenseMissing" ncy-breadcrumb></div>
<div ng-hide="licenseMissing" ncy-breadcrumb></div>
<div class="BreadCrumb-menuLink"
id="bread_crumb_activity_stream"
aw-tool-tip="View Activity Stream"

View File

@ -6,21 +6,15 @@
export default
['$state', '$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', '$q',
function($state, $rootScope, Rest, GetBasePath, ProcessErrors, $q){
'Store', 'ConfigService',
function($state, $rootScope, Rest, GetBasePath, ProcessErrors, $q, Store,
ConfigService){
return {
get: function() {
var defaultUrl = GetBasePath('config');
Rest.setUrl(defaultUrl);
return Rest.get()
.success(function(res){
$rootScope.license_tested = true;
return res;
})
.error(function(res, status){
ProcessErrors($rootScope, res, status, null, {hdr: 'Error!',
msg: 'Call to '+ defaultUrl + ' failed. Return status: '+ status});
});
var config = ConfigService.get();
return config.license_info;
},
post: function(license, eula){
var defaultUrl = GetBasePath('config');
Rest.setUrl(defaultUrl);
@ -51,27 +45,37 @@ export default
}
return true;
},
notify: function(){
var deferred = $q.defer(),
self = this;
if($rootScope.license_tested !== true){
this.get()
.then(function(res){
if(self.valid(res.data.license_info) === false) {
$rootScope.licenseMissing = true;
deferred.resolve();
$state.go('license');
}
else {
$rootScope.licenseMissing = false;
}
});
test: function(event){
var //deferred = $q.defer(),
license = this.get();
if(license === null || !$rootScope.license_tested){
if(this.valid(license) === false) {
$rootScope.licenseMissing = true;
if(event){
event.preventDefault();
}
$state.go('license');
// deferred.reject();
}
else {
$rootScope.licenseMissing = false;
// deferred.resolve();
}
}
else{
deferred.resolve();
else if(this.valid(license) === false) {
$rootScope.licenseMissing = true;
$state.transitionTo('license');
if(event){
event.preventDefault();
}
// deferred.reject(license);
}
return deferred.promise;
else {
$rootScope.licenseMissing = false;
// deferred.resolve(license);
}
return;
// return deferred.promise;
}
};

View File

@ -0,0 +1,201 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:License
* @description Routines for checking and reporting license status
* CheckLicense.test() is called in app.js, in line 532, which is when the license is checked. The license information is
* stored in local storage using 'Store()'.
*
*
*
*
*/
// import '../forms';
export default
['$q', '$rootScope', '$compile', 'CreateDialog', 'Store',
'GenerateForm', 'TextareaResize', 'ToJSON', 'GetBasePath',
'Rest', 'ProcessErrors', 'Alert', 'IsAdmin', '$state', 'pendoService',
'Authorization', 'Wait',
function($q, $rootScope, $compile, CreateDialog, Store, GenerateForm,
TextareaResize, ToJSON, GetBasePath, Rest, ProcessErrors, Alert, IsAdmin, $state,
pendoService, Authorization, Wait) {
return {
getRemainingDays: function(time_remaining) {
// assumes time_remaining will be in seconds
var tr = parseInt(time_remaining, 10);
return Math.floor(tr / 86400);
},
shouldNotify: function(license) {
if (license && typeof license === 'object' && Object.keys(license).length > 0) {
// we have a license object
if (!license.valid_key) {
// missing valid key
return true;
}
else if (license.free_instances <= 0) {
// host count exceeded
return true;
}
else if (this.getRemainingDays(license.time_remaining) < 15) {
// below 15 days remaining on license
return true;
}
return false;
} else {
// missing license object
return true;
}
},
isAdmin: function() {
return IsAdmin();
},
post: function(license, eula){
var defaultUrl = GetBasePath('config');
Rest.setUrl(defaultUrl);
var data = license;
data.eula_accepted = eula;
return Rest.post(JSON.stringify(data))
.success(function(res){
return res;
})
.error(function(res, status){
ProcessErrors($rootScope, res, status, null, {hdr: 'Error!',
msg: 'Call to '+ defaultUrl + ' failed. Return status: '+ status});
});
},
postLicense: function(license_key, in_scope) {
var url = GetBasePath('config'),
self = this,
json_data, scope;
scope = (in_scope) ? in_scope : self.scope;
json_data = ToJSON('json', license_key);
json_data.eula_accepted = scope.eula_agreement;
if (typeof json_data === 'object' && Object.keys(json_data).length > 0) {
Rest.setUrl(url);
Rest.post(json_data)
.success(function (response) {
response.license_info = response;
Alert('License Accepted', 'The Ansible Tower license was updated. To review or update the license, choose View License from the Setup menu.','alert-info');
$rootScope.features = undefined;
Authorization.getLicense()
.success(function (data) {
Authorization.setLicense(data);
pendoService.issuePendoIdentity();
Wait("stop");
$state.go('home');
})
.error(function () {
Wait('stop');
Alert('Error', 'Failed to access license information. GET returned status: ' + status, 'alert-danger',
$state.go('signOut'));
});
})
.catch(function (response) {
scope.license_json_api_error = "A valid license key in JSON format is required";
ProcessErrors(scope, response.data, response.status, null, { hdr: 'Error!',
msg: 'Failed to update license. POST returned: ' + response.status
});
});
} else {
scope.license_json_api_error = "A valid license key in JSON format is required";
}
},
test: function() {
var license = Store('license'),
self = this;
// scope;
var getLicense = function() {
var deferred = $q.defer();
if (license === null) {
Rest.setUrl(GetBasePath('config'));
return Rest.get()
.then(function (data) {
license = data.data.license_info;
deferred.resolve();
return deferred.promise;
}, function () {
deferred.resolve();
return deferred.promise;
});
} else {
deferred.resolve(license);
return deferred.promise;
}
};
var promise = getLicense();
promise.then(function() {
// self.scope = $rootScope.$new();
// scope = self.scope;
if (license && typeof license === 'object' && Object.keys(license).length > 0) {
// if (license.tested) {
// return true;
// }
license.tested = true;
Store('license',license); //update with tested flag
}
// Don't do anything when the license is valid
if (!self.shouldNotify(license)) {
$rootScope.licenseMissing = false;
return true; // if the license is valid it would exit 'test' here, otherwise it moves on to making the modal for the license
}
$rootScope.licenseMissing = true;
$state.go('license');
});
},
// Checks current license validity
// Intended to for runtime or pre-state checks
// Returns false if invalid
valid: function(license) {
if (!license.valid_key){
return false;
}
else if (license.free_instances <= 0){
return false;
}
// notify if less than 15 days remaining
else if (license.time_remaining / 1000 / 60 / 60 / 24 > 15){
return false;
}
return true;
},
GetLicense: function() {
// Retrieve license detail
var //self = this,
// scope = (inScope) ? inScope : self.scope,
url = GetBasePath('config');
Rest.setUrl(url);
return Rest.get()
.success(function(res){
$rootScope.license_tested = true;
return res;
})
.error(function (data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Failed to retrieve license. GET status: ' + status
});
});
}
};
}];

View File

@ -6,7 +6,6 @@
@import "awx/ui/client/src/shared/branding/colors.default.less";
@import "awx/ui/client/src/shared/layouts/one-plus-two.less";
.License-container{
.OnePlusTwo-container;
}

View File

@ -74,23 +74,22 @@ export default
var calcExpiresOn = function(days){
// calculate the expiration date of the license
days = parseInt(days);
return moment().add(days, 'days').calendar();
};
var init = function(){
$scope.fileName = "No file selected.";
$scope.title = $rootScope.licenseMissing ? "Tower License" : "License Management";
Wait('start');
CheckLicense.get()
.then(function(res){
$scope.license = res.data;
$scope.license.version = res.data.version.split('-')[0];
var license = CheckLicense.get();
//.then(function(res){
$scope.license = license;
$scope.license.version = license.version.split('-')[0];
$scope.time = {};
$scope.time.remaining = calcDaysRemaining($scope.license.license_info.time_remaining);
$scope.time.expiresOn = calcExpiresOn($scope.time.remaining);
$scope.valid = CheckLicense.valid($scope.license.license_info);
Wait('stop');
});
// });
};
var reset = function(){
document.getElementById('License-form').reset();

View File

@ -1,6 +1,6 @@
<div class="License-container"
ng-class="{'License-container--missing': licenseMissing}">
<div class="License-details" ng-if="!licenseMissing">
<div class="License-details" ng-hide="licenseMissing">
<div class="Panel">
<div class="License-titleText">Details</div>
<div class="License-fields">

View File

@ -16,4 +16,4 @@ export default
.factory('CheckLicense', CheckLicense)
.run(['$stateExtender', function($stateExtender) {
$stateExtender.addState(route);
}]);
}]);

View File

@ -98,6 +98,7 @@ export default
$rootScope.license_tested = undefined;
$rootScope.userLoggedIn = false;
$rootScope.sessionExpired = false;
$rootScope.licenseMissing = true;
$rootScope.token = null;
$rootScope.token_expires = null;
$rootScope.login_username = null;
@ -127,7 +128,7 @@ export default
Store('license', license);
$rootScope.features = Store('license').features;
},
licenseTested: function () {
var license, result;
if ($rootScope.license_tested !== undefined) {

View File

@ -7,9 +7,9 @@
export default
[ '$rootScope', '$pendolytics', 'Rest', 'GetBasePath', 'ProcessErrors', '$q',
'Store', '$log',
'ConfigService', '$log',
function ($rootScope, $pendolytics, Rest, GetBasePath, ProcessErrors, $q,
Store, $log) {
ConfigService, $log) {
return {
setPendoOptions: function (config) {
var tower_version = config.version.split('-')[0],
@ -93,7 +93,7 @@ export default
},
getConfig: function () {
var config = Store('license'),
var config = ConfigService.get(),
deferred = $q.defer();
if(_.isEmpty(config)){
var url = GetBasePath('config');

View File

@ -2,8 +2,8 @@
<!-- Menu logo item -->
<a id="main_menu_logo"
href="/#/"
class="MainMenu-logo"
ng-if="!licenseMissing"
class="MainMenu-logo ng-cloak"
ng-hide="licenseMissing"
ng-class="{'is-loggedOut' : !$root.current_user.username}">
<img class="MainMenu-logoImage"
ng-src="/static/assets/tower-logo-header.svg">
@ -86,10 +86,10 @@
</span>
<!-- Non-mobile menu items -->
<a class="MainMenu-item MainMenu-item--notMobile MainMenu-item--left"
<a class="MainMenu-item MainMenu-item--notMobile MainMenu-item--left ng-cloak"
id="main_menu_projects_link"
href="/#/projects"
ng-if="!licenseMissing"
ng-hide="licenseMissing"
ng-class="{'is-currentRoute' : isCurrentState('projects'), 'is-loggedOut' : !$root.current_user.username}">
<span class="MainMenu-itemText">
PROJECTS
@ -98,7 +98,7 @@
<a class="MainMenu-item MainMenu-item--notMobile MainMenu-item--left"
id="main_menu_inventories_link"
href="/#/inventories"
ng-if="!licenseMissing"
ng-hide="licenseMissing"
ng-class="{'is-currentRoute' : isCurrentState('inventories'), 'is-loggedOut' : !$root.current_user.username}">
<span class="MainMenu-itemText">
INVENTORIES
@ -107,7 +107,7 @@
<a class="MainMenu-item MainMenu-item--notMobile MainMenu-item--left"
id="main_menu_job_templates_link"
href="/#/job_templates"
ng-if="!licenseMissing"
ng-hide="licenseMissing"
ng-class="{'is-currentRoute' : isCurrentState('jobTemplates'), 'is-loggedOut' : !$root.current_user.username}">
<span class="MainMenu-itemText">
JOB TEMPLATES
@ -116,7 +116,7 @@
<a class="MainMenu-item MainMenu-item--notMobile MainMenu-item--left MainMenu-item--lastLeft"
id="main_menu_jobs_link"
href="/#/jobs"
ng-if="!licenseMissing"
ng-hide="licenseMissing"
ng-class="{'is-currentRoute' : isCurrentState('jobs'), 'is-loggedOut' : !$root.current_user.username}">
<span class="MainMenu-itemText">
JOBS
@ -125,7 +125,7 @@
<a class="MainMenu-item MainMenu-item--notMobile MainMenu-item--user MainMenu-item--right"
id="main_menu_current_user_link"
ng-href="/#/users/{{ $root.current_user.id }}"
ng-if="!licenseMissing"
ng-hide="licenseMissing"
ng-class="{'is-currentRoute' : isCurrentState('users.edit'), 'is-loggedOut' : !$root.current_user.username}"
aw-tool-tip="{{currentUserTip}}"
aw-tip-watch="currentUserTip"
@ -142,7 +142,7 @@
<a class="MainMenu-item MainMenu-item--notMobile MainMenu-item--right"
id="main_menu_setup_link"
ng-href="/#/setup"
ng-if="!licenseMissing"
ng-hide="licenseMissing"
ng-class="{'is-currentRoute' : isCurrentState('setup'), 'is-loggedOut' : !$root.current_user.username}"
aw-tool-tip="Settings"
data-placement="bottom"
@ -155,7 +155,7 @@
<a class="MainMenu-item MainMenu-item--notMobile MainMenu-item--right"
id="main_menu_portal_link"
ng-href="/#/portal"
ng-if="!licenseMissing"
ng-hide="licenseMissing"
ng-class="{'is-currentRoute' : isCurrentState('portalMode'), 'is-loggedOut' : !$root.current_user.username}"
aw-tool-tip="My View"
data-placement="bottom"
@ -168,7 +168,7 @@
<a class="MainMenu-item MainMenu-item--notMobile MainMenu-item--right"
id="main_menu_docs_link"
ng-href="http://docs.ansible.com/ansible-tower/"
ng-if="!licenseMissing"
ng-hide="licenseMissing"
ng-class="{'is-loggedOut' : !$root.current_user.username}"
aw-tool-tip="View Documentation"
data-placement="bottom"
@ -213,7 +213,7 @@
<button
id="main_menu_mobile_toggle_button"
class="MainMenu-toggle"
ng-if="!licenseMissing"
ng-hide="licenseMissing"
ng-class="{'is-active': !isHiddenOnMobile, 'is-loggedOut' : !$root.current_user.username}"
ng-click="toggleMenu()">
<i class="fa fa-bars MainMenu-toggleImage"></i>

View File

@ -0,0 +1,52 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
[ '$rootScope', '$pendolytics', 'Rest', 'GetBasePath', 'ProcessErrors', '$q',
function ($rootScope, $pendolytics, Rest, GetBasePath, ProcessErrors, $q) {
return {
get: function(){
return this.config;
},
set: function(config){
this.config = config;
},
getConfig: function () {
var config = this.get(),
that = this,
deferred = $q.defer();
if(_.isEmpty(config)){
var url = GetBasePath('config');
Rest.setUrl(url);
var promise = Rest.get();
promise.then(function (response) {
var config = response.data;
that.set(config);
deferred.resolve(response.data);
});
promise.catch(function (response) {
ProcessErrors($rootScope, response.data, response.status, null, {
hdr: 'Error!',
msg: 'Failed to get inventory name. GET returned status: ' +
response.status });
deferred.reject('Could not resolve pendo config.');
});
}
else if(config){
this.set(config);
deferred.resolve(config);
}
else {
deferred.reject('Config not found.');
}
return deferred.promise;
}
};
}
];

View File

@ -0,0 +1,11 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import ConfigService from './config.service';
export default
angular.module('config', [])
.service('ConfigService', ConfigService);

View File

@ -4,26 +4,32 @@
* All Rights Reserved
*************************************************/
export default ['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', '$http', '$q',
function ($rootScope, Rest, GetBasePath, ProcessErrors, $http, $q) {
export default ['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', '$http',
'$q', 'ConfigService',
function ($rootScope, Rest, GetBasePath, ProcessErrors, $http, $q,
ConfigService) {
var license_info;
return {
getFeatures: function(){
var promise;
Rest.setUrl(GetBasePath('config'));
promise = Rest.get();
return promise.then(function (data) {
license_info = data.data.license_info;
$rootScope.features = data.data.license_info.features;
return $rootScope.features;
}).catch(function (response) {
ProcessErrors($rootScope, response.data, response.status, null, {
hdr: 'Error!',
msg: 'Failed to get license info. GET returned status: ' +
response.status
});
});
var config = ConfigService.get();
license_info = config.license_info;
$rootScope.features = config.license_info.features;
return $rootScope.features;
// var promise;
// Rest.setUrl(GetBasePath('config'));
// promise = Rest.get();
// return promise.then(function (data) {
// license_info = data.data.license_info;
// $rootScope.features = data.data.license_info.features;
// return $rootScope.features;
// }).catch(function (response) {
// ProcessErrors($rootScope, response.data, response.status, null, {
// hdr: 'Error!',
// msg: 'Failed to get license info. GET returned status: ' +
// response.status
// });
// });
},
get: function(){
if(_.isEmpty($rootScope.features)){
@ -41,9 +47,6 @@ function ($rootScope, Rest, GetBasePath, ProcessErrors, $http, $q) {
else {
return false;
}
},
getLicenseInfo: function() {
return license_info;
}
};
}];

View File

@ -11,10 +11,17 @@ export default function($stateProvider) {
resolve.features = ['FeaturesService', function(FeaturesService) {
return FeaturesService.get();
}];
// resolve.features = ['CheckLicense', 'Store', '$state',
// function(CheckLicense, Store, $state) {
// var license = Store('license');
// if(CheckLicense.valid(license)=== false){
// $state.go('license');
// }
// }];
return resolve;
}
},
addState: function(state) {
var route = state.route || state.url,
resolve = this.getResolves(state);