Skip to content
Merged
4 changes: 3 additions & 1 deletion web/messages/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,7 @@
"search_empty_common_title": "Nothing is found.",
"search_empty_common_subtitle": "Try adjusting your filters or search terms.",
"search_users": "Find users",
"select_users_all": "All users"
"select_users_all": "All users",
"sucessfull_enrollment_email": "Enrollment email sent successfully",
"failed_to_start_enrollment": "Failed to start enrollment"
}
3 changes: 2 additions & 1 deletion web/messages/en/modal.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"modal_add_user_device_manual_download_actions_download_all": "All locations",
"modal_add_user_enrollment_details": "Enrollment details",
"modal_add_user_enrollment_explain": "Provide the user with their token and URL in a convenient manner so they can complete the enrollment process independently.",
"modal_add_user_enrollment_form_label_url": "URL",
"modal_add_user_enrollment_form_label_instance_url": "Defguard instance URL",
"modal_add_user_enrollment_form_label_token": "Activation token",
"modal_add_user_enrollment_form_label_send": "Send enrollment details to user by email",
"modal_edit_user_device_title": "Edit device",
Expand All @@ -85,6 +85,7 @@
"modal_select_log_streaming_destination_title": "Select destination",
"modal_add_log_streaming_destination_title": "Add destination",
"modal_add_user_groups_title": "Assign user to group(s)",
"modal_initiate_self_enrollment_title": "Initiate self-enrollment for user",
"modal_add_user_enroll_title": "Start enrollment for user",
"modal_add_user_choice_enroll_title": "Add user with self-enrollment option",
"modal_add_user_choice_enroll_content": "User will be able to configure their account during the setup of their desktop client (e.g., set their own password, etc.).",
Expand Down
1 change: 1 addition & 0 deletions web/messages/en/users.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@
"users_row_menu_edit": "Edit details",
"users_row_menu_change_password": "Change password",
"users_row_menu_edit_groups": "Edit groups",
"users_row_menu_initiate_self_enrollment": "Initiate self-enrollment",
"modal_edit_user_groups_title": "Edit user groups"
}
2 changes: 2 additions & 0 deletions web/src/pages/UsersOverviewPage/UsersOverviewPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { getUsersQueryOptions } from '../../shared/query';
import { AddUserModal } from './modals/AddUserModal/AddUserModal';
import { AssignUsersToGroupsModal } from './modals/AssignUsersToGroupsModal/AssignUsersToGroupsModal';
import { EditUserModal } from './modals/EditUserModal/EditUserModal';
import { EnrollmentTokenModal } from './modals/EnrollmentTokenModal/EnrollmentTokenModal';
import { UsersTable } from './UsersTable';

export const UsersOverviewPage = () => {
Expand All @@ -23,6 +24,7 @@ export const UsersOverviewPage = () => {
</Page>
<AddUserModal />
<EditUserModal />
<EnrollmentTokenModal />
<AddAuthKeyModal />
<ChangePasswordModal />
<AssignUsersToGroupsModal />
Expand Down
230 changes: 132 additions & 98 deletions web/src/pages/UsersOverviewPage/UsersTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,21 @@ import { Button } from '../../shared/defguard-ui/components/Button/Button';
import type { ButtonProps } from '../../shared/defguard-ui/components/Button/types';
import { EmptyState } from '../../shared/defguard-ui/components/EmptyState/EmptyState';
import { EmptyStateFlexible } from '../../shared/defguard-ui/components/EmptyStateFlexible/EmptyStateFlexible';
import { Icon } from '../../shared/defguard-ui/components/Icon';
import { Icon, IconKind } from '../../shared/defguard-ui/components/Icon';
import { IconButtonMenu } from '../../shared/defguard-ui/components/IconButtonMenu/IconButtonMenu';
import type { MenuItemsGroup } from '../../shared/defguard-ui/components/Menu/types';
import { Search } from '../../shared/defguard-ui/components/Search/Search';
import { tableEditColumnSize } from '../../shared/defguard-ui/components/table/consts';
import { TableBody } from '../../shared/defguard-ui/components/table/TableBody/TableBody';
import { TableCell } from '../../shared/defguard-ui/components/table/TableCell/TableCell';
import { TableFlexCell } from '../../shared/defguard-ui/components/table/TableFlexCell/TableFlexCell';
import { TableRowContainer } from '../../shared/defguard-ui/components/table/TableRowContainer/TableRowContainer';
import { TableTop } from '../../shared/defguard-ui/components/table/TableTop/TableTop';
import { Snackbar } from '../../shared/defguard-ui/providers/snackbar/snackbar';
import { isPresent } from '../../shared/defguard-ui/utils/isPresent';
import { openModal } from '../../shared/hooks/modalControls/modalsSubjects';
import { ModalName } from '../../shared/hooks/modalControls/modalTypes';
import { useApp } from '../../shared/hooks/useApp';
import { getGroupsInfoQueryOptions } from '../../shared/query';
import { displayDate } from '../../shared/utils/displayDate';
import { useAddUserModal } from './modals/AddUserModal/useAddUserModal';
Expand All @@ -51,6 +54,7 @@ type RowData = UsersListItem;
const columnHelper = createColumnHelper<RowData>();

export const UsersTable = ({ users }: Props) => {
const appInfo = useApp((s) => s.appInfo);
const reservedEmails = useMemo(() => users.map((u) => u.email.toLowerCase()), [users]);
const reservedUsernames = useMemo(() => users.map((u) => u.username), [users]);

Expand Down Expand Up @@ -235,109 +239,138 @@ export const UsersTable = ({ users }: Props) => {
enableResizing: false,
cell: (info) => {
const rowData = info.row.original;
return (
<TableCell>
<IconButtonMenu
icon="menu"
menuItems={[
{
items: [
{
text: m.users_row_menu_edit(),
icon: 'edit',
onClick: () => {
openModal(ModalName.EditUserModal, {
user: rowData,
reservedEmails,
reservedUsernames,
});
},
},
{
text: m.users_row_menu_change_password(),
icon: 'lock-open',
testId: 'change-password',
onClick: () => {
openModal(ModalName.ChangePassword, {
adminForm: true,
user: rowData,
});
},
},
{
text: m.users_row_menu_go_profile(),
icon: 'profile',
onClick: () => {
navigate({
to: '/user/$username',
params: {
username: rowData.username,
},
});
},
},
{
text: m.users_row_menu_edit_groups(),
icon: 'add-group',
testId: 'edit-groups',
onClick: () => {
useSelectionModal.setState({
isOpen: true,
options: groupsOptions,
title: m.modal_edit_user_groups_title(),
selected: new Set(rowData.groups),
onSubmit: (selected) => {
handleEditGroups(rowData, selected as string[]);
},
});
},
},
],

const menuItems: MenuItemsGroup[] = [
{
items: [
{
text: m.users_row_menu_edit(),
icon: 'edit',
onClick: () => {
openModal(ModalName.EditUserModal, {
user: rowData,
reservedEmails,
reservedUsernames,
});
},
{
items: [
{
text: m.users_row_menu_add_auth(),
icon: 'key',
onClick: () => {
openModal(ModalName.AddAuthKey, {
username: rowData.username,
});
},
},
],
},
{
text: m.users_row_menu_change_password(),
icon: 'lock-open',
testId: 'change-password',
onClick: () => {
openModal(ModalName.ChangePassword, {
adminForm: true,
user: rowData,
});
},
{
items: [
{
text: rowData.is_active
? m.users_row_menu_disable()
: m.users_row_menu_enable(),
icon: rowData.is_active ? 'disabled' : 'check-circle',
testId: 'change-account-status',
onClick: () => {
changeAccountActiveState({
active: !rowData.is_active,
username: rowData.username,
});
},
},
{
text: m.users_row_menu_go_profile(),
icon: 'profile',
onClick: () => {
navigate({
to: '/user/$username',
params: {
username: rowData.username,
},
],
});
},
{
items: [
{
text: m.users_row_menu_delete(),
icon: 'delete',
variant: 'danger',
onClick: () => {
deleteUser(rowData.username);
},
},
{
text: m.users_row_menu_edit_groups(),
icon: 'add-group',
testId: 'edit-groups',
onClick: () => {
useSelectionModal.setState({
isOpen: true,
options: groupsOptions,
title: m.modal_edit_user_groups_title(),
selected: new Set(rowData.groups),
onSubmit: (selected) => {
handleEditGroups(rowData, selected as string[]);
},
],
});
},
]}
/>
},
],
},
{
items: [
{
text: m.users_row_menu_add_auth(),
icon: 'key',
onClick: () => {
openModal(ModalName.AddAuthKey, {
username: rowData.username,
});
},
},
],
},
{
items: [
{
text: rowData.is_active
? m.users_row_menu_disable()
: m.users_row_menu_enable(),
icon: rowData.is_active ? 'disabled' : 'check-circle',
testId: 'change-account-status',
onClick: () => {
changeAccountActiveState({
active: !rowData.is_active,
username: rowData.username,
});
},
},
],
},
{
items: [
{
text: m.users_row_menu_delete(),
icon: 'delete',
variant: 'danger',
onClick: () => {
deleteUser(rowData.username);
},
},
],
},
];

if (!rowData.enrolled) {
menuItems.splice(1, 0, {
items: [
{
text: m.users_row_menu_initiate_self_enrollment(),
icon: IconKind.AddUser,
onClick: () => {
api.user
.startEnrollment({
send_enrollment_notification: false,
username: rowData.username,
})
.then((response) => {
openModal(ModalName.SelfEnrollmentToken, {
user: rowData,
appInfo,
enrollmentResponse: response.data,
});
})
.catch((error) => {
Snackbar.error('Failed to initiate enrollment');
console.error(error);
});
},
},
],
});
}

return (
<TableCell>
<IconButtonMenu icon="menu" menuItems={menuItems} />
</TableCell>
);
},
Expand All @@ -352,6 +385,7 @@ export const UsersTable = ({ users }: Props) => {
groupsOptions,
handleEditGroups,
groups,
appInfo,
],
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ const EnrollmentStep = () => {
<SizedBox height={ThemeSpacing.Xl2} />
<CopyField
copyTooltip={m.misc_clipboard_copy()}
label={m.modal_add_user_enrollment_form_label_url()}
label={m.modal_add_user_enrollment_form_label_instance_url()}
data-testid="activation-url-field"
text={enrollResponse.enrollment_url}
/>
Expand Down Expand Up @@ -358,13 +358,19 @@ const AddUserModalForm = () => {
data: { groups },
} = await api.group.getGroups();
if (enrollmentEnabled) {
const enrollmentResponse = (
await api.user.startEnrollment({
send_enrollment_notification: false,
username: created.username,
})
).data;
useAddUserModal.setState({ enrollResponse: enrollmentResponse });
try {
const enrollmentResponse = (
await api.user.startEnrollment({
send_enrollment_notification: false,
username: created.username,
})
).data;
useAddUserModal.setState({ enrollResponse: enrollmentResponse, user: created });
} catch (error) {
console.error(m.failed_to_start_enrollment(), error);
useAddUserModal.setState({ isOpen: false });
return;
}
}
if (assignToGroups) {
useAddUserModal.setState({
Expand Down
Loading
Loading