1
0
Fork 0
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:
Chaim Lev-Ari 2024-08-28 14:04:32 -06:00 committed by GitHub
parent 33ce841040
commit 3c1441d462
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 967 additions and 681 deletions

View file

@ -381,8 +381,7 @@ angular
url: '/users',
views: {
'content@': {
templateUrl: './views/users/users.html',
controller: 'UsersController',
component: 'usersListView',
},
},
data: {

View file

@ -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)), [

View file

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

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

View file

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

View file

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

View file

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