mirror of
https://github.com/portainer/portainer.git
synced 2025-08-05 22:05:23 +02:00
feat(extensions): introduce RBAC extension (#2900)
This commit is contained in:
parent
27a0188949
commit
8057aa45c4
196 changed files with 3321 additions and 1316 deletions
19
app/extensions/rbac/__module.js
Normal file
19
app/extensions/rbac/__module.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
angular.module('portainer.extensions.rbac', ['ngResource'])
|
||||
.constant('API_ENDPOINT_ROLES', 'api/roles')
|
||||
.config(['$stateRegistryProvider', function ($stateRegistryProvider) {
|
||||
'use strict';
|
||||
|
||||
var roles = {
|
||||
name: 'portainer.roles',
|
||||
url: '/roles',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/roles/roles.html',
|
||||
controller: 'RolesController',
|
||||
controllerAs: 'ctrl'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$stateRegistryProvider.register(roles);
|
||||
}]);
|
|
@ -0,0 +1,44 @@
|
|||
<div class="col-sm-12" style="margin-bottom: 0px;">
|
||||
<rd-widget ng-if="ctrl.users">
|
||||
<rd-widget-header icon="fa-user-lock" title-text="Effective access viewer"></rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
User
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<span class="small text-muted" ng-if="ctrl.users.length === 0">
|
||||
No user available
|
||||
</span>
|
||||
<ui-select ng-if="ctrl.users.length > 0" ng-model="ctrl.selectedUser" ng-change="ctrl.onUserSelect()">
|
||||
<ui-select-match placeholder="Select a user">
|
||||
<span>{{ $select.selected.Username }}</span>
|
||||
</ui-select-match>
|
||||
<ui-select-choices repeat="item in (ctrl.users | filter: $select.search)">
|
||||
<span>{{ item.Username }}</span>
|
||||
</ui-select-choices>
|
||||
</ui-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Access
|
||||
</div>
|
||||
<div>
|
||||
<div class="small text-muted" style="margin-bottom: 15px;">
|
||||
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Effective role for each endpoint will be displayed for the selected user
|
||||
</div>
|
||||
</div>
|
||||
<access-viewer-datatable
|
||||
ng-if="ctrl.users"
|
||||
table-key="access_viewer"
|
||||
dataset="ctrl.userRoles"
|
||||
order-by="EndpointName"
|
||||
>
|
||||
</access-viewer-datatable>
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
|
@ -0,0 +1,5 @@
|
|||
angular.module('portainer.app').component('accessViewer', {
|
||||
templateUrl: './accessViewer.html',
|
||||
controller: 'AccessViewerController',
|
||||
controllerAs: 'ctrl'
|
||||
});
|
|
@ -0,0 +1,128 @@
|
|||
import _ from "lodash-es";
|
||||
import angular from "angular";
|
||||
|
||||
import AccessViewerPolicyModel from '../../models/access'
|
||||
|
||||
class AccessViewerController {
|
||||
/* @ngInject */
|
||||
constructor(Notifications, ExtensionService, RoleService, UserService, EndpointService, GroupService, TeamService, TeamMembershipService) {
|
||||
this.Notifications = Notifications;
|
||||
this.ExtensionService = ExtensionService;
|
||||
this.RoleService = RoleService;
|
||||
this.UserService = UserService;
|
||||
this.EndpointService = EndpointService;
|
||||
this.GroupService = GroupService;
|
||||
this.TeamService = TeamService;
|
||||
this.TeamMembershipService = TeamMembershipService;
|
||||
}
|
||||
|
||||
onUserSelect() {
|
||||
this.userRoles = [];
|
||||
const userRoles = {};
|
||||
const user = this.selectedUser;
|
||||
const userMemberships = _.filter(this.teamMemberships, {UserId: user.Id});
|
||||
|
||||
for (const [,endpoint] of _.entries(this.endpoints)) {
|
||||
let role = this.getRoleFromUserEndpointPolicy(user, endpoint);
|
||||
if (role) {
|
||||
userRoles[endpoint.Id] = role;
|
||||
continue;
|
||||
}
|
||||
|
||||
role = this.getRoleFromUserEndpointGroupPolicy(user, endpoint);
|
||||
if (role) {
|
||||
userRoles[endpoint.Id] = role;
|
||||
continue;
|
||||
}
|
||||
|
||||
role = this.getRoleFromTeamEndpointPolicies(userMemberships, endpoint);
|
||||
if (role) {
|
||||
userRoles[endpoint.Id] = role;
|
||||
continue;
|
||||
}
|
||||
|
||||
role = this.getRoleFromTeamEndpointGroupPolicies(userMemberships, endpoint);
|
||||
if (role) {
|
||||
userRoles[endpoint.Id] = role;
|
||||
}
|
||||
}
|
||||
|
||||
this.userRoles = _.values(userRoles);
|
||||
}
|
||||
|
||||
findLowestRole(policies) {
|
||||
return _.first(_.orderBy(policies, 'RoleId', 'desc'));
|
||||
}
|
||||
|
||||
getRoleFromUserEndpointPolicy(user, endpoint) {
|
||||
const policyRoles = [];
|
||||
const policy = endpoint.UserAccessPolicies[user.Id];
|
||||
if (policy) {
|
||||
const accessPolicy = new AccessViewerPolicyModel(policy, endpoint, this.roles, null, null);
|
||||
policyRoles.push(accessPolicy);
|
||||
}
|
||||
return this.findLowestRole(policyRoles);
|
||||
}
|
||||
|
||||
getRoleFromUserEndpointGroupPolicy(user, endpoint) {
|
||||
const policyRoles = [];
|
||||
const policy = this.groupUserAccessPolicies[endpoint.GroupId][user.Id];
|
||||
if (policy) {
|
||||
const accessPolicy = new AccessViewerPolicyModel(policy, endpoint, this.roles, this.groups[endpoint.GroupId], null);
|
||||
policyRoles.push(accessPolicy);
|
||||
}
|
||||
return this.findLowestRole(policyRoles);
|
||||
}
|
||||
|
||||
getRoleFromTeamEndpointPolicies(memberships, endpoint) {
|
||||
const policyRoles = [];
|
||||
for (const membership of memberships) {
|
||||
const policy = endpoint.TeamAccessPolicies[membership.TeamId];
|
||||
if (policy) {
|
||||
const accessPolicy = new AccessViewerPolicyModel(policy, endpoint, this.roles, null, this.teams[membership.TeamId]);
|
||||
policyRoles.push(accessPolicy);
|
||||
}
|
||||
}
|
||||
return this.findLowestRole(policyRoles);
|
||||
}
|
||||
|
||||
getRoleFromTeamEndpointGroupPolicies(memberships, endpoint) {
|
||||
const policyRoles = [];
|
||||
for (const membership of memberships) {
|
||||
const policy = this.groupTeamAccessPolicies[endpoint.GroupId][membership.TeamId]
|
||||
if (policy) {
|
||||
const accessPolicy = new AccessViewerPolicyModel(policy, endpoint, this.roles, this.groups[endpoint.GroupId], this.teams[membership.TeamId]);
|
||||
policyRoles.push(accessPolicy);
|
||||
}
|
||||
}
|
||||
return this.findLowestRole(policyRoles);
|
||||
}
|
||||
|
||||
async $onInit() {
|
||||
try {
|
||||
this.rbacEnabled = await this.ExtensionService.extensionEnabled(this.ExtensionService.EXTENSIONS.RBAC);
|
||||
if (this.rbacEnabled) {
|
||||
this.users = await this.UserService.users();
|
||||
this.endpoints = _.keyBy(await this.EndpointService.endpoints(), 'Id');
|
||||
const groups = await this.GroupService.groups();
|
||||
this.groupUserAccessPolicies = {};
|
||||
this.groupTeamAccessPolicies = {};
|
||||
_.forEach(groups, group => {
|
||||
this.groupUserAccessPolicies[group.Id] = group.UserAccessPolicies;
|
||||
this.groupTeamAccessPolicies[group.Id] = group.TeamAccessPolicies;
|
||||
});
|
||||
this.groups = _.keyBy(groups, 'Id');
|
||||
this.roles = _.keyBy(await this.RoleService.roles(), 'Id');
|
||||
this.teams = _.keyBy(await this.TeamService.teams(), 'Id');
|
||||
this.teamMemberships = await this.TeamMembershipService.memberships();
|
||||
}
|
||||
} catch (err) {
|
||||
this.Notifications.error("Failure", err, "Unable to retrieve accesses");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default AccessViewerController;
|
||||
angular
|
||||
.module("portainer.app")
|
||||
.controller("AccessViewerController", AccessViewerController);
|
|
@ -0,0 +1,74 @@
|
|||
<div class="datatable">
|
||||
<div class="searchBar">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" ng-change="$ctrl.onTextFilterChange()"
|
||||
placeholder="Search...">
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover nowrap-cells">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('EndpointName')">
|
||||
Endpoint
|
||||
<i class="fa fa-sort-alpha-down" aria-hidden="true"
|
||||
ng-if="$ctrl.state.orderBy === 'EndpointName' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-up" aria-hidden="true"
|
||||
ng-if="$ctrl.state.orderBy === 'EndpointName' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('RoleName')">
|
||||
Role
|
||||
<i class="fa fa-sort-alpha-down" aria-hidden="true"
|
||||
ng-if="$ctrl.state.orderBy === 'RoleName' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-up" aria-hidden="true"
|
||||
ng-if="$ctrl.state.orderBy === 'RoleName' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>Access origin</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit)) track by $index">
|
||||
<td>{{ item.EndpointName }}</td>
|
||||
<td>{{ item.RoleName }}</td>
|
||||
<td>{{ item.TeamName ? 'Team' : 'User'}} <code ng-if="item.TeamName">{{ item.TeamName }}</code> access defined on {{ item.AccessLocation }} <code ng-if="item.GroupName">{{item.GroupName}}</code>
|
||||
<a ng-if="item.AccessLocation === 'endpoint'" ui-sref="portainer.endpoints.endpoint.access({id: item.EndpointId})"><i style="margin-left: 5px;" class="fa fa-users" aria-hidden="true"></i> Manage access </a>
|
||||
<a ng-if="item.AccessLocation === 'endpoint group'" ui-sref="portainer.groups.group.access({id: item.GroupId})"><i style="margin-left: 5px;" class="fa fa-users" aria-hidden="true"></i> Manage access </a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
<td colspan="3" class="text-center text-muted">Select a user to show associated access and role</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
|
||||
<td colspan="3" class="text-center text-muted">The selected user do not have access to any endpoint</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<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="paginationControls">
|
||||
<form class="form-inline">
|
||||
<span class="limitSelector">
|
||||
<span style="margin-right: 5px;">
|
||||
Items per page
|
||||
</span>
|
||||
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit"
|
||||
ng-change="$ctrl.changePaginationLimit()">
|
||||
<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>
|
||||
</span>
|
||||
<dir-pagination-controls max-size="5"></dir-pagination-controls>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,11 @@
|
|||
angular.module('portainer.app').component('accessViewerDatatable', {
|
||||
templateUrl: './accessViewerDatatable.html',
|
||||
controller: 'GenericDatatableController',
|
||||
bindings: {
|
||||
titleText: '@',
|
||||
titleIcon: '@',
|
||||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
dataset: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,71 @@
|
|||
<div class="datatable" ng-class="{'portainer-disabled-datatable': !$ctrl.rbacEnabled}">
|
||||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<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="searchBar">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" ng-change="$ctrl.onTextFilterChange()" placeholder="Search..." auto-focus ng-disabled="!$ctrl.rbacEnabled">
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover nowrap-cells">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Name')">
|
||||
Name
|
||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Description')">
|
||||
Description
|
||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Description' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Description' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
|
||||
<td>{{ item.Name }}</td>
|
||||
<td>{{ item.Description }}</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
<td colspan="2" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
|
||||
<td colspan="2" class="text-center text-muted">No role available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<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="paginationControls">
|
||||
<form class="form-inline">
|
||||
<span class="limitSelector">
|
||||
<span style="margin-right: 5px;">
|
||||
Items per page
|
||||
</span>
|
||||
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()">
|
||||
<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>
|
||||
</span>
|
||||
<dir-pagination-controls max-size="5"></dir-pagination-controls>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
|
@ -0,0 +1,13 @@
|
|||
angular.module('portainer.extensions.rbac').component('rolesDatatable', {
|
||||
templateUrl: './rolesDatatable.html',
|
||||
controller: 'GenericDatatableController',
|
||||
bindings: {
|
||||
titleText: '@',
|
||||
titleIcon: '@',
|
||||
dataset: '<',
|
||||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
reverseOrder: '<',
|
||||
rbacEnabled: '<'
|
||||
}
|
||||
});
|
33
app/extensions/rbac/directives/authorization.js
Normal file
33
app/extensions/rbac/directives/authorization.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
angular.module('portainer.extensions.rbac').directive('authorization', ['Authentication', 'ExtensionService',
|
||||
function(Authentication, ExtensionService) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: async function(scope, elem, attrs) {
|
||||
elem.hide();
|
||||
try {
|
||||
const rbacEnabled = await ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC);
|
||||
if (!rbacEnabled) {
|
||||
elem.show();
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
elem.show();
|
||||
return;
|
||||
}
|
||||
|
||||
var authorizations = attrs.authorization.split(",");
|
||||
for (var i = 0; i < authorizations.length; i++) {
|
||||
authorizations[i] = authorizations[i].trim();
|
||||
}
|
||||
|
||||
var hasAuthorizations = Authentication.hasAuthorizations(authorizations);
|
||||
|
||||
if (hasAuthorizations) {
|
||||
elem.show();
|
||||
} else if (!hasAuthorizations && elem[0].tagName === 'A') {
|
||||
elem.show();
|
||||
elem.addClass('portainer-disabled-link');
|
||||
}
|
||||
}
|
||||
}
|
||||
}]);
|
27
app/extensions/rbac/directives/disable-authorization.js
Normal file
27
app/extensions/rbac/directives/disable-authorization.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
angular.module('portainer.extensions.rbac')
|
||||
.directive('disableAuthorization', ['Authentication', 'ExtensionService', function(Authentication, ExtensionService) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: async function (scope, elem, attrs) {
|
||||
try {
|
||||
const rbacEnabled = await ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC);
|
||||
if (!rbacEnabled) {
|
||||
elem.show();
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
elem.show();
|
||||
return;
|
||||
}
|
||||
|
||||
var authorizations = attrs.disableAuthorization.split(",");
|
||||
for (var i = 0; i < authorizations.length; i++) {
|
||||
authorizations[i] = authorizations[i].trim();
|
||||
}
|
||||
|
||||
if (!Authentication.hasAuthorizations(authorizations)) {
|
||||
elem.attr('disabled', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}]);
|
15
app/extensions/rbac/models/access.js
Normal file
15
app/extensions/rbac/models/access.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
export default function AccessViewerPolicyModel(policy, endpoint, roles, group, team) {
|
||||
this.EndpointId = endpoint.Id;
|
||||
this.EndpointName = endpoint.Name;
|
||||
this.RoleId = policy.RoleId;
|
||||
this.RoleName = roles[policy.RoleId].Name;
|
||||
if (group) {
|
||||
this.GroupId = group.Id;
|
||||
this.GroupName = group.Name
|
||||
}
|
||||
if (team) {
|
||||
this.TeamId = team.Id;
|
||||
this.TeamName = team.Name;
|
||||
}
|
||||
this.AccessLocation = group ? 'endpoint group' : 'endpoint';
|
||||
}
|
6
app/extensions/rbac/models/role.js
Normal file
6
app/extensions/rbac/models/role.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
export function RoleViewModel(data) {
|
||||
this.ID = data.Id;
|
||||
this.Name = data.Name;
|
||||
this.Description = data.Description;
|
||||
this.Authorizations = data.Authorizations;
|
||||
}
|
11
app/extensions/rbac/rest/role.js
Normal file
11
app/extensions/rbac/rest/role.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
angular.module('portainer.app')
|
||||
.factory('Roles', ['$resource', 'API_ENDPOINT_ROLES', function RolesFactory($resource, API_ENDPOINT_ROLES) {
|
||||
'use strict';
|
||||
return $resource(API_ENDPOINT_ROLES + '/:id', {}, {
|
||||
create: { method: 'POST', ignoreLoadingBar: true },
|
||||
query: { method: 'GET', isArray: true },
|
||||
get: { method: 'GET', params: { id: '@id' } },
|
||||
update: { method: 'PUT', params: { id: '@id' } },
|
||||
remove: { method: 'DELETE', params: { id: '@id'} }
|
||||
});
|
||||
}]);
|
47
app/extensions/rbac/services/roleService.js
Normal file
47
app/extensions/rbac/services/roleService.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
import {
|
||||
RoleViewModel,
|
||||
// EndpointRoleCreateRequest,
|
||||
// EndpointRoleUpdateRequest
|
||||
} from '../models/role';
|
||||
|
||||
angular.module('portainer.extensions.rbac')
|
||||
.factory('RoleService', ['$q', 'Roles',
|
||||
function RoleService($q, Roles) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.role = function(roleId) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Roles.get({ id: roleId }).$promise
|
||||
.then(function success(data) {
|
||||
var role = new RoleViewModel(data);
|
||||
deferred.resolve(role);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve role', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.roles = function() {
|
||||
return Roles.query({}).$promise;
|
||||
};
|
||||
|
||||
// service.createRole = function(model, endpoints) {
|
||||
// var payload = new EndpointRoleCreateRequest(model, endpoints);
|
||||
// return EndpointRoles.create(payload).$promise;
|
||||
// };
|
||||
//
|
||||
// service.updateRole = function(model, endpoints) {
|
||||
// var payload = new EndpointRoleUpdateRequest(model, endpoints);
|
||||
// return EndpointRoles.update(payload).$promise;
|
||||
// };
|
||||
|
||||
service.deleteRole = function(roleId) {
|
||||
return Roles.remove({ id: roleId }).$promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
36
app/extensions/rbac/views/roles/roles.html
Normal file
36
app/extensions/rbac/views/roles/roles.html
Normal file
|
@ -0,0 +1,36 @@
|
|||
<rd-header>
|
||||
<rd-header-title title-text="Roles">
|
||||
<a data-toggle="tooltip" title="Refresh" ui-sref="portainer.roles" ui-sref-opts="{reload: true}">
|
||||
<i class="fa fa-sync" aria-hidden="true"></i>
|
||||
</a>
|
||||
</rd-header-title>
|
||||
<rd-header-content>Role management</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<information-panel ng-if="!ctrl.rbacEnabled" title-text="Information">
|
||||
<span class="small">
|
||||
<p class="text-muted">
|
||||
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
The <a ui-sref="portainer.extensions.extension({id: 3})"
|
||||
tooltip-append-to-body="true" tooltip-placement="bottom" tooltip-class="portainer-tooltip"
|
||||
uib-tooltip="Feature available via an extension">RBAC extension</a>
|
||||
is required to use this feature.
|
||||
</p>
|
||||
</span>
|
||||
</information-panel>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<roles-datatable
|
||||
title-text="Roles" title-icon="fa-file-code"
|
||||
dataset="ctrl.roles" table-key="roles"
|
||||
order-by="Name"
|
||||
rbac-enabled="ctrl.rbacEnabled"
|
||||
></roles-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<access-viewer ng-if="ctrl.rbacEnabled">
|
||||
</access-viewer>
|
||||
</div>
|
25
app/extensions/rbac/views/roles/rolesController.js
Normal file
25
app/extensions/rbac/views/roles/rolesController.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
import angular from 'angular';
|
||||
|
||||
class RolesController {
|
||||
|
||||
/* @ngInject */
|
||||
constructor(Notifications, RoleService, ExtensionService) {
|
||||
this.Notifications = Notifications;
|
||||
this.RoleService = RoleService;
|
||||
this.ExtensionService = ExtensionService;
|
||||
}
|
||||
|
||||
async $onInit() {
|
||||
this.roles = [];
|
||||
this.rbacEnabled = false;
|
||||
|
||||
try {
|
||||
this.rbacEnabled = await this.ExtensionService.extensionEnabled(this.ExtensionService.EXTENSIONS.RBAC);
|
||||
this.roles = await this.RoleService.roles();
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve roles');
|
||||
}
|
||||
}
|
||||
}
|
||||
export default RolesController;
|
||||
angular.module('portainer.extensions.rbac').controller('RolesController', RolesController);
|
Loading…
Add table
Add a link
Reference in a new issue