Merge branch 'main' into add-account-suspention

This commit is contained in:
Aine 2024-12-10 22:32:07 +02:00 committed by GitHub
commit 005be8eec5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 190 additions and 22 deletions

View File

@ -21,6 +21,7 @@
* [Fork differences](#fork-differences)
* [Changes](#changes)
* [exclusive for etke.cc customers](#exclusive-for-etkecc-customers)
* [Development](#development)
* [Support](#support)
* [Configuration](#configuration)
@ -58,6 +59,8 @@ The full list is described below in the [Changes](#changes) section.
### Changes
_the list will be updated as new changes are added_
The following changes are already implemented:
* 🛑 [Prevent admins from deleting themselves](https://github.com/etkecc/synapse-admin/pull/1)
@ -101,10 +104,14 @@ The following changes are already implemented:
* 🖼️ [Add rooms' avatars](https://github.com/etkecc/synapse-admin/pull/158)
* 🤖 [User Badges](https://github.com/etkecc/synapse-admin/pull/160)
* 🔑 [Allow prefilling any fields on the login form via GET params](https://github.com/etkecc/synapse-admin/pull/181)
* _(for [etke.cc](https://etke.cc) customers only)_ [Server Status indicator and page](https://github.com/etkecc/synapse-admin/pull/182)
* 🖼️ [Add "Media" tab for rooms](https://github.com/etkecc/synapse-admin/pull/196)
#### exclusive for [etke.cc](https://etke.cc) customers
_the list will be updated as new changes are added_
We at [etke.cc](https://etke.cc) attempting to develop everything open-source, but some things are too specific to be used by anyone else.
The following list contains such features - they are only available for [etke.cc](https://etke.cc) customers.
* 📊 [Server Status indicator and page](https://github.com/etkecc/synapse-admin/pull/182)
### Development

View File

@ -2,7 +2,7 @@ import { merge } from "lodash";
import polyglotI18nProvider from "ra-i18n-polyglot";
import { Admin, CustomRoutes, Resource, resolveBrowserLocale } from "react-admin";
import { createContext, useContext } from "react";
import { Route } from "react-router-dom";
import AdminLayout from "./components/AdminLayout";
@ -22,9 +22,8 @@ import rooms from "./resources/rooms";
import userMediaStats from "./resources/user_media_statistics";
import users from "./resources/users";
import authProvider from "./synapse/authProvider";
import dataProvider, { ServerStatusResponse } from "./synapse/dataProvider";
import dataProvider from "./synapse/dataProvider";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Config } from "./utils/config";
import ServerStatusPage from "./components/etke.cc/ServerStatusPage";
// TODO: Can we use lazy loading together with browser locale?
@ -88,8 +87,4 @@ export const App = () => (
</QueryClientProvider>
);
export const AppContext = createContext({});
export const useAppContext = () => useContext(AppContext) as Config;
export default App;

7
src/Context.tsx Normal file
View File

@ -0,0 +1,7 @@
import { createContext, useContext } from "react";
import { Config } from "./utils/config";
export const AppContext = createContext<Config>({} as Config);
export const useAppContext = () => useContext(AppContext) as Config;

View File

@ -1,6 +1,6 @@
import { Avatar, Badge, Theme, Tooltip } from "@mui/material";
import { useEffect } from "react";
import { useAppContext } from "../../App";
import { useAppContext } from "../../Context";
import { Button, useDataProvider, useStore } from "react-admin";
import { styled } from '@mui/material/styles';
import MonitorHeartIcon from '@mui/icons-material/MonitorHeart';

View File

@ -314,6 +314,7 @@ export const QuarantineMediaButton = (props: ButtonProps) => {
export const ViewMediaButton = ({ mxcURL, label, uploadName, mimetype }) => {
const translate = useTranslate();
const [loading, setLoading] = useState(false);
const notify = useNotify();
const isImage = mimetype && mimetype.startsWith("image/");
const openFileInNewTab = (blobURL: string) => {
@ -340,12 +341,25 @@ export const ViewMediaButton = ({ mxcURL, label, uploadName, mimetype }) => {
const handleFile = async (preview: boolean) => {
setLoading(true);
const response = await fetchAuthenticatedMedia(mxcURL, "original");
const blob = await response.blob();
const blobURL = URL.createObjectURL(blob);
if (preview) {
openFileInNewTab(blobURL);
if (response.ok) {
const blob = await response.blob();
const blobURL = URL.createObjectURL(blob);
if (preview) {
openFileInNewTab(blobURL);
} else {
downloadFile(blobURL);
}
} else {
downloadFile(blobURL);
const body = await response.json();
notify("resources.room_media.action.error", {
messageArgs: {
errcode: body.errcode,
errstatus: response.status,
error: body.error,
},
type: "error",
});
}
setLoading(false);
};
@ -391,8 +405,16 @@ export const MediaIDField = ({ source }) => {
return null;
}
const uploadName = decodeURIComponent(get(record, "upload_name")?.toString());
const mxcURL = `mxc://${homeserver}/${mediaID}`;
let uploadName = mediaID;
if (get(record, "upload_name")) {
uploadName = decodeURIComponent(get(record, "upload_name")?.toString());
}
let mxcURL = mediaID;
if (!mediaID.startsWith(`mxc://${homeserver}`)) {
// this is user's media, where mediaID doesn't have the mxc://home_server/ prefix as it has in the rooms
mxcURL = `mxc://${homeserver}/${mediaID}`;
}
return <ViewMediaButton mxcURL={mxcURL} label={mediaID} uploadName={uploadName} mimetype={record.media_type}/>;
};

View File

@ -64,6 +64,7 @@ const de: SynapseTranslationMessages = {
members: "Mitglieder",
detail: "Details",
permission: "Berechtigungen",
media: "Medien",
},
},
reports: { tabs: { basic: "Allgemein", detail: "Details" } },
@ -432,6 +433,18 @@ const de: SynapseTranslationMessages = {
sender: "Absender",
},
},
room_media: {
name: "Medien",
fields: {
media_id: "Medien ID",
},
helper: {
info: "Dies ist eine Liste der Medien, die in den Raum hochgeladen wurden. Es ist nicht möglich, Medien zu löschen, die in externen Medien-Repositories hochgeladen wurden.",
},
action: {
error: "%{errcode} (%{errstatus}) %{error}"
}
},
room_directory: {
name: "Raumverzeichnis",
fields: {

View File

@ -38,6 +38,7 @@ const en: SynapseTranslationMessages = {
members: "Members",
detail: "Details",
permission: "Permissions",
media: "Media",
}
},
reports: { tabs: { basic: "Basic", detail: "Details" } },
@ -404,6 +405,18 @@ const en: SynapseTranslationMessages = {
sender: "Sender",
},
},
room_media: {
name: "Media",
fields: {
media_id: "Media ID",
},
helper: {
info: "This is a list of media that has been uploaded to the room. It is not possible to delete media that has been uploaded to external media repositories.",
},
action: {
error: "%{errcode} (%{errstatus}) %{error}",
},
},
room_directory: {
name: "Room directory",
fields: {

View File

@ -32,6 +32,7 @@ const fa: SynapseTranslationMessages = {
members: "اعضا",
detail: "جزئیات",
permission: "مجوزها",
media: "رسانه ها",
},
},
reports: { tabs: { basic: "اصلی", detail: "جزئیات" } },
@ -384,6 +385,18 @@ const fa: SynapseTranslationMessages = {
sender: "فرستنده",
},
},
room_media: {
name: "رسانه ها",
fields: {
media_id: "شناسه رسانه",
},
helper: {
info: "این یک لیست از رسانه ها است که در اتاق بارگذاری شده است. نمی توان رسانه ها را حذف کرد که در اتاق های خارجی بارگذاری شده اند.",
},
action: {
error: "%{errcode} (%{errstatus}) %{error}"
}
},
room_directory: {
name: "راهنمای اتاق",
fields: {

View File

@ -32,6 +32,7 @@ const fr: SynapseTranslationMessages = {
members: "Membres",
detail: "Détails",
permission: "Permissions",
media: "Médias",
},
},
reports: { tabs: { basic: "Informations de base", detail: "Détails" } },
@ -386,6 +387,18 @@ const fr: SynapseTranslationMessages = {
sender: "Expéditeur",
},
},
room_media: {
name: "Médias",
fields: {
media_id: "Identifiant du média",
},
helper: {
info: "Cette liste contient les médias qui ont été téléchargés dans le salon. Il n'est pas possible de supprimer les médias qui ont été téléversés dans des dépôts de médias externes.",
},
action: {
error: "%{errcode} (%{errstatus}) %{error}"
}
},
room_directory: {
name: "Répertoire des salons",
fields: {

13
src/i18n/index.d.ts vendored
View File

@ -31,6 +31,7 @@ interface SynapseTranslationMessages extends TranslationMessages {
members: string;
detail: string;
permission: string;
media: string;
};
};
reports: { tabs: { basic: string; detail: string } };
@ -396,6 +397,18 @@ interface SynapseTranslationMessages extends TranslationMessages {
sender: string;
};
};
room_media?: {
name: string;
fields: {
media_id: string;
};
helper: {
info: string;
};
action: {
error: string;
};
};
room_directory?: {
name: string;
fields: {

View File

@ -32,6 +32,7 @@ const it: SynapseTranslationMessages = {
members: "Membro",
detail: "Dettagli",
permission: "Permessi",
media: "Media",
},
},
reports: { tabs: { basic: "Semplice", detail: "Dettagli" } },
@ -379,6 +380,18 @@ const it: SynapseTranslationMessages = {
sender: "Mittente",
},
},
room_media: {
name: "Media",
fields: {
media_id: "ID Media",
},
helper: {
info: "Questo è un elenco dei media caricati nella stanza. Non è possibile eliminare i media caricati su repository esterni.",
},
action: {
error: "%{errcode} (%{errstatus}) %{error}"
}
},
room_directory: {
name: "Elenco delle stanze",
fields: {

View File

@ -59,6 +59,7 @@ const ru: SynapseTranslationMessages = {
members: "Участники",
detail: "Подробности",
permission: "Права доступа",
media: "Медиа",
},
},
reports: { tabs: { basic: "Основные", detail: "Подробности" } },
@ -437,6 +438,18 @@ const ru: SynapseTranslationMessages = {
sender: "Отправитель",
},
},
room_media: {
name: "Медиа",
fields: {
media_id: "ID медиа",
},
helper: {
info: "Это список медиа, которые были загружены в комнату. Невозможно удалить медиа, которые были загружены в внешние медиа-репозитории.",
},
action: {
error: "%{errcode} (%{errstatus}) %{error}"
}
},
room_directory: {
name: "Каталог комнат",
fields: {

View File

@ -60,6 +60,7 @@ const zh: SynapseTranslationMessages = {
members: "成员",
detail: "细节",
permission: "权限",
media: "媒体",
},
},
reports: { tabs: { basic: "基本", detail: "细节" } },
@ -348,6 +349,18 @@ const zh: SynapseTranslationMessages = {
media_length: "媒体文件长度",
},
},
room_media: {
name: "媒体",
fields: {
media_id: "媒体ID",
},
helper: {
info: "这是上传到房间的媒体列表。无法删除上传到外部媒体存储库的媒体。",
},
action: {
error: "%{errcode} (%{errstatus}) %{error}"
}
},
},
};
export default zh;

View File

@ -2,7 +2,8 @@ import React from "react";
import { createRoot } from "react-dom/client";
import {App, AppContext } from "./App";
import { App } from "./App";
import { AppContext } from "./Context";
import { FetchConfig, GetConfig } from "./utils/config";
await FetchConfig();

View File

@ -4,7 +4,7 @@ import { render, screen } from "@testing-library/react";
import { AdminContext } from "react-admin";
import LoginPage from "./LoginPage";
import { AppContext } from "../App";
import { AppContext } from "../Context";
import englishMessages from "../i18n/en";
const i18nProvider = polyglotI18nProvider(() => englishMessages, "en", [{ locale: "en", name: "English" }]);
@ -33,7 +33,9 @@ describe("LoginForm", () => {
it("renders with single restricted homeserver", () => {
render(
<AppContext.Provider value={{ restrictBaseUrl: "https://matrix.example.com" }}>
<AppContext.Provider
value={{ restrictBaseUrl: "https://matrix.example.com", asManagedUsers: [], menu: [] }}
>
<AdminContext i18nProvider={i18nProvider}>
<LoginPage />
</AdminContext>
@ -56,6 +58,8 @@ describe("LoginForm", () => {
<AppContext.Provider
value={{
restrictBaseUrl: ["https://matrix.example.com", "https://matrix.example.org"],
asManagedUsers: [],
menu: [],
}}
>
<AdminContext i18nProvider={i18nProvider}>

View File

@ -16,7 +16,7 @@ import {
} from "react-admin";
import { useFormContext } from "react-hook-form";
import LoginFormBox from "../components/LoginFormBox";
import { useAppContext } from "../App";
import { useAppContext } from "../Context";
import {
getServerVersion,
getSupportedFeatures,

View File

@ -7,12 +7,12 @@ import PageviewIcon from "@mui/icons-material/Pageview";
import ViewListIcon from "@mui/icons-material/ViewList";
import RoomIcon from "@mui/icons-material/ViewList";
import VisibilityIcon from "@mui/icons-material/Visibility";
import PermMediaIcon from "@mui/icons-material/PermMedia";
import Box from "@mui/material/Box";
import { useTheme } from "@mui/material/styles";
import {
BooleanField,
DateField,
EditButton,
WrapperField,
Datagrid,
DatagridConfigurable,
@ -38,6 +38,7 @@ import {
useTranslate,
useListContext,
useNotify,
DeleteButton,
} from "react-admin";
import TextField from "@mui/material/TextField";
@ -49,6 +50,7 @@ import {
} from "./room_directory";
import { DATE_FORMAT } from "../utils/date";
import DeleteRoomButton from "../components/DeleteRoomButton";
import { MediaIDField } from "../components/media";
import AvatarField from "../components/AvatarField";
import { Room } from "../synapse/dataProvider";
import { useMutation } from "@tanstack/react-query";
@ -58,6 +60,8 @@ import { useState } from "react";
import Button from "@mui/material/Button";
import PersonIcon from '@mui/icons-material/Person';
import Typography from "@mui/material/Typography";
import Alert from "@mui/material/Alert";
const RoomPagination = () => <Pagination rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />;
const RoomTitle = () => {
@ -231,6 +235,16 @@ export const RoomShow = (props: ShowProps) => {
</ReferenceManyField>
</Tab>
<Tab label="synapseadmin.rooms.tabs.media" icon={<PermMediaIcon />} path="media">
<Alert severity="warning">{translate("resources.room_media.helper.info")}</Alert>
<ReferenceManyField reference="room_media" target="room_id" label={false}>
<Datagrid sx={{ width: "100%" }} bulkActionButtons={false}>
<MediaIDField source="media_id" />
<DeleteButton mutationMode="pessimistic" redirect={false} />
</Datagrid>
</ReferenceManyField>
</Tab>
<Tab label="synapseadmin.rooms.tabs.permission" icon={<VisibilityIcon />} path="permission">
<BooleanField source="federatable" />
<BooleanField source="public" />

View File

@ -385,6 +385,20 @@ const resourceMap = {
data: "members",
total: json => json.total,
},
room_media: {
map: (mediaId: string) => ({
id: mediaId.replace("mxc://" + localStorage.getItem("home_server") + "/", ""),
media_id: mediaId.replace("mxc://" + localStorage.getItem("home_server") + "/", ""),
}),
reference: (id: Identifier) => ({
endpoint: `/_synapse/admin/v1/room/${id}/media`,
}),
total: json => json.total,
data: "local",
delete: (params: DeleteParams) => ({
endpoint: `/_synapse/admin/v1/media/${localStorage.getItem("home_server")}/${params.id}`,
}),
},
room_state: {
map: (rs: RoomState) => ({
...rs,