mirror of
				https://github.com/UA-Fediland/synapse-admin.git
				synced 2025-11-04 15:48:28 +00:00 
			
		
		
		
	Add admin API for destinations (#213)
* Add admin API for destinations * Add rooms and connections to federation/destinations
This commit is contained in:
		
							parent
							
								
									6d30af9976
								
							
						
					
					
						commit
						8501f19a03
					
				
					 6 changed files with 258 additions and 4 deletions
				
			
		| 
						 | 
				
			
			@ -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).
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										11
									
								
								src/App.js
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								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}
 | 
			
		||||
    />
 | 
			
		||||
    <Resource
 | 
			
		||||
      name="destinations"
 | 
			
		||||
      list={DestinationList}
 | 
			
		||||
      show={DestinationShow}
 | 
			
		||||
      icon={CloudQueueIcon}
 | 
			
		||||
    />
 | 
			
		||||
    <Resource
 | 
			
		||||
      name="registration_tokens"
 | 
			
		||||
      list={RegistrationTokenList}
 | 
			
		||||
| 
						 | 
				
			
			@ -88,6 +96,7 @@ const App = () => (
 | 
			
		|||
    <Resource name="servernotices" />
 | 
			
		||||
    <Resource name="forward_extremities" />
 | 
			
		||||
    <Resource name="room_state" />
 | 
			
		||||
    <Resource name="destination_rooms" />
 | 
			
		||||
  </Admin>
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										185
									
								
								src/components/destinations.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								src/components/destinations.js
									
									
									
									
									
										Normal 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>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -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: {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue