From 1f5f45a16222ef6466ede43eec60acfe37bb49ec Mon Sep 17 00:00:00 2001 From: Chris Houseknecht Date: Fri, 8 Aug 2014 14:30:54 -0400 Subject: [PATCH] License Updated language changes. Dropping mention of 'grace period' when trial flag set. Showing existing license JSON in the license text field when appropriate. Updated unit tests. --- awx/ui/static/js/forms/LicenseForm.js | 2 +- awx/ui/static/js/helpers/License.js | 214 +++++++++++++++----------- awx/ui/static/less/ansible-ui.less | 8 +- awx/ui/tests/unit/CheckLicense.js | 87 ++++++++++- 4 files changed, 210 insertions(+), 101 deletions(-) diff --git a/awx/ui/static/js/forms/LicenseForm.js b/awx/ui/static/js/forms/LicenseForm.js index fcea0fa4ee..218d70e47e 100644 --- a/awx/ui/static/js/forms/LicenseForm.js +++ b/awx/ui/static/js/forms/LicenseForm.js @@ -49,7 +49,7 @@ angular.module('LicenseFormDefinition', []) tab: 'license' }, time_remaining: { - label: 'Time Left', + label: 'Time Remaining', type: 'text', readonly: true, tab: 'license' diff --git a/awx/ui/static/js/helpers/License.js b/awx/ui/static/js/helpers/License.js index f66349b3d6..0860087a3b 100644 --- a/awx/ui/static/js/helpers/License.js +++ b/awx/ui/static/js/helpers/License.js @@ -62,26 +62,41 @@ function($rootScope, $compile, CreateDialog, Store, LicenseUpdateForm, GenerateF if (parseInt(license.grace_period_remaining,10) > 86400) { title = "License Expired"; html = "

Thank you for using Ansible Tower. The Ansible Tower license " + - "has expired. You will no longer be able to add managed hosts or run playbooks after " + this.getRemainingDays(license.grace_period_remaining) + " days

"; + "has expired. "; + // trial licenses don't get a grace period + if (license.trial) { + html += "Managed hosts cannot be added and playbooks will no longer run.

"; + } else { + html += "After " + this.getRemainingDays(license.grace_period_remaining) + " grace days managed hosts cannot be added and playbooks will no longer run.

"; + } } else { title = "License Expired"; html = "

Thank you for using Ansible Tower. The Ansible Tower license " + - "has expired, and the 30 day grace period has been exceeded. To continue using Tower to run playbooks and add managed hosts a " + - "valid license key is required.

"; + "has expired"; + // trial licenses don't get a grace period + html += (!license.trial) ? ", and the 30 day grace period has been exceeded." : "."; + html += " To continue using Tower to run playbooks and add managed hosts a valid license key is required.

"; } } else if (this.getRemainingDays(license.time_remaining) < 15) { + // Warning: license expiring in less than 15 days title = "License Warning"; html = "

Thank you for using Ansible Tower. The Ansible Tower license " + - "has " + this.getRemainingDays(license.time_remaining) + " days remaining. Once the license expires you will no longer be able to add managed hosts or run playbooks.

"; + "has " + this.getRemainingDays(license.time_remaining) + " days remaining. "; + // trial licenses don't get a grace period + if (license.trial) { + html += "After the license expires playbooks will no longer run and managed hosts cannot be added.

"; + } else { + html += "After a short grace period of 30 days, playbooks will no longer run and managed hosts cannot be added.

"; + } } else if (license.free_instances <= 0) { title = "Host Count Exceeded"; html = "

The Ansible Tower license has reached capacity for the number of " + - "managed hosts allowed. No additional hosts can be added.

"; + "managed hosts allowed. No additional hosts can be added. Existing playbooks can still be run against hosts already in inventory.

"; } else { // license is valid. the following text is displayed in the license viewer - title = "Update License"; // not actually used + title = "Update License"; html = "

The Ansible Tower license is valid.

"; license_is_valid = true; } @@ -156,13 +171,11 @@ function($rootScope, $compile, CreateDialog, Store, LicenseUpdateForm, GenerateF var license = Store('license'), notify = this.shouldNotify(license), self = this, - scope, height, html, buttons, submitKey; + scope, height, html, buttons; self.scope = $rootScope.$new(); scope = self.scope; - submitKey = this.postLicense; - if (license && typeof license === 'object' && Object.keys(license).length > 0) { if (license.tested) { return true; @@ -181,7 +194,6 @@ function($rootScope, $compile, CreateDialog, Store, LicenseUpdateForm, GenerateF scope.flashMessage = null; scope.parseType = 'json'; - scope.license_json = " "; scope.removeLicenseDialogReady = scope.$on('LicenseDialogReady', function() { var e = angular.element(document.getElementById('license-modal-dialog')); @@ -190,7 +202,7 @@ function($rootScope, $compile, CreateDialog, Store, LicenseUpdateForm, GenerateF }); scope.submitLicenseKey = function() { - submitKey(scope.license_json); + self.postLicense(scope.license_json); }; if (IsAdmin()) { @@ -222,37 +234,33 @@ function($rootScope, $compile, CreateDialog, Store, LicenseUpdateForm, GenerateF height = (IsAdmin()) ? 600 : 350; - CreateDialog({ - scope: scope, - buttons: buttons, - width: 700, - height: height, - minWidth: 400, - title: html.title, - id: 'license-modal-dialog', - clonseOnEscape: false, - onClose: function() { - if (scope.codeMirror) { - scope.codeMirror.destroy(); - } - $('#license-modal-dialog').empty(); - }, - onResizeStop: function() { - if (IsAdmin()) { - TextareaResize({ - scope: scope, - textareaId: 'license_license_json', - modalId: 'license-modal-dialog', - formId: 'license-notification-body', - fld: 'license_json', - parse: true, - onChange: function() { scope.license_json_api_error = ''; } - }); - } - }, - onOpen: function() { - if (IsAdmin()) { - setTimeout(function() { + if (scope.removeLicenseReady) { + scope.removeLicenseReady(); + } + scope.removeLicenseReady = scope.$on('LicenseReady', function(e, data) { + + scope.license_json = " "; + if (data.license_info && data.license_info.valid_key !== undefined) { + scope.license_json = JSON.stringify(data.license_info, null, ' '); + } + + CreateDialog({ + scope: scope, + buttons: buttons, + width: 700, + height: height, + minWidth: 400, + title: html.title, + id: 'license-modal-dialog', + clonseOnEscape: false, + onClose: function() { + if (scope.codeMirror) { + scope.codeMirror.destroy(); + } + $('#license-modal-dialog').empty(); + }, + onResizeStop: function() { + if (IsAdmin()) { TextareaResize({ scope: scope, textareaId: 'license_license_json', @@ -262,20 +270,60 @@ function($rootScope, $compile, CreateDialog, Store, LicenseUpdateForm, GenerateF parse: true, onChange: function() { scope.license_json_api_error = ''; } }); - $('#cm-license_json-container .CodeMirror textarea').focus(); - }, 300); - } else { - $('#license-ok-button').focus(); - } - }, - callback: 'LicenseDialogReady' + } + }, + onOpen: function() { + if (IsAdmin()) { + setTimeout(function() { + TextareaResize({ + scope: scope, + textareaId: 'license_license_json', + modalId: 'license-modal-dialog', + formId: 'license-notification-body', + fld: 'license_json', + parse: true, + onChange: function() { scope.license_json_api_error = ''; } + }); + $('#cm-license_json-container .CodeMirror textarea').focus(); + }, 300); + } else { + $('#license-ok-button').focus(); + } + }, + callback: 'LicenseDialogReady' + }); }); + + self.GetLicense('LicenseReady'); + + }, + + GetLicense: function(callback, inScope) { + // Retrieve license detail + var self = this, + scope = (inScope) ? inScope : self.scope, + url = GetBasePath('config'); + Rest.setUrl(url); + Rest.get() + .success(function (data) { + if (scope && callback) { + scope.$emit(callback, data); + } + else if (scope) { + scope.$emit('CheckLicenseReady', data); + } + }) + .error(function (data, status) { + ProcessErrors($rootScope, data, status, null, { hdr: 'Error!', + msg: 'Failed to retrieve license. GET status: ' + status + }); + }); } }; }]) -.factory('LicenseViewer', ['$location', '$rootScope', '$compile', 'GenerateForm', 'Rest', 'Alert', 'GetBasePath', 'ProcessErrors', 'FormatDate', 'Prompt', 'Empty', 'LicenseForm', 'IsAdmin', 'CreateDialog', 'CheckLicense', 'TextareaResize', -function ($location, $rootScope, $compile, GenerateForm, Rest, Alert, GetBasePath, ProcessErrors, FormatDate, Prompt, Empty, LicenseForm, IsAdmin, CreateDialog, CheckLicense, TextareaResize) { +.factory('LicenseViewer', ['$location', '$rootScope', '$compile', '$filter', 'GenerateForm', 'Rest', 'Alert', 'GetBasePath', 'ProcessErrors', 'FormatDate', 'Prompt', 'Empty', 'LicenseForm', 'IsAdmin', 'CreateDialog', 'CheckLicense', 'TextareaResize', +function ($location, $rootScope, $compile, $filter, GenerateForm, Rest, Alert, GetBasePath, ProcessErrors, FormatDate, Prompt, Empty, LicenseForm, IsAdmin, CreateDialog, CheckLicense, TextareaResize) { return { createDialog: function(html) { @@ -292,12 +340,12 @@ function ($location, $rootScope, $compile, GenerateForm, Rest, Alert, GetBasePat e = angular.element(document.getElementById('license-modal-dialog')); e.empty().html(html); - if (scope.license_status === 'Invalid License Key') { + if (scope.license_status === 'Invalid License Key' || scope.license_status === 'Missing License Key') { $('#license_tabs li:eq(1)').hide(); } scope.parseType = 'json'; - scope.license_json = " "; + scope.license_json = JSON.stringify(self.license, null, ' '); h = CheckLicense.getHTML(self.getLicense(),true).body; $('#license-modal-dialog #license_tabs').append("
  • Update License
  • "); $('#license-modal-dialog .tab-content').append("
    "); @@ -394,7 +442,7 @@ function ($location, $rootScope, $compile, GenerateForm, Rest, Alert, GetBasePat self.form = angular.copy(LicenseForm); for (fld in self.form.fields) { - if (fld !== 'time_remaining' && fld !== 'license_status') { + if (fld !== 'time_remaining' && fld !== 'license_status' && fld !== 'tower_version') { if (Empty(license_info[fld])) { delete self.form.fields[fld]; } @@ -414,8 +462,8 @@ function ($location, $rootScope, $compile, GenerateForm, Rest, Alert, GetBasePat return html; }, - loadDefaultScope: function(license_info) { - var fld, dt, days, remainder, hours, minutes, seconds, license, + loadDefaultScope: function(license_info, version) { + var fld, dt, days, license, self = this, scope = this.getScope(); @@ -425,19 +473,18 @@ function ($location, $rootScope, $compile, GenerateForm, Rest, Alert, GetBasePat } } + scope.tower_version = version; + if (scope.license_date) { dt = new Date(parseInt(scope.license_date, 10) * 1000); // expects license_date in seconds scope.license_date = FormatDate(dt); - scope.time_remaining = scope.time_remaining * 1000; - days = parseInt(scope.time_remaining / 86400000, 10); - remainder = scope.time_remaining - (days * 86400000); - hours = parseInt(remainder / 3600000, 10); - remainder = remainder - (hours * 3600000); - minutes = parseInt(remainder / 60000, 10); - remainder = remainder - (minutes * 60000); - seconds = parseInt(remainder / 1000, 10); - scope.time_remaining = days + ' days ' + ('0' + hours).slice(-2) + ':' + ('0' + minutes).slice(-2) + ':' + - ('0' + seconds).slice(-2); + scope.time_remaining = parseInt(scope.time_remaining,10) * 1000; + if (scope.time_remaining < 0) { + days = 0; + } else { + days = Math.floor(scope.time_remaining / 86400000); + } + scope.time_remaining = $filter('number')(days, 0) + ' days'; } if (parseInt(scope.free_instances) <= 0) { @@ -472,8 +519,9 @@ function ($location, $rootScope, $compile, GenerateForm, Rest, Alert, GetBasePat return this.scope; }, - setLicense: function(license_info) { + setLicense: function(license_info, version) { this.license = license_info; + this.version = version; }, getLicense: function() { @@ -488,34 +536,14 @@ function ($location, $rootScope, $compile, GenerateForm, Rest, Alert, GetBasePat scope.removeLicenseDataReady(); } scope.removeLicenseDataReady = scope.$on('LicenseDataReady', function(e, data) { - data.license_info.tower_version = data.version; - self.setLicense(data.license_info); - var html = self.getDefaultHTML(data.license_info); - self.loadDefaultScope(data.license_info); + var html, version; + version = data.version.replace(/-\d*$/,''); + self.setLicense(data.license_info, version); + html = self.getDefaultHTML(data.license_info); + self.loadDefaultScope(data.license_info, version); self.createDialog(html); }); - self.GetLicense(); - }, - - GetLicense: function(callback) { - // Retrieve license detail - var scope = this.getScope(), - url = GetBasePath('config'); - Rest.setUrl(url); - Rest.get() - .success(function (data) { - if (scope && callback) { - scope.$emit(callback, data); - } - else if (scope) { - scope.$emit('LicenseDataReady', data); - } - }) - .error(function (data, status) { - ProcessErrors($rootScope, data, status, null, { hdr: 'Error!', - msg: 'Failed to retrieve license. GET status: ' + status - }); - }); + CheckLicense.GetLicense('LicenseDataReady', scope); } }; }]); diff --git a/awx/ui/static/less/ansible-ui.less b/awx/ui/static/less/ansible-ui.less index 0ce4f3f548..ea240ac9a5 100644 --- a/awx/ui/static/less/ansible-ui.less +++ b/awx/ui/static/less/ansible-ui.less @@ -1246,10 +1246,10 @@ input[type="checkbox"].checkbox-no-label { /* license modal */ #license-modal-dialog { - textarea:read-only, - input:read-only { - background-color: #F9F9F9; - border: 1px solid #F5F5F5; + input[readonly], + textarea[readonly] { + background-color: #FBFBFB; + border: 1px solid #E0E0E0; } } diff --git a/awx/ui/tests/unit/CheckLicense.js b/awx/ui/tests/unit/CheckLicense.js index f9a24d5081..c591e3fcaf 100644 --- a/awx/ui/tests/unit/CheckLicense.js +++ b/awx/ui/tests/unit/CheckLicense.js @@ -22,7 +22,7 @@ time_remaining: 0, grace_period_remaining: (86400 * 2), free_instances: 10, - expects: 'after 2 days' + expects: '2 grace days' }, { desc: 'valid license with time remaining = 15 days', valid_key: true, @@ -51,6 +51,63 @@ grace_period_remaining: 0, free_instances: 0, expects: 'license has reached capacity' + }, { + desc: 'expired trial license with > 1 day grace period', + valid_key: true, + trial: true, + time_remaining: 0, + grace_period_remaining: (86400 * 2), + free_instances: 10, + notExpects: 'grace days' + } , { + desc: 'expired trial license with < 1 day grace period', + valid_key: true, + trial: true, + time_remaining: 0, + grace_period_remaining: 0, + free_instances: 10, + notExpects: '30 day grace period' + }, { + desc: 'trial license with time remaining = 15 days', + trial: true, + valid_key: true, + time_remaining: (86400 * 15), + grace_period_remaining: 0, + free_instances: 10, + notExpects: 'grace period' + }, { + desc: 'trial license with time remaining < 15 days', + valid_key: true, + trial: true, + time_remaining: (86400 * 10) , + grace_period_remaining: 0, + free_instances: 10, + notExpects: 'grace period' + }]; + +var should_notify = [{ + desc: 'should notify when license expired', + valid_key: true, + time_remaining: 0, + grace_period_remaining: 85000, + free_instances: 10 + }, { + desc: 'should notify when license time remaining < 15 days', + valid_key: true, + time_remaining: (86400 * 10) , + grace_period_remaining: 0, + free_instances: 10 + }, { + desc: 'should notify when host count <= 0', + valid_key: true, + time_remaining: (86400 * 200) , + grace_period_remaining: 0, + free_instances: 0 + }, { + desc: 'should notify when license is invalid', + valid_key: false + },{ + desc: 'should notify when license is empty', }]; describe('Unit:CheckLicense', function() { @@ -77,10 +134,34 @@ describe('Unit:CheckLicense', function() { expect(CheckLicense.getAdmin).not.toBe(null); })); + it('should have a shouldNotify method', inject(function(CheckLicense) { + expect(CheckLicense.shouldNotify).not.toBe(null); + })); + + it('should not notify when license valid, time remaining > 15 days and host count > 0', inject(function(CheckLicense) { + expect(CheckLicense.shouldNotify({ + valid_key: true, + time_remaining: (86400 * 20), + grace_period_remaining: 0, + free_instances: 10 })).toBe(false); + })); + + should_notify.forEach(function(lic) { + it(lic.desc, inject(function(CheckLicense) { + expect(CheckLicense.shouldNotify(lic)).toBe(true); + })); + }); + licenses.forEach(function(lic) { it(lic.desc, inject(function(CheckLicense) { - var r = new RegExp(lic.expects); - expect(CheckLicense.getHTML(lic).body).toMatch(r); + var r; + if (lic.expects) { + r = new RegExp(lic.expects); + expect(CheckLicense.getHTML(lic).body).toMatch(r); + } else { + r = new RegExp(lic.notExpects); + expect(CheckLicense.getHTML(lic).body).not.toMatch(r); + } })); });