Merge pull request #1036 from leigh-johnson/LicenseMod

new License module, nuke old License module, resolves #1006, #1007, #1055, #1057
This commit is contained in:
Leigh 2016-03-02 13:44:05 -05:00
commit 09da3c6f82
17 changed files with 440 additions and 497 deletions

View File

@ -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;

View File

@ -60,7 +60,7 @@ body {
}
#content-container {
margin-top: 40px;
padding-bottom: 40px;
}
.group-breadcrumbs {

View File

@ -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];

View File

@ -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("<li><a id=\"update_license_link\" ng-click=\"toggleTab($event, 'update_license_link', 'license_tabs')\" href=\"#update_license\" data-toggle=\"tab\">Update License</a></li>");
$('#license-modal-dialog .tab-content').append("<div class=\"tab-pane\" id=\"update_license\"></div>");
$('#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"];

View File

@ -6,7 +6,7 @@
color: #848992;
width: 100%;
z-index: 1040;
position: absolute;
position: fixed;
right: 0;
left: 0;
bottom: 0;

View File

@ -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,

View File

@ -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 = "<a href=\"http://www.ansible.com/contact-us\" target=\"_black\">contact us <i class=\"fa fa-external-link\"></i></a>",
renew = "<a href=\"http://www.ansible.com/renew\" target=\"_blank\">ansible.com/renew <i class=\"fa fa-external-link\"></i></a>",
pricing = "<a href=\"http://www.ansible.com/pricing\" target=\"_blank\">ansible.com/pricing <i class=\"fa fa-external-link\"></i></a>",
license_link = "<a href=\"http://www.ansible.com/license\" target=\"_blank\">click here</a>",
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 = "<div id=\"license-notification-body\"><div style=\"margin-top:5px; margin-bottom:25px;\"><p>The Ansible Tower license is invalid.</p>";
}
else if (this.getRemainingDays(license.time_remaining) <= 0) {
title = "License Expired";
html = "<div id=\"license-notification-body\"><div style=\"margin-top:5px; margin-bottom:25px;\">\n" +
"<p>Thank you for using Ansible Tower. The Ansible Tower license has expired</p>";
if (parseInt(license.grace_period_remaining,10) > 86400) {
// trial licenses don't get a grace period
if (license.trial) {
html += "<p>Don't worry &mdash; 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!</p>";
} else {
html += "<p>Don't worry &mdash; 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 <a href=\"http://www.ansible.com/pricing\" target=\"_blank\">ansible.com/pricing <i class=\"fa fa-external-link\"></i></a> to see all of your license options. Thanks!</p>";
}
} else {
html += "<p>Dont worry &mdash; 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!</p>";
}
}
else if (this.getRemainingDays(license.time_remaining) < 15) {
// Warning: license expiring in less than 15 days
title = "License Warning";
html = "<div id=\"license-notification-body\"><div style=\"margin-top:5px; margin-bottom:25px;\"><p>Thank you for using Ansible Tower. The Ansible Tower license " +
"has " + this.getRemainingDays(license.time_remaining) + " days remaining.</p>";
// trial licenses don't get a grace period
if (license.trial) {
html += "<p>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!</p>";
} else {
html += "<p>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!</p>";
}
// 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 = "<div id=\"license-notification-body\"><div style=\"margin-top:5px; margin-bottom:25px;\"><p>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.</p>" +
"<p>If you are ready to upgrade, contact us at " + renew + ". Thanks!</p>";
} else {
// license is valid. the following text is displayed in the license viewer
title = "Update License";
html = "<div id=\"license-notification-body\"><div style=\"margin-top:5px; margin-bottom:25px;\"><p>The Ansible Tower license is valid.</p>" +
"<p>If you are ready to upgrade, contact us at " + renew + ". Thanks!</p>";
license_is_valid = true;
}
} else {
// No license
title = "Add Your License";
html = "<div id=\"license-notification-body\"><div style=\"margin-top:5px; margin-bottom:25px;\"><p>Now that youve successfully installed or upgraded Ansible Tower, the next step is to add a license file. " +
"If you dont have a license file yet, " + license_link + " to see all of our free and paid license options.</p>" +
"<p style=\"margin-top:15px; margin-bottom 15px; text-align:center;\"><a href=\"http://ansible.com/license\" target=\"_blank\" class=\"btn btn-danger free-button\">Get a Free Tower Trial License</a></p>";
}
if (IsAdmin()) {
html += "<p>Copy and paste the contents of your license in the field below, agree to the End User License Agreement, and click Submit.</p>";
} else {
html += "<p>A system administrator can install the new license by choosing View License on the Account Menu and clicking on the Update License tab.</p>";
}
html += "</div>";
if (IsAdmin()) {
html += GenerateForm.buildHTML(LicenseUpdateForm, { mode: 'edit', showButtons:((includeFormButton) ? true : false) });
}
html += "</div>";
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
});
});
}
};
}]);

