mirror of
https://github.com/inventree/InvenTree.git
synced 2025-07-02 19:50:59 +00:00
feat: improve user/group management actions (#9602)
* feat: improve user management actions add "open profile" actions * add lock / unlock action * add actions for password reset * submit coverage info to codecov no idea why this was turned off * bump api version * add frontend test * add backend test * fix test state * move test * fix style * fix name * hide password change if not superuser * bump playwright see https://github.com/microsoft/playwright/issues/35183 * fix test * fix test order * simplify test --------- Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
This commit is contained in:
@ -11,6 +11,7 @@ export enum ApiEndpoints {
|
||||
|
||||
// User API endpoints
|
||||
user_list = 'user/',
|
||||
user_set_password = 'user/:id/set-password/',
|
||||
user_me = 'user/me/',
|
||||
user_profile = 'user/profile/',
|
||||
user_roles = 'user/roles/',
|
||||
|
@ -72,6 +72,7 @@ export type ApiFormFieldType = {
|
||||
| 'email'
|
||||
| 'url'
|
||||
| 'string'
|
||||
| 'password'
|
||||
| 'icon'
|
||||
| 'boolean'
|
||||
| 'date'
|
||||
|
@ -112,7 +112,7 @@
|
||||
"@lingui/babel-plugin-lingui-macro": "^5.3.0",
|
||||
"@lingui/cli": "^5.3.1",
|
||||
"@lingui/macro": "^5.3.0",
|
||||
"@playwright/test": "^1.49.1",
|
||||
"@playwright/test": "^1.52.0",
|
||||
"@types/node": "^22.13.14",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react": "^18.3.8",
|
||||
|
@ -152,6 +152,18 @@ export function ApiFormField({
|
||||
}}
|
||||
/>
|
||||
);
|
||||
case 'password':
|
||||
return (
|
||||
<TextField
|
||||
definition={{ ...reducedDefinition, type: 'password' }}
|
||||
controller={controller}
|
||||
fieldName={fieldName}
|
||||
onChange={onChange}
|
||||
onKeyDown={(value) => {
|
||||
onKeyDown?.(value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
case 'icon':
|
||||
return (
|
||||
<IconField definition={fieldDefinition} controller={controller} />
|
||||
|
@ -7,6 +7,8 @@ import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { UserRoles } from '@lib/enums/Roles';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { getDetailUrl } from '@lib/index';
|
||||
import { IconUsersGroup } from '@tabler/icons-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||
import { EditApiForm } from '../../components/forms/ApiForm';
|
||||
@ -159,7 +161,14 @@ export function GroupTable({
|
||||
setSelectedGroup(record.pk);
|
||||
deleteGroup.open();
|
||||
}
|
||||
})
|
||||
}),
|
||||
{
|
||||
icon: <IconUsersGroup />,
|
||||
title: t`Open Profile`,
|
||||
onClick: () => {
|
||||
navigate(getDetailUrl(ModelType.group, record.pk));
|
||||
}
|
||||
}
|
||||
];
|
||||
},
|
||||
[user]
|
||||
|
@ -1,13 +1,20 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { Accordion, Alert, LoadingOverlay, Stack, Text } from '@mantine/core';
|
||||
import { IconInfoCircle } from '@tabler/icons-react';
|
||||
import {
|
||||
IconInfoCircle,
|
||||
IconKey,
|
||||
IconLock,
|
||||
IconLockOpen,
|
||||
IconUserCircle
|
||||
} from '@tabler/icons-react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { UserRoles } from '@lib/enums/Roles';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { getDetailUrl } from '@lib/index';
|
||||
import type { TableFilter } from '@lib/types/Filters';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
@ -23,6 +30,7 @@ import {
|
||||
import { DetailDrawer } from '../../components/nav/DetailDrawer';
|
||||
import { showApiErrorMessage } from '../../functions/notifications';
|
||||
import {
|
||||
useApiFormModal,
|
||||
useCreateApiFormModal,
|
||||
useDeleteApiFormModal
|
||||
} from '../../hooks/UseForm';
|
||||
@ -298,7 +306,44 @@ export function UserTable({
|
||||
setSelectedUser(record.pk);
|
||||
deleteUser.open();
|
||||
}
|
||||
})
|
||||
}),
|
||||
{
|
||||
icon: <IconUserCircle />,
|
||||
title: t`Open Profile`,
|
||||
onClick: () => {
|
||||
navigate(getDetailUrl(ModelType.user, record.pk));
|
||||
}
|
||||
},
|
||||
{
|
||||
icon: <IconKey />,
|
||||
title: t`Change Password`,
|
||||
color: 'blue',
|
||||
onClick: () => {
|
||||
setSelectedUser(record.pk);
|
||||
setPassword.open();
|
||||
},
|
||||
hidden: !user.isSuperuser()
|
||||
},
|
||||
{
|
||||
icon: <IconLock />,
|
||||
title: t`Lock user`,
|
||||
color: 'blue',
|
||||
onClick: () => {
|
||||
setUserActiveState(record.pk, false);
|
||||
table.refreshTable();
|
||||
},
|
||||
hidden: !record.is_active
|
||||
},
|
||||
{
|
||||
icon: <IconLockOpen />,
|
||||
title: t`Unlock user`,
|
||||
color: 'blue',
|
||||
onClick: () => {
|
||||
setUserActiveState(record.pk, true);
|
||||
table.refreshTable();
|
||||
},
|
||||
hidden: record.is_active
|
||||
}
|
||||
];
|
||||
},
|
||||
[user]
|
||||
@ -327,6 +372,18 @@ export function UserTable({
|
||||
successMessage: t`Added user`
|
||||
});
|
||||
|
||||
const setPassword = useApiFormModal({
|
||||
url: ApiEndpoints.user_set_password,
|
||||
method: 'PUT',
|
||||
pk: selectedUser,
|
||||
title: t`Set Password`,
|
||||
fields: {
|
||||
password: { field_type: 'password' },
|
||||
override_warning: {}
|
||||
},
|
||||
successMessage: t`Password updated`
|
||||
});
|
||||
|
||||
const tableActions = useMemo(() => {
|
||||
const actions = [];
|
||||
const staff: boolean = user.isStaff() || user.isSuperuser();
|
||||
@ -371,6 +428,7 @@ export function UserTable({
|
||||
|
||||
return (
|
||||
<>
|
||||
{editable && setPassword.modal}
|
||||
{editable && newUser.modal}
|
||||
{editable && deleteUser.modal}
|
||||
{editable && (
|
||||
@ -405,3 +463,21 @@ export function UserTable({
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
async function setUserActiveState(userId: number, active: boolean) {
|
||||
try {
|
||||
await api.patch(apiUrl(ApiEndpoints.user_list, userId), {
|
||||
is_active: active
|
||||
});
|
||||
showNotification({
|
||||
title: t`User updated`,
|
||||
message: t`User updated successfully`,
|
||||
color: 'green'
|
||||
});
|
||||
} catch (error) {
|
||||
showApiErrorMessage({
|
||||
error: error,
|
||||
title: t`Error updating user`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -23,10 +23,34 @@ test('Permissions - Admin', async ({ browser, request }) => {
|
||||
await loadTab(page, 'Plugins');
|
||||
await loadTab(page, 'Users / Access');
|
||||
|
||||
// Let's create a new user
|
||||
// Let's check creating a new user
|
||||
await page.getByLabel('action-button-add-user').click();
|
||||
await page.getByRole('button', { name: 'Submit' }).waitFor();
|
||||
await page.getByRole('button', { name: 'Cancel' }).click();
|
||||
|
||||
// Change password
|
||||
await page.getByRole('cell', { name: 'Ian', exact: true }).click({
|
||||
button: 'right'
|
||||
});
|
||||
await page.getByRole('button', { name: 'Change Password' }).click();
|
||||
await page.getByLabel('text-field-password').fill('123');
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
await page.getByText("['This password is too short").waitFor();
|
||||
await page
|
||||
.locator('label')
|
||||
.filter({ hasText: 'Override warning' })
|
||||
.locator('div')
|
||||
.first()
|
||||
.click();
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
await page.getByText('Password updated').click();
|
||||
|
||||
// Open profile
|
||||
await page.getByRole('cell', { name: 'Ian', exact: true }).click({
|
||||
button: 'right'
|
||||
});
|
||||
await page.getByRole('button', { name: 'Open Profile' }).click();
|
||||
await page.getByText('User: ian', { exact: true }).click();
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -1311,12 +1311,12 @@
|
||||
dependencies:
|
||||
"@octokit/openapi-types" "^22.2.0"
|
||||
|
||||
"@playwright/test@^1.49.1":
|
||||
version "1.49.1"
|
||||
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.49.1.tgz#55fa360658b3187bfb6371e2f8a64f50ef80c827"
|
||||
integrity sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==
|
||||
"@playwright/test@^1.52.0":
|
||||
version "1.52.0"
|
||||
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.52.0.tgz#267ec595b43a8f4fa5e444ea503689629e91a5b8"
|
||||
integrity sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==
|
||||
dependencies:
|
||||
playwright "1.49.1"
|
||||
playwright "1.52.0"
|
||||
|
||||
"@remix-run/router@1.19.2":
|
||||
version "1.19.2"
|
||||
@ -3955,17 +3955,17 @@ pkg-up@^3.1.0:
|
||||
dependencies:
|
||||
find-up "^3.0.0"
|
||||
|
||||
playwright-core@1.49.1:
|
||||
version "1.49.1"
|
||||
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.49.1.tgz#32c62f046e950f586ff9e35ed490a424f2248015"
|
||||
integrity sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==
|
||||
playwright-core@1.52.0:
|
||||
version "1.52.0"
|
||||
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.52.0.tgz#238f1f0c3edd4ebba0434ce3f4401900319a3dca"
|
||||
integrity sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==
|
||||
|
||||
playwright@1.49.1:
|
||||
version "1.49.1"
|
||||
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.49.1.tgz#830266dbca3008022afa7b4783565db9944ded7c"
|
||||
integrity sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==
|
||||
playwright@1.52.0:
|
||||
version "1.52.0"
|
||||
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.52.0.tgz#26cb9a63346651e1c54c8805acfd85683173d4bd"
|
||||
integrity sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==
|
||||
dependencies:
|
||||
playwright-core "1.49.1"
|
||||
playwright-core "1.52.0"
|
||||
optionalDependencies:
|
||||
fsevents "2.3.2"
|
||||
|
||||
|
Reference in New Issue
Block a user