From 846e67ee6ab9faecf2d34d545194771c4407cfa0 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Wed, 28 Aug 2019 08:46:34 -0400 Subject: [PATCH 01/13] update trial license enforcement logic --- awx/api/views/root.py | 35 ++++++++++++++++--- awx/main/access.py | 19 +++++++--- .../management/commands/inventory_import.py | 9 +++-- 3 files changed, 52 insertions(+), 11 deletions(-) diff --git a/awx/api/views/root.py b/awx/api/views/root.py index 922041c8b5..44b8cb293b 100644 --- a/awx/api/views/root.py +++ b/awx/api/views/root.py @@ -17,6 +17,8 @@ from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.response import Response from rest_framework import status +import requests + from awx.api.generics import APIView from awx.main.ha import is_ha_environment from awx.main.utils import ( @@ -248,14 +250,39 @@ class ApiV2ConfigView(APIView): logger.info(smart_text(u"Invalid JSON submitted for license."), extra=dict(actor=request.user.username)) return Response({"error": _("Invalid JSON")}, status=status.HTTP_400_BAD_REQUEST) + try: from awx.main.utils.common import get_licenser license_data = json.loads(data_actual) + if license_data.get('rh_password') == '$encrypted$': + license_data['rh_password'] = settings.REDHAT_PASSWORD license_data_validated = get_licenser(**license_data).validate() - except Exception: - logger.warning(smart_text(u"Invalid license submitted."), - extra=dict(actor=request.user.username)) - return Response({"error": _("Invalid License")}, status=status.HTTP_400_BAD_REQUEST) + if license_data_validated.get('valid_key') and 'license_key' not in license_data: + if license_data.get('rh_username') and license_data.get('rh_password'): + settings.REDHAT_USERNAME = license_data['rh_username'] + settings.REDHAT_PASSWORD = license_data['rh_password'] + license_data = { + "eula_accepted": eula_accepted, + "features": license_data_validated['features'], + "license_type": license_data_validated['license_type'], + "license_date": license_data_validated['license_date'], + "license_key": license_data_validated['license_key'], + "instance_count": license_data_validated['instance_count'], + } + if license_data_validated.get('trial'): + license_data['trial'] = True + except Exception as exc: + msg = _("Invalid License") + if ( + isinstance(exc, requests.exceptions.HTTPError) and + getattr(getattr(exc, 'response', None), 'status_code', None) == 401 + ): + msg = _("The provided credentials are invalid (HTTP 401).") + if isinstance(exc, ValueError) and exc.args: + msg = exc.args[0] + logger.exception(smart_text(u"Invalid license submitted."), + extra=dict(actor=request.user.username)) + return Response({"error": msg}, status=status.HTTP_400_BAD_REQUEST) # If the license is valid, write it to the database. if license_data_validated['valid_key']: diff --git a/awx/main/access.py b/awx/main/access.py index 1a87eff38a..e9957656de 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -317,10 +317,19 @@ class BaseAccess(object): validation_info['time_remaining'] = 99999999 validation_info['grace_period_remaining'] = 99999999 + report_violation = lambda message: logger.error(message) + + if ( + validation_info.get('trial', False) is True or + validation_info['instance_count'] == 10 # basic 10 license + ): + def report_violation(message): + raise PermissionDenied(message) + if check_expiration and validation_info.get('time_remaining', None) is None: raise PermissionDenied(_("License is missing.")) - if check_expiration and validation_info.get("grace_period_remaining") <= 0: - raise PermissionDenied(_("License has expired.")) + elif check_expiration and validation_info.get("grace_period_remaining") <= 0: + report_violation(_("License has expired.")) free_instances = validation_info.get('free_instances', 0) available_instances = validation_info.get('available_instances', 0) @@ -328,11 +337,11 @@ class BaseAccess(object): if add_host_name: host_exists = Host.objects.filter(name=add_host_name).exists() if not host_exists and free_instances == 0: - raise PermissionDenied(_("License count of %s instances has been reached.") % available_instances) + report_violation(_("License count of %s instances has been reached.") % available_instances) elif not host_exists and free_instances < 0: - raise PermissionDenied(_("License count of %s instances has been exceeded.") % available_instances) + report_violation(_("License count of %s instances has been exceeded.") % available_instances) elif not add_host_name and free_instances < 0: - raise PermissionDenied(_("Host count exceeds available instances.")) + report_violation(_("Host count exceeds available instances.")) def check_org_host_limit(self, data, add_host_name=None): validation_info = get_licenser().validate() diff --git a/awx/main/management/commands/inventory_import.py b/awx/main/management/commands/inventory_import.py index 6ae7ba69d5..7a23c1a63f 100644 --- a/awx/main/management/commands/inventory_import.py +++ b/awx/main/management/commands/inventory_import.py @@ -919,7 +919,8 @@ class Command(BaseCommand): new_count = Host.objects.active_count() if time_remaining <= 0 and not license_info.get('demo', False): logger.error(LICENSE_EXPIRED_MESSAGE) - raise CommandError("License has expired!") + if license_info.get('trial', False) is True: + raise CommandError("License has expired!") # special check for tower-type inventory sources # but only if running the plugin TOWER_SOURCE_FILES = ['tower.yml', 'tower.yaml'] @@ -936,7 +937,11 @@ class Command(BaseCommand): logger.error(DEMO_LICENSE_MESSAGE % d) else: logger.error(LICENSE_MESSAGE % d) - raise CommandError('License count exceeded!') + if ( + license_info.get('trial', False) is True or + license_info['instance_count'] == 10 # basic 10 license + ): + raise CommandError('License count exceeded!') def check_org_host_limit(self): license_info = get_licenser().validate() From 900fcbf87ec0e9aca758b75843e8da03f3476428 Mon Sep 17 00:00:00 2001 From: mabashian Date: Wed, 28 Aug 2019 17:32:10 -0400 Subject: [PATCH 02/13] Add rh username and pass to license form --- .../src/license/checkLicense.factory.js | 20 ++--- awx/ui/client/src/license/license.block.less | 37 +++++++- .../client/src/license/license.controller.js | 86 +++++++++++-------- .../client/src/license/license.partial.html | 38 ++++++-- 4 files changed, 129 insertions(+), 52 deletions(-) diff --git a/awx/ui/client/src/license/checkLicense.factory.js b/awx/ui/client/src/license/checkLicense.factory.js index 04a658fa82..d5cbc0ed44 100644 --- a/awx/ui/client/src/license/checkLicense.factory.js +++ b/awx/ui/client/src/license/checkLicense.factory.js @@ -5,29 +5,29 @@ *************************************************/ export default - ['$state', '$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', - 'ConfigService', - function($state, $rootScope, Rest, GetBasePath, ProcessErrors, - ConfigService){ + ['$state', '$rootScope', 'Rest', 'GetBasePath', + 'ConfigService', '$q', + function($state, $rootScope, Rest, GetBasePath, + ConfigService, $q){ return { get: function() { var config = ConfigService.get(); return config.license_info; }, - post: function(license, eula){ + post: function(payload, eula){ var defaultUrl = GetBasePath('config'); Rest.setUrl(defaultUrl); - var data = license; + var data = payload; data.eula_accepted = eula; + return Rest.post(JSON.stringify(data)) .then((response) =>{ return response.data; }) - .catch(({res, status}) => { - ProcessErrors($rootScope, res, status, null, {hdr: 'Error!', - msg: 'Call to '+ defaultUrl + ' failed. Return status: '+ status}); - }); + .catch(() => { + return $q.reject(); + }); }, valid: function(license) { diff --git a/awx/ui/client/src/license/license.block.less b/awx/ui/client/src/license/license.block.less index 08a6c624a6..f274b903a9 100644 --- a/awx/ui/client/src/license/license.block.less +++ b/awx/ui/client/src/license/license.block.less @@ -26,6 +26,21 @@ display: block; width: 100%; } +.License-file--left { + display: flex; + flex:1; + overflow: hidden; +} +.License-file--middle { + display: flex; + flex: 0 0 auto; + padding: 0px 20px; + flex-direction: column; +} +.License-file--right { + display: flex; + flex:1; +} .License-submit--success.ng-hide-add, .License-submit--success.ng-hide-remove { transition: all ease-in-out 0.5s; } @@ -109,10 +124,12 @@ } } -.License-submit--success{ +.License-submit--success, .License-submit--failure{ + line-height: 33px; margin: 0; } .License-file--container { + display: flex; input[type=file] { display: none; } @@ -148,3 +165,21 @@ padding: 10px 0; font-weight: bold; } + +.License-separator { + display: flex; + flex: 1; + background: linear-gradient(#d7d7d7, #d7d7d7) no-repeat center/2px 100%; +} + +.License-licenseStepHelp { + font-size: 12px; + font-style: italic; + margin-bottom: 10px; +} + +.License-filePicker { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} diff --git a/awx/ui/client/src/license/license.controller.js b/awx/ui/client/src/license/license.controller.js index ac80686a4f..7809a664f2 100644 --- a/awx/ui/client/src/license/license.controller.js +++ b/awx/ui/client/src/license/license.controller.js @@ -34,6 +34,8 @@ export default const reset = function() { document.getElementById('License-form').reset(); + $scope.rhPassword = null; + $scope.rhUsername = null; }; const init = function(config) { @@ -87,7 +89,7 @@ export default // 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() { - if($scope.user_is_superuser) { + if($scope.user_is_superuser && (!$scope.rhUsername || $scope.rhUsername === '') && (!$scope.rhPassword || $scope.rhPassword === '')) { $('#License-file').click(); } }; @@ -96,44 +98,58 @@ export default $window.open('https://www.ansible.com/license', '_blank'); }; - $scope.submit = function() { - Wait('start'); - CheckLicense.post($scope.newLicense.file, $scope.newLicense.eula) - .then((licenseInfo) => { - reset(); + $scope.submit = function() { + Wait('start'); + $scope.licenseError = false; + let payload = {}; + if ($scope.newLicense.file) { + payload = $scope.newLicense.file; + } else if ($scope.rhUsername && $scope.rhPassword) { + payload = { + rh_password: $scope.rhPassword, + rh_username: $scope.rhUsername + }; + } + CheckLicense.post(payload, $scope.newLicense.eula) + .then((licenseInfo) => { + reset(); - ConfigService.delete(); - ConfigService.getConfig(licenseInfo) - .then(function(config) { + ConfigService.delete(); + ConfigService.getConfig(licenseInfo) + .then(function(config) { - if ($rootScope.licenseMissing === true) { - if ($scope.newLicense.pendo) { - pendoService.updatePendoTrackingState('detailed'); - pendoService.issuePendoIdentity(); - } else { - pendoService.updatePendoTrackingState('off'); - } + if ($rootScope.licenseMissing === true) { + if ($scope.newLicense.pendo) { + pendoService.updatePendoTrackingState('detailed'); + pendoService.issuePendoIdentity(); + } else { + pendoService.updatePendoTrackingState('off'); + } - if ($scope.newLicense.insights) { - insightsEnablementService.updateInsightsTrackingState(true); - } else { - insightsEnablementService.updateInsightsTrackingState(false); - } + if ($scope.newLicense.insights) { + insightsEnablementService.updateInsightsTrackingState(true); + } else { + insightsEnablementService.updateInsightsTrackingState(false); + } - $state.go('dashboard', { - licenseMissing: false - }); - } else { - init(config); - $scope.success = true; - $rootScope.licenseMissing = false; - // for animation purposes - const successTimeout = setTimeout(function() { - $scope.success = false; - clearTimeout(successTimeout); - }, 4000); - } + $state.go('dashboard', { + licenseMissing: false }); + } else { + init(config); + $scope.success = true; + $rootScope.licenseMissing = false; + // for animation purposes + const successTimeout = setTimeout(function() { + $scope.success = false; + clearTimeout(successTimeout); + }, 4000); + } }); - }; + }).catch(() => { + Wait('stop'); + reset(); + $scope.licenseError = true; + }); + }; }]; diff --git a/awx/ui/client/src/license/license.partial.html b/awx/ui/client/src/license/license.partial.html index 96b44b6bb9..7c3e5e6e4d 100644 --- a/awx/ui/client/src/license/license.partial.html +++ b/awx/ui/client/src/license/license.partial.html @@ -94,19 +94,44 @@ 2 - Choose your license file, agree to the End User License Agreement, and click submit. + Choose your license file or provide your Red Hat subscription credentials, agree to the End User License Agreement, and click submit.
* - License File + License
- Browse - {{fileName|translate}} - +
+
+
Upload a license file
+
+ Browse + {{fileName|translate}} + +
+
+
+
+
+
OR
+
+
+
+
+
Provide your Red Hat subscription credentials
+
+ + +
+
+ + +
+
+
* @@ -142,8 +167,9 @@
- + Save successful! + Invalid License - Save unsuccessful
From 2474a3a2eafd426410861df3d42bd0efc2043873 Mon Sep 17 00:00:00 2001 From: mabashian Date: Thu, 29 Aug 2019 15:15:50 -0400 Subject: [PATCH 03/13] Pull creds into license form if available via settings --- awx/ui/client/src/license/license.block.less | 6 +++ .../client/src/license/license.controller.js | 43 +++++++++++++------ .../client/src/license/license.partial.html | 29 +++++++++---- awx/ui/client/src/license/license.route.js | 33 +++++++------- 4 files changed, 74 insertions(+), 37 deletions(-) diff --git a/awx/ui/client/src/license/license.block.less b/awx/ui/client/src/license/license.block.less index f274b903a9..b0091d71ee 100644 --- a/awx/ui/client/src/license/license.block.less +++ b/awx/ui/client/src/license/license.block.less @@ -183,3 +183,9 @@ white-space: nowrap; overflow: hidden; } + +.License-label { + color: @field-label; + font-weight: 400; + margin-top: 10px; +} diff --git a/awx/ui/client/src/license/license.controller.js b/awx/ui/client/src/license/license.controller.js index 7809a664f2..799e1c6b32 100644 --- a/awx/ui/client/src/license/license.controller.js +++ b/awx/ui/client/src/license/license.controller.js @@ -7,11 +7,10 @@ import {N_} from "../i18n"; export default - ['Wait', '$state', '$scope', '$rootScope', - 'ProcessErrors', 'CheckLicense', 'moment','$window', - 'ConfigService', 'pendoService', 'insightsEnablementService', 'i18n', 'config', - function(Wait, $state, $scope, $rootScope, ProcessErrors, CheckLicense, moment, - $window, ConfigService, pendoService, insightsEnablementService, i18n, config) { + ['Wait', '$state', '$scope', '$rootScope', 'ProcessErrors', 'CheckLicense', 'moment', 'Rest', + '$window', 'ConfigService', 'pendoService', 'insightsEnablementService', 'i18n', 'config', 'GetBasePath', + function(Wait, $state, $scope, $rootScope, ProcessErrors, CheckLicense, moment, Rest, + $window, ConfigService, pendoService, insightsEnablementService, i18n, config, GetBasePath) { const calcDaysRemaining = function(seconds) { // calculate the number of days remaining on the license @@ -34,11 +33,10 @@ export default const reset = function() { document.getElementById('License-form').reset(); - $scope.rhPassword = null; - $scope.rhUsername = null; + $scope.rhCreds = {}; }; - const init = function(config) { + const initVars = (config) => { // license/license.partial.html compares fileName $scope.fileName = N_("No file selected."); @@ -59,8 +57,29 @@ export default pendo: true, insights: true }; + + $scope.rhCreds = {}; }; + const init = (config) => { + Rest.setUrl(`${GetBasePath('settings')}system/`); + Rest.get() + .then(({data}) => { + initVars(config); + + if (data.REDHAT_USERNAME && data.REDHAT_USERNAME !== "") { + $scope.rhCreds.username = data.REDHAT_USERNAME; + } + + if (data.REDHAT_PASSWORD && data.REDHAT_PASSWORD !== "") { + $scope.rhCreds.password = data.REDHAT_PASSWORD; + $scope.hasPasswordFromSettings = true; + } + }).catch(() => { + initVars(config); + }); + }; + init(config); $scope.getKey = function(event) { @@ -89,7 +108,7 @@ export default // 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() { - if($scope.user_is_superuser && (!$scope.rhUsername || $scope.rhUsername === '') && (!$scope.rhPassword || $scope.rhPassword === '')) { + if($scope.user_is_superuser && (!$scope.rhCreds.username || $scope.rhCreds.username === '') && (!$scope.rhCreds.password || $scope.rhCreds.password === '')) { $('#License-file').click(); } }; @@ -104,10 +123,10 @@ export default let payload = {}; if ($scope.newLicense.file) { payload = $scope.newLicense.file; - } else if ($scope.rhUsername && $scope.rhPassword) { + } else if ($scope.rhCreds.username && $scope.rhCreds.password) { payload = { - rh_password: $scope.rhPassword, - rh_username: $scope.rhUsername + rh_password: $scope.rhCreds.password, + rh_username: $scope.rhCreds.username }; } CheckLicense.post(payload, $scope.newLicense.eula) diff --git a/awx/ui/client/src/license/license.partial.html b/awx/ui/client/src/license/license.partial.html index 7c3e5e6e4d..f7cabdee32 100644 --- a/awx/ui/client/src/license/license.partial.html +++ b/awx/ui/client/src/license/license.partial.html @@ -8,7 +8,7 @@
License
Valid License - Invalid License + Invalid License
@@ -94,7 +94,7 @@ 2 - Choose your license file or provide your Red Hat subscription credentials, agree to the End User License Agreement, and click submit. + Choose your license file or provide your Red Hat customer credentials, agree to the End User License Agreement, and click submit.
@@ -108,7 +108,7 @@
Upload a license file
- Browse + Browse {{fileName|translate}}
@@ -121,15 +121,26 @@
-
Provide your Red Hat subscription credentials
+
Provide your Red Hat customer credentials
- - + +
- - + +
+ + + +
+
+ + + + +
+
@@ -167,7 +178,7 @@
- + Save successful! Invalid License - Save unsuccessful
diff --git a/awx/ui/client/src/license/license.route.js b/awx/ui/client/src/license/license.route.js index 98ba25d79c..430da0174e 100644 --- a/awx/ui/client/src/license/license.route.js +++ b/awx/ui/client/src/license/license.route.js @@ -17,30 +17,31 @@ export default { ncyBreadcrumb: { label: N_('LICENSE') }, - onEnter: ['$state', 'ConfigService', (state, configService) => { - return configService.getConfig() - .then(config => { - if (_.get(config, 'license_info.license_type') === 'open') { - return state.go('setup'); - } - }); - }], + onEnter: ['$state', 'ConfigService', (state, configService) => { + return configService.getConfig() + .then(config => { + if (_.get(config, 'license_info.license_type') === 'open') { + return state.go('setup'); + } + }); + }], resolve: { features: ['CheckLicense', '$rootScope', function(CheckLicense, $rootScope) { if($rootScope.licenseMissing === undefined){ return CheckLicense.notify(); } - - }], + } + ], config: ['ConfigService', 'CheckLicense', '$rootScope', function(ConfigService, CheckLicense, $rootScope) { ConfigService.delete(); - return ConfigService.getConfig() - .then(function(config){ - $rootScope.licenseMissing = (CheckLicense.valid(config.license_info) === false) ? true : false; - return config; - }); - }] + return ConfigService.getConfig() + .then(function(config){ + $rootScope.licenseMissing = (CheckLicense.valid(config.license_info) === false) ? true : false; + return config; + }); + } + ] }, }; From 53925f5e9898cbac0ba669e89b06f29449de36f3 Mon Sep 17 00:00:00 2001 From: mabashian Date: Thu, 29 Aug 2019 16:59:54 -0400 Subject: [PATCH 04/13] Tweaks to the ux for saved creds --- awx/ui/client/src/license/license.controller.js | 17 ++++++++++++++--- awx/ui/client/src/license/license.partial.html | 17 ++++++++--------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/awx/ui/client/src/license/license.controller.js b/awx/ui/client/src/license/license.controller.js index 799e1c6b32..eaa0cbf3d1 100644 --- a/awx/ui/client/src/license/license.controller.js +++ b/awx/ui/client/src/license/license.controller.js @@ -7,9 +7,9 @@ import {N_} from "../i18n"; export default - ['Wait', '$state', '$scope', '$rootScope', 'ProcessErrors', 'CheckLicense', 'moment', 'Rest', + ['Wait', '$state', '$scope', '$rootScope', 'ProcessErrors', 'CheckLicense', 'moment', 'Rest', '$timeout', '$window', 'ConfigService', 'pendoService', 'insightsEnablementService', 'i18n', 'config', 'GetBasePath', - function(Wait, $state, $scope, $rootScope, ProcessErrors, CheckLicense, moment, Rest, + function(Wait, $state, $scope, $rootScope, ProcessErrors, CheckLicense, moment, Rest, $timeout, $window, ConfigService, pendoService, insightsEnablementService, i18n, config, GetBasePath) { const calcDaysRemaining = function(seconds) { @@ -73,7 +73,7 @@ export default if (data.REDHAT_PASSWORD && data.REDHAT_PASSWORD !== "") { $scope.rhCreds.password = data.REDHAT_PASSWORD; - $scope.hasPasswordFromSettings = true; + $scope.showPlaceholderPassword = true; } }).catch(() => { initVars(config); @@ -117,6 +117,17 @@ export default $window.open('https://www.ansible.com/license', '_blank'); }; + $scope.replacePassword = () => { + if ($scope.user_is_superuser && !$scope.newLicense.file) { + $scope.showPlaceholderPassword = false; + $scope.rhCreds.password = ""; + $timeout(() => { + $('.tooltip').remove(); + $('#rh-password').focus(); + }); + } + }; + $scope.submit = function() { Wait('start'); $scope.licenseError = false; diff --git a/awx/ui/client/src/license/license.partial.html b/awx/ui/client/src/license/license.partial.html index f7cabdee32..cdcdacb6a2 100644 --- a/awx/ui/client/src/license/license.partial.html +++ b/awx/ui/client/src/license/license.partial.html @@ -128,17 +128,16 @@
-
- - +
+ + + -
-
- - - - +
+
From 38a7d62745f51264159b80ce65de37e6efeb318d Mon Sep 17 00:00:00 2001 From: mabashian Date: Fri, 30 Aug 2019 10:08:13 -0400 Subject: [PATCH 05/13] Add support for custom error messaging on license page --- .../client/src/license/checkLicense.factory.js | 4 ++-- awx/ui/client/src/license/license.block.less | 3 +-- awx/ui/client/src/license/license.controller.js | 9 ++++++--- awx/ui/client/src/license/license.partial.html | 16 ++++++++++------ 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/awx/ui/client/src/license/checkLicense.factory.js b/awx/ui/client/src/license/checkLicense.factory.js index d5cbc0ed44..e76f1ea0b7 100644 --- a/awx/ui/client/src/license/checkLicense.factory.js +++ b/awx/ui/client/src/license/checkLicense.factory.js @@ -25,8 +25,8 @@ export default .then((response) =>{ return response.data; }) - .catch(() => { - return $q.reject(); + .catch(({data}) => { + return $q.reject(data); }); }, diff --git a/awx/ui/client/src/license/license.block.less b/awx/ui/client/src/license/license.block.less index b0091d71ee..415c8a1fab 100644 --- a/awx/ui/client/src/license/license.block.less +++ b/awx/ui/client/src/license/license.block.less @@ -124,8 +124,7 @@ } } -.License-submit--success, .License-submit--failure{ - line-height: 33px; +.License-submit--success, .License-submit--failure { margin: 0; } .License-file--container { diff --git a/awx/ui/client/src/license/license.controller.js b/awx/ui/client/src/license/license.controller.js index eaa0cbf3d1..40eeeff071 100644 --- a/awx/ui/client/src/license/license.controller.js +++ b/awx/ui/client/src/license/license.controller.js @@ -33,7 +33,10 @@ export default const reset = function() { document.getElementById('License-form').reset(); - $scope.rhCreds = {}; + $scope.newLicense.eula = undefined; + if (!$scope.licenseError) { + $scope.rhCreds = {}; + } }; const initVars = (config) => { @@ -176,10 +179,10 @@ export default }, 4000); } }); - }).catch(() => { + }).catch((data) => { Wait('stop'); reset(); - $scope.licenseError = true; + $scope.licenseError = data.error; }); }; }]; diff --git a/awx/ui/client/src/license/license.partial.html b/awx/ui/client/src/license/license.partial.html index cdcdacb6a2..4a6ae096cf 100644 --- a/awx/ui/client/src/license/license.partial.html +++ b/awx/ui/client/src/license/license.partial.html @@ -167,19 +167,23 @@
- + User analytics: This data is used to enhance future releases of the Tower Software and help streamline customer experience and success.
- + Automation analytics: This data is used to enhance future releases of the Tower Software and to provide Automation Insights to Tower subscribers.
-
- - Save successful! - Invalid License - Save unsuccessful +
+
+ Save successful! + Save unsuccessful - {{licenseError}} +
+
+ +
From 04f458c007edcc165b0ba459ca6c6fb934224268 Mon Sep 17 00:00:00 2001 From: mabashian Date: Fri, 30 Aug 2019 10:16:24 -0400 Subject: [PATCH 06/13] Cleanup inline styles --- awx/ui/client/src/license/license.block.less | 10 ++++++++++ awx/ui/client/src/license/license.controller.js | 1 - awx/ui/client/src/license/license.partial.html | 4 ++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/awx/ui/client/src/license/license.block.less b/awx/ui/client/src/license/license.block.less index 415c8a1fab..bc7008e7e6 100644 --- a/awx/ui/client/src/license/license.block.less +++ b/awx/ui/client/src/license/license.block.less @@ -188,3 +188,13 @@ font-weight: 400; margin-top: 10px; } + +.License-action { + display: flex; + flex-direction: row; + align-content:flex-end; +} + +.License-actionError { + flex: 1; +} diff --git a/awx/ui/client/src/license/license.controller.js b/awx/ui/client/src/license/license.controller.js index 40eeeff071..318e28c864 100644 --- a/awx/ui/client/src/license/license.controller.js +++ b/awx/ui/client/src/license/license.controller.js @@ -181,7 +181,6 @@ export default }); }).catch((data) => { Wait('stop'); - reset(); $scope.licenseError = data.error; }); }; diff --git a/awx/ui/client/src/license/license.partial.html b/awx/ui/client/src/license/license.partial.html index 4a6ae096cf..7b857ad506 100644 --- a/awx/ui/client/src/license/license.partial.html +++ b/awx/ui/client/src/license/license.partial.html @@ -176,8 +176,8 @@ -
-
+
+
Save successful! Save unsuccessful - {{licenseError}}
From 29fd399b06f524337e109cab2757cdd9477ab22d Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Fri, 30 Aug 2019 14:38:57 -0400 Subject: [PATCH 07/13] introduce a new API for generating licenses from candlepin creds --- awx/api/urls/urls.py | 2 ++ awx/api/views/__init__.py | 1 + awx/api/views/root.py | 72 +++++++++++++++++++++++---------------- 3 files changed, 46 insertions(+), 29 deletions(-) diff --git a/awx/api/urls/urls.py b/awx/api/urls/urls.py index ede960ecb6..ab7d61fd23 100644 --- a/awx/api/urls/urls.py +++ b/awx/api/urls/urls.py @@ -14,6 +14,7 @@ from awx.api.views import ( ApiV2RootView, ApiV2PingView, ApiV2ConfigView, + ApiV2SubscriptionView, AuthView, UserMeList, DashboardView, @@ -94,6 +95,7 @@ v2_urls = [ url(r'^metrics/$', MetricsView.as_view(), name='metrics_view'), url(r'^ping/$', ApiV2PingView.as_view(), name='api_v2_ping_view'), url(r'^config/$', ApiV2ConfigView.as_view(), name='api_v2_config_view'), + url(r'^config/subscriptions/$', ApiV2SubscriptionView.as_view(), name='api_v2_subscription_view'), url(r'^auth/$', AuthView.as_view()), url(r'^me/$', UserMeList.as_view(), name='user_me_list'), url(r'^dashboard/$', DashboardView.as_view(), name='dashboard_view'), diff --git a/awx/api/views/__init__.py b/awx/api/views/__init__.py index a6d92e9578..9302249e67 100644 --- a/awx/api/views/__init__.py +++ b/awx/api/views/__init__.py @@ -147,6 +147,7 @@ from awx.api.views.root import ( # noqa ApiV2RootView, ApiV2PingView, ApiV2ConfigView, + ApiV2SubscriptionView, ) diff --git a/awx/api/views/root.py b/awx/api/views/root.py index 44b8cb293b..b45ccc0071 100644 --- a/awx/api/views/root.py +++ b/awx/api/views/root.py @@ -171,6 +171,45 @@ class ApiV2PingView(APIView): return Response(response) +class ApiV2SubscriptionView(APIView): + + permission_classes = (IsAuthenticated,) + name = _('Configuration') + swagger_topic = 'System Configuration' + + def check_permissions(self, request): + super(ApiV2SubscriptionView, self).check_permissions(request) + if not request.user.is_superuser and request.method.lower() not in {'options', 'head'}: + self.permission_denied(request) # Raises PermissionDenied exception. + + def post(self, request): + from awx.main.utils.common import get_licenser + data = request.data.copy() + if data.get('rh_password') == '$encrypted$': + data['rh_password'] = settings.REDHAT_PASSWORD + try: + user, pw = data.get('rh_username'), data.get('rh_password') + validated = get_licenser().validate_rh(user, pw) + if user: + settings.REDHAT_USERNAME = data['rh_username'] + if pw: + settings.REDHAT_PASSWORD = data['rh_password'] + except Exception as exc: + msg = _("Invalid License") + if ( + isinstance(exc, requests.exceptions.HTTPError) and + getattr(getattr(exc, 'response', None), 'status_code', None) == 401 + ): + msg = _("The provided credentials are invalid (HTTP 401).") + if isinstance(exc, ValueError) and exc.args: + msg = exc.args[0] + logger.exception(smart_text(u"Invalid license submitted."), + extra=dict(actor=request.user.username)) + return Response({"error": msg}, status=status.HTTP_400_BAD_REQUEST) + + return Response(validated) + + class ApiV2ConfigView(APIView): permission_classes = (IsAuthenticated,) @@ -250,39 +289,14 @@ class ApiV2ConfigView(APIView): logger.info(smart_text(u"Invalid JSON submitted for license."), extra=dict(actor=request.user.username)) return Response({"error": _("Invalid JSON")}, status=status.HTTP_400_BAD_REQUEST) - try: from awx.main.utils.common import get_licenser license_data = json.loads(data_actual) - if license_data.get('rh_password') == '$encrypted$': - license_data['rh_password'] = settings.REDHAT_PASSWORD license_data_validated = get_licenser(**license_data).validate() - if license_data_validated.get('valid_key') and 'license_key' not in license_data: - if license_data.get('rh_username') and license_data.get('rh_password'): - settings.REDHAT_USERNAME = license_data['rh_username'] - settings.REDHAT_PASSWORD = license_data['rh_password'] - license_data = { - "eula_accepted": eula_accepted, - "features": license_data_validated['features'], - "license_type": license_data_validated['license_type'], - "license_date": license_data_validated['license_date'], - "license_key": license_data_validated['license_key'], - "instance_count": license_data_validated['instance_count'], - } - if license_data_validated.get('trial'): - license_data['trial'] = True - except Exception as exc: - msg = _("Invalid License") - if ( - isinstance(exc, requests.exceptions.HTTPError) and - getattr(getattr(exc, 'response', None), 'status_code', None) == 401 - ): - msg = _("The provided credentials are invalid (HTTP 401).") - if isinstance(exc, ValueError) and exc.args: - msg = exc.args[0] - logger.exception(smart_text(u"Invalid license submitted."), - extra=dict(actor=request.user.username)) - return Response({"error": msg}, status=status.HTTP_400_BAD_REQUEST) + except Exception: + logger.warning(smart_text(u"Invalid license submitted."), + extra=dict(actor=request.user.username)) + return Response({"error": _("Invalid License")}, status=status.HTTP_400_BAD_REQUEST) # If the license is valid, write it to the database. if license_data_validated['valid_key']: From 608567795de40e92cf1cd67d816f424b33bf5817 Mon Sep 17 00:00:00 2001 From: mabashian Date: Wed, 4 Sep 2019 17:34:17 -0400 Subject: [PATCH 08/13] Add support for looking up and selecting licenses on license page --- awx/ui/client/legacy/styles/ansible-ui.less | 2 +- awx/ui/client/src/license/license.block.less | 92 +++++- .../client/src/license/license.controller.js | 67 ++++- .../client/src/license/license.partial.html | 277 +++++++++++------- awx/ui/client/src/shared/Utilities.js | 4 +- .../src/shared/config/config.service.js | 5 + 6 files changed, 330 insertions(+), 117 deletions(-) diff --git a/awx/ui/client/legacy/styles/ansible-ui.less b/awx/ui/client/legacy/styles/ansible-ui.less index 708cc44496..a85621fd9b 100644 --- a/awx/ui/client/legacy/styles/ansible-ui.less +++ b/awx/ui/client/legacy/styles/ansible-ui.less @@ -1595,7 +1595,7 @@ tr td button i { padding: 20px 0; .alert { - padding: 10px; + padding: 0px; margin: 0; word-wrap: break-word; } diff --git a/awx/ui/client/src/license/license.block.less b/awx/ui/client/src/license/license.block.less index bc7008e7e6..f86d323ab8 100644 --- a/awx/ui/client/src/license/license.block.less +++ b/awx/ui/client/src/license/license.block.less @@ -183,10 +183,13 @@ overflow: hidden; } +.License-rhCredField { + margin-bottom: 10px; +} + .License-label { color: @field-label; font-weight: 400; - margin-top: 10px; } .License-action { @@ -198,3 +201,90 @@ .License-actionError { flex: 1; } + +.License-subSelectorModal { + height: 100%; + width: 100%; + position: fixed; + top: 0; + left: 0; + background: rgba(0, 0, 0, 0.3); + z-index: 1040; + display: flex; + align-items: center; + justify-content: center; +} + +.License-modal { + width: 750px; +} + +.License-modalBody { + border: 1px solid @b7grey; + max-height: 550px; + overflow: scroll; + border-radius: 4px; +} + +.License-modalRow { + display: flex; + padding: 10px; +} + +.License-modalRow:not(:last-of-type) { + border-bottom: 1px solid @b7grey; +} + +.License-modalRowRadio { + flex: 0 0 40px; + display: flex; + align-items: center; +} + +.License-trialTag { + font-weight: 100; + background-color: #ebebeb; + border-radius: 5px; + color: #606060; + font-size: 10px; + margin-right: 10px; + padding: 3px 9px; + line-height: 14px; + word-break: keep-all; + display: inline-flex; +} + +.License-introText { + margin-bottom: 10px; +} + +.License-getLicensesButton { + display: flex; + justify-content: flex-end; + margin-bottom: 20px; +} + +.License-checkboxLabel { + margin-left: 5px; + font-weight: normal; +} + +.License-modalRowDetails { + flex: 1; +} + +.License-modalRowDetailsLabel { + font-weight: normal; + width: 100%; +} + +.License-modalRowDetailsRow { + margin-bottom: 10px; +} + +.License-modalRowDetails--50 { + display: flex; + flex-basis: 50%; + align-items: center; + line-height: 21px; +} diff --git a/awx/ui/client/src/license/license.controller.js b/awx/ui/client/src/license/license.controller.js index 318e28c864..3b1f3496ce 100644 --- a/awx/ui/client/src/license/license.controller.js +++ b/awx/ui/client/src/license/license.controller.js @@ -32,11 +32,9 @@ export default }; const reset = function() { - document.getElementById('License-form').reset(); $scope.newLicense.eula = undefined; - if (!$scope.licenseError) { - $scope.rhCreds = {}; - } + $scope.rhCreds = {}; + $scope.selectedLicense = {}; }; const initVars = (config) => { @@ -56,6 +54,7 @@ export default $scope.time.expiresOn = calcExpiresOn($scope.license.license_info.license_date); $scope.valid = CheckLicense.valid($scope.license.license_info); $scope.compliant = $scope.license.license_info.compliant; + $scope.selectedLicense = {}; $scope.newLicense = { pendo: true, insights: true @@ -131,18 +130,60 @@ export default } }; + $scope.lookupLicenses = () => { + if ($scope.rhCreds.username && $scope.rhCreds.password) { + Wait('start'); + ConfigService.getSubscriptions($scope.rhCreds.username, $scope.rhCreds.password) + .then(({data}) => { + Wait('stop'); + if (data && data.length > 0) { + $scope.rhLicenses = data; + if ($scope.selectedLicense.fullLicense) { + $scope.selectedLicense.modalKey = $scope.selectedLicense.fullLicense.license_key; + } + $scope.showLicenseModal = true; + } else { + ProcessErrors($scope, data, status, null, { + hdr: i18n._('No Licenses Found'), + msg: i18n._('We were unable to locate licenses associated with this account') + }); + } + }) + .catch(({data, status}) => { + Wait('stop'); + ProcessErrors($scope, data, status, null, { + hdr: i18n._('Error Fetching Licenses') + }); + }); + } + }; + + $scope.confirmLicenseSelection = () => { + $scope.showLicenseModal = false; + $scope.selectedLicense.fullLicense = $scope.rhLicenses.find((license) => { + return license.license_key === $scope.selectedLicense.modalKey; + }); + $scope.selectedLicense.modalKey = undefined; + }; + + $scope.cancelLicenseLookup = () => { + $scope.showLicenseModal = false; + $scope.selectedLicense.modalKey = undefined; + }; + $scope.submit = function() { Wait('start'); - $scope.licenseError = false; let payload = {}; if ($scope.newLicense.file) { payload = $scope.newLicense.file; - } else if ($scope.rhCreds.username && $scope.rhCreds.password) { - payload = { - rh_password: $scope.rhCreds.password, - rh_username: $scope.rhCreds.username - }; + } else if ($scope.selectedLicense.fullLicense) { + payload = $scope.selectedLicense.fullLicense; + if ($scope.rhCreds.username && $scope.rhCreds.password) { + payload.rh_password = $scope.rhCreds.password; + payload.rh_username = $scope.rhCreds.username; + } } + CheckLicense.post(payload, $scope.newLicense.eula) .then((licenseInfo) => { reset(); @@ -179,9 +220,11 @@ export default }, 4000); } }); - }).catch((data) => { + }).catch(({data, status}) => { Wait('stop'); - $scope.licenseError = data.error; + ProcessErrors($scope, data, status, null, { + hdr: i18n._('Error Applying License') + }); }); }; }]; diff --git a/awx/ui/client/src/license/license.partial.html b/awx/ui/client/src/license/license.partial.html index 7b857ad506..924ec5c459 100644 --- a/awx/ui/client/src/license/license.partial.html +++ b/awx/ui/client/src/license/license.partial.html @@ -75,118 +75,193 @@
{{title}}
-
Welcome to Ansible Tower! Please complete the steps below to acquire a license.
-
- - 1 - - - Please click the button below to visit Ansible's website to get a Tower license key. - -
- - - -
- - 2 - - - Choose your license file or provide your Red Hat customer credentials, agree to the End User License Agreement, and click submit. - -
- -
-
- * - License -
-
-
-
-
Upload a license file
-
- Browse - {{fileName|translate}} - -
+
Welcome to Ansible Tower! Please complete the steps below to acquire a license.
+
+
+
+
+ + 1 + + + Please click the button below to visit Ansible's website to get a Tower license key. +
-
-
-
-
OR
-
-
-
-
-
Provide your Red Hat customer credentials
-
- - -
-
- -
- - - - -
-
- -
-
+ + + +
+ + 2 + + + Choose your license file, agree to the End User License Agreement, and click submit. + +
+
+ * + License +
+
Upload a license file
+
+ Browse + {{fileName|translate}} +
-
- * - End User License Agreement +
+
+
OR
+
-
{{ license.eula }}
-
+
+
+
+ + Provide your Red Hat customer credentials and you can choose from a list of your available licenses. The credentials you use will be stored for future use in retrieving renewal or expanded licenses. You can update or remove them in SETTINGS > SYSTEM. + +
+
+ + +
+
+ +
+ + + + +
+
+ +
+
+
+ GET LICENSES +
+
+
+ Selected +
+ {{selectedLicense.fullLicense.subscription_name}} +
+
+
+
+
+ * + End User License Agreement +
+
{{ license.eula }}
+
+
+ +
+
+
+ Tracking and Analytics +
+
+ + + By default, Tower collects and transmits analytics data on Tower usage to Red Hat. There are two categories of data collected by Tower. For more information, see this Tower documentation page. Uncheck the following boxes to disable this feature. + +
- -
-
-
- Tracking and Analytics -
-
- - - By default, Tower collects and transmits analytics data on Tower usage to Red Hat. There are two categories of data collected by Tower. For more information, see this Tower documentation page. Uncheck the following boxes to disable this feature. - -
-
+
-
- - Automation analytics: This data is used to enhance future releases of the Tower Software and to provide Automation Insights to Tower subscribers. -
+ User analytics: This data is used to enhance future releases of the Tower Software and help streamline customer experience and success. + +
+
+
-
-
- Save successful! - Save unsuccessful - {{licenseError}} -
-
- -
+
+
+
+ Save successful!
- +
+ +
+
+
+ +
diff --git a/awx/ui/client/src/shared/Utilities.js b/awx/ui/client/src/shared/Utilities.js index 8c3a2b9d7b..c731a54bba 100644 --- a/awx/ui/client/src/shared/Utilities.js +++ b/awx/ui/client/src/shared/Utilities.js @@ -238,9 +238,9 @@ angular.module('Utilities', ['RestServices', 'Utilities']) msg = ""; _.forOwn(data, function(value, key) { if (Array.isArray(data[key])) { - msg += `${key}: ${data[key][0]}`; + msg += `${key.toUpperCase()}: ${data[key][0]}`; } else { - msg += `${key} : ${value} `; + msg += `${key.toUpperCase()}: ${value} `; } }); Alert(defaultMsg.hdr, msg); diff --git a/awx/ui/client/src/shared/config/config.service.js b/awx/ui/client/src/shared/config/config.service.js index 910e89c9e4..8432303f8e 100644 --- a/awx/ui/client/src/shared/config/config.service.js +++ b/awx/ui/client/src/shared/config/config.service.js @@ -58,6 +58,11 @@ export default deferred.reject('Config not found.'); } return deferred.promise; + }, + + getSubscriptions: function(username, password) { + Rest.setUrl(`${GetBasePath('config')}subscriptions`); + return Rest.post({ rh_username: username, rh_password: password} ); } }; } From 113622c05ece11478eb28647b37fc1a667dc4882 Mon Sep 17 00:00:00 2001 From: mabashian Date: Thu, 5 Sep 2019 09:10:59 -0400 Subject: [PATCH 09/13] Moves initial system settings rest call out to resolve --- awx/api/views/root.py | 2 +- .../client/src/license/license.controller.js | 23 +++++++++++++------ awx/ui/client/src/license/license.route.js | 20 +++++++++++++++- .../spec/license/license.controller-test.js | 15 +++++++++--- 4 files changed, 48 insertions(+), 12 deletions(-) diff --git a/awx/api/views/root.py b/awx/api/views/root.py index b45ccc0071..91f6b62149 100644 --- a/awx/api/views/root.py +++ b/awx/api/views/root.py @@ -201,7 +201,7 @@ class ApiV2SubscriptionView(APIView): getattr(getattr(exc, 'response', None), 'status_code', None) == 401 ): msg = _("The provided credentials are invalid (HTTP 401).") - if isinstance(exc, ValueError) and exc.args: + if isinstance(exc, (ValueError, OSError)) and exc.args: msg = exc.args[0] logger.exception(smart_text(u"Invalid license submitted."), extra=dict(actor=request.user.username)) diff --git a/awx/ui/client/src/license/license.controller.js b/awx/ui/client/src/license/license.controller.js index 3b1f3496ce..c0f9012edf 100644 --- a/awx/ui/client/src/license/license.controller.js +++ b/awx/ui/client/src/license/license.controller.js @@ -7,10 +7,10 @@ import {N_} from "../i18n"; export default - ['Wait', '$state', '$scope', '$rootScope', 'ProcessErrors', 'CheckLicense', 'moment', 'Rest', '$timeout', - '$window', 'ConfigService', 'pendoService', 'insightsEnablementService', 'i18n', 'config', 'GetBasePath', - function(Wait, $state, $scope, $rootScope, ProcessErrors, CheckLicense, moment, Rest, $timeout, - $window, ConfigService, pendoService, insightsEnablementService, i18n, config, GetBasePath) { + ['Wait', '$state', '$scope', '$rootScope', 'ProcessErrors', 'CheckLicense', 'moment', '$timeout', 'Rest', + '$window', 'ConfigService', 'pendoService', 'insightsEnablementService', 'i18n', 'config', 'rhCreds', 'GetBasePath', + function(Wait, $state, $scope, $rootScope, ProcessErrors, CheckLicense, moment, $timeout, Rest, + $window, ConfigService, pendoService, insightsEnablementService, i18n, config, rhCreds, GetBasePath) { const calcDaysRemaining = function(seconds) { // calculate the number of days remaining on the license @@ -61,9 +61,18 @@ export default }; $scope.rhCreds = {}; + + if (rhCreds.REDHAT_USERNAME && rhCreds.REDHAT_USERNAME !== "") { + $scope.rhCreds.username = rhCreds.REDHAT_USERNAME; + } + + if (rhCreds.REDHAT_PASSWORD && rhCreds.REDHAT_PASSWORD !== "") { + $scope.rhCreds.password = rhCreds.REDHAT_PASSWORD; + $scope.showPlaceholderPassword = true; + } }; - const init = (config) => { + const updateRHCreds = (config) => { Rest.setUrl(`${GetBasePath('settings')}system/`); Rest.get() .then(({data}) => { @@ -82,7 +91,7 @@ export default }); }; - init(config); + initVars(config); $scope.getKey = function(event) { // Mimic HTML5 spec, show filename @@ -210,7 +219,7 @@ export default licenseMissing: false }); } else { - init(config); + updateRHCreds(config); $scope.success = true; $rootScope.licenseMissing = false; // for animation purposes diff --git a/awx/ui/client/src/license/license.route.js b/awx/ui/client/src/license/license.route.js index 430da0174e..4c1251ca6a 100644 --- a/awx/ui/client/src/license/license.route.js +++ b/awx/ui/client/src/license/license.route.js @@ -42,6 +42,24 @@ export default { return config; }); } - ] + ], + rhCreds: ['Rest', 'GetBasePath', function(Rest, GetBasePath) { + Rest.setUrl(`${GetBasePath('settings')}system/`); + return Rest.get() + .then(({data}) => { + const rhCreds = {}; + if (data.REDHAT_USERNAME && data.REDHAT_USERNAME !== "") { + rhCreds.REDHAT_USERNAME = data.REDHAT_USERNAME; + } + + if (data.REDHAT_PASSWORD && data.REDHAT_PASSWORD !== "") { + rhCreds.REDHAT_PASSWORD = data.REDHAT_PASSWORD; + } + + return rhCreds; + }).catch(() => { + return {}; + }); + }] }, }; diff --git a/awx/ui/test/spec/license/license.controller-test.js b/awx/ui/test/spec/license/license.controller-test.js index eb013776b4..660e918e79 100644 --- a/awx/ui/test/spec/license/license.controller-test.js +++ b/awx/ui/test/spec/license/license.controller-test.js @@ -6,7 +6,8 @@ describe('Controller: LicenseController', () => { LicenseController, ConfigService, ProcessErrors, - config; + config, + rhCreds; beforeEach(angular.mock.module('awApp')); beforeEach(angular.mock.module('license', ($provide) => { @@ -22,23 +23,31 @@ describe('Controller: LicenseController', () => { version: '3.1.0-devel' }; + rhCreds = { + password: '$encrypted$', + username: 'foo', + } + ProcessErrors = jasmine.createSpy('ProcessErrors'); $provide.value('ConfigService', ConfigService); $provide.value('ProcessErrors', ProcessErrors); $provide.value('config', config); + $provide.value('rhCreds', rhCreds); })); - beforeEach(angular.mock.inject( ($rootScope, $controller, _ConfigService_, _ProcessErrors_, _config_) => { + beforeEach(angular.mock.inject( ($rootScope, $controller, _ConfigService_, _ProcessErrors_, _config_, _rhCreds_) => { scope = $rootScope.$new(); ConfigService = _ConfigService_; ProcessErrors = _ProcessErrors_; config = _config_; + rhCreds = _rhCreds_; LicenseController = $controller('licenseController', { $scope: scope, ConfigService: ConfigService, ProcessErrors: ProcessErrors, - config: config + config: config, + rhCreds: rhCreds }); })); From 611f16328919d7d7b366606e053ce8da1cad49d3 Mon Sep 17 00:00:00 2001 From: mabashian Date: Tue, 10 Sep 2019 14:26:52 -0400 Subject: [PATCH 10/13] Make sure that the license page under settings has creds available --- .../forms/settings-form.route.js | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/awx/ui/client/src/configuration/forms/settings-form.route.js b/awx/ui/client/src/configuration/forms/settings-form.route.js index a03b4cbfa5..a67ec164e8 100644 --- a/awx/ui/client/src/configuration/forms/settings-form.route.js +++ b/awx/ui/client/src/configuration/forms/settings-form.route.js @@ -53,4 +53,24 @@ export default { } }); }], + resolve: { + rhCreds: ['Rest', 'GetBasePath', function(Rest, GetBasePath) { + Rest.setUrl(`${GetBasePath('settings')}system/`); + return Rest.get() + .then(({data}) => { + const rhCreds = {}; + if (data.REDHAT_USERNAME && data.REDHAT_USERNAME !== "") { + rhCreds.REDHAT_USERNAME = data.REDHAT_USERNAME; + } + + if (data.REDHAT_PASSWORD && data.REDHAT_PASSWORD !== "") { + rhCreds.REDHAT_PASSWORD = data.REDHAT_PASSWORD; + } + + return rhCreds; + }).catch(() => { + return {}; + }); + }] + } }; \ No newline at end of file From 8a29276a7d8c17325a7ed638e50de8490c258838 Mon Sep 17 00:00:00 2001 From: mabashian Date: Tue, 10 Sep 2019 14:28:58 -0400 Subject: [PATCH 11/13] Fix whitespace --- .../client/src/configuration/forms/settings-form.route.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/awx/ui/client/src/configuration/forms/settings-form.route.js b/awx/ui/client/src/configuration/forms/settings-form.route.js index a67ec164e8..20ca06fd7f 100644 --- a/awx/ui/client/src/configuration/forms/settings-form.route.js +++ b/awx/ui/client/src/configuration/forms/settings-form.route.js @@ -55,8 +55,8 @@ export default { }], resolve: { rhCreds: ['Rest', 'GetBasePath', function(Rest, GetBasePath) { - Rest.setUrl(`${GetBasePath('settings')}system/`); - return Rest.get() + Rest.setUrl(`${GetBasePath('settings')}system/`); + return Rest.get() .then(({data}) => { const rhCreds = {}; if (data.REDHAT_USERNAME && data.REDHAT_USERNAME !== "") { @@ -71,6 +71,6 @@ export default { }).catch(() => { return {}; }); - }] + }] } }; \ No newline at end of file From eba69142f1c8c31021c1631a945c49a1d0cc5c01 Mon Sep 17 00:00:00 2001 From: Elijah DeLee Date: Tue, 10 Sep 2019 17:02:15 -0400 Subject: [PATCH 12/13] add subscriptions endpoint to awxkit --- awxkit/awxkit/api/pages/__init__.py | 1 + awxkit/awxkit/api/pages/subscriptions.py | 11 +++++++++++ awxkit/awxkit/api/resources.py | 1 + 3 files changed, 13 insertions(+) create mode 100644 awxkit/awxkit/api/pages/subscriptions.py diff --git a/awxkit/awxkit/api/pages/__init__.py b/awxkit/awxkit/api/pages/__init__.py index 19cf7323ca..fafe5dc08f 100644 --- a/awxkit/awxkit/api/pages/__init__.py +++ b/awxkit/awxkit/api/pages/__init__.py @@ -38,3 +38,4 @@ from .instances import * # NOQA from .instance_groups import * # NOQA from .credential_input_sources import * # NOQA from .metrics import * # NOQA +from .subscriptions import * # NOQA diff --git a/awxkit/awxkit/api/pages/subscriptions.py b/awxkit/awxkit/api/pages/subscriptions.py new file mode 100644 index 0000000000..025c100f41 --- /dev/null +++ b/awxkit/awxkit/api/pages/subscriptions.py @@ -0,0 +1,11 @@ +from awxkit.api.resources import resources +from . import base +from . import page + + +class Subscriptions(page.Page): + + def get_possible_licenses(self, **kwargs): + return self.post(json=kwargs).json + +page.register_page(resources.subscriptions, Subscriptions) diff --git a/awxkit/awxkit/api/resources.py b/awxkit/awxkit/api/resources.py index 657a41b7f3..d317bcc55d 100644 --- a/awxkit/awxkit/api/resources.py +++ b/awxkit/awxkit/api/resources.py @@ -265,6 +265,7 @@ class Resources(object): _workflow_job_template_workflow_nodes = r'workflow_job_templates/\d+/workflow_nodes/' _workflow_job_templates = 'workflow_job_templates/' _workflow_job_workflow_nodes = r'workflow_jobs/\d+/workflow_nodes/' + _subscriptions = 'config/subscriptions/' _workflow_jobs = 'workflow_jobs/' api = '/api/' common = api + r'v\d+/' From 662033db444c49242c8041c6d21d1f42f956ff5a Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Thu, 12 Sep 2019 08:08:52 -0400 Subject: [PATCH 13/13] fix some awxkit flake8 failures --- awxkit/awxkit/api/pages/subscriptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awxkit/awxkit/api/pages/subscriptions.py b/awxkit/awxkit/api/pages/subscriptions.py index 025c100f41..749776c000 100644 --- a/awxkit/awxkit/api/pages/subscriptions.py +++ b/awxkit/awxkit/api/pages/subscriptions.py @@ -1,5 +1,4 @@ from awxkit.api.resources import resources -from . import base from . import page @@ -8,4 +7,5 @@ class Subscriptions(page.Page): def get_possible_licenses(self, **kwargs): return self.post(json=kwargs).json + page.register_page(resources.subscriptions, Subscriptions)