Compare commits
13 Commits
2.34.0
...
feat/CE/48
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57f8c77e3c | ||
|
|
bc11177ad4 | ||
|
|
0a7295be2f | ||
|
|
eecb0c0c6e | ||
|
|
69b07ca800 | ||
|
|
c8999c743a | ||
|
|
f37f0858dc | ||
|
|
7f5c08ef64 | ||
|
|
85bd4f3901 | ||
|
|
5a7889dd1f | ||
|
|
bd58991c2c | ||
|
|
5f4b375bab | ||
|
|
3d937d7afc |
@@ -1,8 +1,11 @@
|
||||
package migrator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/errors"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
"github.com/portainer/portainer/api/internal/stackutils"
|
||||
)
|
||||
|
||||
@@ -38,3 +41,38 @@ func (m *Migrator) updateStackResourceControlToDB27() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migrator) updateRegistriesToDB27() error {
|
||||
registries, err := m.registryService.Registries()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
endpoints, err := m.endpointService.Endpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
for _, registry := range registries {
|
||||
userIDs := []portainer.UserID{}
|
||||
for id := range registry.UserAccessPolicies {
|
||||
userIDs = append(userIDs, portainer.UserID(id))
|
||||
}
|
||||
|
||||
teamIDs := []portainer.TeamID{}
|
||||
for id := range registry.TeamAccessPolicies {
|
||||
teamIDs = append(teamIDs, portainer.TeamID(id))
|
||||
}
|
||||
|
||||
resourceControl := authorization.NewRestrictedResourceControl(
|
||||
fmt.Sprintf("%d-%d", int(registry.ID), int(endpoint.ID)),
|
||||
portainer.RegistryResourceControl,
|
||||
userIDs,
|
||||
teamIDs,
|
||||
)
|
||||
m.resourceControlService.CreateResourceControl(resourceControl)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -356,6 +356,11 @@ func (m *Migrator) Migrate() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.updateRegistriesToDB27()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return m.versionService.StoreDBVersion(portainer.DBVersion)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package registries
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
@@ -8,6 +9,7 @@ import (
|
||||
"github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/http/proxy"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
)
|
||||
|
||||
func hideFields(registry *portainer.Registry) {
|
||||
@@ -47,3 +49,69 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||
bouncer.AdminAccess(httperror.LoggerHandler(h.proxyRequestsToGitlabAPIWithoutRegistry)))
|
||||
return h
|
||||
}
|
||||
|
||||
func (handler *Handler) userIsAdmin(userID portainer.UserID) (bool, error) {
|
||||
user, err := handler.DataStore.User().User(userID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
isAdmin := user.Role == portainer.AdministratorRole
|
||||
|
||||
return isAdmin, nil
|
||||
}
|
||||
|
||||
func (handler *Handler) userIsAdminOrEndpointAdmin(user *portainer.User, endpointID portainer.EndpointID) (bool, error) {
|
||||
isAdmin := user.Role == portainer.AdministratorRole
|
||||
|
||||
return isAdmin, nil
|
||||
}
|
||||
|
||||
func (handler *Handler) userCanCreateRegistry(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)
|
||||
}
|
||||
|
||||
func (handler *Handler) userCanAccessRegistry(securityContext *security.RestrictedRequestContext, endpointID portainer.EndpointID, resourceControl *portainer.ResourceControl) (bool, error) {
|
||||
user, err := handler.DataStore.User().User(securityContext.UserID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
userTeamIDs := make([]portainer.TeamID, 0)
|
||||
for _, membership := range securityContext.UserMemberships {
|
||||
userTeamIDs = append(userTeamIDs, membership.TeamID)
|
||||
}
|
||||
|
||||
if resourceControl != nil && authorization.UserCanAccessResource(securityContext.UserID, userTeamIDs, resourceControl) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return handler.userIsAdminOrEndpointAdmin(user, endpointID)
|
||||
}
|
||||
|
||||
func (handler *Handler) computeRegistryResourceControlID(registryID portainer.RegistryID, endpointID portainer.EndpointID) string {
|
||||
return fmt.Sprintf("%d-%d", int(registryID), int(endpointID))
|
||||
}
|
||||
|
||||
func (handler *Handler) filterAndDecorateRegistries(registries []portainer.Registry, resourceControls []portainer.ResourceControl, endpointID portainer.EndpointID) []portainer.Registry {
|
||||
if endpointID == 0 {
|
||||
return registries
|
||||
}
|
||||
|
||||
filteredRegistries := make([]portainer.Registry, 0, len(registries))
|
||||
for _, registry := range registries {
|
||||
for _, resourceControl := range resourceControls {
|
||||
if resourceControl.ResourceID == handler.computeRegistryResourceControlID(registry.ID, endpointID) {
|
||||
registry.ResourceControl = &resourceControl
|
||||
filteredRegistries = append(filteredRegistries, registry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filteredRegistries
|
||||
}
|
||||
|
||||
@@ -93,17 +93,17 @@ func (payload *registryConfigurePayload) Validate(r *http.Request) error {
|
||||
// @failure 500 "Server error"
|
||||
// @router /registries/{id}/configure [post]
|
||||
func (handler *Handler) registryConfigure(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
payload := ®istryConfigurePayload{}
|
||||
err := payload.Validate(r)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
||||
}
|
||||
|
||||
registryID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid registry identifier route variable", err}
|
||||
}
|
||||
|
||||
payload := ®istryConfigurePayload{}
|
||||
err = payload.Validate(r)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
||||
}
|
||||
|
||||
registry, err := handler.DataStore.Registry().Registry(portainer.RegistryID(registryID))
|
||||
if err == bolterrors.ErrObjectNotFound {
|
||||
return &httperror.HandlerError{http.StatusNotFound, "Unable to find a registry with the specified identifier inside the database", err}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
)
|
||||
|
||||
type registryCreatePayload struct {
|
||||
@@ -78,7 +79,20 @@ func (handler *Handler) registryCreate(w http.ResponseWriter, r *http.Request) *
|
||||
|
||||
err = handler.DataStore.Registry().CreateRegistry(registry)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist the registry inside the database", err}
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to create registry", err}
|
||||
}
|
||||
|
||||
endpoints, err := handler.DataStore.Endpoint().Endpoints()
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve endpoints information inside the database", err}
|
||||
}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
resourceControl := authorization.NewAdministratorsOnlyResourceControl(handler.computeRegistryResourceControlID(registry.ID, endpoint.ID), portainer.RegistryResourceControl)
|
||||
err = handler.DataStore.ResourceControl().CreateResourceControl(resourceControl)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist resource control inside the database", err}
|
||||
}
|
||||
}
|
||||
|
||||
hideFields(registry)
|
||||
|
||||
@@ -28,7 +28,7 @@ func (handler *Handler) registryDelete(w http.ResponseWriter, r *http.Request) *
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid registry identifier route variable", err}
|
||||
}
|
||||
|
||||
_, err = handler.DataStore.Registry().Registry(portainer.RegistryID(registryID))
|
||||
registry, err := handler.DataStore.Registry().Registry(portainer.RegistryID(registryID))
|
||||
if err == errors.ErrObjectNotFound {
|
||||
return &httperror.HandlerError{http.StatusNotFound, "Unable to find a registry with the specified identifier inside the database", err}
|
||||
} else if err != nil {
|
||||
@@ -40,5 +40,17 @@ func (handler *Handler) registryDelete(w http.ResponseWriter, r *http.Request) *
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove the registry from the database", err}
|
||||
}
|
||||
|
||||
endpoints, err := handler.DataStore.Endpoint().Endpoints()
|
||||
for _, endpoint := range endpoints {
|
||||
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(handler.computeRegistryResourceControlID(registry.ID, endpoint.ID), portainer.RegistryResourceControl)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the registry", err}
|
||||
}
|
||||
err = handler.DataStore.ResourceControl().DeleteResourceControl(resourceControl.ID)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove the resource control associated to the registry", err}
|
||||
}
|
||||
}
|
||||
|
||||
return response.Empty(w)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
)
|
||||
|
||||
// @id RegistryInspect
|
||||
@@ -44,6 +45,35 @@ func (handler *Handler) registryInspect(w http.ResponseWriter, r *http.Request)
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access registry", errors.ErrEndpointAccessDenied}
|
||||
}
|
||||
|
||||
endpointID, err := request.RetrieveNumericQueryParameter(r, "endpointId", false)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid query parameter: endpointId", err}
|
||||
}
|
||||
|
||||
securityContext, err := security.RetrieveRestrictedRequestContext(r)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve user info from request context", err}
|
||||
}
|
||||
|
||||
if endpointID != 0 || !securityContext.IsAdmin {
|
||||
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(handler.computeRegistryResourceControlID(registry.ID, portainer.EndpointID(endpointID)), portainer.RegistryResourceControl)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the registry", err}
|
||||
}
|
||||
|
||||
access, err := handler.userCanAccessRegistry(securityContext, portainer.EndpointID(endpointID), resourceControl)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to verify user authorizations to validate registry access", err}
|
||||
}
|
||||
if !access {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", errors.ErrResourceAccessDenied}
|
||||
}
|
||||
|
||||
if resourceControl != nil {
|
||||
registry.ResourceControl = resourceControl
|
||||
}
|
||||
}
|
||||
|
||||
hideFields(registry)
|
||||
return response.JSON(w, registry)
|
||||
}
|
||||
|
||||
@@ -4,8 +4,11 @@ import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
)
|
||||
|
||||
// @id RegistryList
|
||||
@@ -21,21 +24,45 @@ import (
|
||||
// @failure 500 "Server error"
|
||||
// @router /registries [get]
|
||||
func (handler *Handler) registryList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
endpointID, err := request.RetrieveNumericQueryParameter(r, "endpointId", false)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid query parameter: endpointId", err}
|
||||
}
|
||||
|
||||
registries, err := handler.DataStore.Registry().Registries()
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve registries from the database", err}
|
||||
}
|
||||
|
||||
resourceControls, err := handler.DataStore.ResourceControl().ResourceControls()
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve resource controls from the database", err}
|
||||
}
|
||||
|
||||
securityContext, err := security.RetrieveRestrictedRequestContext(r)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
|
||||
}
|
||||
|
||||
filteredRegistries := security.FilterRegistries(registries, securityContext)
|
||||
registries = handler.filterAndDecorateRegistries(registries, resourceControls, portainer.EndpointID(endpointID))
|
||||
|
||||
for idx := range filteredRegistries {
|
||||
hideFields(&filteredRegistries[idx])
|
||||
if !securityContext.IsAdmin {
|
||||
user, err := handler.DataStore.User().User(securityContext.UserID)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve user information from the database", err}
|
||||
}
|
||||
|
||||
userTeamIDs := make([]portainer.TeamID, 0)
|
||||
for _, membership := range securityContext.UserMemberships {
|
||||
userTeamIDs = append(userTeamIDs, membership.TeamID)
|
||||
}
|
||||
|
||||
registries = authorization.FilterAuthorizedRegistries(registries, user, userTeamIDs)
|
||||
}
|
||||
|
||||
return response.JSON(w, filteredRegistries)
|
||||
for idx := range registries {
|
||||
hideFields(®istries[idx])
|
||||
}
|
||||
|
||||
return response.JSON(w, registries)
|
||||
}
|
||||
|
||||
@@ -52,12 +52,6 @@ func (handler *Handler) registryUpdate(w http.ResponseWriter, r *http.Request) *
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid registry identifier route variable", err}
|
||||
}
|
||||
|
||||
var payload registryUpdatePayload
|
||||
err = request.DecodeAndValidateJSONPayload(r, &payload)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
||||
}
|
||||
|
||||
registry, err := handler.DataStore.Registry().Registry(portainer.RegistryID(registryID))
|
||||
if err == bolterrors.ErrObjectNotFound {
|
||||
return &httperror.HandlerError{http.StatusNotFound, "Unable to find a registry with the specified identifier inside the database", err}
|
||||
@@ -65,6 +59,12 @@ func (handler *Handler) registryUpdate(w http.ResponseWriter, r *http.Request) *
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a registry with the specified identifier inside the database", err}
|
||||
}
|
||||
|
||||
var payload registryUpdatePayload
|
||||
err = request.DecodeAndValidateJSONPayload(r, &payload)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
||||
}
|
||||
|
||||
if payload.Name != nil {
|
||||
registry.Name = *payload.Name
|
||||
}
|
||||
@@ -102,14 +102,6 @@ func (handler *Handler) registryUpdate(w http.ResponseWriter, r *http.Request) *
|
||||
}
|
||||
}
|
||||
|
||||
if payload.UserAccessPolicies != nil {
|
||||
registry.UserAccessPolicies = payload.UserAccessPolicies
|
||||
}
|
||||
|
||||
if payload.TeamAccessPolicies != nil {
|
||||
registry.TeamAccessPolicies = payload.TeamAccessPolicies
|
||||
}
|
||||
|
||||
err = handler.DataStore.Registry().UpdateRegistry(registry.ID, registry)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist registry changes inside the database", err}
|
||||
|
||||
@@ -134,6 +134,19 @@ func DecorateCustomTemplates(templates []portainer.CustomTemplate, resourceContr
|
||||
return templates
|
||||
}
|
||||
|
||||
// FilterAuthorizedRegistries returns a list of decorated registries filtered through resource control access checks.
|
||||
func FilterAuthorizedRegistries(registries []portainer.Registry, user *portainer.User, userTeamIDs []portainer.TeamID) []portainer.Registry {
|
||||
authorizedRegistries := make([]portainer.Registry, 0)
|
||||
|
||||
for _, registry := range registries {
|
||||
if registry.ResourceControl != nil && UserCanAccessResource(user.ID, userTeamIDs, registry.ResourceControl) {
|
||||
authorizedRegistries = append(authorizedRegistries, registry)
|
||||
}
|
||||
}
|
||||
|
||||
return authorizedRegistries
|
||||
}
|
||||
|
||||
// FilterAuthorizedStacks returns a list of decorated stacks filtered through resource control access checks.
|
||||
func FilterAuthorizedStacks(stacks []portainer.Stack, user *portainer.User, userTeamIDs []portainer.TeamID) []portainer.Stack {
|
||||
authorizedStacks := make([]portainer.Stack, 0)
|
||||
|
||||
@@ -508,8 +508,12 @@ type (
|
||||
Password string `json:"Password,omitempty" example:"registry_password"`
|
||||
ManagementConfiguration *RegistryManagementConfiguration `json:"ManagementConfiguration"`
|
||||
Gitlab GitlabRegistryData `json:"Gitlab"`
|
||||
UserAccessPolicies UserAccessPolicies `json:"UserAccessPolicies"`
|
||||
TeamAccessPolicies TeamAccessPolicies `json:"TeamAccessPolicies"`
|
||||
ResourceControl *ResourceControl `json:"ResourceControl"`
|
||||
|
||||
// Deprecated fields
|
||||
// Deprecated in DBVersion == 26
|
||||
UserAccessPolicies UserAccessPolicies `json:"UserAccessPolicies"`
|
||||
TeamAccessPolicies TeamAccessPolicies `json:"TeamAccessPolicies"`
|
||||
|
||||
// Deprecated fields
|
||||
// Deprecated in DBVersion == 18
|
||||
@@ -546,7 +550,7 @@ type (
|
||||
// List of Docker resources that will inherit this access control
|
||||
SubResourceIDs []string `json:"SubResourceIds" example:"617c5f22bb9b023d6daab7cba43a57576f83492867bc767d1c59416b065e5f08"`
|
||||
// Type of Docker resource. Valid values are: 1- container, 2 -service
|
||||
// 3 - volume, 4 - secret, 5 - stack, 6 - config or 7 - custom template
|
||||
// 3 - volume, 4 - secret, 5 - stack, 6 - config, 7 - custom template or 8 - registry
|
||||
Type ResourceControlType `json:"Type" example:"1"`
|
||||
UserAccesses []UserResourceAccess `json:"UserAccesses" example:""`
|
||||
TeamAccesses []TeamResourceAccess `json:"TeamAccesses" example:""`
|
||||
@@ -1487,6 +1491,8 @@ const (
|
||||
ConfigResourceControl
|
||||
// CustomTemplateResourceControl represents a resource control associated to a custom template
|
||||
CustomTemplateResourceControl
|
||||
// RegistryResourceControl represents a resource control associated to a registry
|
||||
RegistryResourceControl
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -591,6 +591,26 @@ angular.module('portainer.docker', ['portainer.app']).config([
|
||||
},
|
||||
};
|
||||
|
||||
const registries = {
|
||||
name: 'docker.registries',
|
||||
url: '/registries',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'endpointRegistriesView',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const registryAccess = {
|
||||
name: 'docker.registries.access',
|
||||
url: '/:id/access',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'dockerRegistryAccessView',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
$stateRegistryProvider.register(configs);
|
||||
$stateRegistryProvider.register(config);
|
||||
$stateRegistryProvider.register(configCreation);
|
||||
@@ -641,5 +661,7 @@ angular.module('portainer.docker', ['portainer.app']).config([
|
||||
$stateRegistryProvider.register(volumeBrowse);
|
||||
$stateRegistryProvider.register(volumeCreation);
|
||||
$stateRegistryProvider.register(dockerFeaturesConfiguration);
|
||||
$stateRegistryProvider.register(registries);
|
||||
$stateRegistryProvider.register(registryAccess);
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -38,14 +38,34 @@
|
||||
<li class="sidebar-list" ng-if="$ctrl.swarmManagement">
|
||||
<a ui-sref="docker.swarm({endpointId: $ctrl.endpointId})" ui-sref-active="active">Swarm <span class="menu-icon fa fa-object-group fa-fw"></span></a>
|
||||
|
||||
<div class="sidebar-sublist" ng-if="$ctrl.adminAccess && ['docker.featuresConfiguration', 'docker.swarm'].includes($ctrl.currentRouteName)">
|
||||
<div
|
||||
class="sidebar-sublist"
|
||||
ng-if="$ctrl.adminAccess && ['docker.featuresConfiguration', 'docker.registries', 'docker.registries.access', 'docker.swarm'].includes($ctrl.currentRouteName)"
|
||||
>
|
||||
<a ui-sref="docker.featuresConfiguration({endpointId: $ctrl.endpointId})" ui-sref-active="active">Setup</a>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="sidebar-sublist"
|
||||
ng-if="$ctrl.adminAccess && ['docker.registries', 'docker.registries.access', 'docker.featuresConfiguration', 'docker.swarm'].includes($ctrl.currentRouteName)"
|
||||
>
|
||||
<a ui-sref="docker.registries({endpointId: $ctrl.endpointId})" ui-sref-active="active">Registries</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="$ctrl.standaloneManagement">
|
||||
<a ui-sref="docker.host({endpointId: $ctrl.endpointId})" ui-sref-active="active">Host <span class="menu-icon fa fa-th fa-fw"></span></a>
|
||||
|
||||
<div class="sidebar-sublist" ng-if="$ctrl.adminAccess && ['docker.featuresConfiguration', 'docker.host'].includes($ctrl.currentRouteName)">
|
||||
<div
|
||||
class="sidebar-sublist"
|
||||
ng-if="$ctrl.adminAccess && ['docker.featuresConfiguration', 'docker.registries', 'docker.registries.access', 'docker.host'].includes($ctrl.currentRouteName)"
|
||||
>
|
||||
<a ui-sref="docker.featuresConfiguration({endpointId: $ctrl.endpointId})" ui-sref-active="active">Setup</a>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="sidebar-sublist"
|
||||
ng-if="$ctrl.adminAccess && ['docker.registries', 'docker.registries.access', 'docker.featuresConfiguration', 'docker.host'].includes($ctrl.currentRouteName)"
|
||||
>
|
||||
<a ui-sref="docker.registries({endpointId: $ctrl.endpointId})" ui-sref-active="active">Registries</a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
42
app/docker/views/registries/access/registryAccess.html
Normal file
42
app/docker/views/registries/access/registryAccess.html
Normal file
@@ -0,0 +1,42 @@
|
||||
<rd-header>
|
||||
<rd-header-title title-text="Registry access"></rd-header-title>
|
||||
<rd-header-content> <a ui-sref="portainer.registries">Registries</a> > {{ ctrl.registry.Name }} > Access management </rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row" ng-if="ctrl.registry">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-plug" title-text="Registry"></rd-widget-header>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>
|
||||
{{ ctrl.registry.Name }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>URL</td>
|
||||
<td>
|
||||
{{ ctrl.registry.URL }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- access-control-panel -->
|
||||
<por-access-control-panel
|
||||
ng-if="ctrl.registry"
|
||||
resource-id="ctrl.registry.ResourceControl.ResourceId"
|
||||
resource-control="ctrl.registry.ResourceControl"
|
||||
resource-type="'registry'"
|
||||
disable-ownership-change="false"
|
||||
>
|
||||
</por-access-control-panel>
|
||||
|
||||
<!-- !access-control-panel -->
|
||||
8
app/docker/views/registries/access/registryAccess.js
Normal file
8
app/docker/views/registries/access/registryAccess.js
Normal file
@@ -0,0 +1,8 @@
|
||||
angular.module('portainer.docker').component('dockerRegistryAccessView', {
|
||||
templateUrl: './registryAccess.html',
|
||||
controller: 'DockerRegistryAccessController',
|
||||
controllerAs: 'ctrl',
|
||||
bindings: {
|
||||
$transition$: '<',
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import { RegistryViewModel } from '../../../../portainer/models/registry';
|
||||
|
||||
class DockerRegistryAccessController {
|
||||
/* @ngInject */
|
||||
constructor($async, Notifications, EndpointProvider, RegistryService) {
|
||||
this.$async = $async;
|
||||
this.Notifications = Notifications;
|
||||
this.EndpointProvider = EndpointProvider;
|
||||
this.RegistryService = RegistryService;
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
return this.$async(async () => {
|
||||
try {
|
||||
this.state = {
|
||||
actionInProgress: false,
|
||||
};
|
||||
|
||||
const endpointId = this.EndpointProvider.currentEndpoint().Id;
|
||||
const registry = await this.RegistryService.registry(endpointId, this.$transition$.params().id);
|
||||
this.registry = new RegistryViewModel(registry);
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve registry details');
|
||||
} finally {
|
||||
this.state.viewReady = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default DockerRegistryAccessController;
|
||||
angular.module('portainer.docker').controller('DockerRegistryAccessController', DockerRegistryAccessController);
|
||||
@@ -247,6 +247,26 @@ angular.module('portainer.kubernetes', ['portainer.app']).config([
|
||||
},
|
||||
};
|
||||
|
||||
const registries = {
|
||||
name: 'kubernetes.registries',
|
||||
url: '/registries',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'endpointRegistriesView',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const registriesAccess = {
|
||||
name: 'kubernetes.registries.access',
|
||||
url: '/:id/access',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'kubernetesRegistryAccessView',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
$stateRegistryProvider.register(kubernetes);
|
||||
$stateRegistryProvider.register(applications);
|
||||
$stateRegistryProvider.register(applicationCreation);
|
||||
@@ -270,5 +290,7 @@ angular.module('portainer.kubernetes', ['portainer.app']).config([
|
||||
$stateRegistryProvider.register(resourcePoolAccess);
|
||||
$stateRegistryProvider.register(volumes);
|
||||
$stateRegistryProvider.register(volume);
|
||||
$stateRegistryProvider.register(registries);
|
||||
$stateRegistryProvider.register(registriesAccess);
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -15,7 +15,30 @@
|
||||
</li>
|
||||
<li class="sidebar-list">
|
||||
<a ui-sref="kubernetes.cluster({endpointId: $ctrl.endpointId})" ui-sref-active="active">Cluster <span class="menu-icon fa fa-server fa-fw"></span></a>
|
||||
<div class="sidebar-sublist" ng-if="$ctrl.adminAccess && ($ctrl.currentState === 'kubernetes.cluster' || $ctrl.currentState === 'portainer.endpoints.endpoint.kubernetesConfig')">
|
||||
|
||||
<div
|
||||
class="sidebar-sublist"
|
||||
ng-if="
|
||||
$ctrl.adminAccess &&
|
||||
($ctrl.currentState === 'kubernetes.cluster' ||
|
||||
$ctrl.currentState === 'portainer.endpoints.endpoint.kubernetesConfig' ||
|
||||
$ctrl.currentState === 'kubernetes.registries' ||
|
||||
$ctrl.currentState === 'kubernetes.registries.access')
|
||||
"
|
||||
>
|
||||
<a ui-sref="portainer.endpoints.endpoint.kubernetesConfig({id: $ctrl.endpointId})" ui-sref-active="active">Setup</a>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="sidebar-sublist"
|
||||
ng-if="
|
||||
$ctrl.adminAccess &&
|
||||
($ctrl.currentState === 'kubernetes.cluster' ||
|
||||
$ctrl.currentState === 'kubernetes.registries' ||
|
||||
$ctrl.currentState === 'kubernetes.registries.access' ||
|
||||
$ctrl.currentState === 'portainer.endpoints.endpoint.kubernetesConfig')
|
||||
"
|
||||
>
|
||||
<a ui-sref="kubernetes.registries({endpointId: $ctrl.endpointId})" ui-sref-active="active">Registries</a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
<rd-header>
|
||||
<rd-header-title title-text="Registry access"></rd-header-title>
|
||||
<rd-header-content>
|
||||
<a ui-sref="portainer.registries">Registries</a> > <a ui-sref="portainer.registries.registry({id: registry.Id})">{{ registry.Name }}</a> > Access management
|
||||
</rd-header-content>
|
||||
<rd-header-content> <a ui-sref="portainer.registries">Registries</a> > {{ ctrl.registry.Name }} > Access management </rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row" ng-if="registry">
|
||||
<div class="row" ng-if="ctrl.registry">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-plug" title-text="Registry"></rd-widget-header>
|
||||
@@ -15,13 +13,13 @@
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>
|
||||
{{ registry.Name }}
|
||||
{{ ctrl.registry.Name }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>URL</td>
|
||||
<td>
|
||||
{{ registry.URL }}
|
||||
{{ ctrl.registry.URL }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -31,5 +29,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<por-access-management ng-if="registry" access-controlled-entity="registry" entity-type="registry" action-in-progress="state.actionInProgress" update-access="updateAccess">
|
||||
<por-access-management
|
||||
ng-if="ctrl.registry"
|
||||
access-controlled-entity="registry"
|
||||
entity-type="registry"
|
||||
action-in-progress="ctrl.state.actionInProgress"
|
||||
update-access="ctrl.updateAccess"
|
||||
>
|
||||
</por-access-management>
|
||||
8
app/kubernetes/views/registries/access/registryAccess.js
Normal file
8
app/kubernetes/views/registries/access/registryAccess.js
Normal file
@@ -0,0 +1,8 @@
|
||||
angular.module('portainer.kubernetes').component('kubernetesRegistryAccessView', {
|
||||
templateUrl: './registryAccess.html',
|
||||
controller: 'KubernetesRegistryAccessController',
|
||||
controllerAs: 'ctrl',
|
||||
bindings: {
|
||||
$transition$: '<',
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import { RegistryViewModel } from '../../../../portainer/models/registry';
|
||||
|
||||
class KubernetesRegistryAccessController {
|
||||
/* @ngInject */
|
||||
constructor($async, Notifications, RegistryService) {
|
||||
this.$async = $async;
|
||||
this.Notifications = Notifications;
|
||||
this.RegistryService = RegistryService;
|
||||
}
|
||||
|
||||
updateAccess() {
|
||||
this.state.actionInProgress = true;
|
||||
this.RegistryService.updateRegistry(this.registry)
|
||||
.then(() => {
|
||||
this.Notifications.success('Access successfully updated');
|
||||
this.reload();
|
||||
})
|
||||
.catch((err) => {
|
||||
this.state.actionInProgress = false;
|
||||
this.Notifications.error('Failure', err, 'Unable to update accesses');
|
||||
});
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
return this.$async(async () => {
|
||||
try {
|
||||
this.state = {
|
||||
actionInProgress: false,
|
||||
};
|
||||
const registry = await this.RegistryService.registry(this.$transition$.params().id);
|
||||
this.registry = new RegistryViewModel(registry);
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve registry details');
|
||||
} finally {
|
||||
this.state.viewReady = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default KubernetesRegistryAccessController;
|
||||
angular.module('portainer.kubernetes').controller('KubernetesRegistryAccessController', KubernetesRegistryAccessController);
|
||||
@@ -304,17 +304,6 @@ angular.module('portainer.app', ['portainer.oauth']).config([
|
||||
},
|
||||
};
|
||||
|
||||
var registryAccess = {
|
||||
name: 'portainer.registries.registry.access',
|
||||
url: '/access',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/registries/access/registryAccess.html',
|
||||
controller: 'RegistryAccessController',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var settings = {
|
||||
name: 'portainer.settings',
|
||||
url: '/settings',
|
||||
@@ -423,7 +412,6 @@ angular.module('portainer.app', ['portainer.oauth']).config([
|
||||
$stateRegistryProvider.register(initAdmin);
|
||||
$stateRegistryProvider.register(registries);
|
||||
$stateRegistryProvider.register(registry);
|
||||
$stateRegistryProvider.register(registryAccess);
|
||||
$stateRegistryProvider.register(registryCreation);
|
||||
$stateRegistryProvider.register(settings);
|
||||
$stateRegistryProvider.register(settingsAuthentication);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<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>
|
||||
<div class="actionBar" ng-if="$ctrl.accessManagement">
|
||||
<div class="actionBar" ng-if="$ctrl.accessManagement && !$ctrl.endpointType">
|
||||
<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>
|
||||
@@ -27,7 +27,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<span class="md-checkbox" ng-if="$ctrl.accessManagement">
|
||||
<span class="md-checkbox" ng-if="$ctrl.accessManagement && !$ctrl.endpointType">
|
||||
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
|
||||
<label for="select_all"></label>
|
||||
</span>
|
||||
@@ -53,19 +53,22 @@
|
||||
ng-class="{ active: item.Checked }"
|
||||
>
|
||||
<td>
|
||||
<span class="md-checkbox" ng-if="$ctrl.accessManagement">
|
||||
<span class="md-checkbox" ng-if="$ctrl.accessManagement && !$ctrl.endpointType">
|
||||
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)" />
|
||||
<label for="select_{{ $index }}"></label>
|
||||
</span>
|
||||
<a ui-sref="portainer.registries.registry({id: item.Id})" ng-if="$ctrl.accessManagement">{{ item.Name }}</a>
|
||||
<span ng-if="!$ctrl.accessManagement">{{ item.Name }}</span>
|
||||
<a ui-sref="portainer.registries.registry({id: item.Id})" ng-if="$ctrl.accessManagement && !$ctrl.endpointType">{{ item.Name }}</a>
|
||||
<span ng-if="!$ctrl.accessManagement && $ctrl.endpointType">{{ item.Name }}</span>
|
||||
<span ng-if="$ctrl.accessManagement && $ctrl.endpointType">{{ item.Name }}</span>
|
||||
<span ng-if="item.Authentication" style="margin-left: 5px;" class="label label-info image-tag">authentication-enabled</span>
|
||||
</td>
|
||||
<td>
|
||||
{{ item.URL }}
|
||||
</td>
|
||||
<td>
|
||||
<a ui-sref="portainer.registries.registry.access({id: item.Id})" ng-if="$ctrl.accessManagement"> <i class="fa fa-users" aria-hidden="true"></i> Manage access </a>
|
||||
<a ng-click="$ctrl.redirectToManageAccess(item)" ng-if="$ctrl.accessManagement && $ctrl.endpointType">
|
||||
<i class="fa fa-users" aria-hidden="true"></i> Manage access
|
||||
</a>
|
||||
<span class="text-muted space-left" style="cursor: pointer;" data-toggle="tooltip" title="This feature is available in Portainer Business Edition">
|
||||
<i class="fa fa-search" aria-hidden="true"></i> Browse</span
|
||||
>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
angular.module('portainer.app').component('registriesDatatable', {
|
||||
templateUrl: './registriesDatatable.html',
|
||||
controller: 'GenericDatatableController',
|
||||
controller: 'RegistriesDatatableController',
|
||||
bindings: {
|
||||
titleText: '@',
|
||||
titleIcon: '@',
|
||||
@@ -11,5 +11,6 @@ angular.module('portainer.app').component('registriesDatatable', {
|
||||
accessManagement: '<',
|
||||
removeAction: '<',
|
||||
canBrowse: '<',
|
||||
endpointType: '<',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { PortainerEndpointTypes } from 'Portainer/models/endpoint/models';
|
||||
|
||||
angular.module('portainer.docker').controller('RegistriesDatatableController', [
|
||||
'$scope',
|
||||
'$controller',
|
||||
'$state',
|
||||
'DatatableService',
|
||||
function ($scope, $controller, $state, DatatableService) {
|
||||
angular.extend(this, $controller('GenericDatatableController', { $scope: $scope }));
|
||||
|
||||
this.allowSelection = function (item) {
|
||||
return item.Id;
|
||||
};
|
||||
|
||||
this.goToRegistry = function (item) {
|
||||
if (
|
||||
this.endpointType === PortainerEndpointTypes.KubernetesLocalEnvironment ||
|
||||
this.endpointType === PortainerEndpointTypes.AgentOnKubernetesEnvironment ||
|
||||
this.endpointType === PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment
|
||||
) {
|
||||
$state.go('kubernetes.registries.registry', { id: item.Id });
|
||||
} else if (
|
||||
this.endpointType === PortainerEndpointTypes.DockerEnvironment ||
|
||||
this.endpointType === PortainerEndpointTypes.AgentOnDockerEnvironment ||
|
||||
this.endpointType === PortainerEndpointTypes.EdgeAgentOnDockerEnvironment
|
||||
) {
|
||||
$state.go('docker.registries.registry', { id: item.Id });
|
||||
} else {
|
||||
$state.go('portainer.registries.registry', { id: item.Id });
|
||||
}
|
||||
};
|
||||
|
||||
this.redirectToManageAccess = function (item) {
|
||||
if (
|
||||
this.endpointType === PortainerEndpointTypes.KubernetesLocalEnvironment ||
|
||||
this.endpointType === PortainerEndpointTypes.AgentOnKubernetesEnvironment ||
|
||||
this.endpointType === PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment
|
||||
) {
|
||||
$state.go('kubernetes.registries.access', { id: item.Id });
|
||||
} else {
|
||||
$state.go('docker.registries.access', { id: item.Id });
|
||||
}
|
||||
};
|
||||
|
||||
this.$onInit = function () {
|
||||
this.setDefaults();
|
||||
this.prepareTableFromDataset();
|
||||
|
||||
this.state.orderBy = this.orderBy;
|
||||
var storedOrder = DatatableService.getDataTableOrder(this.tableKey);
|
||||
if (storedOrder !== null) {
|
||||
this.state.reverseOrder = storedOrder.reverse;
|
||||
this.state.orderBy = storedOrder.orderBy;
|
||||
}
|
||||
|
||||
var textFilter = DatatableService.getDataTableTextFilters(this.tableKey);
|
||||
if (textFilter !== null) {
|
||||
this.state.textFilter = textFilter;
|
||||
this.onTextFilterChange();
|
||||
}
|
||||
|
||||
var storedFilters = DatatableService.getDataTableFilters(this.tableKey);
|
||||
if (storedFilters !== null) {
|
||||
this.filters = storedFilters;
|
||||
}
|
||||
if (this.filters && this.filters.state) {
|
||||
this.filters.state.open = false;
|
||||
}
|
||||
|
||||
var storedSettings = DatatableService.getDataTableSettings(this.tableKey);
|
||||
if (storedSettings !== null) {
|
||||
this.settings = storedSettings;
|
||||
this.settings.open = false;
|
||||
}
|
||||
this.onSettingsRepeaterChange();
|
||||
|
||||
var storedColumnVisibility = DatatableService.getColumnVisibilitySettings(this.tableKey);
|
||||
if (storedColumnVisibility !== null) {
|
||||
this.columnVisibility = storedColumnVisibility;
|
||||
}
|
||||
};
|
||||
},
|
||||
]);
|
||||
@@ -13,6 +13,7 @@ export function RegistryViewModel(data) {
|
||||
this.AuthorizedTeams = data.AuthorizedTeams;
|
||||
this.UserAccessPolicies = data.UserAccessPolicies;
|
||||
this.TeamAccessPolicies = data.TeamAccessPolicies;
|
||||
this.ResourceControl = data.ResourceControl;
|
||||
this.Checked = false;
|
||||
this.Gitlab = data.Gitlab;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ export const ResourceControlTypeString = Object.freeze({
|
||||
STACK: 'stack',
|
||||
VOLUME: 'volume',
|
||||
CUSTOM_TEMPLATE: 'custom-template',
|
||||
REGISTRY: 'registry',
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -21,4 +22,5 @@ export const ResourceControlTypeInt = Object.freeze({
|
||||
STACK: 6,
|
||||
CONFIG: 7,
|
||||
CUSTOM_TEMPLATE: 8,
|
||||
REGISTRY: 9,
|
||||
});
|
||||
|
||||
@@ -8,8 +8,8 @@ angular.module('portainer.app').factory('Registries', [
|
||||
{},
|
||||
{
|
||||
create: { method: 'POST', ignoreLoadingBar: true },
|
||||
query: { method: 'GET', isArray: true },
|
||||
get: { method: 'GET', params: { id: '@id' } },
|
||||
query: { method: 'GET', params: { endpointId: '@endpointId' }, isArray: true },
|
||||
get: { method: 'GET', params: { id: '@id', action: '', endpointId: '@endpointId' } },
|
||||
update: { method: 'PUT', params: { id: '@id' } },
|
||||
updateAccess: { method: 'PUT', params: { id: '@id', action: 'access' } },
|
||||
remove: { method: 'DELETE', params: { id: '@id' } },
|
||||
|
||||
@@ -14,10 +14,10 @@ angular.module('portainer.app').factory('RegistryService', [
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.registries = function () {
|
||||
service.registries = function (endpointId) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Registries.query()
|
||||
Registries.query({ endpointId: endpointId })
|
||||
.$promise.then(function success(data) {
|
||||
var registries = data.map(function (item) {
|
||||
return new RegistryViewModel(item);
|
||||
@@ -31,10 +31,10 @@ angular.module('portainer.app').factory('RegistryService', [
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.registry = function (id) {
|
||||
service.registry = function (endpointId, id) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Registries.get({ id: id })
|
||||
Registries.get({ id: id, endpointId: endpointId })
|
||||
.$promise.then(function success(data) {
|
||||
var registry = new RegistryViewModel(data);
|
||||
deferred.resolve(registry);
|
||||
|
||||
23
app/portainer/views/endpoint-registries/registries.html
Normal file
23
app/portainer/views/endpoint-registries/registries.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<rd-header>
|
||||
<rd-header-title title-text="Environment registries">
|
||||
<a data-toggle="tooltip" title="Refresh" ui-sref="docker.registries" ui-sref-opts="{reload: true}">
|
||||
<i class="fa fa-sync" aria-hidden="true"></i>
|
||||
</a>
|
||||
</rd-header-title>
|
||||
<rd-header-content>Manage registry access inside this environment</rd-header-content>
|
||||
</rd-header>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<registries-datatable
|
||||
title-text="Registries"
|
||||
title-icon="fa-database"
|
||||
dataset="ctrl.registries"
|
||||
table-key="registries"
|
||||
order-by="Name"
|
||||
access-management="ctrl.isAdmin"
|
||||
remove-action="removeAction"
|
||||
can-browse="canBrowse"
|
||||
endpoint-type="ctrl.endpointType"
|
||||
></registries-datatable>
|
||||
</div>
|
||||
</div>
|
||||
8
app/portainer/views/endpoint-registries/registries.js
Normal file
8
app/portainer/views/endpoint-registries/registries.js
Normal file
@@ -0,0 +1,8 @@
|
||||
angular.module('portainer.app').component('endpointRegistriesView', {
|
||||
templateUrl: './registries.html',
|
||||
controller: 'EndpointRegistriesController',
|
||||
controllerAs: 'ctrl',
|
||||
bindings: {
|
||||
$transition$: '<',
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
class EndpointRegistriesController {
|
||||
/* @ngInject */
|
||||
constructor($async, Notifications, EndpointProvider, Authentication, RegistryService) {
|
||||
this.$async = $async;
|
||||
this.Notifications = Notifications;
|
||||
this.EndpointProvider = EndpointProvider;
|
||||
this.Authentication = Authentication;
|
||||
this.RegistryService = RegistryService;
|
||||
|
||||
this.getRegistriesAsync = this.getRegistriesAsync.bind(this);
|
||||
}
|
||||
|
||||
async getRegistriesAsync() {
|
||||
try {
|
||||
this.registries = await this.RegistryService.registries(this.endpointId);
|
||||
} catch (err) {
|
||||
this.Notifications.Error('Failure', err, 'Unable to retrieve registries');
|
||||
}
|
||||
}
|
||||
|
||||
getRegistries() {
|
||||
return this.$async(this.getRegistriesAsync);
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
return this.$async(async () => {
|
||||
this.state = {
|
||||
viewReady: false,
|
||||
};
|
||||
|
||||
try {
|
||||
const endpoint = this.EndpointProvider.currentEndpoint();
|
||||
this.endpointType = endpoint.Type;
|
||||
this.endpointId = endpoint.Id;
|
||||
await this.getRegistries();
|
||||
this.isAdmin = this.Authentication.isAdmin();
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve registries');
|
||||
} finally {
|
||||
this.state.viewReady = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default EndpointRegistriesController;
|
||||
angular.module('portainer.app').controller('EndpointRegistriesController', EndpointRegistriesController);
|
||||
@@ -1,34 +0,0 @@
|
||||
angular.module('portainer.app').controller('RegistryAccessController', [
|
||||
'$scope',
|
||||
'$state',
|
||||
'$transition$',
|
||||
'RegistryService',
|
||||
'Notifications',
|
||||
function ($scope, $state, $transition$, RegistryService, Notifications) {
|
||||
$scope.updateAccess = function () {
|
||||
$scope.state.actionInProgress = true;
|
||||
RegistryService.updateRegistry($scope.registry)
|
||||
.then(() => {
|
||||
Notifications.success('Access successfully updated');
|
||||
$state.reload();
|
||||
})
|
||||
.catch((err) => {
|
||||
$scope.state.actionInProgress = false;
|
||||
Notifications.error('Failure', err, 'Unable to update accesses');
|
||||
});
|
||||
};
|
||||
|
||||
function initView() {
|
||||
$scope.state = { actionInProgress: false };
|
||||
RegistryService.registry($transition$.params().id)
|
||||
.then(function success(data) {
|
||||
$scope.registry = data;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve registry details');
|
||||
});
|
||||
}
|
||||
|
||||
initView();
|
||||
},
|
||||
]);
|
||||
@@ -33,7 +33,7 @@ angular.module('portainer.app').controller('RegistryController', [
|
||||
|
||||
function initView() {
|
||||
var registryID = $transition$.params().id;
|
||||
RegistryService.registry(registryID)
|
||||
RegistryService.registry(0, registryID)
|
||||
.then(function success(data) {
|
||||
$scope.registry = data;
|
||||
})
|
||||
|
||||
@@ -7,15 +7,6 @@
|
||||
<rd-header-content>Registry management</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<information-panel title-text="Registry usage">
|
||||
<span class="small">
|
||||
<p class="text-muted">
|
||||
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
DockerHub credentials and registries can only be used with Docker endpoints at the time.
|
||||
</p>
|
||||
</span>
|
||||
</information-panel>
|
||||
|
||||
<div class="row" ng-if="dockerhub && isAdmin">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
|
||||
@@ -72,7 +72,7 @@ angular.module('portainer.app').controller('RegistriesController', [
|
||||
|
||||
function initView() {
|
||||
$q.all({
|
||||
registries: RegistryService.registries(),
|
||||
registries: RegistryService.registries(0),
|
||||
dockerhub: DockerHubService.dockerhub(),
|
||||
})
|
||||
.then(function success(data) {
|
||||
|
||||
Reference in New Issue
Block a user