@boarteam/boar-pack-users-frontend 2.5.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boarteam/boar-pack-users-frontend",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
4
4
  "description": "Users frontend package for Boar Pack",
5
5
  "repository": "git@github.com:boarteam/boar-pack.git",
6
6
  "author": "Andrew Balakirev <balakirev.andrey@gmail.com>",
@@ -15,7 +15,7 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "@ant-design/plots": "^2.3.2",
18
- "@boarteam/boar-pack-common-frontend": "^2.8.1",
18
+ "@boarteam/boar-pack-common-frontend": "^2.9.0",
19
19
  "@fortawesome/fontawesome-svg-core": "^6.6.0",
20
20
  "@fortawesome/free-brands-svg-icons": "^6.6.0",
21
21
  "@umijs/max": "^4.1.10",
@@ -34,5 +34,5 @@
34
34
  "build": "tsc --project tsconfig.build.json",
35
35
  "yalc:push": "yalc push"
36
36
  },
37
- "gitHead": "79fe5a34f9a9bd6e1e3e010f7a64b3b82c9e19b5"
37
+ "gitHead": "e6e2920ef9786ec27cc616c35796f64f6183a3f5"
38
38
  }
@@ -0,0 +1,140 @@
1
+ import React, { useState } from "react";
2
+ import { User, UserCreateDto } from "@@api/generated";
3
+ import { Alert, Card, Switch, Table, Typography, Space } from "antd";
4
+ import apiClient from "@@api/apiClient";
5
+ import { useAccess } from "umi";
6
+
7
+ const { Title } = Typography;
8
+
9
+ export type PermissionItemConfig = {
10
+ key: string;
11
+ title: string;
12
+ icon?: React.ReactNode;
13
+ };
14
+
15
+ export type PermissionGroupConfig = {
16
+ title: string;
17
+ icon?: React.ReactNode;
18
+ permissions: PermissionItemConfig[];
19
+ };
20
+
21
+ export type PermissionsConfig = Array<PermissionItemConfig | PermissionGroupConfig>;
22
+
23
+ interface PermissionsListProps {
24
+ user: User;
25
+ permissionsConfig: PermissionsConfig;
26
+ }
27
+
28
+ export const PermissionsList: React.FC<PermissionsListProps> = ({
29
+ user,
30
+ permissionsConfig,
31
+ }) => {
32
+ const [permissionsSet, setPermissionsSet] = useState<Set<string>>(
33
+ new Set(user.permissions as string[])
34
+ );
35
+ const [loading, setLoading] = useState(false);
36
+ const { canManageAll } = useAccess() || {};
37
+
38
+ const togglePermission = (permission: string) => {
39
+ setLoading(true);
40
+ const newPermissionsSet = new Set(permissionsSet);
41
+ if (permissionsSet.has(permission)) {
42
+ newPermissionsSet.delete(permission);
43
+ } else {
44
+ newPermissionsSet.add(permission);
45
+ }
46
+ setPermissionsSet(newPermissionsSet);
47
+ apiClient.users
48
+ .updateOneBaseUsersControllerUser({
49
+ id: user.id,
50
+ requestBody: {
51
+ permissions: Array.from(newPermissionsSet),
52
+ }
53
+ })
54
+ .then((user) => {
55
+ setPermissionsSet(new Set(user.permissions as string[]));
56
+ })
57
+ .catch(e => {
58
+ console.error(e);
59
+ setPermissionsSet(permissionsSet);
60
+ })
61
+ .finally(() => {
62
+ setLoading(false);
63
+ });
64
+ }
65
+
66
+ const renderSwitch = (permission: string) => {
67
+ const disabledSwitch = user.role === UserCreateDto.role.ADMIN || !canManageAll;
68
+ const isGranted = user.role === UserCreateDto.role.ADMIN || permissionsSet.has(permission);
69
+
70
+ return (
71
+ <Switch
72
+ checked={isGranted}
73
+ onChange={() => togglePermission(permission)}
74
+ disabled={disabledSwitch}
75
+ />
76
+ );
77
+ }
78
+
79
+ const buildTableDataSource = () => {
80
+ return permissionsConfig.map((item, index) => {
81
+ if ('permissions' in item) {
82
+ // It's a group with children
83
+ return {
84
+ key: `group-${index}`,
85
+ permission: <Space>{item.icon}<strong>{item.title}</strong></Space>,
86
+ children: item.permissions.map(childItem => ({
87
+ key: childItem.key,
88
+ permission: childItem.title,
89
+ granted: renderSwitch(childItem.key)
90
+ }))
91
+ };
92
+ } else {
93
+ // It's an individual permission
94
+ return {
95
+ key: item.key,
96
+ permission: item.icon
97
+ ? <Space>{item.icon}<strong>{item.title}</strong></Space>
98
+ : item.title,
99
+ granted: renderSwitch(item.key)
100
+ };
101
+ }
102
+ });
103
+ };
104
+
105
+ return (
106
+ <Card>
107
+ <Title level={5}>Permissions for {user.name}</Title>
108
+ {user.role === UserCreateDto.role.ADMIN && (
109
+ <Alert
110
+ message="Admin can perform any action, in order to change permissions, change user role."
111
+ type="info"
112
+ showIcon
113
+ style={{ marginBottom: '16px' }}
114
+ />
115
+ ) || null}
116
+ <Table
117
+ loading={loading}
118
+ expandable={{
119
+ defaultExpandAllRows: true,
120
+ expandIcon: () => null,
121
+ }}
122
+ size="small"
123
+ showHeader={false}
124
+ columns={[
125
+ {
126
+ title: 'Permission',
127
+ dataIndex: 'permission',
128
+ width: '400px',
129
+ },
130
+ {
131
+ title: 'Granted',
132
+ dataIndex: 'granted',
133
+ }
134
+ ]}
135
+ dataSource={buildTableDataSource()}
136
+ pagination={false}
137
+ />
138
+ </Card>
139
+ );
140
+ };
@@ -0,0 +1,87 @@
1
+ import { Table, isRecordNew, Operators } from "@boarteam/boar-pack-common-frontend";
2
+ import apiClient from "@@api/apiClient";
3
+ import { User, UserCreateDto, UserUpdateDto } from "@@api/generated";
4
+ import { useUsersColumns } from "./useUsersColumns";
5
+ import pick from "lodash/pick";
6
+ import { PermissionsConfig, PermissionsList } from "./PermissionsList";
7
+ import { useAccess, useModel } from "umi";
8
+
9
+ function entityToDto(entity: User) {
10
+ return pick(entity, [
11
+ 'name',
12
+ 'email',
13
+ 'role',
14
+ 'pass'
15
+ ]);
16
+ }
17
+
18
+ type TUserFilterParams = {
19
+ name?: string,
20
+ email?: string,
21
+ role?: string,
22
+ }
23
+
24
+ export const UsersTable = ({
25
+ permissionsConfig = [],
26
+ userPageUrlPrefix = '/admin/users',
27
+ }: {
28
+ permissionsConfig?: PermissionsConfig;
29
+ userPageUrlPrefix?: string | null;
30
+ }) => {
31
+ const columns = useUsersColumns({
32
+ userPageUrlPrefix,
33
+ });
34
+ const { canManageAll } = useAccess() || {};
35
+ const { initialState } = useModel('@@initialState');
36
+ const { currentUser } = initialState || {};
37
+
38
+ return (
39
+ <Table<User, UserCreateDto, UserUpdateDto, TUserFilterParams>
40
+ getAll={params => {
41
+ const fields = params.fields?.[0] || '';
42
+ params.fields = [[fields, 'permissions'].join(',')];
43
+ return apiClient.users.getManyBaseUsersControllerUser(params);
44
+ }}
45
+ onCreate={params => apiClient.users.createOneBaseUsersControllerUser(params)}
46
+ onUpdate={params => apiClient.users.updateOneBaseUsersControllerUser(params)}
47
+ onDelete={params => apiClient.users.deleteOneBaseUsersControllerUser(params)}
48
+ entityToCreateDto={entityToDto}
49
+ entityToUpdateDto={entityToDto}
50
+ pathParams={{}}
51
+ columns={columns}
52
+ idColumnName='id'
53
+ searchableColumns={[
54
+ {
55
+ field: 'name',
56
+ operator: Operators.containsLow,
57
+ },
58
+ {
59
+ field: 'email',
60
+ operator: Operators.containsLow,
61
+ },
62
+ {
63
+ field: 'role',
64
+ operator: Operators.containsLow,
65
+ }
66
+ ]}
67
+ expandable={{
68
+ // hide expandable icon for new records which are not saved yet, since you can't set permissions for them
69
+ rowExpandable: record => !isRecordNew(record),
70
+ expandedRowRender: record => <PermissionsList
71
+ user={record}
72
+ permissionsConfig={permissionsConfig}
73
+ />,
74
+ }}
75
+ viewOnly={!canManageAll}
76
+ editable={{
77
+ actionRender: (row, config, dom) => {
78
+ if (row.id === currentUser?.id) {
79
+ return [dom.save, dom.cancel];
80
+ }
81
+
82
+ return [dom.save, dom.delete, dom.cancel];
83
+ }
84
+ }}
85
+ ></Table>
86
+ );
87
+ }
@@ -0,0 +1,132 @@
1
+ import { Link, useIntl } from "@umijs/max";
2
+ import { ProColumns } from "@ant-design/pro-components";
3
+ import { EditOutlined, UserOutlined } from "@ant-design/icons";
4
+ import { Password } from "@boarteam/boar-pack-common-frontend";
5
+ import { useAccess, useModel } from "umi";
6
+ import { Tooltip } from "antd";
7
+ import { User } from "@@api/generated";
8
+ import safetyRun from "@boarteam/boar-pack-common-frontend/src/tools/safetyRun";
9
+ import apiClient from "@@api/apiClient";
10
+
11
+ export const useUsersColumns = ({
12
+ userPageUrlPrefix = '/admin/users',
13
+ }: {
14
+ userPageUrlPrefix?: string | null;
15
+ }): ProColumns<User>[] => {
16
+ const intl = useIntl();
17
+ const { initialState } = useModel('@@initialState');
18
+ const { currentUser } = initialState || {};
19
+ const { canManageAll } = useAccess() || {};
20
+
21
+ const onLoginAsUser = (userId: string) => {
22
+ safetyRun(apiClient.authentication.loginAsUser({
23
+ userId,
24
+ })
25
+ .then(() => {
26
+ window.location.href = '/';
27
+ }));
28
+ }
29
+
30
+ const columns: ProColumns<User>[] = [
31
+ {
32
+ title: intl.formatMessage({ id: 'pages.users.name' }),
33
+ dataIndex: 'name',
34
+ width: '20%',
35
+ formItemProps: {
36
+ rules: [
37
+ {
38
+ required: true,
39
+ }
40
+ ]
41
+ },
42
+ fieldProps: {
43
+ autoComplete: 'one-time-code', // disable browser autocomplete
44
+ },
45
+ render(text, record) {
46
+ return userPageUrlPrefix ? <Link to={`${userPageUrlPrefix}/${record.id}`}>{text}</Link> : text;
47
+ }
48
+ },
49
+ {
50
+ title: intl.formatMessage({ id: 'pages.users.email' }),
51
+ dataIndex: 'email',
52
+ valueType: 'text',
53
+ formItemProps: {
54
+ rules: [
55
+ {
56
+ required: true,
57
+ }
58
+ ]
59
+ },
60
+ fieldProps: {
61
+ autoComplete: 'one-time-code', // disable browser autocomplete
62
+ },
63
+ editable: (v, record) => record.id !== currentUser?.id,
64
+ },
65
+ {
66
+ title: intl.formatMessage({ id: 'pages.users.password' }),
67
+ dataIndex: 'pass',
68
+ width: '20%',
69
+ valueType: 'password',
70
+ renderFormItem() {
71
+ return <Password />;
72
+ },
73
+ fieldProps: {
74
+ autoComplete: 'one-time-code', // disable browser autocomplete
75
+ },
76
+ },
77
+ {
78
+ title: intl.formatMessage({ id: 'pages.users.role' }),
79
+ dataIndex: 'role',
80
+ width: '15%',
81
+ valueType: 'select',
82
+ valueEnum: {
83
+ admin: {
84
+ text: intl.formatMessage({ id: 'pages.users.roles.admin' }),
85
+ },
86
+ user: {
87
+ text: intl.formatMessage({ id: 'pages.users.roles.user' }),
88
+ },
89
+ },
90
+ formItemProps: {
91
+ rules: [
92
+ {
93
+ required: true,
94
+ }
95
+ ]
96
+ },
97
+ editable: (v, record) => record.id !== currentUser?.id,
98
+ },
99
+ ];
100
+
101
+ if (canManageAll) {
102
+ columns.push({
103
+ title: intl.formatMessage({ id: 'table.actions' }),
104
+ valueType: 'option',
105
+ render: (text, record, _, action) => [
106
+ <a
107
+ key="editable"
108
+ onClick={() => {
109
+ action?.startEditable?.(record.id);
110
+ }}
111
+ >
112
+ <EditOutlined />
113
+ </a>,
114
+ <Tooltip
115
+ title={'Login as user'}
116
+ key="loginAsUser"
117
+ >
118
+ <a
119
+ onClick={() => {
120
+ onLoginAsUser(record.id);
121
+ }}
122
+ >
123
+ <UserOutlined />
124
+ </a>
125
+ </Tooltip>
126
+ ],
127
+ });
128
+ }
129
+
130
+
131
+ return columns;
132
+ };
@@ -7,4 +7,7 @@ export * from './EventLogs/useEventLogsColumns';
7
7
  export * from './Tokens/TokensTable';
