Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff0201273a | ||
|
|
e50c95b4be | ||
|
|
9f16e5c6ba | ||
|
|
509a45cba4 | ||
|
|
5c6e5a9641 | ||
|
|
e3d5d51342 | ||
|
|
985673b161 | ||
|
|
d72357f64f | ||
|
|
e19c34324b | ||
|
|
3ea1f51eb5 | ||
|
|
229518e456 | ||
|
|
5a5a7143af | ||
|
|
dda8ba5e85 | ||
|
|
5208198b76 | ||
|
|
c8082a7198 | ||
|
|
10831796e3 | ||
|
|
5ee5288edf | ||
|
|
a5528d9fe7 | ||
|
|
e2fd934851 | ||
|
|
0bc1ce3226 | ||
|
|
41ce58bac8 |
5
.env
Normal file
5
.env
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# This setting allows to fix the homeserver.
|
||||||
|
# If you set this setting, the user will not be able to select
|
||||||
|
# the server and have to use synapse-admin with this server.
|
||||||
|
|
||||||
|
#REACT_APP_SERVER=https://yourmatrixserver.example.com
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- 13
|
- lts/*
|
||||||
|
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
This project is built using [react-admin](https://marmelab.com/react-admin/).
|
This project is built using [react-admin](https://marmelab.com/react-admin/).
|
||||||
|
|
||||||
It needs at least Synapse v1.27.0 for all functions to work as expected!
|
It needs at least Synapse v1.34.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://github.com/matrix-org/synapse/blob/develop/docs/admin_api/version_api.rst).
|
See also [Synapse version API](https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/version_api.rst).
|
||||||
@@ -33,6 +33,11 @@ Steps for 1):
|
|||||||
- download dependencies: `yarn install`
|
- download dependencies: `yarn install`
|
||||||
- start web server: `yarn start`
|
- start web server: `yarn start`
|
||||||
|
|
||||||
|
You can fix the homeserver, so that the user can no longer define it himself.
|
||||||
|
Either you define it at startup (e.g. `REACT_APP_SERVER=https://yourmatrixserver.example.com yarn start`)
|
||||||
|
or by editing it in the [.env](.env) file. See also the
|
||||||
|
[documentation](https://create-react-app.dev/docs/adding-custom-environment-variables/).
|
||||||
|
|
||||||
Steps for 2):
|
Steps for 2):
|
||||||
|
|
||||||
- run the Docker container from the public docker registry: `docker run -p 8080:80 awesometechnologies/synapse-admin` or use the (docker-compose.yml)[docker-compose.yml]: `docker-compose up -d`
|
- run the Docker container from the public docker registry: `docker run -p 8080:80 awesometechnologies/synapse-admin` or use the (docker-compose.yml)[docker-compose.yml]: `docker-compose up -d`
|
||||||
|
|||||||
24
package.json
24
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "synapse-admin",
|
"name": "synapse-admin",
|
||||||
"version": "0.8.0",
|
"version": "0.8.2",
|
||||||
"description": "Admin GUI for the Matrix.org server Synapse",
|
"description": "Admin GUI for the Matrix.org server Synapse",
|
||||||
"author": "Awesome Technologies Innovationslabor GmbH",
|
"author": "Awesome Technologies Innovationslabor GmbH",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
@@ -11,26 +11,24 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/jest-dom": "^5.1.1",
|
"@testing-library/jest-dom": "^5.1.1",
|
||||||
"@testing-library/react": "^10.0.2",
|
"@testing-library/react": "^11.2.6",
|
||||||
"@testing-library/user-event": "^12.0.11",
|
"@testing-library/user-event": "^13.1.8",
|
||||||
"enzyme": "^3.11.0",
|
"eslint": "^7.25.0",
|
||||||
"enzyme-adapter-react-16": "^1.15.2",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint": "^6.8.0",
|
|
||||||
"eslint-config-prettier": "^6.10.1",
|
|
||||||
"eslint-plugin-prettier": "^3.1.2",
|
"eslint-plugin-prettier": "^3.1.2",
|
||||||
"jest-fetch-mock": "^3.0.3",
|
"jest-fetch-mock": "^3.0.3",
|
||||||
"prettier": "^2.0.0",
|
"prettier": "^2.2.0",
|
||||||
"ra-test": "^3.14.0"
|
"ra-test": "^3.15.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"papaparse": "^5.2.0",
|
"papaparse": "^5.2.0",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"ra-language-chinese": "^2.0.10",
|
"ra-language-chinese": "^2.0.10",
|
||||||
"ra-language-german": "^2.1.2",
|
"ra-language-german": "^3.13.4",
|
||||||
"react": "^17.0.0",
|
"react": "^17.0.0",
|
||||||
"react-admin": "^3.14.0",
|
"react-admin": "^3.15.0",
|
||||||
"react-dom": "^16.14.0",
|
"react-dom": "^17.0.2",
|
||||||
"react-scripts": "^3.4.4"
|
"react-scripts": "^4.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "REACT_APP_VERSION=$(git describe --tags) react-scripts start",
|
"start": "REACT_APP_VERSION=$(git describe --tags) react-scripts start",
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ const App = () => (
|
|||||||
<Resource name="joined_rooms" />
|
<Resource name="joined_rooms" />
|
||||||
<Resource name="pushers" />
|
<Resource name="pushers" />
|
||||||
<Resource name="servernotices" />
|
<Resource name="servernotices" />
|
||||||
|
<Resource name="forward_extremities" />
|
||||||
<Resource name="room_state" />
|
<Resource name="room_state" />
|
||||||
</Admin>
|
</Admin>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { TestContext } from "ra-test";
|
import { render } from "@testing-library/react";
|
||||||
import { shallow } from "enzyme";
|
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
|
|
||||||
describe("App", () => {
|
describe("App", () => {
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
shallow(
|
render(<App />);
|
||||||
<TestContext>
|
|
||||||
<App />
|
|
||||||
</TestContext>
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ const LoginPage = ({ theme }) => {
|
|||||||
const setLocale = useSetLocale();
|
const setLocale = useSetLocale();
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
const base_url = localStorage.getItem("base_url");
|
const base_url = localStorage.getItem("base_url");
|
||||||
|
const cfg_base_url = process.env.REACT_APP_SERVER;
|
||||||
|
|
||||||
const renderInput = ({
|
const renderInput = ({
|
||||||
meta: { touched, error } = {},
|
meta: { touched, error } = {},
|
||||||
@@ -111,7 +112,9 @@ const LoginPage = ({ theme }) => {
|
|||||||
if (!values.base_url.match(/^(http|https):\/\//)) {
|
if (!values.base_url.match(/^(http|https):\/\//)) {
|
||||||
errors.base_url = translate("synapseadmin.auth.protocol_error");
|
errors.base_url = translate("synapseadmin.auth.protocol_error");
|
||||||
} else if (
|
} else if (
|
||||||
!values.base_url.match(/^(http|https):\/\/[a-zA-Z0-9\-.]+(:\d{1,5})?$/)
|
!values.base_url.match(
|
||||||
|
/^(http|https):\/\/[a-zA-Z0-9\-.]+(:\d{1,5})?[^?&\s]*$/
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
errors.base_url = translate("synapseadmin.auth.url_error");
|
errors.base_url = translate("synapseadmin.auth.url_error");
|
||||||
}
|
}
|
||||||
@@ -147,7 +150,7 @@ const LoginPage = ({ theme }) => {
|
|||||||
const [serverVersion, setServerVersion] = useState("");
|
const [serverVersion, setServerVersion] = useState("");
|
||||||
|
|
||||||
const handleUsernameChange = _ => {
|
const handleUsernameChange = _ => {
|
||||||
if (formData.base_url) return;
|
if (formData.base_url || cfg_base_url) return;
|
||||||
// check if username is a full qualified userId then set base_url accordially
|
// check if username is a full qualified userId then set base_url accordially
|
||||||
const home_server = extractHomeServer(formData.username);
|
const home_server = extractHomeServer(formData.username);
|
||||||
const wellKnownUrl = `https://${home_server}/.well-known/matrix/client`;
|
const wellKnownUrl = `https://${home_server}/.well-known/matrix/client`;
|
||||||
@@ -199,6 +202,7 @@ const LoginPage = ({ theme }) => {
|
|||||||
label={translate("ra.auth.username")}
|
label={translate("ra.auth.username")}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
onBlur={handleUsernameChange}
|
onBlur={handleUsernameChange}
|
||||||
|
resettable
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -209,6 +213,7 @@ const LoginPage = ({ theme }) => {
|
|||||||
label={translate("ra.auth.password")}
|
label={translate("ra.auth.password")}
|
||||||
type="password"
|
type="password"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
|
resettable
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -217,7 +222,8 @@ const LoginPage = ({ theme }) => {
|
|||||||
name="base_url"
|
name="base_url"
|
||||||
component={renderInput}
|
component={renderInput}
|
||||||
label={translate("synapseadmin.auth.base_url")}
|
label={translate("synapseadmin.auth.base_url")}
|
||||||
disabled={loading}
|
disabled={cfg_base_url || loading}
|
||||||
|
resettable
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -228,7 +234,7 @@ const LoginPage = ({ theme }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
initialValues={{ base_url: base_url }}
|
initialValues={{ base_url: cfg_base_url || base_url }}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
validate={validate}
|
validate={validate}
|
||||||
render={({ handleSubmit }) => (
|
render={({ handleSubmit }) => (
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { render } from "@testing-library/react";
|
||||||
import { TestContext } from "ra-test";
|
import { TestContext } from "ra-test";
|
||||||
import { shallow } from "enzyme";
|
|
||||||
import LoginPage from "./LoginPage";
|
import LoginPage from "./LoginPage";
|
||||||
|
|
||||||
describe("LoginForm", () => {
|
describe("LoginForm", () => {
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
shallow(
|
render(
|
||||||
<TestContext>
|
<TestContext>
|
||||||
<LoginPage />
|
<LoginPage />
|
||||||
</TestContext>
|
</TestContext>
|
||||||
|
|||||||
@@ -171,10 +171,14 @@ const RoomDirectoryFilter = ({ ...props }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FilterableRoomDirectoryList = ({ ...props }) => {
|
export const FilterableRoomDirectoryList = ({
|
||||||
|
roomDirectoryFilters,
|
||||||
|
dispatch,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
const filter = props.roomDirectoryFilters;
|
const filter = roomDirectoryFilters;
|
||||||
const roomIdFilter = filter && filter.room_id ? true : false;
|
const roomIdFilter = filter && filter.room_id ? true : false;
|
||||||
const topicFilter = filter && filter.topic ? true : false;
|
const topicFilter = filter && filter.topic ? true : false;
|
||||||
const canonicalAliasFilter = filter && filter.canonical_alias ? true : false;
|
const canonicalAliasFilter = filter && filter.canonical_alias ? true : false;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
DeleteButton,
|
DeleteButton,
|
||||||
Filter,
|
Filter,
|
||||||
List,
|
List,
|
||||||
|
NumberField,
|
||||||
Pagination,
|
Pagination,
|
||||||
ReferenceField,
|
ReferenceField,
|
||||||
ReferenceManyField,
|
ReferenceManyField,
|
||||||
@@ -18,10 +19,14 @@ import {
|
|||||||
TabbedShowLayout,
|
TabbedShowLayout,
|
||||||
TextField,
|
TextField,
|
||||||
TopToolbar,
|
TopToolbar,
|
||||||
|
useRecordContext,
|
||||||
useTranslate,
|
useTranslate,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import get from "lodash/get";
|
import get from "lodash/get";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
import { Tooltip, Typography, Chip } from "@material-ui/core";
|
import { Tooltip, Typography, Chip } from "@material-ui/core";
|
||||||
|
import FastForwardIcon from "@material-ui/icons/FastForward";
|
||||||
import HttpsIcon from "@material-ui/icons/Https";
|
import HttpsIcon from "@material-ui/icons/Https";
|
||||||
import NoEncryptionIcon from "@material-ui/icons/NoEncryption";
|
import NoEncryptionIcon from "@material-ui/icons/NoEncryption";
|
||||||
import PageviewIcon from "@material-ui/icons/Pageview";
|
import PageviewIcon from "@material-ui/icons/Pageview";
|
||||||
@@ -36,6 +41,13 @@ import {
|
|||||||
RoomDirectorySaveButton,
|
RoomDirectorySaveButton,
|
||||||
} from "./RoomDirectory";
|
} from "./RoomDirectory";
|
||||||
|
|
||||||
|
const useStyles = makeStyles(theme => ({
|
||||||
|
helper_forward_extremities: {
|
||||||
|
fontFamily: "Roboto, Helvetica, Arial, sans-serif",
|
||||||
|
margin: "0.5em",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
const RoomPagination = props => (
|
const RoomPagination = props => (
|
||||||
<Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
|
<Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
|
||||||
);
|
);
|
||||||
@@ -107,6 +119,7 @@ const RoomShowActions = ({ basePath, data, resource }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const RoomShow = props => {
|
export const RoomShow = props => {
|
||||||
|
const classes = useStyles({ props });
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
return (
|
return (
|
||||||
<Show {...props} actions={<RoomShowActions />} title={<RoomTitle />}>
|
<Show {...props} actions={<RoomShowActions />} title={<RoomTitle />}>
|
||||||
@@ -218,6 +231,7 @@ export const RoomShow = props => {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab
|
<Tab
|
||||||
label={translate("resources.room_state.name", { smart_count: 2 })}
|
label={translate("resources.room_state.name", { smart_count: 2 })}
|
||||||
icon={<EventIcon />}
|
icon={<EventIcon />}
|
||||||
@@ -254,6 +268,40 @@ export const RoomShow = props => {
|
|||||||
</Datagrid>
|
</Datagrid>
|
||||||
</ReferenceManyField>
|
</ReferenceManyField>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
|
<Tab
|
||||||
|
label="resources.forward_extremities.name"
|
||||||
|
icon={<FastForwardIcon />}
|
||||||
|
path="forward_extremities"
|
||||||
|
>
|
||||||
|
<div className={classes.helper_forward_extremities}>
|
||||||
|
{translate("resources.rooms.helper.forward_extremities")}
|
||||||
|
</div>
|
||||||
|
<ReferenceManyField
|
||||||
|
reference="forward_extremities"
|
||||||
|
target="room_id"
|
||||||
|
addLabel={false}
|
||||||
|
>
|
||||||
|
<Datagrid style={{ width: "100%" }}>
|
||||||
|
<TextField source="id" sortable={false} />
|
||||||
|
<DateField
|
||||||
|
source="received_ts"
|
||||||
|
showTime
|
||||||
|
options={{
|
||||||
|
year: "numeric",
|
||||||
|
month: "2-digit",
|
||||||
|
day: "2-digit",
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
second: "2-digit",
|
||||||
|
}}
|
||||||
|
sortable={false}
|
||||||
|
/>
|
||||||
|
<NumberField source="depth" sortable={false} />
|
||||||
|
<TextField source="state_group" sortable={false} />
|
||||||
|
</Datagrid>
|
||||||
|
</ReferenceManyField>
|
||||||
|
</Tab>
|
||||||
</TabbedShowLayout>
|
</TabbedShowLayout>
|
||||||
</Show>
|
</Show>
|
||||||
);
|
);
|
||||||
@@ -305,8 +353,22 @@ const RoomFilter = ({ ...props }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const FilterableRoomList = ({ ...props }) => {
|
const RoomNameField = props => {
|
||||||
const filter = props.roomFilters;
|
const { source } = props;
|
||||||
|
const record = useRecordContext(props);
|
||||||
|
return (
|
||||||
|
<span>{record[source] || record["canonical_alias"] || record["id"]}</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
RoomNameField.propTypes = {
|
||||||
|
label: PropTypes.string,
|
||||||
|
record: PropTypes.object,
|
||||||
|
source: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
const FilterableRoomList = ({ roomFilters, dispatch, ...props }) => {
|
||||||
|
const filter = roomFilters;
|
||||||
const localMembersFilter =
|
const localMembersFilter =
|
||||||
filter && filter.joined_local_members ? true : false;
|
filter && filter.joined_local_members ? true : false;
|
||||||
const stateEventsFilter = filter && filter.state_events ? true : false;
|
const stateEventsFilter = filter && filter.state_events ? true : false;
|
||||||
@@ -327,7 +389,7 @@ const FilterableRoomList = ({ ...props }) => {
|
|||||||
sortBy="encryption"
|
sortBy="encryption"
|
||||||
label={<HttpsIcon />}
|
label={<HttpsIcon />}
|
||||||
/>
|
/>
|
||||||
<TextField source="name" />
|
<RoomNameField source="name" />
|
||||||
<TextField source="joined_members" />
|
<TextField source="joined_members" />
|
||||||
{localMembersFilter && <TextField source="joined_local_members" />}
|
{localMembersFilter && <TextField source="joined_local_members" />}
|
||||||
{stateEventsFilter && <TextField source="state_events" />}
|
{stateEventsFilter && <TextField source="state_events" />}
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import {
|
|||||||
DeleteButton,
|
DeleteButton,
|
||||||
SaveButton,
|
SaveButton,
|
||||||
regex,
|
regex,
|
||||||
useRedirect,
|
|
||||||
useTranslate,
|
useTranslate,
|
||||||
Pagination,
|
Pagination,
|
||||||
CreateButton,
|
CreateButton,
|
||||||
@@ -45,11 +44,12 @@ import {
|
|||||||
sanitizeListRestProps,
|
sanitizeListRestProps,
|
||||||
NumberField,
|
NumberField,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
import { ServerNoticeButton, ServerNoticeBulkButton } from "./ServerNotices";
|
import { ServerNoticeButton, ServerNoticeBulkButton } from "./ServerNotices";
|
||||||
import { DeviceRemoveButton } from "./devices";
|
import { DeviceRemoveButton } from "./devices";
|
||||||
import { makeStyles } from "@material-ui/core/styles";
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
|
|
||||||
const redirect = (basePath, id, data) => {
|
const redirect = () => {
|
||||||
return {
|
return {
|
||||||
pathname: "/import_users",
|
pathname: "/import_users",
|
||||||
};
|
};
|
||||||
@@ -85,7 +85,6 @@ const UserListActions = ({
|
|||||||
total,
|
total,
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
const redirectTo = useRedirect();
|
|
||||||
return (
|
return (
|
||||||
<TopToolbar className={className} {...sanitizeListRestProps(rest)}>
|
<TopToolbar className={className} {...sanitizeListRestProps(rest)}>
|
||||||
{filters &&
|
{filters &&
|
||||||
@@ -106,12 +105,7 @@ const UserListActions = ({
|
|||||||
maxResults={maxResults}
|
maxResults={maxResults}
|
||||||
/>
|
/>
|
||||||
{/* Add your custom actions */}
|
{/* Add your custom actions */}
|
||||||
<Button
|
<Button component={Link} to={redirect} label="CSV Import">
|
||||||
onClick={() => {
|
|
||||||
redirectTo(redirect);
|
|
||||||
}}
|
|
||||||
label="CSV Import"
|
|
||||||
>
|
|
||||||
<GetAppIcon style={{ transform: "rotate(180deg)", fontSize: "20" }} />
|
<GetAppIcon style={{ transform: "rotate(180deg)", fontSize: "20" }} />
|
||||||
</Button>
|
</Button>
|
||||||
</TopToolbar>
|
</TopToolbar>
|
||||||
@@ -162,6 +156,7 @@ export const UserList = props => {
|
|||||||
{...props}
|
{...props}
|
||||||
filters={<UserFilter />}
|
filters={<UserFilter />}
|
||||||
filterDefaultValues={{ guests: true, deactivated: false }}
|
filterDefaultValues={{ guests: true, deactivated: false }}
|
||||||
|
sort={{ field: "name", order: "ASC" }}
|
||||||
actions={<UserListActions maxResults={10000} />}
|
actions={<UserListActions maxResults={10000} />}
|
||||||
bulkActionButtons={<UserBulkActionButtons />}
|
bulkActionButtons={<UserBulkActionButtons />}
|
||||||
pagination={<UserPagination />}
|
pagination={<UserPagination />}
|
||||||
@@ -169,14 +164,14 @@ export const UserList = props => {
|
|||||||
<Datagrid rowClick="edit">
|
<Datagrid rowClick="edit">
|
||||||
<AvatarField
|
<AvatarField
|
||||||
source="avatar_src"
|
source="avatar_src"
|
||||||
sortable={false}
|
|
||||||
className={classes.small}
|
className={classes.small}
|
||||||
|
sortBy="avatar_url"
|
||||||
/>
|
/>
|
||||||
<TextField source="id" sortable={false} />
|
<TextField source="id" sortBy="name" />
|
||||||
<TextField source="displayname" sortable={false} />
|
<TextField source="displayname" />
|
||||||
<BooleanField source="is_guest" sortable={false} />
|
<BooleanField source="is_guest" />
|
||||||
<BooleanField source="admin" sortable={false} />
|
<BooleanField source="admin" />
|
||||||
<BooleanField source="deactivated" sortable={false} />
|
<BooleanField source="deactivated" />
|
||||||
</Datagrid>
|
</Datagrid>
|
||||||
</List>
|
</List>
|
||||||
);
|
);
|
||||||
@@ -420,6 +415,7 @@ export const UserEdit = props => {
|
|||||||
addLabel={false}
|
addLabel={false}
|
||||||
pagination={<UserPagination />}
|
pagination={<UserPagination />}
|
||||||
perPage={50}
|
perPage={50}
|
||||||
|
sort={{ field: "created_ts", order: "DESC" }}
|
||||||
>
|
>
|
||||||
<Datagrid style={{ width: "100%" }}>
|
<Datagrid style={{ width: "100%" }}>
|
||||||
<DateField
|
<DateField
|
||||||
@@ -433,7 +429,6 @@ export const UserEdit = props => {
|
|||||||
minute: "2-digit",
|
minute: "2-digit",
|
||||||
second: "2-digit",
|
second: "2-digit",
|
||||||
}}
|
}}
|
||||||
sortable={false}
|
|
||||||
/>
|
/>
|
||||||
<DateField
|
<DateField
|
||||||
source="last_access_ts"
|
source="last_access_ts"
|
||||||
@@ -446,14 +441,13 @@ export const UserEdit = props => {
|
|||||||
minute: "2-digit",
|
minute: "2-digit",
|
||||||
second: "2-digit",
|
second: "2-digit",
|
||||||
}}
|
}}
|
||||||
sortable={false}
|
|
||||||
/>
|
/>
|
||||||
<TextField source="media_id" sortable={false} />
|
<TextField source="media_id" />
|
||||||
<NumberField source="media_length" sortable={false} />
|
<NumberField source="media_length" />
|
||||||
<TextField source="media_type" sortable={false} />
|
<TextField source="media_type" />
|
||||||
<TextField source="upload_name" sortable={false} />
|
<TextField source="upload_name" />
|
||||||
<TextField source="quarantined_by" sortable={false} />
|
<TextField source="quarantined_by" />
|
||||||
<BooleanField source="safe_from_quarantine" sortable={false} />
|
<BooleanField source="safe_from_quarantine" />
|
||||||
<DeleteButton mutationMode="pessimistic" redirect={false} />
|
<DeleteButton mutationMode="pessimistic" redirect={false} />
|
||||||
</Datagrid>
|
</Datagrid>
|
||||||
</ReferenceManyField>
|
</ReferenceManyField>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import germanMessages from "ra-language-german";
|
import germanMessages from "ra-language-german";
|
||||||
|
|
||||||
export default {
|
const de = {
|
||||||
...germanMessages,
|
...germanMessages,
|
||||||
synapseadmin: {
|
synapseadmin: {
|
||||||
auth: {
|
auth: {
|
||||||
@@ -152,6 +152,10 @@ export default {
|
|||||||
topic: "Thema",
|
topic: "Thema",
|
||||||
avatar: "Avatar",
|
avatar: "Avatar",
|
||||||
},
|
},
|
||||||
|
helper: {
|
||||||
|
forward_extremities:
|
||||||
|
"Forward extremities are the leaf events at the end of a Directed acyclic graph (DAG) in a room, aka events that have no children. The more exist in a room, the more state resolution that Synapse needs to perform (hint: it's an expensive operation). While Synapse has code to prevent too many of these existing at one time in a room, bugs can sometimes make them crop up again. If a room has >10 forward extremities, it's worth checking which room is the culprit and potentially removing them using the SQL queries mentioned in #1760.",
|
||||||
|
},
|
||||||
enums: {
|
enums: {
|
||||||
join_rules: {
|
join_rules: {
|
||||||
public: "Öffentlich",
|
public: "Öffentlich",
|
||||||
@@ -295,6 +299,15 @@ export default {
|
|||||||
media_length: "Größe der Dateien",
|
media_length: "Größe der Dateien",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
forward_extremities: {
|
||||||
|
name: "Vorderextremitäten",
|
||||||
|
fields: {
|
||||||
|
id: "Event-ID",
|
||||||
|
received_ts: "Zeitstempel",
|
||||||
|
depth: "Tiefe",
|
||||||
|
state_group: "Zustandsgruppe",
|
||||||
|
},
|
||||||
|
},
|
||||||
room_state: {
|
room_state: {
|
||||||
name: "Zustandsereignisse",
|
name: "Zustandsereignisse",
|
||||||
fields: {
|
fields: {
|
||||||
@@ -349,5 +362,10 @@ export default {
|
|||||||
empty: "Keine Einträge vorhanden",
|
empty: "Keine Einträge vorhanden",
|
||||||
invite: "",
|
invite: "",
|
||||||
},
|
},
|
||||||
|
navigation: {
|
||||||
|
...germanMessages.ra.navigation,
|
||||||
|
skip_nav: "Zum Inhalt springen",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
export default de;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import englishMessages from "ra-language-english";
|
import englishMessages from "ra-language-english";
|
||||||
|
|
||||||
export default {
|
const en = {
|
||||||
...englishMessages,
|
...englishMessages,
|
||||||
synapseadmin: {
|
synapseadmin: {
|
||||||
auth: {
|
auth: {
|
||||||
@@ -150,6 +150,10 @@ export default {
|
|||||||
topic: "Topic",
|
topic: "Topic",
|
||||||
avatar: "Avatar",
|
avatar: "Avatar",
|
||||||
},
|
},
|
||||||
|
helper: {
|
||||||
|
forward_extremities:
|
||||||
|
"Forward extremities are the leaf events at the end of a Directed acyclic graph (DAG) in a room, aka events that have no children. The more exist in a room, the more state resolution that Synapse needs to perform (hint: it's an expensive operation). While Synapse has code to prevent too many of these existing at one time in a room, bugs can sometimes make them crop up again. If a room has >10 forward extremities, it's worth checking which room is the culprit and potentially removing them using the SQL queries mentioned in #1760.",
|
||||||
|
},
|
||||||
enums: {
|
enums: {
|
||||||
join_rules: {
|
join_rules: {
|
||||||
public: "Public",
|
public: "Public",
|
||||||
@@ -228,7 +232,7 @@ export default {
|
|||||||
name: "Media",
|
name: "Media",
|
||||||
fields: {
|
fields: {
|
||||||
media_id: "Media ID",
|
media_id: "Media ID",
|
||||||
media_length: "Lenght",
|
media_length: "File Size (in Bytes)",
|
||||||
media_type: "Type",
|
media_type: "Type",
|
||||||
upload_name: "File name",
|
upload_name: "File name",
|
||||||
quarantined_by: "Quarantined by",
|
quarantined_by: "Quarantined by",
|
||||||
@@ -291,6 +295,15 @@ export default {
|
|||||||
media_length: "Media length",
|
media_length: "Media length",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
forward_extremities: {
|
||||||
|
name: "Forward Extremities",
|
||||||
|
fields: {
|
||||||
|
id: "Event ID",
|
||||||
|
received_ts: "Timestamp",
|
||||||
|
depth: "Depth",
|
||||||
|
state_group: "State group",
|
||||||
|
},
|
||||||
|
},
|
||||||
room_state: {
|
room_state: {
|
||||||
name: "State events",
|
name: "State events",
|
||||||
fields: {
|
fields: {
|
||||||
@@ -319,3 +332,4 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
export default en;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import chineseMessages from "ra-language-chinese";
|
import chineseMessages from "ra-language-chinese";
|
||||||
|
|
||||||
export default {
|
const zh = {
|
||||||
...chineseMessages,
|
...chineseMessages,
|
||||||
synapseadmin: {
|
synapseadmin: {
|
||||||
auth: {
|
auth: {
|
||||||
@@ -288,3 +288,4 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
export default zh;
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
import { configure } from "enzyme";
|
|
||||||
import Adapter from "enzyme-adapter-react-16";
|
|
||||||
import fetchMock from "jest-fetch-mock";
|
import fetchMock from "jest-fetch-mock";
|
||||||
|
|
||||||
configure({ adapter: new Adapter() });
|
|
||||||
fetchMock.enableMocks();
|
fetchMock.enableMocks();
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import { fetchUtils } from "react-admin";
|
|||||||
const authProvider = {
|
const authProvider = {
|
||||||
// called when the user attempts to log in
|
// called when the user attempts to log in
|
||||||
login: ({ base_url, username, password }) => {
|
login: ({ base_url, username, password }) => {
|
||||||
|
// force homeserver for protection in case the form is manipulated
|
||||||
|
base_url = process.env.REACT_APP_SERVER || base_url;
|
||||||
|
|
||||||
console.log("login ");
|
console.log("login ");
|
||||||
const options = {
|
const options = {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -10,6 +13,7 @@ const authProvider = {
|
|||||||
type: "m.login.password",
|
type: "m.login.password",
|
||||||
user: username,
|
user: username,
|
||||||
password: password,
|
password: password,
|
||||||
|
device_id: localStorage.getItem("device_id"),
|
||||||
initial_device_display_name: "Synapse Admin",
|
initial_device_display_name: "Synapse Admin",
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
@@ -17,6 +21,7 @@ const authProvider = {
|
|||||||
// use the base_url from login instead of the well_known entry from the
|
// use the base_url from login instead of the well_known entry from the
|
||||||
// server, since the admin might want to access the admin API via some
|
// server, since the admin might want to access the admin API via some
|
||||||
// private address
|
// private address
|
||||||
|
base_url = base_url.replace(/\/+$/g, "");
|
||||||
localStorage.setItem("base_url", base_url);
|
localStorage.setItem("base_url", base_url);
|
||||||
|
|
||||||
const decoded_base_url = window.decodeURIComponent(base_url);
|
const decoded_base_url = window.decodeURIComponent(base_url);
|
||||||
@@ -48,7 +53,6 @@ const authProvider = {
|
|||||||
if (typeof access_token === "string") {
|
if (typeof access_token === "string") {
|
||||||
fetchUtils.fetchJson(logout_api_url, options).then(({ json }) => {
|
fetchUtils.fetchJson(logout_api_url, options).then(({ json }) => {
|
||||||
localStorage.removeItem("access_token");
|
localStorage.removeItem("access_token");
|
||||||
localStorage.removeItem("device_id");
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
|||||||
@@ -67,9 +67,8 @@ const resourceMap = {
|
|||||||
return json.total_rooms;
|
return json.total_rooms;
|
||||||
},
|
},
|
||||||
delete: params => ({
|
delete: params => ({
|
||||||
endpoint: `/_synapse/admin/v1/rooms/${params.id}/delete`,
|
endpoint: `/_synapse/admin/v1/rooms/${params.id}`,
|
||||||
body: { block: false },
|
body: { block: false },
|
||||||
method: "POST",
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
reports: {
|
reports: {
|
||||||
@@ -208,6 +207,22 @@ const resourceMap = {
|
|||||||
return json.total;
|
return json.total;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
forward_extremities: {
|
||||||
|
map: fe => ({
|
||||||
|
...fe,
|
||||||
|
id: fe.event_id,
|
||||||
|
}),
|
||||||
|
reference: id => ({
|
||||||
|
endpoint: `/_synapse/admin/v1/rooms/${id}/forward_extremities`,
|
||||||
|
}),
|
||||||
|
data: "results",
|
||||||
|
total: json => {
|
||||||
|
return json.count;
|
||||||
|
},
|
||||||
|
delete: params => ({
|
||||||
|
endpoint: `/_synapse/admin/v1/rooms/${params.id}/forward_extremities`,
|
||||||
|
}),
|
||||||
|
},
|
||||||
room_directory: {
|
room_directory: {
|
||||||
path: "/_matrix/client/r0/publicRooms",
|
path: "/_matrix/client/r0/publicRooms",
|
||||||
map: rd => ({
|
map: rd => ({
|
||||||
@@ -314,10 +329,13 @@ const dataProvider = {
|
|||||||
getManyReference: (resource, params) => {
|
getManyReference: (resource, params) => {
|
||||||
console.log("getManyReference " + resource);
|
console.log("getManyReference " + resource);
|
||||||
const { page, perPage } = params.pagination;
|
const { page, perPage } = params.pagination;
|
||||||
|
const { field, order } = params.sort;
|
||||||
const from = (page - 1) * perPage;
|
const from = (page - 1) * perPage;
|
||||||
const query = {
|
const query = {
|
||||||
from: from,
|
from: from,
|
||||||
limit: perPage,
|
limit: perPage,
|
||||||
|
order_by: field,
|
||||||
|
dir: getSearchOrder(order),
|
||||||
};
|
};
|
||||||
|
|
||||||
const homeserver = localStorage.getItem("base_url");
|
const homeserver = localStorage.getItem("base_url");
|
||||||
|
|||||||
Reference in New Issue
Block a user