mirror of
https://github.com/portainer/portainer.git
synced 2025-08-07 06:45:23 +02:00
refactor(users): migrate list view to react [EE-2202] (#11914)
This commit is contained in:
parent
33ce841040
commit
3c1441d462
43 changed files with 967 additions and 681 deletions
|
@ -381,8 +381,7 @@ angular
|
|||
url: '/users',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/users/users.html',
|
||||
controller: 'UsersController',
|
||||
component: 'usersListView',
|
||||
},
|
||||
},
|
||||
data: {
|
||||
|
|
|
@ -2,17 +2,12 @@ import angular from 'angular';
|
|||
|
||||
import { r2a } from '@/react-tools/react2angular';
|
||||
import { withUIRouter } from '@/react-tools/withUIRouter';
|
||||
import { UsersDatatable } from '@/react/portainer/users/ListView/UsersDatatable/UsersDatatable';
|
||||
import { withCurrentUser } from '@/react-tools/withCurrentUser';
|
||||
import { EffectiveAccessViewerDatatable } from '@/react/portainer/users/RolesView/AccessViewer/EffectiveAccessViewerDatatable';
|
||||
import { RbacRolesDatatable } from '@/react/portainer/users/RolesView/RbacRolesDatatable';
|
||||
|
||||
export const usersModule = angular
|
||||
.module('portainer.app.react.components.users', [])
|
||||
.component(
|
||||
'usersDatatable',
|
||||
r2a(withUIRouter(withCurrentUser(UsersDatatable)), ['dataset', 'onRemove'])
|
||||
)
|
||||
.component(
|
||||
'effectiveAccessViewerDatatable',
|
||||
r2a(withUIRouter(withCurrentUser(EffectiveAccessViewerDatatable)), [
|
||||
|
|
|
@ -20,6 +20,7 @@ import { environmentGroupModule } from './env-groups';
|
|||
import { registriesModule } from './registries';
|
||||
import { activityLogsModule } from './activity-logs';
|
||||
import { templatesModule } from './templates';
|
||||
import { usersModule } from './users';
|
||||
|
||||
export const viewsModule = angular
|
||||
.module('portainer.app.react.views', [
|
||||
|
@ -30,6 +31,7 @@ export const viewsModule = angular
|
|||
registriesModule,
|
||||
activityLogsModule,
|
||||
templatesModule,
|
||||
usersModule,
|
||||
])
|
||||
.component(
|
||||
'homeView',
|
||||
|
|
15
app/portainer/react/views/users.ts
Normal file
15
app/portainer/react/views/users.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import angular from 'angular';
|
||||
|
||||
import { ListView } from '@/react/portainer/users/ListView/ListView';
|
||||
import { r2a } from '@/react-tools/react2angular';
|
||||
import { withCurrentUser } from '@/react-tools/withCurrentUser';
|
||||
import { withReactQuery } from '@/react-tools/withReactQuery';
|
||||
import { withUIRouter } from '@/react-tools/withUIRouter';
|
||||
|
||||
export const usersModule = angular
|
||||
.module('portainer.app.react.views.users', [])
|
||||
|
||||
.component(
|
||||
'usersListView',
|
||||
r2a(withUIRouter(withReactQuery(withCurrentUser(ListView))), [])
|
||||
).name;
|
|
@ -1,13 +1,12 @@
|
|||
import _ from 'lodash-es';
|
||||
|
||||
import { UserTokenModel, UserViewModel } from '@/portainer/models/user';
|
||||
import { UserViewModel } from '@/portainer/models/user';
|
||||
import { getUsers } from '@/portainer/users/user.service';
|
||||
import { getUser } from '@/portainer/users/queries/useUser';
|
||||
|
||||
import { TeamMembershipModel } from '../../models/teamMembership';
|
||||
|
||||
/* @ngInject */
|
||||
export function UserService($q, Users, TeamService, TeamMembershipService) {
|
||||
export function UserService($q, Users, TeamService) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
|
@ -23,33 +22,6 @@ export function UserService($q, Users, TeamService, TeamMembershipService) {
|
|||
return new UserViewModel(user);
|
||||
};
|
||||
|
||||
service.createUser = function (username, password, role, teamIds) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
var payload = {
|
||||
username: username,
|
||||
password: password,
|
||||
role: role,
|
||||
};
|
||||
|
||||
Users.create({}, payload)
|
||||
.$promise.then(function success(data) {
|
||||
var userId = data.Id;
|
||||
var teamMembershipQueries = [];
|
||||
angular.forEach(teamIds, function (teamId) {
|
||||
teamMembershipQueries.push(TeamMembershipService.createMembership(userId, teamId, 2));
|
||||
});
|
||||
$q.all(teamMembershipQueries).then(function success() {
|
||||
deferred.resolve();
|
||||
});
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to create user', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.deleteUser = function (id) {
|
||||
return Users.remove({ id: id }).$promise;
|
||||
};
|
||||
|
@ -112,27 +84,6 @@ export function UserService($q, Users, TeamService, TeamMembershipService) {
|
|||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.getAccessTokens = function (id) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Users.getAccessTokens({ id: id })
|
||||
.$promise.then(function success(data) {
|
||||
var userTokens = data.map(function (item) {
|
||||
return new UserTokenModel(item);
|
||||
});
|
||||
deferred.resolve(userTokens);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve user tokens', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.deleteAccessToken = function (id, tokenId) {
|
||||
return Users.deleteAccessToken({ id: id, tokenId: tokenId }).$promise;
|
||||
};
|
||||
|
||||
service.initAdministrator = function (username, password) {
|
||||
return Users.initAdminUser({ Username: username, Password: password }).$promise;
|
||||
};
|
||||
|
|
|
@ -1,169 +0,0 @@
|
|||
<page-header title="'Users'" breadcrumbs="['User management']" reload="true"> </page-header>
|
||||
|
||||
<div class="row" ng-if="isAdmin">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="plus" title-text="Add a new user"> </rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<form name="form" class="form-horizontal">
|
||||
<!-- name-input -->
|
||||
<div class="form-group">
|
||||
<label for="username" class="col-sm-3 col-lg-2 control-label required text-left">
|
||||
Username
|
||||
<portainer-tooltip ng-if="AuthenticationMethod === 2" message="'Username must exactly match username defined in external LDAP source.'"></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="text"
|
||||
data-cy="user-usernameInput"
|
||||
class="form-control"
|
||||
id="username"
|
||||
ng-model="formValues.Username"
|
||||
ng-change="checkUsernameValidity()"
|
||||
placeholder="e.g. jdoe"
|
||||
auto-focus
|
||||
/>
|
||||
<span class="input-group-addon">
|
||||
<pr-icon mode="'success'" icon="'check'" ng-if="state.validUsername"></pr-icon>
|
||||
<pr-icon mode="'danger'" icon="'x'" ng-if="!state.validUsername"></pr-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name-input -->
|
||||
<!-- new-password-input -->
|
||||
<div class="form-group" ng-if="AuthenticationMethod === 1">
|
||||
<label for="password" class="col-sm-3 col-lg-2 control-label required text-left">Password</label>
|
||||
<div class="col-sm-8">
|
||||
<input
|
||||
type="password"
|
||||
class="form-control"
|
||||
ng-model="formValues.Password"
|
||||
id="password"
|
||||
name="password"
|
||||
ng-minlength="requiredPasswordLength"
|
||||
data-cy="user-passwordInput"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !new-password-input -->
|
||||
<!-- confirm-password-input -->
|
||||
<div class="form-group" ng-if="AuthenticationMethod === 1">
|
||||
<label for="confirm_password" class="col-sm-3 col-lg-2 control-label required text-left">Confirm password</label>
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="password"
|
||||
class="form-control form-control--has-icon"
|
||||
ng-model="formValues.ConfirmPassword"
|
||||
id="confirm_password"
|
||||
data-cy="user-passwordConfirmInput"
|
||||
/>
|
||||
<span class="input-group-addon">
|
||||
<pr-icon
|
||||
mode="'success'"
|
||||
icon="'check'"
|
||||
aria-hidden="true"
|
||||
ng-if="form.password.$viewValue !== '' && form.password.$viewValue === formValues.ConfirmPassword"
|
||||
></pr-icon>
|
||||
<pr-icon
|
||||
mode="'danger'"
|
||||
icon="'x'"
|
||||
aria-hidden="true"
|
||||
ng-if="!(form.password.$viewValue !== '' && form.password.$viewValue === formValues.ConfirmPassword)"
|
||||
></pr-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !confirm-password-input -->
|
||||
|
||||
<!-- password-check-hint -->
|
||||
<div class="form-group" ng-if="AuthenticationMethod === 1">
|
||||
<div class="col-sm-3 col-lg-2"></div>
|
||||
<div class="col-sm-8">
|
||||
<password-check-hint password-valid="form.password.$valid && formValues.Password"></password-check-hint>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ! password-check-hint -->
|
||||
|
||||
<!-- admin-checkbox -->
|
||||
<div class="form-group" ng-if="isAdmin">
|
||||
<div class="col-sm-12">
|
||||
<por-switch-field
|
||||
label-class="'col-sm-3 col-lg-2 control-label text-left'"
|
||||
checked="formValues.Administrator"
|
||||
label="'Administrator'"
|
||||
tooltip="'Administrators have access to Portainer settings management as well as full control over all defined environments and their resources.'"
|
||||
on-change="(handleAdministratorChange)"
|
||||
></por-switch-field>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !admin-checkbox -->
|
||||
<!-- teams -->
|
||||
<div class="form-group" ng-if="!formValues.Administrator">
|
||||
<label class="col-sm-3 col-lg-2 control-label text-left" for="teams-selector">Add to team(s)</label>
|
||||
<div class="col-sm-8">
|
||||
<span class="small text-muted" ng-if="teams.length === 0">
|
||||
You don't seem to have any teams to add users into. Head over to the <a ui-sref="portainer.teams">Teams view</a> to create some.
|
||||
</span>
|
||||
<teams-selector
|
||||
ng-if="teams.length > 0"
|
||||
value="formValues.TeamIds"
|
||||
teams="teams"
|
||||
placeholder="'Select one or more teams'"
|
||||
data-cy="'user-teamSelect'"
|
||||
on-change="(onChangeTeamIds)"
|
||||
input-id="'teams-selector'"
|
||||
disabled="teamSync"
|
||||
></teams-selector>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="teamSync">
|
||||
<div class="col-sm-12">
|
||||
<span class="small">
|
||||
<p class="small text-muted vertical-center">
|
||||
<pr-icon icon="'alert-circle'" class-name="'icon-warning =vertical-center'"></pr-icon>
|
||||
The team leader feature is disabled as external authentication is currently enabled with team sync.
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !teams -->
|
||||
<div class="form-group" ng-if="isAdmin && !formValues.Administrator && formValues.Teams.length === 0">
|
||||
<div class="col-sm-12">
|
||||
<span class="small text-muted vertical-center">
|
||||
<pr-icon class="vertical-center" icon="'alert-circle'" aria-hidden="true" mode="'primary'" size="'md'"></pr-icon>
|
||||
<span
|
||||
>Note: non-administrator users who aren't in a team don't have access to any environments by default. Head over to the
|
||||
<a ui-sref="portainer.endpoints">Environments view</a> to manage their accesses.</span
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm !ml-0"
|
||||
ng-disabled="state.actionInProgress || !state.validUsername || formValues.Username === '' || (AuthenticationMethod === 1 && (!formValues.Password || form.$invalid || formValues.Password !== formValues.ConfirmPassword))"
|
||||
ng-click="addUser()"
|
||||
button-spinner="state.actionInProgress"
|
||||
data-cy="user-createUserButton"
|
||||
>
|
||||
<span ng-hide="state.actionInProgress" class="vertical-center icon-white"> <pr-icon icon="'plus'" aria-hidden="true" size="'md'"></pr-icon> Create user</span>
|
||||
<span ng-show="state.actionInProgress">Creating user...</span>
|
||||
</button>
|
||||
<span class="text-danger" ng-if="state.userCreationError">
|
||||
<pr-icon icon="'alert-circle'" aria-hidden="true" mode="'primary'" size="'md'"></pr-icon> {{ state.userCreationError }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<users-datatable dataset="users" on-remove="(removeAction)"></users-datatable>
|
|
@ -1,143 +0,0 @@
|
|||
import _ from 'lodash-es';
|
||||
import { AuthenticationMethod } from '@/react/portainer/settings/types';
|
||||
import { processItemsInBatches } from '@/react/common/processItemsInBatches';
|
||||
|
||||
angular.module('portainer.app').controller('UsersController', [
|
||||
'$q',
|
||||
'$scope',
|
||||
'$state',
|
||||
'UserService',
|
||||
'TeamService',
|
||||
'TeamMembershipService',
|
||||
'Notifications',
|
||||
'Authentication',
|
||||
'SettingsService',
|
||||
function ($q, $scope, $state, UserService, TeamService, TeamMembershipService, Notifications, Authentication, SettingsService) {
|
||||
$scope.state = {
|
||||
userCreationError: '',
|
||||
validUsername: false,
|
||||
actionInProgress: false,
|
||||
};
|
||||
|
||||
$scope.formValues = {
|
||||
Username: '',
|
||||
Password: '',
|
||||
ConfirmPassword: '',
|
||||
Administrator: false,
|
||||
TeamIds: [],
|
||||
};
|
||||
|
||||
$scope.handleAdministratorChange = function (checked) {
|
||||
return $scope.$evalAsync(() => {
|
||||
$scope.formValues.Administrator = checked;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.onChangeTeamIds = function (teamIds) {
|
||||
return $scope.$evalAsync(() => {
|
||||
$scope.formValues.TeamIds = teamIds;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.checkUsernameValidity = function () {
|
||||
var valid = true;
|
||||
for (var i = 0; i < $scope.users.length; i++) {
|
||||
if ($scope.formValues.Username.toLocaleLowerCase() === $scope.users[i].Username.toLocaleLowerCase()) {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$scope.state.validUsername = valid;
|
||||
$scope.state.userCreationError = valid ? '' : 'Username already taken';
|
||||
};
|
||||
|
||||
$scope.addUser = function () {
|
||||
$scope.state.actionInProgress = true;
|
||||
$scope.state.userCreationError = '';
|
||||
var username = $scope.formValues.Username;
|
||||
var password = $scope.formValues.Password;
|
||||
var role = $scope.formValues.Administrator ? 1 : 2;
|
||||
UserService.createUser(username, password, role, $scope.formValues.TeamIds)
|
||||
.then(function success() {
|
||||
Notifications.success('User successfully created', username);
|
||||
$state.reload();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to create user');
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.actionInProgress = false;
|
||||
});
|
||||
};
|
||||
|
||||
async function deleteSelectedUsers(selectedItems) {
|
||||
async function doRemove(user) {
|
||||
return UserService.deleteUser(user.Id)
|
||||
.then(function success() {
|
||||
Notifications.success('User successfully removed', user.Username);
|
||||
var index = $scope.users.indexOf(user);
|
||||
$scope.users.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove user');
|
||||
});
|
||||
}
|
||||
await processItemsInBatches(selectedItems, doRemove);
|
||||
$state.reload();
|
||||
}
|
||||
|
||||
$scope.removeAction = function (selectedItems) {
|
||||
return deleteSelectedUsers(selectedItems);
|
||||
};
|
||||
|
||||
function assignTeamLeaders(users, memberships) {
|
||||
for (var i = 0; i < users.length; i++) {
|
||||
var user = users[i];
|
||||
user.isTeamLeader = false;
|
||||
for (var j = 0; j < memberships.length; j++) {
|
||||
var membership = memberships[j];
|
||||
if (user.Id === membership.UserId && membership.Role === 1) {
|
||||
user.isTeamLeader = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initView() {
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
var isAdmin = Authentication.isAdmin();
|
||||
$scope.isAdmin = isAdmin;
|
||||
$q.all({
|
||||
users: UserService.users(true),
|
||||
teams: isAdmin ? TeamService.teams() : UserService.userLeadingTeams(userDetails.ID),
|
||||
memberships: TeamMembershipService.memberships(),
|
||||
settings: SettingsService.publicSettings(),
|
||||
})
|
||||
.then(function success(data) {
|
||||
$scope.AuthenticationMethod = data.settings.AuthenticationMethod;
|
||||
var users = data.users;
|
||||
assignTeamLeaders(users, data.memberships);
|
||||
users = assignAuthMethod(users, $scope.AuthenticationMethod);
|
||||
$scope.users = users;
|
||||
$scope.teams = _.orderBy(data.teams, 'Name', 'asc');
|
||||
$scope.requiredPasswordLength = data.settings.RequiredPasswordLength;
|
||||
$scope.teamSync = data.settings.TeamSync;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve users and teams');
|
||||
$scope.users = [];
|
||||
$scope.teams = [];
|
||||
});
|
||||
}
|
||||
|
||||
initView();
|
||||
},
|
||||
]);
|
||||
|
||||
function assignAuthMethod(users, authMethod) {
|
||||
return users.map((u) => ({
|
||||
...u,
|
||||
authMethod: AuthenticationMethod[u.Id === 1 ? AuthenticationMethod.Internal : authMethod],
|
||||
}));
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue