mirror of
https://github.com/portainer/portainer.git
synced 2025-08-05 22:05:23 +02:00
feat(registries): add registry management (#930)
This commit is contained in:
parent
9360f24d89
commit
08c5a5a4f6
75 changed files with 2317 additions and 621 deletions
8
app/directives/accessManagement/por-access-management.js
Normal file
8
app/directives/accessManagement/por-access-management.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
angular.module('portainer').component('porAccessManagement', {
|
||||
templateUrl: 'app/directives/accessManagement/porAccessManagement.html',
|
||||
controller: 'porAccessManagementController',
|
||||
bindings: {
|
||||
accessControlledEntity: '<',
|
||||
updateAccess: '&'
|
||||
}
|
||||
});
|
134
app/directives/accessManagement/porAccessManagement.html
Normal file
134
app/directives/accessManagement/porAccessManagement.html
Normal file
|
@ -0,0 +1,134 @@
|
|||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<rd-widget>
|
||||
<rd-widget-header classes="col-sm-12 col-md-6 nopadding" icon="fa-users" title="Users and teams">
|
||||
<div class="pull-md-right pull-lg-right">
|
||||
Items per page:
|
||||
<select ng-model="$ctrl.state.pagination_count_accesses" ng-change="$ctrl.changePaginationCountAccesses()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-taskbar classes="col-sm-12 nopadding">
|
||||
<div class="col-sm-12 col-md-6 nopadding">
|
||||
<button class="btn btn-primary btn-sm" ng-click="$ctrl.authorizeAllAccesses()" ng-disabled="$ctrl.accesses.length === 0 || $ctrl.filteredUsers.length === 0"><i class="fa fa-user-plus space-right" aria-hidden="true"></i>Authorize all</button>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-6 nopadding">
|
||||
<input type="text" id="filter" ng-model="$ctrl.state.filterUsers" placeholder="Filter..." class="form-control input-sm" />
|
||||
</div>
|
||||
</rd-widget-taskbar>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a ng-click="$ctrl.orderAccesses('Name')">
|
||||
Name
|
||||
<span ng-show="$ctrl.state.sortAccessesBy == 'Name' && !$ctrl.state.sortAccessesReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="$ctrl.state.sortAccessesBy == 'Name' && $ctrl.state.sortAccessesReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.orderAccesses('Type')">
|
||||
Type
|
||||
<span ng-show="$ctrl.state.sortAccessesBy == 'Type' && !$ctrl.state.sortAccessesReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="$ctrl.state.sortAccessesBy == 'Type' && $ctrl.state.sortAccessesReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-click="$ctrl.authorizeAccess(user)" class="interactive" dir-paginate="user in $ctrl.accesses | filter:$ctrl.state.filterUsers | orderBy:$ctrl.state.sortAccessesBy:$ctrl.state.sortAccessesReverse | itemsPerPage: $ctrl.state.pagination_count_accesses">
|
||||
<td>{{ user.Name }}</td>
|
||||
<td>
|
||||
<i class="fa" ng-class="user.Type === 'user' ? 'fa-user' : 'fa-users'" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
{{ user.Type }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.accesses">
|
||||
<td colspan="2" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.accesses.length === 0 || ($ctrl.accesses | filter:$ctrl.state.filterUsers | orderBy:$ctrl.state.sortAccessesBy:$ctrl.state.sortAccessesReverse | itemsPerPage: $ctrl.state.pagination_count_accesses).length === 0">
|
||||
<td colspan="2" class="text-center text-muted">No user or team available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="$ctrl.accesses" class="pull-left pagination-controls">
|
||||
<dir-pagination-controls></dir-pagination-controls>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<rd-widget>
|
||||
<rd-widget-header classes="col-sm-12 col-md-6 nopadding" icon="fa-users" title="Authorized users and teams">
|
||||
<div class="pull-md-right pull-lg-right">
|
||||
Items per page:
|
||||
<select ng-model="$ctrl.state.pagination_count_authorizedAccesses" ng-change="$ctrl.changePaginationCountAuthorizedAccesses()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-taskbar classes="col-sm-12 nopadding">
|
||||
<div class="col-sm-12 col-md-6 nopadding">
|
||||
<button class="btn btn-primary btn-sm" ng-click="$ctrl.unauthorizeAllAccesses()" ng-disabled="$ctrl.authorizedAccesses.length === 0 || $ctrl.filteredAuthorizedUsers.length === 0"><i class="fa fa-user-times space-right" aria-hidden="true"></i>Deny all</button>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-6 nopadding">
|
||||
<input type="text" id="filter" ng-model="$ctrl.state.filterAuthorizedUsers" placeholder="Filter..." class="form-control input-sm" />
|
||||
</div>
|
||||
</rd-widget-taskbar>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a ng-click="$ctrl.orderAuthorizedAccesses('Name')">
|
||||
Name
|
||||
<span ng-show="$ctrl.state.sortAuthorizedAccessesBy == 'Name' && !$ctrl.state.sortAuthorizedAccessesReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="$ctrl.state.sortAuthorizedAccessesBy == 'Name' && $ctrl.state.sortAuthorizedAccessesReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.orderAuthorizedAccesses('Type')">
|
||||
Type
|
||||
<span ng-show="$ctrl.state.sortAuthorizedAccessesBy == 'Type' && !$ctrl.state.sortAuthorizedAccessesReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="$ctrl.state.sortAuthorizedAccessesBy == 'Type' && $ctrl.state.sortAuthorizedAccessesReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-click="$ctrl.unauthorizeAccess(user)" class="interactive" pagination-id="table_authaccess" dir-paginate="user in $ctrl.authorizedAccesses | filter:$ctrl.state.filterAuthorizedUsers | orderBy:$ctrl.state.sortAuthorizedAccessesBy:$ctrl.state.sortAuthorizedAccessesReverse | itemsPerPage: $ctrl.state.pagination_count_authorizedAccesses">
|
||||
<td>{{ user.Name }}</td>
|
||||
<td>
|
||||
<i class="fa" ng-class="user.Type === 'user' ? 'fa-user' : 'fa-users'" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
{{ user.Type }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.authorizedAccesses">
|
||||
<td colspan="2" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.authorizedAccesses.length === 0 || (authorizedAccesses | filter:state.filterAuthorizedUsers | orderBy:sortTypeAuthorizedAccesses:sortReverseAuthorizedAccesses | itemsPerPage: state.pagination_count_authorizedAccesses).length === 0">
|
||||
<td colspan="2" class="text-center text-muted">No authorized user or team.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="$ctrl.authorizedAccesses" class="pull-left pagination-controls">
|
||||
<dir-pagination-controls pagination-id="table_authaccess"></dir-pagination-controls>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
157
app/directives/accessManagement/porAccessManagementController.js
Normal file
157
app/directives/accessManagement/porAccessManagementController.js
Normal file
|
@ -0,0 +1,157 @@
|
|||
angular.module('portainer')
|
||||
.controller('porAccessManagementController', ['AccessService', 'Pagination', 'Notifications',
|
||||
function (AccessService, Pagination, Notifications) {
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.state = {
|
||||
pagination_count_accesses: Pagination.getPaginationCount('access_management_accesses'),
|
||||
pagination_count_authorizedAccesses: Pagination.getPaginationCount('access_management_AuthorizedAccesses'),
|
||||
sortAccessesBy: 'Type',
|
||||
sortAccessesReverse: false,
|
||||
sortAuthorizedAccessesBy: 'Type',
|
||||
sortAuthorizedAccessesReverse: false
|
||||
};
|
||||
|
||||
ctrl.orderAccesses = function(sortBy) {
|
||||
ctrl.state.sortAccessesReverse = (ctrl.state.sortAccessesBy === sortBy) ? !ctrl.state.sortAccessesReverse : false;
|
||||
ctrl.state.sortAccessesBy = sortBy;
|
||||
};
|
||||
|
||||
ctrl.orderAuthorizedAccesses = function(sortBy) {
|
||||
ctrl.state.sortAuthorizedAccessesReverse = (ctrl.state.sortAuthorizedAccessesBy === sortBy) ? !ctrl.state.sortAuthorizedAccessesReverse : false;
|
||||
ctrl.state.sortAuthorizedAccessesBy = sortBy;
|
||||
};
|
||||
|
||||
ctrl.changePaginationCountAuthorizedAccesses = function() {
|
||||
Pagination.setPaginationCount('access_management_AuthorizedAccesses', ctrl.state.pagination_count_authorizedAccesses);
|
||||
};
|
||||
|
||||
ctrl.changePaginationCountAccesses = function() {
|
||||
Pagination.setPaginationCount('access_management_accesses', ctrl.state.pagination_count_accesses);
|
||||
};
|
||||
|
||||
function dispatchUserAndTeamIDs(accesses, users, teams) {
|
||||
angular.forEach(accesses, function (access) {
|
||||
if (access.Type === 'user') {
|
||||
users.push(access.Id);
|
||||
} else if (access.Type === 'team') {
|
||||
teams.push(access.Id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function processAuthorizedIDs(accesses, authorizedAccesses) {
|
||||
var authorizedUserIDs = [];
|
||||
var authorizedTeamIDs = [];
|
||||
if (accesses) {
|
||||
dispatchUserAndTeamIDs(accesses, authorizedUserIDs, authorizedTeamIDs);
|
||||
}
|
||||
if (authorizedAccesses) {
|
||||
dispatchUserAndTeamIDs(authorizedAccesses, authorizedUserIDs, authorizedTeamIDs);
|
||||
}
|
||||
return {
|
||||
userIDs: authorizedUserIDs,
|
||||
teamIDs: authorizedTeamIDs
|
||||
};
|
||||
}
|
||||
|
||||
function removeFromAccesses(access, accesses) {
|
||||
_.remove(accesses, function(n) {
|
||||
return n.Id === access.Id;
|
||||
});
|
||||
}
|
||||
|
||||
function removeFromAccessIDs(accessId, accessIDs) {
|
||||
_.remove(accessIDs, function(n) {
|
||||
return n === accessId;
|
||||
});
|
||||
}
|
||||
|
||||
ctrl.authorizeAccess = function(access) {
|
||||
var accessData = processAuthorizedIDs(null, ctrl.authorizedAccesses);
|
||||
var authorizedUserIDs = accessData.userIDs;
|
||||
var authorizedTeamIDs = accessData.teamIDs;
|
||||
|
||||
if (access.Type === 'user') {
|
||||
authorizedUserIDs.push(access.Id);
|
||||
} else if (access.Type === 'team') {
|
||||
authorizedTeamIDs.push(access.Id);
|
||||
}
|
||||
|
||||
ctrl.updateAccess({ userAccesses: authorizedUserIDs, teamAccesses: authorizedTeamIDs })
|
||||
.then(function success(data) {
|
||||
removeFromAccesses(access, ctrl.accesses);
|
||||
ctrl.authorizedAccesses.push(access);
|
||||
Notifications.success('Accesses successfully updated');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to update accesses');
|
||||
});
|
||||
};
|
||||
|
||||
ctrl.unauthorizeAccess = function(access) {
|
||||
var accessData = processAuthorizedIDs(null, ctrl.authorizedAccesses);
|
||||
var authorizedUserIDs = accessData.userIDs;
|
||||
var authorizedTeamIDs = accessData.teamIDs;
|
||||
|
||||
if (access.Type === 'user') {
|
||||
removeFromAccessIDs(access.Id, authorizedUserIDs);
|
||||
} else if (access.Type === 'team') {
|
||||
removeFromAccessIDs(access.Id, authorizedTeamIDs);
|
||||
}
|
||||
|
||||
ctrl.updateAccess({ userAccesses: authorizedUserIDs, teamAccesses: authorizedTeamIDs })
|
||||
.then(function success(data) {
|
||||
removeFromAccesses(access, ctrl.authorizedAccesses);
|
||||
ctrl.accesses.push(access);
|
||||
Notifications.success('Accesses successfully updated');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to update accesses');
|
||||
});
|
||||
};
|
||||
|
||||
ctrl.unauthorizeAllAccesses = function() {
|
||||
ctrl.updateAccess({ userAccesses: [], teamAccesses: [] })
|
||||
.then(function success(data) {
|
||||
ctrl.accesses = ctrl.accesses.concat(ctrl.authorizedAccesses);
|
||||
ctrl.authorizedAccesses = [];
|
||||
Notifications.success('Accesses successfully updated');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to update accesses');
|
||||
});
|
||||
};
|
||||
|
||||
ctrl.authorizeAllAccesses = function() {
|
||||
var accessData = processAuthorizedIDs(ctrl.accesses, ctrl.authorizedAccesses);
|
||||
var authorizedUserIDs = accessData.userIDs;
|
||||
var authorizedTeamIDs = accessData.teamIDs;
|
||||
|
||||
ctrl.updateAccess({ userAccesses: authorizedUserIDs, teamAccesses: authorizedTeamIDs })
|
||||
.then(function success(data) {
|
||||
ctrl.authorizedAccesses = ctrl.authorizedAccesses.concat(ctrl.accesses);
|
||||
ctrl.accesses = [];
|
||||
Notifications.success('Accesses successfully updated');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to update accesses');
|
||||
});
|
||||
};
|
||||
|
||||
function initComponent() {
|
||||
var entity = ctrl.accessControlledEntity;
|
||||
AccessService.accesses(entity.AuthorizedUsers, entity.AuthorizedTeams)
|
||||
.then(function success(data) {
|
||||
ctrl.accesses = data.accesses;
|
||||
ctrl.authorizedAccesses = data.authorizedAccesses;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
ctrl.accesses = [];
|
||||
ctrl.authorizedAccesses = [];
|
||||
Notifications.error('Failure', err, 'Unable to retrieve accesses');
|
||||
});
|
||||
}
|
||||
|
||||
initComponent();
|
||||
}]);
|
8
app/directives/imageRegistry/por-image-registry.js
Normal file
8
app/directives/imageRegistry/por-image-registry.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
angular.module('portainer').component('porImageRegistry', {
|
||||
templateUrl: 'app/directives/imageRegistry/porImageRegistry.html',
|
||||
controller: 'porImageRegistryController',
|
||||
bindings: {
|
||||
'image': '=',
|
||||
'registry': '='
|
||||
}
|
||||
});
|
12
app/directives/imageRegistry/porImageRegistry.html
Normal file
12
app/directives/imageRegistry/porImageRegistry.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
<div>
|
||||
<label for="image_name" class="col-sm-1 control-label text-left">Name</label>
|
||||
<div class="col-sm-11 col-md-6">
|
||||
<input type="text" class="form-control" ng-model="$ctrl.image" id="image_name" placeholder="e.g. myImage:myTag">
|
||||
</div>
|
||||
<label for="image_registry" class="col-sm-2 col-md-1 margin-sm-top control-label text-left">
|
||||
Registry
|
||||
</label>
|
||||
<div class="col-sm-10 col-md-4 margin-sm-top">
|
||||
<select ng-options="registry as registry.Name for registry in $ctrl.availableRegistries" ng-model="$ctrl.registry" id="image_registry" class="form-control"></select>
|
||||
</div>
|
||||
</div>
|
23
app/directives/imageRegistry/porImageRegistryController.js
Normal file
23
app/directives/imageRegistry/porImageRegistryController.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
angular.module('portainer')
|
||||
.controller('porImageRegistryController', ['$q', 'RegistryService', 'DockerHubService', 'Notifications',
|
||||
function ($q, RegistryService, DockerHubService, Notifications) {
|
||||
var ctrl = this;
|
||||
|
||||
function initComponent() {
|
||||
$q.all({
|
||||
registries: RegistryService.registries(),
|
||||
dockerhub: DockerHubService.dockerhub()
|
||||
})
|
||||
.then(function success(data) {
|
||||
var dockerhub = data.dockerhub;
|
||||
var registries = data.registries;
|
||||
ctrl.availableRegistries = [dockerhub].concat(registries);
|
||||
ctrl.registry = dockerhub;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve registries');
|
||||
});
|
||||
}
|
||||
|
||||
initComponent();
|
||||
}]);
|
Loading…
Add table
Add a link
Reference in a new issue