fix(app): wrapper for UI refresh trigger with async/await (#2945)

* fix(app): wrapper for UI refresh trigger with async/await

* fix(async): $async wrapper now accepts functions with params

* fix(async): $async should return a promise to be chained with ES5 .then() style

* fix(async): $async with multiple params was not working

* refactor(app): wrap all async functions with $async

* docs(async): add link to async wrapper documentation
pull/2942/head
xAt0mZ 2019-06-17 16:51:39 +02:00 committed by GitHub
parent 09cf55a7dc
commit 71b1da8d32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 167 additions and 92 deletions

View File

@ -3,12 +3,14 @@ import angular from 'angular';
class ConfigsController {
/* @ngInject */
constructor($state, ConfigService, Notifications) {
constructor($state, ConfigService, Notifications, $async) {
this.$state = $state;
this.ConfigService = ConfigService;
this.Notifications = Notifications;
this.$async = $async;
this.removeAction = this.removeAction.bind(this);
this.removeActionAsync = this.removeActionAsync.bind(this);
}
async $onInit() {
@ -20,7 +22,11 @@ class ConfigsController {
}
}
async removeAction(selectedItems) {
removeAction(selectedItems) {
return this.$async(this.removeActionAsync, selectedItems);
}
async removeActionAsync(selectedItems) {
let actionCount = selectedItems.length;
for (const config of selectedItems) {
try {

View File

@ -5,7 +5,7 @@ import angular from "angular";
class CreateConfigController {
/* @ngInject */
constructor($state, $transition$, Notifications, ConfigService, Authentication, FormValidator, ResourceControlService) {
constructor($async, $state, $transition$, Notifications, ConfigService, Authentication, FormValidator, ResourceControlService) {
this.$state = $state;
this.$transition$ = $transition$;
this.Notifications = Notifications;
@ -13,6 +13,7 @@ class CreateConfigController {
this.Authentication = Authentication;
this.FormValidator = FormValidator;
this.ResourceControlService = ResourceControlService;
this.$async = $async;
this.formValues = {
Name: "",
@ -26,6 +27,30 @@ class CreateConfigController {
};
this.editorUpdate = this.editorUpdate.bind(this);
this.createAsync = this.createAsync.bind(this);
}
async $onInit() {
if (!this.$transition$.params().id) {
this.formValues.displayCodeEditor = true;
return;
}
try {
let data = await this.ConfigService.config(this.$transition$.params().id);
this.formValues.Name = data.Name + "_copy";
this.formValues.Data = data.Data;
let labels = _.keys(data.Labels);
for (let i = 0; i < labels.length; i++) {
let labelName = labels[i];
let labelValue = data.Labels[labelName];
this.formValues.Labels.push({ name: labelName, value: labelValue });
}
this.formValues.displayCodeEditor = true;
} catch (err) {
this.formValues.displayCodeEditor = true;
this.Notifications.error("Failure", err, "Unable to clone config");
}
}
addLabel() {
@ -74,7 +99,11 @@ class CreateConfigController {
return true;
}
async create() {
create() {
return this.$async(this.createAsync);
}
async createAsync() {
let accessControlData = this.formValues.AccessControlData;
let userDetails = this.Authentication.getUserDetails();
let isAdmin = this.Authentication.isAdmin();
@ -111,29 +140,6 @@ class CreateConfigController {
editorUpdate(cm) {
this.formValues.ConfigContent = cm.getValue();
}
async $onInit() {
if (!this.$transition$.params().id) {
this.formValues.displayCodeEditor = true;
return;
}
try {
let data = await this.ConfigService.config(this.$transition$.params().id);
this.formValues.Name = data.Name + "_copy";
this.formValues.Data = data.Data;
let labels = _.keys(data.Labels);
for (let i = 0; i < labels.length; i++) {
let labelName = labels[i];
let labelValue = data.Labels[labelName];
this.formValues.Labels.push({ name: labelName, value: labelValue });
}
this.formValues.displayCodeEditor = true;
} catch (err) {
this.formValues.displayCodeEditor = true;
this.Notifications.error("Failure", err, "Unable to clone config");
}
}
}
export default CreateConfigController;

View File

@ -1,33 +1,37 @@
angular.module('portainer.extensions.rbac').directive('authorization', ['Authentication', 'ExtensionService',
function(Authentication, ExtensionService) {
return {
restrict: 'A',
link: async function(scope, elem, attrs) {
elem.hide();
try {
const rbacEnabled = await ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC);
if (!rbacEnabled) {
elem.show();
return;
}
} catch (err) {
angular.module('portainer.extensions.rbac').directive('authorization', ['Authentication', 'ExtensionService', '$async',
function(Authentication, ExtensionService, $async) {
async function linkAsync(scope, elem, attrs) {
elem.hide();
try {
const rbacEnabled = await ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC);
if (!rbacEnabled) {
elem.show();
return;
}
} catch (err) {
elem.show();
return;
}
var authorizations = attrs.authorization.split(",");
for (var i = 0; i < authorizations.length; i++) {
authorizations[i] = authorizations[i].trim();
}
var authorizations = attrs.authorization.split(",");
for (var i = 0; i < authorizations.length; i++) {
authorizations[i] = authorizations[i].trim();
}
var hasAuthorizations = Authentication.hasAuthorizations(authorizations);
var hasAuthorizations = Authentication.hasAuthorizations(authorizations);
if (hasAuthorizations) {
elem.show();
} else if (!hasAuthorizations && elem[0].tagName === 'A') {
elem.show();
elem.addClass('portainer-disabled-link');
}
}
if (hasAuthorizations) {
elem.show();
} else if (!hasAuthorizations && elem[0].tagName === 'A') {
elem.show();
elem.addClass('portainer-disabled-link');
}
return {
restrict: 'A',
link: function(scope, elem, attrs) {
return $async(linkAsync, scope, elem, attrs);
}
}
}]);

View File

@ -1,27 +1,32 @@
angular.module('portainer.extensions.rbac')
.directive('disableAuthorization', ['Authentication', 'ExtensionService', function(Authentication, ExtensionService) {
return {
restrict: 'A',
link: async function (scope, elem, attrs) {
try {
const rbacEnabled = await ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC);
if (!rbacEnabled) {
elem.show();
return;
}
} catch (err) {
.directive('disableAuthorization', ['Authentication', 'ExtensionService', '$async', function(Authentication, ExtensionService, $async) {
async function linkAsync(scope, elem, attrs) {
try {
const rbacEnabled = await ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC);
if (!rbacEnabled) {
elem.show();
return;
}
} catch (err) {
elem.show();
return;
}
var authorizations = attrs.disableAuthorization.split(",");
for (var i = 0; i < authorizations.length; i++) {
authorizations[i] = authorizations[i].trim();
}
var authorizations = attrs.disableAuthorization.split(",");
for (var i = 0; i < authorizations.length; i++) {
authorizations[i] = authorizations[i].trim();
}
if (!Authentication.hasAuthorizations(authorizations)) {
elem.attr('disabled', true);
}
if (!Authentication.hasAuthorizations(authorizations)) {
elem.attr('disabled', true);
}
}
return {
restrict: 'A',
link: function (scope, elem, attrs) {
return $async(linkAsync, scope, elem, attrs);
}
}
}]);

View File

@ -2,7 +2,7 @@ import _ from 'lodash-es';
import { ExtensionViewModel } from '../../models/extension';
angular.module('portainer.app')
.factory('ExtensionService', ['$q', 'Extension', 'StateManager', function ExtensionServiceFactory($q, Extension, StateManager) {
.factory('ExtensionService', ['$q', 'Extension', 'StateManager', '$async', function ExtensionServiceFactory($q, Extension, StateManager, $async) {
'use strict';
var service = {};
@ -12,19 +12,27 @@ angular.module('portainer.app')
RBAC: 3
});
service.enable = function(license) {
service.enable = enable;
service.update = update;
service.delete = _delete;
service.extensions = extensions;
service.extension = extension;
service.extensionEnabled = extensionEnabled;
service.retrieveAndSaveEnabledExtensions = retrieveAndSaveEnabledExtensions;
function enable(license) {
return Extension.create({ license: license }).$promise;
};
}
service.update = function(id, version) {
function update(id, version) {
return Extension.update({ id: id, version: version }).$promise;
};
}
service.delete = function(id) {
function _delete(id) {
return Extension.delete({ id: id }).$promise;
};
}
service.extensions = function(store) {
function extensions(store) {
var deferred = $q.defer();
Extension.query({ store: store }).$promise
@ -39,9 +47,9 @@ angular.module('portainer.app')
});
return deferred.promise;
};
}
service.extension = function(id) {
function extension(id) {
var deferred = $q.defer();
Extension.get({ id: id }).$promise
@ -54,9 +62,13 @@ angular.module('portainer.app')
});
return deferred.promise;
};
}
service.extensionEnabled = async function(extensionId) {
function extensionEnabled(extensionId) {
return $async(extensionsEnabledAsync, extensionId)
}
async function extensionsEnabledAsync(extensionId) {
if (extensionId === service.EXTENSIONS.RBAC) {
return StateManager.getExtension(extensionId) ? true : false;
} else {
@ -64,13 +76,17 @@ angular.module('portainer.app')
const extension = _.find(extensions, (ext) => ext.Id === extensionId);
return extension ? extension.Enabled : false;
}
};
}
service.retrieveAndSaveEnabledExtensions = async function() {
function retrieveAndSaveEnabledExtensions() {
return $async(retrieveAndSaveEnabledExtensionsAsync)
}
async function retrieveAndSaveEnabledExtensionsAsync() {
const extensions = await service.extensions(false);
_.forEach(extensions, (ext) => delete ext.License);
StateManager.saveExtensions(extensions);
};
}
return service;
}]);

View File

@ -0,0 +1,23 @@
/**
* Look a the following PR for how to use the wrapper
* and documentation about it
* https://github.com/portainer/portainer/pull/2945
*/
angular.module('portainer').factory('$async', ['$q',
function($q) {
return function(asyncFunc, ...args) {
const def = $q.defer();
const wrapper = function(params) {
const deferred = $q.defer();
asyncFunc(...params)
.then(deferred.resolve)
.catch(deferred.reject);
return deferred.promise;
};
wrapper(args).then(def.resolve).catch(def.reject)
return def.promise;
};
}
]);

View File

@ -1,6 +1,6 @@
angular.module('portainer.app')
.controller('AuthenticationController', ['$q', '$scope', '$state', '$stateParams', '$sanitize', 'Authentication', 'UserService', 'EndpointService', 'ExtensionService', 'StateManager', 'Notifications', 'SettingsService', 'URLHelper',
function($q, $scope, $state, $stateParams, $sanitize, Authentication, UserService, EndpointService, ExtensionService, StateManager, Notifications, SettingsService, URLHelper) {
.controller('AuthenticationController', ['$async', '$q', '$scope', '$state', '$stateParams', '$sanitize', 'Authentication', 'UserService', 'EndpointService', 'ExtensionService', 'StateManager', 'Notifications', 'SettingsService', 'URLHelper',
function($async, $q, $scope, $state, $stateParams, $sanitize, Authentication, UserService, EndpointService, ExtensionService, StateManager, Notifications, SettingsService, URLHelper) {
$scope.logo = StateManager.getState().application.logo;
$scope.formValues = {
@ -14,7 +14,11 @@ function($q, $scope, $state, $stateParams, $sanitize, Authentication, UserServic
OAuthProvider: ''
};
async function retrieveAndSaveEnabledExtensions() {
function retrieveAndSaveEnabledExtensions() {
return $async(retrieveAndSaveEnabledExtensionsAsync);
}
async function retrieveAndSaveEnabledExtensionsAsync() {
try {
await ExtensionService.retrieveAndSaveEnabledExtensions();
} catch (err) {

View File

@ -2,13 +2,16 @@ import angular from "angular";
class EndpointAccessController {
/* @ngInject */
constructor($state, $transition$, Notifications, EndpointService, GroupService) {
constructor($state, $transition$, Notifications, EndpointService, GroupService, $async) {
this.$state = $state;
this.$transition$ = $transition$;
this.Notifications = Notifications;
this.EndpointService = EndpointService;
this.GroupService = GroupService;
this.$async = $async;
this.updateAccess = this.updateAccess.bind(this);
this.updateAccessAsync = this.updateAccessAsync.bind(this);
}
async $onInit() {
@ -23,7 +26,11 @@ class EndpointAccessController {
}
}
async updateAccess() {
updateAccess() {
return this.$async(this.updateAccessAsync);
}
async updateAccessAsync() {
try {
this.state.actionInProgress = true;
await this.EndpointService.updateEndpoint(this.$transition$.params().id, this.endpoint);

View File

@ -1,6 +1,6 @@
angular.module('portainer.app')
.controller('InitAdminController', ['$scope', '$state', 'Notifications', 'Authentication', 'StateManager', 'UserService', 'EndpointService', 'ExtensionService',
function ($scope, $state, Notifications, Authentication, StateManager, UserService, EndpointService, ExtensionService) {
.controller('InitAdminController', ['$async', '$scope', '$state', 'Notifications', 'Authentication', 'StateManager', 'UserService', 'EndpointService', 'ExtensionService',
function ($async, $scope, $state, Notifications, Authentication, StateManager, UserService, EndpointService, ExtensionService) {
$scope.logo = StateManager.getState().application.logo;
@ -14,7 +14,11 @@ function ($scope, $state, Notifications, Authentication, StateManager, UserServi
actionInProgress: false
};
async function retrieveAndSaveEnabledExtensions() {
function retrieveAndSaveEnabledExtensions() {
return $async(retrieveAndSaveEnabledExtensionsAsync)
}
async function retrieveAndSaveEnabledExtensionsAsync() {
try {
await ExtensionService.retrieveAndSaveEnabledExtensions();
} catch (err) {