From 0971642d202ffdb0644bd30544591319d7730260 Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Wed, 24 Feb 2016 15:25:47 -0500 Subject: [PATCH 01/32] new License module, nuke old License module, #1006 & #1007 --- awx/ui/client/legacy-styles/ansible-ui.less | 4 +- awx/ui/client/src/app.js | 32 +-- awx/ui/client/src/controllers/License.js | 189 ------------ awx/ui/client/src/helpers.js | 2 - awx/ui/client/src/helpers/License.js | 271 ------------------ awx/ui/client/src/license/license.block.less | 80 ++++++ .../client/src/license/license.controller.js | 50 ++++ .../client/src/license/license.partial.html | 72 +++++ awx/ui/client/src/license/license.route.js | 19 ++ awx/ui/client/src/license/main.js | 71 +++++ awx/ui/client/src/partials/license.html | 4 - 11 files changed, 302 insertions(+), 492 deletions(-) delete mode 100644 awx/ui/client/src/controllers/License.js delete mode 100644 awx/ui/client/src/helpers/License.js create mode 100644 awx/ui/client/src/license/license.block.less create mode 100644 awx/ui/client/src/license/license.controller.js create mode 100644 awx/ui/client/src/license/license.partial.html create mode 100644 awx/ui/client/src/license/license.route.js create mode 100644 awx/ui/client/src/license/main.js delete mode 100644 awx/ui/client/src/partials/license.html diff --git a/awx/ui/client/legacy-styles/ansible-ui.less b/awx/ui/client/legacy-styles/ansible-ui.less index ec58ca474f..22b188eacc 100644 --- a/awx/ui/client/legacy-styles/ansible-ui.less +++ b/awx/ui/client/legacy-styles/ansible-ui.less @@ -99,7 +99,9 @@ a:focus { color: @blue-dark; text-decoration: none; } - +.btn{ + text-transform: uppercase; +} /* Old style TB default button with grey background */ .btn-grey { color: #333; diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 43ad6627fb..a570f0df38 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -1,11 +1,9 @@ /************************************************* - * Copyright (c) 2015 Ansible, Inc. + * Copyright (c) 2016 Ansible, Inc. * * All Rights Reserved *************************************************/ - - var urlPrefix; if ($basePath) { @@ -34,6 +32,7 @@ import managementJobs from './management-jobs/main'; import jobDetail from './job-detail/main'; // modules +import license from './license/main'; import setupMenu from './setup-menu/main'; import mainMenu from './main-menu/main'; import breadCrumb from './bread-crumb/main'; @@ -46,7 +45,6 @@ import login from './login/main'; import activityStream from './activity-stream/main'; import standardOut from './standard-out/main'; import {JobTemplatesList, JobTemplatesAdd, JobTemplatesEdit} from './controllers/JobTemplates'; -import {LicenseController} from './controllers/License'; import {ScheduleEditController} from './controllers/Schedules'; import {ProjectsList, ProjectsAdd, ProjectsEdit} from './controllers/Projects'; import {OrganizationsList, OrganizationsAdd, OrganizationsEdit} from './controllers/Organizations'; @@ -79,6 +77,7 @@ var tower = angular.module('Tower', [ // 'ngAnimate', 'ngSanitize', 'ngCookies', + license.name, RestServices.name, browserData.name, systemTracking.name, @@ -99,7 +98,6 @@ var tower = angular.module('Tower', [ standardOut.name, 'templates', 'Utilities', - 'LicenseHelper', 'OrganizationFormDefinition', 'UserFormDefinition', 'FormGenerator', @@ -858,21 +856,6 @@ var tower = angular.module('Tower', [ } }). - state('license', { - url: '/license', - templateUrl: urlPrefix + 'partials/license.html', - controller: LicenseController, - ncyBreadcrumb: { - parent: 'setup', - label: 'LICENSE' - }, - resolve: { - features: ['FeaturesService', function(FeaturesService) { - return FeaturesService.get(); - }] - } - }). - state('sockets', { url: '/sockets', templateUrl: urlPrefix + 'partials/sockets.html', @@ -1042,7 +1025,6 @@ var tower = angular.module('Tower', [ $rootScope.$on("$stateChangeStart", function (event, next, nextParams, prev) { - // this line removes the query params attached to a route if(prev && prev.$$route && prev.$$route.name === 'systemTracking'){ @@ -1082,15 +1064,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.$$route && (!/^\/(login|logout)/.test(next.$$route.originalPath))) { - // if not headed to /login or /logout, then check the license - CheckLicense.test(); - } } activateTab(); }); $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams) { + // catch license expiration notifications immediately after user logs in, redirect + if (fromState.name == 'signIn'){ + CheckLicense.notify(); + } // broadcast event change if editing crud object if ($location.$$path && $location.$$path.split("/")[3] && $location.$$path.split("/")[3] === "schedules") { var list = $location.$$path.split("/")[3]; diff --git a/awx/ui/client/src/controllers/License.js b/awx/ui/client/src/controllers/License.js deleted file mode 100644 index 6bca4f5121..0000000000 --- a/awx/ui/client/src/controllers/License.js +++ /dev/null @@ -1,189 +0,0 @@ -/************************************ - * Copyright (c) 2015 Ansible, Inc. - * - * - * Organizations.js - * - * Controller functions for Organization model. - * - */ -/** - * @ngdoc function - * @name controllers.function:Organizations - * @description This controller's for the Organizations page -*/ - - -export function LicenseController(ClearScope, $location, $rootScope, $compile, $filter, GenerateForm, Rest, Alert, - GetBasePath, ProcessErrors, FormatDate, Prompt, Empty, LicenseForm, IsAdmin, CreateDialog, CheckLicense, - TextareaResize, $scope, Wait) { - - ClearScope(); - - $scope.getDefaultHTML = function(license_info) { - var fld, html, - self = this, - generator = GenerateForm; - - self.form = angular.copy(LicenseForm); - - for (fld in self.form.fields) { - if (fld !== 'time_remaining' && fld !== 'license_status' && fld !== 'tower_version') { - if (Empty(license_info[fld])) { - delete self.form.fields[fld]; - } - } - } - - if (!IsAdmin()) { - delete self.form.fields.license_key; - } - - if (license_info.is_aws || Empty(license_info.license_date)) { - delete self.form.fields.license_date; - delete self.form.fields.time_remaining; - } - - html = generator.buildHTML(self.form, { mode: 'edit', showButtons: false }); - return html; - }; - - $scope.loadDefaultScope = function(license_info, version) { - var fld, dt, days, license, - self = this; - - for (fld in self.form.fields) { - if (!Empty(license_info[fld])) { - $scope[fld] = license_info[fld]; - } - } - - $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 = parseInt($scope.time_remaining,10) * 1000; - if ($scope.time_remaining < 0) { - days = 0; - } else { - days = Math.floor($scope.time_remaining / 86400000); - } - $scope.time_remaining = (days!==1) ? $filter('number')(days, 0) + ' days' : $filter('number')(days, 0) + ' day'; // '1 day' and '0 days/2 days' or more - } - - if (parseInt($scope.free_instances) <= 0) { - $scope.free_instances_class = 'field-failure'; - } else { - $scope.free_instances_class = 'field-success'; - } - - license = license_info; - if (license.valid_key === undefined) { - $scope.license_status = 'Missing License Key'; - $scope.status_color = 'license-invalid'; - } else if (!license.valid_key) { - $scope.license_status = 'Invalid License Key'; - $scope.status_color = 'license-invalid'; - } else if (license.date_expired !== undefined && license.date_expired) { - $scope.license_status = 'License Expired'; - $scope.status_color = 'license-expired'; - } else if (license.date_warning !== undefined && license.date_warning) { - $scope.license_status = 'License Expiring Soon'; - $scope.status_color = 'license-warning'; - } else if (license.free_instances !== undefined && parseInt(license.free_instances) <= 0) { - $scope.license_status = 'No Available Managed Hosts'; - $scope.status_color = 'license-invalid'; - } else { - $scope.license_status = 'Valid License'; - $scope.status_color = 'license-valid'; - } - }; - - $scope.setLicense = function(license_info, version) { - this.license = license_info; - this.version = version; - }; - - $scope.getLicense = function(){ - return this.license; - }; - - $scope.submitLicenseKey = function() { - CheckLicense.postLicense($scope.license_json, $scope); - }; - - if ($scope.removeLicenseDataReady) { - $scope.removeLicenseDataReady(); - } - $scope.removeLicenseDataReady = $scope.$on('LicenseDataReady', function(e, data) { - var html, version, eula, h; - version = data.version.replace(/-.*$/,''); - $scope.setLicense(data.license_info, version); - html = $scope.getDefaultHTML(data.license_info); - $scope.loadDefaultScope(data.license_info, version); - eula = (data.eula) ? data.eula : "" ; - - e = angular.element(document.getElementById('license-modal-dialog')); - e.empty().html(html); - - $scope.parseType = 'json'; - $scope.license_json = JSON.stringify($scope.license, null, ' '); - $scope.eula = eula; - $scope.eula_agreement = false; - - - h = CheckLicense.getHTML($scope.getLicense(),true).body; - $('#license-modal-dialog #license_tabs').append("
  • Update License
  • "); - $('#license-modal-dialog .tab-content').append("
    "); - $('#license-modal-dialog #update_license').html(h); - - if ($scope.license_status === 'Invalid License Key' || $scope.license_status === 'Missing License Key') { - $('#license_tabs li:eq(1)').hide(); - $('#license_tabs li:eq(2) a').tab('show'); - } - - $('#license_license_json').attr('ng-required' , 'true' ); - $('#license_eula_agreement_chbox').attr('ng-required' , 'true' ); - $('#license_form_submit_btn').attr('ng-disabled' , "license_form.$invalid" ); - e = angular.element(document.getElementById('license-modal-dialog')); - $compile(e)($scope); - - if (IsAdmin()) { - setTimeout(function() { - TextareaResize({ - scope: $scope, - textareaId: 'license_license_json', - modalId: 'license-modal-dialog', - formId: 'license-notification-body', - fld: 'license_json', - parse: true, - bottom_margin: 90, - onChange: function() { $scope.license_json_api_error = ''; } - }); - }, 300); - } - $('#license-ok-button').focus(); - $('#update_license_link').on('shown.bs.tab', function() { - if (IsAdmin()) { - TextareaResize({ - scope: $scope, - textareaId: 'license_license_json', - modalId: 'license-modal-dialog', - formId: 'license-notification-body', - fld: 'license_json', - bottom_margin: 90, - parse: true, - onChange: function() { $scope.license_json_api_error = ''; } - }); - } - }); - Wait("stop"); - }); - CheckLicense.GetLicense('LicenseDataReady', $scope); - - } - -LicenseController.$inject = ['ClearScope', '$location', '$rootScope', '$compile', '$filter', 'GenerateForm', 'Rest', 'Alert', -'GetBasePath', 'ProcessErrors', 'FormatDate', 'Prompt', 'Empty', 'LicenseForm', 'IsAdmin', 'CreateDialog', -'CheckLicense', 'TextareaResize', '$scope', "Wait"]; diff --git a/awx/ui/client/src/helpers.js b/awx/ui/client/src/helpers.js index 4a277970da..8c61d61b08 100644 --- a/awx/ui/client/src/helpers.js +++ b/awx/ui/client/src/helpers.js @@ -19,7 +19,6 @@ import JobDetail from "./helpers/JobDetail"; import JobSubmission from "./helpers/JobSubmission"; import JobTemplates from "./helpers/JobTemplates"; import Jobs from "./helpers/Jobs"; -import License from "./helpers/License"; import LoadConfig from "./helpers/LoadConfig"; import LogViewer from "./helpers/LogViewer"; import Lookup from "./helpers/Lookup"; @@ -57,7 +56,6 @@ export JobSubmission, JobTemplates, Jobs, - License, LoadConfig, LogViewer, Lookup, diff --git a/awx/ui/client/src/helpers/License.js b/awx/ui/client/src/helpers/License.js deleted file mode 100644 index 70cbb59c72..0000000000 --- a/awx/ui/client/src/helpers/License.js +++ /dev/null @@ -1,271 +0,0 @@ -/************************************************* - * 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 - angular.module('LicenseHelper', ['RestServices', 'Utilities', 'LicenseUpdateFormDefinition', - 'FormGenerator', 'ParseHelper', 'ModalDialog', 'VariablesHelper', 'LicenseFormDefinition']) - - - .factory('CheckLicense', ['$q', '$rootScope', '$compile', 'CreateDialog', 'Store', - 'LicenseUpdateForm', 'GenerateForm', 'TextareaResize', 'ToJSON', 'GetBasePath', - 'Rest', 'ProcessErrors', 'Alert', 'IsAdmin', '$location', 'pendoService', - 'Authorization', 'Wait', - function($q, $rootScope, $compile, CreateDialog, Store, LicenseUpdateForm, GenerateForm, - TextareaResize, ToJSON, GetBasePath, Rest, ProcessErrors, Alert, IsAdmin, $location, - 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(); - }, - - getHTML: function(license, includeFormButton) { - - var title, html, - contact_us = "contact us ", - renew = "ansible.com/renew ", - pricing = "ansible.com/pricing ", - license_link = "click here", - result = {}, - license_is_valid=false; - - if (license && typeof license === 'object' && Object.keys(license).length > 0 && license.valid_key !== undefined) { - // we have a license - if (!license.valid_key) { - title = "Invalid License"; - html = "

    The Ansible Tower license is invalid.

    "; - } - else if (this.getRemainingDays(license.time_remaining) <= 0) { - title = "License Expired"; - html = "
    \n" + - "

    Thank you for using Ansible Tower. The Ansible Tower license has expired

    "; - if (parseInt(license.grace_period_remaining,10) > 86400) { - // trial licenses don't get a grace period - if (license.trial) { - html += "

    Don't worry — your existing history and content has not been affected, but playbooks will no longer run and new hosts cannot be added. " + - "If you are ready to upgrade, " + contact_us + " or visit " + pricing + " to see all of your license options. Thanks!

    "; - } else { - html += "

    Don't worry — your existing history and content has not been affected, but in " + this.getRemainingDays(license.grace_period_remaining) + " days playbooks will no longer " + - "run and new hosts cannot be added. If you are ready to upgrade, " + contact_us + " " + - "or visit ansible.com/pricing to see all of your license options. Thanks!

    "; - } - } else { - html += "

    Don’t worry — your existing history and content has not been affected, but playbooks will no longer run and new hosts cannot be added. If you are ready to renew or upgrade, contact us " + - "at " + renew + ". Thanks!

    "; - } - } - 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.

    "; - // trial licenses don't get a grace period - if (license.trial) { - html += "

    After this license expires, playbooks will no longer run and hosts cannot be added. If you are ready to upgrade, " + contact_us + " or visit " + pricing + " to see all of your license options. Thanks!

    "; - } else { - html += "

    After this license expires, playbooks will no longer run and hosts cannot be added. If you are ready to renew or upgrade, contact us at " + renew + ". Thanks!

    "; - } - - // If there is exactly one day remaining, change "days remaining" - // to "day remaining". - html = html.replace('has 1 days remaining', 'has 1 day remaining'); - } - 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 new hosts can be added. Existing " + - "playbooks can still be run against hosts already in inventory.

    " + - "

    If you are ready to upgrade, contact us at " + renew + ". Thanks!

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

    The Ansible Tower license is valid.

    " + - "

    If you are ready to upgrade, contact us at " + renew + ". Thanks!

    "; - license_is_valid = true; - } - } else { - // No license - title = "Add Your License"; - html = "

    Now that you’ve successfully installed or upgraded Ansible Tower, the next step is to add a license file. " + - "If you don’t have a license file yet, " + license_link + " to see all of our free and paid license options.

    " + - "

    Get a Free Tower Trial License

    "; - } - - if (IsAdmin()) { - html += "

    Copy and paste the contents of your license in the field below, agree to the End User License Agreement, and click Submit.

    "; - } else { - html += "

    A system administrator can install the new license by choosing View License on the Account Menu and clicking on the Update License tab.

    "; - } - - html += "
    "; - - if (IsAdmin()) { - html += GenerateForm.buildHTML(LicenseUpdateForm, { mode: 'edit', showButtons:((includeFormButton) ? true : false) }); - } - - html += "
    "; - - result.body = html; - result.title = title; - return result; - }, - - 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"); - $location.path('/home'); - }) - .error(function () { - Wait('stop'); - Alert('Error', 'Failed to access license information. GET returned status: ' + status, 'alert-danger', - $location.path('/logout')); - }); - - - - - }) - .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)) { - return true; // if the license is valid it would exit 'test' here, otherwise it moves on to making the modal for the license - } - - $location.path('/license'); - }); - }, - - 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 - }); - }); - } - }; - }]); diff --git a/awx/ui/client/src/license/license.block.less b/awx/ui/client/src/license/license.block.less new file mode 100644 index 0000000000..3cd5b2d213 --- /dev/null +++ b/awx/ui/client/src/license/license.block.less @@ -0,0 +1,80 @@ +@import "awx/ui/client/src/shared/branding/colors.default.less"; + +.License-container{ + display: flex; + flex-direction: row; + flex-wrap: wrap; +} +.License-container label{ + text-transform: uppercase; + color: @default-interface-txt; + font-weight: 500; +} +.License-management .CodeMirror-scroll{ + min-height: 140px; +} +.License-file textarea{ + display: block; + width: 100%; +} +.License-eula textarea{ + width: 100%; + height: 140px; +} +.License-field label{ + width: 155px; +} +.License-field span{ + width: 255px; + display: inline-block; +} +.License-field{ + word-wrap: break-word; + width: 100%; + margin-top: 4px; + margin-bottom: 4px; +} +.License-greenText{ + color: @submit-button-bg; +} +.License-redText{ + color: #d9534f; +} +.License-fields{ + display: flex; + flex-flow: row wrap; + justify-content: space-around; +} +.License-details { + height: 520px; + width: 460px; + flex-grow: 1; + display: inline-block; + background-color: @default-bg; + padding: 20px; + border-radius: 5px; + border: @default-interface-txt; + align-items: baseline; + margin-top: 20px; + margin-bottom: 20px; + margin-right: 20px; +} +.License-titleText { + color: @default-interface-txt; + font-size: 14px; + font-weight: bold; + margin-right: 10px; + margin-bottom: 10px; + text-transform: uppercase; +} +.License-management{ + height: 520px; + flex-grow: 2; + display: inline-block; + background-color: @default-bg; + padding: 20px; + border-radius: 5px; + border: @default-interface-txt; + align-items: baseline; + margin-top: 20px; +} \ No newline at end of file diff --git a/awx/ui/client/src/license/license.controller.js b/awx/ui/client/src/license/license.controller.js new file mode 100644 index 0000000000..a7cb28b56e --- /dev/null +++ b/awx/ui/client/src/license/license.controller.js @@ -0,0 +1,50 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +export default + [ 'Wait', '$state', '$scope', '$location', + 'GetBasePath', 'Rest', 'ProcessErrors', 'CheckLicense', 'moment', + function( Wait, $state, $scope, $location, + GetBasePath, Rest, ProcessErrors, CheckLicense, moment){ + // codemirror + var textArea = document.getElementById('License-codemirror'); + var editor = CodeMirror.fromTextArea(textArea, { + lineNumbers: true, + mode: 'json' + }); + editor.on('blur', function(cm){ + $scope.newLicense.file = cm.getValue() + }); + $scope.newLicense = {}; + $scope.submit = function(e){ + Wait('start') + CheckLicense.post($scope.newLicense.file, $scope.newLicense.eula) + .success(function(res){ + console.log(res) + }); + } + var calcDaysRemaining = function(ms){ + // calculate the number of days remaining on the license + var duration = moment.duration(ms); + return duration.days() + } + + var calcExpiresOn = function(days){ + // calculate the expiration date of the license + return moment().add(days, 'days').calendar() + } + Wait('start'); + CheckLicense.get() + .then(function(res){ + $scope.license = res.data; + $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'); + }); + } + ]; \ No newline at end of file diff --git a/awx/ui/client/src/license/license.partial.html b/awx/ui/client/src/license/license.partial.html new file mode 100644 index 0000000000..3584c441e6 --- /dev/null +++ b/awx/ui/client/src/license/license.partial.html @@ -0,0 +1,72 @@ +
    +
    +
    Details
    +
    +
    + + Valid + Invalid +
    +
    + + {{license.version}} +
    +
    + + {{license.license_info.license_type}} +
    +
    + + {{license.license_info.subscription_name}} +
    +
    + + {{license.license_info.license_key}} +
    +
    + + {{time.expiresOn}} +
    +
    + + {{time.remaining}} Days +
    +
    + + {{license.license_info.available_instances}} +
    +
    + + {{license.license_info.current_instances}} +
    +
    + + {{license.license_info.free_instances}} +
    +
    +
    +

    If you are ready to upgrade, please contact us by clicking the button below

    + +
    +
    +
    License Management
    +

    Copy and paste the contents of your license in the field below, agree to the End User License Agreement, and click submit.

    +
    +
    + + +
    +
    End User License Agreement
    +
    + +
    +
    +
    + + +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/awx/ui/client/src/license/license.route.js b/awx/ui/client/src/license/license.route.js new file mode 100644 index 0000000000..b32f6299ea --- /dev/null +++ b/awx/ui/client/src/license/license.route.js @@ -0,0 +1,19 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import {templateUrl} from '../shared/template-url/template-url.factory'; + +export default { + name: 'license', + route: '/license', + templateUrl: templateUrl('license/license'), + controller: 'licenseController', + data: {}, + ncyBreadcrumb: { + parent: 'setup', + label: 'LICENSE' + } +} \ No newline at end of file diff --git a/awx/ui/client/src/license/main.js b/awx/ui/client/src/license/main.js new file mode 100644 index 0000000000..c97613816d --- /dev/null +++ b/awx/ui/client/src/license/main.js @@ -0,0 +1,71 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import route from './license.route'; +import controller from './license.controller'; + +export default + angular.module('license', []) + .controller('licenseController', controller) + .factory('CheckLicense', ['$state', '$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', function($state, $rootScope, Rest, GetBasePath, ProcessErrors){ + return { + get: function() { + var defaultUrl = GetBasePath('config'); + Rest.setUrl(defaultUrl); + return Rest.get() + .success(function(res){ + return res + }) + .error(function(res, status){ + ProcessErrors($rootScope, res, status, null, {hdr: 'Error!', + msg: 'Call to '+ defaultUrl + ' failed. Return status: '+ status}); + }); + }, + post: function(license, eula){ + var defaultUrl = GetBasePath('config'); + Rest.setUrl(defaultUrl); + var data = { + eula_accepted: eula, + license_key: license + }; + console.log(data) + 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}); + }); + }, + // 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(){ + this.get() + .then(function(res){ + this.valid(res.license_info) ? null : $state.go('license'); + }); + } + + } + }]) + .run(['$stateExtender', function($stateExtender) { + $stateExtender.addState(route); + }]); \ No newline at end of file diff --git a/awx/ui/client/src/partials/license.html b/awx/ui/client/src/partials/license.html deleted file mode 100644 index c83f858354..0000000000 --- a/awx/ui/client/src/partials/license.html +++ /dev/null @@ -1,4 +0,0 @@ -
    -
    -
    -
    From 211ae9afad6300245de461dd85a7e632cecce9ca Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Mon, 29 Feb 2016 15:01:44 -0500 Subject: [PATCH 02/32] create 1/3+2/3 LESS layout, normalize site-wide footer positioning, move CheckLicense factory to separate file, upgrade URL, add file picker + form reset behavior, #1055, #1057, #1007 --- awx/ui/client/legacy-styles/main-layout.less | 2 +- awx/ui/client/src/footer/footer.block.less | 2 +- .../src/license/checkLicense.factory.js | 62 +++++++ .../src/license/fileOnChange.directive.js | 16 ++ awx/ui/client/src/license/license.block.less | 76 ++++----- .../client/src/license/license.controller.js | 53 ++++-- .../client/src/license/license.partial.html | 161 ++++++++++-------- awx/ui/client/src/license/main.js | 60 +------ .../src/shared/layouts/one-plus-two.less | 79 +++++++++ awx/ui/templates/ui/index.html | 5 +- 10 files changed, 325 insertions(+), 191 deletions(-) create mode 100644 awx/ui/client/src/license/checkLicense.factory.js create mode 100644 awx/ui/client/src/license/fileOnChange.directive.js create mode 100644 awx/ui/client/src/shared/layouts/one-plus-two.less diff --git a/awx/ui/client/legacy-styles/main-layout.less b/awx/ui/client/legacy-styles/main-layout.less index 803d554eaf..38c6ffbb19 100644 --- a/awx/ui/client/legacy-styles/main-layout.less +++ b/awx/ui/client/legacy-styles/main-layout.less @@ -60,7 +60,7 @@ body { } #content-container { - margin-top: 40px; + padding-bottom: 40px; } .group-breadcrumbs { diff --git a/awx/ui/client/src/footer/footer.block.less b/awx/ui/client/src/footer/footer.block.less index 86ca6888b5..057d926568 100644 --- a/awx/ui/client/src/footer/footer.block.less +++ b/awx/ui/client/src/footer/footer.block.less @@ -6,7 +6,7 @@ color: #848992; width: 100%; z-index: 1040; - position: absolute; + position: fixed; right: 0; left: 0; bottom: 0; diff --git a/awx/ui/client/src/license/checkLicense.factory.js b/awx/ui/client/src/license/checkLicense.factory.js new file mode 100644 index 0000000000..a654e1dc32 --- /dev/null +++ b/awx/ui/client/src/license/checkLicense.factory.js @@ -0,0 +1,62 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +export default + ['$state', '$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', function($state, $rootScope, Rest, GetBasePath, ProcessErrors){ + return { + get: function() { + var defaultUrl = GetBasePath('config'); + Rest.setUrl(defaultUrl); + return Rest.get() + .success(function(res){ + return res + }) + .error(function(res, status){ + ProcessErrors($rootScope, res, status, null, {hdr: 'Error!', + msg: 'Call to '+ defaultUrl + ' failed. Return status: '+ status}); + }); + }, + 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}); + }); + }, + // 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(){ + self = this; + this.get() + .then(function(res){ + self.valid(res.data.license_info) ? null : $state.go('license'); + }); + } + + } + } + ]; \ No newline at end of file diff --git a/awx/ui/client/src/license/fileOnChange.directive.js b/awx/ui/client/src/license/fileOnChange.directive.js new file mode 100644 index 0000000000..eac1f5ffe2 --- /dev/null +++ b/awx/ui/client/src/license/fileOnChange.directive.js @@ -0,0 +1,16 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +export default + [function(){ + return { + restrict: 'A', + link: function(scope, el, attrs){ + var onChange = scope.$eval(attrs.fileOnChange); + el.bind('change', onChange); + } + } + }]; \ No newline at end of file diff --git a/awx/ui/client/src/license/license.block.less b/awx/ui/client/src/license/license.block.less index 3cd5b2d213..58ce83542a 100644 --- a/awx/ui/client/src/license/license.block.less +++ b/awx/ui/client/src/license/license.block.less @@ -1,14 +1,17 @@ +/* +* Style conventions +* .ModuleName-component-subComponent +* Naming describes components of the view +*/ @import "awx/ui/client/src/shared/branding/colors.default.less"; +@import "awx/ui/client/src/shared/layouts/one-plus-two.less"; + .License-container{ - display: flex; - flex-direction: row; - flex-wrap: wrap; + .OnePlusTwo-container; } -.License-container label{ - text-transform: uppercase; - color: @default-interface-txt; - font-weight: 500; +.License-field--label{ + .OnePlusTwo-left--detailsLabel; } .License-management .CodeMirror-scroll{ min-height: 140px; @@ -19,20 +22,16 @@ } .License-eula textarea{ width: 100%; - height: 140px; + height: 300px; } .License-field label{ width: 155px; } -.License-field span{ - width: 255px; - display: inline-block; +.License-field--content{ + .OnePlusTwo-left--detailsContent; } .License-field{ - word-wrap: break-word; - width: 100%; - margin-top: 4px; - margin-bottom: 4px; + .OnePlusTwo-left--detailsRow; } .License-greenText{ color: @submit-button-bg; @@ -41,40 +40,27 @@ color: #d9534f; } .License-fields{ - display: flex; - flex-flow: row wrap; - justify-content: space-around; + .OnePlusTwo-left--details; } .License-details { - height: 520px; - width: 460px; - flex-grow: 1; - display: inline-block; - background-color: @default-bg; - padding: 20px; - border-radius: 5px; - border: @default-interface-txt; - align-items: baseline; - margin-top: 20px; - margin-bottom: 20px; - margin-right: 20px; + .OnePlusTwo-left--panel(600px); } .License-titleText { - color: @default-interface-txt; - font-size: 14px; - font-weight: bold; - margin-right: 10px; - margin-bottom: 10px; - text-transform: uppercase; + .OnePlusTwo-panelHeader; } .License-management{ - height: 520px; - flex-grow: 2; - display: inline-block; - background-color: @default-bg; - padding: 20px; - border-radius: 5px; - border: @default-interface-txt; - align-items: baseline; - margin-top: 20px; + .OnePlusTwo-right--panel(600px); +} +.License-submit--container{ + height: 33px; +} +.License-submit--success{ + line-height:33px; + margin: 0 10px 0 0; +} +.License-file--container { + margin: 20px 0 20px 0; + input[type=file] { + display: none; + } } \ No newline at end of file diff --git a/awx/ui/client/src/license/license.controller.js b/awx/ui/client/src/license/license.controller.js index a7cb28b56e..56a586cf0d 100644 --- a/awx/ui/client/src/license/license.controller.js +++ b/awx/ui/client/src/license/license.controller.js @@ -9,35 +9,47 @@ export default 'GetBasePath', 'Rest', 'ProcessErrors', 'CheckLicense', 'moment', function( Wait, $state, $scope, $location, GetBasePath, Rest, ProcessErrors, CheckLicense, moment){ - // codemirror - var textArea = document.getElementById('License-codemirror'); - var editor = CodeMirror.fromTextArea(textArea, { - lineNumbers: true, - mode: 'json' - }); - editor.on('blur', function(cm){ - $scope.newLicense.file = cm.getValue() - }); + $scope.getKey = function(event){ + // Mimic HTML5 spec, show filename + $scope.fileName = event.target.files[0].name; + // Grab the key from the raw license file + var raw = new FileReader(); + // readAsFoo runs async + raw.onload = function(){ + $scope.newLicense.file = JSON.parse(raw.result); + } + raw.readAsText(event.target.files[0]); + }; + // HTML5 spec doesn't provide a way to customize file input css + // So we hide the default input, show our own, and simulate clicks to the hidden input + $scope.fakeClick = function(){ + $('#License-file').click(); + //document.getElementById('License-file').click(); + } $scope.newLicense = {}; - $scope.submit = function(e){ - Wait('start') + $scope.submit = function(event){ + Wait('start'); CheckLicense.post($scope.newLicense.file, $scope.newLicense.eula) .success(function(res){ - console.log(res) + reset(); + init(); + $scope.success = true; }); - } + }; var calcDaysRemaining = function(ms){ // calculate the number of days remaining on the license var duration = moment.duration(ms); return duration.days() - } + }; var calcExpiresOn = function(days){ // calculate the expiration date of the license return moment().add(days, 'days').calendar() - } - Wait('start'); - CheckLicense.get() + }; + var init = function(){ + $scope.fileName = "Please choose a file..." + Wait('start'); + CheckLicense.get() .then(function(res){ $scope.license = res.data; $scope.time = {}; @@ -46,5 +58,10 @@ export default $scope.valid = CheckLicense.valid($scope.license.license_info); Wait('stop'); }); - } + }; + var reset = function(){ + document.getElementById('License-form').reset() + }; + init(); + } ]; \ No newline at end of file diff --git a/awx/ui/client/src/license/license.partial.html b/awx/ui/client/src/license/license.partial.html index 3584c441e6..dcbde280e7 100644 --- a/awx/ui/client/src/license/license.partial.html +++ b/awx/ui/client/src/license/license.partial.html @@ -1,72 +1,99 @@
    -
    -
    Details
    -
    -
    - - Valid - Invalid -
    -
    - - {{license.version}} -
    -
    - - {{license.license_info.license_type}} -
    -
    - - {{license.license_info.subscription_name}} -
    -
    - - {{license.license_info.license_key}} -
    -
    - - {{time.expiresOn}} -
    -
    - - {{time.remaining}} Days -
    -
    - - {{license.license_info.available_instances}} -
    -
    - - {{license.license_info.current_instances}} -
    -
    - - {{license.license_info.free_instances}} -
    -
    -
    -

    If you are ready to upgrade, please contact us by clicking the button below

    - -
    -
    -
    License Management
    -

    Copy and paste the contents of your license in the field below, agree to the End User License Agreement, and click submit.

    -
    -
    - - -
    -
    End User License Agreement
    -
    - -
    -
    -
    - - +
    +
    +
    Details
    +
    +
    +
    License
    +
    + Valid + Invalid +
    +
    +
    +
    Version
    +
    + {{license.version}} +
    +
    +
    +
    License Type
    +
    + {{license.license_info.license_type}} +
    +
    +
    +
    Subscription
    +
    + {{license.license_info.subscription_name}} +
    +
    +
    +
    License Key
    +
    + {{license.license_info.license_key}} +
    +
    +
    +
    Expires On
    +
    + {{time.expiresOn}} +
    +
    +
    +
    Time Remaining
    +
    + {{time.remaining}} Day +
    +
    +
    +
    Hosts Available
    +
    + {{license.license_info.available_instances}} +
    +
    +
    +
    Hosts Used
    +
    + {{license.license_info.current_instances}} +
    +
    +
    +
    Hosts Remaining
    +
    + {{license.license_info.free_instances}} +
    - +

    If you are ready to upgrade, please contact us by clicking the button below

    + +
    +
    +
    +
    +
    License Management
    +

    Choose your license file, agree to the End User License Agreement, and click submit.

    +
    +
    + Browse... + + +
    +
    End User License Agreement
    +
    + +
    +
    +
    +
    I agree to the End User License Agreement
    +
    + Save successful! + +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/awx/ui/client/src/license/main.js b/awx/ui/client/src/license/main.js index c97613816d..6eeb8bf12e 100644 --- a/awx/ui/client/src/license/main.js +++ b/awx/ui/client/src/license/main.js @@ -6,66 +6,14 @@ import route from './license.route'; import controller from './license.controller'; +import CheckLicense from './checkLicense.factory'; +import fileOnChange from './fileOnChange.directive'; export default angular.module('license', []) .controller('licenseController', controller) - .factory('CheckLicense', ['$state', '$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', function($state, $rootScope, Rest, GetBasePath, ProcessErrors){ - return { - get: function() { - var defaultUrl = GetBasePath('config'); - Rest.setUrl(defaultUrl); - return Rest.get() - .success(function(res){ - return res - }) - .error(function(res, status){ - ProcessErrors($rootScope, res, status, null, {hdr: 'Error!', - msg: 'Call to '+ defaultUrl + ' failed. Return status: '+ status}); - }); - }, - post: function(license, eula){ - var defaultUrl = GetBasePath('config'); - Rest.setUrl(defaultUrl); - var data = { - eula_accepted: eula, - license_key: license - }; - console.log(data) - 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}); - }); - }, - // 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(){ - this.get() - .then(function(res){ - this.valid(res.license_info) ? null : $state.go('license'); - }); - } - - } - }]) + .directive('fileOnChange', fileOnChange) + .factory('CheckLicense', CheckLicense) .run(['$stateExtender', function($stateExtender) { $stateExtender.addState(route); }]); \ No newline at end of file diff --git a/awx/ui/client/src/shared/layouts/one-plus-two.less b/awx/ui/client/src/shared/layouts/one-plus-two.less new file mode 100644 index 0000000000..fe43e40b65 --- /dev/null +++ b/awx/ui/client/src/shared/layouts/one-plus-two.less @@ -0,0 +1,79 @@ +/* +* Large resolution: 1/3 + 2/3 width panels +* Small resolution: 100% width panels, stacked +* Options: static height, custom breakpoint +* +* Style conventions +* .ModuleName-component--subComponent +*/ +@import "awx/ui/client/src/shared/branding/colors.default.less"; + + +.OnePlusTwo-container(@height: 100%; @breakpoint: 900px){ + height: @height; + display: flex; + flex-direction: row; + @media screen and (max-width: @breakpoint){ + flex-direction: column; + } +} + +.OnePlusTwo-left--panel(@height: 100%; @breakpoint: 900px) { + flex: 0 0; + height: @height; + width: 100%; + .Panel{ + height: 100%; + } + @media screen and (min-width: @breakpoint){ + max-width: 400px; + } +} + +.OnePlusTwo-right--panel(@height: 100%; @breakpoint: 900px) { + height: @height; + flex: 1 0; + margin-left: 20px; + .Panel{ + height: 100%; + } + @media screen and (max-width: @breakpoint){ + flex-direction: column; + margin-left: 0px; + margin-top: 25px; + } +} + +.OnePlusTwo-panelHeader { + color: @default-interface-txt; + font-size: 14px; + font-weight: bold; + margin-right: 10px; + text-transform: uppercase; +} + +.OnePlusTwo-left--details { + margin-top: 25px; +} + +.OnePlusTwo-left--detailsRow { + display: flex; + :not(:last-child){ + margin-bottom: 20px; + } +} + +.OnePlusTwo-left--detailsLabel { + width: 140px; + display: inline-block; + color: @default-interface-txt; + text-transform: uppercase; + font-weight: 400; +} + +.OnePlusTwo-left--detailsContent { + display: inline-block; + max-width: 220px; + word-wrap: break-word; +} + diff --git a/awx/ui/templates/ui/index.html b/awx/ui/templates/ui/index.html index b431239a5a..79111c287d 100644 --- a/awx/ui/templates/ui/index.html +++ b/awx/ui/templates/ui/index.html @@ -41,7 +41,7 @@ -
    +
    @@ -221,8 +221,7 @@

    working...

    - - +