8
8
  export * from './Tokens/MyTokensTable';
9
9
  export * from './Tokens/useTokensColumns';
10
+ export * from './Users/UsersTable';
11
+ export * from './Users/useUsersColumns';
12
+ export * from './Users/PermissionsList';
10
13
  export {default as UserAgentDisplay} from './EventLogs/UserAgentDisplay';
@@ -5,6 +5,7 @@
5
5
  import type { BaseHttpRequest } from './core/BaseHttpRequest';
6
6
  import type { OpenAPIConfig } from './core/OpenAPI';
7
7
  import { NodeHttpRequest } from './core/NodeHttpRequest';
8
+ import { AuthenticationService } from './services/AuthenticationService';
8
9
  import { EventLogsService } from './services/EventLogsService';
9
10
  import { SettingsService } from './services/SettingsService';
10
11
  import { TelegrafService } from './services/TelegrafService';
@@ -12,6 +13,7 @@ import { TokensService } from './services/TokensService';
12
13
  import { UsersService } from './services/UsersService';
13
14
  type HttpRequestConstructor = new (config: OpenAPIConfig) => BaseHttpRequest;
14
15
  export class ApiClient {
16
+ public readonly authentication: AuthenticationService;
15
17
  public readonly eventLogs: EventLogsService;
16
18
  public readonly settings: SettingsService;
17
19
  public readonly telegraf: TelegrafService;
@@ -30,6 +32,7 @@ export class ApiClient {
30
32
  HEADERS: config?.HEADERS,
31
33
  ENCODE_PATH: config?.ENCODE_PATH,
32
34
  });
