From 8501f19a03a80031cd27c06ccd0cc8ed6bb7f499 Mon Sep 17 00:00:00 2001
From: Dirk Klimpel <5740567+dklimpel@users.noreply.github.com>
Date: Tue, 24 Jan 2023 16:35:42 +0100
Subject: [PATCH 1/2] Add admin API for destinations (#213)
* Add admin API for destinations
* Add rooms and connections to federation/destinations
---
README.md | 2 +-
src/App.js | 11 +-
src/components/destinations.js | 185 +++++++++++++++++++++++++++++++++
src/i18n/de.js | 12 +++
src/i18n/en.js | 12 +++
src/synapse/dataProvider.js | 40 ++++++-
6 files changed, 258 insertions(+), 4 deletions(-)
create mode 100644 src/components/destinations.js
diff --git a/README.md b/README.md
index d01d397..3b7a2e9 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ This project is built using [react-admin](https://marmelab.com/react-admin/).
### Supported Synapse
-It needs at least [Synapse](https://github.com/matrix-org/synapse) v1.48.0 for all functions to work as expected!
+It needs at least [Synapse](https://github.com/matrix-org/synapse) v1.52.0 for all functions to work as expected!
You get your server version with the request `/_synapse/admin/v1/server_version`.
See also [Synapse version API](https://matrix-org.github.io/synapse/develop/admin_api/version_api.html).
diff --git a/src/App.js b/src/App.js
index ee24fac..7a27d0c 100644
--- a/src/App.js
+++ b/src/App.js
@@ -7,13 +7,15 @@ import { UserList, UserCreate, UserEdit } from "./components/users";
import { RoomList, RoomShow } from "./components/rooms";
import { ReportList, ReportShow } from "./components/EventReports";
import LoginPage from "./components/LoginPage";
-import UserIcon from "@material-ui/icons/Group";
import ConfirmationNumberIcon from "@material-ui/icons/ConfirmationNumber";
+import CloudQueueIcon from "@material-ui/icons/CloudQueue";
import EqualizerIcon from "@material-ui/icons/Equalizer";
+import UserIcon from "@material-ui/icons/Group";
import { UserMediaStatsList } from "./components/statistics";
import RoomIcon from "@material-ui/icons/ViewList";
import ReportIcon from "@material-ui/icons/Warning";
import FolderSharedIcon from "@material-ui/icons/FolderShared";
+import { DestinationList, DestinationShow } from "./components/destinations";
import { ImportFeature } from "./components/ImportFeature";
import {
RegistrationTokenCreate,
@@ -72,6 +74,12 @@ const App = () => (
list={RoomDirectoryList}
icon={FolderSharedIcon}
/>
+
(
+
);
diff --git a/src/components/destinations.js b/src/components/destinations.js
new file mode 100644
index 0000000..542b89b
--- /dev/null
+++ b/src/components/destinations.js
@@ -0,0 +1,185 @@
+import React from "react";
+import {
+ Button,
+ Datagrid,
+ DateField,
+ Filter,
+ List,
+ Pagination,
+ ReferenceField,
+ ReferenceManyField,
+ SearchInput,
+ Show,
+ Tab,
+ TabbedShowLayout,
+ TextField,
+ TopToolbar,
+ useRecordContext,
+ useDelete,
+ useNotify,
+ useRefresh,
+ useTranslate,
+} from "react-admin";
+import AutorenewIcon from "@material-ui/icons/Autorenew";
+import FolderSharedIcon from "@material-ui/icons/FolderShared";
+import ViewListIcon from "@material-ui/icons/ViewList";
+
+const DestinationPagination = props => (
+
+);
+
+const date_format = {
+ year: "numeric",
+ month: "2-digit",
+ day: "2-digit",
+ hour: "2-digit",
+ minute: "2-digit",
+ second: "2-digit",
+};
+
+const destinationRowStyle = (record, index) => ({
+ backgroundColor: record.retry_last_ts > 0 ? "#ffcccc" : "white",
+});
+
+const DestinationFilter = ({ ...props }) => {
+ return (
+
+
+
+ );
+};
+
+export const DestinationReconnectButton = props => {
+ const record = useRecordContext();
+ const refresh = useRefresh();
+ const notify = useNotify();
+ const [handleReconnect, { isLoading }] = useDelete("destinations");
+
+ // Reconnect is not required if no error has occurred. (`failure_ts`)
+ if (!record || !record.failure_ts) return null;
+
+ const handleClick = e => {
+ // Prevents redirection to the detail page when clicking in the list
+ e.stopPropagation();
+
+ handleReconnect(
+ { payload: { id: record.id } },
+ {
+ onSuccess: () => {
+ notify("ra.notification.updated", {
+ messageArgs: { smart_count: 1 },
+ });
+ refresh();
+ },
+ onFailure: () => {
+ notify("ra.message.error", { type: "error" });
+ },
+ }
+ );
+ };
+
+ return (
+
+ );
+};
+
+const DestinationShowActions = props => (
+
+
+
+);
+
+const DestinationTitle = props => {
+ const record = useRecordContext();
+ const translate = useTranslate();
+ return (
+
+ {translate("resources.destinations.name", 1)} {record.destination}
+
+ );
+};
+
+export const DestinationList = props => {
+ return (
+
}
+ pagination={}
+ sort={{ field: "destination", order: "ASC" }}
+ bulkActionButtons={false}
+ >
+ `${basePath}/${id}/show/rooms`}
+ >
+
+
+
+
+
+
+
+
+ );
+};
+
+export const DestinationShow = props => {
+ const translate = useTranslate();
+ return (
+ }
+ title={}
+ {...props}
+ >
+
+ }>
+
+
+
+
+
+
+
+ }
+ path="rooms"
+ >
+ }
+ perPage={50}
+ >
+ `/rooms/${id}/show`}
+ >
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/i18n/de.js b/src/i18n/de.js
index b98d4ba..0b77fd2 100644
--- a/src/i18n/de.js
+++ b/src/i18n/de.js
@@ -355,6 +355,18 @@ const de = {
send_failure: "Beim Entfernen ist ein Fehler aufgetreten.",
},
},
+ destinations: {
+ name: "Föderation",
+ fields: {
+ destination: "Ziel",
+ failure_ts: "Fehlerzeitpunkt",
+ retry_last_ts: "Letzter Wiederholungsversuch",
+ retry_interval: "Wiederholungsintervall",
+ last_successful_stream_ordering: "letzte erfogreicher Stream",
+ stream_ordering: "Stream",
+ },
+ action: { reconnect: "Neu verbinden" },
+ },
registration_tokens: {
name: "Registrierungstoken",
fields: {
diff --git a/src/i18n/en.js b/src/i18n/en.js
index 6c5836c..d3ccda3 100644
--- a/src/i18n/en.js
+++ b/src/i18n/en.js
@@ -352,6 +352,18 @@ const en = {
send_failure: "An error has occurred.",
},
},
+ destinations: {
+ name: "Federation",
+ fields: {
+ destination: "Destination",
+ failure_ts: "Failure timestamp",
+ retry_last_ts: "Last retry timestamp",
+ retry_interval: "Retry interval",
+ last_successful_stream_ordering: "Last successful stream",
+ stream_ordering: "Stream",
+ },
+ action: { reconnect: "Reconnect" },
+ },
},
registration_tokens: {
name: "Registration tokens",
diff --git a/src/synapse/dataProvider.js b/src/synapse/dataProvider.js
index 27ffc94..db57ec7 100644
--- a/src/synapse/dataProvider.js
+++ b/src/synapse/dataProvider.js
@@ -281,6 +281,34 @@ const resourceMap = {
method: "PUT",
}),
},
+ destinations: {
+ path: "/_synapse/admin/v1/federation/destinations",
+ map: dst => ({
+ ...dst,
+ id: dst.destination,
+ }),
+ data: "destinations",
+ total: json => {
+ return json.total;
+ },
+ delete: params => ({
+ endpoint: `/_synapse/admin/v1/federation/destinations/${params.id}/reset_connection`,
+ method: "POST",
+ }),
+ },
+ destination_rooms: {
+ map: dstroom => ({
+ ...dstroom,
+ id: dstroom.room_id,
+ }),
+ reference: id => ({
+ endpoint: `/_synapse/admin/v1/federation/destinations/${id}/rooms`,
+ }),
+ data: "rooms",
+ total: json => {
+ return json.total;
+ },
+ },
registration_tokens: {
path: "/_synapse/admin/v1/registration_tokens",
map: rt => ({
@@ -322,8 +350,15 @@ function getSearchOrder(order) {
const dataProvider = {
getList: (resource, params) => {
console.log("getList " + resource);
- const { user_id, name, guests, deactivated, search_term, valid } =
- params.filter;
+ const {
+ user_id,
+ name,
+ guests,
+ deactivated,
+ search_term,
+ destination,
+ valid,
+ } = params.filter;
const { page, perPage } = params.pagination;
const { field, order } = params.sort;
const from = (page - 1) * perPage;
@@ -333,6 +368,7 @@ const dataProvider = {
user_id: user_id,
search_term: search_term,
name: name,
+ destination: destination,
guests: guests,
deactivated: deactivated,
valid: valid,
From 74f77e6988a78d768ec1b9e07d67bd8988b02b02 Mon Sep 17 00:00:00 2001
From: Dirk Klimpel <5740567+dklimpel@users.noreply.github.com>
Date: Tue, 24 Jan 2023 16:36:08 +0100
Subject: [PATCH 2/2] Replace `({ record })` with `useRecordContext()` (#236)
* replace `({ record })` with `useRecordContext()`
* code style
Co-authored-by: Michael Albert <37796947+awesome-michael@users.noreply.github.com>
---
src/components/RoomDirectory.js | 4 +++-
src/components/ServerNotices.js | 4 +++-
src/components/devices.js | 11 +++++++++--
src/components/media.js | 5 +++--
src/components/rooms.js | 5 +++--
src/components/users.js | 4 +++-
6 files changed, 24 insertions(+), 9 deletions(-)
diff --git a/src/components/RoomDirectory.js b/src/components/RoomDirectory.js
index 5a9487f..feb5e71 100644
--- a/src/components/RoomDirectory.js
+++ b/src/components/RoomDirectory.js
@@ -19,6 +19,7 @@ import {
useMutation,
useNotify,
useTranslate,
+ useRecordContext,
useRefresh,
useUnselectAll,
} from "react-admin";
@@ -105,7 +106,8 @@ export const RoomDirectoryBulkSaveButton = ({ selectedIds }) => {
);
};
-export const RoomDirectorySaveButton = ({ record }) => {
+export const RoomDirectorySaveButton = props => {
+ const record = useRecordContext();
const notify = useNotify();
const refresh = useRefresh();
const [create, { loading }] = useCreate("room_directory");
diff --git a/src/components/ServerNotices.js b/src/components/ServerNotices.js
index a8e186e..1117321 100644
--- a/src/components/ServerNotices.js
+++ b/src/components/ServerNotices.js
@@ -9,6 +9,7 @@ import {
useCreate,
useMutation,
useNotify,
+ useRecordContext,
useTranslate,
useUnselectAll,
} from "react-admin";
@@ -64,7 +65,8 @@ const ServerNoticeDialog = ({ open, loading, onClose, onSend }) => {
);
};
-export const ServerNoticeButton = ({ record }) => {
+export const ServerNoticeButton = props => {
+ const record = useRecordContext();
const [open, setOpen] = useState(false);
const notify = useNotify();
const [create, { loading }] = useCreate("servernotices");
diff --git a/src/components/devices.js b/src/components/devices.js
index b4cb701..7e24dff 100644
--- a/src/components/devices.js
+++ b/src/components/devices.js
@@ -1,5 +1,12 @@
import React, { Fragment, useState } from "react";
-import { Button, useDelete, useNotify, Confirm, useRefresh } from "react-admin";
+import {
+ Button,
+ useDelete,
+ useNotify,
+ Confirm,
+ useRecordContext,
+ useRefresh,
+} from "react-admin";
import ActionDelete from "@material-ui/icons/Delete";
import { makeStyles } from "@material-ui/core/styles";
import { alpha } from "@material-ui/core/styles/colorManipulator";
@@ -22,7 +29,7 @@ const useStyles = makeStyles(
);
export const DeviceRemoveButton = props => {
- const { record } = props;
+ const record = useRecordContext();
const classes = useStyles(props);
const [open, setOpen] = useState(false);
const refresh = useRefresh();
diff --git a/src/components/media.js b/src/components/media.js
index aed1ee8..66340eb 100644
--- a/src/components/media.js
+++ b/src/components/media.js
@@ -14,6 +14,7 @@ import {
useCreate,
useDelete,
useNotify,
+ useRecordContext,
useRefresh,
useTranslate,
} from "react-admin";
@@ -154,7 +155,7 @@ export const DeleteMediaButton = props => {
};
export const ProtectMediaButton = props => {
- const { record } = props;
+ const record = useRecordContext();
const translate = useTranslate();
const refresh = useRefresh();
const notify = useNotify();
@@ -250,7 +251,7 @@ export const ProtectMediaButton = props => {
};
export const QuarantineMediaButton = props => {
- const { record } = props;
+ const record = useRecordContext();
const translate = useTranslate();
const refresh = useRefresh();
const notify = useNotify();
diff --git a/src/components/rooms.js b/src/components/rooms.js
index 6417221..276d3d4 100644
--- a/src/components/rooms.js
+++ b/src/components/rooms.js
@@ -87,7 +87,8 @@ const EncryptionField = ({ source, record = {}, emptyText }) => {
);
};
-const RoomTitle = ({ record }) => {
+const RoomTitle = props => {
+ const record = useRecordContext();
const translate = useTranslate();
var name = "";
if (record) {
@@ -354,7 +355,7 @@ const RoomFilter = ({ ...props }) => {
const RoomNameField = props => {
const { source } = props;
- const record = useRecordContext(props);
+ const record = useRecordContext();
return (
{record[source] || record["canonical_alias"] || record["id"]}
);
diff --git a/src/components/users.js b/src/components/users.js
index 198c6c1..3759705 100644
--- a/src/components/users.js
+++ b/src/components/users.js
@@ -39,6 +39,7 @@ import {
maxLength,
regex,
required,
+ useRecordContext,
useTranslate,
Pagination,
CreateButton,
@@ -329,7 +330,8 @@ export const UserCreate = props => (
);
-const UserTitle = ({ record }) => {
+const UserTitle = props => {
+ const record = useRecordContext();
const translate = useTranslate();
return (