Merge branch 'main' into add-account-suspention
This commit is contained in:
@@ -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,
|
||||
|
Reference in New Issue
Block a user