Add admin API for destinations (#213)

* Add admin API for destinations

* Add rooms and connections to federation/destinations
This commit is contained in:
Dirk Klimpel 2023-01-24 16:35:42 +01:00 committed by GitHub
parent 6d30af9976
commit 8501f19a03
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 258 additions and 4 deletions

View file

@ -13,7 +13,7 @@ This project is built using [react-admin](https://marmelab.com/react-admin/).
### Supported Synapse ### 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`. 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). See also [Synapse version API](https://matrix-org.github.io/synapse/develop/admin_api/version_api.html).

View file

@ -7,13 +7,15 @@ import { UserList, UserCreate, UserEdit } from "./components/users";
import { RoomList, RoomShow } from "./components/rooms"; import { RoomList, RoomShow } from "./components/rooms";
import { ReportList, ReportShow } from "./components/EventReports"; import { ReportList, ReportShow } from "./components/EventReports";
import LoginPage from "./components/LoginPage"; import LoginPage from "./components/LoginPage";
import UserIcon from "@material-ui/icons/Group";
import ConfirmationNumberIcon from "@material-ui/icons/ConfirmationNumber"; import ConfirmationNumberIcon from "@material-ui/icons/ConfirmationNumber";
import CloudQueueIcon from "@material-ui/icons/CloudQueue";
import EqualizerIcon from "@material-ui/icons/Equalizer"; import EqualizerIcon from "@material-ui/icons/Equalizer";
import UserIcon from "@material-ui/icons/Group";
import { UserMediaStatsList } from "./components/statistics"; import { UserMediaStatsList } from "./components/statistics";
import RoomIcon from "@material-ui/icons/ViewList"; import RoomIcon from "@material-ui/icons/ViewList";
import ReportIcon from "@material-ui/icons/Warning"; import ReportIcon from "@material-ui/icons/Warning";
import FolderSharedIcon from "@material-ui/icons/FolderShared"; import FolderSharedIcon from "@material-ui/icons/FolderShared";
import { DestinationList, DestinationShow } from "./components/destinations";
import { ImportFeature } from "./components/ImportFeature"; import { ImportFeature } from "./components/ImportFeature";
import { import {
RegistrationTokenCreate, RegistrationTokenCreate,
@ -72,6 +74,12 @@ const App = () => (
list={RoomDirectoryList} list={RoomDirectoryList}
icon={FolderSharedIcon} icon={FolderSharedIcon}
/> />
<Resource
name="destinations"
list={DestinationList}
show={DestinationShow}
icon={CloudQueueIcon}
/>
<Resource <Resource
name="registration_tokens" name="registration_tokens"
list={RegistrationTokenList} list={RegistrationTokenList}
@ -88,6 +96,7 @@ const App = () => (
<Resource name="servernotices" /> <Resource name="servernotices" />
<Resource name="forward_extremities" /> <Resource name="forward_extremities" />
<Resource name="room_state" /> <Resource name="room_state" />
<Resource name="destination_rooms" />
</Admin> </Admin>
); );

View file

@ -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 => (
<Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
);
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 (
<Filter {...props}>
<SearchInput source="destination" alwaysOn />
</Filter>
);
};
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 (
<Button
label="resources.destinations.action.reconnect"
onClick={handleClick}
disabled={isLoading}
>
<AutorenewIcon />
</Button>
);
};
const DestinationShowActions = props => (
<TopToolbar>
<DestinationReconnectButton />
</TopToolbar>
);
const DestinationTitle = props => {
const record = useRecordContext();
const translate = useTranslate();
return (
<span>
{translate("resources.destinations.name", 1)} {record.destination}
</span>
);
};
export const DestinationList = props => {
return (
<List
{...props}
filters={<DestinationFilter />}
pagination={<DestinationPagination />}
sort={{ field: "destination", order: "ASC" }}
bulkActionButtons={false}
>
<Datagrid
rowStyle={destinationRowStyle}
rowClick={(id, basePath, record) => `${basePath}/${id}/show/rooms`}
>
<TextField source="destination" />
<DateField source="failure_ts" showTime options={date_format} />
<DateField source="retry_last_ts" showTime options={date_format} />
<TextField source="retry_interval" />
<TextField source="last_successful_stream_ordering" />
<DestinationReconnectButton />
</Datagrid>
</List>
);
};
export const DestinationShow = props => {
const translate = useTranslate();
return (
<Show
actions={<DestinationShowActions />}
title={<DestinationTitle />}
{...props}
>
<TabbedShowLayout>
<Tab label="status" icon={<ViewListIcon />}>
<TextField source="destination" />
<DateField source="failure_ts" showTime options={date_format} />
<DateField source="retry_last_ts" showTime options={date_format} />
<TextField source="retry_interval" />
<TextField source="last_successful_stream_ordering" />
</Tab>
<Tab
label={translate("resources.rooms.name", { smart_count: 2 })}
icon={<FolderSharedIcon />}
path="rooms"
>
<ReferenceManyField
reference="destination_rooms"
target="destination"
addLabel={false}
pagination={<DestinationPagination />}
perPage={50}
>
<Datagrid
style={{ width: "100%" }}
rowClick={(id, basePath, record) => `/rooms/${id}/show`}
>
<TextField
source="room_id"
label="resources.rooms.fields.room_id"
/>
<TextField source="stream_ordering" sortable={false} />
<ReferenceField
label="resources.rooms.fields.name"
source="id"
reference="rooms"
sortable={false}
link=""
>
<TextField source="name" sortable={false} />
</ReferenceField>
</Datagrid>
</ReferenceManyField>
</Tab>
</TabbedShowLayout>
</Show>
);
};

View file

@ -355,6 +355,18 @@ const de = {
send_failure: "Beim Entfernen ist ein Fehler aufgetreten.", 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: { registration_tokens: {
name: "Registrierungstoken", name: "Registrierungstoken",
fields: { fields: {

View file

@ -352,6 +352,18 @@ const en = {
send_failure: "An error has occurred.", 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: { registration_tokens: {
name: "Registration tokens", name: "Registration tokens",

View file

@ -281,6 +281,34 @@ const resourceMap = {
method: "PUT", 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: { registration_tokens: {
path: "/_synapse/admin/v1/registration_tokens", path: "/_synapse/admin/v1/registration_tokens",
map: rt => ({ map: rt => ({
@ -322,8 +350,15 @@ function getSearchOrder(order) {
const dataProvider = { const dataProvider = {
getList: (resource, params) => { getList: (resource, params) => {
console.log("getList " + resource); console.log("getList " + resource);
const { user_id, name, guests, deactivated, search_term, valid } = const {
params.filter; user_id,
name,
guests,
deactivated,
search_term,
destination,
valid,
} = params.filter;
const { page, perPage } = params.pagination; const { page, perPage } = params.pagination;
const { field, order } = params.sort; const { field, order } = params.sort;
const from = (page - 1) * perPage; const from = (page - 1) * perPage;
@ -333,6 +368,7 @@ const dataProvider = {
user_id: user_id, user_id: user_id,
search_term: search_term, search_term: search_term,
name: name, name: name,
destination: destination,
guests: guests, guests: guests,
deactivated: deactivated, deactivated: deactivated,
valid: valid, valid: valid,