35
+ this.authentication = new AuthenticationService(this.request);
33
36
  this.eventLogs = new EventLogsService(this.request);
34
37
  this.settings = new SettingsService(this.request);
35
38
  this.telegraf = new TelegrafService(this.request);
@@ -19,6 +19,8 @@ export type { EventSettingsDto } from './models/EventSettingsDto';
19
19
  export type { GetManyEventLogResponseDto } from './models/GetManyEventLogResponseDto';
20
20
  export type { GetManyTokenResponseDto } from './models/GetManyTokenResponseDto';
21
21
  export type { GetManyUserResponseDto } from './models/GetManyUserResponseDto';
22
+ export type { LocalAuthLoginDto } from './models/LocalAuthLoginDto';
23
+ export type { LocalAuthTokenDto } from './models/LocalAuthTokenDto';
22
24
  export type { PermissionDto } from './models/PermissionDto';
23
25
  export type { TelegramSettingsDto } from './models/TelegramSettingsDto';
24
26
  export type { TelegramSettingsUpdateDto } from './models/TelegramSettingsUpdateDto';
@@ -30,6 +32,7 @@ export { User } from './models/User';
30
32
  export { UserCreateDto } from './models/UserCreateDto';
31
33
  export { UserUpdateDto } from './models/UserUpdateDto';
