Merge branch 'main' into add-account-suspention
This commit is contained in:
commit
005be8eec5
11
README.md
11
README.md
@ -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
|
||||
|
||||
|
@ -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
7
src/Context.tsx
Normal 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;
|
@ -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';
|
||||
|
@ -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}/>;
|
||||
};
|
||||
|
@ -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: {
|
||||
|
@ -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: {
|
||||
|
@ -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: {
|
||||
|
@ -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
13
src/i18n/index.d.ts
vendored
@ -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: {
|
||||
|
@ -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: {
|
||||
|
@ -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: {
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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}>
|
||||
|
@ -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,
|
||||
|
@ -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" />
|
||||
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user