diff --git a/awx/ui/client/legacy-styles/ansible-ui.less b/awx/ui/client/legacy-styles/ansible-ui.less index 06d924b141..6df1c3bada 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/legacy-styles/main-layout.less b/awx/ui/client/legacy-styles/main-layout.less index 5b7f8f1c01..48d6709168 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/app.js b/awx/ui/client/src/app.js index 4cbd091356..8b0757f370 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'; @@ -47,7 +46,6 @@ import activityStream from './activity-stream/main'; import standardOut from './standard-out/main'; import lookUpHelper from './lookup/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'; @@ -80,6 +78,7 @@ var tower = angular.module('Tower', [ // 'ngAnimate', 'ngSanitize', 'ngCookies', + license.name, RestServices.name, browserData.name, systemTracking.name, @@ -100,7 +99,6 @@ var tower = angular.module('Tower', [ standardOut.name, 'templates', 'Utilities', - 'LicenseHelper', 'OrganizationFormDefinition', 'UserFormDefinition', 'FormGenerator', @@ -859,21 +857,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', @@ -1043,7 +1026,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'){ @@ -1083,15 +1065,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/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/helpers.js b/awx/ui/client/src/helpers.js index 0e47fbf52b..9d6875f145 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 PaginationHelpers from "./helpers/PaginationHelpers"; import Parse from "./helpers/Parse"; @@ -55,7 +54,6 @@ export JobSubmission, JobTemplates, Jobs, - License, LoadConfig, PaginationHelpers, Parse, 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/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 new file mode 100644 index 0000000000..58ce83542a --- /dev/null +++ b/awx/ui/client/src/license/license.block.less @@ -0,0 +1,66 @@ +/* +* 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{ + .OnePlusTwo-container; +} +.License-field--label{ + .OnePlusTwo-left--detailsLabel; +} +.License-management .CodeMirror-scroll{ + min-height: 140px; +} +.License-file textarea{ + display: block; + width: 100%; +} +.License-eula textarea{ + width: 100%; + height: 300px; +} +.License-field label{ + width: 155px; +} +.License-field--content{ + .OnePlusTwo-left--detailsContent; +} +.License-field{ + .OnePlusTwo-left--detailsRow; +} +.License-greenText{ + color: @submit-button-bg; +} +.License-redText{ + color: #d9534f; +} +.License-fields{ + .OnePlusTwo-left--details; +} +.License-details { + .OnePlusTwo-left--panel(600px); +} +.License-titleText { + .OnePlusTwo-panelHeader; +} +.License-management{ + .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 new file mode 100644 index 0000000000..6e2801a85f --- /dev/null +++ b/awx/ui/client/src/license/license.controller.js @@ -0,0 +1,66 @@ +/************************************************* + * 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){ + $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(); + } + $scope.newLicense = {}; + $scope.submit = function(event){ + Wait('start'); + CheckLicense.post($scope.newLicense.file, $scope.newLicense.eula) + .success(function(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() + }; + var init = function(){ + $scope.fileName = "Please choose a file..." + 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'); + }); + }; + 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 new file mode 100644 index 0000000000..dcbde280e7 --- /dev/null +++ b/awx/ui/client/src/license/license.partial.html @@ -0,0 +1,99 @@ +
    +
    +
    +
    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/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..6eeb8bf12e --- /dev/null +++ b/awx/ui/client/src/license/main.js @@ -0,0 +1,19 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +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) + .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/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 @@ -
    -
    -
    -
    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...

    - - +