Use custom data provider method for "delete_media"

This is not a REST endpoint, so it's better to use a custom method, see
https://marmelab.com/react-admin/DataProviders.html#adding-custom-methods

Change-Id: I256286949e77b998f759f671b2d4e9790f8ca39c
This commit is contained in:
Manuel Stahl 2024-04-24 10:13:56 +02:00
parent f55e02730e
commit ec0fc14b68
9 changed files with 175 additions and 149 deletions

View file

@ -21,15 +21,18 @@ import {
Toolbar, Toolbar,
ToolbarProps, ToolbarProps,
useCreate, useCreate,
useDataProvider,
useDelete, useDelete,
useNotify, useNotify,
useRecordContext, useRecordContext,
useRefresh, useRefresh,
useTranslate, useTranslate,
} from "react-admin"; } from "react-admin";
import { useMutation } from "react-query";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { dateParser } from "./date"; import { dateParser } from "./date";
import { DeleteMediaParams, SynapseDataProvider } from "../synapse/dataProvider";
import { getMediaUrl } from "../synapse/synapse"; import { getMediaUrl } from "../synapse/synapse";
const DeleteMediaDialog = ({ open, onClose, onSubmit }) => { const DeleteMediaDialog = ({ open, onClose, onSubmit }) => {
@ -37,7 +40,7 @@ const DeleteMediaDialog = ({ open, onClose, onSubmit }) => {
const DeleteMediaToolbar = (props: ToolbarProps) => ( const DeleteMediaToolbar = (props: ToolbarProps) => (
<Toolbar {...props}> <Toolbar {...props}>
<SaveButton label="resources.delete_media.action.send" icon={<DeleteSweepIcon />} /> <SaveButton label="delete_media.action.send" icon={<DeleteSweepIcon />} />
<Button label="ra.action.cancel" onClick={onClose}> <Button label="ra.action.cancel" onClick={onClose}>
<IconCancel /> <IconCancel />
</Button> </Button>
@ -46,21 +49,21 @@ const DeleteMediaDialog = ({ open, onClose, onSubmit }) => {
return ( return (
<Dialog open={open} onClose={onClose}> <Dialog open={open} onClose={onClose}>
<DialogTitle>{translate("resources.delete_media.action.send")}</DialogTitle> <DialogTitle>{translate("delete_media.action.send")}</DialogTitle>
<DialogContent> <DialogContent>
<DialogContentText>{translate("resources.delete_media.helper.send")}</DialogContentText> <DialogContentText>{translate("delete_media.helper.send")}</DialogContentText>
<SimpleForm toolbar={<DeleteMediaToolbar />} onSubmit={onSubmit}> <SimpleForm toolbar={<DeleteMediaToolbar />} onSubmit={onSubmit}>
<DateTimeInput <DateTimeInput
fullWidth fullWidth
source="before_ts" source="before_ts"
label="resources.delete_media.fields.before_ts" label="delete_media.fields.before_ts"
defaultValue={0} defaultValue={0}
parse={dateParser} parse={dateParser}
/> />
<NumberInput <NumberInput
fullWidth fullWidth
source="size_gt" source="size_gt"
label="resources.delete_media.fields.size_gt" label="delete_media.fields.size_gt"
defaultValue={0} defaultValue={0}
min={0} min={0}
step={1024} step={1024}
@ -68,7 +71,7 @@ const DeleteMediaDialog = ({ open, onClose, onSubmit }) => {
<BooleanInput <BooleanInput
fullWidth fullWidth
source="keep_profiles" source="keep_profiles"
label="resources.delete_media.fields.keep_profiles" label="delete_media.fields.keep_profiles"
defaultValue={true} defaultValue={true}
/> />
</SimpleForm> </SimpleForm>
@ -81,34 +84,30 @@ export const DeleteMediaButton = (props: ButtonProps) => {
const theme = useTheme(); const theme = useTheme();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const notify = useNotify(); const notify = useNotify();
const [deleteOne, { isLoading }] = useDelete(); const dataProvider = useDataProvider<SynapseDataProvider>();
const { mutate: deleteMedia, isLoading } = useMutation(
(values: DeleteMediaParams) => dataProvider.deleteMedia(values),
{
onSuccess: () => {
notify("delete_media.action.send_success");
closeDialog();
},
onError: () => {
notify("delete_media.action.send_failure", {
type: "error",
});
},
}
);
const openDialog = () => setOpen(true); const openDialog = () => setOpen(true);
const closeDialog = () => setOpen(false); const closeDialog = () => setOpen(false);
const deleteMedia = (values: { before_ts: string; size_gt: number; keep_profiles: boolean }) => {
deleteOne(
"delete_media",
// needs meta.before_ts, meta.size_gt and meta.keep_profiles
{ meta: values },
{
onSuccess: () => {
notify("resources.delete_media.action.send_success");
closeDialog();
},
onError: () =>
notify("resources.delete_media.action.send_failure", {
type: "error",
}),
}
);
};
return ( return (
<> <>
<Button <Button
{...props} {...props}
label="resources.delete_media.action.send" label="delete_media.action.send"
onClick={openDialog} onClick={openDialog}
disabled={isLoading} disabled={isLoading}
sx={{ sx={{

View file

@ -92,6 +92,22 @@ const de: SynapseTranslationMessages = {
}, },
}, },
}, },
delete_media: {
name: "Medien",
fields: {
before_ts: "Letzter Zugriff vor",
size_gt: "Größer als (in Bytes)",
keep_profiles: "Behalte Profilbilder",
},
action: {
send: "Medien löschen",
send_success: "Anfrage erfolgreich versendet.",
send_failure: "Beim Versenden ist ein Fehler aufgetreten.",
},
helper: {
send: "Diese API löscht die lokalen Medien von der Festplatte des eigenen Servers. Dies umfasst alle lokalen Miniaturbilder und Kopien von Medien. Diese API wirkt sich nicht auf Medien aus, die sich in externen Medien-Repositories befinden.",
},
},
resources: { resources: {
users: { users: {
name: "Benutzer", name: "Benutzer",
@ -260,22 +276,6 @@ const de: SynapseTranslationMessages = {
open: "Mediendatei in neuem Fenster öffnen", open: "Mediendatei in neuem Fenster öffnen",
}, },
}, },
delete_media: {
name: "Medien",
fields: {
before_ts: "Letzter Zugriff vor",
size_gt: "Größer als (in Bytes)",
keep_profiles: "Behalte Profilbilder",
},
action: {
send: "Medien löschen",
send_success: "Anfrage erfolgreich versendet.",
send_failure: "Beim Versenden ist ein Fehler aufgetreten.",
},
helper: {
send: "Diese API löscht die lokalen Medien von der Festplatte des eigenen Servers. Dies umfasst alle lokalen Miniaturbilder und Kopien von Medien. Diese API wirkt sich nicht auf Medien aus, die sich in externen Medien-Repositories befinden.",
},
},
protect_media: { protect_media: {
action: { action: {
create: "Ungeschützt, Schutz erstellen", create: "Ungeschützt, Schutz erstellen",

View file

@ -91,6 +91,22 @@ const en: SynapseTranslationMessages = {
}, },
}, },
}, },
delete_media: {
name: "Media",
fields: {
before_ts: "last access before",
size_gt: "Larger then (in bytes)",
keep_profiles: "Keep profile images",
},
action: {
send: "Delete media",
send_success: "Request successfully sent.",
send_failure: "An error has occurred.",
},
helper: {
send: "This API deletes the local media from the disk of your own server. This includes any local thumbnails and copies of media downloaded. This API will not affect media that has been uploaded to external media repositories.",
},
},
resources: { resources: {
users: { users: {
name: "User |||| Users", name: "User |||| Users",
@ -258,22 +274,6 @@ const en: SynapseTranslationMessages = {
open: "Open media file in new window", open: "Open media file in new window",
}, },
}, },
delete_media: {
name: "Media",
fields: {
before_ts: "last access before",
size_gt: "Larger then (in bytes)",
keep_profiles: "Keep profile images",
},
action: {
send: "Delete media",
send_success: "Request successfully sent.",
send_failure: "An error has occurred.",
},
helper: {
send: "This API deletes the local media from the disk of your own server. This includes any local thumbnails and copies of media downloaded. This API will not affect media that has been uploaded to external media repositories.",
},
},
protect_media: { protect_media: {
action: { action: {
create: "Unprotected, create protection", create: "Unprotected, create protection",

View file

@ -89,6 +89,22 @@ const fa: SynapseTranslationMessages = {
}, },
}, },
}, },
delete_media: {
name: "رسانه ها",
fields: {
before_ts: "آخرین دسترسی قبل",
size_gt: "بزرگتر از آن (به بایت)",
keep_profiles: "تصاویر پروفایل را نگه دارید",
},
action: {
send: "حذف رسانه ها",
send_success: "درخواست با موفقیت ارسال شد.",
send_failure: "خطایی رخ داده است.",
},
helper: {
send: "این API رسانه های محلی را از دیسک سرور خود حذف می کند. این شامل هر تصویر کوچک محلی و کپی از رسانه دانلود شده است. این API بر رسانه‌هایی که در مخازن رسانه خارجی آپلود شده‌اند تأثیری نخواهد گذاشت.",
},
},
resources: { resources: {
users: { users: {
name: "کاربر |||| کاربران", name: "کاربر |||| کاربران",
@ -241,22 +257,6 @@ const fa: SynapseTranslationMessages = {
last_access_ts: "آخرین دسترسی", last_access_ts: "آخرین دسترسی",
}, },
}, },
delete_media: {
name: "رسانه ها",
fields: {
before_ts: "آخرین دسترسی قبل",
size_gt: "بزرگتر از آن (به بایت)",
keep_profiles: "تصاویر پروفایل را نگه دارید",
},
action: {
send: "حذف رسانه ها",
send_success: "درخواست با موفقیت ارسال شد.",
send_failure: "خطایی رخ داده است.",
},
helper: {
send: "این API رسانه های محلی را از دیسک سرور خود حذف می کند. این شامل هر تصویر کوچک محلی و کپی از رسانه دانلود شده است. این API بر رسانه‌هایی که در مخازن رسانه خارجی آپلود شده‌اند تأثیری نخواهد گذاشت.",
},
},
protect_media: { protect_media: {
action: { action: {
create: "محافظت نشده، حفاظت ایجاد کنید", create: "محافظت نشده، حفاظت ایجاد کنید",

View file

@ -92,6 +92,22 @@ const fr: SynapseTranslationMessages = {
}, },
}, },
}, },
delete_media: {
name: "Media",
fields: {
before_ts: "Dernier accès avant",
size_gt: "Plus grand que (en octets)",
keep_profiles: "Conserver les images de profil",
},
action: {
send: "Supprimer le média",
send_success: "Requête envoyée avec succès",
send_failure: "Une erreur s'est produite",
},
helper: {
send: "Cette API supprime les médias locaux du disque de votre propre serveur. Cela inclut toutes les vignettes locales et les copies des médias téléchargés. Cette API n'affectera pas les médias qui ont été téléversés dans des dépôts de médias externes.",
},
},
resources: { resources: {
users: { users: {
name: "Utilisateur |||| Utilisateurs", name: "Utilisateur |||| Utilisateurs",
@ -243,22 +259,6 @@ const fr: SynapseTranslationMessages = {
last_access_ts: "Dernier accès", last_access_ts: "Dernier accès",
}, },
}, },
delete_media: {
name: "Media",
fields: {
before_ts: "Dernier accès avant",
size_gt: "Plus grand que (en octets)",
keep_profiles: "Conserver les images de profil",
},
action: {
send: "Supprimer le média",
send_success: "Requête envoyée avec succès",
send_failure: "Une erreur s'est produite",
},
helper: {
send: "Cette API supprime les médias locaux du disque de votre propre serveur. Cela inclut toutes les vignettes locales et les copies des médias téléchargés. Cette API n'affectera pas les médias qui ont été téléversés dans des dépôts de médias externes.",
},
},
protect_media: { protect_media: {
action: { action: {
create: "Protéger", create: "Protéger",

32
src/i18n/index.d.ts vendored
View file

@ -87,6 +87,22 @@ interface SynapseTranslationMessages extends TranslationMessages {
}; };
}; };
}; };
delete_media: {
name: string;
fields: {
before_ts: string;
size_gt: string;
keep_profiles: string;
};
action: {
send: string;
send_success: string;
send_failure: string;
};
helper: {
send: string;
};
};
resources: { resources: {
users: { users: {
name: string; name: string;
@ -252,22 +268,6 @@ interface SynapseTranslationMessages extends TranslationMessages {
open: string; open: string;
}; };
}; };
delete_media: {
name: string;
fields: {
before_ts: string;
size_gt: string;
keep_profiles: string;
};
action: {
send: string;
send_success: string;
send_failure: string;
};
helper: {
send: string;
};
};
protect_media?: { protect_media?: {
action: { action: {
create: string; create: string;

View file

@ -89,6 +89,22 @@ const it: SynapseTranslationMessages = {
}, },
}, },
}, },
delete_media: {
name: "Media",
fields: {
before_ts: "ultimo accesso effettuato prima",
size_gt: "Più grande di (in byte)",
keep_profiles: "Mantieni le immagini del profilo",
},
action: {
send: "Cancella media",
send_success: "Richiesta inviata con successo.",
send_failure: "C'è stato un errore.",
},
helper: {
send: "Questa API cancella i media locali dal disco del tuo server. Questo include anche ogni miniatura e copia del media scaricato. Questa API non inciderà sui media che sono stati caricati nei repository esterni.",
},
},
resources: { resources: {
users: { users: {
name: "Utente |||| Utenti", name: "Utente |||| Utenti",
@ -242,22 +258,6 @@ const it: SynapseTranslationMessages = {
last_access_ts: "Ultimo accesso", last_access_ts: "Ultimo accesso",
}, },
}, },
delete_media: {
name: "Media",
fields: {
before_ts: "ultimo accesso effettuato prima",
size_gt: "Più grande di (in byte)",
keep_profiles: "Mantieni le immagini del profilo",
},
action: {
send: "Cancella media",
send_success: "Richiesta inviata con successo.",
send_failure: "C'è stato un errore.",
},
helper: {
send: "Questa API cancella i media locali dal disco del tuo server. Questo include anche ogni miniatura e copia del media scaricato. Questa API non inciderà sui media che sono stati caricati nei repository esterni.",
},
},
protect_media: { protect_media: {
action: { action: {
create: "Non protetto, proteggi", create: "Non protetto, proteggi",

View file

@ -89,6 +89,22 @@ const zh: SynapseTranslationMessages = {
}, },
}, },
}, },
delete_media: {
name: "媒体文件",
fields: {
before_ts: "最后访问时间",
size_gt: "大于 (字节)",
keep_profiles: "保留头像",
},
action: {
send: "删除媒体",
send_success: "请求发送成功。",
send_failure: "出现了一个错误。",
},
helper: {
send: "这个API会删除您硬盘上的本地媒体。包含了任何的本地缓存和下载的媒体备份。这个API不会影响上传到外部媒体存储库上的媒体文件。",
},
},
resources: { resources: {
users: { users: {
name: "用户", name: "用户",
@ -224,22 +240,6 @@ const zh: SynapseTranslationMessages = {
last_access_ts: "上一次访问", last_access_ts: "上一次访问",
}, },
}, },
delete_media: {
name: "媒体文件",
fields: {
before_ts: "最后访问时间",
size_gt: "大于 (字节)",
keep_profiles: "保留头像",
},
action: {
send: "删除媒体",
send_success: "请求发送成功。",
send_failure: "出现了一个错误。",
},
helper: {
send: "这个API会删除您硬盘上的本地媒体。包含了任何的本地缓存和下载的媒体备份。这个API不会影响上传到外部媒体存储库上的媒体文件。",
},
},
pushers: { pushers: {
name: "发布者", name: "发布者",
fields: { fields: {

View file

@ -201,6 +201,21 @@ interface DestinationRoom {
stream_ordering: number; stream_ordering: number;
} }
export interface DeleteMediaParams {
before_ts: string;
size_gt: number;
keep_profiles: boolean;
}
export interface DeleteMediaResult {
deleted_media: Identifier[];
total: number;
}
export interface SynapseDataProvider extends DataProvider {
deleteMedia: (params: DeleteMediaParams) => Promise<DeleteMediaResult>;
}
const resourceMap = { const resourceMap = {
users: { users: {
path: "/_synapse/admin/v2/users", path: "/_synapse/admin/v2/users",
@ -329,16 +344,6 @@ const resourceMap = {
endpoint: `/_synapse/admin/v1/media/${localStorage.getItem("home_server")}/${params.id}`, endpoint: `/_synapse/admin/v1/media/${localStorage.getItem("home_server")}/${params.id}`,
}), }),
}, },
delete_media: {
delete: (params: DeleteParams) => ({
endpoint: `/_synapse/admin/v1/media/${localStorage.getItem(
"home_server"
)}/delete?before_ts=${params.meta.before_ts}&size_gt=${
params.meta.size_gt
}&keep_profiles=${params.meta.keep_profiles}`,
method: "POST",
}),
},
protect_media: { protect_media: {
map: (pm: UserMedia) => ({ id: pm.media_id }), map: (pm: UserMedia) => ({ id: pm.media_id }),
create: (params: UserMedia) => ({ create: (params: UserMedia) => ({
@ -481,7 +486,7 @@ function getSearchOrder(order: "ASC" | "DESC") {
} }
} }
const dataProvider: DataProvider = { const dataProvider: SynapseDataProvider = {
getList: async (resource, params) => { getList: async (resource, params) => {
console.log("getList " + resource); console.log("getList " + resource);
const { user_id, name, guests, deactivated, search_term, destination, valid } = params.filter; const { user_id, name, guests, deactivated, search_term, destination, valid } = params.filter;
@ -700,6 +705,28 @@ const dataProvider: DataProvider = {
return { data: responses.map(({ json }) => json) }; return { data: responses.map(({ json }) => json) };
} }
}, },
// Custom methods (https://marmelab.com/react-admin/DataProviders.html#adding-custom-methods)
/**
* Delete media by date or size
*
* @link https://matrix-org.github.io/synapse/latest/admin_api/media_admin_api.html#delete-local-media-by-date-or-size
*
* @param before_ts Unix timestamp in milliseconds. Files that were last used before this timestamp will be deleted. It is the timestamp of last access, not the timestamp when the file was created.
* @param size_gt Size of the media in bytes. Files that are larger will be deleted.
* @param keep_profiles Switch to also delete files that are still used in image data (e.g user profile, room avatar). If false these files will be deleted.
* @returns
*/
deleteMedia: async ({ before_ts, size_gt = 0, keep_profiles = true }) => {
const homeserver = localStorage.getItem("home_server"); // TODO only required for synapse < 1.78.0
const endpoint = `/_synapse/admin/v1/media/${homeserver}/delete?before_ts=${before_ts}&size_gt=${size_gt}&keep_profiles=${keep_profiles}`;
const base_url = localStorage.getItem("base_url");
const endpoint_url = base_url + endpoint;
const { json } = await jsonClient(endpoint_url, { method: "POST" });
return json as DeleteMediaResult;
},
}; };
export default dataProvider; export default dataProvider;