mirror of https://github.com/portainer/portainer
feat(settings): introduce disable stack management setting (#4100)
* feat(stacks): add a setting to disable the creation of stacks for non-admin users * feat(settings): introduce a setting to prevent non-admin from stack creation * feat(settings): update stack creation setting * feat(settings): fail stack creation if user is non admin * fix(settings): save preventStackCreation setting to state * feat(stacks): disable add button when settings is enabled * format(stacks): remove line * feat(stacks): setting to hide stacks from users * feat(settings): rename disable stacks setting * refactor(settings): rename setting to disableStackManagementForRegularUsers * feat(settings): hide stacks for non admin when settings is set * refactor(settings): replace disableDeviceMapping with allow * feat(dashboard): hide stacks if settings disabled and non admin * refactor(sidebar): check if user is endpoint admin * feat(settings): set the default value for stack management * feat(settings): rename field label * fix(sidebar): refresh show stacks state * fix(docker): hide stacks when not adminpull/4115/head
parent
07efd4bdda
commit
fa9eeaf3b1
|
@ -24,16 +24,17 @@ func (store *Store) Init() error {
|
||||||
portainer.LDAPGroupSearchSettings{},
|
portainer.LDAPGroupSearchSettings{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
OAuthSettings: portainer.OAuthSettings{},
|
OAuthSettings: portainer.OAuthSettings{},
|
||||||
AllowBindMountsForRegularUsers: true,
|
AllowBindMountsForRegularUsers: true,
|
||||||
AllowPrivilegedModeForRegularUsers: true,
|
AllowPrivilegedModeForRegularUsers: true,
|
||||||
AllowVolumeBrowserForRegularUsers: false,
|
AllowVolumeBrowserForRegularUsers: false,
|
||||||
AllowHostNamespaceForRegularUsers: true,
|
AllowHostNamespaceForRegularUsers: true,
|
||||||
AllowDeviceMappingForRegularUsers: true,
|
AllowDeviceMappingForRegularUsers: true,
|
||||||
EnableHostManagementFeatures: false,
|
AllowStackManagementForRegularUsers: true,
|
||||||
EdgeAgentCheckinInterval: portainer.DefaultEdgeAgentCheckinIntervalInSeconds,
|
EnableHostManagementFeatures: false,
|
||||||
TemplatesURL: portainer.DefaultTemplatesURL,
|
EdgeAgentCheckinInterval: portainer.DefaultEdgeAgentCheckinIntervalInSeconds,
|
||||||
UserSessionTimeout: portainer.DefaultUserSessionTimeout,
|
TemplatesURL: portainer.DefaultTemplatesURL,
|
||||||
|
UserSessionTimeout: portainer.DefaultUserSessionTimeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = store.SettingsService.UpdateSettings(defaultSettings)
|
err = store.SettingsService.UpdateSettings(defaultSettings)
|
||||||
|
|
|
@ -8,6 +8,7 @@ func (m *Migrator) updateSettingsToDB24() error {
|
||||||
|
|
||||||
legacySettings.AllowHostNamespaceForRegularUsers = true
|
legacySettings.AllowHostNamespaceForRegularUsers = true
|
||||||
legacySettings.AllowDeviceMappingForRegularUsers = true
|
legacySettings.AllowDeviceMappingForRegularUsers = true
|
||||||
|
legacySettings.AllowStackManagementForRegularUsers = true
|
||||||
|
|
||||||
return m.settingsService.UpdateSettings(legacySettings)
|
return m.settingsService.UpdateSettings(legacySettings)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,20 +6,21 @@ import (
|
||||||
|
|
||||||
httperror "github.com/portainer/libhttp/error"
|
httperror "github.com/portainer/libhttp/error"
|
||||||
"github.com/portainer/libhttp/response"
|
"github.com/portainer/libhttp/response"
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
type publicSettingsResponse struct {
|
type publicSettingsResponse struct {
|
||||||
LogoURL string `json:"LogoURL"`
|
LogoURL string `json:"LogoURL"`
|
||||||
AuthenticationMethod portainer.AuthenticationMethod `json:"AuthenticationMethod"`
|
AuthenticationMethod portainer.AuthenticationMethod `json:"AuthenticationMethod"`
|
||||||
AllowBindMountsForRegularUsers bool `json:"AllowBindMountsForRegularUsers"`
|
AllowBindMountsForRegularUsers bool `json:"AllowBindMountsForRegularUsers"`
|
||||||
AllowPrivilegedModeForRegularUsers bool `json:"AllowPrivilegedModeForRegularUsers"`
|
AllowPrivilegedModeForRegularUsers bool `json:"AllowPrivilegedModeForRegularUsers"`
|
||||||
AllowVolumeBrowserForRegularUsers bool `json:"AllowVolumeBrowserForRegularUsers"`
|
AllowVolumeBrowserForRegularUsers bool `json:"AllowVolumeBrowserForRegularUsers"`
|
||||||
AllowHostNamespaceForRegularUsers bool `json:"AllowHostNamespaceForRegularUsers"`
|
AllowHostNamespaceForRegularUsers bool `json:"AllowHostNamespaceForRegularUsers"`
|
||||||
AllowDeviceMappingForRegularUsers bool `json:"AllowDeviceMappingForRegularUsers"`
|
AllowDeviceMappingForRegularUsers bool `json:"AllowDeviceMappingForRegularUsers"`
|
||||||
EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"`
|
AllowStackManagementForRegularUsers bool `json:"AllowStackManagementForRegularUsers"`
|
||||||
EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures"`
|
EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"`
|
||||||
OAuthLoginURI string `json:"OAuthLoginURI"`
|
EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures"`
|
||||||
|
OAuthLoginURI string `json:"OAuthLoginURI"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET request on /api/settings/public
|
// GET request on /api/settings/public
|
||||||
|
@ -30,15 +31,16 @@ func (handler *Handler) settingsPublic(w http.ResponseWriter, r *http.Request) *
|
||||||
}
|
}
|
||||||
|
|
||||||
publicSettings := &publicSettingsResponse{
|
publicSettings := &publicSettingsResponse{
|
||||||
LogoURL: settings.LogoURL,
|
LogoURL: settings.LogoURL,
|
||||||
AuthenticationMethod: settings.AuthenticationMethod,
|
AuthenticationMethod: settings.AuthenticationMethod,
|
||||||
AllowBindMountsForRegularUsers: settings.AllowBindMountsForRegularUsers,
|
AllowBindMountsForRegularUsers: settings.AllowBindMountsForRegularUsers,
|
||||||
AllowPrivilegedModeForRegularUsers: settings.AllowPrivilegedModeForRegularUsers,
|
AllowPrivilegedModeForRegularUsers: settings.AllowPrivilegedModeForRegularUsers,
|
||||||
AllowVolumeBrowserForRegularUsers: settings.AllowVolumeBrowserForRegularUsers,
|
AllowVolumeBrowserForRegularUsers: settings.AllowVolumeBrowserForRegularUsers,
|
||||||
AllowHostNamespaceForRegularUsers: settings.AllowHostNamespaceForRegularUsers,
|
AllowHostNamespaceForRegularUsers: settings.AllowHostNamespaceForRegularUsers,
|
||||||
AllowDeviceMappingForRegularUsers: settings.AllowDeviceMappingForRegularUsers,
|
AllowDeviceMappingForRegularUsers: settings.AllowDeviceMappingForRegularUsers,
|
||||||
EnableHostManagementFeatures: settings.EnableHostManagementFeatures,
|
AllowStackManagementForRegularUsers: settings.AllowStackManagementForRegularUsers,
|
||||||
EnableEdgeComputeFeatures: settings.EnableEdgeComputeFeatures,
|
EnableHostManagementFeatures: settings.EnableHostManagementFeatures,
|
||||||
|
EnableEdgeComputeFeatures: settings.EnableEdgeComputeFeatures,
|
||||||
OAuthLoginURI: fmt.Sprintf("%s?response_type=code&client_id=%s&redirect_uri=%s&scope=%s&prompt=login",
|
OAuthLoginURI: fmt.Sprintf("%s?response_type=code&client_id=%s&redirect_uri=%s&scope=%s&prompt=login",
|
||||||
settings.OAuthSettings.AuthorizationURI,
|
settings.OAuthSettings.AuthorizationURI,
|
||||||
settings.OAuthSettings.ClientID,
|
settings.OAuthSettings.ClientID,
|
||||||
|
|
|
@ -9,28 +9,29 @@ import (
|
||||||
httperror "github.com/portainer/libhttp/error"
|
httperror "github.com/portainer/libhttp/error"
|
||||||
"github.com/portainer/libhttp/request"
|
"github.com/portainer/libhttp/request"
|
||||||
"github.com/portainer/libhttp/response"
|
"github.com/portainer/libhttp/response"
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
bolterrors "github.com/portainer/portainer/api/bolt/errors"
|
bolterrors "github.com/portainer/portainer/api/bolt/errors"
|
||||||
"github.com/portainer/portainer/api/filesystem"
|
"github.com/portainer/portainer/api/filesystem"
|
||||||
)
|
)
|
||||||
|
|
||||||
type settingsUpdatePayload struct {
|
type settingsUpdatePayload struct {
|
||||||
LogoURL *string
|
LogoURL *string
|
||||||
BlackListedLabels []portainer.Pair
|
BlackListedLabels []portainer.Pair
|
||||||
AuthenticationMethod *int
|
AuthenticationMethod *int
|
||||||
LDAPSettings *portainer.LDAPSettings
|
LDAPSettings *portainer.LDAPSettings
|
||||||
OAuthSettings *portainer.OAuthSettings
|
OAuthSettings *portainer.OAuthSettings
|
||||||
AllowBindMountsForRegularUsers *bool
|
AllowBindMountsForRegularUsers *bool
|
||||||
AllowPrivilegedModeForRegularUsers *bool
|
AllowPrivilegedModeForRegularUsers *bool
|
||||||
AllowHostNamespaceForRegularUsers *bool
|
AllowHostNamespaceForRegularUsers *bool
|
||||||
AllowVolumeBrowserForRegularUsers *bool
|
AllowVolumeBrowserForRegularUsers *bool
|
||||||
AllowDeviceMappingForRegularUsers *bool
|
AllowDeviceMappingForRegularUsers *bool
|
||||||
EnableHostManagementFeatures *bool
|
AllowStackManagementForRegularUsers *bool
|
||||||
SnapshotInterval *string
|
EnableHostManagementFeatures *bool
|
||||||
TemplatesURL *string
|
SnapshotInterval *string
|
||||||
EdgeAgentCheckinInterval *int
|
TemplatesURL *string
|
||||||
EnableEdgeComputeFeatures *bool
|
EdgeAgentCheckinInterval *int
|
||||||
UserSessionTimeout *string
|
EnableEdgeComputeFeatures *bool
|
||||||
|
UserSessionTimeout *string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (payload *settingsUpdatePayload) Validate(r *http.Request) error {
|
func (payload *settingsUpdatePayload) Validate(r *http.Request) error {
|
||||||
|
@ -131,6 +132,10 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) *
|
||||||
settings.AllowHostNamespaceForRegularUsers = *payload.AllowHostNamespaceForRegularUsers
|
settings.AllowHostNamespaceForRegularUsers = *payload.AllowHostNamespaceForRegularUsers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if payload.AllowStackManagementForRegularUsers != nil {
|
||||||
|
settings.AllowStackManagementForRegularUsers = *payload.AllowStackManagementForRegularUsers
|
||||||
|
}
|
||||||
|
|
||||||
if payload.SnapshotInterval != nil && *payload.SnapshotInterval != settings.SnapshotInterval {
|
if payload.SnapshotInterval != nil && *payload.SnapshotInterval != settings.SnapshotInterval {
|
||||||
err := handler.updateSnapshotInterval(settings, *payload.SnapshotInterval)
|
err := handler.updateSnapshotInterval(settings, *payload.SnapshotInterval)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -58,8 +58,9 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *Handler) userCanAccessStack(securityContext *security.RestrictedRequestContext, endpointID portainer.EndpointID, resourceControl *portainer.ResourceControl) (bool, error) {
|
func (handler *Handler) userCanAccessStack(securityContext *security.RestrictedRequestContext, endpointID portainer.EndpointID, resourceControl *portainer.ResourceControl) (bool, error) {
|
||||||
if securityContext.IsAdmin {
|
user, err := handler.DataStore.User().User(securityContext.UserID)
|
||||||
return true, nil
|
if err != nil {
|
||||||
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
userTeamIDs := make([]portainer.TeamID, 0)
|
userTeamIDs := make([]portainer.TeamID, 0)
|
||||||
|
@ -71,23 +72,7 @@ func (handler *Handler) userCanAccessStack(securityContext *security.RestrictedR
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := handler.DataStore.Extension().Extension(portainer.RBACExtension)
|
return handler.userIsAdminOrEndpointAdmin(user, endpointID)
|
||||||
if err == bolterrors.ErrObjectNotFound {
|
|
||||||
return false, nil
|
|
||||||
} else if err != nil && err != bolterrors.ErrObjectNotFound {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := handler.DataStore.User().User(securityContext.UserID)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, ok := user.EndpointAuthorizations[endpointID][portainer.EndpointResourcesAccess]
|
|
||||||
if ok {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *Handler) userIsAdminOrEndpointAdmin(user *portainer.User, endpointID portainer.EndpointID) (bool, error) {
|
func (handler *Handler) userIsAdminOrEndpointAdmin(user *portainer.User, endpointID portainer.EndpointID) (bool, error) {
|
||||||
|
@ -109,3 +94,12 @@ func (handler *Handler) userIsAdminOrEndpointAdmin(user *portainer.User, endpoin
|
||||||
|
|
||||||
return endpointResourceAccess, nil
|
return endpointResourceAccess, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (handler *Handler) userCanCreateStack(securityContext *security.RestrictedRequestContext, endpointID portainer.EndpointID) (bool, error) {
|
||||||
|
user, err := handler.DataStore.User().User(securityContext.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler.userIsAdminOrEndpointAdmin(user, endpointID)
|
||||||
|
}
|
||||||
|
|
|
@ -46,6 +46,29 @@ func (handler *Handler) stackCreate(w http.ResponseWriter, r *http.Request) *htt
|
||||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid query parameter: endpointId", err}
|
return &httperror.HandlerError{http.StatusBadRequest, "Invalid query parameter: endpointId", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
settings, err := handler.DataStore.Settings().Settings()
|
||||||
|
if err != nil {
|
||||||
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve settings from the database", err}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !settings.AllowStackManagementForRegularUsers {
|
||||||
|
securityContext, err := security.RetrieveRestrictedRequestContext(r)
|
||||||
|
if err != nil {
|
||||||
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve user info from request context", err}
|
||||||
|
}
|
||||||
|
|
||||||
|
canCreate, err := handler.userCanCreateStack(securityContext, portainer.EndpointID(endpointID))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to verify user authorizations to validate stack creation", err}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !canCreate {
|
||||||
|
errMsg := "Stack creation is disabled for non-admin users"
|
||||||
|
return &httperror.HandlerError{http.StatusForbidden, errMsg, errors.New(errMsg)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID))
|
endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID))
|
||||||
if err == bolterrors.ErrObjectNotFound {
|
if err == bolterrors.ErrObjectNotFound {
|
||||||
return &httperror.HandlerError{http.StatusNotFound, "Unable to find an endpoint with the specified identifier inside the database", err}
|
return &httperror.HandlerError{http.StatusNotFound, "Unable to find an endpoint with the specified identifier inside the database", err}
|
||||||
|
|
|
@ -510,22 +510,23 @@ type (
|
||||||
|
|
||||||
// Settings represents the application settings
|
// Settings represents the application settings
|
||||||
Settings struct {
|
Settings struct {
|
||||||
LogoURL string `json:"LogoURL"`
|
LogoURL string `json:"LogoURL"`
|
||||||
BlackListedLabels []Pair `json:"BlackListedLabels"`
|
BlackListedLabels []Pair `json:"BlackListedLabels"`
|
||||||
AuthenticationMethod AuthenticationMethod `json:"AuthenticationMethod"`
|
AuthenticationMethod AuthenticationMethod `json:"AuthenticationMethod"`
|
||||||
LDAPSettings LDAPSettings `json:"LDAPSettings"`
|
LDAPSettings LDAPSettings `json:"LDAPSettings"`
|
||||||
OAuthSettings OAuthSettings `json:"OAuthSettings"`
|
OAuthSettings OAuthSettings `json:"OAuthSettings"`
|
||||||
AllowBindMountsForRegularUsers bool `json:"AllowBindMountsForRegularUsers"`
|
AllowBindMountsForRegularUsers bool `json:"AllowBindMountsForRegularUsers"`
|
||||||
AllowPrivilegedModeForRegularUsers bool `json:"AllowPrivilegedModeForRegularUsers"`
|
AllowPrivilegedModeForRegularUsers bool `json:"AllowPrivilegedModeForRegularUsers"`
|
||||||
AllowVolumeBrowserForRegularUsers bool `json:"AllowVolumeBrowserForRegularUsers"`
|
AllowVolumeBrowserForRegularUsers bool `json:"AllowVolumeBrowserForRegularUsers"`
|
||||||
AllowHostNamespaceForRegularUsers bool `json:"AllowHostNamespaceForRegularUsers"`
|
AllowHostNamespaceForRegularUsers bool `json:"AllowHostNamespaceForRegularUsers"`
|
||||||
AllowDeviceMappingForRegularUsers bool `json:"AllowDeviceMappingForRegularUsers"`
|
AllowDeviceMappingForRegularUsers bool `json:"AllowDeviceMappingForRegularUsers"`
|
||||||
SnapshotInterval string `json:"SnapshotInterval"`
|
AllowStackManagementForRegularUsers bool `json:"AllowStackManagementForRegularUsers"`
|
||||||
TemplatesURL string `json:"TemplatesURL"`
|
SnapshotInterval string `json:"SnapshotInterval"`
|
||||||
EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"`
|
TemplatesURL string `json:"TemplatesURL"`
|
||||||
EdgeAgentCheckinInterval int `json:"EdgeAgentCheckinInterval"`
|
EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"`
|
||||||
EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures"`
|
EdgeAgentCheckinInterval int `json:"EdgeAgentCheckinInterval"`
|
||||||
UserSessionTimeout string `json:"UserSessionTimeout"`
|
EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures"`
|
||||||
|
UserSessionTimeout string `json:"UserSessionTimeout"`
|
||||||
|
|
||||||
// Deprecated fields
|
// Deprecated fields
|
||||||
DisplayDonationHeader bool
|
DisplayDonationHeader bool
|
||||||
|
|
|
@ -9,5 +9,6 @@ angular.module('portainer.docker').component('dockerSidebarContent', {
|
||||||
toggle: '<',
|
toggle: '<',
|
||||||
currentRouteName: '<',
|
currentRouteName: '<',
|
||||||
endpointId: '<',
|
endpointId: '<',
|
||||||
|
showStacks: '<',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
<a ui-sref="docker.templates.custom({endpointId: $ctrl.endpointId})" ui-sref-active="active">Custom Templates</a>
|
<a ui-sref="docker.templates.custom({endpointId: $ctrl.endpointId})" ui-sref-active="active">Custom Templates</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="sidebar-list">
|
<li class="sidebar-list" ng-if="$ctrl.showStacks">
|
||||||
<a ui-sref="docker.stacks({endpointId: $ctrl.endpointId})" ui-sref-active="active">Stacks <span class="menu-icon fa fa-th-list fa-fw"></span></a>
|
<a ui-sref="docker.stacks({endpointId: $ctrl.endpointId})" ui-sref-active="active">Stacks <span class="menu-icon fa fa-th-list fa-fw"></span></a>
|
||||||
</li>
|
</li>
|
||||||
<li class="sidebar-list" ng-if="$ctrl.swarmManagement">
|
<li class="sidebar-list" ng-if="$ctrl.swarmManagement">
|
||||||
|
|
|
@ -81,7 +81,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12 col-md-6">
|
<div class="col-xs-12 col-md-6" ng-if="showStacks">
|
||||||
<a ui-sref="docker.stacks">
|
<a ui-sref="docker.stacks">
|
||||||
<rd-widget>
|
<rd-widget>
|
||||||
<rd-widget-body>
|
<rd-widget-body>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
angular.module('portainer.docker').controller('DashboardController', [
|
angular.module('portainer.docker').controller('DashboardController', [
|
||||||
'$scope',
|
'$scope',
|
||||||
'$q',
|
'$q',
|
||||||
|
'Authentication',
|
||||||
'ContainerService',
|
'ContainerService',
|
||||||
'ImageService',
|
'ImageService',
|
||||||
'NetworkService',
|
'NetworkService',
|
||||||
|
@ -11,10 +12,12 @@ angular.module('portainer.docker').controller('DashboardController', [
|
||||||
'EndpointService',
|
'EndpointService',
|
||||||
'Notifications',
|
'Notifications',
|
||||||
'EndpointProvider',
|
'EndpointProvider',
|
||||||
|
'ExtensionService',
|
||||||
'StateManager',
|
'StateManager',
|
||||||
function (
|
function (
|
||||||
$scope,
|
$scope,
|
||||||
$q,
|
$q,
|
||||||
|
Authentication,
|
||||||
ContainerService,
|
ContainerService,
|
||||||
ImageService,
|
ImageService,
|
||||||
NetworkService,
|
NetworkService,
|
||||||
|
@ -25,6 +28,7 @@ angular.module('portainer.docker').controller('DashboardController', [
|
||||||
EndpointService,
|
EndpointService,
|
||||||
Notifications,
|
Notifications,
|
||||||
EndpointProvider,
|
EndpointProvider,
|
||||||
|
ExtensionService,
|
||||||
StateManager
|
StateManager
|
||||||
) {
|
) {
|
||||||
$scope.dismissInformationPanel = function (id) {
|
$scope.dismissInformationPanel = function (id) {
|
||||||
|
@ -32,12 +36,15 @@ angular.module('portainer.docker').controller('DashboardController', [
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.offlineMode = false;
|
$scope.offlineMode = false;
|
||||||
|
$scope.showStacks = false;
|
||||||
|
|
||||||
function initView() {
|
async function initView() {
|
||||||
const endpointMode = $scope.applicationState.endpoint.mode;
|
const endpointMode = $scope.applicationState.endpoint.mode;
|
||||||
const endpointId = EndpointProvider.endpointID();
|
const endpointId = EndpointProvider.endpointID();
|
||||||
$scope.endpointId = endpointId;
|
$scope.endpointId = endpointId;
|
||||||
|
|
||||||
|
$scope.showStacks = await shouldShowStacks();
|
||||||
|
|
||||||
$q.all({
|
$q.all({
|
||||||
containers: ContainerService.containers(1),
|
containers: ContainerService.containers(1),
|
||||||
images: ImageService.images(false),
|
images: ImageService.images(false),
|
||||||
|
@ -64,6 +71,19 @@ angular.module('portainer.docker').controller('DashboardController', [
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function shouldShowStacks() {
|
||||||
|
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']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
initView();
|
initView();
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<rd-widget>
|
<rd-widget>
|
||||||
<rd-widget-body classes="no-padding">
|
<rd-widget-body classes="no-padding">
|
||||||
<div class="toolBar">
|
<div class="toolBar">
|
||||||
<div class="toolBarTitle"> <i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }} </div>
|
<div class="toolBarTitle"><i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }} </div>
|
||||||
<div class="settings">
|
<div class="settings">
|
||||||
<span class="setting" ng-class="{ 'setting-active': $ctrl.settings.open }" uib-dropdown dropdown-append-to-body auto-close="disabled" is-open="$ctrl.settings.open">
|
<span class="setting" ng-class="{ 'setting-active': $ctrl.settings.open }" uib-dropdown dropdown-append-to-body auto-close="disabled" is-open="$ctrl.settings.open">
|
||||||
<span uib-dropdown-toggle><i class="fa fa-cog" aria-hidden="true"></i> Settings</span>
|
<span uib-dropdown-toggle><i class="fa fa-cog" aria-hidden="true"></i> Settings</span>
|
||||||
|
@ -52,7 +52,7 @@
|
||||||
>
|
>
|
||||||
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
|
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-sm btn-primary" ui-sref="docker.stacks.newstack" authorization="PortainerStackCreate">
|
<button ng-disabled="!$ctrl.createEnabled" type="button" class="btn btn-sm btn-primary" ui-sref="docker.stacks.newstack" authorization="PortainerStackCreate">
|
||||||
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add stack
|
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add stack
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -144,7 +144,10 @@
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer" ng-if="$ctrl.dataset">
|
<div class="footer" ng-if="$ctrl.dataset">
|
||||||
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0"> {{ $ctrl.state.selectedItemCount }} item(s) selected </div>
|
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0">
|
||||||
|
{{ $ctrl.state.selectedItemCount }}
|
||||||
|
item(s) selected
|
||||||
|
</div>
|
||||||
<div class="paginationControls">
|
<div class="paginationControls">
|
||||||
<form class="form-inline">
|
<form class="form-inline">
|
||||||
<span class="limitSelector">
|
<span class="limitSelector">
|
||||||
|
|
|
@ -11,5 +11,6 @@ angular.module('portainer.app').component('stacksDatatable', {
|
||||||
removeAction: '<',
|
removeAction: '<',
|
||||||
offlineMode: '<',
|
offlineMode: '<',
|
||||||
refreshCallback: '<',
|
refreshCallback: '<',
|
||||||
|
createEnabled: '<',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,6 +9,7 @@ export function SettingsViewModel(data) {
|
||||||
this.AllowVolumeBrowserForRegularUsers = data.AllowVolumeBrowserForRegularUsers;
|
this.AllowVolumeBrowserForRegularUsers = data.AllowVolumeBrowserForRegularUsers;
|
||||||
this.AllowHostNamespaceForRegularUsers = data.AllowHostNamespaceForRegularUsers;
|
this.AllowHostNamespaceForRegularUsers = data.AllowHostNamespaceForRegularUsers;
|
||||||
this.AllowDeviceMappingForRegularUsers = data.AllowDeviceMappingForRegularUsers;
|
this.AllowDeviceMappingForRegularUsers = data.AllowDeviceMappingForRegularUsers;
|
||||||
|
this.AllowStackManagementForRegularUsers = data.AllowStackManagementForRegularUsers;
|
||||||
this.SnapshotInterval = data.SnapshotInterval;
|
this.SnapshotInterval = data.SnapshotInterval;
|
||||||
this.TemplatesURL = data.TemplatesURL;
|
this.TemplatesURL = data.TemplatesURL;
|
||||||
this.EnableHostManagementFeatures = data.EnableHostManagementFeatures;
|
this.EnableHostManagementFeatures = data.EnableHostManagementFeatures;
|
||||||
|
@ -21,12 +22,13 @@ export function PublicSettingsViewModel(settings) {
|
||||||
this.AllowBindMountsForRegularUsers = settings.AllowBindMountsForRegularUsers;
|
this.AllowBindMountsForRegularUsers = settings.AllowBindMountsForRegularUsers;
|
||||||
this.AllowPrivilegedModeForRegularUsers = settings.AllowPrivilegedModeForRegularUsers;
|
this.AllowPrivilegedModeForRegularUsers = settings.AllowPrivilegedModeForRegularUsers;
|
||||||
this.AllowVolumeBrowserForRegularUsers = settings.AllowVolumeBrowserForRegularUsers;
|
this.AllowVolumeBrowserForRegularUsers = settings.AllowVolumeBrowserForRegularUsers;
|
||||||
|
this.AllowDeviceMappingForRegularUsers = settings.AllowDeviceMappingForRegularUsers;
|
||||||
|
this.AllowStackManagementForRegularUsers = settings.AllowStackManagementForRegularUsers;
|
||||||
this.AuthenticationMethod = settings.AuthenticationMethod;
|
this.AuthenticationMethod = settings.AuthenticationMethod;
|
||||||
this.EnableHostManagementFeatures = settings.EnableHostManagementFeatures;
|
this.EnableHostManagementFeatures = settings.EnableHostManagementFeatures;
|
||||||
this.EnableEdgeComputeFeatures = settings.EnableEdgeComputeFeatures;
|
this.EnableEdgeComputeFeatures = settings.EnableEdgeComputeFeatures;
|
||||||
this.LogoURL = settings.LogoURL;
|
this.LogoURL = settings.LogoURL;
|
||||||
this.OAuthLoginURI = settings.OAuthLoginURI;
|
this.OAuthLoginURI = settings.OAuthLoginURI;
|
||||||
this.AllowDeviceMappingForRegularUsers = settings.AllowDeviceMappingForRegularUsers;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LDAPSettingsViewModel(data) {
|
export function LDAPSettingsViewModel(data) {
|
||||||
|
|
|
@ -86,6 +86,11 @@ angular.module('portainer.app').factory('StateManager', [
|
||||||
LocalStorage.storeApplicationState(state.application);
|
LocalStorage.storeApplicationState(state.application);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
manager.updateAllowStackManagementForRegularUsers = function updateAllowStackManagementForRegularUsers(allowStackManagementForRegularUsers) {
|
||||||
|
state.application.allowStackManagementForRegularUsers = allowStackManagementForRegularUsers;
|
||||||
|
LocalStorage.storeApplicationState(state.application);
|
||||||
|
};
|
||||||
|
|
||||||
function assignStateFromStatusAndSettings(status, settings) {
|
function assignStateFromStatusAndSettings(status, settings) {
|
||||||
state.application.analytics = status.Analytics;
|
state.application.analytics = status.Analytics;
|
||||||
state.application.version = status.Version;
|
state.application.version = status.Version;
|
||||||
|
@ -95,6 +100,7 @@ angular.module('portainer.app').factory('StateManager', [
|
||||||
state.application.enableVolumeBrowserForNonAdminUsers = settings.AllowVolumeBrowserForRegularUsers;
|
state.application.enableVolumeBrowserForNonAdminUsers = settings.AllowVolumeBrowserForRegularUsers;
|
||||||
state.application.enableEdgeComputeFeatures = settings.EnableEdgeComputeFeatures;
|
state.application.enableEdgeComputeFeatures = settings.EnableEdgeComputeFeatures;
|
||||||
state.application.allowDeviceMappingForRegularUsers = settings.AllowDeviceMappingForRegularUsers;
|
state.application.allowDeviceMappingForRegularUsers = settings.AllowDeviceMappingForRegularUsers;
|
||||||
|
state.application.allowStackManagementForRegularUsers = settings.AllowStackManagementForRegularUsers;
|
||||||
state.application.validity = moment().unix();
|
state.application.validity = moment().unix();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -116,6 +116,16 @@
|
||||||
</label>
|
</label>
|
||||||
<label class="switch" style="margin-left: 20px;">
|
<label class="switch" style="margin-left: 20px;">
|
||||||
<input type="checkbox" name="toggle_allowHostNamespaceForRegularUsers" ng-model="formValues.restrictHostNamespaceForRegularUsers" /><i></i>
|
<input type="checkbox" name="toggle_allowHostNamespaceForRegularUsers" ng-model="formValues.restrictHostNamespaceForRegularUsers" /><i></i>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<label for="toggle_disableStackManagementForRegularUsers" class="control-label text-left">
|
||||||
|
Disable the use of Stacks for non-administrators
|
||||||
|
</label>
|
||||||
|
<label class="switch" style="margin-left: 20px;">
|
||||||
|
<input type="checkbox" name="toggle_disableStackManagementForRegularUsers" ng-model="formValues.disableStackManagementForRegularUsers" /><i></i>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -34,6 +34,7 @@ angular.module('portainer.app').controller('SettingsController', [
|
||||||
enableEdgeComputeFeatures: false,
|
enableEdgeComputeFeatures: false,
|
||||||
restrictHostNamespaceForRegularUsers: false,
|
restrictHostNamespaceForRegularUsers: false,
|
||||||
allowDeviceMappingForRegularUsers: false,
|
allowDeviceMappingForRegularUsers: false,
|
||||||
|
allowStackManagementForRegularUsers: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.removeFilteredContainerLabel = function (index) {
|
$scope.removeFilteredContainerLabel = function (index) {
|
||||||
|
@ -68,6 +69,7 @@ angular.module('portainer.app').controller('SettingsController', [
|
||||||
settings.EnableEdgeComputeFeatures = $scope.formValues.enableEdgeComputeFeatures;
|
settings.EnableEdgeComputeFeatures = $scope.formValues.enableEdgeComputeFeatures;
|
||||||
settings.AllowHostNamespaceForRegularUsers = !$scope.formValues.restrictHostNamespaceForRegularUsers;
|
settings.AllowHostNamespaceForRegularUsers = !$scope.formValues.restrictHostNamespaceForRegularUsers;
|
||||||
settings.AllowDeviceMappingForRegularUsers = !$scope.formValues.disableDeviceMappingForRegularUsers;
|
settings.AllowDeviceMappingForRegularUsers = !$scope.formValues.disableDeviceMappingForRegularUsers;
|
||||||
|
settings.AllowStackManagementForRegularUsers = !$scope.formValues.disableStackManagementForRegularUsers;
|
||||||
|
|
||||||
$scope.state.actionInProgress = true;
|
$scope.state.actionInProgress = true;
|
||||||
updateSettings(settings);
|
updateSettings(settings);
|
||||||
|
@ -84,6 +86,7 @@ angular.module('portainer.app').controller('SettingsController', [
|
||||||
StateManager.updateAllowHostNamespaceForRegularUsers(settings.AllowHostNamespaceForRegularUsers);
|
StateManager.updateAllowHostNamespaceForRegularUsers(settings.AllowHostNamespaceForRegularUsers);
|
||||||
StateManager.updateEnableEdgeComputeFeatures(settings.EnableEdgeComputeFeatures);
|
StateManager.updateEnableEdgeComputeFeatures(settings.EnableEdgeComputeFeatures);
|
||||||
StateManager.updateAllowDeviceMappingForRegularUsers(settings.AllowDeviceMappingForRegularUsers);
|
StateManager.updateAllowDeviceMappingForRegularUsers(settings.AllowDeviceMappingForRegularUsers);
|
||||||
|
StateManager.updateAllowStackManagementForRegularUsers(settings.AllowStackManagementForRegularUsers);
|
||||||
$state.reload();
|
$state.reload();
|
||||||
})
|
})
|
||||||
.catch(function error(err) {
|
.catch(function error(err) {
|
||||||
|
@ -110,6 +113,7 @@ angular.module('portainer.app').controller('SettingsController', [
|
||||||
$scope.formValues.enableEdgeComputeFeatures = settings.EnableEdgeComputeFeatures;
|
$scope.formValues.enableEdgeComputeFeatures = settings.EnableEdgeComputeFeatures;
|
||||||
$scope.formValues.restrictHostNamespaceForRegularUsers = !settings.AllowHostNamespaceForRegularUsers;
|
$scope.formValues.restrictHostNamespaceForRegularUsers = !settings.AllowHostNamespaceForRegularUsers;
|
||||||
$scope.formValues.disableDeviceMappingForRegularUsers = !settings.AllowDeviceMappingForRegularUsers;
|
$scope.formValues.disableDeviceMappingForRegularUsers = !settings.AllowDeviceMappingForRegularUsers;
|
||||||
|
$scope.formValues.disableStackManagementForRegularUsers = !settings.AllowStackManagementForRegularUsers;
|
||||||
})
|
})
|
||||||
.catch(function error(err) {
|
.catch(function error(err) {
|
||||||
Notifications.error('Failure', err, 'Unable to retrieve application settings');
|
Notifications.error('Failure', err, 'Unable to retrieve application settings');
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
ng-if="applicationState.endpoint.mode && applicationState.endpoint.mode.provider !== 'AZURE' && applicationState.endpoint.mode.provider !== 'KUBERNETES'"
|
ng-if="applicationState.endpoint.mode && applicationState.endpoint.mode.provider !== 'AZURE' && applicationState.endpoint.mode.provider !== 'KUBERNETES'"
|
||||||
current-route-name="$state.current.name"
|
current-route-name="$state.current.name"
|
||||||
toggle="toggle"
|
toggle="toggle"
|
||||||
|
show-stacks="showStacks"
|
||||||
endpoint-api-version="applicationState.endpoint.apiVersion"
|
endpoint-api-version="applicationState.endpoint.apiVersion"
|
||||||
swarm-management="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER'"
|
swarm-management="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER'"
|
||||||
standalone-management="applicationState.endpoint.mode.provider === 'DOCKER_STANDALONE'"
|
standalone-management="applicationState.endpoint.mode.provider === 'DOCKER_STANDALONE'"
|
||||||
|
|
|
@ -7,7 +7,8 @@ angular.module('portainer.app').controller('SidebarController', [
|
||||||
'Authentication',
|
'Authentication',
|
||||||
'UserService',
|
'UserService',
|
||||||
'EndpointProvider',
|
'EndpointProvider',
|
||||||
function ($q, $scope, $transitions, StateManager, Notifications, Authentication, UserService, EndpointProvider) {
|
'ExtensionService',
|
||||||
|
function ($q, $scope, $transitions, StateManager, Notifications, Authentication, UserService, EndpointProvider, ExtensionService) {
|
||||||
function checkPermissions(memberships) {
|
function checkPermissions(memberships) {
|
||||||
var isLeader = false;
|
var isLeader = false;
|
||||||
angular.forEach(memberships, function (membership) {
|
angular.forEach(memberships, function (membership) {
|
||||||
|
@ -18,9 +19,10 @@ angular.module('portainer.app').controller('SidebarController', [
|
||||||
$scope.isTeamLeader = isLeader;
|
$scope.isTeamLeader = isLeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
function initView() {
|
async function initView() {
|
||||||
$scope.uiVersion = StateManager.getState().application.version;
|
$scope.uiVersion = StateManager.getState().application.version;
|
||||||
$scope.logo = StateManager.getState().application.logo;
|
$scope.logo = StateManager.getState().application.logo;
|
||||||
|
$scope.showStacks = await shouldShowStacks();
|
||||||
|
|
||||||
let userDetails = Authentication.getUserDetails();
|
let userDetails = Authentication.getUserDetails();
|
||||||
let isAdmin = Authentication.isAdmin();
|
let isAdmin = Authentication.isAdmin();
|
||||||
|
@ -41,5 +43,24 @@ angular.module('portainer.app').controller('SidebarController', [
|
||||||
}
|
}
|
||||||
|
|
||||||
initView();
|
initView();
|
||||||
|
|
||||||
|
async function shouldShowStacks() {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
$transitions.onEnter({}, async () => {
|
||||||
|
$scope.showStacks = await shouldShowStacks();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
remove-action="removeAction"
|
remove-action="removeAction"
|
||||||
offline-mode="offlineMode"
|
offline-mode="offlineMode"
|
||||||
refresh-callback="getStacks"
|
refresh-callback="getStacks"
|
||||||
|
create-enabled="createEnabled"
|
||||||
></stacks-datatable>
|
></stacks-datatable>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,65 +1,85 @@
|
||||||
angular.module('portainer.app').controller('StacksController', [
|
angular.module('portainer.app').controller('StacksController', StacksController);
|
||||||
'$scope',
|
|
||||||
'$state',
|
|
||||||
'Notifications',
|
|
||||||
'StackService',
|
|
||||||
'ModalService',
|
|
||||||
'EndpointProvider',
|
|
||||||
function ($scope, $state, Notifications, StackService, ModalService, EndpointProvider) {
|
|
||||||
$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) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
deleteSelectedStacks(selectedItems);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function deleteSelectedStacks(stacks) {
|
/* @ngInject */
|
||||||
var endpointId = EndpointProvider.endpointID();
|
function StacksController($scope, $state, Notifications, StackService, ModalService, EndpointProvider, Authentication, StateManager, ExtensionService) {
|
||||||
var actionCount = stacks.length;
|
$scope.removeAction = function (selectedItems) {
|
||||||
angular.forEach(stacks, function (stack) {
|
ModalService.confirmDeletion('Do you want to remove the selected stack(s)? Associated services will be removed as well.', function onConfirm(confirmed) {
|
||||||
StackService.remove(stack, stack.External, endpointId)
|
if (!confirmed) {
|
||||||
.then(function success() {
|
return;
|
||||||
Notifications.success('Stack successfully removed', stack.Name);
|
}
|
||||||
var index = $scope.stacks.indexOf(stack);
|
deleteSelectedStacks(selectedItems);
|
||||||
$scope.stacks.splice(index, 1);
|
});
|
||||||
})
|
};
|
||||||
.catch(function error(err) {
|
|
||||||
Notifications.error('Failure', err, 'Unable to remove stack ' + stack.Name);
|
|
||||||
})
|
|
||||||
.finally(function final() {
|
|
||||||
--actionCount;
|
|
||||||
if (actionCount === 0) {
|
|
||||||
$state.reload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.offlineMode = false;
|
function deleteSelectedStacks(stacks) {
|
||||||
|
var endpointId = EndpointProvider.endpointID();
|
||||||
$scope.getStacks = getStacks;
|
var actionCount = stacks.length;
|
||||||
function getStacks() {
|
angular.forEach(stacks, function (stack) {
|
||||||
var endpointMode = $scope.applicationState.endpoint.mode;
|
StackService.remove(stack, stack.External, endpointId)
|
||||||
var endpointId = EndpointProvider.endpointID();
|
.then(function success() {
|
||||||
|
Notifications.success('Stack successfully removed', stack.Name);
|
||||||
StackService.stacks(true, endpointMode.provider === 'DOCKER_SWARM_MODE' && endpointMode.role === 'MANAGER', endpointId)
|
var index = $scope.stacks.indexOf(stack);
|
||||||
.then(function success(data) {
|
$scope.stacks.splice(index, 1);
|
||||||
var stacks = data;
|
|
||||||
$scope.stacks = stacks;
|
|
||||||
$scope.offlineMode = EndpointProvider.offlineMode();
|
|
||||||
})
|
})
|
||||||
.catch(function error(err) {
|
.catch(function error(err) {
|
||||||
$scope.stacks = [];
|
Notifications.error('Failure', err, 'Unable to remove stack ' + stack.Name);
|
||||||
Notifications.error('Failure', err, 'Unable to retrieve stacks');
|
})
|
||||||
|
.finally(function final() {
|
||||||
|
--actionCount;
|
||||||
|
if (actionCount === 0) {
|
||||||
|
$state.reload();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.offlineMode = false;
|
||||||
|
$scope.createEnabled = false;
|
||||||
|
|
||||||
|
$scope.getStacks = getStacks;
|
||||||
|
|
||||||
|
function getStacks() {
|
||||||
|
var endpointMode = $scope.applicationState.endpoint.mode;
|
||||||
|
var endpointId = EndpointProvider.endpointID();
|
||||||
|
|
||||||
|
StackService.stacks(true, endpointMode.provider === 'DOCKER_SWARM_MODE' && endpointMode.role === 'MANAGER', endpointId)
|
||||||
|
.then(function success(data) {
|
||||||
|
var stacks = data;
|
||||||
|
$scope.stacks = stacks;
|
||||||
|
$scope.offlineMode = EndpointProvider.offlineMode();
|
||||||
|
})
|
||||||
|
.catch(function error(err) {
|
||||||
|
$scope.stacks = [];
|
||||||
|
Notifications.error('Failure', err, 'Unable to retrieve stacks');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadCreateEnabled() {
|
||||||
|
const appState = StateManager.getState().application;
|
||||||
|
if (appState.allowStackManagementForRegularUsers) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function initView() {
|
let isAdmin = true;
|
||||||
getStacks();
|
if (appState.authentication) {
|
||||||
|
isAdmin = Authentication.isAdmin();
|
||||||
|
}
|
||||||
|
if (isAdmin) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
initView();
|
const RBACExtensionEnabled = await ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC);
|
||||||
},
|
if (!RBACExtensionEnabled) {
|
||||||
]);
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Authentication.hasAuthorizations(['EndpointResourcesAccess']);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initView() {
|
||||||
|
getStacks();
|
||||||
|
$scope.createEnabled = await loadCreateEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
initView();
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue