34 Commits
0.8.0 ... 0.8.3

Author SHA1 Message Date
Manuel Stahl
9c36ed6566 Increment version
Change-Id: I48fb812632dc2e498ad267477809a16fc882cf4c
2021-08-25 10:42:34 +02:00
dklimpel
8536f552d4 Add button to quarantine media (#180)
Change-Id: I6496826fdf75ab8b7b3ed5a9056abf86a50caea3
2021-08-25 10:42:34 +02:00
Manuel Stahl
aaf782d24f Use .gitignore for prettier as well
Change-Id: Ibfe73812a5375cc251b859b8aa441583c0cdcd41
2021-08-25 10:42:34 +02:00
Manuel Stahl
2886203594 Add github action that builds docker images and pushes them to docker hub
Change-Id: I60d39cc266c2e8b905e2ac4a367bd5a22b79b57b
2021-08-25 10:42:34 +02:00
csett86
865fc98336 Add github action that packages a release tarball (#148)
Change-Id: I368a834a27f69550596a041c1e6b84afd40011b7
2021-08-24 09:58:35 +02:00
Dirk Klimpel
e6f01f035b Add buttons to protect and unprotect users' media from quarantine (#150) 2021-08-19 11:51:07 +02:00
Manuel Stahl
32e088ac5a yarn: Upgrade packages
- babel 7.15
- eslint 7.32
- react-admin 3.17

Change-Id: Ief4ad5870ae721fb432e19686607376b83eff12a
2021-08-18 09:48:41 +02:00
Dirk Klimpel
bf3d13916f Add SSO external_ids to user (#168) 2021-08-18 09:33:22 +02:00
Manuel Stahl
07b1df5855 Add Github action badge for build checks 2021-08-18 09:27:25 +02:00
Dirk Klimpel
634341ea07 Add GitHub Action to run CI tests (#162) 2021-08-18 09:18:09 +02:00
Dirk Klimpel
361643c3da Fix link in README.md (#175) 2021-08-17 08:22:36 +02:00
Dirk Klimpel
5262518699 Update links to new Synapse documentation (#164) 2021-07-06 09:57:44 +02:00
Dirk Klimpel
78a282863a Fix broken CI with language files (#165) 2021-07-06 09:56:09 +02:00
Michael Albert
ff0201273a Bump version and update packages
Change-Id: If148587c9e5895ab6b8a70ec34c9190f0bb8f2e0
2021-07-05 14:48:25 +02:00
Dirk Klimpel
e50c95b4be Fix CSV import button (#154) 2021-07-05 14:32:51 +02:00
Dirk Klimpel
9f16e5c6ba Change delete room API to DELETE (#151) 2021-07-05 14:32:44 +02:00
dependabot[bot]
509a45cba4 Bump dns-packet from 1.3.1 to 1.3.4 (#145)
Bumps [dns-packet](https://github.com/mafintosh/dns-packet) from 1.3.1 to 1.3.4.
- [Release notes](https://github.com/mafintosh/dns-packet/releases)
- [Changelog](https://github.com/mafintosh/dns-packet/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mafintosh/dns-packet/compare/v1.3.1...v1.3.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-15 11:03:47 +02:00
dependabot[bot]
5c6e5a9641 Bump ws from 6.2.1 to 6.2.2 (#152)
Bumps [ws](https://github.com/websockets/ws) from 6.2.1 to 6.2.2.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/commits)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-15 11:03:31 +02:00
Dirk Klimpel
e3d5d51342 Fix broken RoomDirectoryFilter (#155) 2021-06-15 11:03:03 +02:00
Michael Albert
985673b161 Increment version
Change-Id: I149896f55be7840b240d92fed5880e3f5624b857
2021-05-25 15:03:15 +02:00
John Francis Sukamto
d72357f64f Update en.js (#144)
Suggested a UI name change for media size (assuming length is size in bytes)
2021-05-25 15:01:13 +02:00
Dirk Klimpel
e19c34324b Allow fixed homeserver (#142) 2021-05-18 12:39:53 +02:00
Dirk Klimpel
3ea1f51eb5 Add a new tab to rooms with forward extremities (#107)
Add a new tab to rooms with forward extremities.
2021-05-08 19:10:51 +02:00
Manuel Stahl
229518e456 Show room alias or room id in room list if room has no name
Change-Id: Iad769f31347566ccf0b8a978b31f5123553e9dbc
2021-05-05 20:24:15 +02:00
Dirk Klimpel
5a5a7143af Enable sorting of user list (#133)
New in Synapse 1.32.0
Fixes: #132, #136
2021-05-05 19:36:47 +02:00
Manuel Stahl
dda8ba5e85 Update nodejs version for travis
Change-Id: I7d44f5df7d4479efcb1d44f5ba23467effad147e
2021-05-05 19:31:50 +02:00
Manuel Stahl
5208198b76 Replace enzyme with testing-library/react
Enzyme is not compatible with react 17.

Change-Id: If9bca2c482bfe10a18d2ee2bc213dab966849b5b
2021-05-05 19:23:01 +02:00
Manuel Stahl
c8082a7198 Remove TestContext from App.test.js
The TestContext is only required for components that depend on react-admin,
but not for the Admin component itself.

Change-Id: I3e07cb6bfa592f1bf59ca282cdf1c2e6c922f619
2021-05-05 19:23:01 +02:00
Michael Albert
10831796e3 Add missing translation
Change-Id: Iab3203742498d2c6768b5885c0522ff8365b58f2
2021-05-05 09:41:05 +00:00
Michael Albert
5ee5288edf Fix some DOM errors
Change-Id: I22a108fd5ce6a344e629e4af0345a0221de44052
2021-05-05 09:40:48 +00:00
Manuel Stahl
a5528d9fe7 yarn: Upgrade packages
- eslint 7.25
- ra-language-german 3.13
- react-admin 3.15
- react-dom 17.0
- react-scripts 4.0

Change-Id: Iad982cf647470bc16194000519a72c401009c9fa
2021-05-04 18:42:05 +02:00
Manuel Stahl
e2fd934851 Allow base URL with path
Ignore and remove trailing slashes.
Fixes #134.

Change-Id: Iedf266e9a93e6939f7f66707fee59a2b56226216
2021-05-04 18:41:54 +02:00
Manuel Stahl
0bc1ce3226 Reuse device_id for synapse-admin on login
Change-Id: I47bbfd1e33ef8bffb618101ae233aeb093cf0ada
2021-05-04 17:10:49 +02:00
Manuel Stahl
41ce58bac8 Enable sorting in tab of users' media (#138) 2021-05-04 16:18:12 +02:00
22 changed files with 4723 additions and 4057 deletions

5
.env Normal file
View 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

21
.github/workflows/build-test.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: build-test
on:
push:
branches: ["master"]
pull_request:
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: 14
- name: Install dependencies
run: yarn --frozen-lockfile
- name: Run tests
run: yarn test

36
.github/workflows/docker-release.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: Create docker image(s) and push to docker hub
on:
push:
tags:
- '[0-9]+\.[0-9]+\.[0-9]+'
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: awesometechnologies/synapse-admin:latest
- name: Update repo description
uses: peter-evans/dockerhub-description@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
repository: awesometechnologies/synapse-admin

28
.github/workflows/github-release.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
name: Create release tarball and attach to tag
on:
push:
tags:
- "*"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: "14"
- run: yarn install
- run: yarn build
- run: |
version=`git describe --dirty --tags || echo unknown`
mkdir -p dist
cp -r build synapse-admin-$version
tar chvzf dist/synapse-admin-$version.tar.gz synapse-admin-$version
- uses: softprops/action-gh-release@b7e450da2a4b4cb4bfbae528f788167786cfcedf
with:
files: dist/*.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,5 +1,5 @@
language: node_js
node_js:
- 13
- lts/*
cache: yarn

View File

@@ -1,13 +1,14 @@
[![Build Status](https://travis-ci.org/Awesome-Technologies/synapse-admin.svg?branch=master)](https://travis-ci.org/Awesome-Technologies/synapse-admin)
[![build-test](https://github.com/Awesome-Technologies/synapse-admin/actions/workflows/build-test.yml/badge.svg)](https://github.com/Awesome-Technologies/synapse-admin/actions/workflows/build-test.yml)
# Synapse admin ui
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.38.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://github.com/matrix-org/synapse/blob/develop/docs/admin_api/version_api.rst).
See also [Synapse version API](https://matrix-org.github.io/synapse/develop/admin_api/version_api.html).
After entering the URL on the login page of synapse-admin the server version appears below the input field.
@@ -16,26 +17,41 @@ You need access to the following endpoints:
- `/_matrix`
- `/_synapse/admin`
See also [Synapse administration endpoints](https://github.com/matrix-org/synapse/blob/develop/docs/reverse_proxy.md#synapse-administration-endpoints)
See also [Synapse administration endpoints](https://matrix-org.github.io/synapse/develop/reverse_proxy.html#synapse-administration-endpoints)
## Step-By-Step install:
You have two options:
You have three options:
1. Download the source code from github and run using nodejs
2. Run the Docker container
1. Download the tarball and serve with any webserver
2. Download the source code from github and run using nodejs
3. Run the Docker container
Steps for 1):
- make sure you have a webserver installed that can serve static files (any webserver like nginx or apache will do)
- configure a vhost for synapse admin on your webserver
- download the .tar.gz from the latest release: https://github.com/Awesome-Technologies/synapse-admin/releases/latest
- unpack the .tar.gz
- move or symlink the `synapse-admin-x.x.x` into your vhosts root dir
- open the url of the vhost in your browser
Steps for 2):
- make sure you have installed the following: git, yarn, nodejs
- download the source code: `git clone https://github.com/Awesome-Technologies/synapse-admin.git`
- change into downloaded directory: `cd synapse-admin`
- download dependencies: `yarn install`
- start web server: `yarn start`
Steps for 2):
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/).
- 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`
Steps for 3):
- 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`
> note: if you're building on an architecture other than amd64 (for example a raspberry pi), make sure to define a maximum ram for node. otherwise the build will fail.

View File

@@ -1,6 +1,6 @@
{
"name": "synapse-admin",
"version": "0.8.0",
"version": "0.8.3",
"description": "Admin GUI for the Matrix.org server Synapse",
"author": "Awesome Technologies Innovationslabor GmbH",
"license": "Apache-2.0",
@@ -11,26 +11,24 @@
},
"devDependencies": {
"@testing-library/jest-dom": "^5.1.1",
"@testing-library/react": "^10.0.2",
"@testing-library/user-event": "^12.0.11",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.10.1",
"@testing-library/react": "^11.2.6",
"@testing-library/user-event": "^13.1.8",
"eslint": "^7.25.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.1.2",
"jest-fetch-mock": "^3.0.3",
"prettier": "^2.0.0",
"ra-test": "^3.14.0"
"prettier": "^2.2.0",
"ra-test": "^3.15.0"
},
"dependencies": {
"papaparse": "^5.2.0",
"prop-types": "^15.7.2",
"ra-language-chinese": "^2.0.10",
"ra-language-german": "^2.1.2",
"ra-language-german": "^3.13.4",
"react": "^17.0.0",
"react-admin": "^3.14.0",
"react-dom": "^16.14.0",
"react-scripts": "^3.4.4"
"react-admin": "^3.15.0",
"react-dom": "^17.0.2",
"react-scripts": "^4.0.0"
},
"scripts": {
"start": "REACT_APP_VERSION=$(git describe --tags) react-scripts start",
@@ -38,7 +36,7 @@
"fix:other": "yarn prettier --write",
"fix:code": "yarn test:lint --fix",
"fix": "yarn fix:code && yarn fix:other",
"prettier": "prettier \"**/*.{js,jsx,json,md,scss,yaml,yml}\"",
"prettier": "prettier --ignore-path .gitignore \"**/*.{js,jsx,json,md,scss,yaml,yml}\"",
"test:code": "react-scripts test",
"test:lint": "eslint --ignore-path .gitignore --ext .js,.jsx .",
"test:style": "yarn prettier --list-different",

View File

@@ -73,6 +73,7 @@ const App = () => (
<Resource name="joined_rooms" />
<Resource name="pushers" />
<Resource name="servernotices" />
<Resource name="forward_extremities" />
<Resource name="room_state" />
</Admin>
);

View File

@@ -1,14 +1,9 @@
import React from "react";
import { TestContext } from "ra-test";
import { shallow } from "enzyme";
import { render } from "@testing-library/react";
import App from "./App";
describe("App", () => {
it("renders", () => {
shallow(
<TestContext>
<App />
</TestContext>
);
render(<App />);
});
});

View File

@@ -82,6 +82,7 @@ const LoginPage = ({ theme }) => {
const setLocale = useSetLocale();
const translate = useTranslate();
const base_url = localStorage.getItem("base_url");
const cfg_base_url = process.env.REACT_APP_SERVER;
const renderInput = ({
meta: { touched, error } = {},
@@ -111,7 +112,9 @@ const LoginPage = ({ theme }) => {
if (!values.base_url.match(/^(http|https):\/\//)) {
errors.base_url = translate("synapseadmin.auth.protocol_error");
} 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");
}
@@ -147,7 +150,7 @@ const LoginPage = ({ theme }) => {
const [serverVersion, setServerVersion] = useState("");
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
const home_server = extractHomeServer(formData.username);
const wellKnownUrl = `https://${home_server}/.well-known/matrix/client`;
@@ -199,6 +202,7 @@ const LoginPage = ({ theme }) => {
label={translate("ra.auth.username")}
disabled={loading}
onBlur={handleUsernameChange}
resettable
fullWidth
/>
</div>
@@ -209,6 +213,7 @@ const LoginPage = ({ theme }) => {
label={translate("ra.auth.password")}
type="password"
disabled={loading}
resettable
fullWidth
/>
</div>
@@ -217,7 +222,8 @@ const LoginPage = ({ theme }) => {
name="base_url"
component={renderInput}
label={translate("synapseadmin.auth.base_url")}
disabled={loading}
disabled={cfg_base_url || loading}
resettable
fullWidth
/>
</div>
@@ -228,7 +234,7 @@ const LoginPage = ({ theme }) => {
return (
<Form
initialValues={{ base_url: base_url }}
initialValues={{ base_url: cfg_base_url || base_url }}
onSubmit={handleSubmit}
validate={validate}
render={({ handleSubmit }) => (

View File

@@ -1,11 +1,11 @@
import React from "react";
import { render } from "@testing-library/react";
import { TestContext } from "ra-test";
import { shallow } from "enzyme";
import LoginPage from "./LoginPage";
describe("LoginForm", () => {
it("renders", () => {
shallow(
render(
<TestContext>
<LoginPage />
</TestContext>

View File

@@ -171,10 +171,14 @@ const RoomDirectoryFilter = ({ ...props }) => {
);
};
export const FilterableRoomDirectoryList = ({ ...props }) => {
export const FilterableRoomDirectoryList = ({
roomDirectoryFilters,
dispatch,
...props
}) => {
const classes = useStyles();
const translate = useTranslate();
const filter = props.roomDirectoryFilters;
const filter = roomDirectoryFilters;
const roomIdFilter = filter && filter.room_id ? true : false;
const topicFilter = filter && filter.topic ? true : false;
const canonicalAliasFilter = filter && filter.canonical_alias ? true : false;

View File

@@ -2,6 +2,7 @@ import React, { Fragment, useState } from "react";
import classnames from "classnames";
import { fade } from "@material-ui/core/styles/colorManipulator";
import { makeStyles } from "@material-ui/core/styles";
import { Tooltip } from "@material-ui/core";
import {
BooleanInput,
Button,
@@ -10,16 +11,22 @@ import {
SaveButton,
SimpleForm,
Toolbar,
useCreate,
useDelete,
useNotify,
useRefresh,
useTranslate,
} from "react-admin";
import IconCancel from "@material-ui/icons/Cancel";
import BlockIcon from "@material-ui/icons/Block";
import ClearIcon from "@material-ui/icons/Clear";
import DeleteSweepIcon from "@material-ui/icons/DeleteSweep";
import Dialog from "@material-ui/core/Dialog";
import DialogContent from "@material-ui/core/DialogContent";
import DialogContentText from "@material-ui/core/DialogContentText";
import DialogTitle from "@material-ui/core/DialogTitle";
import DeleteSweepIcon from "@material-ui/icons/DeleteSweep";
import IconCancel from "@material-ui/icons/Cancel";
import LockIcon from "@material-ui/icons/Lock";
import LockOpenIcon from "@material-ui/icons/LockOpen";
const useStyles = makeStyles(
theme => ({
@@ -143,3 +150,178 @@ export const DeleteMediaButton = props => {
</Fragment>
);
};
export const ProtectMediaButton = props => {
const { record } = props;
const translate = useTranslate();
const refresh = useRefresh();
const notify = useNotify();
const [create, { loading }] = useCreate("protect_media");
const [deleteOne] = useDelete("protect_media");
if (!record) return null;
const handleProtect = () => {
create(
{ payload: { data: record } },
{
onSuccess: () => {
notify("resources.protect_media.action.send_success");
refresh();
},
onFailure: () =>
notify("resources.protect_media.action.send_failure", "error"),
}
);
};
const handleUnprotect = () => {
deleteOne(
{ payload: { ...record } },
{
onSuccess: () => {
notify("resources.protect_media.action.send_success");
refresh();
},
onFailure: () =>
notify("resources.protect_media.action.send_failure", "error"),
}
);
};
return (
/*
Wrapping Tooltip with <div>
https://github.com/marmelab/react-admin/issues/4349#issuecomment-578594735
*/
<Fragment>
{record.quarantined_by && (
<Tooltip
title={translate("resources.protect_media.action.none", {
_: "resources.protect_media.action.none",
})}
>
<div>
{/*
Button instead BooleanField for
consistent appearance and position in the column
*/}
<Button disabled={true}>
<ClearIcon />
</Button>
</div>
</Tooltip>
)}
{record.safe_from_quarantine && (
<Tooltip
title={translate("resources.protect_media.action.delete", {
_: "resources.protect_media.action.delete",
})}
arrow
>
<div>
<Button onClick={handleUnprotect} disabled={loading}>
<LockIcon />
</Button>
</div>
</Tooltip>
)}
{!record.safe_from_quarantine && !record.quarantined_by && (
<Tooltip
title={translate("resources.protect_media.action.create", {
_: "resources.protect_media.action.create",
})}
>
<div>
<Button onClick={handleProtect} disabled={loading}>
<LockOpenIcon />
</Button>
</div>
</Tooltip>
)}
</Fragment>
);
};
export const QuarantineMediaButton = props => {
const { record } = props;
const translate = useTranslate();
const refresh = useRefresh();
const notify = useNotify();
const [create, { loading }] = useCreate("quarantine_media");
const [deleteOne] = useDelete("quarantine_media");
if (!record) return null;
const handleQuarantaine = () => {
create(
{ payload: { data: record } },
{
onSuccess: () => {
notify("resources.quarantine_media.action.send_success");
refresh();
},
onFailure: () =>
notify("resources.quarantine_media.action.send_failure", "error"),
}
);
};
const handleRemoveQuarantaine = () => {
deleteOne(
{ payload: { ...record } },
{
onSuccess: () => {
notify("resources.quarantine_media.action.send_success");
refresh();
},
onFailure: () =>
notify("resources.quarantine_media.action.send_failure", "error"),
}
);
};
return (
<Fragment>
{record.safe_from_quarantine && (
<Tooltip
title={translate("resources.quarantine_media.action.none", {
_: "resources.quarantine_media.action.none",
})}
>
<div>
<Button disabled={true}>
<ClearIcon />
</Button>
</div>
</Tooltip>
)}
{record.quarantined_by && (
<Tooltip
title={translate("resources.quarantine_media.action.delete", {
_: "resources.quarantine_media.action.delete",
})}
>
<div>
<Button onClick={handleRemoveQuarantaine} disabled={loading}>
<BlockIcon color="error" />
</Button>
</div>
</Tooltip>
)}
{!record.safe_from_quarantine && !record.quarantined_by && (
<Tooltip
title={translate("resources.quarantine_media.action.create", {
_: "resources.quarantine_media.action.create",
})}
>
<div>
<Button onClick={handleQuarantaine} disabled={loading}>
<BlockIcon />
</Button>
</div>
</Tooltip>
)}
</Fragment>
);
};

View File

@@ -8,6 +8,7 @@ import {
DeleteButton,
Filter,
List,
NumberField,
Pagination,
ReferenceField,
ReferenceManyField,
@@ -18,10 +19,14 @@ import {
TabbedShowLayout,
TextField,
TopToolbar,
useRecordContext,
useTranslate,
} from "react-admin";
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 FastForwardIcon from "@material-ui/icons/FastForward";
import HttpsIcon from "@material-ui/icons/Https";
import NoEncryptionIcon from "@material-ui/icons/NoEncryption";
import PageviewIcon from "@material-ui/icons/Pageview";
@@ -36,6 +41,13 @@ import {
RoomDirectorySaveButton,
} from "./RoomDirectory";
const useStyles = makeStyles(theme => ({
helper_forward_extremities: {
fontFamily: "Roboto, Helvetica, Arial, sans-serif",
margin: "0.5em",
},
}));
const RoomPagination = props => (
<Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
);
@@ -107,6 +119,7 @@ const RoomShowActions = ({ basePath, data, resource }) => {
};
export const RoomShow = props => {
const classes = useStyles({ props });
const translate = useTranslate();
return (
<Show {...props} actions={<RoomShowActions />} title={<RoomTitle />}>
@@ -218,6 +231,7 @@ export const RoomShow = props => {
]}
/>
</Tab>
<Tab
label={translate("resources.room_state.name", { smart_count: 2 })}
icon={<EventIcon />}
@@ -254,6 +268,40 @@ export const RoomShow = props => {
</Datagrid>
</ReferenceManyField>
</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>
</Show>
);
@@ -305,8 +353,22 @@ const RoomFilter = ({ ...props }) => {
);
};
const FilterableRoomList = ({ ...props }) => {
const filter = props.roomFilters;
const RoomNameField = props => {
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 =
filter && filter.joined_local_members ? true : false;
const stateEventsFilter = filter && filter.state_events ? true : false;
@@ -327,7 +389,7 @@ const FilterableRoomList = ({ ...props }) => {
sortBy="encryption"
label={<HttpsIcon />}
/>
<TextField source="name" />
<RoomNameField source="name" />
<TextField source="joined_members" />
{localMembersFilter && <TextField source="joined_local_members" />}
{stateEventsFilter && <TextField source="state_events" />}

View File

@@ -1,12 +1,13 @@
import React, { cloneElement, Fragment } from "react";
import Avatar from "@material-ui/core/Avatar";
import PersonPinIcon from "@material-ui/icons/PersonPin";
import AssignmentIndIcon from "@material-ui/icons/AssignmentInd";
import ContactMailIcon from "@material-ui/icons/ContactMail";
import DevicesIcon from "@material-ui/icons/Devices";
import GetAppIcon from "@material-ui/icons/GetApp";
import SettingsInputComponentIcon from "@material-ui/icons/SettingsInputComponent";
import NotificationsIcon from "@material-ui/icons/Notifications";
import PermMediaIcon from "@material-ui/icons/PermMedia";
import PersonPinIcon from "@material-ui/icons/PersonPin";
import SettingsInputComponentIcon from "@material-ui/icons/SettingsInputComponent";
import ViewListIcon from "@material-ui/icons/ViewList";
import {
ArrayInput,
@@ -36,7 +37,6 @@ import {
DeleteButton,
SaveButton,
regex,
useRedirect,
useTranslate,
Pagination,
CreateButton,
@@ -45,11 +45,13 @@ import {
sanitizeListRestProps,
NumberField,
} from "react-admin";
import { Link } from "react-router-dom";
import { ServerNoticeButton, ServerNoticeBulkButton } from "./ServerNotices";
import { DeviceRemoveButton } from "./devices";
import { ProtectMediaButton, QuarantineMediaButton } from "./media";
import { makeStyles } from "@material-ui/core/styles";
const redirect = (basePath, id, data) => {
const redirect = () => {
return {
pathname: "/import_users",
};
@@ -85,7 +87,6 @@ const UserListActions = ({
total,
...rest
}) => {
const redirectTo = useRedirect();
return (
<TopToolbar className={className} {...sanitizeListRestProps(rest)}>
{filters &&
@@ -106,12 +107,7 @@ const UserListActions = ({
maxResults={maxResults}
/>
{/* Add your custom actions */}
<Button
onClick={() => {
redirectTo(redirect);
}}
label="CSV Import"
>
<Button component={Link} to={redirect} label="CSV Import">
<GetAppIcon style={{ transform: "rotate(180deg)", fontSize: "20" }} />
</Button>
</TopToolbar>
@@ -162,6 +158,7 @@ export const UserList = props => {
{...props}
filters={<UserFilter />}
filterDefaultValues={{ guests: true, deactivated: false }}
sort={{ field: "name", order: "ASC" }}
actions={<UserListActions maxResults={10000} />}
bulkActionButtons={<UserBulkActionButtons />}
pagination={<UserPagination />}
@@ -169,14 +166,14 @@ export const UserList = props => {
<Datagrid rowClick="edit">
<AvatarField
source="avatar_src"
sortable={false}
className={classes.small}
sortBy="avatar_url"
/>
<TextField source="id" sortable={false} />
<TextField source="displayname" sortable={false} />
<BooleanField source="is_guest" sortable={false} />
<BooleanField source="admin" sortable={false} />
<BooleanField source="deactivated" sortable={false} />
<TextField source="id" sortBy="name" />
<TextField source="displayname" />
<BooleanField source="is_guest" />
<BooleanField source="admin" />
<BooleanField source="deactivated" />
</Datagrid>
</List>
);
@@ -337,6 +334,23 @@ export const UserEdit = props => {
</ArrayInput>
</FormTab>
<FormTab
label="synapseadmin.users.tabs.sso"
icon={<AssignmentIndIcon />}
path="sso"
>
<ArrayField source="external_ids" label={false}>
<Datagrid style={{ width: "100%" }}>
<TextField source="auth_provider" sortable={false} />
<TextField
source="external_id"
label="resources.users.fields.id"
sortable={false}
/>
</Datagrid>
</ArrayField>
</FormTab>
<FormTab
label={translate("resources.devices.name", { smart_count: 2 })}
icon={<DevicesIcon />}
@@ -420,6 +434,7 @@ export const UserEdit = props => {
addLabel={false}
pagination={<UserPagination />}
perPage={50}
sort={{ field: "created_ts", order: "DESC" }}
>
<Datagrid style={{ width: "100%" }}>
<DateField
@@ -433,7 +448,6 @@ export const UserEdit = props => {
minute: "2-digit",
second: "2-digit",
}}
sortable={false}
/>
<DateField
source="last_access_ts"
@@ -446,14 +460,14 @@ export const UserEdit = props => {
minute: "2-digit",
second: "2-digit",
}}
sortable={false}
/>
<TextField source="media_id" sortable={false} />
<NumberField source="media_length" sortable={false} />
<TextField source="media_type" sortable={false} />
<TextField source="upload_name" sortable={false} />
<TextField source="quarantined_by" sortable={false} />
<BooleanField source="safe_from_quarantine" sortable={false} />
<TextField source="media_id" />
<NumberField source="media_length" />
<TextField source="media_type" />
<TextField source="upload_name" />
<TextField source="quarantined_by" />
<QuarantineMediaButton label="resources.quarantine_media.action.name" />
<ProtectMediaButton label="resources.users_media.fields.safe_from_quarantine" />
<DeleteButton mutationMode="pessimistic" redirect={false} />
</Datagrid>
</ReferenceManyField>

View File

@@ -1,6 +1,6 @@
import germanMessages from "ra-language-german";
export default {
const de = {
...germanMessages,
synapseadmin: {
auth: {
@@ -14,6 +14,7 @@ export default {
users: {
invalid_user_id:
"Muss eine vollständige Matrix Benutzer-ID sein, z.B. @benutzer_id:homeserver",
tabs: { sso: "SSO" },
},
rooms: {
details: "Raumdetails",
@@ -120,6 +121,7 @@ export default {
address: "Adresse",
creation_ts_ms: "Zeitpunkt der Erstellung",
consent_version: "Zugestimmte Geschäftsbedingungen",
auth_provider: "Provider",
},
helper: {
deactivate:
@@ -152,6 +154,10 @@ export default {
topic: "Thema",
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: {
join_rules: {
public: "Öffentlich",
@@ -236,7 +242,7 @@ export default {
media_type: "Typ",
upload_name: "Dateiname",
quarantined_by: "Zur Quarantäne hinzugefügt",
safe_from_quarantine: "Geschützt vor Quarantäne",
safe_from_quarantine: "Schutz vor Quarantäne",
created_ts: "Erstellt",
last_access_ts: "Letzter Zugriff",
},
@@ -254,8 +260,26 @@ export default {
send_failure: "Beim Versenden ist ein Fehler aufgetreten.",
},
helper: {
send:
"Diese API löscht die lokalen Medien von der Festplatte des eigenen Servers. Dies umfasst alle lokalen Miniaturbilder und Kopien von Medien. Diese API wirkt sich nicht auf Medien aus, die sich in externen Medien-Repositories befinden.",
send: "Diese API löscht die lokalen Medien von der Festplatte des eigenen Servers. Dies umfasst alle lokalen Miniaturbilder und Kopien von Medien. Diese API wirkt sich nicht auf Medien aus, die sich in externen Medien-Repositories befinden.",
},
},
protect_media: {
action: {
create: "Ungeschützt, Schutz erstellen",
delete: "Geschützt, Schutz aufheben",
none: "In Quarantäne",
send_success: "Erfolgreich den Schutz-Status geändert.",
send_failure: "Beim Versenden ist ein Fehler aufgetreten.",
},
},
quarantine_media: {
action: {
name: "Quarantäne",
create: "Zur Quarantäne hinzufügen",
delete: "In Quarantäne, Quarantäne aufheben",
none: "Geschützt vor Quarantäne",
send_success: "Erfolgreich den Quarantäne-Status geändert.",
send_failure: "Beim Versenden ist ein Fehler aufgetreten.",
},
},
pushers: {
@@ -284,8 +308,7 @@ export default {
send_failure: "Beim Versenden ist ein Fehler aufgetreten.",
},
helper: {
send:
'Sendet eine Serverbenachrichtigung an die ausgewählten Nutzer. Hierfür muss das Feature "Server Notices" auf dem Server aktiviert sein.',
send: 'Sendet eine Serverbenachrichtigung an die ausgewählten Nutzer. Hierfür muss das Feature "Server Notices" auf dem Server aktiviert sein.',
},
},
user_media_statistics: {
@@ -295,6 +318,15 @@ export default {
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: {
name: "Zustandsereignisse",
fields: {
@@ -349,5 +381,10 @@ export default {
empty: "Keine Einträge vorhanden",
invite: "",
},
navigation: {
...germanMessages.ra.navigation,
skip_nav: "Zum Inhalt springen",
},
},
};
export default de;

View File

@@ -1,6 +1,6 @@
import englishMessages from "ra-language-english";
export default {
const en = {
...englishMessages,
synapseadmin: {
auth: {
@@ -14,6 +14,7 @@ export default {
users: {
invalid_user_id:
"Must be a fully qualified Matrix user-id, e.g. @user_id:homeserver",
tabs: { sso: "SSO" },
},
rooms: {
tabs: {
@@ -119,6 +120,7 @@ export default {
address: "Address",
creation_ts_ms: "Creation timestamp",
consent_version: "Consent version",
auth_provider: "Provider",
},
helper: {
deactivate: "You must provide a password to re-activate an account.",
@@ -150,6 +152,10 @@ export default {
topic: "Topic",
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: {
join_rules: {
public: "Public",
@@ -228,7 +234,7 @@ export default {
name: "Media",
fields: {
media_id: "Media ID",
media_length: "Lenght",
media_length: "File Size (in Bytes)",
media_type: "Type",
upload_name: "File name",
quarantined_by: "Quarantined by",
@@ -250,8 +256,26 @@ export default {
send_failure: "An error has occurred.",
},
helper: {
send:
"This API deletes the local media from the disk of your own server. This includes any local thumbnails and copies of media downloaded. This API will not affect media that has been uploaded to external media repositories.",
send: "This API deletes the local media from the disk of your own server. This includes any local thumbnails and copies of media downloaded. This API will not affect media that has been uploaded to external media repositories.",
},
},
protect_media: {
action: {
create: "Unprotected, create protection",
delete: "Protected, remove protection",
none: "In quarantine",
send_success: "Successfully changed the protection status.",
send_failure: "An error has occurred.",
},
},
quarantine_media: {
action: {
name: "Quarantine",
create: "Add to quarantine",
delete: "In quarantine, unquarantine",
none: "Protected from quarantine",
send_success: "Successfully changed the quarantine status.",
send_failure: "An error has occurred.",
},
},
pushers: {
@@ -280,8 +304,7 @@ export default {
send_failure: "An error has occurred.",
},
helper: {
send:
'Sends a server notice to the selected users. The feature "Server Notices" has to be activated at the server.',
send: 'Sends a server notice to the selected users. The feature "Server Notices" has to be activated at the server.',
},
},
user_media_statistics: {
@@ -291,6 +314,15 @@ export default {
media_length: "Media length",
},
},
forward_extremities: {
name: "Forward Extremities",
fields: {
id: "Event ID",
received_ts: "Timestamp",
depth: "Depth",
state_group: "State group",
},
},
room_state: {
name: "State events",
fields: {
@@ -319,3 +351,4 @@ export default {
},
},
};
export default en;

View File

@@ -1,6 +1,6 @@
import chineseMessages from "ra-language-chinese";
export default {
const zh = {
...chineseMessages,
synapseadmin: {
auth: {
@@ -245,8 +245,7 @@ export default {
send_failure: "出现了一个错误。",
},
helper: {
send:
"这个API会删除您硬盘上的本地媒体。包含了任何的本地缓存和下载的媒体备份。这个API不会影响上传到外部媒体存储库上的媒体文件。",
send: "这个API会删除您硬盘上的本地媒体。包含了任何的本地缓存和下载的媒体备份。这个API不会影响上传到外部媒体存储库上的媒体文件。",
},
},
pushers: {
@@ -275,8 +274,7 @@ export default {
send_failure: "出现了一个错误。",
},
helper: {
send:
'向选中的用户发送服务器提示。服务器配置中的 "服务器提示(Server Notices)" 选项需要被设置为启用。',
send: '向选中的用户发送服务器提示。服务器配置中的 "服务器提示(Server Notices)" 选项需要被设置为启用。',
},
},
user_media_statistics: {
@@ -288,3 +286,4 @@ export default {
},
},
};
export default zh;

View File

@@ -1,6 +1,3 @@
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import fetchMock from "jest-fetch-mock";
configure({ adapter: new Adapter() });
fetchMock.enableMocks();

View File

@@ -3,6 +3,9 @@ import { fetchUtils } from "react-admin";
const authProvider = {
// called when the user attempts to log in
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 ");
const options = {
method: "POST",
@@ -10,6 +13,7 @@ const authProvider = {
type: "m.login.password",
user: username,
password: password,
device_id: localStorage.getItem("device_id"),
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
// server, since the admin might want to access the admin API via some
// private address
base_url = base_url.replace(/\/+$/g, "");
localStorage.setItem("base_url", base_url);
const decoded_base_url = window.decodeURIComponent(base_url);
@@ -48,7 +53,6 @@ const authProvider = {
if (typeof access_token === "string") {
fetchUtils.fetchJson(logout_api_url, options).then(({ json }) => {
localStorage.removeItem("access_token");
localStorage.removeItem("device_id");
});
}
return Promise.resolve();

View File

@@ -67,9 +67,8 @@ const resourceMap = {
return json.total_rooms;
},
delete: params => ({
endpoint: `/_synapse/admin/v1/rooms/${params.id}/delete`,
endpoint: `/_synapse/admin/v1/rooms/${params.id}`,
body: { block: false },
method: "POST",
}),
},
reports: {
@@ -183,6 +182,32 @@ const resourceMap = {
method: "POST",
}),
},
protect_media: {
map: pm => ({ id: pm.media_id }),
create: params => ({
endpoint: `/_synapse/admin/v1/media/protect/${params.media_id}`,
method: "POST",
}),
delete: params => ({
endpoint: `/_synapse/admin/v1/media/unprotect/${params.media_id}`,
method: "POST",
}),
},
quarantine_media: {
map: qm => ({ id: qm.media_id }),
create: params => ({
endpoint: `/_synapse/admin/v1/media/quarantine/${localStorage.getItem(
"home_server"
)}/${params.media_id}`,
method: "POST",
}),
delete: params => ({
endpoint: `/_synapse/admin/v1/media/unquarantine/${localStorage.getItem(
"home_server"
)}/${params.media_id}`,
method: "POST",
}),
},
servernotices: {
map: n => ({ id: n.event_id }),
create: data => ({
@@ -208,6 +233,22 @@ const resourceMap = {
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: {
path: "/_matrix/client/r0/publicRooms",
map: rd => ({
@@ -314,10 +355,13 @@ const dataProvider = {
getManyReference: (resource, params) => {
console.log("getManyReference " + resource);
const { page, perPage } = params.pagination;
const { field, order } = params.sort;
const from = (page - 1) * perPage;
const query = {
from: from,
limit: perPage,
order_by: field,
dir: getSearchOrder(order),
};
const homeserver = localStorage.getItem("base_url");

8116
yarn.lock

File diff suppressed because it is too large Load Diff