View File

@ -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');
});
}
}
}
];

View File

@ -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);
}
}
}];

View File

@ -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;
}
}

View File

@ -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();
}
];

View File

@ -0,0 +1,99 @@
<div class="License-container">
<div class="License-details">
<div class="Panel">
<div class="License-titleText">Details</div>
<div class="License-fields">
<div class="License-field">
<div class="License-field--label">License</div>
<div class="License-field--content">
<span ng-show='valid'><i class="fa fa-circle License-greenText"></i> Valid</span>
<span ng-show='invalid'><i class="fa fa-circle License-redText"></i> Invalid</span>
</div>
</div>
<div class="License-field">
<div class="License-field--label">Version</div>
<div class="License-field--content">
{{license.version}}
</div>
</div>
<div class="License-field">
<div class="License-field--label">License Type</div>
<div class="License-field--content">
{{license.license_info.license_type}}
</div>
</div>
<div class="License-field">
<div class="License-field--label">Subscription</div>
<div class="License-field--content">
{{license.license_info.subscription_name}}
</div>
</div>
<div class="License-field">
<div class="License-field--label">License Key</div>
<div class="License-field--content">
{{license.license_info.license_key}}
</div>
</div>
<div class="License-field">
<div class="License-field--label">Expires On</div>
<div class="License-field--content">
{{time.expiresOn}}
</div>
</div>
<div class="License-field">
<div class="License-field--label">Time Remaining</div>
<div class="License-field--content">
{{time.remaining}} Day
</div>
</div>
<div class="License-field">
<div class="License-field--label">Hosts Available</div>
<div class="License-field--content">
{{license.license_info.available_instances}}
</div>
</div>
<div class="License-field">
<div class="License-field--label">Hosts Used</div>
<div class="License-field--content">
{{license.license_info.current_instances}}
</div>
</div>
<div class="License-field License-greenText">
<div class="License-field--label">Hosts Remaining</div>
<div class="License-field--content">
{{license.license_info.free_instances}}
</div>
</div>
</div>
<p>If you are ready to upgrade, please contact us by clicking the button below</p>
<a href="https://www.ansible.com/renew" target="_blank"><button class="btn btn-default">Upgrade</button></a>
</div>
</div>
<div class="License-management">
<div class="Panel">
<div class="License-titleText">License Management</div>
<p>Choose your license file, agree to the End User License Agreement, and click submit.</p>
<form id="License-form" name="license">
<div class="input-group License-file--container">
<span class="btn btn-default input-group-addon" ng-click="fakeClick()">Browse...</span>
<input class="form-control" ng-disabled="true" placeholder="{{fileName}}" />
<input id="License-file" class="form-control" type="file" file-on-change="getKey"/>
</div>
<div class="License-titleText prepend-asterisk"> End User License Agreement</div>
<div class="form-group License-eula">
<textarea class="form-control">{{license.eula}}
</textarea>
</div>
<div class="form-group">
<div class="checkbox">
<div class="License-details--label"><input type="checkbox" ng-model="newLicense.eula" required> I agree to the End User License Agreement</div>
<div class="License-submit--container pull-right">
<span ng-hide="success == null || false" class="License-greenText License-submit--success pull-left">Save successful!</span>
<button ng-click="submit()" class="btn btn-success pull-right" ng-disabled="newLicense.file.license_key == null || newLicense.eula == null">Submit</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>

View File

@ -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'
}
}

View File

@ -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);
}]);

View File

@ -1,4 +0,0 @@
<div class="tab-pane" id="license-partial">
<div ng-cloak id="htmlTemplate" class="Panel"></div>
<div id="license-modal-dialog"></div>
</div>

View File

@ -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;
}

View File

@ -41,7 +41,7 @@
<main-menu></main-menu>
<bread-crumb></bread-crumb>
<div class="container-fluid" id="#content-container">
<div class="container-fluid" id="content-container">
<div class="row">
<div class="col-lg-12">
<div ui-view id="main-view"></div>
@ -221,8 +221,7 @@
<div class="overlay"></div>
<div class="spinny"><i class="fa fa-cog fa-spin fa-2x"></i> <p>working...</p></div>
<!-- <div class="site-footer"></div> -->
</div>
<tower-footer></tower-footer>
<script>
// HACK: Need this to support global-dependent