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

This commit is contained in:
Leigh Johnson
2016-02-29 15:01:44 -05:00
parent 0971642d20
commit 211ae9afad
10 changed files with 325 additions and 191 deletions

View File

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

View File

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

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

@@ -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/branding/colors.default.less";
@import "awx/ui/client/src/shared/layouts/one-plus-two.less";
.License-container{ .License-container{
display: flex; .OnePlusTwo-container;
flex-direction: row;
flex-wrap: wrap;
} }
.License-container label{ .License-field--label{
text-transform: uppercase; .OnePlusTwo-left--detailsLabel;
color: @default-interface-txt;
font-weight: 500;
} }
.License-management .CodeMirror-scroll{ .License-management .CodeMirror-scroll{
min-height: 140px; min-height: 140px;
@@ -19,20 +22,16 @@
} }
.License-eula textarea{ .License-eula textarea{
width: 100%; width: 100%;
height: 140px; height: 300px;
} }
.License-field label{ .License-field label{
width: 155px; width: 155px;
} }
.License-field span{ .License-field--content{
width: 255px; .OnePlusTwo-left--detailsContent;
display: inline-block;
} }
.License-field{ .License-field{
word-wrap: break-word; .OnePlusTwo-left--detailsRow;
width: 100%;
margin-top: 4px;
margin-bottom: 4px;
} }
.License-greenText{ .License-greenText{
color: @submit-button-bg; color: @submit-button-bg;
@@ -41,40 +40,27 @@
color: #d9534f; color: #d9534f;
} }
.License-fields{ .License-fields{
display: flex; .OnePlusTwo-left--details;
flex-flow: row wrap;
justify-content: space-around;
} }
.License-details { .License-details {
height: 520px; .OnePlusTwo-left--panel(600px);
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 { .License-titleText {
color: @default-interface-txt; .OnePlusTwo-panelHeader;
font-size: 14px;
font-weight: bold;
margin-right: 10px;
margin-bottom: 10px;
text-transform: uppercase;
} }
.License-management{ .License-management{
height: 520px; .OnePlusTwo-right--panel(600px);
flex-grow: 2; }
display: inline-block; .License-submit--container{
background-color: @default-bg; height: 33px;
padding: 20px; }
border-radius: 5px; .License-submit--success{
border: @default-interface-txt; line-height:33px;
align-items: baseline; margin: 0 10px 0 0;
margin-top: 20px; }
.License-file--container {
margin: 20px 0 20px 0;
input[type=file] {
display: none;
}
} }

View File

@@ -9,35 +9,47 @@ export default
'GetBasePath', 'Rest', 'ProcessErrors', 'CheckLicense', 'moment', 'GetBasePath', 'Rest', 'ProcessErrors', 'CheckLicense', 'moment',
function( Wait, $state, $scope, $location, function( Wait, $state, $scope, $location,
GetBasePath, Rest, ProcessErrors, CheckLicense, moment){ GetBasePath, Rest, ProcessErrors, CheckLicense, moment){
// codemirror $scope.getKey = function(event){
var textArea = document.getElementById('License-codemirror'); // Mimic HTML5 spec, show filename
var editor = CodeMirror.fromTextArea(textArea, { $scope.fileName = event.target.files[0].name;
lineNumbers: true, // Grab the key from the raw license file
mode: 'json' var raw = new FileReader();
}); // readAsFoo runs async
editor.on('blur', function(cm){ raw.onload = function(){
$scope.newLicense.file = cm.getValue() $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.newLicense = {};
$scope.submit = function(e){ $scope.submit = function(event){
Wait('start') Wait('start');
CheckLicense.post($scope.newLicense.file, $scope.newLicense.eula) CheckLicense.post($scope.newLicense.file, $scope.newLicense.eula)
.success(function(res){ .success(function(res){
console.log(res) reset();
init();
$scope.success = true;
}); });
} };
var calcDaysRemaining = function(ms){ var calcDaysRemaining = function(ms){
// calculate the number of days remaining on the license // calculate the number of days remaining on the license
var duration = moment.duration(ms); var duration = moment.duration(ms);
return duration.days() return duration.days()
} };
var calcExpiresOn = function(days){ var calcExpiresOn = function(days){
// calculate the expiration date of the license // calculate the expiration date of the license
return moment().add(days, 'days').calendar() return moment().add(days, 'days').calendar()
} };
Wait('start'); var init = function(){
CheckLicense.get() $scope.fileName = "Please choose a file..."
Wait('start');
CheckLicense.get()
.then(function(res){ .then(function(res){
$scope.license = res.data; $scope.license = res.data;
$scope.time = {}; $scope.time = {};
@@ -46,5 +58,10 @@ export default
$scope.valid = CheckLicense.valid($scope.license.license_info); $scope.valid = CheckLicense.valid($scope.license.license_info);
Wait('stop'); Wait('stop');
}); });
} };
var reset = function(){
document.getElementById('License-form').reset()
};
init();
}
]; ];

View File

@@ -1,72 +1,99 @@
<div class="License-container"> <div class="License-container">
<div class="License-details ng-cloak"> <div class="License-details">
<div class="License-titleText">Details</div> <div class="Panel">
<div class="License-fields"> <div class="License-titleText">Details</div>
<div class="License-field"> <div class="License-fields">
<label>License</label> <div class="License-field">
<span ng-show='valid'><i class="fa fa-circle License-greenText"></i> Valid</span> <div class="License-field--label">License</div>
<span ng-show='invalid'><i class="fa fa-circle License-redText"></i> Invalid</span> <div class="License-field--content">
</div> <span ng-show='valid'><i class="fa fa-circle License-greenText"></i> Valid</span>
<div class="License-field"> <span ng-show='invalid'><i class="fa fa-circle License-redText"></i> Invalid</span>
<label>Version</label> </div>
<span>{{license.version}}</span> </div>
</div> <div class="License-field">
<div class="License-field"> <div class="License-field--label">Version</div>
<label>License Type</label> <div class="License-field--content">
<span>{{license.license_info.license_type}}</span> {{license.version}}
</div> </div>
<div class="License-field"> </div>
<label>Subscription</label> <div class="License-field">
<span>{{license.license_info.subscription_name}}</span> <div class="License-field--label">License Type</div>
</div> <div class="License-field--content">
<div class="License-field"> {{license.license_info.license_type}}
<label>License Key</label> </div>
<span>{{license.license_info.license_key}}</span> </div>
</div> <div class="License-field">
<div class="License-field"> <div class="License-field--label">Subscription</div>
<label>Expires On</label> <div class="License-field--content">
<span>{{time.expiresOn}}</span> {{license.license_info.subscription_name}}
</div> </div>
<div class="License-field"> </div>
<label>Time Remaining</label> <div class="License-field">
<span>{{time.remaining}} Days</span> <div class="License-field--label">License Key</div>
</div> <div class="License-field--content">
<div class="License-field"> {{license.license_info.license_key}}
<label>Hosts Available</label> </div>
<span>{{license.license_info.available_instances}}</span> </div>
</div> <div class="License-field">
<div class="License-field"> <div class="License-field--label">Expires On</div>
<label>Hosts Used</label> <div class="License-field--content">
<span>{{license.license_info.current_instances}}</span> {{time.expiresOn}}
</div> </div>
<div class="License-field License-greenText"> </div>
<label>Hosts Remaining</label> <div class="License-field">
<span>{{license.license_info.free_instances}}</span> <div class="License-field--label">Time Remaining</div>
</div> <div class="License-field--content">
</div> {{time.remaining}} Day
<br> </div>
<p>If you are ready to upgrade, please contact us by clicking the button below</p> </div>
<button class="btn btn-default">Upgrade</button> <div class="License-field">
</div> <div class="License-field--label">Hosts Available</div>
<div class="License-management"> <div class="License-field--content">
<div class="License-titleText">License Management</div> {{license.license_info.available_instances}}
<p>Copy and paste the contents of your license in the field below, agree to the End User License Agreement, and click submit.</p> </div>
<form name="license"> </div>
<div class="form-group License-file prepend-asterisk "> <div class="License-field">
<label>License File</label> <div class="License-field--label">Hosts Used</div>
<textarea id="License-codemirror" placeholder="Please paste your license here"></textarea> <div class="License-field--content">
</div> {{license.license_info.current_instances}}
<div class="License-titleText prepend-asterisk"> End User License Agreement</div> </div>
<div class="form-group License-eula"> </div>
<textarea class="form-control">{{license.eula}} <div class="License-field License-greenText">
</textarea> <div class="License-field--label">Hosts Remaining</div>
</div> <div class="License-field--content">
<div class="form-group"> {{license.license_info.free_instances}}
<div class="checkbox"> </div>
<label><input type="checkbox" ng-model="newLicense.eula" required> I agree to the End User License Agreement</label>
<button ng-click="submit()" class="btn btn-success pull-right" ng-disabled="newLicense.file == null || newLicense.eula == null">Submit</button>
</div> </div>
</div> </div>
</form> <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>
</div> </div>

View File

@@ -6,66 +6,14 @@
import route from './license.route'; import route from './license.route';
import controller from './license.controller'; import controller from './license.controller';
import CheckLicense from './checkLicense.factory';
import fileOnChange from './fileOnChange.directive';
export default export default
angular.module('license', []) angular.module('license', [])
.controller('licenseController', controller) .controller('licenseController', controller)
.factory('CheckLicense', ['$state', '$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', function($state, $rootScope, Rest, GetBasePath, ProcessErrors){ .directive('fileOnChange', fileOnChange)
return { .factory('CheckLicense', CheckLicense)
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) { .run(['$stateExtender', function($stateExtender) {
$stateExtender.addState(route); $stateExtender.addState(route);
}]); }]);

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