Compare commits

...

5 Commits

18 changed files with 395 additions and 63 deletions

View File

@@ -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);
},
]);

View File

@@ -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>

View File

@@ -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> &gt; <a ui-sref="portainer.registries.registry({id: registry.Id})">{{ registry.Name }}</a> &gt; Access management
</rd-header-content>
<rd-header-content> <a ui-sref="portainer.registries">Registries</a> &gt; {{ ctrl.registry.Name }} &gt; 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>

View File

@@ -0,0 +1,8 @@
angular.module('portainer.docker').component('dockerRegistryAccessView', {
templateUrl: './registryAccess.html',
controller: 'DockerRegistryAccessController',
controllerAs: 'ctrl',
bindings: {
$transition$: '<',
},
});

View File

@@ -0,0 +1,42 @@
import { RegistryViewModel } from '../../../../portainer/models/registry';
class DockerRegistryAccessController {
/* @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 DockerRegistryAccessController;
angular.module('portainer.docker').controller('DockerRegistryAccessController', DockerRegistryAccessController);

View File

@@ -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);
},
]);

View File

@@ -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>

View File

@@ -0,0 +1,39 @@
<rd-header>
<rd-header-title title-text="Registry access"></rd-header-title>
<rd-header-content> <a ui-sref="portainer.registries">Registries</a> &gt; {{ ctrl.registry.Name }} &gt; 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>
<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>

View File

@@ -0,0 +1,8 @@
angular.module('portainer.kubernetes').component('kubernetesRegistryAccessView', {
templateUrl: './registryAccess.html',
controller: 'KubernetesRegistryAccessController',
controllerAs: 'ctrl',
bindings: {
$transition$: '<',
},
});

View File

@@ -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);

View File

@@ -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);

View File

@@ -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
>

View File

@@ -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: '<',
},
});

View File

@@ -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;
}
};
},
]);

View 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>

View File

@@ -0,0 +1,8 @@
angular.module('portainer.app').component('endpointRegistriesView', {
templateUrl: './registries.html',
controller: 'EndpointRegistriesController',
controllerAs: 'ctrl',
bindings: {
$transition$: '<',
},
});

View File

@@ -0,0 +1,30 @@
class EndpointRegistriesController {
/* @ngInject */
constructor($async, Notifications, EndpointProvider, Authentication) {
this.$async = $async;
this.Notifications = Notifications;
this.EndpointProvider = EndpointProvider;
this.Authentication = Authentication;
}
$onInit() {
return this.$async(async () => {
this.state = {
viewReady: false,
};
try {
// get registries
this.endpointType = this.EndpointProvider.currentEndpoint().Type;
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);

View File

@@ -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();
},
]);