diff --git a/endpoints/api/suconfig.py b/endpoints/api/suconfig.py index 526eb7d71..9235e8ef3 100644 --- a/endpoints/api/suconfig.py +++ b/endpoints/api/suconfig.py @@ -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, } diff --git a/endpoints/api/superuser.py b/endpoints/api/superuser.py index 5f635e642..7344da266 100644 --- a/endpoints/api/superuser.py +++ b/endpoints/api/superuser.py @@ -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) diff --git a/static/css/core-ui.css b/static/css/core-ui.css index bd482b37e..5ce33c73b 100644 --- a/static/css/core-ui.css +++ b/static/css/core-ui.css @@ -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; } diff --git a/static/css/pages/setup.css b/static/css/pages/setup.css index 2ca87dbdc..2cdeb027b 100644 --- a/static/css/pages/setup.css +++ b/static/css/pages/setup.css @@ -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; -} diff --git a/static/directives/config/config-license-field.html b/static/directives/config/config-license-field.html index 9eb688156..f414f2aa6 100644 --- a/static/directives/config/config-license-field.html +++ b/static/directives/config/config-license-field.html @@ -3,19 +3,54 @@ config if the license is invalid (since this box will be empty and therefore "required") --> -
+
-
+

License Valid

- - + + + + + + + + + + + + + +
Product:{{ licenseDecoded.publicProductName || licenseDecoded.productName }}
Plan:{{ licenseDecoded.publicPlanName || licenseDecoded.planName }}
RequirementRequired CountSubscriptionSubscription CountExpiration Date
{{ requirementTitles[status.requirement.name] }}{{ status.requirement.count }}{{ status.entitlement.product_name }}{{ status.entitlement.count }}
-
+

Validation Failed

-
{{ licenseError }}
+
{{ licenseError }}
+
+

The following errors were found:

+
    +
  • +
    + +
    + {{ requirementTitles[status.requirement.name] }}: {{ status.requirement.count }} areis required: License provides {{ status.entitlement.count }} +
    + + +
    + {{ requirementTitles[status.requirement.name] }}: License is missing requirement +
    + + +
    + {{ requirementTitles[status.requirement.name] }}: Requirement expired on {{ status.entitlement.expiration.expiration_date }} +
    +
    +
  • +
+
@@ -28,12 +63,12 @@ + ng-readonly="state == LicenseStates.validating"> - -
+
Validating License
diff --git a/static/js/core-config-setup.js b/static/js/core-config-setup.js index 19977d359..3cbafb175 100644 --- a/static/js/core-config-setup.js +++ b/static/js/core-config-setup.js @@ -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); + } }; } }; diff --git a/static/js/pages/setup.js b/static/js/pages/setup.js index 3759a9bf2..ab1938fe1 100644 --- a/static/js/pages/setup.js +++ b/static/js/pages/setup.js @@ -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() { diff --git a/static/partials/setup.html b/static/partials/setup.html index e238ab846..a59c506e6 100644 --- a/static/partials/setup.html +++ b/static/partials/setup.html @@ -130,21 +130,9 @@ The container must be restarted to apply the configuration changes.
- - - - - @@ -259,26 +241,12 @@ Database Validation Issue: {{ errors.DatabaseValidationError }}
- + - - - diff --git a/util/license.py b/util/license.py index 694a0b54b..663349a31 100644 --- a/util/license.py +++ b/util/license.py @@ -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. """