1
0
mirror of https://github.com/ansible/awx.git synced 2024-11-01 16:51:11 +03:00

Merge pull request #2149 from jaredevantabor/session

Session Fixes
This commit is contained in:
Jared Tabor 2016-06-03 13:25:30 -07:00
commit 58ecbc254e
33 changed files with 356 additions and 393 deletions

View File

@ -18,25 +18,26 @@ export default {
label: "ACTIVITY STREAM"
},
resolve: {
features: ['FeaturesService', 'ProcessErrors', '$state', function(FeaturesService, ProcessErrors, $state) {
FeaturesService.get()
.then(function(features) {
features: ['FeaturesService', 'ProcessErrors', '$state', '$rootScope',
function(FeaturesService, ProcessErrors, $state, $rootScope) {
var features = FeaturesService.get();
if(features){
if(FeaturesService.featureEnabled('activity_streams')) {
// Good to go - pass the features along to the controller.
return features;
}
else {
// The activity stream feature isn't enabled. Take the user
// back to the dashboard
$state.go('dashboard');
}
})
.catch(function (response) {
ProcessErrors(null, response.data, response.status, null, {
hdr: 'Error!',
msg: 'Failed to get feature info. GET returned status: ' +
response.status
});
}
$rootScope.featuresConfigured.promise.then(function(features){
if(features){
if(FeaturesService.featureEnabled('activity_streams')) {
return features;
}
else {
$state.go('dashboard');
}
}
});
}],
subTitle:

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',
@ -212,8 +214,10 @@ var tower = angular.module('Tower', [
timeout: 4000
});
}])
.config(['$stateProvider', '$urlRouterProvider', '$breadcrumbProvider', '$urlMatcherFactoryProvider',
function ($stateProvider, $urlRouterProvider, $breadcrumbProvider, $urlMatcherFactoryProvider) {
.config(['$stateProvider', '$urlRouterProvider', '$breadcrumbProvider',
'$urlMatcherFactoryProvider',
function ($stateProvider, $urlRouterProvider, $breadcrumbProvider,
$urlMatcherFactoryProvider) {
$urlMatcherFactoryProvider.strictMode(false);
$breadcrumbProvider.setOptions({
templateUrl: urlPrefix + 'partials/breadcrumb.html'
@ -241,10 +245,9 @@ var tower = angular.module('Tower', [
label: "DASHBOARD"
},
resolve: {
graphData: ['$q', 'jobStatusGraphData', 'FeaturesService', function($q, jobStatusGraphData, FeaturesService) {
graphData: ['$q', 'jobStatusGraphData', function($q, jobStatusGraphData) {
return $q.all({
jobStatus: jobStatusGraphData.get("month", "all"),
features: FeaturesService.get()
});
}]
}
@ -514,10 +517,16 @@ 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',
'FeaturesService',
function ($q, $compile, $cookieStore, $rootScope, $log, CheckLicense,
$location, Authorization, LoadBasePaths, Timer, ClearScope, Socket,
LoadConfig, Store, ShowSocketHelp, pendoService, Prompt, Rest, Wait,
ProcessErrors, $state, GetBasePath, ConfigService, FeaturesService) {
var sock;
$rootScope.addPermission = function (scope) {
$compile("<add-permissions class='AddPermissions'></add-permissions>")(scope);
@ -585,11 +594,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 +624,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'
@ -745,7 +754,7 @@ var tower = angular.module('Tower', [
control_socket.on("limit_reached", function(data) {
$log.debug(data.reason);
$rootScope.sessionTimer.expireSession('session_limit');
$location.url('/login');
$state.go('signOut');
});
}
openSocket();
@ -760,9 +769,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 +820,15 @@ 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 === 'license' && toParams.hasOwnProperty('licenseMissing')){
$rootScope.licenseMissing = toParams.licenseMissing;
@ -865,11 +872,20 @@ var tower = angular.module('Tower', [
$rootScope.user_is_superuser = Authorization.getUserInfo('is_superuser');
// state the user refreshes we want to open the socket, except if the user is on the login page, which should happen after the user logs in (see the AuthService module for that call to OpenSocket)
if(!_.contains($location.$$url, '/login')){
Timer.init().then(function(timer){
$rootScope.sessionTimer = timer;
$rootScope.$emit('OpenSocket');
pendoService.issuePendoIdentity();
CheckLicense.notify();
ConfigService.getConfig().then(function(){
Timer.init().then(function(timer){
$rootScope.sessionTimer = timer;
$rootScope.$emit('OpenSocket');
pendoService.issuePendoIdentity();
CheckLicense.test();
FeaturesService.get();
if($location.$$path === "/home" && $state.current && $state.current.name === ""){
$state.go('dashboard');
}
else if($location.$$path === "/portal" && $state.current && $state.current.name === ""){
$state.go('portalMode');
}
});
});
}
}
@ -904,7 +920,11 @@ var tower = angular.module('Tower', [
// create a promise that will resolve state $AnsibleConfig is loaded
$rootScope.loginConfig = $q.defer();
}
if (!$rootScope.featuresConfigured) {
// create a promise that will resolve when features are loaded
$rootScope.featuresConfigured = $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,6 @@
export default
[ 'templateUrl', '$state', 'FeaturesService', 'ProcessErrors', 'Store', 'Empty', '$log', function(templateUrl, $state, FeaturesService, ProcessErrors, Store, Empty, $log) {
['templateUrl', '$state', 'FeaturesService', 'ProcessErrors','$rootScope',
function(templateUrl, $state, FeaturesService, ProcessErrors, $rootScope) {
return {
restrict: 'E',
templateUrl: templateUrl('bread-crumb/bread-crumb'),
@ -12,49 +13,21 @@ export default
scope.toggleActivityStream = function() {
// If the user is not already on the activity stream then they want to navigate to it
if(!scope.activityStreamActive) {
var stateGoParams = {};
var stateGoParams = {};
if(streamConfig && streamConfig.activityStream) {
if(streamConfig.activityStreamTarget) {
stateGoParams.target = streamConfig.activityStreamTarget;
}
if(streamConfig.activityStreamId) {
stateGoParams.id = $state.params[streamConfig.activityStreamId];
}
if(streamConfig && streamConfig.activityStream) {
if(streamConfig.activityStreamTarget) {
stateGoParams.target = streamConfig.activityStreamTarget;
}
$state.go('activityStream', stateGoParams);
}
// The user is navigating away from the activity stream - take them back from whence they came
else {
// Pull the previous state out of local storage
var previousState = Store('previous_state');
if(previousState && !Empty(previousState.name)) {
$state.go(previousState.name, previousState.fromParams);
if(streamConfig.activityStreamId) {
stateGoParams.id = $state.params[streamConfig.activityStreamId];
}
else {
// If for some reason something went wrong (like local storage was wiped, etc) take the
// user back to the dashboard
$state.go('dashboard');
}
}
$state.go('activityStream', stateGoParams);
};
scope.$on("$stateChangeSuccess", function updateActivityStreamButton(event, toState, toParams, fromState, fromParams) {
if(fromState && !Empty(fromState.name)) {
// Go ahead and attach the from params to the state object so that it can all be stored together
fromState.fromParams = fromParams ? fromParams : {};
// Store the state that we're coming from in local storage to be accessed when navigating away from the
// activity stream
Store('previous_state', fromState);
}
scope.$on("$stateChangeStart", function updateActivityStreamButton(event, toState) {
streamConfig = (toState && toState.data) ? toState.data : {};
@ -65,27 +38,12 @@ export default
// point. We use the get() function call here just in case the features aren't available.
// The get() function will only fire off the server call if the features aren't already
// attached to the $rootScope.
FeaturesService.get()
.then(function() {
var features = FeaturesService.get();
if(features){
scope.loadingLicense = false;
scope.activityStreamActive = (toState.name === 'activityStream') ? true : false;
scope.showActivityStreamButton = (FeaturesService.featureEnabled('activity_streams') || toState.name === 'activityStream') ? true : false;
var licenseInfo = FeaturesService.getLicenseInfo();
scope.licenseType = licenseInfo ? licenseInfo.license_type : null;
if (!licenseInfo) {
console.warn("License info not loaded correctly"); // jshint ignore:line
$log.error("License info not loaded correctly");
}
})
.catch(function (response) {
ProcessErrors(null, response.data, response.status, null, {
hdr: 'Error!',
msg: 'Failed to get feature info. GET returned status: ' +
response.status
});
});
scope.showActivityStreamButton = (FeaturesService.featureEnabled('activity_streams') || toState.name ==='activityStream') ? true : false;
}
}
else {
@ -94,6 +52,15 @@ export default
}
});
// scope.$on('featuresLoaded', function(){
$rootScope.featuresConfigured.promise.then(function(features){
// var features = FeaturesService.get();
if(features){
scope.loadingLicense = false;
scope.activityStreamActive = ($state.name === 'activityStream') ? true : false;
scope.showActivityStreamButton = (FeaturesService.featureEnabled('activity_streams') || $state.name ==='activityStream') ? true : false;
}
});
}
};
}];

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"
@ -8,7 +8,7 @@
data-container="body"
ng-class="{'BreadCrumb-menuLinkActive' : activityStreamActive}"
ng-if="showActivityStreamButton"
ng-hide= "loadingLicense || licenseMissing || licenseType == 'basic'"
ng-hide= "loadingLicense || licenseMissing"
ng-click="toggleActivityStream()">
<i class="BreadCrumb-menuLinkImage icon-activity-stream"
alt="Activity Stream">

View File

@ -22,9 +22,6 @@ var dashboardHostsList = {
label: "HOSTS"
},
resolve: {
features: ['FeaturesService', function(FeaturesService) {
return FeaturesService.get();
}],
hosts: ['Rest', 'GetBasePath', '$stateParams', function(Rest, GetBasePath, $stateParams){
var defaultUrl = GetBasePath('hosts') + '?page_size=10' + ($stateParams['active-failures'] ? '&has_active_failures=true' : '' );
Rest.setUrl(defaultUrl);

View File

@ -316,7 +316,8 @@ export default
buttons: { //for now always generates <button> tags
add_survey: {
ngClick: 'addSurvey()',
ngShow: 'job_type.value !== "scan" && !survey_exists'
ngShow: 'job_type.value !== "scan" && !survey_exists',
awFeature: 'surveys'
},
edit_survey: {
ngClick: 'editSurvey()',

View File

@ -16,7 +16,8 @@
export default
angular.module('ParseHelper', ['Utilities', 'AngularCodeMirrorModule'])
.factory('ParseTypeChange', ['Alert', 'AngularCodeMirror', function (Alert, AngularCodeMirror) {
.factory('ParseTypeChange', ['Alert', 'AngularCodeMirror', '$rootScope',
function (Alert, AngularCodeMirror, $rootScope) {
return function (params) {
var scope = params.scope,
@ -45,16 +46,18 @@ export default
function createField(onChange, onReady) {
//hide the textarea and show a fresh CodeMirror with the current mode (json or yaml)
scope.codeMirror = AngularCodeMirror();
scope.codeMirror.addModes($AnsibleConfig.variable_edit_modes);
scope.codeMirror.showTextArea({
scope: scope,
model: fld,
element: field_id,
lineNumbers: true,
mode: scope[pfld],
onReady: onReady,
onChange: onChange
$rootScope.loginConfig.promise.then(function () {
scope.codeMirror = AngularCodeMirror();
scope.codeMirror.addModes($AnsibleConfig.variable_edit_modes);
scope.codeMirror.showTextArea({
scope: scope,
model: fld,
element: field_id,
lineNumbers: true,
mode: scope[pfld],
onReady: onReady,
onChange: onChange
});
});
}

View File

@ -17,10 +17,5 @@ export default {
},
ncyBreadcrumb: {
label: "INVENTORY EDIT"
},
resolve: {
features: ['FeaturesService', function(FeaturesService) {
return FeaturesService.get();
}]
}
};

View File

@ -17,10 +17,5 @@ export default {
},
ncyBreadcrumb: {
label: "JOB TEMPLATES"
},
resolve: {
features: ['FeaturesService', function(FeaturesService) {
return FeaturesService.get();
}]
}
};

View File

@ -6,21 +6,15 @@
export default
['$state', '$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', '$q',
function($state, $rootScope, Rest, GetBasePath, ProcessErrors, $q){
'ConfigService',
function($state, $rootScope, Rest, GetBasePath, ProcessErrors, $q,
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);
@ -35,43 +29,51 @@ export default
msg: 'Call to '+ defaultUrl + ' failed. Return status: '+ status});
});
},
// 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;
},
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;
}
});
}
else{
deferred.resolve();
}
return deferred.promise;
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;
},
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 if(this.valid(license) === false) {
$rootScope.licenseMissing = true;
$state.transitionTo('license');
if(event){
event.preventDefault();
}
// deferred.reject(license);
}
else {
$rootScope.licenseMissing = false;
// deferred.resolve(license);
}
return;// deferred.promise;
}
};

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

@ -7,8 +7,10 @@
export default
['Wait', '$state', '$scope', '$rootScope', '$location', 'GetBasePath',
'Rest', 'ProcessErrors', 'CheckLicense', 'moment','$window',
'ConfigService', 'FeaturesService', 'pendoService',
function( Wait, $state, $scope, $rootScope, $location, GetBasePath, Rest,
ProcessErrors, CheckLicense, moment, $window){
ProcessErrors, CheckLicense, moment, $window, ConfigService,
FeaturesService, pendoService){
$scope.getKey = function(event){
// Mimic HTML5 spec, show filename
$scope.fileName = event.target.files[0].name;
@ -46,21 +48,27 @@ export default
CheckLicense.post($scope.newLicense.file, $scope.newLicense.eula)
.success(function(){
reset();
init();
if($rootScope.licenseMissing === true){
$state.go('dashboard', {
licenseMissing: false
});
}
else{
$scope.success = true;
$rootScope.licenseMissing = false;
// for animation purposes
var successTimeout = setTimeout(function(){
$scope.success = false;
clearTimeout(successTimeout);
}, 4000);
}
ConfigService.delete();
ConfigService.getConfig().then(function(){
delete($rootScope.features);
FeaturesService.get();
pendoService.issuePendoIdentity();
if($rootScope.licenseMissing === true){
$state.go('dashboard', {
licenseMissing: false
});
}
else{
init();
$scope.success = true;
$rootScope.licenseMissing = false;
// for animation purposes
var successTimeout = setTimeout(function(){
$scope.success = false;
clearTimeout(successTimeout);
}, 4000);
}
});
});
};
var calcDaysRemaining = function(seconds){
@ -74,17 +82,15 @@ 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];
ConfigService.getConfig().then(function(config){
$scope.license = config;
$scope.license.version = config.version.split('-')[0];
$scope.time = {};
$scope.time.remaining = calcDaysRemaining($scope.license.license_info.time_remaining);
$scope.time.expiresOn = calcExpiresOn($scope.time.remaining);

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">
@ -74,6 +74,7 @@
<div class="Panel">
<div class="License-titleText">{{title}}</div>
<div class="License-body">
<div class="License-helperText" ng-if="licenseMissing">Welcome to Ansible Tower! Please complete the steps below to acquire a license.</div>
<div class="AddPermissions-directions" ng-if="licenseMissing">
<span class="AddPermissions-directionNumber">
1

View File

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

View File

@ -16,7 +16,9 @@
export default
['$http', '$rootScope', '$location', '$cookieStore', 'GetBasePath', 'Store',
function ($http, $rootScope, $location, $cookieStore, GetBasePath, Store) {
'$injector',
function ($http, $rootScope, $location, $cookieStore, GetBasePath, Store,
$injector) {
return {
setToken: function (token, expires) {
// set the session cookie
@ -61,11 +63,12 @@ export default
// the following puts our primary scope up for garbage collection, which
// should prevent content flash from the prior user.
var x, scope = angular.element(document.getElementById('main-view')).scope();
var x,
ConfigService = $injector.get('ConfigService'),
scope = angular.element(document.getElementById('main-view')).scope();
scope.$destroy();
//$rootScope.$destroy();
if($cookieStore.get('lastPath')==='/portal'){
$cookieStore.put( 'lastPath', '/portal');
$rootScope.lastPath = '/portal';
@ -88,6 +91,7 @@ export default
if ($cookieStore.get('current_user')) {
$rootScope.lastUser = $cookieStore.get('current_user').id;
}
ConfigService.delete();
$cookieStore.remove('token_expires');
$cookieStore.remove('current_user');
$cookieStore.remove('token');
@ -97,7 +101,8 @@ export default
$rootScope.current_user = {};
$rootScope.license_tested = undefined;
$rootScope.userLoggedIn = false;
$rootScope.sessionExpired = false;
// $rootScope.sessionExpired = false;
$rootScope.licenseMissing = true;
$rootScope.token = null;
$rootScope.token_expires = null;
$rootScope.login_username = null;
@ -107,27 +112,6 @@ export default
}
},
getLicense: function () {
//check in here first to see if license is already obtained, if we do have it, then rootScope.license
return $http({
method: 'GET',
url: GetBasePath('config'),
headers: {
'Authorization': 'Token ' + this.getToken()
}
});
},
setLicense: function (data) {
var license = data.license_info;
license.analytics_status = data.analytics_status;
license.version = data.version;
license.ansible_version = data.ansible_version;
license.tested = false;
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

@ -23,9 +23,9 @@
*/
export default
['$rootScope', '$cookieStore', 'CreateDialog', 'Authorization',
'Store', '$interval', '$location', '$q',
'Store', '$interval', '$state', '$q',
function ($rootScope, $cookieStore, CreateDialog, Authorization,
Store, $interval, $location, $q) {
Store, $interval, $state, $q) {
return {
sessionTime: null,
@ -62,7 +62,7 @@ export default
now = new Date().getTime()/1000,
diff = stime-now;
if(diff < 61){
if(diff < 60){
return diff;
}
else {
@ -157,13 +157,13 @@ export default
$('#idle-modal').dialog('close');
}
that.expireSession('idle');
$location.url('/login');
$state.go('signOut');
}
if(Store('sessionTime') &&
Store('sessionTime')[$rootScope.current_user.id] &&
Store('sessionTime')[$rootScope.current_user.id].loggedIn === false){
that.expireSession();
$location.url('/login');
$state.go('signOut');
}

View File

@ -43,8 +43,8 @@
* - Start the expiration timer by calling the init() method of [js/shared/Timer.js](/static/docs/api/shared.function:Timer)
* - Get user informaton by calling Authorization.getUser() - sends a GET request to /api/v1/me
* - Store user information in the session cookie by calling Authorization.setUser().
* - Get the Tower license by calling Authorization.getLicense() - sends a GET request to /api/vi/config
* - Stores the license object in local storage by calling Authorization.setLicense(). This adds the Tower version and a tested flag to the license object. The tested flag is initially set to false.
* - Get the Tower license by calling ConfigService.getConfig() - sends a GET request to /api/vi/config
* - Stores the license object in memory by calling CheckLicense.test(). This adds the Tower version and a tested flag to the license object. The tested flag is initially set to false. Additionally, the pendoService and FeaturesService are called to initiate the other startup services of Tower
*
* Note that there is a session timer kept on the server side as well as the client side. Each time an API request is made, Tower (in app.js) calls
* Timer.isExpired(). This verifies the UI does not think the session is expired, and if not, moves the expiration time into the future. The number of
@ -54,10 +54,13 @@
* This is usage information.
*/
export default ['$log', '$cookieStore', '$compile', '$window', '$rootScope', '$location', 'Authorization', 'ToggleClass', 'Alert', 'Wait',
'Timer', 'Empty', 'ClearScope', '$scope', 'pendoService',
function ($log, $cookieStore, $compile, $window, $rootScope, $location, Authorization, ToggleClass, Alert, Wait,
Timer, Empty, ClearScope, scope, pendoService) {
export default ['$log', '$cookieStore', '$compile', '$window', '$rootScope',
'$location', 'Authorization', 'ToggleClass', 'Alert', 'Wait', 'Timer',
'Empty', 'ClearScope', '$scope', 'pendoService', 'ConfigService',
'CheckLicense', 'FeaturesService',
function ($log, $cookieStore, $compile, $window, $rootScope, $location,
Authorization, ToggleClass, Alert, Wait, Timer, Empty, ClearScope,
scope, pendoService, ConfigService, CheckLicense, FeaturesService) {
var lastPath, lastUser, sessionExpired, loginAgain;
@ -110,10 +113,10 @@ export default ['$log', '$cookieStore', '$compile', '$window', '$rootScope', '$l
scope.removeAuthorizationGetLicense();
}
scope.removeAuthorizationGetLicense = scope.$on('AuthorizationGetLicense', function() {
Authorization.getLicense()
.success(function (data) {
Authorization.setLicense(data);
ConfigService.getConfig().then(function(){
CheckLicense.test();
pendoService.issuePendoIdentity();
FeaturesService.get();
Wait("stop");
if (lastPath() && lastUser()) {
// Go back to most recent navigation path
@ -122,7 +125,7 @@ export default ['$log', '$cookieStore', '$compile', '$window', '$rootScope', '$l
$location.url('/home');
}
})
.error(function () {
.catch(function () {
Wait('stop');
Alert('Error', 'Failed to access license information. GET returned status: ' + status, 'alert-danger', loginAgain);
});

View File

@ -9,9 +9,9 @@
export default {
name: 'signOut',
route: '/logout',
controller: ['Authorization', '$location', function(Authorization, $location) {
controller: ['Authorization', '$state', function(Authorization, $state) {
Authorization.logout();
$location.path('/login');
$state.go('signIn');
}],
ncyBreadcrumb: {
skip: true

View File

@ -18,7 +18,7 @@
}
.MainMenu--licenseMissing{
justify-content: flex-end;
justify-content: space-between;
}
.MainMenu-logo,
@ -35,6 +35,11 @@
margin: 20px;
}
.MainMenu-logoImage--licenseMissing:hover{
cursor:default;
background-color: @default-bg!important;
}
.MainMenu-item {
padding: 0 20px;
}
@ -192,6 +197,23 @@
display: none;
}
.MainMenu-item--licenseMissing{
justify-content: space-between!important;
display:flex!important;
align-items: center!important;
flex: initial!important;
padding-top:0px;
}
.MainMenu-item--licenseMissing:hover{
padding-top:20px;
border-bottom: 5px solid @menu-link-btm-hov;
}
.MainMenu-itemImage--licenseMissing{
font-size: 20px!important;
}
.MainMenu {
flex-direction: row;
flex-wrap: wrap;

View File

@ -2,10 +2,10 @@
<!-- Menu logo item -->
<a id="main_menu_logo"
href="/#/"
class="MainMenu-logo"
ng-if="!licenseMissing"
ng-class="{'is-loggedOut' : !$root.current_user.username}">
class="MainMenu-logo ng-cloak"
ng-class="{'is-loggedOut' : !$root.current_user.username, 'MainMenu-logoImage--licenseMissing': licenseMissing}">
<img class="MainMenu-logoImage"
ng-class="{'MainMenu-logoImage--licenseMissing': licenseMissing}"
ng-src="/static/assets/tower-logo-header.svg">
</a>
@ -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"
@ -182,13 +182,16 @@
<a class="MainMenu-item MainMenu-item--notMobile MainMenu-item--right"
id="main_menu_logout_link"
ng-href="/#/logout"
ng-class="{'is-currentRoute' : isCurrentState('logout'), 'is-loggedOut' : !$root.current_user.username}"
ng-class="{'is-currentRoute' : isCurrentState('logout'),
'is-loggedOut' : !$root.current_user.username,
'MainMenu-item--licenseMissing' : licenseMissing}"
aw-tool-tip="Log Out"
data-placement="bottom"
data-trigger="hover"
data-container="body"
data-tooltip_inner_class="tooltip-inner--logOut">
<i class="MainMenu-itemImage fa fa-power-off"
ng-class="{'MainMenu-itemImage--licenseMissing' : licenseMissing}"
alt="Log Out">
</i>
</a>
@ -213,7 +216,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

@ -11,11 +11,6 @@ export default {
route: '/add',
templateUrl: templateUrl('notifications/add/add'),
controller: 'notificationsAddController',
resolve: {
features: ['FeaturesService', function(FeaturesService) {
return FeaturesService.get();
}]
},
ncyBreadcrumb: {
parent: 'notifications',
label: 'Create Notification Template'

View File

@ -15,10 +15,5 @@ export default {
ncyBreadcrumb: {
parent: "organizations",
label: "CREATE ORGANIZATION"
},
resolve: {
features: ['FeaturesService', function(FeaturesService) {
return FeaturesService.get();
}]
}
};

View File

@ -10,10 +10,5 @@ export default {
name: 'userPermissionsAdd',
route: '/users/:user_id/permissions/add',
templateUrl: templateUrl('permissions/shared/user-permissions'),
controller: 'permissionsAddController',
resolve: {
features: ['FeaturesService', function(FeaturesService) {
return FeaturesService.get();
}]
}
controller: 'permissionsAddController'
};

View File

@ -10,10 +10,5 @@ export default {
name: 'userPermissionsEdit',
route: '/users/:user_id/permissions/:permission_id',
templateUrl: templateUrl('permissions/shared/user-permissions'),
controller: 'permissionsEditController',
resolve: {
features: ['FeaturesService', function(FeaturesService) {
return FeaturesService.get();
}]
}
controller: 'permissionsEditController'
};

View File

@ -10,11 +10,6 @@ export default {
ncyBreadcrumb: {
label: "MY VIEW"
},
resolve: {
features: ['FeaturesService', function(FeaturesService) {
return FeaturesService.get();
}]
},
views: {
// the empty parent ui-view
"" : {

View File

@ -16,15 +16,17 @@
return {
response: function(config) {
if(config.headers('auth-token-timeout') !== null){
$AnsibleConfig.session_timeout = Number(config.headers('auth-token-timeout'));
$rootScope.loginConfig.promise.then(function () {
$AnsibleConfig.session_timeout = Number(config.headers('auth-token-timeout'));
});
}
return config;
},
responseError: function(rejection){
if(rejection && rejection.data && rejection.data.detail && rejection.data.detail === "Maximum per-user sessions reached"){
$rootScope.sessionTimer.expireSession('session_limit');
var location = $injector.get('$location');
location.url('/login');
var state = $injector.get('$state');
state.go('signOut');
return $q.reject(rejection);
}
return $q.reject(rejection);

View File

@ -0,0 +1,56 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
['GetBasePath', 'ProcessErrors', '$q', 'Rest', '$rootScope',
function (GetBasePath, ProcessErrors, $q, Rest, $rootScope) {
return {
get: function(){
return this.config;
},
set: function(config){
this.config = config;
},
delete: function(){
delete(this.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 config. 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,36 +4,27 @@
* All Rights Reserved
*************************************************/
export default ['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', '$http', '$q',
function ($rootScope, Rest, GetBasePath, ProcessErrors, $http, $q) {
var license_info;
export default ['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', '$http',
'$q', 'ConfigService',
function ($rootScope, Rest, GetBasePath, ProcessErrors, $http, $q,
ConfigService) {
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
});
});
},
get: function(){
if(_.isEmpty($rootScope.features)){
return this.getFeatures();
} else {
// $q.when will ensure that the result is returned
// as a resovled promise.
return $q.when($rootScope.features);
if (_.isEmpty($rootScope.features)) {
var config = ConfigService.get();
if(config){
$rootScope.features = config.license_info.features;
if($rootScope.featuresConfigured){
$rootScope.featuresConfigured.resolve($rootScope.features);
}
return $rootScope.features;
}
}
else{
return $rootScope.features;
}
},
featureEnabled: function(feature) {
if($rootScope.features && $rootScope.features[feature] && $rootScope.features[feature] === true) {
return true;
@ -41,9 +32,6 @@ function ($rootScope, Rest, GetBasePath, ProcessErrors, $http, $q) {
else {
return false;
}
},
getLicenseInfo: function() {
return license_info;
}
};
}];

View File

@ -1702,6 +1702,9 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
if (button.ngClick) {
html += this.attr(button, 'ngClick');
}
if (button.awFeature) {
html += this.attr(button, 'awFeature');
}
if (button.ngDisabled) {
ngDisabled = (button.ngDisabled===true) ? this.form.name+"_form.$invalid" : button.ngDisabled;
if (btn !== 'reset') {

View File

@ -1,29 +1,15 @@
export default function($stateProvider) {
this.$get = function() {
return {
getResolves: function(state){
var resolve = state.resolve || {},
routes = ["signIn", "signOut"];
if(_.indexOf(routes, state.name)>-1){
return;
}
else{
resolve.features = ['FeaturesService', function(FeaturesService) {
return FeaturesService.get();
}];
return resolve;
}
},
addState: function(state) {
var route = state.route || state.url,
resolve = this.getResolves(state);
var route = state.route || state.url;
$stateProvider.state(state.name, {
url: route,
controller: state.controller,
templateUrl: state.templateUrl,
resolve: resolve,
resolve: state.resolve,
params: state.params,
data: state.data,
ncyBreadcrumb: state.ncyBreadcrumb,

View File

@ -1,57 +0,0 @@
import '../support/node';
import features from 'shared/features/main';
import {describeModule} from '../support/describe-module';
//test that it returns features, as well as test that it is returned in rootScope
describeModule(features.name)
.testService('FeaturesService', function(test, restStub) {
var service;
test.withService(function(_service) {
service = _service;
});
it('returns list of features', function() {
var features = {},
result = {
data: {
license_info: {
features: features
}
}
};
var actual = service.get();
restStub.succeed(result);
restStub.flush();
return expect(actual).to.eventually.equal(features);
});
it('caches in rootScope', window.inject(['$rootScope',
function($rootScope){
var features = {},
result = {
data: {
license_info: {
features: features
}
}
};
var actual = service.get();
restStub.succeed(result);
restStub.flush();
return actual.then(function(){
expect($rootScope.features).to.equal(features);
});
}]));
});