32
34
 
35
+ export { AuthenticationService } from './services/AuthenticationService';
33
36
  export { EventLogsService } from './services/EventLogsService';
34
37
  export { SettingsService } from './services/SettingsService';
35
38
  export { TelegrafService } from './services/TelegrafService';
@@ -0,0 +1,9 @@
1
+ /* generated using openapi-typescript-codegen -- do not edit */
2
+ /* istanbul ignore file */
3
+ /* tslint:disable */
4
+ /* eslint-disable */
5
+ export type LocalAuthLoginDto = {
6
+ email: string;
7
+ password: string;
8
+ };
9
+
@@ -0,0 +1,8 @@
1
+ /* generated using openapi-typescript-codegen -- do not edit */
2
+ /* istanbul ignore file */
3
+ /* tslint:disable */
4
+ /* eslint-disable */
5
+ export type LocalAuthTokenDto = {
6
+ accessToken: string;
7
+ };
8
+
@@ -0,0 +1,104 @@
1
+ /* generated using openapi-typescript-codegen -- do not edit */
2
+ /* istanbul ignore file */
3
+ /* tslint:disable */
4
+ /* eslint-disable */
5
+ import type { LocalAuthLoginDto } from '../models/LocalAuthLoginDto';
6
+ import type { LocalAuthTokenDto } from '../models/LocalAuthTokenDto';
7
+ import type { CancelablePromise } from '../core/CancelablePromise';
8
+ import type { BaseHttpRequest } from '../core/BaseHttpRequest';
9
+ export class AuthenticationService {
10
+ constructor(public readonly httpRequest: BaseHttpRequest) {}
11
+ /**
12
+ * @returns LocalAuthTokenDto
13
+ * @throws ApiError
14
+ */
15
+ public login({
16
+ requestBody,
17
+ }: {
18
+ requestBody: LocalAuthLoginDto,
19
+ }): CancelablePromise<LocalAuthTokenDto> {
20
+ return this.httpRequest.request({
21
+ method: 'POST',
22
+ url: '/auth/login',
23
+ body: requestBody,
24
+ mediaType: 'application/json',
25
+ });
26
+ }
27
+ /**
28
+ * @returns LocalAuthTokenDto
29
+ * @throws ApiError
30
+ */
31
+ public token(): CancelablePromise<LocalAuthTokenDto> {
32
+ return this.httpRequest.request({
33
+ method: 'POST',
34
+ url: '/auth/token',
35
+ });
36
+ }
37
+ /**
38
+ * @returns any
39
+ * @throws ApiError
40
+ */
41
+ public loginGoogle(): CancelablePromise<any> {
42
+ return this.httpRequest.request({
43
+ method: 'GET',
44
+ url: '/auth/google',
45
+ });
46
+ }
47
+ /**
48
+ * @returns any
49
+ * @throws ApiError
50
+ */
51
+ public loginGoogleCallback(): CancelablePromise<any> {
52
+ return this.httpRequest.request({
53
+ method: 'GET',
54
+ url: '/auth/google/callback',
55
+ });
56
+ }
57
+ /**
58
+ * @returns any
59
+ * @throws ApiError
60
+ */
61
+ public loginMs(): CancelablePromise<any> {
62
+ return this.httpRequest.request({
63
+ method: 'GET',
64
+ url: '/auth/ms',
65
+ });
66
+ }
67
+ /**
68
+ * @returns any
69
+ * @throws ApiError
70
+ */
71
+ public loginMsCallback(): CancelablePromise<any> {
72
+ return this.httpRequest.request({
73
+ method: 'GET',
74
+ url: '/auth/ms/callback',
75
+ });
76
+ }
77
+ /**
78
+ * @returns any
79
+ * @throws ApiError
80
+ */
81
+ public logout(): CancelablePromise<any> {
82
+ return this.httpRequest.request({
83
+ method: 'POST',
84
+ url: '/auth/logout',
85
+ });
86
+ }
87
+ /**
88
+ * @returns LocalAuthTokenDto
89
+ * @throws ApiError
90
+ */
91
+ public loginAsUser({
92
+ userId,
93
+ }: {
94
+ userId: string,
95
+ }): CancelablePromise<LocalAuthTokenDto> {
96
+ return this.httpRequest.request({
97
+ method: 'POST',
98
+ url: '/auth-manage/login-as-user/{userId}',
99
+ path: {
100
+ 'userId': userId,
101
+ },
102
+ });
103
+ }
104
+ }