Fix UI for real license handling
Following this change, the user gets detailed errors and entitlement information
This commit is contained in:
parent
e450b109a2
commit
213cc856e4
9 changed files with 172 additions and 136 deletions
|
@ -288,13 +288,12 @@ class SuperUserSetAndValidateLicense(ApiResource):
|
|||
|
||||
statuses = decoded_license.validate({})
|
||||
all_met = all(status.is_met() for status in statuses)
|
||||
if not all_met:
|
||||
raise InvalidRequest('License is insufficient')
|
||||
if all_met:
|
||||
config_provider.save_license(license_contents)
|
||||
|
||||
config_provider.save_license(license_contents)
|
||||
return {
|
||||
'decoded': {},
|
||||
'success': True
|
||||
'status': [status.as_dict(for_private=True) for status in statuses],
|
||||
'success': all_met,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -856,12 +856,10 @@ class SuperUserLicense(ApiResource):
|
|||
|
||||
statuses = decoded_license.validate(app.config)
|
||||
all_met = all(status.is_met() for status in statuses)
|
||||
if not all_met:
|
||||
raise InvalidRequest('License is insufficient')
|
||||
|
||||
return {
|
||||
'decoded': {},
|
||||
'success': True
|
||||
'status': [status.as_dict(for_private=True) for status in statuses],
|
||||
'success': all_met,
|
||||
}
|
||||
|
||||
abort(403)
|
||||
|
@ -882,16 +880,14 @@ class SuperUserLicense(ApiResource):
|
|||
|
||||
statuses = decoded_license.validate(app.config)
|
||||
all_met = all(status.is_met() for status in statuses)
|
||||
if not all_met:
|
||||
raise InvalidRequest('License is insufficient')
|
||||
|
||||
config_provider.save_license(license_contents)
|
||||
|
||||
license_validator.compute_license_sufficiency()
|
||||
if all_met:
|
||||
# Save the license and update the license check thread.
|
||||
config_provider.save_license(license_contents)
|
||||
license_validator.compute_license_sufficiency()
|
||||
|
||||
return {
|
||||
'decoded': {},
|
||||
'success': True
|
||||
'status': [status.as_dict(for_private=True) for status in statuses],
|
||||
'success': all_met,
|
||||
}
|
||||
|
||||
abort(403)
|
||||
|
|
|
@ -567,6 +567,11 @@ a:focus {
|
|||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.config-license-field-element .required {
|
||||
background-color: #f5f5f5;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.config-license-field-element textarea {
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
|
@ -577,9 +582,8 @@ a:focus {
|
|||
margin-bottom: 26px;
|
||||
}
|
||||
|
||||
.config-license-field-element table td:first-child {
|
||||
width: 150px;
|
||||
font-weight: bold;
|
||||
.config-license-field-element table {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.config-license-field-element .fa {
|
||||
|
@ -594,6 +598,10 @@ a:focus {
|
|||
color: #D64456;
|
||||
}
|
||||
|
||||
.config-license-field-element li {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.co-checkbox {
|
||||
position: relative;
|
||||
}
|
||||
|
|
|
@ -36,6 +36,10 @@
|
|||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.initial-setup-modal .config-license-field {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.initial-setup-modal .license-valid .fa {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
@ -43,14 +47,3 @@
|
|||
.initial-setup-modal .license-valid table {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.initial-setup-modal .license-valid table td {
|
||||
border: 0px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.initial-setup-modal .license-valid table td:first-child {
|
||||
font-weight: bold;
|
||||
max-width: 100px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
|
|
@ -3,19 +3,54 @@
|
|||
config if the license is invalid (since this box will be empty and therefore "required") -->
|
||||
<input type="text" name="licenseRequiredBox" ng-model="requiredBox" style="visibility: hidden; height: 1px; position: absolute;" required>
|
||||
|
||||
<div class="cor-loader-inline" ng-show="state == 'loading-license'"></div>
|
||||
<div class="cor-loader-inline" ng-show="state == LicenseStates.validating"></div>
|
||||
|
||||
<div class="license-valid license-status" ng-show="state == 'license-valid'">
|
||||
<div class="license-valid license-status" ng-show="state == LicenseStates.valid">
|
||||
<h4><i class="fa fa-check-circle"></i>License Valid</h4>
|
||||
<table class="co-table">
|
||||
<tr><td>Product:</td><td>{{ licenseDecoded.publicProductName || licenseDecoded.productName }}</td></tr>
|
||||
<tr><td>Plan:</td><td>{{ licenseDecoded.publicPlanName || licenseDecoded.planName }}</td></tr>
|
||||
<thead>
|
||||
<td>Requirement</td>
|
||||
<td>Required Count</td>
|
||||
<td>Subscription</td>
|
||||
<td>Subscription Count</td>
|
||||
<td>Expiration Date</td>
|
||||
</thead>
|
||||
<tr ng-repeat="status in licenseStatus">
|
||||
<td>{{ requirementTitles[status.requirement.name] }}</td>
|
||||
<td>{{ status.requirement.count }}</td>
|
||||
<td>{{ status.entitlement.product_name }}</td>
|
||||
<td>{{ status.entitlement.count }}</td>
|
||||
<td><span am-time-ago="status.entitlement.expiration.expiration_date" data-title="{{ status.entitlement.expiration.expiration_date }}" bs-tooltip></span></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="license-invalid license-status" ng-show="state == 'license-error'">
|
||||
<div class="license-invalid license-status" ng-show="state == LicenseStates.invalid">
|
||||
<h4><i class="fa fa-times-circle"></i> Validation Failed</h4>
|
||||
<h5>{{ licenseError }}</h5>
|
||||
<h5 ng-if="licenseError">{{ licenseError }}</h5>
|
||||
<h5 ng-if="!licenseError && licenseStatus">
|
||||
<p>The following errors were found:</p>
|
||||
<ul>
|
||||
<li ng-repeat="status in licenseStatus" ng-if="status.status != 'EntitlementStatus.met'">
|
||||
<div ng-switch on="status.status">
|
||||
<!-- insufficient_count -->
|
||||
<div ng-switch-when="EntitlementStatus.insufficient_count">
|
||||
<strong>{{ requirementTitles[status.requirement.name] }}</strong>: <code class="required">{{ status.requirement.count }}</code> <span ng-if="status.requirement.count != 1">are</span><span ng-if="status.requirement.count == 1">is</span> required: License provides <code>{{ status.entitlement.count }}</code>
|
||||
</div>
|
||||
|
||||
<!-- no_matching -->
|
||||
<div ng-switch-when="EntitlementStatus.no_matching">
|
||||
<strong>{{ requirementTitles[status.requirement.name] }}</strong>: License is missing requirement
|
||||
</div>
|
||||
|
||||
<!-- expired -->
|
||||
<div ng-switch-when="EntitlementStatus.expired">
|
||||
<strong>{{ requirementTitles[status.requirement.name] }}</strong>: Requirement expired on <code>{{ status.entitlement.expiration.expiration_date }}</code>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-default" ng-show="!showingEditor" ng-click="showEditor($event)"><i class="fa fa-pencil"></i> Update License</button>
|
||||
|
@ -28,12 +63,12 @@
|
|||
|
||||
<textarea id="enterLicenseBox" ng-model="licenseContents" class="form-control"
|
||||
placeholder="Paste your raw license here, which should already be in base64 format: GtqMjMwNDgyM3Vq..."
|
||||
ng-readonly="state == 'validating-license'"></textarea>
|
||||
ng-readonly="state == LicenseStates.validating"></textarea>
|
||||
|
||||
<button class="btn btn-primary" ng-show="state != 'validating-license'"
|
||||
<button class="btn btn-primary" ng-show="state != LicenseStates.validating"
|
||||
ng-click="validateAndUpdate($event)" ng-disabled="!licenseContents">Update License</button>
|
||||
|
||||
<div class="license-validating" ng-show="state == 'validating-license'">
|
||||
<div class="license-validating" ng-show="state == LicenseStates.validating">
|
||||
<span class="cor-loader-inline"></span> Validating License
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1256,24 +1256,52 @@ angular.module("core-config-setup", ['angularFileUpload'])
|
|||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'isValid': '=?isValid',
|
||||
'forSetup': '@forSetup'
|
||||
},
|
||||
controller: function($scope, $element, ApiService, UserService) {
|
||||
$scope.state = 'loading-license';
|
||||
$scope.showingEditor = false;
|
||||
$scope.LicenseStates = {
|
||||
none: 'no-license',
|
||||
loading: 'license-loading',
|
||||
valid: 'license-valid',
|
||||
invalid: 'license-error',
|
||||
validating: 'validating-license'
|
||||
};
|
||||
|
||||
$scope.state = $scope.forSetup == 'true' ? $scope.LicenseStates.none : $scope.LicenseStates.loading;
|
||||
$scope.showingEditor = $scope.forSetup == 'true';
|
||||
$scope.requiredBox = '';
|
||||
|
||||
$scope.requirementTitles = {
|
||||
'software.quay': 'Quay Enterprise',
|
||||
'software.quay.regions': 'Distributed Storage Regions'
|
||||
};
|
||||
|
||||
var handleLicenseSuccess = function(resp) {
|
||||
$scope.state = resp['success'] ? $scope.LicenseStates.valid : $scope.LicenseStates.invalid;
|
||||
$scope.requiredBox = resp['success'] ? 'filled' : '';
|
||||
$scope.showingEditor = !resp['success'];
|
||||
$scope.licenseStatus = resp['status'];
|
||||
$scope.licenseError = null;
|
||||
$scope.isValid = resp['success'];
|
||||
};
|
||||
|
||||
var handleLicenseError = function(resp) {
|
||||
$scope.licenseError = ApiService.getErrorMessage(resp);
|
||||
$scope.licenseStatus = null;
|
||||
$scope.state = 'license-error';
|
||||
$scope.showingEditor = true;
|
||||
$scope.requiredBox = '';
|
||||
$scope.isValid = false;
|
||||
};
|
||||
|
||||
var loadLicense = function() {
|
||||
ApiService.getLicense().then(function(resp) {
|
||||
$scope.state = 'license-valid';
|
||||
$scope.showingEditor = false;
|
||||
$scope.licenseDecoded = resp['decoded'];
|
||||
$scope.requiredBox = 'filled';
|
||||
}, function(resp) {
|
||||
$scope.licenseError = ApiService.getErrorMessage(resp);
|
||||
$scope.state = 'license-error';
|
||||
$scope.showingEditor = true;
|
||||
$scope.requiredBox = '';
|
||||
});
|
||||
if ($scope.forSetup == 'true') {
|
||||
$scope.state = $scope.LicenseStates.none;
|
||||
return;
|
||||
}
|
||||
|
||||
ApiService.getLicense().then(handleLicenseSuccess, handleLicenseError);
|
||||
};
|
||||
|
||||
UserService.updateUserIn($scope, function(user) {
|
||||
|
@ -1293,23 +1321,17 @@ angular.module("core-config-setup", ['angularFileUpload'])
|
|||
$event.preventDefault();
|
||||
$event.stopPropagation();
|
||||
|
||||
$scope.state = 'validating-license';
|
||||
$scope.state = $scope.LicenseStates.validating;
|
||||
|
||||
var data = {
|
||||
'license': $scope.licenseContents
|
||||
};
|
||||
|
||||
ApiService.updateLicense(data).then(function(resp) {
|
||||
$scope.state = 'license-valid';
|
||||
$scope.showingEditor = false;
|
||||
$scope.licenseDecoded = resp['decoded'];
|
||||
$scope.requiredBox = 'filled';
|
||||
}, function(resp) {
|
||||
$scope.licenseError = ApiService.getErrorMessage(resp);
|
||||
$scope.state = 'license-error';
|
||||
$scope.showingEditor = true;
|
||||
$scope.requiredBox = '';
|
||||
});
|
||||
if ($scope.forSetup == 'true') {
|
||||
ApiService.suSetAndValidateLicense(data).then(handleLicenseSuccess, handleLicenseError);
|
||||
} else {
|
||||
ApiService.updateLicense(data).then(handleLicenseSuccess, handleLicenseError);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -40,12 +40,6 @@
|
|||
// License is being uploaded.
|
||||
'UPLOAD_LICENSE': 'upload-license',
|
||||
|
||||
// License is being validated.
|
||||
'VALIDATING_LICENSE': 'upload-license-validating',
|
||||
|
||||
// License is validated.
|
||||
'VALIDATED_LICENSE': 'upload-license-validated',
|
||||
|
||||
// DB is being configured.
|
||||
'CONFIG_DB': 'config-db',
|
||||
|
||||
|
@ -105,9 +99,7 @@
|
|||
|
||||
$scope.currentState = {
|
||||
'hasDatabaseSSLCert': false,
|
||||
'licenseContents': '',
|
||||
'licenseError': null,
|
||||
'licenseDecoded': null
|
||||
'licenseValid': false
|
||||
};
|
||||
|
||||
$scope.$watch('currentStep', function(currentStep) {
|
||||
|
@ -144,27 +136,6 @@
|
|||
}
|
||||
});
|
||||
|
||||
$scope.validateLicense = function() {
|
||||
$scope.currentStep = $scope.States.VALIDATING_LICENSE;
|
||||
|
||||
var data = {
|
||||
'license': $scope.currentState.licenseContents
|
||||
};
|
||||
|
||||
ApiService.suSetAndValidateLicense(data).then(function(resp) {
|
||||
$scope.currentStep = $scope.States.VALIDATED_LICENSE;
|
||||
|
||||
$scope.currentState.licenseError = null;
|
||||
$scope.currentState.licenseDecoded = resp['decoded'];
|
||||
}, function(resp) {
|
||||
$scope.currentStep = $scope.States.UPLOAD_LICENSE;
|
||||
|
||||
$scope.currentState.licenseError = ApiService.getErrorMessage(resp);
|
||||
$scope.currentState.licenseContents = '';
|
||||
$scope.currentState.licenseDecoded = null;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.restartContainer = function(state) {
|
||||
$scope.currentStep = state;
|
||||
ContainerService.restartContainer(function() {
|
||||
|
|
|
@ -130,21 +130,9 @@
|
|||
The container must be restarted to apply the configuration changes.
|
||||
</div>
|
||||
|
||||
<!-- Content: VALIDATED_LICENSE -->
|
||||
<div class="modal-body license-valid" style="padding: 20px;"
|
||||
ng-show="isStep(currentStep, States.VALIDATED_LICENSE)">
|
||||
<h5><i class="fa fa-check"></i> License Validated</h5>
|
||||
Your license has been validated and saved. Please press "Next" to continue setup of your Quay Enterprise installation.
|
||||
<table class="co-table">
|
||||
<tr><td>Product:</td><td>{{ currentState.licenseDecoded.publicProductName || currentState.licenseDecoded.productName }}</td></tr>
|
||||
<tr><td>Plan:</td><td>{{ currentState.licenseDecoded.publicPlanName || currentState.licenseDecoded.planName }}</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Content: UPLOAD_LICENSE or VALIDATING_LICENSE -->
|
||||
<div class="modal-body upload-license" style="padding: 20px;"
|
||||
ng-show="isStep(currentStep, States.UPLOAD_LICENSE, States.VALIDATING_LICENSE)"
|
||||
ng-class="isStep(currentStep, States.VALIDATING_LICENSE) ? 'validating' : 'entering'">
|
||||
<!-- Content: UPLOAD_LICENSE -->
|
||||
<div class="modal-body upload-license entering" style="padding: 20px;"
|
||||
ng-show="isStep(currentStep, States.UPLOAD_LICENSE)">
|
||||
<h4>
|
||||
Quay Enterprise License
|
||||
</h4>
|
||||
|
@ -152,13 +140,7 @@
|
|||
Please provide your Quay Enterprise License. It can be found under the "Raw Format" tab
|
||||
of your Quay Enterprise subscription in the <a href="https://account.tectonic.com" target="_blank">Tectonic Account</a>.
|
||||
</div>
|
||||
<textarea id="enterLicenseBox" ng-model="currentState.licenseContents" placeholder="Paste your raw license here, which should already be in base64 format: GtqMjMwNDgyM3Vq..."
|
||||
ng-readonly="isStep(currentStep, States.VALIDATING_LICENSE)"></textarea>
|
||||
<div class="license-invalid" ng-visible="isStep(currentStep, States.UPLOAD_LICENSE) && currentState.licenseError">
|
||||
<h5><i class="fa fa-times-circle"></i> Validation Failed</h5>
|
||||
<h6>{{ currentState.licenseError }}</h6>
|
||||
Please try copying your license from the Tectonic Account again.
|
||||
</div>
|
||||
<div class="config-license-field" for-setup="true" is-valid="currentState.licenseValid"></div>
|
||||
</div>
|
||||
|
||||
<!-- Content: DB_SETUP or DB_SETUP_ERROR -->
|
||||
|
@ -259,26 +241,12 @@
|
|||
Database Validation Issue: {{ errors.DatabaseValidationError }}
|
||||
</div>
|
||||
|
||||
<!-- Footer: UPLOAD_LICENSE or VALIDATING_LICENSE -->
|
||||
<!-- Footer: UPLOAD_LICENSE -->
|
||||
<div class="modal-footer"
|
||||
ng-show="isStep(currentStep, States.UPLOAD_LICENSE, States.VALIDATING_LICENSE)">
|
||||
<div ng-show="isStep(currentStep, States.VALIDATING_LICENSE)">
|
||||
<span class="cor-loader-inline"></span>
|
||||
Validating License...
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary" ng-click="validateLicense()"
|
||||
ng-disabled="!currentState.licenseContents"
|
||||
ng-show="isStep(currentStep, States.UPLOAD_LICENSE)">
|
||||
Validate License
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Footer: VALIDATED_LICENSE -->
|
||||
<div class="modal-footer"
|
||||
ng-show="isStep(currentStep, States.VALIDATED_LICENSE)">
|
||||
<button type="submit" class="btn btn-primary" ng-click="beginSetup()">
|
||||
Next
|
||||
ng-show="isStep(currentStep, States.UPLOAD_LICENSE)">
|
||||
<button type="submit" class="btn btn-primary" ng-click="beginSetup()"
|
||||
ng-disabled="!currentState.licenseValid">
|
||||
Continue
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -61,6 +61,20 @@ class Entitlement(object):
|
|||
expiration=repr(self.expiration),
|
||||
))
|
||||
|
||||
def as_dict(self, for_private=False):
|
||||
data = {
|
||||
'name': self.name,
|
||||
}
|
||||
|
||||
if for_private:
|
||||
data.update({
|
||||
'count': self.count,
|
||||
'product_name': self.product_name,
|
||||
'expiration': self.expiration.as_dict(for_private=True),
|
||||
})
|
||||
|
||||
return data
|
||||
|
||||
class ExpirationType(Enum):
|
||||
""" An enum which represents the different possible types of expirations. If
|
||||
you posess an expired enum, you can use this to figure out at what level
|
||||
|
@ -105,6 +119,19 @@ class Expiration(object):
|
|||
grace_period=repr(self.grace_period),
|
||||
))
|
||||
|
||||
def as_dict(self, for_private=False):
|
||||
data = {
|
||||
'expiration_type': str(self.expiration_type),
|
||||
}
|
||||
|
||||
if for_private:
|
||||
data.update({
|
||||
'expiration_date': str(self.expiration_date),
|
||||
'grace_period': str(self.grace_period),
|
||||
})
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class EntitlementStatus(IntEnum):
|
||||
""" An EntitlementStatus represent the current effectiveness of an
|
||||
|
@ -162,6 +189,23 @@ class EntitlementValidationResult(object):
|
|||
entitlement=repr(self.entitlement),
|
||||
))
|
||||
|
||||
def as_dict(self, for_private=False):
|
||||
def req_view():
|
||||
return {
|
||||
'name': self.requirement.name,
|
||||
'count': self.requirement.count,
|
||||
}
|
||||
|
||||
data = {
|
||||
'requirement': req_view(),
|
||||
'status': str(self.get_status()),
|
||||
}
|
||||
|
||||
if self.entitlement is not None:
|
||||
data['entitlement'] = self.entitlement.as_dict(for_private=for_private)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class License(object):
|
||||
""" License represents a fully decoded and validated (but potentially expired) license. """
|
||||
|
|
Reference in a new issue