mirror of
https://github.com/portainer/portainer.git
synced 2025-08-09 15:55:23 +02:00
feat(extensions): remove rbac extension (#4157)
* feat(extensions): remove rbac extension client code * feat(extensions): remove server rbac code * remove extensions code * fix(notifications): remove error * feat(extensions): remove authorizations service * feat(rbac): deprecate fields * fix(portainer): revert change * fix(bouncer): remove rbac authorization check * feat(sidebar): remove roles link * fix(portainer): remove portainer module
This commit is contained in:
parent
8629738e34
commit
9d18d47194
117 changed files with 98 additions and 3487 deletions
|
@ -281,28 +281,6 @@ angular.module('portainer.app', ['portainer.oauth']).config([
|
|||
},
|
||||
};
|
||||
|
||||
var extensions = {
|
||||
name: 'portainer.extensions',
|
||||
url: '/extensions',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/extensions/extensions.html',
|
||||
controller: 'ExtensionsController',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var extension = {
|
||||
name: 'portainer.extensions.extension',
|
||||
url: '/extension/:id',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/extensions/inspect/extension.html',
|
||||
controller: 'ExtensionController',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var registries = {
|
||||
name: 'portainer.registries',
|
||||
url: '/registries',
|
||||
|
@ -469,8 +447,6 @@ angular.module('portainer.app', ['portainer.oauth']).config([
|
|||
$stateRegistryProvider.register(init);
|
||||
$stateRegistryProvider.register(initEndpoint);
|
||||
$stateRegistryProvider.register(initAdmin);
|
||||
$stateRegistryProvider.register(extensions);
|
||||
$stateRegistryProvider.register(extension);
|
||||
$stateRegistryProvider.register(registries);
|
||||
$stateRegistryProvider.register(registry);
|
||||
$stateRegistryProvider.register(registryAccess);
|
||||
|
|
|
@ -10,15 +10,6 @@
|
|||
<button type="button" class="btn btn-sm btn-danger" ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
|
||||
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
|
||||
</button>
|
||||
<button
|
||||
ng-if="$ctrl.rbacEnabled"
|
||||
type="button"
|
||||
class="btn btn-sm btn-primary"
|
||||
ng-disabled="($ctrl.dataset | filter:{ Updated: true}).length === 0 "
|
||||
ng-click="$ctrl.updateAction()"
|
||||
>
|
||||
<i class="fa fa-check space-right" aria-hidden="true"></i>Update
|
||||
</button>
|
||||
</div>
|
||||
<div class="searchBar">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
|
@ -53,13 +44,6 @@
|
|||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Type' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th ng-if="$ctrl.rbacEnabled">
|
||||
<a ng-click="$ctrl.changeOrderBy('RoleName')">
|
||||
Role
|
||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'RoleName' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'RoleName' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -79,18 +63,6 @@
|
|||
<span ng-if="$ctrl.inheritFrom && item.Override" class="text-muted small" style="margin-left: 2px;"><code style="font-size: 85% !important;">override</code></span>
|
||||
</td>
|
||||
<td>{{ item.Type }}</td>
|
||||
<td ng-if="$ctrl.rbacEnabled">
|
||||
<span ng-if="!item.Updated">
|
||||
{{ item.Role.Name }}
|
||||
<a ng-if="!item.Inherited" class="interactive" ng-click="item.Updated = true; item.OldRole = item.Role; $event.stopPropagation();">
|
||||
<i class="fa fa-edit" aria-hidden="true"></i> Edit
|
||||
</a>
|
||||
</span>
|
||||
<span ng-if="item.Updated">
|
||||
<select ng-model="item.Role" ng-options="role.Name for role in $ctrl.roles"> </select>
|
||||
<a class="interactive" ng-click="item.Updated = false; item.Role = item.OldRole; item.OldRole = null; $event.stopPropagation();"><i class="fa fa-times"></i></a>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
<td colspan="4" class="text-center text-muted">Loading...</td>
|
||||
|
|
|
@ -11,7 +11,6 @@ angular.module('portainer.app').component('accessDatatable', {
|
|||
removeAction: '<',
|
||||
updateAction: '<',
|
||||
reverseOrder: '<',
|
||||
rbacEnabled: '<',
|
||||
inheritFrom: '<',
|
||||
},
|
||||
});
|
||||
|
|
|
@ -27,17 +27,7 @@
|
|||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="ctrl.entityType !== 'registry'">
|
||||
<label class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Role
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-4">
|
||||
<select ng-if="ctrl.rbacEnabled" class="form-control" ng-model="ctrl.formValues.selectedRole" ng-options="role.Name for role in ctrl.roles"> </select>
|
||||
<span class="small text-muted" ng-if="!ctrl.rbacEnabled">
|
||||
The <a ui-sref="portainer.extensions.extension({id: 3})">Role-Based Access Control extension</a> is required to select a specific role.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- actions -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
|
@ -67,10 +57,8 @@
|
|||
title-icon="fa-user-lock"
|
||||
table-key="{{ 'access_' + ctrl.entityType }}"
|
||||
order-by="Name"
|
||||
rbac-enabled="ctrl.rbacEnabled && ctrl.entityType !== 'registry'"
|
||||
inherit-from="ctrl.inheritFrom"
|
||||
dataset="ctrl.authorizedUsersAndTeams"
|
||||
roles="ctrl.roles"
|
||||
update-action="ctrl.updateAction"
|
||||
remove-action="ctrl.unauthorizeAccess"
|
||||
>
|
||||
|
|
|
@ -4,11 +4,9 @@ import angular from 'angular';
|
|||
|
||||
class PorAccessManagementController {
|
||||
/* @ngInject */
|
||||
constructor(Notifications, ExtensionService, AccessService, RoleService) {
|
||||
constructor(Notifications, AccessService) {
|
||||
this.Notifications = Notifications;
|
||||
this.ExtensionService = ExtensionService;
|
||||
this.AccessService = AccessService;
|
||||
this.RoleService = RoleService;
|
||||
|
||||
this.unauthorizeAccess = this.unauthorizeAccess.bind(this);
|
||||
this.updateAction = this.updateAction.bind(this);
|
||||
|
@ -31,11 +29,10 @@ class PorAccessManagementController {
|
|||
const entity = this.accessControlledEntity;
|
||||
const oldUserAccessPolicies = entity.UserAccessPolicies;
|
||||
const oldTeamAccessPolicies = entity.TeamAccessPolicies;
|
||||
const selectedRoleId = this.rbacEnabled ? this.formValues.selectedRole.Id : 0;
|
||||
const selectedUserAccesses = _.filter(this.formValues.multiselectOutput, (access) => access.Type === 'user');
|
||||
const selectedTeamAccesses = _.filter(this.formValues.multiselectOutput, (access) => access.Type === 'team');
|
||||
|
||||
const accessPolicies = this.AccessService.generateAccessPolicies(oldUserAccessPolicies, oldTeamAccessPolicies, selectedUserAccesses, selectedTeamAccesses, selectedRoleId);
|
||||
const accessPolicies = this.AccessService.generateAccessPolicies(oldUserAccessPolicies, oldTeamAccessPolicies, selectedUserAccesses, selectedTeamAccesses, 0);
|
||||
this.accessControlledEntity.UserAccessPolicies = accessPolicies.userAccessPolicies;
|
||||
this.accessControlledEntity.TeamAccessPolicies = accessPolicies.teamAccessPolicies;
|
||||
this.updateAccess();
|
||||
|
@ -56,18 +53,7 @@ class PorAccessManagementController {
|
|||
try {
|
||||
const entity = this.accessControlledEntity;
|
||||
const parent = this.inheritFrom;
|
||||
// TODO: refactor
|
||||
// extract this code and locate it in AccessService.accesses() function
|
||||
// see resourcePoolAccessController for another usage of AccessService.accesses()
|
||||
// which needs RBAC support
|
||||
this.roles = [];
|
||||
this.rbacEnabled = await this.ExtensionService.extensionEnabled(this.ExtensionService.EXTENSIONS.RBAC);
|
||||
if (this.rbacEnabled) {
|
||||
this.roles = await this.RoleService.roles();
|
||||
this.formValues = {
|
||||
selectedRole: this.roles[0],
|
||||
};
|
||||
}
|
||||
|
||||
const data = await this.AccessService.accesses(entity, parent, this.roles);
|
||||
this.availableUsersAndTeams = _.orderBy(data.availableUsersAndTeams, 'Name', 'asc');
|
||||
this.authorizedUsersAndTeams = data.authorizedUsersAndTeams;
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
angular.module('portainer.app').component('extensionItem', {
|
||||
templateUrl: './extensionItem.html',
|
||||
controller: 'ExtensionItemController',
|
||||
bindings: {
|
||||
model: '<',
|
||||
currentDate: '<',
|
||||
},
|
||||
});
|
|
@ -1,47 +0,0 @@
|
|||
<!-- extension -->
|
||||
<div class="blocklist-item" ng-click="$ctrl.goToExtensionView()" ng-class="{ 'blocklist-item--disabled': !$ctrl.model.Available }">
|
||||
<div class="blocklist-item-box">
|
||||
<!-- extension-image -->
|
||||
<span ng-if="$ctrl.model.Logo" style="width: 75px; text-align: center;">
|
||||
<!-- <img class="blocklist-item-logo" ng-src="{{ $ctrl.model.Logo }}" /> -->
|
||||
<i class="{{ $ctrl.model.Logo }} fa fa-4x blue-icon" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span class="blocklist-item-logo" ng-if="!$ctrl.model.Logo">
|
||||
<i class="fa fa-bolt fa-4x blue-icon" style="margin-left: 14px;" aria-hidden="true"></i>
|
||||
</span>
|
||||
<!-- !extension-image -->
|
||||
<!-- extension-details -->
|
||||
<span class="col-sm-12">
|
||||
<!-- blocklist-item-line1 -->
|
||||
<div class="blocklist-item-line">
|
||||
<span>
|
||||
<span class="blocklist-item-title">
|
||||
{{ $ctrl.model.Name }}
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
<span class="label label-primary" ng-if="!$ctrl.model.Enabled && !$ctrl.model.Available">coming soon</span>
|
||||
<span class="label label-warning" ng-if="!$ctrl.model.Enabled && $ctrl.model.Deal && !$ctrl.model.License.Expiration">deal</span>
|
||||
<span class="label label-danger" ng-if="!$ctrl.model.Enabled && $ctrl.model.License.Expiration && !$ctrl.model.License.Valid">expired</span>
|
||||
<span class="label label-success" ng-if="$ctrl.model.Enabled && $ctrl.model.License.Valid">enabled</span>
|
||||
<span class="label label-primary" ng-if="$ctrl.model.Enabled && $ctrl.model.License.Valid && $ctrl.model.UpdateAvailable">update available</span>
|
||||
</span>
|
||||
</div>
|
||||
<!-- !blocklist-item-line1 -->
|
||||
<!-- blocklist-item-line2 -->
|
||||
<div class="blocklist-item-line">
|
||||
<span>
|
||||
<span class="blocklist-item-desc">
|
||||
{{ $ctrl.model.ShortDescription }}
|
||||
</span>
|
||||
</span>
|
||||
<span ng-if="$ctrl.model.License.Company">
|
||||
<span class="small text-muted">Licensed to {{ $ctrl.model.License.Company }} - Expires on {{ $ctrl.model.License.Expiration }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<!-- !blocklist-item-line2 -->
|
||||
</span>
|
||||
<!-- !extension-details -->
|
||||
</div>
|
||||
<!-- !extension -->
|
||||
</div>
|
|
@ -1,13 +0,0 @@
|
|||
angular.module('portainer.app').controller('ExtensionItemController', [
|
||||
'$state',
|
||||
function ($state) {
|
||||
var ctrl = this;
|
||||
ctrl.goToExtensionView = goToExtensionView;
|
||||
|
||||
function goToExtensionView() {
|
||||
if (ctrl.model.Available) {
|
||||
$state.go('portainer.extensions.extension', { id: ctrl.model.Id });
|
||||
}
|
||||
}
|
||||
},
|
||||
]);
|
|
@ -1,7 +0,0 @@
|
|||
angular.module('portainer.app').component('extensionList', {
|
||||
templateUrl: './extensionList.html',
|
||||
bindings: {
|
||||
extensions: '<',
|
||||
currentDate: '<',
|
||||
},
|
||||
});
|
|
@ -1,13 +0,0 @@
|
|||
<div class="datatable">
|
||||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="toolBar">
|
||||
<div class="toolBarTitle"> <i class="fa fa-bolt" aria-hidden="true" style="margin-right: 2px;"></i> Available extensions </div>
|
||||
</div>
|
||||
|
||||
<div class="blocklist">
|
||||
<extension-item ng-repeat="extension in $ctrl.extensions" model="extension" current-date="$ctrl.currentDate"></extension-item>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
|
@ -1,17 +0,0 @@
|
|||
export function ExtensionViewModel(data) {
|
||||
this.Id = data.Id;
|
||||
this.Name = data.Name;
|
||||
this.Enabled = data.Enabled;
|
||||
this.Description = data.Description;
|
||||
this.Price = data.Price;
|
||||
this.PriceDescription = data.PriceDescription;
|
||||
this.Available = data.Available;
|
||||
this.Deal = data.Deal;
|
||||
this.ShortDescription = data.ShortDescription;
|
||||
this.License = data.License;
|
||||
this.Version = data.Version;
|
||||
this.UpdateAvailable = data.UpdateAvailable;
|
||||
this.ShopURL = data.ShopURL;
|
||||
this.Images = data.Images;
|
||||
this.Logo = data.Logo;
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
angular.module('portainer.app').factory('Extension', [
|
||||
'$resource',
|
||||
'API_ENDPOINT_EXTENSIONS',
|
||||
function ExtensionFactory($resource, API_ENDPOINT_EXTENSIONS) {
|
||||
'use strict';
|
||||
return $resource(
|
||||
API_ENDPOINT_EXTENSIONS + '/:id/:action',
|
||||
{},
|
||||
{
|
||||
create: { method: 'POST' },
|
||||
query: { method: 'GET', isArray: true },
|
||||
get: { method: 'GET', params: { id: '@id' } },
|
||||
delete: { method: 'DELETE', params: { id: '@id' } },
|
||||
update: { method: 'POST', params: { id: '@id', action: 'update' } },
|
||||
}
|
||||
);
|
||||
},
|
||||
]);
|
|
@ -11,15 +11,7 @@ angular.module('portainer.app').factory('AccessService', [
|
|||
'use strict';
|
||||
var service = {};
|
||||
|
||||
function _getRole(roles, roleId) {
|
||||
if (roles.length) {
|
||||
const role = _.find(roles, (role) => role.Id === roleId);
|
||||
return role ? role : { Id: 0, Name: '-' };
|
||||
}
|
||||
return { Id: 0, Name: '-' };
|
||||
}
|
||||
|
||||
function _mapAccessData(accesses, authorizedPolicies, inheritedPolicies, roles) {
|
||||
function _mapAccessData(accesses, authorizedPolicies, inheritedPolicies) {
|
||||
var availableAccesses = [];
|
||||
var authorizedAccesses = [];
|
||||
|
||||
|
@ -30,14 +22,11 @@ angular.module('portainer.app').factory('AccessService', [
|
|||
const inherited = inheritedPolicies && inheritedPolicies[access.Id];
|
||||
|
||||
if (authorized && inherited) {
|
||||
access.Role = _getRole(roles, authorizedPolicies[access.Id].RoleId);
|
||||
access.Override = true;
|
||||
authorizedAccesses.push(access);
|
||||
} else if (authorized && !inherited) {
|
||||
access.Role = _getRole(roles, authorizedPolicies[access.Id].RoleId);
|
||||
authorizedAccesses.push(access);
|
||||
} else if (!authorized && inherited) {
|
||||
access.Role = _getRole(roles, inheritedPolicies[access.Id].RoleId);
|
||||
access.Inherited = true;
|
||||
authorizedAccesses.push(access);
|
||||
availableAccesses.push(access);
|
||||
|
@ -52,7 +41,7 @@ angular.module('portainer.app').factory('AccessService', [
|
|||
};
|
||||
}
|
||||
|
||||
function getAccesses(authorizedUserPolicies, authorizedTeamPolicies, inheritedUserPolicies, inheritedTeamPolicies, roles) {
|
||||
function getAccesses(authorizedUserPolicies, authorizedTeamPolicies, inheritedUserPolicies, inheritedTeamPolicies) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
$q.all({
|
||||
|
@ -67,8 +56,8 @@ angular.module('portainer.app').factory('AccessService', [
|
|||
return new TeamAccessViewModel(team);
|
||||
});
|
||||
|
||||
var userAccessData = _mapAccessData(userAccesses, authorizedUserPolicies, inheritedUserPolicies, roles);
|
||||
var teamAccessData = _mapAccessData(teamAccesses, authorizedTeamPolicies, inheritedTeamPolicies, roles);
|
||||
var userAccessData = _mapAccessData(userAccesses, authorizedUserPolicies, inheritedUserPolicies);
|
||||
var teamAccessData = _mapAccessData(teamAccesses, authorizedTeamPolicies, inheritedTeamPolicies);
|
||||
|
||||
var accessData = {
|
||||
availableUsersAndTeams: userAccessData.available.concat(teamAccessData.available),
|
||||
|
@ -84,7 +73,7 @@ angular.module('portainer.app').factory('AccessService', [
|
|||
return deferred.promise;
|
||||
}
|
||||
|
||||
async function accessesAsync(entity, parent, roles) {
|
||||
async function accessesAsync(entity, parent) {
|
||||
try {
|
||||
if (!entity) {
|
||||
throw { msg: 'Unable to retrieve accesses' };
|
||||
|
@ -101,14 +90,14 @@ angular.module('portainer.app').factory('AccessService', [
|
|||
if (parent && !parent.TeamAccessPolicies) {
|
||||
parent.TeamAccessPolicies = {};
|
||||
}
|
||||
return await getAccesses(entity.UserAccessPolicies, entity.TeamAccessPolicies, parent ? parent.UserAccessPolicies : {}, parent ? parent.TeamAccessPolicies : {}, roles);
|
||||
return await getAccesses(entity.UserAccessPolicies, entity.TeamAccessPolicies, parent ? parent.UserAccessPolicies : {}, parent ? parent.TeamAccessPolicies : {});
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function accesses(entity, parent, roles) {
|
||||
return $async(accessesAsync, entity, parent, roles);
|
||||
function accesses(entity, parent) {
|
||||
return $async(accessesAsync, entity, parent);
|
||||
}
|
||||
|
||||
service.accesses = accesses;
|
||||
|
|
|
@ -1,102 +0,0 @@
|
|||
import _ from 'lodash-es';
|
||||
import { ExtensionViewModel } from '../../models/extension';
|
||||
|
||||
angular.module('portainer.app').factory('ExtensionService', [
|
||||
'$q',
|
||||
'Extension',
|
||||
'StateManager',
|
||||
'$async',
|
||||
'FileUploadService',
|
||||
function ExtensionServiceFactory($q, Extension, StateManager, $async, FileUploadService) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.EXTENSIONS = Object.freeze({
|
||||
REGISTRY_MANAGEMENT: 1,
|
||||
OAUTH_AUTHENTICATION: 2,
|
||||
RBAC: 3,
|
||||
});
|
||||
|
||||
service.enable = enable;
|
||||
service.update = update;
|
||||
service.delete = _delete;
|
||||
service.extensions = extensions;
|
||||
service.extension = extension;
|
||||
service.extensionEnabled = extensionEnabled;
|
||||
service.retrieveAndSaveEnabledExtensions = retrieveAndSaveEnabledExtensions;
|
||||
|
||||
function enable(license, extensionFile) {
|
||||
if (extensionFile) {
|
||||
return FileUploadService.uploadExtension(license, extensionFile);
|
||||
} else {
|
||||
return Extension.create({ license: license }).$promise;
|
||||
}
|
||||
}
|
||||
|
||||
function update(id, version) {
|
||||
return Extension.update({ id: id, version: version }).$promise;
|
||||
}
|
||||
|
||||
function _delete(id) {
|
||||
return Extension.delete({ id: id }).$promise;
|
||||
}
|
||||
|
||||
function extensions(store) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Extension.query({ store: store })
|
||||
.$promise.then(function success(data) {
|
||||
var extensions = data.map(function (item) {
|
||||
return new ExtensionViewModel(item);
|
||||
});
|
||||
deferred.resolve(extensions);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve extensions', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function extension(id) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Extension.get({ id: id })
|
||||
.$promise.then(function success(data) {
|
||||
var extension = new ExtensionViewModel(data);
|
||||
deferred.resolve(extension);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve extension details', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function extensionEnabled(extensionId) {
|
||||
return $async(extensionsEnabledAsync, extensionId);
|
||||
}
|
||||
|
||||
async function extensionsEnabledAsync(extensionId) {
|
||||
if (extensionId === service.EXTENSIONS.RBAC) {
|
||||
return StateManager.getExtension(extensionId) ? true : false;
|
||||
} else {
|
||||
const extensions = await service.extensions(false);
|
||||
const extension = _.find(extensions, (ext) => ext.Id === extensionId);
|
||||
return extension ? extension.Enabled : false;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
},
|
||||
]);
|
|
@ -197,19 +197,6 @@ angular.module('portainer.app').factory('FileUploadService', [
|
|||
return $q.all(queue);
|
||||
};
|
||||
|
||||
service.uploadExtension = function (license, extensionFile) {
|
||||
const payload = {
|
||||
License: license,
|
||||
file: extensionFile,
|
||||
ArchiveFileName: extensionFile.name,
|
||||
};
|
||||
return Upload.upload({
|
||||
url: 'api/extensions/upload',
|
||||
data: payload,
|
||||
ignoreLoadingBar: true,
|
||||
});
|
||||
};
|
||||
|
||||
return service;
|
||||
},
|
||||
]);
|
||||
|
|
|
@ -50,12 +50,6 @@ angular.module('portainer.app').factory('LocalStorage', [
|
|||
getUIState: function () {
|
||||
return localStorageService.get('UI_STATE');
|
||||
},
|
||||
storeExtensionState: function (state) {
|
||||
localStorageService.set('EXTENSION_STATE', state);
|
||||
},
|
||||
getExtensionState: function () {
|
||||
return localStorageService.get('EXTENSION_STATE');
|
||||
},
|
||||
storeJWT: function (jwt) {
|
||||
localStorageService.set('JWT', jwt);
|
||||
},
|
||||
|
@ -145,7 +139,7 @@ angular.module('portainer.app').factory('LocalStorage', [
|
|||
localStorageService.clearAll();
|
||||
},
|
||||
cleanAuthData() {
|
||||
localStorageService.remove('JWT', 'EXTENSION_STATE', 'APPLICATION_STATE', 'LOGIN_STATE_UUID');
|
||||
localStorageService.remove('JWT', 'APPLICATION_STATE', 'LOGIN_STATE_UUID');
|
||||
},
|
||||
cleanEndpointData() {
|
||||
localStorageService.remove('ENDPOINT_ID', 'ENDPOINT_PUBLIC_URL', 'ENDPOINT_OFFLINE_MODE', 'ENDPOINTS_DATA', 'ENDPOINT_STATE');
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import _ from 'lodash-es';
|
||||
import moment from 'moment';
|
||||
|
||||
angular.module('portainer.app').factory('StateManager', [
|
||||
|
@ -60,7 +59,6 @@ angular.module('portainer.app').factory('StateManager', [
|
|||
|
||||
manager.clean = function () {
|
||||
state.endpoint = {};
|
||||
state.extensions = [];
|
||||
};
|
||||
|
||||
manager.updateLogo = function (logoURL) {
|
||||
|
@ -174,11 +172,6 @@ angular.module('portainer.app').factory('StateManager', [
|
|||
state.UI = UIState;
|
||||
}
|
||||
|
||||
const extensionState = LocalStorage.getExtensionState();
|
||||
if (extensionState) {
|
||||
state.extensions = extensionState;
|
||||
}
|
||||
|
||||
var endpointState = LocalStorage.getEndpointState();
|
||||
if (endpointState) {
|
||||
state.endpoint = endpointState;
|
||||
|
@ -276,19 +269,6 @@ angular.module('portainer.app').factory('StateManager', [
|
|||
return state.endpoint.agentApiVersion;
|
||||
};
|
||||
|
||||
manager.saveExtensions = function (extensions) {
|
||||
state.extensions = extensions;
|
||||
LocalStorage.storeExtensionState(state.extensions);
|
||||
};
|
||||
|
||||
manager.getExtensions = function () {
|
||||
return state.extensions;
|
||||
};
|
||||
|
||||
manager.getExtension = function (extensionId) {
|
||||
return _.find(state.extensions, { Id: extensionId, Enabled: true });
|
||||
};
|
||||
|
||||
return manager;
|
||||
},
|
||||
]);
|
||||
|
|
|
@ -12,7 +12,6 @@ class AuthenticationController {
|
|||
Authentication,
|
||||
UserService,
|
||||
EndpointService,
|
||||
ExtensionService,
|
||||
StateManager,
|
||||
Notifications,
|
||||
SettingsService,
|
||||
|
@ -28,7 +27,6 @@ class AuthenticationController {
|
|||
this.Authentication = Authentication;
|
||||
this.UserService = UserService;
|
||||
this.EndpointService = EndpointService;
|
||||
this.ExtensionService = ExtensionService;
|
||||
this.StateManager = StateManager;
|
||||
this.Notifications = Notifications;
|
||||
this.SettingsService = SettingsService;
|
||||
|
@ -47,7 +45,6 @@ class AuthenticationController {
|
|||
OAuthProvider: '',
|
||||
};
|
||||
|
||||
this.retrieveAndSaveEnabledExtensionsAsync = this.retrieveAndSaveEnabledExtensionsAsync.bind(this);
|
||||
this.checkForEndpointsAsync = this.checkForEndpointsAsync.bind(this);
|
||||
this.checkForLatestVersionAsync = this.checkForLatestVersionAsync.bind(this);
|
||||
this.postLoginSteps = this.postLoginSteps.bind(this);
|
||||
|
@ -117,14 +114,6 @@ class AuthenticationController {
|
|||
* POST LOGIN STEPS SECTION
|
||||
*/
|
||||
|
||||
async retrieveAndSaveEnabledExtensionsAsync() {
|
||||
try {
|
||||
await this.ExtensionService.retrieveAndSaveEnabledExtensions();
|
||||
} catch (err) {
|
||||
this.error(err, 'Unable to retrieve enabled extensions');
|
||||
}
|
||||
}
|
||||
|
||||
async checkForEndpointsAsync() {
|
||||
try {
|
||||
const endpoints = await this.EndpointService.endpoints(0, 1);
|
||||
|
@ -158,7 +147,6 @@ class AuthenticationController {
|
|||
}
|
||||
|
||||
async postLoginSteps() {
|
||||
await this.retrieveAndSaveEnabledExtensionsAsync();
|
||||
await this.checkForEndpointsAsync();
|
||||
await this.checkForLatestVersionAsync();
|
||||
}
|
||||
|
|
|
@ -1,129 +0,0 @@
|
|||
<rd-header>
|
||||
<rd-header-title title-text="Extensions"></rd-header-title>
|
||||
<rd-header-content>Portainer extensions</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<information-panel title-text="Information">
|
||||
<span class="text-muted" style="font-size: 90%;">
|
||||
<p>
|
||||
Portainer CE is a great way of managing clusters, provisioning containers and services and managing container environment lifecycles. To extend the benefit of Portainer CE
|
||||
even more, and to address the needs of larger, complex or critical environments, the Portainer team provides a growing range of low-cost Extensions.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To ensure that Portainer remains the best choice for managing production container platforms, the Portainer team have chosen a modular, extensible design approach, where
|
||||
additional capability can be added to the Portainer CE core as needed, and at very low cost.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Available through a simple subscription process from the list below, Portainer Extensions provide a simple way to enhance Portainer CE’s core functionality through
|
||||
incremental capability in important areas.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
For additional information on Portainer Extensions, see our website
|
||||
<a href="https://www.portainer.io/products-services/portainer-extension-software/" target="_blank">here</a>.
|
||||
</p>
|
||||
</span>
|
||||
</information-panel>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal" name="extensionEnableForm">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Enable extension
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<p class="small text-muted" ng-if="!state.offlineActivation">
|
||||
Portainer will download the latest version of the extension. Ensure that you have a valid license.
|
||||
</p>
|
||||
<p class="small text-muted" ng-if="state.offlineActivation">
|
||||
You will need to upload the extension archive manually. Ensure that you have a valid license.
|
||||
</p>
|
||||
<p class="small text-muted" ng-if="state.offlineActivation">
|
||||
You can download the latest version of our extensions <a target="_blank" href="https://downloads.portainer.io/extensions.zip">here</a>.
|
||||
</p>
|
||||
<p>
|
||||
<a class="small interactive" ng-if="!state.offlineActivation" ng-click="state.offlineActivation = true;">
|
||||
<i class="fa fa-toggle-off space-right" aria-hidden="true"></i> Switch to offline activation
|
||||
</a>
|
||||
<a class="small interactive" ng-if="state.offlineActivation" ng-click="state.offlineActivation = false;">
|
||||
<i class="fa fa-wifi space-right" aria-hidden="true"></i> Switch to online activation
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="extension_license" class="col-sm-2 control-label text-left">License</label>
|
||||
<div class="col-sm-10">
|
||||
<input
|
||||
type="text"
|
||||
name="extension_license"
|
||||
class="form-control"
|
||||
ng-model="formValues.License"
|
||||
ng-change="isValidLicenseFormat(extensionEnableForm)"
|
||||
required
|
||||
placeholder="Enter a license key here"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="extensionEnableForm.extension_license.$invalid">
|
||||
<div class="col-sm-12 small text-warning">
|
||||
<div ng-messages="extensionEnableForm.extension_license.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||
<p ng-message="invalidLicense"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Invalid license format.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="state.offlineActivation">
|
||||
<div class="col-sm-12">
|
||||
<button type="button" class="btn btn-primary btn-sm" ngf-select ng-model="formValues.ExtensionFile" style="margin-left: 0px;">Select file</button>
|
||||
<span style="margin-left: 5px;">
|
||||
{{ formValues.ExtensionFile.name }}
|
||||
<i class="fa fa-times red-icon" ng-if="!formValues.ExtensionFile" aria-hidden="true"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm"
|
||||
ng-click="enableExtension()"
|
||||
ng-disabled="state.actionInProgress || !extensionEnableForm.$valid || (state.offlineActivation && !formValues.ExtensionFile)"
|
||||
button-spinner="state.actionInProgress"
|
||||
style="margin-left: 0px;"
|
||||
>
|
||||
<span ng-hide="state.actionInProgress">Enable extension</span>
|
||||
<span ng-show="state.actionInProgress">Enabling extension...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-if="extensions && extensions.length > 0">
|
||||
<div class="col-sm-12">
|
||||
<extension-list current-date="state.currentDate" extensions="extensions"></extension-list>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<information-panel title-text="Error" ng-if="extensions && extensions.length === 0">
|
||||
<span class="small text-muted">
|
||||
<p>
|
||||
<i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true"></i>
|
||||
Portainer must be connected to the Internet to fetch the list of available extensions.
|
||||
</p>
|
||||
</span>
|
||||
</information-panel>
|
|
@ -1,67 +0,0 @@
|
|||
import moment from 'moment';
|
||||
|
||||
angular.module('portainer.app').controller('ExtensionsController', [
|
||||
'$scope',
|
||||
'$state',
|
||||
'ExtensionService',
|
||||
'Notifications',
|
||||
function ($scope, $state, ExtensionService, Notifications) {
|
||||
$scope.state = {
|
||||
actionInProgress: false,
|
||||
currentDate: moment().format('YYYY-MM-dd'),
|
||||
};
|
||||
|
||||
$scope.formValues = {
|
||||
License: '',
|
||||
ExtensionFile: null,
|
||||
};
|
||||
|
||||
function initView() {
|
||||
ExtensionService.extensions(true)
|
||||
.then(function onSuccess(data) {
|
||||
$scope.extensions = data;
|
||||
})
|
||||
.catch(function onError(err) {
|
||||
$scope.extensions = [];
|
||||
Notifications.error('Failure', err, 'Unable to access extension store');
|
||||
});
|
||||
}
|
||||
|
||||
$scope.enableExtension = function () {
|
||||
const license = $scope.formValues.License;
|
||||
const extensionFile = $scope.formValues.ExtensionFile;
|
||||
|
||||
$scope.state.actionInProgress = true;
|
||||
ExtensionService.enable(license, extensionFile)
|
||||
.then(function onSuccess() {
|
||||
return ExtensionService.retrieveAndSaveEnabledExtensions();
|
||||
})
|
||||
.then(function () {
|
||||
Notifications.success('Extension successfully enabled');
|
||||
$state.reload();
|
||||
})
|
||||
.catch(function onError(err) {
|
||||
Notifications.error('Failure', err, 'Unable to enable extension');
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.actionInProgress = false;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.isValidLicenseFormat = function (form) {
|
||||
var valid = true;
|
||||
|
||||
if (!$scope.formValues.License) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNaN($scope.formValues.License[0])) {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
form.extension_license.$setValidity('invalidLicense', valid);
|
||||
};
|
||||
|
||||
initView();
|
||||
},
|
||||
]);
|
|
@ -1,196 +0,0 @@
|
|||
<rd-header>
|
||||
<rd-header-title title-text="Extension details"></rd-header-title>
|
||||
<rd-header-content> <a ui-sref="portainer.extensions">Portainer extensions</a> > {{ extension.Name }} </rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row" ng-if="extension">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<div style="display: flex;">
|
||||
<div style="flex-grow: 4; display: flex; flex-direction: column; justify-content: space-between;">
|
||||
<div class="form-group">
|
||||
<div class="text-muted" style="font-size: 150%;"> {{ extension.Name }} extension </div>
|
||||
|
||||
<div class="small text-muted" style="margin-top: 5px;"> By <a href="https://portainer.io" href="_blank">Portainer.io</a> </div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="text-muted">
|
||||
{{ extension.ShortDescription }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="flex-grow: 1; border-left: 1px solid #777;">
|
||||
<div class="form-group" style="margin-left: 40px;">
|
||||
<div style="font-size: 125%; border-bottom: 2px solid #2d3e63; padding-bottom: 5px;">
|
||||
<span ng-if="extension.Enabled">Enabled</span>
|
||||
<span ng-if="!extension.Enabled && extension.License.Expiration && !extension.License.Valid">Expired</span>
|
||||
<span ng-if="!extension.Enabled && !extension.License.Expiration">{{ extension.Price }}</span>
|
||||
</div>
|
||||
|
||||
<div class="small text-muted col-sm-12" style="margin: 15px 0 15px 0;" ng-if="!extension.Enabled">
|
||||
{{ extension.PriceDescription }}
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 10px; margin-bottom: 95px;" ng-if="!extension.Enabled && extension.Available">
|
||||
<label for="instances_qty" class="col-sm-7 control-label text-left" style="margin-top: 7px;">Instances</label>
|
||||
<div class="col-sm-5">
|
||||
<input type="number" class="form-control" ng-model="formValues.instances" id="instances_qty" placeholder="1" min="1" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 15px;" ng-if="!extension.Enabled && extension.Available">
|
||||
<a href="{{ extension.ShopURL }}&quantity={{ formValues.instances }}" target="_blank" class="btn btn-primary btn-sm" style="width: 100%; margin-left: 0;">
|
||||
Buy
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 10px;" ng-if="!extension.Enabled && extension.Available">
|
||||
<a ui-sref="portainer.extensions" class="btn btn-primary btn-sm" style="width: 100%; margin-left: 0;">
|
||||
Add license key
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 15px;" ng-if="!extension.Enabled && !extension.Available">
|
||||
<btn class="btn btn-primary btn-sm" style="width: 100%; margin-left: 0;" disabled>Coming soon</btn>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 15px;" ng-if="extension.Enabled && extension.UpdateAvailable && !state.offlineUpdate">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm"
|
||||
ng-click="updateExtensionOnline(extension)"
|
||||
ng-disabled="state.onlineUpdateInProgress"
|
||||
button-spinner="state.onlineUpdateInProgress"
|
||||
style="width: 100%; margin-left: 0;"
|
||||
>
|
||||
<span ng-hide="state.onlineUpdateInProgress">Update via Internet</span>
|
||||
<span ng-show="state.onlineUpdateInProgress">Updating extension...</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 5px;" ng-if="extension.Enabled">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-danger btn-sm"
|
||||
ng-click="deleteExtension(extension)"
|
||||
ng-disabled="state.deleteInProgress"
|
||||
button-spinner="state.deleteInProgress"
|
||||
style="width: 100%; margin-left: 0;"
|
||||
>
|
||||
<span ng-hide="state.deleteInProgress">Delete</span>
|
||||
<span ng-show="state.deleteInProgress">Deleting extension...</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 15px;" ng-if="extension.Enabled && extension.UpdateAvailable">
|
||||
<p>
|
||||
<a class="small interactive" ng-if="!state.offlineUpdate" ng-click="state.offlineUpdate = true;">
|
||||
<i class="fa fa-toggle-off space-right" aria-hidden="true"></i> Switch to offline update
|
||||
</a>
|
||||
<a class="small interactive" ng-if="state.offlineUpdate" ng-click="state.offlineUpdate = false;">
|
||||
<i class="fa fa-wifi space-right" aria-hidden="true"></i> Switch to online update
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-if="extension && state.offlineUpdate">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
<span>
|
||||
Offline update
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<p class="small text-muted" ng-if="state.offlineUpdate">
|
||||
You will need to upload the extension archive manually. You can download the latest version of our extensions
|
||||
<a target="_blank" href="https://download.portainer.io/extensions.zip">here</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="state.offlineUpdate">
|
||||
<div class="col-sm-12">
|
||||
<button type="button" class="btn btn-primary btn-sm" ngf-select ng-model="formValues.ExtensionFile" style="margin-left: 0px;">Select file</button>
|
||||
<span style="margin-left: 5px;">
|
||||
{{ formValues.ExtensionFile.name }}
|
||||
<i class="fa fa-times red-icon" ng-if="!formValues.ExtensionFile" aria-hidden="true"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm"
|
||||
ng-click="updateExtensionOffline(extension)"
|
||||
ng-disabled="state.offlineUpdateInProgress || !formValues.ExtensionFile"
|
||||
button-spinner="state.offlineUpdateInProgress"
|
||||
style="margin-left: 0px;"
|
||||
>
|
||||
<span ng-hide="state.offlineUpdateInProgress">Update extension</span>
|
||||
<span ng-show="state.offlineUpdateInProgress">Updating extension...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-if="extension">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<div class="col-sm-12 form-section-title">
|
||||
<span>
|
||||
Description
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group" ng-if="extension.Description">
|
||||
<div class="text-muted" style="font-size: 90%;" ng-bind-html="extension.Description"></div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="!extension.Description">
|
||||
<div class="small text-muted">
|
||||
<p>
|
||||
<i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true"></i>
|
||||
Unable to provide a description in an offline environment.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-if="extension.Description && extension.Images">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<div class="col-sm-12 form-section-title">
|
||||
<span>
|
||||
Screenshots
|
||||
</span>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<div ng-repeat="image in extension.Images" style="margin-top: 25px; cursor: zoom-in;">
|
||||
<img ng-src="{{ image }}" ng-click="enlargeImage(image)" style="max-width: 1024px;" />
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
|
@ -1,90 +0,0 @@
|
|||
angular.module('portainer.app').controller('ExtensionController', [
|
||||
'$q',
|
||||
'$scope',
|
||||
'$transition$',
|
||||
'$state',
|
||||
'ExtensionService',
|
||||
'Notifications',
|
||||
'ModalService',
|
||||
function ($q, $scope, $transition$, $state, ExtensionService, Notifications, ModalService) {
|
||||
$scope.state = {
|
||||
onlineUpdateInProgress: false,
|
||||
offlineUpdateInProgress: false,
|
||||
deleteInProgress: false,
|
||||
offlineUpdate: false,
|
||||
};
|
||||
|
||||
$scope.formValues = {
|
||||
instances: 1,
|
||||
extensionFile: null,
|
||||
};
|
||||
|
||||
$scope.updateExtensionOnline = updateExtensionOnline;
|
||||
$scope.updateExtensionOffline = updateExtensionOffline;
|
||||
$scope.deleteExtension = deleteExtension;
|
||||
$scope.enlargeImage = enlargeImage;
|
||||
|
||||
function enlargeImage(image) {
|
||||
ModalService.enlargeImage(image);
|
||||
}
|
||||
|
||||
function deleteExtension(extension) {
|
||||
$scope.state.deleteInProgress = true;
|
||||
ExtensionService.delete(extension.Id)
|
||||
.then(function onSuccess() {
|
||||
Notifications.success('Extension successfully deleted');
|
||||
$state.go('portainer.extensions');
|
||||
})
|
||||
.catch(function onError(err) {
|
||||
Notifications.error('Failure', err, 'Unable to delete extension');
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.deleteInProgress = false;
|
||||
});
|
||||
}
|
||||
|
||||
function updateExtensionOnline(extension) {
|
||||
$scope.state.onlineUpdateInProgress = true;
|
||||
ExtensionService.update(extension.Id, extension.Version)
|
||||
.then(function onSuccess() {
|
||||
Notifications.success('Extension successfully updated');
|
||||
$state.reload();
|
||||
})
|
||||
.catch(function onError(err) {
|
||||
Notifications.error('Failure', err, 'Unable to update extension');
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.onlineUpdateInProgress = false;
|
||||
});
|
||||
}
|
||||
|
||||
function updateExtensionOffline(extension) {
|
||||
$scope.state.offlineUpdateInProgress = true;
|
||||
const extensionFile = $scope.formValues.ExtensionFile;
|
||||
|
||||
ExtensionService.enable(extension.License.LicenseKey, extensionFile)
|
||||
.then(function onSuccess() {
|
||||
Notifications.success('Extension successfully updated');
|
||||
$state.reload();
|
||||
})
|
||||
.catch(function onError(err) {
|
||||
Notifications.error('Failure', err, 'Unable to update extension');
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.offlineUpdateInProgress = false;
|
||||
});
|
||||
}
|
||||
|
||||
function initView() {
|
||||
ExtensionService.extension($transition$.params().id)
|
||||
.then(function onSuccess(extension) {
|
||||
$scope.extension = extension;
|
||||
})
|
||||
.catch(function onError(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve extension information');
|
||||
});
|
||||
}
|
||||
|
||||
initView();
|
||||
},
|
||||
]);
|
|
@ -8,8 +8,7 @@ angular.module('portainer.app').controller('InitAdminController', [
|
|||
'SettingsService',
|
||||
'UserService',
|
||||
'EndpointService',
|
||||
'ExtensionService',
|
||||
function ($async, $scope, $state, Notifications, Authentication, StateManager, SettingsService, UserService, EndpointService, ExtensionService) {
|
||||
function ($async, $scope, $state, Notifications, Authentication, StateManager, SettingsService, UserService, EndpointService) {
|
||||
$scope.logo = StateManager.getState().application.logo;
|
||||
|
||||
$scope.formValues = {
|
||||
|
@ -23,18 +22,6 @@ angular.module('portainer.app').controller('InitAdminController', [
|
|||
actionInProgress: false,
|
||||
};
|
||||
|
||||
function retrieveAndSaveEnabledExtensions() {
|
||||
return $async(retrieveAndSaveEnabledExtensionsAsync);
|
||||
}
|
||||
|
||||
async function retrieveAndSaveEnabledExtensionsAsync() {
|
||||
try {
|
||||
await ExtensionService.retrieveAndSaveEnabledExtensions();
|
||||
} catch (err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve enabled extensions');
|
||||
}
|
||||
}
|
||||
|
||||
$scope.createAdminUser = function () {
|
||||
var username = $scope.formValues.Username;
|
||||
var password = $scope.formValues.Password;
|
||||
|
@ -44,9 +31,6 @@ angular.module('portainer.app').controller('InitAdminController', [
|
|||
.then(function success() {
|
||||
return Authentication.login(username, password);
|
||||
})
|
||||
.then(function success() {
|
||||
return retrieveAndSaveEnabledExtensions();
|
||||
})
|
||||
.then(function success() {
|
||||
StateManager.updateEnableTelemetry($scope.formValues.enableTelemetry);
|
||||
return SettingsService.update({ enableTelemetry: $scope.formValues.enableTelemetry });
|
||||
|
|
|
@ -101,10 +101,7 @@
|
|||
<div class="col-sm-12">
|
||||
<label for="toggle_allowvolumebrowser" class="control-label text-left">
|
||||
Enable volume management for non-administrators
|
||||
<portainer-tooltip
|
||||
position="bottom"
|
||||
message="When enabled, non-admin users & users with helpdesk, standard and read-only roles from the RBAC extension will be able to use Portainer volume management features."
|
||||
></portainer-tooltip>
|
||||
<portainer-tooltip position="bottom" message="When enabled, regular users will be able to use Portainer volume management features."></portainer-tooltip>
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" name="toggle_allowvolumebrowser" ng-model="formValues.enableVolumeBrowser" /><i></i> </label>
|
||||
</div>
|
||||
|
|
|
@ -111,9 +111,6 @@
|
|||
<li class="sidebar-title">
|
||||
<span>Settings</span>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="isAdmin">
|
||||
<a ui-sref="portainer.extensions" ui-sref-active="active">Extensions <span class="menu-icon fa fa-bolt fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="isAdmin || isTeamLeader">
|
||||
<a ui-sref="portainer.users" ui-sref-active="active">Users <span class="menu-icon fa fa-users fa-fw"></span></a>
|
||||
<div
|
||||
|
@ -123,29 +120,11 @@
|
|||
($state.current.name === 'portainer.users' ||
|
||||
$state.current.name === 'portainer.users.user' ||
|
||||
$state.current.name === 'portainer.teams' ||
|
||||
$state.current.name === 'portainer.teams.team' ||
|
||||
$state.current.name === 'portainer.roles' ||
|
||||
$state.current.name === 'portainer.roles.role' ||
|
||||
$state.current.name === 'portainer.roles.new')
|
||||
$state.current.name === 'portainer.teams.team')
|
||||
"
|
||||
>
|
||||
<a ui-sref="portainer.teams" ui-sref-active="active">Teams</a>
|
||||
</div>
|
||||
<div
|
||||
class="sidebar-sublist"
|
||||
ng-if="
|
||||
toggle &&
|
||||
($state.current.name === 'portainer.users' ||
|
||||
$state.current.name === 'portainer.users.user' ||
|
||||
$state.current.name === 'portainer.teams' ||
|
||||
$state.current.name === 'portainer.teams.team' ||
|
||||
$state.current.name === 'portainer.roles' ||
|
||||
$state.current.name === 'portainer.roles.role' ||
|
||||
$state.current.name === 'portainer.roles.new')
|
||||
"
|
||||
>
|
||||
<a ui-sref="portainer.roles" ui-sref-active="active">Roles</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="isAdmin">
|
||||
<a ui-sref="portainer.endpoints" ui-sref-active="active">Endpoints <span class="menu-icon fa fa-plug fa-fw"></span></a>
|
||||
|
|
|
@ -7,8 +7,7 @@ angular.module('portainer.app').controller('SidebarController', [
|
|||
'Authentication',
|
||||
'UserService',
|
||||
'EndpointProvider',
|
||||
'ExtensionService',
|
||||
function ($q, $scope, $transitions, StateManager, Notifications, Authentication, UserService, EndpointProvider, ExtensionService) {
|
||||
function ($q, $scope, $transitions, StateManager, Notifications, Authentication, UserService, EndpointProvider) {
|
||||
function checkPermissions(memberships) {
|
||||
var isLeader = false;
|
||||
angular.forEach(memberships, function (membership) {
|
||||
|
@ -48,15 +47,7 @@ angular.module('portainer.app').controller('SidebarController', [
|
|||
const isAdmin = Authentication.isAdmin();
|
||||
const { allowStackManagementForRegularUsers } = $scope.applicationState.application;
|
||||
|
||||
if (isAdmin || allowStackManagementForRegularUsers) {
|
||||
return true;
|
||||
}
|
||||
const rbacEnabled = await ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC);
|
||||
if (rbacEnabled) {
|
||||
return Authentication.hasAuthorizations(['EndpointResourcesAccess']);
|
||||
}
|
||||
|
||||
return false;
|
||||
return isAdmin || allowStackManagementForRegularUsers;
|
||||
}
|
||||
|
||||
$transitions.onEnter({}, async () => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
angular.module('portainer.app').controller('StacksController', StacksController);
|
||||
|
||||
/* @ngInject */
|
||||
function StacksController($scope, $state, Notifications, StackService, ModalService, EndpointProvider, Authentication, StateManager, ExtensionService) {
|
||||
function StacksController($scope, $state, Notifications, StackService, ModalService, EndpointProvider, Authentication, StateManager) {
|
||||
$scope.removeAction = function (selectedItems) {
|
||||
ModalService.confirmDeletion('Do you want to remove the selected stack(s)? Associated services will be removed as well.', function onConfirm(confirmed) {
|
||||
if (!confirmed) {
|
||||
|
@ -56,24 +56,7 @@ function StacksController($scope, $state, Notifications, StackService, ModalServ
|
|||
|
||||
async function loadCreateEnabled() {
|
||||
const appState = StateManager.getState().application;
|
||||
if (appState.allowStackManagementForRegularUsers) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let isAdmin = true;
|
||||
if (appState.authentication) {
|
||||
isAdmin = Authentication.isAdmin();
|
||||
}
|
||||
if (isAdmin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const RBACExtensionEnabled = await ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC);
|
||||
if (!RBACExtensionEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Authentication.hasAuthorizations(['EndpointResourcesAccess']);
|
||||
return appState.allowStackManagementForRegularUsers || Authentication.isAdmin();
|
||||
}
|
||||
|
||||
async function initView() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue