mirror of
https://github.com/etkecc/synapse-admin.git
synced 2024-11-21 23:35:22 +03:00
Prevent self user delete
This commit is contained in:
parent
dbcb4f92dc
commit
056d9c6b4c
8 changed files with 101 additions and 17 deletions
|
@ -142,6 +142,7 @@ const de: SynapseTranslationMessages = {
|
||||||
password: "Durch die Änderung des Passworts wird der Benutzer von allen Sitzungen abgemeldet.",
|
password: "Durch die Änderung des Passworts wird der Benutzer von allen Sitzungen abgemeldet.",
|
||||||
deactivate: "Sie müssen ein Passwort angeben, um ein Konto wieder zu aktivieren.",
|
deactivate: "Sie müssen ein Passwort angeben, um ein Konto wieder zu aktivieren.",
|
||||||
erase: "DSGVO konformes Löschen der Benutzerdaten",
|
erase: "DSGVO konformes Löschen der Benutzerdaten",
|
||||||
|
erase_admin_error: "Das Löschen des eigenen Benutzers ist nicht erlaubt",
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
erase: "Lösche Benutzerdaten",
|
erase: "Lösche Benutzerdaten",
|
||||||
|
|
|
@ -141,6 +141,7 @@ const en: SynapseTranslationMessages = {
|
||||||
password: "Changing password will log user out of all sessions.",
|
password: "Changing password will log user out of all sessions.",
|
||||||
deactivate: "You must provide a password to re-activate an account.",
|
deactivate: "You must provide a password to re-activate an account.",
|
||||||
erase: "Mark the user as GDPR-erased",
|
erase: "Mark the user as GDPR-erased",
|
||||||
|
erase_admin_error: "Deleting own user is not allowed.",
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
erase: "Erase user data",
|
erase: "Erase user data",
|
||||||
|
|
|
@ -139,6 +139,7 @@ const fr: SynapseTranslationMessages = {
|
||||||
helper: {
|
helper: {
|
||||||
deactivate: "Vous devrez fournir un mot de passe pour réactiver le compte.",
|
deactivate: "Vous devrez fournir un mot de passe pour réactiver le compte.",
|
||||||
erase: "Marquer l'utilisateur comme effacé conformément au RGPD",
|
erase: "Marquer l'utilisateur comme effacé conformément au RGPD",
|
||||||
|
erase_admin_error: "La suppression de son propre utilisateur n'est pas autorisée.",
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
erase: "Effacer les données de l'utilisateur",
|
erase: "Effacer les données de l'utilisateur",
|
||||||
|
|
1
src/i18n/index.d.ts
vendored
1
src/i18n/index.d.ts
vendored
|
@ -137,6 +137,7 @@ interface SynapseTranslationMessages extends TranslationMessages {
|
||||||
password?: string;
|
password?: string;
|
||||||
deactivate: string;
|
deactivate: string;
|
||||||
erase: string;
|
erase: string;
|
||||||
|
erase_admin_error: string;
|
||||||
};
|
};
|
||||||
action: {
|
action: {
|
||||||
erase: string;
|
erase: string;
|
||||||
|
|
|
@ -141,6 +141,7 @@ const it: SynapseTranslationMessages = {
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
erase: "Cancella i dati dell'utente",
|
erase: "Cancella i dati dell'utente",
|
||||||
|
erase_admin_error: "Non è consentito eliminare il proprio utente.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
rooms: {
|
rooms: {
|
||||||
|
|
|
@ -150,6 +150,7 @@ const ru: SynapseTranslationMessages = {
|
||||||
password: "Смена пароля завершит все сессии пользователя.",
|
password: "Смена пароля завершит все сессии пользователя.",
|
||||||
deactivate: "Вы должны предоставить пароль для реактивации учётной записи.",
|
deactivate: "Вы должны предоставить пароль для реактивации учётной записи.",
|
||||||
erase: "Пометить пользователя как удалённого в соответствии с GDPR",
|
erase: "Пометить пользователя как удалённого в соответствии с GDPR",
|
||||||
|
erase_admin_error: "Удаление собственного пользователя запрещено.",
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
erase: "Удалить данные пользователя",
|
erase: "Удалить данные пользователя",
|
||||||
|
|
|
@ -134,6 +134,7 @@ const zh: SynapseTranslationMessages = {
|
||||||
helper: {
|
helper: {
|
||||||
deactivate: "您必须提供一串密码来激活账户。",
|
deactivate: "您必须提供一串密码来激活账户。",
|
||||||
erase: "将用户标记为根据 GDPR 的要求抹除了",
|
erase: "将用户标记为根据 GDPR 的要求抹除了",
|
||||||
|
erase_admin_error: "不允许删除自己的用户",
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
erase: "抹除用户信息",
|
erase: "抹除用户信息",
|
||||||
|
|
|
@ -8,6 +8,8 @@ import PermMediaIcon from "@mui/icons-material/PermMedia";
|
||||||
import PersonPinIcon from "@mui/icons-material/PersonPin";
|
import PersonPinIcon from "@mui/icons-material/PersonPin";
|
||||||
import SettingsInputComponentIcon from "@mui/icons-material/SettingsInputComponent";
|
import SettingsInputComponentIcon from "@mui/icons-material/SettingsInputComponent";
|
||||||
import ViewListIcon from "@mui/icons-material/ViewList";
|
import ViewListIcon from "@mui/icons-material/ViewList";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Alert, ownerDocument } from "@mui/material";
|
||||||
import {
|
import {
|
||||||
ArrayInput,
|
ArrayInput,
|
||||||
ArrayField,
|
ArrayField,
|
||||||
|
@ -42,11 +44,15 @@ import {
|
||||||
useRecordContext,
|
useRecordContext,
|
||||||
useTranslate,
|
useTranslate,
|
||||||
Pagination,
|
Pagination,
|
||||||
|
SaveButton,
|
||||||
CreateButton,
|
CreateButton,
|
||||||
ExportButton,
|
ExportButton,
|
||||||
TopToolbar,
|
TopToolbar,
|
||||||
|
Toolbar,
|
||||||
NumberField,
|
NumberField,
|
||||||
useListContext,
|
useListContext,
|
||||||
|
useNotify,
|
||||||
|
ToolbarClasses,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
@ -92,16 +98,47 @@ const userFilters = [
|
||||||
<BooleanInput label="resources.users.fields.show_deactivated" source="deactivated" alwaysOn />,
|
<BooleanInput label="resources.users.fields.show_deactivated" source="deactivated" alwaysOn />,
|
||||||
];
|
];
|
||||||
|
|
||||||
const UserBulkActionButtons = () => (
|
const UserPreventSelfDelete: React.FC<{ children: React.ReactNode, ownUserIsSelected: boolean }> = (props) => {
|
||||||
<>
|
const ownUserIsSelected = props.ownUserIsSelected;
|
||||||
|
const notify = useNotify();
|
||||||
|
const translate = useTranslate();
|
||||||
|
|
||||||
|
const handleDeleteClick = (ev: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
if (ownUserIsSelected) {
|
||||||
|
notify(<Alert severity="error">{translate("resources.users.helper.erase_admin_error")}</Alert>)
|
||||||
|
ev.stopPropagation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return <div onClickCapture={handleDeleteClick}>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
};
|
||||||
|
|
||||||
|
const UserBulkActionButtons = () => {
|
||||||
|
const record = useListContext();
|
||||||
|
const [ ownUserIsSelected, setOwnUserIsSelected ] = useState(false);
|
||||||
|
const selectedIds = record.selectedIds;
|
||||||
|
const ownUserId = localStorage.getItem("user_id");
|
||||||
|
const notify = useNotify();
|
||||||
|
const translate = useTranslate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setOwnUserIsSelected(selectedIds.includes(ownUserId));
|
||||||
|
}, [ selectedIds ]);
|
||||||
|
|
||||||
|
|
||||||
|
return <>
|
||||||
<ServerNoticeBulkButton />
|
<ServerNoticeBulkButton />
|
||||||
|
<UserPreventSelfDelete ownUserIsSelected={ownUserIsSelected}>
|
||||||
<BulkDeleteButton
|
<BulkDeleteButton
|
||||||
label="resources.users.action.erase"
|
label="resources.users.action.erase"
|
||||||
confirmTitle="resources.users.helper.erase"
|
confirmTitle="resources.users.helper.erase"
|
||||||
mutationMode="pessimistic"
|
mutationMode="pessimistic"
|
||||||
/>
|
/>
|
||||||
|
</UserPreventSelfDelete>
|
||||||
</>
|
</>
|
||||||
);
|
};
|
||||||
|
|
||||||
export const UserList = (props: ListProps) => (
|
export const UserList = (props: ListProps) => (
|
||||||
<List
|
<List
|
||||||
|
@ -137,10 +174,16 @@ const validateAddress = [required(), maxLength(255)];
|
||||||
const UserEditActions = () => {
|
const UserEditActions = () => {
|
||||||
const record = useRecordContext();
|
const record = useRecordContext();
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
|
const ownUserId = localStorage.getItem("user_id");
|
||||||
|
let ownUserIsSelected = false;
|
||||||
|
if (record && record.id) {
|
||||||
|
ownUserIsSelected = record.id === ownUserId;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TopToolbar>
|
<TopToolbar>
|
||||||
{!record?.deactivated && <ServerNoticeButton />}
|
{!record?.deactivated && <ServerNoticeButton />}
|
||||||
|
<UserPreventSelfDelete ownUserIsSelected={ownUserIsSelected}>
|
||||||
<DeleteButton
|
<DeleteButton
|
||||||
label="resources.users.action.erase"
|
label="resources.users.action.erase"
|
||||||
confirmTitle={translate("resources.users.helper.erase", {
|
confirmTitle={translate("resources.users.helper.erase", {
|
||||||
|
@ -148,6 +191,7 @@ const UserEditActions = () => {
|
||||||
})}
|
})}
|
||||||
mutationMode="pessimistic"
|
mutationMode="pessimistic"
|
||||||
/>
|
/>
|
||||||
|
</UserPreventSelfDelete>
|
||||||
</TopToolbar>
|
</TopToolbar>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -189,11 +233,44 @@ const UserTitle = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const UserEditToolbar = () => {
|
||||||
|
const record = useRecordContext();
|
||||||
|
const ownUserId = localStorage.getItem("user_id");
|
||||||
|
let ownUserIsSelected = false;
|
||||||
|
if (record && record.id) {
|
||||||
|
ownUserIsSelected = record.id === ownUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<div className={ToolbarClasses.defaultToolbar}>
|
||||||
|
<Toolbar sx={{ justifyContent: "space-between" }}>
|
||||||
|
<SaveButton />
|
||||||
|
<UserPreventSelfDelete ownUserIsSelected={ownUserIsSelected}>
|
||||||
|
<DeleteButton />
|
||||||
|
</UserPreventSelfDelete>
|
||||||
|
</Toolbar>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
};
|
||||||
|
|
||||||
|
const UserBooleanInput = (props) => {
|
||||||
|
const record = useRecordContext();
|
||||||
|
const ownUserId = localStorage.getItem("user_id");
|
||||||
|
const isOwnUser = false;
|
||||||
|
let ownUserIsSelected = false;
|
||||||
|
if (record && (record.id === ownUserId)) {
|
||||||
|
ownUserIsSelected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <UserPreventSelfDelete ownUserIsSelected={ownUserIsSelected}><BooleanInput {...props} disabled={ownUserIsSelected} /></UserPreventSelfDelete>
|
||||||
|
}
|
||||||
|
|
||||||
export const UserEdit = (props: EditProps) => {
|
export const UserEdit = (props: EditProps) => {
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Edit {...props} title={<UserTitle />} actions={<UserEditActions />}>
|
<Edit {...props} title={<UserTitle />} actions={<UserEditActions />}>
|
||||||
<TabbedForm>
|
<TabbedForm toolbar={<UserEditToolbar />}>
|
||||||
<FormTab label={translate("resources.users.name", { smart_count: 1 })} icon={<PersonPinIcon />}>
|
<FormTab label={translate("resources.users.name", { smart_count: 1 })} icon={<PersonPinIcon />}>
|
||||||
<AvatarField source="avatar_src" sortable={false} sx={{ height: "120px", width: "120px", float: "right" }} />
|
<AvatarField source="avatar_src" sortable={false} sx={{ height: "120px", width: "120px", float: "right" }} />
|
||||||
<TextInput source="id" disabled />
|
<TextInput source="id" disabled />
|
||||||
|
@ -202,7 +279,7 @@ export const UserEdit = (props: EditProps) => {
|
||||||
<SelectInput source="user_type" choices={choices_type} translateChoice={false} resettable />
|
<SelectInput source="user_type" choices={choices_type} translateChoice={false} resettable />
|
||||||
<BooleanInput source="admin" />
|
<BooleanInput source="admin" />
|
||||||
<BooleanInput source="locked" />
|
<BooleanInput source="locked" />
|
||||||
<BooleanInput source="deactivated" helperText="resources.users.helper.deactivate" />
|
<UserBooleanInput source="deactivated" helperText="resources.users.helper.deactivate" />
|
||||||
<BooleanInput source="erased" disabled />
|
<BooleanInput source="erased" disabled />
|
||||||
<DateField source="creation_ts_ms" showTime options={DATE_FORMAT} />
|
<DateField source="creation_ts_ms" showTime options={DATE_FORMAT} />
|
||||||
<TextField source="consent_version" />
|
<TextField source="consent_version" />
|
||||||
|
|
Loading…
Reference in a new issue