1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-09 07:45:22 +02:00

feat(teamleader) EE-294 redesign team leader (#6973)

feat(teamleader) EE-294 redesign team leader (#6973)
This commit is contained in:
congs 2022-06-03 16:44:42 +12:00 committed by GitHub
parent bca1c6b9cf
commit 0522032515
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 223 additions and 135 deletions

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">
<div class="actionBar" ng-show="$ctrl.isAdmin">
<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>
@ -25,7 +25,7 @@
<thead>
<tr>
<th>
<span class="md-checkbox">
<span class="md-checkbox" ng-show="$ctrl.isAdmin">
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
<label for="select_all"></label>
</span>
@ -43,7 +43,7 @@
ng-class="{ active: item.Checked }"
>
<td>
<span class="md-checkbox">
<span class="md-checkbox" ng-show="$ctrl.isAdmin">
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)" />
<label for="select_{{ $index }}"></label>
</span>

View file

@ -9,5 +9,6 @@ angular.module('portainer.app').component('teamsDatatable', {
orderBy: '@',
reverseOrder: '<',
removeAction: '<',
isAdmin: '<',
},
});

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">
<div class="actionBar" ng-show="$ctrl.isAdmin">
<button
type="button"
class="btn btn-sm btn-danger"
@ -31,7 +31,7 @@
<thead>
<tr>
<th>
<span class="md-checkbox">
<span class="md-checkbox" ng-show="$ctrl.isAdmin">
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
<label for="select_all"></label>
</span>
@ -63,11 +63,12 @@
ng-class="{ active: item.Checked }"
>
<td>
<span class="md-checkbox">
<span class="md-checkbox" ng-show="$ctrl.isAdmin">
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)" ng-disabled="!$ctrl.allowSelection(item)" />
<label for="select_{{ $index }}"></label>
</span>
<a ui-sref="portainer.users.user({id: item.Id})">{{ item.Username }}</a>
<a ui-sref="portainer.users.user({id: item.Id})" ng-show="$ctrl.isAdmin">{{ item.Username }}</a>
<span ng-show="!$ctrl.isAdmin">{{ item.Username }}</span>
</td>
<td>
<span>

View file

@ -13,5 +13,6 @@ angular.module('portainer.app').component('usersDatatable', {
reverseOrder: '<',
removeAction: '<',
authenticationMethod: '<',
isAdmin: '<',
},
});

View file

