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] 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,