mirror of
https://github.com/ansible/awx.git
synced 2026-01-13 19:10:07 -03:30
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:
parent
0971642d20
commit
211ae9afad
@ -60,7 +60,7 @@ body {
|
||||
}
|
||||
|
||||
#content-container {
|
||||
margin-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.group-breadcrumbs {
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
color: #848992;
|
||||
width: 100%;
|
||||
z-index: 1040;
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
|
||||
62
awx/ui/client/src/license/checkLicense.factory.js
Normal file
62
awx/ui/client/src/license/checkLicense.factory.js
Normal 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');
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
];
|
||||
16
awx/ui/client/src/license/fileOnChange.directive.js
Normal file
16
awx/ui/client/src/license/fileOnChange.directive.js
Normal 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);
|
||||
}
|
||||
}
|
||||
}];
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
];
|
||||
@ -1,72 +1,99 @@
|
||||
<div class="License-container">
|
||||
<div class="License-details ng-cloak">
|
||||
<div class="License-titleText">Details</div>
|
||||
<div class="License-fields">
|
||||
<div class="License-field">
|
||||
<label>License</label>
|
||||
<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 class="License-field">
|
||||
<label>Version</label>
|
||||
<span>{{license.version}}</span>
|
||||
</div>
|
||||
<div class="License-field">
|
||||
<label>License Type</label>
|
||||
<span>{{license.license_info.license_type}}</span>
|
||||
</div>
|
||||
<div class="License-field">
|
||||
<label>Subscription</label>
|
||||
<span>{{license.license_info.subscription_name}}</span>
|
||||
</div>
|
||||
<div class="License-field">
|
||||
<label>License Key</label>
|
||||
<span>{{license.license_info.license_key}}</span>
|
||||
</div>
|
||||
<div class="License-field">
|
||||
<label>Expires On</label>
|
||||
<span>{{time.expiresOn}}</span>
|
||||
</div>
|
||||
<div class="License-field">
|
||||
<label>Time Remaining</label>
|
||||
<span>{{time.remaining}} Days</span>
|
||||
</div>
|
||||
<div class="License-field">
|
||||
<label>Hosts Available</label>
|
||||
<span>{{license.license_info.available_instances}}</span>
|
||||
</div>
|
||||
<div class="License-field">
|
||||
<label>Hosts Used</label>
|
||||
<span>{{license.license_info.current_instances}}</span>
|
||||
</div>
|
||||
<div class="License-field License-greenText">
|
||||
<label>Hosts Remaining</label>
|
||||
<span>{{license.license_info.free_instances}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<p>If you are ready to upgrade, please contact us by clicking the button below</p>
|
||||
<button class="btn btn-default">Upgrade</button>
|
||||
</div>
|
||||
<div class="License-management">
|
||||
<div class="License-titleText">License Management</div>
|
||||
<p>Copy and paste the contents of your license in the field below, agree to the End User License Agreement, and click submit.</p>
|
||||
<form name="license">
|
||||
<div class="form-group License-file prepend-asterisk ">
|
||||
<label>License File</label>
|
||||
<textarea id="License-codemirror" placeholder="Please paste your license here"></textarea>
|
||||
</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">
|
||||
<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 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>
|
||||
</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>
|
||||
@ -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);
|
||||
}]);
|
||||
79
awx/ui/client/src/shared/layouts/one-plus-two.less
Normal file
79
awx/ui/client/src/shared/layouts/one-plus-two.less
Normal 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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user