@ -24,6 +24,7 @@ export function SettingsViewModel(data) {
export function PublicSettingsViewModel(settings) {
this.AuthenticationMethod = settings.AuthenticationMethod;
this.TeamSync = settings.TeamSync;
this.RequiredPasswordLength = settings.RequiredPasswordLength;
this.EnableEdgeComputeFeatures = settings.EnableEdgeComputeFeatures;
this.EnforceEdgeID = settings.EnforceEdgeID;

View file

@ -33,10 +33,10 @@
<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.GroupName" ui-sref="portainer.endpoints.endpoint.access({id: item.EndpointId})"
<a ng-if="!item.GroupName && $ctrl.isAdmin" 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.GroupName" ui-sref="portainer.groups.group.access({id: item.GroupId})"
<a ng-if="item.GroupName && $ctrl.isAdmin" 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>

View file

@ -7,5 +7,6 @@ export const accessViewerDatatable = {
tableKey: '@',
orderBy: '@',
dataset: '<',
isAdmin: '<',
},
};

View file

@ -5,7 +5,7 @@ import AccessViewerPolicyModel from '../../models/access';
export default class AccessViewerController {
/* @ngInject */
constructor(Notifications, RoleService, UserService, EndpointService, GroupService, TeamService, TeamMembershipService) {
constructor(Notifications, RoleService, UserService, EndpointService, GroupService, TeamService, TeamMembershipService, Authentication) {
this.Notifications = Notifications;
this.RoleService = RoleService;
this.UserService = UserService;
@ -13,6 +13,7 @@ export default class AccessViewerController {
this.GroupService = GroupService;
this.TeamService = TeamService;
this.TeamMembershipService = TeamMembershipService;
this.Authentication = Authentication;
this.limitedFeature = 'rbac-roles';
this.users = [];
@ -100,6 +101,33 @@ export default class AccessViewerController {
return this.findLowestRole(policyRoles);
}
// for admin, returns all users
// for team leader, only return all his/her team member users
async teamMemberUsers(users, teamMemberships) {
if (this.isAdmin) {
return users;
}
const filteredUsers = [];
const userId = this.Authentication.getUserDetails().ID;
const leadingTeams = await this.UserService.userLeadingTeams(userId);
const isMember = (userId, teamId) => {
return !!_.find(teamMemberships, { UserId: userId, TeamId: teamId });
};
for (const user of users) {
for (const leadingTeam of leadingTeams) {
if (isMember(user.Id, leadingTeam.Id)) {
filteredUsers.push(user);
break;
}
}
}
return filteredUsers;
}
async $onInit() {
try {
const limitedToBE = isLimitedToBE(this.limitedFeature);
@ -108,7 +136,8 @@ export default class AccessViewerController {
return;
}
this.users = await this.UserService.users();
this.isAdmin = this.Authentication.isAdmin();
this.allUsers = await this.UserService.users();
this.endpoints = _.keyBy((await this.EndpointService.endpoints()).value, 'Id');
const groups = await this.GroupService.groups();
this.groupUserAccessPolicies = {};
@ -121,6 +150,7 @@ export default class AccessViewerController {
this.roles = _.keyBy(await this.RoleService.roles(), 'Id');
this.teams = _.keyBy(await this.TeamService.teams(), 'Id');
this.teamMemberships = await this.TeamMembershipService.memberships();
this.users = await this.teamMemberUsers(this.allUsers, this.teamMemberships);
} catch (err) {
this.Notifications.error('Failure', err, 'Unable to retrieve accesses');
}

View file

@ -30,7 +30,7 @@
Effective role for each environment will be displayed for the selected user
</div>
</div>
<access-viewer-datatable table-key="access_viewer" dataset="$ctrl.userRoles" order-by="EndpointName"> </access-viewer-datatable>
<access-viewer-datatable table-key="access_viewer" dataset="$ctrl.userRoles" order-by="EndpointName" is-admin="$ctrl.isAdmin"> </access-viewer-datatable>
</form>
</rd-widget-body>
</rd-widget>

View file

@ -4,8 +4,7 @@ import { TeamMembershipModel } from '../../models/teamMembership';
angular.module('portainer.app').factory('TeamService', [
'$q',
'Teams',
'TeamMembershipService',
function TeamServiceFactory($q, Teams, TeamMembershipService) {
function TeamServiceFactory($q, Teams) {
'use strict';
var service = {};
@ -41,17 +40,11 @@ angular.module('portainer.app').factory('TeamService', [
var deferred = $q.defer();
var payload = {
Name: name,
TeamLeaders: leaderIds,
};
Teams.create({}, payload)
.$promise.then(function success(data) {
var teamId = data.Id;
var teamMembershipQueries = [];
angular.forEach(leaderIds, function (userId) {
teamMembershipQueries.push(TeamMembershipService.createMembership(userId, teamId, 1));
});
$q.all(teamMembershipQueries).then(function success() {
deferred.resolve();
});
.$promise.then(function success() {
deferred.resolve();
})
.catch(function error(err) {
deferred.reject({ msg: 'Unable to create team', err: err });

View file

@ -54,7 +54,7 @@
>
</sidebar-section>
<sidebar-section ng-if="isAdmin || isTeamLeader" title="Settings">
<sidebar-section ng-if="showUsersSection" title="Settings">
<sidebar-menu
ng-show="display"
icon-class="fa-users fa-fw"

View file

@ -1,6 +1,6 @@
angular.module('portainer.app').controller('SidebarController', SidebarController);
function SidebarController($rootScope, $scope, $transitions, StateManager, Notifications, Authentication, UserService, EndpointProvider) {
function SidebarController($rootScope, $scope, $transitions, StateManager, Notifications, Authentication, UserService, EndpointProvider, SettingsService) {
$scope.applicationState = StateManager.getState();
$scope.endpointState = EndpointProvider.endpoint();
$scope.display = !window.ddExtension;
@ -29,11 +29,14 @@ function SidebarController($rootScope, $scope, $transitions, StateManager, Notif
const userDetails = Authentication.getUserDetails();
const isAdmin = isClusterAdmin();
$scope.isAdmin = isAdmin;
$scope.showUsersSection = isAdmin;
if (!isAdmin) {
try {
const memberships = await UserService.userMemberships(userDetails.ID);
checkPermissions(memberships);
const settings = await SettingsService.publicSettings();
$scope.showUsersSection = $scope.isTeamLeader && !settings.TeamSync;
} catch (err) {
Notifications.error('Failure', err, 'Unable to retrieve user memberships');
}

View file

@ -36,6 +36,13 @@
</div>
</div>
<div class="row" ng-if="team && settings.TeamSync">
<div class="col-sm-12 text-muted">
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px"></i>
The team leader feature is disabled as external authentication is currently enabled with team sync.
</div>
</div>
<div class="row" ng-if="team">
<div class="col-sm-6">
<rd-widget>
@ -53,7 +60,7 @@
</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="addAllUsers()" ng-if="isAdmin" ng-disabled="users.length === 0 || filteredUsers.length === 0"
<button class="btn btn-primary btn-sm" ng-click="addAllUsers()" ng-if="isAdmin" ng-disabled="users.length === 0 || filteredUsers.length === 0 || settings.TeamSync"
><i class="fa fa-user-plus space-right" aria-hidden="true"></i>Add all users</button
>
</div>
@ -83,7 +90,7 @@
<td>
{{ user.Username }}
<span style="margin-left: 5px">
<a ng-click="addUser(user)"><i class="fa fa-plus-circle space-right" aria-hidden="true"></i>Add</a>
<a ng-click="addUser(user)" ng-class="{ 'btn disabled py-0': settings.TeamSync }"> <i class="fa fa-plus-circle space-right" aria-hidden="true"></i>Add </a>
</span>
</td>
</tr>
@ -118,7 +125,11 @@
</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="removeAllUsers()" ng-if="isAdmin" ng-disabled="teamMembers.length === 0 || filteredGroupMembers.length === 0"
<button
class="btn btn-primary btn-sm"
ng-click="removeAllUsers()"
ng-if="isAdmin"
ng-disabled="teamMembers.length === 0 || filteredGroupMembers.length === 0 || settings.TeamSync"
><i class="fa fa-user-times space-right" aria-hidden="true"></i>Remove all users</button
>
</div>
@ -155,7 +166,9 @@
<td>
{{ user.Username }}
<span style="margin-left: 5px" ng-if="isAdmin || user.TeamRole === 'Member'">
<a ng-click="removeUser(user)"><i class="fa fa-minus-circle space-right" aria-hidden="true"></i>Remove</a>
<a ng-click="removeUser(user)" ng-class="{ 'btn disabled py-0': settings.TeamSync }">
<i class="fa fa-minus-circle space-right" aria-hidden="true"></i>Remove
</a>
</span>
</td>
<td>
@ -163,10 +176,10 @@
<i ng-if="user.TeamRole === 'Member'" class="fa fa-user" aria-hidden="true" style="margin-right: 2px"></i>
{{ user.TeamRole }}
<span style="margin-left: 5px" ng-if="isAdmin">
<a style="margin-left: 5px" ng-click="promoteToLeader(user)" ng-if="user.TeamRole === 'Member'"
<a style="margin-left: 5px" ng-click="promoteToLeader(user)" ng-if="user.TeamRole === 'Member'" ng-class="{ 'btn disabled py-0': settings.TeamSync }"
><i class="fa fa-user-plus space-right" aria-hidden="true"></i>Leader</a
>
<a style="margin-left: 5px" ng-click="demoteToMember(user)" ng-if="user.TeamRole === 'Leader'"
<a style="margin-left: 5px" ng-click="demoteToMember(user)" ng-if="user.TeamRole === 'Leader'" ng-class="{ 'btn disabled py-0': settings.TeamSync }"
><i class="fa fa-user-times space-right" aria-hidden="true"></i>Member</a
>
</span>

View file

@ -10,7 +10,8 @@ angular.module('portainer.app').controller('TeamController', [
'Notifications',
'PaginationService',
'Authentication',
function ($q, $scope, $state, $transition$, TeamService, UserService, TeamMembershipService, ModalService, Notifications, PaginationService, Authentication) {
'SettingsService',
function ($q, $scope, $state, $transition$, TeamService, UserService, TeamMembershipService, ModalService, Notifications, PaginationService, Authentication, SettingsService) {
$scope.state = {
pagination_count_users: PaginationService.getPaginationLimit('team_available_users'),
pagination_count_members: PaginationService.getPaginationLimit('team_members'),
@ -189,21 +190,23 @@ angular.module('portainer.app').controller('TeamController', [
}
}
function initView() {
async function initView() {
$scope.isAdmin = Authentication.isAdmin();
$q.all({
team: TeamService.team($transition$.params().id),
users: UserService.users(false),
memberships: TeamService.userMemberships($transition$.params().id),
})
.then(function success(data) {
var users = data.users;
$scope.team = data.team;
assignUsersAndMembers(users, data.memberships);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve team details');
try {
$scope.settings = await SettingsService.publicSettings();
const data = await $q.all({
team: TeamService.team($transition$.params().id),
users: UserService.users($scope.isAdmin && $scope.settings.TeamSync),
memberships: TeamService.userMemberships($transition$.params().id),
});
$scope.team = data.team;
assignUsersAndMembers(data.users, data.memberships);
} catch (err) {
Notifications.error('Failure', err, 'Unable to retrieve team details');
}
}
initView();

View file

@ -11,6 +11,6 @@
<div class="row">
<div class="col-sm-12">
<teams-datatable title-text="Teams" title-icon="fa-users" dataset="teams" table-key="teams" order-by="Name" remove-action="removeAction"></teams-datatable>
<teams-datatable title-text="Teams" title-icon="fa-users" dataset="teams" table-key="teams" order-by="Name" remove-action="removeAction" is-admin="isAdmin"></teams-datatable>
</div>
</div>

View file

@ -89,6 +89,7 @@ angular.module('portainer.app').controller('TeamsController', [
var teams = data.teams;
$scope.teams = teams;
$scope.users = _.orderBy(data.users, 'Username', 'asc');
$scope.isTeamLeader = !!teams.length;
})
.catch(function error(err) {
$scope.teams = [];

View file

@ -7,7 +7,7 @@
<rd-header-content>User management</rd-header-content>
</rd-header>
<div class="row">
<div class="row" ng-if="isAdmin">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-plus" title-text="Add a new user"> </rd-widget-header>
@ -169,6 +169,7 @@
order-by="Username"
authentication-method="AuthenticationMethod"
remove-action="removeAction"
is-admin="isAdmin"
></users-datatable>
</div>
</div>