Merge branch 'master' into useRecordContext

This commit is contained in:
Michael Albert 2023-01-24 15:27:52 +01:00 committed by GitHub
commit 5e7ef1775f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 5022 additions and 5915 deletions

20
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,20 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
ignore:
# Major updates for react-admin have breaking changes
- dependency-name: "react-admin"
update-types: ["version-update:semver-major"]
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

View File

@ -10,11 +10,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Setup node - name: Setup node
uses: actions/setup-node@v2 uses: actions/setup-node@v3
with: with:
node-version: 14 node-version: 16
- name: Install dependencies - name: Install dependencies
run: yarn --frozen-lockfile run: yarn --frozen-lockfile
- name: Run tests - name: Run tests

View File

@ -17,13 +17,13 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1 uses: docker/setup-buildx-action@v2
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v1 uses: docker/login-action@v2
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
@ -43,7 +43,7 @@ jobs:
esac esac
echo "::set-output name=tag::$tag" echo "::set-output name=tag::$tag"
- name: Build and Push Tag - name: Build and Push Tag
uses: docker/build-push-action@v2 uses: docker/build-push-action@v3
with: with:
context: . context: .
push: true push: true

26
.github/workflows/edge_ghpage.yml vendored Normal file
View File

@ -0,0 +1,26 @@
name: Build and Deploy Edge version to GH Pages
on:
workflow_dispatch:
push:
branches:
- main
- master
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "16"
- name: Install and Build 🔧
run: |
yarn install
yarn build
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@v4.4.1
with:
branch: gh-pages
folder: build

View File

@ -13,10 +13,10 @@ jobs:
packages: write packages: write
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions/setup-node@v2 - uses: actions/setup-node@v3
with: with:
node-version: "14" node-version: "16"
- run: yarn install - run: yarn install
- run: yarn build - run: yarn build
- run: | - run: |
@ -24,7 +24,7 @@ jobs:
mkdir -p dist mkdir -p dist
cp -r build synapse-admin-$version cp -r build synapse-admin-$version
tar chvzf dist/synapse-admin-$version.tar.gz synapse-admin-$version tar chvzf dist/synapse-admin-$version.tar.gz synapse-admin-$version
- uses: softprops/action-gh-release@b7e450da2a4b4cb4bfbae528f788167786cfcedf - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844
with: with:
files: dist/*.tar.gz files: dist/*.tar.gz
env: env:

View File

@ -6,6 +6,6 @@
"singleQuote": false, "singleQuote": false,
"trailingComma": "es5", "trailingComma": "es5",
"bracketSpacing": true, "bracketSpacing": true,
"jsxBracketSameLine": false, "bracketSameLine": false,
"arrowParens": "avoid", "arrowParens": "avoid"
} }

View File

@ -1,5 +1,6 @@
dist: focal
language: node_js language: node_js
node_js: node_js:
- lts/* - 17
cache: yarn cache: yarn

View File

@ -1,11 +1,14 @@
# Builder # Builder
FROM node:lts as builder FROM node:lts as builder
ARG PUBLIC_URL=/
ARG REACT_APP_SERVER
WORKDIR /src WORKDIR /src
COPY . /src COPY . /src
RUN yarn --network-timeout=100000 install RUN yarn --network-timeout=100000 install
RUN yarn build RUN PUBLIC_URL=$PUBLIC_URL REACT_APP_SERVER=$REACT_APP_SERVER yarn build
# App # App

View File

@ -1,17 +1,27 @@
[![Build Status](https://travis-ci.org/Awesome-Technologies/synapse-admin.svg?branch=master)](https://travis-ci.org/Awesome-Technologies/synapse-admin) [![GitHub license](https://img.shields.io/github/license/Awesome-Technologies/synapse-admin)](https://github.com/Awesome-Technologies/synapse-admin/blob/master/LICENSE)
[![Build Status](https://api.travis-ci.com/Awesome-Technologies/synapse-admin.svg?branch=master)](https://app.travis-ci.com/github/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) [![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)
[![gh-pages](https://github.com/Awesome-Technologies/synapse-admin/actions/workflows/edge_ghpage.yml/badge.svg)](https://awesome-technologies.github.io/synapse-admin/)
[![docker-release](https://github.com/Awesome-Technologies/synapse-admin/actions/workflows/docker-release.yml/badge.svg)](https://hub.docker.com/r/awesometechnologies/synapse-admin)
[![github-release](https://github.com/Awesome-Technologies/synapse-admin/actions/workflows/github-release.yml/badge.svg)](https://github.com/Awesome-Technologies/synapse-admin/releases)
# Synapse admin ui # Synapse admin ui
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.41.0 for all functions to work as expected! ## Usage
### Supported Synapse
It needs at least [Synapse](https://github.com/matrix-org/synapse) v1.48.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).
After entering the URL on the login page of synapse-admin the server version appears below the input field. After entering the URL on the login page of synapse-admin the server version appears below the input field.
### Prerequisites
You need access to the following endpoints: You need access to the following endpoints:
- `/_matrix` - `/_matrix`
@ -19,15 +29,25 @@ You need access to the following endpoints:
See also [Synapse administration endpoints](https://matrix-org.github.io/synapse/develop/reverse_proxy.html#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: ### Use without install
You can use the current version of Synapse Admin without own installation direct
via [GitHub Pages](https://awesome-technologies.github.io/synapse-admin/).
**Note:**
If you want to use the deployment, you have to make sure that the admin endpoints (`/_synapse/admin`) are accessible for your browser.
**Remember: You have no need to expose these endpoints to the internet but to your network.**
If you want your own deployment, follow the [Step-By-Step Install Guide](#step-by-step-install) below.
### Step-By-Step install
You have three options: You have three options:
1. Download the tarball and serve with any webserver 1. [Download the tarball and serve with any webserver](#steps-for-1)
2. Download the source code from github and run using nodejs 2. [Download the source code from github and run using nodejs](#steps-for-2)
3. Run the Docker container 3. [Run the Docker container](#steps-for-3)
Steps for 1): #### Steps for 1)
- make sure you have a webserver installed that can serve static files (any webserver like nginx or apache will do) - 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 - configure a vhost for synapse admin on your webserver
@ -36,7 +56,7 @@ Steps for 1):
- move or symlink the `synapse-admin-x.x.x` into your vhosts root dir - move or symlink the `synapse-admin-x.x.x` into your vhosts root dir
- open the url of the vhost in your browser - open the url of the vhost in your browser
Steps for 2): #### Steps for 2)
- make sure you have installed the following: git, yarn, nodejs - make sure you have installed the following: git, yarn, nodejs
- download the source code: `git clone https://github.com/Awesome-Technologies/synapse-admin.git` - download the source code: `git clone https://github.com/Awesome-Technologies/synapse-admin.git`
@ -49,7 +69,7 @@ Either you define it at startup (e.g. `REACT_APP_SERVER=https://yourmatrixserver
or by editing it in the [.env](.env) file. See also the or by editing it in the [.env](.env) file. See also the
[documentation](https://create-react-app.dev/docs/adding-custom-environment-variables/). [documentation](https://create-react-app.dev/docs/adding-custom-environment-variables/).
Steps for 3): #### 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` - 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`
@ -66,6 +86,9 @@ Steps for 3):
context: https://github.com/Awesome-Technologies/synapse-admin.git context: https://github.com/Awesome-Technologies/synapse-admin.git
# args: # args:
# - NODE_OPTIONS="--max_old_space_size=1024" # - NODE_OPTIONS="--max_old_space_size=1024"
# # see #266, PUBLIC_URL must be without surrounding quotation marks
# - PUBLIC_URL=/synapse-admin
# - REACT_APP_SERVER="https://matrix.example.com"
ports: ports:
- "8080:80" - "8080:80"
restart: unless-stopped restart: unless-stopped

View File

@ -12,10 +12,15 @@ services:
# replace the context definition with this: # replace the context definition with this:
# context: https://github.com/Awesome-Technologies/synapse-admin.git # context: https://github.com/Awesome-Technologies/synapse-admin.git
# args:
# if you're building on an architecture other than amd64, make sure # if you're building on an architecture other than amd64, make sure
# to define a maximum ram for node. otherwise the build will fail. # to define a maximum ram for node. otherwise the build will fail.
# args:
# - NODE_OPTIONS="--max_old_space_size=1024" # - NODE_OPTIONS="--max_old_space_size=1024"
# default is /
# - PUBLIC_URL=/synapse-admin
# You can use a fixed homeserver, so that the user can no longer
# define it himself
# - REACT_APP_SERVER="https://matrix.example.com"
ports: ports:
- "8080:80" - "8080:80"
restart: unless-stopped restart: unless-stopped

View File

@ -1,6 +1,6 @@
{ {
"name": "synapse-admin", "name": "synapse-admin",
"version": "0.8.4", "version": "0.8.5",
"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",
@ -10,12 +10,13 @@
"url": "https://github.com/Awesome-Technologies/synapse-admin" "url": "https://github.com/Awesome-Technologies/synapse-admin"
}, },
"devDependencies": { "devDependencies": {
"@testing-library/jest-dom": "^5.1.1", "@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^11.2.6", "@testing-library/react": "^11.2.6",
"@testing-library/user-event": "^13.1.8", "@testing-library/user-event": "^14.4.3",
"eslint": "^7.25.0", "eslint": "^8.32.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.1.2", "eslint-config-react-app": "^7.0.1",
"eslint-plugin-prettier": "^4.2.1",
"jest-fetch-mock": "^3.0.3", "jest-fetch-mock": "^3.0.3",
"prettier": "^2.2.0", "prettier": "^2.2.0",
"ra-test": "^3.15.0" "ra-test": "^3.15.0"
@ -26,9 +27,9 @@
"ra-language-chinese": "^2.0.10", "ra-language-chinese": "^2.0.10",
"ra-language-german": "^3.13.4", "ra-language-german": "^3.13.4",
"react": "^17.0.0", "react": "^17.0.0",
"react-admin": "^3.15.0", "react-admin": "^3.19.7",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-scripts": "^4.0.0" "react-scripts": "^5.0.1"
}, },
"scripts": { "scripts": {
"start": "REACT_APP_VERSION=$(git describe --tags) react-scripts start", "start": "REACT_APP_VERSION=$(git describe --tags) react-scripts start",

View File

@ -8,12 +8,18 @@ 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 UserIcon from "@material-ui/icons/Group";
import ConfirmationNumberIcon from "@material-ui/icons/ConfirmationNumber";
import EqualizerIcon from "@material-ui/icons/Equalizer"; import EqualizerIcon from "@material-ui/icons/Equalizer";
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 { ImportFeature } from "./components/ImportFeature"; import { ImportFeature } from "./components/ImportFeature";
import {
RegistrationTokenCreate,
RegistrationTokenEdit,
RegistrationTokenList,
} from "./components/RegistrationTokens";
import { RoomDirectoryList } from "./components/RoomDirectory"; import { RoomDirectoryList } from "./components/RoomDirectory";
import { Route } from "react-router-dom"; import { Route } from "react-router-dom";
import germanMessages from "./i18n/de"; import germanMessages from "./i18n/de";
@ -66,6 +72,13 @@ const App = () => (
list={RoomDirectoryList} list={RoomDirectoryList}
icon={FolderSharedIcon} icon={FolderSharedIcon}
/> />
<Resource
name="registration_tokens"
list={RegistrationTokenList}
create={RegistrationTokenCreate}
edit={RegistrationTokenEdit}
icon={ConfirmationNumberIcon}
/>
<Resource name="connections" /> <Resource name="connections" />
<Resource name="devices" /> <Resource name="devices" />
<Resource name="room_members" /> <Resource name="room_members" />

View File

@ -15,6 +15,15 @@ import {
import PageviewIcon from "@material-ui/icons/Pageview"; import PageviewIcon from "@material-ui/icons/Pageview";
import ViewListIcon from "@material-ui/icons/ViewList"; import ViewListIcon from "@material-ui/icons/ViewList";
const date_format = {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
};
const ReportPagination = props => ( const ReportPagination = props => (
<Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} /> <Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
); );
@ -33,14 +42,7 @@ export const ReportShow = props => {
<DateField <DateField
source="received_ts" source="received_ts"
showTime showTime
options={{ options={date_format}
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}}
sortable={true} sortable={true}
/> />
<ReferenceField source="user_id" reference="users"> <ReferenceField source="user_id" reference="users">
@ -68,18 +70,10 @@ export const ReportShow = props => {
icon={<PageviewIcon />} icon={<PageviewIcon />}
path="detail" path="detail"
> >
{" "}
<DateField <DateField
source="event_json.origin_server_ts" source="event_json.origin_server_ts"
showTime showTime
options={{ options={date_format}
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}}
sortable={true} sortable={true}
/> />
<ReferenceField source="sender" reference="users"> <ReferenceField source="sender" reference="users">
@ -116,14 +110,7 @@ export const ReportList = ({ ...props }) => {
<DateField <DateField
source="received_ts" source="received_ts"
showTime showTime
options={{ options={date_format}
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}}
sortable={true} sortable={true}
/> />
<TextField sortable={false} source="user_id" /> <TextField sortable={false} source="user_id" />

View File

@ -78,11 +78,48 @@ const LoginPage = ({ theme }) => {
const login = useLogin(); const login = useLogin();
const notify = useNotify(); const notify = useNotify();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [supportPassAuth, setSupportPassAuth] = useState(true);
var locale = useLocale(); var locale = useLocale();
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 cfg_base_url = process.env.REACT_APP_SERVER;
const [ssoBaseUrl, setSSOBaseUrl] = useState("");
const loginToken = /\?loginToken=([a-zA-Z0-9_-]+)/.exec(window.location.href);
if (loginToken) {
const ssoToken = loginToken[1];
console.log("SSO token is", ssoToken);
// Prevent further requests
window.history.replaceState(
{},
"",
window.location.href.replace(loginToken[0], "#").split("#")[0]
);
const baseUrl = localStorage.getItem("sso_base_url");
localStorage.removeItem("sso_base_url");
if (baseUrl) {
const auth = {
base_url: baseUrl,
username: null,
password: null,
loginToken: ssoToken,
};
console.log("Base URL is:", baseUrl);
console.log("SSO Token is:", ssoToken);
console.log("Let's try token login...");
login(auth).catch(error => {
alert(
typeof error === "string"
? error
: typeof error === "undefined" || !error.message
? "ra.auth.sign_in_error"
: error.message
);
console.error(error);
});
}
}
const renderInput = ({ const renderInput = ({
meta: { touched, error } = {}, meta: { touched, error } = {},
@ -137,6 +174,14 @@ const LoginPage = ({ theme }) => {
}); });
}; };
const handleSSO = () => {
localStorage.setItem("sso_base_url", ssoBaseUrl);
const ssoFullUrl = `${ssoBaseUrl}/_matrix/client/r0/login/sso/redirect?redirectUrl=${encodeURIComponent(
window.location.href
)}`;
window.location.href = ssoFullUrl;
};
const extractHomeServer = username => { const extractHomeServer = username => {
const usernameRegex = /@[a-zA-Z0-9._=\-/]+:([a-zA-Z0-9\-.]+\.[a-zA-Z]+)/; const usernameRegex = /@[a-zA-Z0-9._=\-/]+:([a-zA-Z0-9\-.]+\.[a-zA-Z]+)/;
if (!username) return null; if (!username) return null;
@ -188,6 +233,31 @@ const LoginPage = ({ theme }) => {
.catch(_ => { .catch(_ => {
setServerVersion(""); setServerVersion("");
}); });
// Set SSO Url
const authMethodUrl = `${formData.base_url}/_matrix/client/r0/login`;
let supportPass = false,
supportSSO = false;
fetchUtils
.fetchJson(authMethodUrl, { method: "GET" })
.then(({ json }) => {
json.flows.forEach(f => {
if (f.type === "m.login.password") {
supportPass = true;
} else if (f.type === "m.login.sso") {
supportSSO = true;
}
});
setSupportPassAuth(supportPass);
if (supportSSO) {
setSSOBaseUrl(formData.base_url);
} else {
setSSOBaseUrl("");
}
})
.catch(_ => {
setSSOBaseUrl("");
});
}, },
[formData.base_url] [formData.base_url]
); );
@ -200,7 +270,7 @@ const LoginPage = ({ theme }) => {
name="username" name="username"
component={renderInput} component={renderInput}
label={translate("ra.auth.username")} label={translate("ra.auth.username")}
disabled={loading} disabled={loading || !supportPassAuth}
onBlur={handleUsernameChange} onBlur={handleUsernameChange}
resettable resettable
fullWidth fullWidth
@ -212,7 +282,7 @@ const LoginPage = ({ theme }) => {
component={renderInput} component={renderInput}
label={translate("ra.auth.password")} label={translate("ra.auth.password")}
type="password" type="password"
disabled={loading} disabled={loading || !supportPassAuth}
resettable resettable
fullWidth fullWidth
/> />
@ -273,13 +343,24 @@ const LoginPage = ({ theme }) => {
variant="contained" variant="contained"
type="submit" type="submit"
color="primary" color="primary"
disabled={loading} disabled={loading || !supportPassAuth}
className={classes.button} className={classes.button}
fullWidth fullWidth
> >
{loading && <CircularProgress size={25} thickness={2} />} {loading && <CircularProgress size={25} thickness={2} />}
{translate("ra.auth.sign_in")} {translate("ra.auth.sign_in")}
</Button> </Button>
<Button
variant="contained"
color="secondary"
onClick={handleSSO}
disabled={loading || ssoBaseUrl === ""}
className={classes.button}
fullWidth
>
{loading && <CircularProgress size={25} thickness={2} />}
{translate("synapseadmin.auth.sso_sign_in")}
</Button>
</CardActions> </CardActions>
</Card> </Card>
<Notification /> <Notification />

View File

@ -0,0 +1,132 @@
import React from "react";
import {
BooleanInput,
Create,
Datagrid,
DateField,
DateTimeInput,
Edit,
Filter,
List,
maxValue,
number,
NumberField,
NumberInput,
regex,
SimpleForm,
TextInput,
TextField,
Toolbar,
} from "react-admin";
const date_format = {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
};
const validateToken = [regex(/^[A-Za-z0-9._~-]{0,64}$/)];
const validateUsesAllowed = [number()];
const validateLength = [number(), maxValue(64)];
const dateParser = v => {
const d = new Date(v);
if (isNaN(d)) return 0;
return d.getTime();
};
const dateFormatter = v => {
if (v === undefined || v === null) return;
const d = new Date(v);
const pad = "00";
const year = d.getFullYear().toString();
const month = (pad + (d.getMonth() + 1).toString()).slice(-2);
const day = (pad + d.getDate().toString()).slice(-2);
const hour = (pad + d.getHours().toString()).slice(-2);
const minute = (pad + d.getMinutes().toString()).slice(-2);
// target format yyyy-MM-ddThh:mm
return `${year}-${month}-${day}T${hour}:${minute}`;
};
const RegistrationTokenFilter = props => (
<Filter {...props}>
<BooleanInput source="valid" alwaysOn />
</Filter>
);
export const RegistrationTokenList = props => {
return (
<List
{...props}
filters={<RegistrationTokenFilter />}
filterDefaultValues={{ valid: true }}
pagination={false}
perPage={500}
>
<Datagrid rowClick="edit">
<TextField source="token" sortable={false} />
<NumberField source="uses_allowed" sortable={false} />
<NumberField source="pending" sortable={false} />
<NumberField source="completed" sortable={false} />
<DateField
source="expiry_time"
showTime
options={date_format}
sortable={false}
/>
</Datagrid>
</List>
);
};
export const RegistrationTokenCreate = props => (
<Create {...props}>
<SimpleForm redirect="list" toolbar={<Toolbar alwaysEnableSaveButton />}>
<TextInput
source="token"
autoComplete="off"
validate={validateToken}
resettable
/>
<NumberInput
source="length"
validate={validateLength}
helperText="resources.registration_tokens.helper.length"
step={1}
/>
<NumberInput
source="uses_allowed"
validate={validateUsesAllowed}
step={1}
/>
<DateTimeInput source="expiry_time" parse={dateParser} />
</SimpleForm>
</Create>
);
export const RegistrationTokenEdit = props => {
return (
<Edit {...props}>
<SimpleForm>
<TextInput source="token" disabled />
<NumberInput source="pending" disabled />
<NumberInput source="completed" disabled />
<NumberInput
source="uses_allowed"
validate={validateUsesAllowed}
step={1}
/>
<DateTimeInput
source="expiry_time"
parse={dateParser}
format={dateFormatter}
/>
</SimpleForm>
</Edit>
);
};

View File

@ -1,15 +1,8 @@
import React, { Fragment, useState } from "react"; import React, { Fragment, useState } from "react";
import { import { Button, useDelete, useNotify, Confirm, useRecordContext, useRefresh } from "react-admin";
Button,
useMutation,
useNotify,
Confirm,
useRecordContext,
useRefresh,
} from "react-admin";
import ActionDelete from "@material-ui/icons/Delete"; import ActionDelete from "@material-ui/icons/Delete";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import { fade } from "@material-ui/core/styles/colorManipulator"; import { alpha } from "@material-ui/core/styles/colorManipulator";
import classnames from "classnames"; import classnames from "classnames";
const useStyles = makeStyles( const useStyles = makeStyles(
@ -17,7 +10,7 @@ const useStyles = makeStyles(
deleteButton: { deleteButton: {
color: theme.palette.error.main, color: theme.palette.error.main,
"&:hover": { "&:hover": {
backgroundColor: fade(theme.palette.error.main, 0.12), backgroundColor: alpha(theme.palette.error.main, 0.12),
// Reset on mouse devices // Reset on mouse devices
"@media (hover: none)": { "@media (hover: none)": {
backgroundColor: "transparent", backgroundColor: "transparent",
@ -35,7 +28,7 @@ export const DeviceRemoveButton = props => {
const refresh = useRefresh(); const refresh = useRefresh();
const notify = useNotify(); const notify = useNotify();
const [removeDevice, { loading }] = useMutation(); const [removeDevice, { isLoading }] = useDelete("devices");
if (!record) return null; if (!record) return null;
@ -44,21 +37,15 @@ export const DeviceRemoveButton = props => {
const handleConfirm = () => { const handleConfirm = () => {
removeDevice( removeDevice(
{ { payload: { id: record.id, user_id: record.user_id } },
type: "delete",
resource: "devices",
payload: {
id: record.id,
user_id: record.user_id,
},
},
{ {
onSuccess: () => { onSuccess: () => {
notify("resources.devices.action.erase.success"); notify("resources.devices.action.erase.success");
refresh(); refresh();
}, },
onFailure: () => onFailure: () => {
notify("resources.devices.action.erase.failure", "error"), notify("resources.devices.action.erase.failure", { type: "error" });
},
} }
); );
setOpen(false); setOpen(false);
@ -75,7 +62,7 @@ export const DeviceRemoveButton = props => {
</Button> </Button>
<Confirm <Confirm
isOpen={open} isOpen={open}
loading={loading} loading={isLoading}
onConfirm={handleConfirm} onConfirm={handleConfirm}
onClose={handleDialogClose} onClose={handleDialogClose}
title="resources.devices.action.erase.title" title="resources.devices.action.erase.title"

View File

@ -1,6 +1,6 @@
import React, { Fragment, useState } from "react"; import React, { Fragment, useState } from "react";
import classnames from "classnames"; import classnames from "classnames";
import { fade } from "@material-ui/core/styles/colorManipulator"; import { alpha } from "@material-ui/core/styles/colorManipulator";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import { Tooltip } from "@material-ui/core"; import { Tooltip } from "@material-ui/core";
import { import {
@ -34,7 +34,7 @@ const useStyles = makeStyles(
deleteButton: { deleteButton: {
color: theme.palette.error.main, color: theme.palette.error.main,
"&:hover": { "&:hover": {
backgroundColor: fade(theme.palette.error.main, 0.12), backgroundColor: alpha(theme.palette.error.main, 0.12),
// Reset on mouse devices // Reset on mouse devices
"@media (hover: none)": { "@media (hover: none)": {
backgroundColor: "transparent", backgroundColor: "transparent",

View File

@ -41,6 +41,15 @@ import {
RoomDirectorySaveButton, RoomDirectorySaveButton,
} from "./RoomDirectory"; } from "./RoomDirectory";
const date_format = {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
};
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles(theme => ({
helper_forward_extremities: { helper_forward_extremities: {
fontFamily: "Roboto, Helvetica, Arial, sans-serif", fontFamily: "Roboto, Helvetica, Arial, sans-serif",
@ -150,7 +159,11 @@ export const RoomShow = props => {
/> />
</Tab> </Tab>
<Tab label="synapseadmin.rooms.tabs.members" icon={<UserIcon />}> <Tab
label="synapseadmin.rooms.tabs.members"
icon={<UserIcon />}
path="members"
>
<ReferenceManyField <ReferenceManyField
reference="room_members" reference="room_members"
target="room_id" target="room_id"
@ -248,14 +261,7 @@ export const RoomShow = props => {
<DateField <DateField
source="origin_server_ts" source="origin_server_ts"
showTime showTime
options={{ options={date_format}
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}}
sortable={false} sortable={false}
/> />
<TextField source="content" sortable={false} /> <TextField source="content" sortable={false} />
@ -288,14 +294,7 @@ export const RoomShow = props => {
<DateField <DateField
source="received_ts" source="received_ts"
showTime showTime
options={{ options={date_format}
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}}
sortable={false} sortable={false}
/> />
<NumberField source="depth" sortable={false} /> <NumberField source="depth" sortable={false} />

View File

@ -72,6 +72,25 @@ const useStyles = makeStyles({
}, },
}); });
const choices_medium = [
{ id: "email", name: "resources.users.email" },
{ id: "msisdn", name: "resources.users.msisdn" },
];
const choices_type = [
{ id: "bot", name: "bot" },
{ id: "support", name: "support" },
];
const date_format = {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
};
const UserListActions = ({ const UserListActions = ({
currentSort, currentSort,
className, className,
@ -181,14 +200,7 @@ export const UserList = props => {
source="creation_ts" source="creation_ts"
label="resources.users.fields.creation_ts_ms" label="resources.users.fields.creation_ts_ms"
showTime showTime
options={{ options={date_format}
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}}
/> />
</Datagrid> </Datagrid>
</List> </List>
@ -248,20 +260,31 @@ export function generateRandomUser() {
}; };
} }
const UserEditToolbar = props => { const UserEditToolbar = props => (
<Toolbar {...props}>
<SaveButton submitOnEnter={true} disabled={props.pristine} />
</Toolbar>
);
const UserEditActions = ({ data }) => {
const translate = useTranslate(); const translate = useTranslate();
var userStatus = "";
if (data) {
userStatus = data.deactivated;
}
return ( return (
<Toolbar {...props}> <TopToolbar>
<SaveButton submitOnEnter={true} disabled={props.pristine} /> {!userStatus && <ServerNoticeButton record={data} />}
<DeleteButton <DeleteButton
record={data}
label="resources.users.action.erase" label="resources.users.action.erase"
confirmTitle={translate("resources.users.helper.erase", { confirmTitle={translate("resources.users.helper.erase", {
smart_count: 1, smart_count: 1,
})} })}
mutationMode="pessimistic" mutationMode="pessimistic"
/> />
<ServerNoticeButton /> </TopToolbar>
</Toolbar>
); );
}; };
@ -275,22 +298,26 @@ export const UserCreate = props => (
autoComplete="new-password" autoComplete="new-password"
validate={maxLength(512)} validate={maxLength(512)}
/> />
<SelectInput
source="user_type"
choices={choices_type}
translateChoice={false}
allowEmpty={true}
resettable
/>
<BooleanInput source="admin" /> <BooleanInput source="admin" />
<ArrayInput source="threepids"> <ArrayInput source="threepids">
<SimpleFormIterator> <SimpleFormIterator disableReordering>
<SelectInput <SelectInput
source="medium" source="medium"
choices={[ choices={choices_medium}
{ id: "email", name: "resources.users.email" },
{ id: "msisdn", name: "resources.users.msisdn" },
]}
validate={required()} validate={required()}
/> />
<TextInput source="address" validate={validateAddress} /> <TextInput source="address" validate={validateAddress} />
</SimpleFormIterator> </SimpleFormIterator>
</ArrayInput> </ArrayInput>
<ArrayInput source="external_ids" label="synapseadmin.users.tabs.sso"> <ArrayInput source="external_ids" label="synapseadmin.users.tabs.sso">
<SimpleFormIterator> <SimpleFormIterator disableReordering>
<TextInput source="auth_provider" validate={required()} /> <TextInput source="auth_provider" validate={required()} />
<TextInput <TextInput
source="external_id" source="external_id"
@ -315,11 +342,12 @@ const UserTitle = props => {
</span> </span>
); );
}; };
export const UserEdit = props => { export const UserEdit = props => {
const classes = useStyles(); const classes = useStyles();
const translate = useTranslate(); const translate = useTranslate();
return ( return (
<Edit {...props} title={<UserTitle />}> <Edit {...props} title={<UserTitle />} actions={<UserEditActions />}>
<TabbedForm toolbar={<UserEditToolbar />}> <TabbedForm toolbar={<UserEditToolbar />}>
<FormTab <FormTab
label={translate("resources.users.name", { smart_count: 1 })} label={translate("resources.users.name", { smart_count: 1 })}
@ -332,24 +360,24 @@ export const UserEdit = props => {
/> />
<TextInput source="id" disabled /> <TextInput source="id" disabled />
<TextInput source="displayname" /> <TextInput source="displayname" />
<PasswordInput source="password" autoComplete="new-password" /> <PasswordInput
source="password"
autoComplete="new-password"
helperText="resources.users.helper.password"
/>
<SelectInput
source="user_type"
choices={choices_type}
translateChoice={false}
allowEmpty={true}
resettable
/>
<BooleanInput source="admin" /> <BooleanInput source="admin" />
<BooleanInput <BooleanInput
source="deactivated" source="deactivated"
helperText="resources.users.helper.deactivate" helperText="resources.users.helper.deactivate"
/> />
<DateField <DateField source="creation_ts_ms" showTime options={date_format} />
source="creation_ts_ms"
showTime
options={{
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}}
/>
<TextField source="consent_version" /> <TextField source="consent_version" />
</FormTab> </FormTab>
@ -359,14 +387,8 @@ export const UserEdit = props => {
path="threepid" path="threepid"
> >
<ArrayInput source="threepids"> <ArrayInput source="threepids">
<SimpleFormIterator> <SimpleFormIterator disableReordering>
<SelectInput <SelectInput source="medium" choices={choices_medium} />
source="medium"
choices={[
{ id: "email", name: "resources.users.email" },
{ id: "msisdn", name: "resources.users.msisdn" },
]}
/>
<TextInput source="address" /> <TextInput source="address" />
</SimpleFormIterator> </SimpleFormIterator>
</ArrayInput> </ArrayInput>
@ -378,7 +400,7 @@ export const UserEdit = props => {
path="sso" path="sso"
> >
<ArrayInput source="external_ids" label={false}> <ArrayInput source="external_ids" label={false}>
<SimpleFormIterator> <SimpleFormIterator disableReordering>
<TextInput source="auth_provider" validate={required()} /> <TextInput source="auth_provider" validate={required()} />
<TextInput <TextInput
source="external_id" source="external_id"
@ -406,14 +428,7 @@ export const UserEdit = props => {
<DateField <DateField
source="last_seen_ts" source="last_seen_ts"
showTime showTime
options={{ options={date_format}
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}}
sortable={false} sortable={false}
/> />
<DeviceRemoveButton /> <DeviceRemoveButton />
@ -441,14 +456,7 @@ export const UserEdit = props => {
<DateField <DateField
source="last_seen" source="last_seen"
showTime showTime
options={{ options={date_format}
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}}
sortable={false} sortable={false}
/> />
<TextField <TextField
@ -475,29 +483,11 @@ export const UserEdit = props => {
sort={{ field: "created_ts", order: "DESC" }} sort={{ field: "created_ts", order: "DESC" }}
> >
<Datagrid style={{ width: "100%" }}> <Datagrid style={{ width: "100%" }}>
<DateField <DateField source="created_ts" showTime options={date_format} />
source="created_ts"
showTime
options={{
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}}
/>
<DateField <DateField
source="last_access_ts" source="last_access_ts"
showTime showTime
options={{ options={date_format}
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}}
/> />
<TextField source="media_id" /> <TextField source="media_id" />
<NumberField source="media_length" /> <NumberField source="media_length" />

View File

@ -10,6 +10,7 @@ const de = {
username_error: "Bitte vollständigen Nutzernamen angeben: '@user:domain'", username_error: "Bitte vollständigen Nutzernamen angeben: '@user:domain'",
protocol_error: "Die URL muss mit 'http://' oder 'https://' beginnen", protocol_error: "Die URL muss mit 'http://' oder 'https://' beginnen",
url_error: "Keine gültige Matrix Server URL", url_error: "Keine gültige Matrix Server URL",
sso_sign_in: "Anmeldung mit SSO",
}, },
users: { users: {
invalid_user_id: "Lokaler Anteil der Matrix Benutzer-ID ohne Homeserver.", invalid_user_id: "Lokaler Anteil der Matrix Benutzer-ID ohne Homeserver.",
@ -96,7 +97,6 @@ const de = {
}, },
resources: { resources: {
users: { users: {
backtolist: "Zurück zur Liste",
name: "Benutzer", name: "Benutzer",
email: "E-Mail", email: "E-Mail",
msisdn: "Telefon", msisdn: "Telefon",
@ -121,8 +121,11 @@ const de = {
creation_ts_ms: "Zeitpunkt der Erstellung", creation_ts_ms: "Zeitpunkt der Erstellung",
consent_version: "Zugestimmte Geschäftsbedingungen", consent_version: "Zugestimmte Geschäftsbedingungen",
auth_provider: "Provider", auth_provider: "Provider",
user_type: "Benutzertyp",
}, },
helper: { helper: {
password:
"Durch die Änderung des Passworts wird der Benutzer von allen Sitzungen abgemeldet.",
deactivate: deactivate:
"Sie müssen ein Passwort angeben, um ein Konto wieder zu aktivieren.", "Sie müssen ein Passwort angeben, um ein Konto wieder zu aktivieren.",
erase: "DSGVO konformes Löschen der Benutzerdaten", erase: "DSGVO konformes Löschen der Benutzerdaten",
@ -352,6 +355,19 @@ const de = {
send_failure: "Beim Entfernen ist ein Fehler aufgetreten.", send_failure: "Beim Entfernen ist ein Fehler aufgetreten.",
}, },
}, },
registration_tokens: {
name: "Registrierungstoken",
fields: {
token: "Token",
valid: "Gültige Token",
uses_allowed: "Anzahl",
pending: "Ausstehend",
completed: "Abgeschlossen",
expiry_time: "Ablaufzeit",
length: "Länge",
},
helper: { length: "Länge des Tokens, wenn kein Token vorgegeben wird." },
},
}, },
ra: { ra: {
...germanMessages.ra, ...germanMessages.ra,
@ -372,7 +388,7 @@ const de = {
}, },
}, },
notification: { notification: {
...germanMessages.ra.notifiaction, ...germanMessages.ra.notification,
logged_out: "Abgemeldet", logged_out: "Abgemeldet",
}, },
page: { page: {

View File

@ -10,6 +10,7 @@ const en = {
username_error: "Please enter fully qualified user ID: '@user:domain'", username_error: "Please enter fully qualified user ID: '@user:domain'",
protocol_error: "URL has to start with 'http://' or 'https://'", protocol_error: "URL has to start with 'http://' or 'https://'",
url_error: "Not a valid Matrix server URL", url_error: "Not a valid Matrix server URL",
sso_sign_in: "Sign in with SSO",
}, },
users: { users: {
invalid_user_id: "Localpart of a Matrix user-id without homeserver.", invalid_user_id: "Localpart of a Matrix user-id without homeserver.",
@ -95,7 +96,6 @@ const en = {
}, },
resources: { resources: {
users: { users: {
backtolist: "Back to list",
name: "User |||| Users", name: "User |||| Users",
email: "Email", email: "Email",
msisdn: "Phone", msisdn: "Phone",
@ -120,8 +120,10 @@ const en = {
creation_ts_ms: "Creation timestamp", creation_ts_ms: "Creation timestamp",
consent_version: "Consent version", consent_version: "Consent version",
auth_provider: "Provider", auth_provider: "Provider",
user_type: "User type",
}, },
helper: { helper: {
password: "Changing password will log user out of all sessions.",
deactivate: "You must provide a password to re-activate an account.", deactivate: "You must provide a password to re-activate an account.",
erase: "Mark the user as GDPR-erased", erase: "Mark the user as GDPR-erased",
}, },
@ -174,10 +176,12 @@ const en = {
}, },
unencrypted: "Unencrypted", unencrypted: "Unencrypted",
}, },
erase: { action: {
title: "Delete room", erase: {
content: title: "Delete room",
"Are you sure you want to delete the room? This cannot be undone. All messages and shared media in the room will be deleted from the server!", content:
"Are you sure you want to delete the room? This cannot be undone. All messages and shared media in the room will be deleted from the server!",
},
}, },
}, },
reports: { reports: {
@ -349,5 +353,18 @@ const en = {
}, },
}, },
}, },
registration_tokens: {
name: "Registration tokens",
fields: {
token: "Token",
valid: "Valid token",
uses_allowed: "Uses allowed",
pending: "Pending",
completed: "Completed",
expiry_time: "Expiry time",
length: "Length",
},
helper: { length: "Length of the token if no token is given." },
},
}; };
export default en; export default en;

View File

@ -10,10 +10,12 @@ const zh = {
username_error: "请输入完整有效的用户 ID: '@user:domain'", username_error: "请输入完整有效的用户 ID: '@user:domain'",
protocol_error: "URL 需要以'http://'或'https://'作为起始", protocol_error: "URL 需要以'http://'或'https://'作为起始",
url_error: "不是一个有效的 Matrix 服务器地址", url_error: "不是一个有效的 Matrix 服务器地址",
sso_sign_in: "使用 SSO 登录",
}, },
users: { users: {
invalid_user_id: invalid_user_id:
"必须要是一个有效的 Matrix 用户 ID ,例如 @user_id:homeserver", "必须要是一个有效的 Matrix 用户 ID ,例如 @user_id:homeserver",
tabs: { sso: "SSO" },
}, },
rooms: { rooms: {
tabs: { tabs: {
@ -98,7 +100,6 @@ const zh = {
}, },
resources: { resources: {
users: { users: {
backtolist: "回到列表",
name: "用户", name: "用户",
email: "邮箱", email: "邮箱",
msisdn: "电话", msisdn: "电话",

View File

@ -2,20 +2,31 @@ 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, loginToken }) => {
// force homeserver for protection in case the form is manipulated // force homeserver for protection in case the form is manipulated
base_url = process.env.REACT_APP_SERVER || base_url; base_url = process.env.REACT_APP_SERVER || base_url;
console.log("login "); console.log("login ");
const options = { const options = {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify(
type: "m.login.password", Object.assign(
user: username, {
password: password, device_id: localStorage.getItem("device_id"),
device_id: localStorage.getItem("device_id"), initial_device_display_name: "Synapse Admin",
initial_device_display_name: "Synapse Admin", },
}), loginToken
? {
type: "m.login.token",
token: loginToken,
}
: {
type: "m.login.password",
user: username,
password: password,
}
)
),
}; };
// 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

View File

@ -41,14 +41,16 @@ const resourceMap = {
data: "users", data: "users",
total: json => json.total, total: json => json.total,
create: data => ({ create: data => ({
endpoint: `/_synapse/admin/v2/users/@${data.id}:${localStorage.getItem( endpoint: `/_synapse/admin/v2/users/@${encodeURIComponent(
"home_server" data.id
)}`, )}:${localStorage.getItem("home_server")}`,
body: data, body: data,
method: "PUT", method: "PUT",
}), }),
delete: params => ({ delete: params => ({
endpoint: `/_synapse/admin/v1/deactivate/${params.id}`, endpoint: `/_synapse/admin/v1/deactivate/${encodeURIComponent(
params.id
)}`,
body: { erase: true }, body: { erase: true },
method: "POST", method: "POST",
}), }),
@ -69,7 +71,7 @@ const resourceMap = {
return json.total_rooms; return json.total_rooms;
}, },
delete: params => ({ delete: params => ({
endpoint: `/_synapse/admin/v1/rooms/${params.id}`, endpoint: `/_synapse/admin/v2/rooms/${params.id}`,
body: { block: false }, body: { block: false },
}), }),
}, },
@ -92,10 +94,12 @@ const resourceMap = {
return json.total; return json.total;
}, },
reference: id => ({ reference: id => ({
endpoint: `/_synapse/admin/v2/users/${id}/devices`, endpoint: `/_synapse/admin/v2/users/${encodeURIComponent(id)}/devices`,
}), }),
delete: params => ({ delete: params => ({
endpoint: `/_synapse/admin/v2/users/${params.user_id}/devices/${params.id}`, endpoint: `/_synapse/admin/v2/users/${encodeURIComponent(
params.user_id
)}/devices/${params.id}`,
}), }),
}, },
connections: { connections: {
@ -137,7 +141,7 @@ const resourceMap = {
id: p.pushkey, id: p.pushkey,
}), }),
reference: id => ({ reference: id => ({
endpoint: `/_synapse/admin/v1/users/${id}/pushers`, endpoint: `/_synapse/admin/v1/users/${encodeURIComponent(id)}/pushers`,
}), }),
data: "pushers", data: "pushers",
total: json => { total: json => {
@ -149,7 +153,9 @@ const resourceMap = {
id: jr, id: jr,
}), }),
reference: id => ({ reference: id => ({
endpoint: `/_synapse/admin/v1/users/${id}/joined_rooms`, endpoint: `/_synapse/admin/v1/users/${encodeURIComponent(
id
)}/joined_rooms`,
}), }),
data: "joined_rooms", data: "joined_rooms",
total: json => { total: json => {
@ -162,7 +168,7 @@ const resourceMap = {
id: um.media_id, id: um.media_id,
}), }),
reference: id => ({ reference: id => ({
endpoint: `/_synapse/admin/v1/users/${id}/media`, endpoint: `/_synapse/admin/v1/users/${encodeURIComponent(id)}/media`,
}), }),
data: "media", data: "media",
total: json => { total: json => {
@ -275,11 +281,31 @@ const resourceMap = {
method: "PUT", method: "PUT",
}), }),
}, },
registration_tokens: {
path: "/_synapse/admin/v1/registration_tokens",
map: rt => ({
...rt,
id: rt.token,
}),
data: "registration_tokens",
total: json => {
return json.registration_tokens.length;
},
create: params => ({
endpoint: "/_synapse/admin/v1/registration_tokens/new",
body: params,
method: "POST",
}),
delete: params => ({
endpoint: `/_synapse/admin/v1/registration_tokens/${params.id}`,
}),
},
}; };
function filterNullValues(key, value) { function filterNullValues(key, value) {
// Filtering out null properties // Filtering out null properties
if (value === null) { // to reset user_type from user, it must be null
if (value === null && key !== "user_type") {
return undefined; return undefined;
} }
return value; return value;
@ -296,7 +322,8 @@ 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 } = params.filter; const { user_id, name, guests, deactivated, search_term, 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;
@ -308,6 +335,7 @@ const dataProvider = {
name: name, name: name,
guests: guests, guests: guests,
deactivated: deactivated, deactivated: deactivated,
valid: valid,
order_by: field, order_by: field,
dir: getSearchOrder(order), dir: getSearchOrder(order),
}; };
@ -333,9 +361,11 @@ const dataProvider = {
const res = resourceMap[resource]; const res = resourceMap[resource];
const endpoint_url = homeserver + res.path; const endpoint_url = homeserver + res.path;
return jsonClient(`${endpoint_url}/${params.id}`).then(({ json }) => ({ return jsonClient(`${endpoint_url}/${encodeURIComponent(params.id)}`).then(
data: res.map(json), ({ json }) => ({
})); data: res.map(json),
})
);
}, },
getMany: (resource, params) => { getMany: (resource, params) => {
@ -347,7 +377,9 @@ const dataProvider = {
const endpoint_url = homeserver + res.path; const endpoint_url = homeserver + res.path;
return Promise.all( return Promise.all(
params.ids.map(id => jsonClient(`${endpoint_url}/${id}`)) params.ids.map(id =>
jsonClient(`${endpoint_url}/${encodeURIComponent(id)}`)
)
).then(responses => ({ ).then(responses => ({
data: responses.map(({ json }) => res.map(json)), data: responses.map(({ json }) => res.map(json)),
total: responses.length, total: responses.length,
@ -388,7 +420,7 @@ const dataProvider = {
const res = resourceMap[resource]; const res = resourceMap[resource];
const endpoint_url = homeserver + res.path; const endpoint_url = homeserver + res.path;
return jsonClient(`${endpoint_url}/${params.data.id}`, { return jsonClient(`${endpoint_url}/${encodeURIComponent(params.data.id)}`, {
method: "PUT", method: "PUT",
body: JSON.stringify(params.data, filterNullValues), body: JSON.stringify(params.data, filterNullValues),
}).then(({ json }) => ({ }).then(({ json }) => ({
@ -405,10 +437,13 @@ const dataProvider = {
const endpoint_url = homeserver + res.path; const endpoint_url = homeserver + res.path;
return Promise.all( return Promise.all(
params.ids.map(id => jsonClient(`${endpoint_url}/${id}`), { params.ids.map(
method: "PUT", id => jsonClient(`${endpoint_url}/${encodeURIComponent(id)}`),
body: JSON.stringify(params.data, filterNullValues), {
}) method: "PUT",
body: JSON.stringify(params.data, filterNullValues),
}
)
).then(responses => ({ ).then(responses => ({
data: responses.map(({ json }) => json), data: responses.map(({ json }) => json),
})); }));

10155
yarn.lock

File diff suppressed because it is too large Load Diff