Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0a616a7a32 | |||
| 774beb07ec | |||
| e88ee20a19 | |||
| 4861835d02 | |||
| f33ad41242 | |||
| 36fbd70a42 | |||
| e6a1be74de | |||
| ca48495bbf |
@@ -1,5 +0,0 @@
|
|||||||
# 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,20 +0,0 @@
|
|||||||
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"
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
name: build-test
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: ["master"]
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
check:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Setup node
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: "18"
|
|
||||||
- name: Install dependencies
|
|
||||||
run: yarn --frozen-lockfile
|
|
||||||
- name: Run tests
|
|
||||||
run: yarn test
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
name: Create docker image(s) and push to docker hub
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
# Sequence of patterns matched against refs/heads
|
|
||||||
# prettier-ignore
|
|
||||||
branches:
|
|
||||||
# Push events on master branch
|
|
||||||
- master
|
|
||||||
# Sequence of patterns matched against refs/tags
|
|
||||||
tags:
|
|
||||||
- '[0-9]+\.[0-9]+\.[0-9]+' # Push events to 0.X.X tag
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
docker:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
- name: Calculate docker image tag
|
|
||||||
id: set-tag
|
|
||||||
run: |
|
|
||||||
case "${GITHUB_REF}" in
|
|
||||||
refs/heads/master|refs/heads/main)
|
|
||||||
tag=latest
|
|
||||||
;;
|
|
||||||
refs/tags/*)
|
|
||||||
tag=${GITHUB_REF#refs/tags/}
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
tag=${GITHUB_SHA}
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
echo "::set-output name=tag::$tag"
|
|
||||||
- name: Build and Push Tag
|
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
tags: "awesometechnologies/synapse-admin:${{ steps.set-tag.outputs.tag }}"
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
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@v4
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: "18"
|
|
||||||
- name: Install and Build 🔧
|
|
||||||
run: |
|
|
||||||
yarn install
|
|
||||||
yarn build
|
|
||||||
|
|
||||||
- name: Deploy 🚀
|
|
||||||
uses: JamesIves/github-pages-deploy-action@v4.4.3
|
|
||||||
with:
|
|
||||||
branch: gh-pages
|
|
||||||
folder: build
|
|
||||||
@@ -8,15 +8,12 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
packages: write
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "14"
|
||||||
- run: yarn install
|
- run: yarn install
|
||||||
- run: yarn build
|
- run: yarn build
|
||||||
- run: |
|
- run: |
|
||||||
@@ -24,7 +21,8 @@ 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@de2c0eb89ae2a093876385947365aca7b0e5f844
|
rm -r synapse-admin-$version
|
||||||
|
- uses: softprops/action-gh-release@b7e450da2a4b4cb4bfbae528f788167786cfcedf
|
||||||
with:
|
with:
|
||||||
files: dist/*.tar.gz
|
files: dist/*.tar.gz
|
||||||
env:
|
env:
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
name: Test docker image creation
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
# Sequence of patterns matched against refs/heads
|
|
||||||
# prettier-ignore
|
|
||||||
branches:
|
|
||||||
# Push events on branch fix_docker_cd
|
|
||||||
- fix_docker_cd
|
|
||||||
# Sequence of patterns matched against refs/tags
|
|
||||||
tags:
|
|
||||||
- '[0-9]+\.[0-9]+\.[0-9]+' # Push events to 0.X.X tag
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
docker:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
- name: Calculate docker image tag
|
|
||||||
id: set-tag
|
|
||||||
run: |
|
|
||||||
case "${GITHUB_REF}" in
|
|
||||||
refs/heads/master|refs/heads/main)
|
|
||||||
tag=latest
|
|
||||||
;;
|
|
||||||
refs/tags/*)
|
|
||||||
tag=${GITHUB_REF#refs/tags/}
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
tag=${GITHUB_SHA}
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
echo "::set-output name=tag::$tag"
|
|
||||||
- name: Build and Push Tag
|
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: false
|
|
||||||
tags: "awesometechnologies/synapse-admin:${{ steps.set-tag.outputs.tag }}"
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
+2
-2
@@ -6,6 +6,6 @@
|
|||||||
"singleQuote": false,
|
"singleQuote": false,
|
||||||
"trailingComma": "es5",
|
"trailingComma": "es5",
|
||||||
"bracketSpacing": true,
|
"bracketSpacing": true,
|
||||||
"bracketSameLine": false,
|
"jsxBracketSameLine": false,
|
||||||
"arrowParens": "avoid"
|
"arrowParens": "avoid",
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -1,6 +1,5 @@
|
|||||||
dist: focal
|
|
||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- 18
|
- 13
|
||||||
|
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
|||||||
+3
-5
@@ -1,13 +1,11 @@
|
|||||||
# Builder
|
# Builder
|
||||||
FROM node:lts as builder
|
FROM node:current as builder
|
||||||
|
|
||||||
ARG REACT_APP_SERVER
|
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
COPY . /src
|
COPY . /src
|
||||||
RUN yarn --network-timeout=300000 install
|
RUN yarn --network-timeout=100000 install
|
||||||
RUN REACT_APP_SERVER=$REACT_APP_SERVER yarn build
|
RUN yarn build
|
||||||
|
|
||||||
|
|
||||||
# App
|
# App
|
||||||
|
|||||||
@@ -1,62 +1,31 @@
|
|||||||
[](https://github.com/Awesome-Technologies/synapse-admin/blob/master/LICENSE)
|
[](https://travis-ci.org/Awesome-Technologies/synapse-admin)
|
||||||
[](https://app.travis-ci.com/github/Awesome-Technologies/synapse-admin)
|
|
||||||
[](https://github.com/Awesome-Technologies/synapse-admin/actions/workflows/build-test.yml)
|
|
||||||
[](https://awesome-technologies.github.io/synapse-admin/)
|
|
||||||
[](https://hub.docker.com/r/awesometechnologies/synapse-admin)
|
|
||||||
[](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/).
|
||||||
|
|
||||||
## Usage
|
It needs at least Synapse v1.23.0 for all functions to work as expected!
|
||||||
|
|
||||||
### Supported Synapse
|
|
||||||
|
|
||||||
It needs at least [Synapse](https://github.com/matrix-org/synapse) v1.52.0 for all functions to work as expected!
|
|
||||||
|
|
||||||
You get your server version with the request `/_synapse/admin/v1/server_version`.
|
You get your server version with the request `/_synapse/admin/v1/server_version`.
|
||||||
See also [Synapse version API](https://matrix-org.github.io/synapse/develop/admin_api/version_api.html).
|
See also [Synapse version API](https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/version_api.rst).
|
||||||
|
|
||||||
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`
|
||||||
- `/_synapse/admin`
|
- `/_synapse/admin`
|
||||||
|
|
||||||
See also [Synapse administration endpoints](https://matrix-org.github.io/synapse/develop/reverse_proxy.html#synapse-administration-endpoints)
|
See also [Synapse administration endpoints](https://github.com/matrix-org/synapse/blob/develop/docs/reverse_proxy.md#synapse-administration-endpoints)
|
||||||
|
|
||||||
### Use without install
|
## Step-By-Step install:
|
||||||
|
|
||||||
You can use the current version of Synapse Admin without own installation direct
|
You have two options:
|
||||||
via [GitHub Pages](https://awesome-technologies.github.io/synapse-admin/).
|
|
||||||
|
|
||||||
**Note:**
|
1. Download the source code from github and run using nodejs
|
||||||
If you want to use the deployment, you have to make sure that the admin endpoints (`/_synapse/admin`) are accessible for your browser.
|
2. Run the Docker container
|
||||||
**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
|
Steps for 1):
|
||||||
|
|
||||||
You have three options:
|
|
||||||
|
|
||||||
1. [Download the tarball and serve with any webserver](#steps-for-1)
|
|
||||||
2. [Download the source code from github and run using nodejs](#steps-for-2)
|
|
||||||
3. [Run the Docker container](#steps-for-3)
|
|
||||||
|
|
||||||
#### 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
|
- 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`
|
||||||
@@ -64,14 +33,9 @@ You have three options:
|
|||||||
- 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.
|
Steps for 2):
|
||||||
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 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`
|
|
||||||
|
|
||||||
> 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.
|
> 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.
|
||||||
|
|
||||||
@@ -86,9 +50,6 @@ or by editing it in the [.env](.env) file. See also the
|
|||||||
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
|
||||||
|
|||||||
+1
-6
@@ -12,15 +12,10 @@ 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
|
||||||
|
|||||||
+18
-22
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "synapse-admin",
|
"name": "synapse-admin",
|
||||||
"version": "0.9.0",
|
"version": "0.7.0",
|
||||||
"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,30 +10,26 @@
|
|||||||
"url": "https://github.com/Awesome-Technologies/synapse-admin"
|
"url": "https://github.com/Awesome-Technologies/synapse-admin"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^5.1.1",
|
||||||
"@testing-library/react": "^12.1.5",
|
"@testing-library/react": "^10.0.2",
|
||||||
"@testing-library/user-event": "^14.4.3",
|
"@testing-library/user-event": "^12.0.11",
|
||||||
"eslint": "^8.55.0",
|
"enzyme": "^3.11.0",
|
||||||
"eslint-config-prettier": "^9.0.0",
|
"enzyme-adapter-react-16": "^1.15.2",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
"eslint": "^6.8.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-config-prettier": "^6.10.1",
|
||||||
|
"eslint-plugin-prettier": "^3.1.2",
|
||||||
"jest-fetch-mock": "^3.0.3",
|
"jest-fetch-mock": "^3.0.3",
|
||||||
"prettier": "^2.2.0"
|
"prettier": "^2.0.0",
|
||||||
|
"ra-test": "^3.14.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mui/icons-material": "^5.14.19",
|
"papaparse": "^5.2.0",
|
||||||
"@mui/material": "^5.14.8",
|
"prop-types": "^15.7.2",
|
||||||
"@mui/styles": "5.14.10",
|
"ra-language-german": "^2.1.2",
|
||||||
"papaparse": "^5.4.1",
|
|
||||||
"prop-types": "^15.8.1",
|
|
||||||
"ra-language-chinese": "^2.0.10",
|
|
||||||
"ra-language-french": "^4.16.2",
|
|
||||||
"ra-language-german": "^3.13.4",
|
|
||||||
"ra-language-italian": "^3.13.1",
|
|
||||||
"react": "^17.0.0",
|
"react": "^17.0.0",
|
||||||
"react-admin": "^4.16.9",
|
"react-admin": "^3.14.0",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^16.14.0",
|
||||||
"react-scripts": "^5.0.1"
|
"react-scripts": "^3.4.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "REACT_APP_VERSION=$(git describe --tags) react-scripts start",
|
"start": "REACT_APP_VERSION=$(git describe --tags) react-scripts start",
|
||||||
@@ -41,7 +37,7 @@
|
|||||||
"fix:other": "yarn prettier --write",
|
"fix:other": "yarn prettier --write",
|
||||||
"fix:code": "yarn test:lint --fix",
|
"fix:code": "yarn test:lint --fix",
|
||||||
"fix": "yarn fix:code && yarn fix:other",
|
"fix": "yarn fix:code && yarn fix:other",
|
||||||
"prettier": "prettier --ignore-path .gitignore \"**/*.{js,jsx,json,md,scss,yaml,yml}\"",
|
"prettier": "prettier \"**/*.{js,jsx,json,md,scss,yaml,yml}\"",
|
||||||
"test:code": "react-scripts test",
|
"test:code": "react-scripts test",
|
||||||
"test:lint": "eslint --ignore-path .gitignore --ext .js,.jsx .",
|
"test:lint": "eslint --ignore-path .gitignore --ext .js,.jsx .",
|
||||||
"test:style": "yarn prettier --list-different",
|
"test:style": "yarn prettier --list-different",
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
# https://www.robotstxt.org/robotstxt.html
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
User-agent: *
|
User-agent: *
|
||||||
Disallow: /
|
|
||||||
|
|||||||
+8
-50
@@ -1,10 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
import { Admin, Resource, resolveBrowserLocale } from "react-admin";
|
||||||
Admin,
|
|
||||||
CustomRoutes,
|
|
||||||
Resource,
|
|
||||||
resolveBrowserLocale,
|
|
||||||
} from "react-admin";
|
|
||||||
import polyglotI18nProvider from "ra-i18n-polyglot";
|
import polyglotI18nProvider from "ra-i18n-polyglot";
|
||||||
import authProvider from "./synapse/authProvider";
|
import authProvider from "./synapse/authProvider";
|
||||||
import dataProvider from "./synapse/dataProvider";
|
import dataProvider from "./synapse/dataProvider";
|
||||||
@@ -12,36 +7,20 @@ import { UserList, UserCreate, UserEdit } from "./components/users";
|
|||||||
import { RoomList, RoomShow } from "./components/rooms";
|
import { RoomList, RoomShow } from "./components/rooms";
|
||||||
import { ReportList, ReportShow } from "./components/EventReports";
|
import { ReportList, ReportShow } from "./components/EventReports";
|
||||||
import LoginPage from "./components/LoginPage";
|
import LoginPage from "./components/LoginPage";
|
||||||
import ConfirmationNumberIcon from "@mui/icons-material/ConfirmationNumber";
|
import UserIcon from "@material-ui/icons/Group";
|
||||||
import CloudQueueIcon from "@mui/icons-material/CloudQueue";
|
import EqualizerIcon from "@material-ui/icons/Equalizer";
|
||||||
import EqualizerIcon from "@mui/icons-material/Equalizer";
|
|
||||||
import UserIcon from "@mui/icons-material/Group";
|
|
||||||
import { UserMediaStatsList } from "./components/statistics";
|
import { UserMediaStatsList } from "./components/statistics";
|
||||||
import RoomIcon from "@mui/icons-material/ViewList";
|
import RoomIcon from "@material-ui/icons/ViewList";
|
||||||
import ReportIcon from "@mui/icons-material/Warning";
|
import ReportIcon from "@material-ui/icons/Warning";
|
||||||
import FolderSharedIcon from "@mui/icons-material/FolderShared";
|
|
||||||
import { DestinationList, DestinationShow } from "./components/destinations";
|
|
||||||
import { ImportFeature } from "./components/ImportFeature";
|
import { ImportFeature } from "./components/ImportFeature";
|
||||||
import {
|
|
||||||
RegistrationTokenCreate,
|
|
||||||
RegistrationTokenEdit,
|
|
||||||
RegistrationTokenList,
|
|
||||||
} from "./components/RegistrationTokens";
|
|
||||||
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";
|
||||||
import englishMessages from "./i18n/en";
|
import englishMessages from "./i18n/en";
|
||||||
import frenchMessages from "./i18n/fr";
|
|
||||||
import chineseMessages from "./i18n/zh";
|
|
||||||
import italianMessages from "./i18n/it";
|
|
||||||
|
|
||||||
// TODO: Can we use lazy loading together with browser locale?
|
// TODO: Can we use lazy loading together with browser locale?
|
||||||
const messages = {
|
const messages = {
|
||||||
de: germanMessages,
|
de: germanMessages,
|
||||||
en: englishMessages,
|
en: englishMessages,
|
||||||
fr: frenchMessages,
|
|
||||||
it: italianMessages,
|
|
||||||
zh: chineseMessages,
|
|
||||||
};
|
};
|
||||||
const i18nProvider = polyglotI18nProvider(
|
const i18nProvider = polyglotI18nProvider(
|
||||||
locale => (messages[locale] ? messages[locale] : messages.en),
|
locale => (messages[locale] ? messages[locale] : messages.en),
|
||||||
@@ -55,10 +34,10 @@ const App = () => (
|
|||||||
authProvider={authProvider}
|
authProvider={authProvider}
|
||||||
dataProvider={dataProvider}
|
dataProvider={dataProvider}
|
||||||
i18nProvider={i18nProvider}
|
i18nProvider={i18nProvider}
|
||||||
|
customRoutes={[
|
||||||
|
<Route key="userImport" path="/import_users" component={ImportFeature} />,
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<CustomRoutes>
|
|
||||||
<Route path="/import_users" element={<ImportFeature />} />
|
|
||||||
</CustomRoutes>
|
|
||||||
<Resource
|
<Resource
|
||||||
name="users"
|
name="users"
|
||||||
list={UserList}
|
list={UserList}
|
||||||
@@ -78,24 +57,6 @@ const App = () => (
|
|||||||
show={ReportShow}
|
show={ReportShow}
|
||||||
icon={ReportIcon}
|
icon={ReportIcon}
|
||||||
/>
|
/>
|
||||||
<Resource
|
|
||||||
name="room_directory"
|
|
||||||
list={RoomDirectoryList}
|
|
||||||
icon={FolderSharedIcon}
|
|
||||||
/>
|
|
||||||
<Resource
|
|
||||||
name="destinations"
|
|
||||||
list={DestinationList}
|
|
||||||
show={DestinationShow}
|
|
||||||
icon={CloudQueueIcon}
|
|
||||||
/>
|
|
||||||
<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" />
|
||||||
@@ -103,9 +64,6 @@ 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="destination_rooms" />
|
|
||||||
</Admin>
|
</Admin>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
+7
-2
@@ -1,9 +1,14 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { render } from "@testing-library/react";
|
import { TestContext } from "ra-test";
|
||||||
|
import { shallow } from "enzyme";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
|
|
||||||
describe("App", () => {
|
describe("App", () => {
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
render(<App />);
|
shallow(
|
||||||
|
<TestContext>
|
||||||
|
<App />
|
||||||
|
</TestContext>
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import get from "lodash/get";
|
|
||||||
import { Avatar } from "@mui/material";
|
|
||||||
import { useRecordContext } from "react-admin";
|
|
||||||
|
|
||||||
const AvatarField = ({ source, ...rest }) => {
|
|
||||||
const record = useRecordContext(rest);
|
|
||||||
const src = get(record, source)?.toString();
|
|
||||||
return <Avatar src={src} {...rest} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AvatarField;
|
|
||||||
@@ -12,17 +12,8 @@ import {
|
|||||||
TextField,
|
TextField,
|
||||||
useTranslate,
|
useTranslate,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import PageviewIcon from "@mui/icons-material/Pageview";
|
import PageviewIcon from "@material-ui/icons/Pageview";
|
||||||
import ViewListIcon from "@mui/icons-material/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]} />
|
||||||
@@ -42,7 +33,14 @@ export const ReportShow = props => {
|
|||||||
<DateField
|
<DateField
|
||||||
source="received_ts"
|
source="received_ts"
|
||||||
showTime
|
showTime
|
||||||
options={date_format}
|
options={{
|
||||||
|
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">
|
||||||
@@ -70,16 +68,23 @@ 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={date_format}
|
options={{
|
||||||
|
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">
|
||||||
<TextField source="id" />
|
<TextField source="id" />
|
||||||
</ReferenceField>
|
</ReferenceField>
|
||||||
<TextField source="sender" label="Sender (raw user ID)" />
|
|
||||||
<TextField source="event_id" />
|
<TextField source="event_id" />
|
||||||
<TextField source="event_json.origin" />
|
<TextField source="event_json.origin" />
|
||||||
<TextField source="event_json.type" />
|
<TextField source="event_json.type" />
|
||||||
@@ -111,7 +116,14 @@ export const ReportList = ({ ...props }) => {
|
|||||||
<DateField
|
<DateField
|
||||||
source="received_ts"
|
source="received_ts"
|
||||||
showTime
|
showTime
|
||||||
options={date_format}
|
options={{
|
||||||
|
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" />
|
||||||
|
|||||||
@@ -1,22 +1,41 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useDataProvider, useNotify, Title } from "react-admin";
|
import {
|
||||||
|
Button as ReactAdminButton,
|
||||||
|
useDataProvider,
|
||||||
|
useNotify,
|
||||||
|
Title,
|
||||||
|
} from "react-admin";
|
||||||
import { parse as parseCsv, unparse as unparseCsv } from "papaparse";
|
import { parse as parseCsv, unparse as unparseCsv } from "papaparse";
|
||||||
|
import GetAppIcon from "@material-ui/icons/GetApp";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
CardActions,
|
CardActions,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
Checkbox,
|
|
||||||
Container,
|
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
|
Checkbox,
|
||||||
NativeSelect,
|
NativeSelect,
|
||||||
} from "@mui/material";
|
} from "@material-ui/core";
|
||||||
import { useTranslate } from "ra-core";
|
import { useTranslate } from "ra-core";
|
||||||
|
import Container from "@material-ui/core/Container/Container";
|
||||||
import { generateRandomUser } from "./users";
|
import { generateRandomUser } from "./users";
|
||||||
|
|
||||||
const LOGGING = true;
|
const LOGGING = true;
|
||||||
|
|
||||||
|
export const ImportButton = ({ label, variant = "text" }) => {
|
||||||
|
return (
|
||||||
|
<ReactAdminButton
|
||||||
|
color="primary"
|
||||||
|
component="span"
|
||||||
|
variant={variant}
|
||||||
|
label={label}
|
||||||
|
>
|
||||||
|
<GetAppIcon style={{ transform: "rotate(180deg)", fontSize: "20" }} />
|
||||||
|
</ReactAdminButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const expectedFields = ["id", "displayname"].sort();
|
const expectedFields = ["id", "displayname"].sort();
|
||||||
const optionalFields = [
|
const optionalFields = [
|
||||||
"user_type",
|
"user_type",
|
||||||
|
|||||||
+87
-165
@@ -1,21 +1,19 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
fetchUtils,
|
fetchUtils,
|
||||||
Form,
|
|
||||||
FormDataConsumer,
|
FormDataConsumer,
|
||||||
Notification,
|
Notification,
|
||||||
required,
|
|
||||||
useLogin,
|
useLogin,
|
||||||
useNotify,
|
useNotify,
|
||||||
useLocaleState,
|
useLocale,
|
||||||
|
useSetLocale,
|
||||||
useTranslate,
|
useTranslate,
|
||||||
PasswordInput,
|
PasswordInput,
|
||||||
TextInput,
|
TextInput,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import { useFormContext } from "react-hook-form";
|
import { Form, useForm } from "react-final-form";
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
Box,
|
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
CardActions,
|
CardActions,
|
||||||
@@ -23,12 +21,12 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
Select,
|
Select,
|
||||||
TextField,
|
TextField,
|
||||||
Typography,
|
} from "@material-ui/core";
|
||||||
} from "@mui/material";
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
import { styled } from "@mui/material/styles";
|
import LockIcon from "@material-ui/icons/Lock";
|
||||||
import LockIcon from "@mui/icons-material/Lock";
|
|
||||||
|
|
||||||
const FormBox = styled(Box)(({ theme }) => ({
|
const useStyles = makeStyles(theme => ({
|
||||||
|
main: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
minHeight: "calc(100vh - 1em)",
|
minHeight: "calc(100vh - 1em)",
|
||||||
@@ -38,88 +36,52 @@ const FormBox = styled(Box)(({ theme }) => ({
|
|||||||
backgroundColor: "#f9f9f9",
|
backgroundColor: "#f9f9f9",
|
||||||
backgroundRepeat: "no-repeat",
|
backgroundRepeat: "no-repeat",
|
||||||
backgroundSize: "cover",
|
backgroundSize: "cover",
|
||||||
|
},
|
||||||
[`& .card`]: {
|
card: {
|
||||||
minWidth: "30em",
|
minWidth: "30em",
|
||||||
marginTop: "6em",
|
marginTop: "6em",
|
||||||
marginBottom: "6em",
|
marginBottom: "6em",
|
||||||
},
|
},
|
||||||
[`& .avatar`]: {
|
avatar: {
|
||||||
margin: "1em",
|
margin: "1em",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
},
|
},
|
||||||
[`& .icon`]: {
|
icon: {
|
||||||
backgroundColor: theme.palette.grey[500],
|
backgroundColor: theme.palette.secondary.main,
|
||||||
},
|
},
|
||||||
[`& .hint`]: {
|
hint: {
|
||||||
marginTop: "1em",
|
marginTop: "1em",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
color: theme.palette.grey[600],
|
color: theme.palette.grey[500],
|
||||||
},
|
},
|
||||||
[`& .form`]: {
|
form: {
|
||||||
padding: "0 1em 1em 1em",
|
padding: "0 1em 1em 1em",
|
||||||
},
|
},
|
||||||
[`& .input`]: {
|
input: {
|
||||||
marginTop: "1em",
|
marginTop: "1em",
|
||||||
},
|
},
|
||||||
[`& .actions`]: {
|
actions: {
|
||||||
padding: "0 1em 1em 1em",
|
padding: "0 1em 1em 1em",
|
||||||
},
|
},
|
||||||
[`& .serverVersion`]: {
|
serverVersion: {
|
||||||
color: theme.palette.grey[500],
|
color: "#9e9e9e",
|
||||||
fontFamily: "Roboto, Helvetica, Arial, sans-serif",
|
fontFamily: "Roboto, Helvetica, Arial, sans-serif",
|
||||||
marginBottom: "1em",
|
marginBottom: "1em",
|
||||||
marginLeft: "0.5em",
|
marginLeft: "0.5em",
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const LoginPage = () => {
|
const LoginPage = ({ theme }) => {
|
||||||
|
const classes = useStyles({ 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();
|
||||||
const [locale, setLocale] = useLocaleState();
|
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 [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 } = {},
|
||||||
@@ -135,16 +97,26 @@ const LoginPage = () => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const validateBaseUrl = value => {
|
const validate = values => {
|
||||||
if (!value.match(/^(http|https):\/\//)) {
|
const errors = {};
|
||||||
return translate("synapseadmin.auth.protocol_error");
|
if (!values.username) {
|
||||||
} else if (
|
errors.username = translate("ra.validation.required");
|
||||||
!value.match(/^(http|https):\/\/[a-zA-Z0-9\-.]+(:\d{1,5})?[^?&\s]*$/)
|
|
||||||
) {
|
|
||||||
return translate("synapseadmin.auth.url_error");
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
if (!values.password) {
|
||||||
|
errors.password = translate("ra.validation.required");
|
||||||
|
}
|
||||||
|
if (!values.base_url) {
|
||||||
|
errors.base_url = translate("ra.validation.required");
|
||||||
|
} else {
|
||||||
|
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})?$/)
|
||||||
|
) {
|
||||||
|
errors.base_url = translate("synapseadmin.auth.url_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = auth => {
|
const handleSubmit = auth => {
|
||||||
@@ -157,19 +129,11 @@ const LoginPage = () => {
|
|||||||
: typeof error === "undefined" || !error.message
|
: typeof error === "undefined" || !error.message
|
||||||
? "ra.auth.sign_in_error"
|
? "ra.auth.sign_in_error"
|
||||||
: error.message,
|
: error.message,
|
||||||
{ type: "warning" }
|
"warning"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
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;
|
||||||
@@ -179,11 +143,11 @@ const LoginPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const UserData = ({ formData }) => {
|
const UserData = ({ formData }) => {
|
||||||
const form = useFormContext();
|
const form = useForm();
|
||||||
const [serverVersion, setServerVersion] = useState("");
|
const [serverVersion, setServerVersion] = useState("");
|
||||||
|
|
||||||
const handleUsernameChange = _ => {
|
const handleUsernameChange = _ => {
|
||||||
if (formData.base_url || cfg_base_url) return;
|
if (formData.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`;
|
||||||
@@ -192,11 +156,11 @@ const LoginPage = () => {
|
|||||||
fetchUtils
|
fetchUtils
|
||||||
.fetchJson(wellKnownUrl, { method: "GET" })
|
.fetchJson(wellKnownUrl, { method: "GET" })
|
||||||
.then(({ json }) => {
|
.then(({ json }) => {
|
||||||
form.setValue("base_url", json["m.homeserver"].base_url);
|
form.change("base_url", json["m.homeserver"].base_url);
|
||||||
})
|
})
|
||||||
.catch(_ => {
|
.catch(_ => {
|
||||||
// if there is no .well-known entry, try the home server name
|
// if there is no .well-known entry, try the home server name
|
||||||
form.setValue("base_url", `https://${home_server}`);
|
form.change("base_url", `https://${home_server}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -205,9 +169,7 @@ const LoginPage = () => {
|
|||||||
_ => {
|
_ => {
|
||||||
if (
|
if (
|
||||||
!formData.base_url ||
|
!formData.base_url ||
|
||||||
!formData.base_url.match(
|
!formData.base_url.match(/^(http|https):\/\/[a-zA-Z0-9\-.]+$/)
|
||||||
/^(http|https):\/\/[a-zA-Z0-9\-.]+(:\d{1,5})?$/
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
const versionUrl = `${formData.base_url}/_synapse/admin/v1/server_version`;
|
const versionUrl = `${formData.base_url}/_synapse/admin/v1/server_version`;
|
||||||
@@ -223,96 +185,66 @@ const LoginPage = () => {
|
|||||||
.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]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div>
|
||||||
<Box>
|
<div className={classes.input}>
|
||||||
<TextInput
|
<TextInput
|
||||||
autoFocus
|
autoFocus
|
||||||
name="username"
|
name="username"
|
||||||
component={renderInput}
|
component={renderInput}
|
||||||
label="ra.auth.username"
|
label={translate("ra.auth.username")}
|
||||||
disabled={loading || !supportPassAuth}
|
disabled={loading}
|
||||||
onBlur={handleUsernameChange}
|
onBlur={handleUsernameChange}
|
||||||
resettable
|
|
||||||
fullWidth
|
fullWidth
|
||||||
className="input"
|
|
||||||
validate={required()}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</div>
|
||||||
<Box>
|
<div className={classes.input}>
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
name="password"
|
name="password"
|
||||||
component={renderInput}
|
component={renderInput}
|
||||||
label="ra.auth.password"
|
label={translate("ra.auth.password")}
|
||||||
type="password"
|
type="password"
|
||||||
disabled={loading || !supportPassAuth}
|
disabled={loading}
|
||||||
resettable
|
|
||||||
fullWidth
|
fullWidth
|
||||||
className="input"
|
|
||||||
validate={required()}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</div>
|
||||||
<Box>
|
<div className={classes.input}>
|
||||||
<TextInput
|
<TextInput
|
||||||
name="base_url"
|
name="base_url"
|
||||||
component={renderInput}
|
component={renderInput}
|
||||||
label="synapseadmin.auth.base_url"
|
label={translate("synapseadmin.auth.base_url")}
|
||||||
disabled={cfg_base_url || loading}
|
disabled={loading}
|
||||||
resettable
|
|
||||||
fullWidth
|
fullWidth
|
||||||
className="input"
|
|
||||||
validate={[required(), validateBaseUrl]}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</div>
|
||||||
<Typography className="serverVersion">{serverVersion}</Typography>
|
<div className={classes.serverVersion}>{serverVersion}</div>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
defaultValues={{ base_url: cfg_base_url || base_url }}
|
initialValues={{ base_url: base_url }}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
mode="onTouched"
|
validate={validate}
|
||||||
>
|
render={({ handleSubmit }) => (
|
||||||
<FormBox>
|
<form onSubmit={handleSubmit} noValidate>
|
||||||
<Card className="card">
|
<div className={classes.main}>
|
||||||
<Box className="avatar">
|
<Card className={classes.card}>
|
||||||
<Avatar className="icon">
|
<div className={classes.avatar}>
|
||||||
|
<Avatar className={classes.icon}>
|
||||||
<LockIcon />
|
<LockIcon />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
</Box>
|
</div>
|
||||||
<Box className="hint">{translate("synapseadmin.auth.welcome")}</Box>
|
<div className={classes.hint}>
|
||||||
<Box className="form">
|
{translate("synapseadmin.auth.welcome")}
|
||||||
|
</div>
|
||||||
|
<div className={classes.form}>
|
||||||
|
<div className={classes.input}>
|
||||||
<Select
|
<Select
|
||||||
value={locale}
|
value={locale}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
@@ -320,44 +252,34 @@ const LoginPage = () => {
|
|||||||
}}
|
}}
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="input"
|
|
||||||
>
|
>
|
||||||
<MenuItem value="de">Deutsch</MenuItem>
|
<MenuItem value="de">Deutsch</MenuItem>
|
||||||
<MenuItem value="en">English</MenuItem>
|
<MenuItem value="en">English</MenuItem>
|
||||||
<MenuItem value="fr">Français</MenuItem>
|
|
||||||
<MenuItem value="it">Italiano</MenuItem>
|
|
||||||
<MenuItem value="zh">简体中文</MenuItem>
|
|
||||||
</Select>
|
</Select>
|
||||||
|
</div>
|
||||||
<FormDataConsumer>
|
<FormDataConsumer>
|
||||||
{formDataProps => <UserData {...formDataProps} />}
|
{formDataProps => <UserData {...formDataProps} />}
|
||||||
</FormDataConsumer>
|
</FormDataConsumer>
|
||||||
<CardActions className="actions">
|
</div>
|
||||||
|
<CardActions className={classes.actions}>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
type="submit"
|
type="submit"
|
||||||
color="primary"
|
color="primary"
|
||||||
disabled={loading || !supportPassAuth}
|
disabled={loading}
|
||||||
|
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 === ""}
|
|
||||||
fullWidth
|
|
||||||
>
|
|
||||||
{loading && <CircularProgress size={25} thickness={2} />}
|
|
||||||
{translate("synapseadmin.auth.sso_sign_in")}
|
|
||||||
</Button>
|
|
||||||
</CardActions>
|
</CardActions>
|
||||||
</Box>
|
|
||||||
</Card>
|
</Card>
|
||||||
</FormBox>
|
|
||||||
<Notification />
|
<Notification />
|
||||||
</Form>
|
</div>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { render } from "@testing-library/react";
|
import { TestContext } from "ra-test";
|
||||||
import { AdminContext } from "react-admin";
|
import { shallow } from "enzyme";
|
||||||
import LoginPage from "./LoginPage";
|
import LoginPage from "./LoginPage";
|
||||||
|
|
||||||
describe("LoginForm", () => {
|
describe("LoginForm", () => {
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
render(
|
shallow(
|
||||||
<AdminContext>
|
<TestContext>
|
||||||
<LoginPage />
|
<LoginPage />
|
||||||
</AdminContext>
|
</TestContext>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
// in src/Menu.js
|
||||||
|
import * as React from "react";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { useMediaQuery } from "@material-ui/core";
|
||||||
|
import { MenuItemLink, getResources } from "react-admin";
|
||||||
|
import DefaultIcon from "@material-ui/icons/ViewList";
|
||||||
|
import LabelIcon from "@material-ui/icons/Label";
|
||||||
|
|
||||||
|
const Menu = ({ onMenuClick, logout }) => {
|
||||||
|
const isXSmall = useMediaQuery(theme => theme.breakpoints.down("xs"));
|
||||||
|
const open = useSelector(state => state.admin.ui.sidebarOpen);
|
||||||
|
const resources = useSelector(getResources);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{resources.map(resource => (
|
||||||
|
<MenuItemLink
|
||||||
|
key={resource.name}
|
||||||
|
to={`/${resource.name}`}
|
||||||
|
primaryText={
|
||||||
|
(resource.options && resource.options.label) || resource.name
|
||||||
|
}
|
||||||
|
leftIcon={resource.icon ? <resource.icon /> : <DefaultIcon />}
|
||||||
|
onClick={onMenuClick}
|
||||||
|
sidebarIsOpen={open}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<MenuItemLink
|
||||||
|
to="/custom-route"
|
||||||
|
primaryText="Miscellaneous"
|
||||||
|
leftIcon={<LabelIcon />}
|
||||||
|
onClick={onMenuClick}
|
||||||
|
sidebarIsOpen={open}
|
||||||
|
/>
|
||||||
|
{isXSmall && logout}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Menu;
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
import React, { Fragment } from "react";
|
|
||||||
import FolderSharedIcon from "@mui/icons-material/FolderShared";
|
|
||||||
import {
|
|
||||||
BooleanField,
|
|
||||||
BulkDeleteButton,
|
|
||||||
Button,
|
|
||||||
DatagridConfigurable,
|
|
||||||
ExportButton,
|
|
||||||
DeleteButton,
|
|
||||||
List,
|
|
||||||
NumberField,
|
|
||||||
Pagination,
|
|
||||||
SelectColumnsButton,
|
|
||||||
TextField,
|
|
||||||
TopToolbar,
|
|
||||||
useCreate,
|
|
||||||
useListContext,
|
|
||||||
useNotify,
|
|
||||||
useTranslate,
|
|
||||||
useRecordContext,
|
|
||||||
useRefresh,
|
|
||||||
useUnselectAll,
|
|
||||||
} from "react-admin";
|
|
||||||
import { useMutation } from "react-query";
|
|
||||||
import AvatarField from "./AvatarField";
|
|
||||||
|
|
||||||
const RoomDirectoryPagination = props => (
|
|
||||||
<Pagination {...props} rowsPerPageOptions={[100, 500, 1000, 2000]} />
|
|
||||||
);
|
|
||||||
|
|
||||||
export const RoomDirectoryDeleteButton = props => {
|
|
||||||
const translate = useTranslate();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DeleteButton
|
|
||||||
{...props}
|
|
||||||
label="resources.room_directory.action.erase"
|
|
||||||
redirect={false}
|
|
||||||
mutationMode="pessimistic"
|
|
||||||
confirmTitle={translate("resources.room_directory.action.title", {
|
|
||||||
smart_count: 1,
|
|
||||||
})}
|
|
||||||
confirmContent={translate("resources.room_directory.action.content", {
|
|
||||||
smart_count: 1,
|
|
||||||
})}
|
|
||||||
resource="room_directory"
|
|
||||||
icon={<FolderSharedIcon />}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const RoomDirectoryBulkDeleteButton = props => (
|
|
||||||
<BulkDeleteButton
|
|
||||||
{...props}
|
|
||||||
label="resources.room_directory.action.erase"
|
|
||||||
mutationMode="pessimistic"
|
|
||||||
confirmTitle="resources.room_directory.action.title"
|
|
||||||
confirmContent="resources.room_directory.action.content"
|
|
||||||
resource="room_directory"
|
|
||||||
icon={<FolderSharedIcon />}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const RoomDirectoryBulkSaveButton = () => {
|
|
||||||
const { selectedIds } = useListContext();
|
|
||||||
const notify = useNotify();
|
|
||||||
const refresh = useRefresh();
|
|
||||||
const unselectAll = useUnselectAll();
|
|
||||||
const { createMany, isloading } = useMutation();
|
|
||||||
|
|
||||||
const handleSend = values => {
|
|
||||||
createMany(
|
|
||||||
["room_directory", "createMany", { ids: selectedIds, data: {} }],
|
|
||||||
{
|
|
||||||
onSuccess: data => {
|
|
||||||
notify("resources.room_directory.action.send_success");
|
|
||||||
unselectAll("rooms");
|
|
||||||
refresh();
|
|
||||||
},
|
|
||||||
onError: error =>
|
|
||||||
notify("resources.room_directory.action.send_failure", {
|
|
||||||
type: "error",
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
label="resources.room_directory.action.create"
|
|
||||||
onClick={handleSend}
|
|
||||||
disabled={isloading}
|
|
||||||
>
|
|
||||||
<FolderSharedIcon />
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const RoomDirectorySaveButton = () => {
|
|
||||||
const record = useRecordContext();
|
|
||||||
const notify = useNotify();
|
|
||||||
const refresh = useRefresh();
|
|
||||||
const [create, { isloading }] = useCreate();
|
|
||||||
|
|
||||||
const handleSend = values => {
|
|
||||||
create(
|
|
||||||
"room_directory",
|
|
||||||
{ data: { id: record.id } },
|
|
||||||
{
|
|
||||||
onSuccess: data => {
|
|
||||||
notify("resources.room_directory.action.send_success");
|
|
||||||
refresh();
|
|
||||||
},
|
|
||||||
onError: error =>
|
|
||||||
notify("resources.room_directory.action.send_failure", {
|
|
||||||
type: "error",
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
label="resources.room_directory.action.create"
|
|
||||||
onClick={handleSend}
|
|
||||||
disabled={isloading}
|
|
||||||
>
|
|
||||||
<FolderSharedIcon />
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const RoomDirectoryBulkActionButtons = () => (
|
|
||||||
<Fragment>
|
|
||||||
<RoomDirectoryBulkDeleteButton />
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
|
|
||||||
const RoomDirectoryListActions = () => (
|
|
||||||
<TopToolbar>
|
|
||||||
<SelectColumnsButton />
|
|
||||||
<ExportButton />
|
|
||||||
</TopToolbar>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const RoomDirectoryList = () => (
|
|
||||||
<List
|
|
||||||
pagination={<RoomDirectoryPagination />}
|
|
||||||
perPage={100}
|
|
||||||
actions={<RoomDirectoryListActions />}
|
|
||||||
>
|
|
||||||
<DatagridConfigurable
|
|
||||||
rowClick={(id, resource, record) => "/rooms/" + id + "/show"}
|
|
||||||
bulkActionButtons={<RoomDirectoryBulkActionButtons />}
|
|
||||||
omit={["room_id", "canonical_alias", "topic"]}
|
|
||||||
>
|
|
||||||
<AvatarField
|
|
||||||
source="avatar_src"
|
|
||||||
sortable={false}
|
|
||||||
sx={{ height: "40px", width: "40px" }}
|
|
||||||
label="resources.rooms.fields.avatar"
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
source="name"
|
|
||||||
sortable={false}
|
|
||||||
label="resources.rooms.fields.name"
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
source="room_id"
|
|
||||||
sortable={false}
|
|
||||||
label="resources.rooms.fields.room_id"
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
source="canonical_alias"
|
|
||||||
sortable={false}
|
|
||||||
label="resources.rooms.fields.canonical_alias"
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
source="topic"
|
|
||||||
sortable={false}
|
|
||||||
label="resources.rooms.fields.topic"
|
|
||||||
/>
|
|
||||||
<NumberField
|
|
||||||
source="num_joined_members"
|
|
||||||
sortable={false}
|
|
||||||
label="resources.rooms.fields.joined_members"
|
|
||||||
/>
|
|
||||||
<BooleanField
|
|
||||||
source="world_readable"
|
|
||||||
sortable={false}
|
|
||||||
label="resources.room_directory.fields.world_readable"
|
|
||||||
/>
|
|
||||||
<BooleanField
|
|
||||||
source="guest_can_join"
|
|
||||||
sortable={false}
|
|
||||||
label="resources.room_directory.fields.guest_can_join"
|
|
||||||
/>
|
|
||||||
</DatagridConfigurable>
|
|
||||||
</List>
|
|
||||||
);
|
|
||||||
@@ -7,31 +7,24 @@ import {
|
|||||||
Toolbar,
|
Toolbar,
|
||||||
required,
|
required,
|
||||||
useCreate,
|
useCreate,
|
||||||
useListContext,
|
useMutation,
|
||||||
useNotify,
|
useNotify,
|
||||||
useRecordContext,
|
|
||||||
useTranslate,
|
useTranslate,
|
||||||
useUnselectAll,
|
useUnselectAll,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import { useMutation } from "react-query";
|
import MessageIcon from "@material-ui/icons/Message";
|
||||||
import MessageIcon from "@mui/icons-material/Message";
|
import IconCancel from "@material-ui/icons/Cancel";
|
||||||
import IconCancel from "@mui/icons-material/Cancel";
|
import Dialog from "@material-ui/core/Dialog";
|
||||||
import {
|
import DialogContent from "@material-ui/core/DialogContent";
|
||||||
Dialog,
|
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||||
DialogContent,
|
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||||
DialogContentText,
|
|
||||||
DialogTitle,
|
|
||||||
} from "@mui/material";
|
|
||||||
|
|
||||||
const ServerNoticeDialog = ({ open, loading, onClose, onSend }) => {
|
const ServerNoticeDialog = ({ open, loading, onClose, onSend }) => {
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
|
|
||||||
const ServerNoticeToolbar = props => (
|
const ServerNoticeToolbar = props => (
|
||||||
<Toolbar {...props}>
|
<Toolbar {...props}>
|
||||||
<SaveButton
|
<SaveButton label="resources.servernotices.action.send" />
|
||||||
label="resources.servernotices.action.send"
|
|
||||||
disabled={props.pristine}
|
|
||||||
/>
|
|
||||||
<Button label="ra.action.cancel" onClick={onClose}>
|
<Button label="ra.action.cancel" onClick={onClose}>
|
||||||
<IconCancel />
|
<IconCancel />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -49,6 +42,7 @@ const ServerNoticeDialog = ({ open, loading, onClose, onSend }) => {
|
|||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
<SimpleForm
|
<SimpleForm
|
||||||
toolbar={<ServerNoticeToolbar />}
|
toolbar={<ServerNoticeToolbar />}
|
||||||
|
submitOnEnter={false}
|
||||||
redirect={false}
|
redirect={false}
|
||||||
save={onSend}
|
save={onSend}
|
||||||
>
|
>
|
||||||
@@ -67,11 +61,10 @@ const ServerNoticeDialog = ({ open, loading, onClose, onSend }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ServerNoticeButton = () => {
|
export const ServerNoticeButton = ({ record }) => {
|
||||||
const record = useRecordContext();
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
const [create, { isloading }] = useCreate("servernotices");
|
const [create, { loading }] = useCreate("servernotices");
|
||||||
|
|
||||||
const handleDialogOpen = () => setOpen(true);
|
const handleDialogOpen = () => setOpen(true);
|
||||||
const handleDialogClose = () => setOpen(false);
|
const handleDialogClose = () => setOpen(false);
|
||||||
@@ -84,10 +77,8 @@ export const ServerNoticeButton = () => {
|
|||||||
notify("resources.servernotices.action.send_success");
|
notify("resources.servernotices.action.send_success");
|
||||||
handleDialogClose();
|
handleDialogClose();
|
||||||
},
|
},
|
||||||
onError: () =>
|
onFailure: () =>
|
||||||
notify("resources.servernotices.action.send_failure", {
|
notify("resources.servernotices.action.send_failure", "error"),
|
||||||
type: "error",
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -97,7 +88,7 @@ export const ServerNoticeButton = () => {
|
|||||||
<Button
|
<Button
|
||||||
label="resources.servernotices.send"
|
label="resources.servernotices.send"
|
||||||
onClick={handleDialogOpen}
|
onClick={handleDialogOpen}
|
||||||
disabled={isloading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
<MessageIcon />
|
<MessageIcon />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -110,29 +101,30 @@ export const ServerNoticeButton = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ServerNoticeBulkButton = () => {
|
export const ServerNoticeBulkButton = ({ selectedIds }) => {
|
||||||
const { selectedIds } = useListContext();
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
const unselectAll = useUnselectAll();
|
const unselectAll = useUnselectAll();
|
||||||
const { createMany, isloading } = useMutation();
|
const [createMany, { loading }] = useMutation();
|
||||||
|
|
||||||
const handleDialogOpen = () => setOpen(true);
|
const handleDialogOpen = () => setOpen(true);
|
||||||
const handleDialogClose = () => setOpen(false);
|
const handleDialogClose = () => setOpen(false);
|
||||||
|
|
||||||
const handleSend = values => {
|
const handleSend = values => {
|
||||||
createMany(
|
createMany(
|
||||||
["servernotices", "createMany", { ids: selectedIds, data: values }],
|
|
||||||
{
|
{
|
||||||
onSuccess: data => {
|
type: "createMany",
|
||||||
|
resource: "servernotices",
|
||||||
|
payload: { ids: selectedIds, data: values },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: ({ data }) => {
|
||||||
notify("resources.servernotices.action.send_success");
|
notify("resources.servernotices.action.send_success");
|
||||||
unselectAll("users");
|
unselectAll("users");
|
||||||
handleDialogClose();
|
handleDialogClose();
|
||||||
},
|
},
|
||||||
onError: error =>
|
onFailure: error =>
|
||||||
notify("resources.servernotices.action.send_failure", {
|
notify("resources.servernotices.action.send_failure", "error"),
|
||||||
type: "error",
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -142,7 +134,7 @@ export const ServerNoticeBulkButton = () => {
|
|||||||
<Button
|
<Button
|
||||||
label="resources.servernotices.send"
|
label="resources.servernotices.send"
|
||||||
onClick={handleDialogOpen}
|
onClick={handleDialogOpen}
|
||||||
disabled={isloading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
<MessageIcon />
|
<MessageIcon />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,185 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Datagrid,
|
|
||||||
DateField,
|
|
||||||
Filter,
|
|
||||||
List,
|
|
||||||
Pagination,
|
|
||||||
ReferenceField,
|
|
||||||
ReferenceManyField,
|
|
||||||
SearchInput,
|
|
||||||
Show,
|
|
||||||
Tab,
|
|
||||||
TabbedShowLayout,
|
|
||||||
TextField,
|
|
||||||
TopToolbar,
|
|
||||||
useRecordContext,
|
|
||||||
useDelete,
|
|
||||||
useNotify,
|
|
||||||
useRefresh,
|
|
||||||
useTranslate,
|
|
||||||
} from "react-admin";
|
|
||||||
import AutorenewIcon from "@mui/icons-material/Autorenew";
|
|
||||||
import FolderSharedIcon from "@mui/icons-material/FolderShared";
|
|
||||||
import ViewListIcon from "@mui/icons-material/ViewList";
|
|
||||||
|
|
||||||
const DestinationPagination = props => (
|
|
||||||
<Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
|
|
||||||
);
|
|
||||||
|
|
||||||
const date_format = {
|
|
||||||
year: "numeric",
|
|
||||||
month: "2-digit",
|
|
||||||
day: "2-digit",
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
second: "2-digit",
|
|
||||||
};
|
|
||||||
|
|
||||||
const destinationRowSx = (record, _index) => ({
|
|
||||||
backgroundColor: record.retry_last_ts > 0 ? "#ffcccc" : "white",
|
|
||||||
});
|
|
||||||
|
|
||||||
const DestinationFilter = props => {
|
|
||||||
return (
|
|
||||||
<Filter {...props}>
|
|
||||||
<SearchInput source="destination" alwaysOn />
|
|
||||||
</Filter>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DestinationReconnectButton = props => {
|
|
||||||
const record = useRecordContext();
|
|
||||||
const refresh = useRefresh();
|
|
||||||
const notify = useNotify();
|
|
||||||
const [handleReconnect, { isLoading }] = useDelete("destinations");
|
|
||||||
|
|
||||||
// Reconnect is not required if no error has occurred. (`failure_ts`)
|
|
||||||
if (!record || !record.failure_ts) return null;
|
|
||||||
|
|
||||||
const handleClick = e => {
|
|
||||||
// Prevents redirection to the detail page when clicking in the list
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
handleReconnect(
|
|
||||||
{ payload: { id: record.id } },
|
|
||||||
{
|
|
||||||
onSuccess: () => {
|
|
||||||
notify("ra.notification.updated", {
|
|
||||||
messageArgs: { smart_count: 1 },
|
|
||||||
});
|
|
||||||
refresh();
|
|
||||||
},
|
|
||||||
onError: () => {
|
|
||||||
notify("ra.message.error", { type: "error" });
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
label="resources.destinations.action.reconnect"
|
|
||||||
onClick={handleClick}
|
|
||||||
disabled={isLoading}
|
|
||||||
>
|
|
||||||
<AutorenewIcon />
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const DestinationShowActions = props => (
|
|
||||||
<TopToolbar>
|
|
||||||
<DestinationReconnectButton />
|
|
||||||
</TopToolbar>
|
|
||||||
);
|
|
||||||
|
|
||||||
const DestinationTitle = props => {
|
|
||||||
const record = useRecordContext();
|
|
||||||
const translate = useTranslate();
|
|
||||||
return (
|
|
||||||
<span>
|
|
||||||
{translate("resources.destinations.name", 1)} {record.destination}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DestinationList = props => {
|
|
||||||
return (
|
|
||||||
<List
|
|
||||||
{...props}
|
|
||||||
filters={<DestinationFilter />}
|
|
||||||
pagination={<DestinationPagination />}
|
|
||||||
sort={{ field: "destination", order: "ASC" }}
|
|
||||||
>
|
|
||||||
<Datagrid
|
|
||||||
rowSx={destinationRowSx}
|
|
||||||
rowClick={(id, _resource, _record) => `${id}/show/rooms`}
|
|
||||||
bulkActionButtons={false}
|
|
||||||
>
|
|
||||||
<TextField source="destination" />
|
|
||||||
<DateField source="failure_ts" showTime options={date_format} />
|
|
||||||
<DateField source="retry_last_ts" showTime options={date_format} />
|
|
||||||
<TextField source="retry_interval" />
|
|
||||||
<TextField source="last_successful_stream_ordering" />
|
|
||||||
<DestinationReconnectButton />
|
|
||||||
</Datagrid>
|
|
||||||
</List>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DestinationShow = props => {
|
|
||||||
const translate = useTranslate();
|
|
||||||
return (
|
|
||||||
<Show
|
|
||||||
actions={<DestinationShowActions />}
|
|
||||||
title={<DestinationTitle />}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<TabbedShowLayout>
|
|
||||||
<Tab label="status" icon={<ViewListIcon />}>
|
|
||||||
<TextField source="destination" />
|
|
||||||
<DateField source="failure_ts" showTime options={date_format} />
|
|
||||||
<DateField source="retry_last_ts" showTime options={date_format} />
|
|
||||||
<TextField source="retry_interval" />
|
|
||||||
<TextField source="last_successful_stream_ordering" />
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab
|
|
||||||
label={translate("resources.rooms.name", { smart_count: 2 })}
|
|
||||||
icon={<FolderSharedIcon />}
|
|
||||||
path="rooms"
|
|
||||||
>
|
|
||||||
<ReferenceManyField
|
|
||||||
reference="destination_rooms"
|
|
||||||
target="destination"
|
|
||||||
addLabel={false}
|
|
||||||
pagination={<DestinationPagination />}
|
|
||||||
perPage={50}
|
|
||||||
>
|
|
||||||
<Datagrid
|
|
||||||
style={{ width: "100%" }}
|
|
||||||
rowClick={(id, resource, record) => `/rooms/${id}/show`}
|
|
||||||
>
|
|
||||||
<TextField
|
|
||||||
source="room_id"
|
|
||||||
label="resources.rooms.fields.room_id"
|
|
||||||
/>
|
|
||||||
<TextField source="stream_ordering" sortable={false} />
|
|
||||||
<ReferenceField
|
|
||||||
label="resources.rooms.fields.name"
|
|
||||||
source="id"
|
|
||||||
reference="rooms"
|
|
||||||
sortable={false}
|
|
||||||
link=""
|
|
||||||
>
|
|
||||||
<TextField source="name" sortable={false} />
|
|
||||||
</ReferenceField>
|
|
||||||
</Datagrid>
|
|
||||||
</ReferenceManyField>
|
|
||||||
</Tab>
|
|
||||||
</TabbedShowLayout>
|
|
||||||
</Show>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
+36
-22
@@ -1,23 +1,40 @@
|
|||||||
import React, { Fragment, useState } from "react";
|
import React, { Fragment, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
useDelete,
|
useMutation,
|
||||||
useNotify,
|
useNotify,
|
||||||
Confirm,
|
Confirm,
|
||||||
useRecordContext,
|
|
||||||
useRefresh,
|
useRefresh,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import ActionDelete from "@mui/icons-material/Delete";
|
import ActionDelete from "@material-ui/icons/Delete";
|
||||||
import { alpha, useTheme } from "@mui/material/styles";
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
|
import { fade } from "@material-ui/core/styles/colorManipulator";
|
||||||
|
import classnames from "classnames";
|
||||||
|
|
||||||
|
const useStyles = makeStyles(
|
||||||
|
theme => ({
|
||||||
|
deleteButton: {
|
||||||
|
color: theme.palette.error.main,
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: fade(theme.palette.error.main, 0.12),
|
||||||
|
// Reset on mouse devices
|
||||||
|
"@media (hover: none)": {
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{ name: "RaDeleteDeviceButton" }
|
||||||
|
);
|
||||||
|
|
||||||
export const DeviceRemoveButton = props => {
|
export const DeviceRemoveButton = props => {
|
||||||
const theme = useTheme();
|
const { record } = props;
|
||||||
const record = useRecordContext();
|
const classes = useStyles(props);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const refresh = useRefresh();
|
const refresh = useRefresh();
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
|
|
||||||
const [removeDevice, { isLoading }] = useDelete("devices");
|
const [removeDevice, { loading }] = useMutation();
|
||||||
|
|
||||||
if (!record) return null;
|
if (!record) return null;
|
||||||
|
|
||||||
@@ -26,15 +43,21 @@ 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", { type: "error" });
|
notify("resources.devices.action.erase.failure", "error"),
|
||||||
},
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
@@ -45,22 +68,13 @@ export const DeviceRemoveButton = props => {
|
|||||||
<Button
|
<Button
|
||||||
label="ra.action.remove"
|
label="ra.action.remove"
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
sx={{
|
className={classnames("ra-delete-button", classes.deleteButton)}
|
||||||
color: theme.palette.error.main,
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: alpha(theme.palette.error.main, 0.12),
|
|
||||||
// Reset on mouse devices
|
|
||||||
"@media (hover: none)": {
|
|
||||||
backgroundColor: "transparent",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<ActionDelete />
|
<ActionDelete />
|
||||||
</Button>
|
</Button>
|
||||||
<Confirm
|
<Confirm
|
||||||
isOpen={open}
|
isOpen={open}
|
||||||
loading={isLoading}
|
loading={loading}
|
||||||
onConfirm={handleConfirm}
|
onConfirm={handleConfirm}
|
||||||
onClose={handleDialogClose}
|
onClose={handleDialogClose}
|
||||||
title="resources.devices.action.erase.title"
|
title="resources.devices.action.erase.title"
|
||||||
|
|||||||
+32
-217
@@ -1,4 +1,7 @@
|
|||||||
import React, { Fragment, useState } from "react";
|
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 {
|
import {
|
||||||
BooleanInput,
|
BooleanInput,
|
||||||
Button,
|
Button,
|
||||||
@@ -7,27 +10,32 @@ import {
|
|||||||
SaveButton,
|
SaveButton,
|
||||||
SimpleForm,
|
SimpleForm,
|
||||||
Toolbar,
|
Toolbar,
|
||||||
useCreate,
|
|
||||||
useDelete,
|
useDelete,
|
||||||
useNotify,
|
useNotify,
|
||||||
useRecordContext,
|
|
||||||
useRefresh,
|
|
||||||
useTranslate,
|
useTranslate,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import BlockIcon from "@mui/icons-material/Block";
|
import IconCancel from "@material-ui/icons/Cancel";
|
||||||
import ClearIcon from "@mui/icons-material/Clear";
|
import Dialog from "@material-ui/core/Dialog";
|
||||||
import DeleteSweepIcon from "@mui/icons-material/DeleteSweep";
|
import DialogContent from "@material-ui/core/DialogContent";
|
||||||
import {
|
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||||
Dialog,
|
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||||
DialogContent,
|
import DeleteSweepIcon from "@material-ui/icons/DeleteSweep";
|
||||||
DialogContentText,
|
|
||||||
DialogTitle,
|
const useStyles = makeStyles(
|
||||||
Tooltip,
|
theme => ({
|
||||||
} from "@mui/material";
|
deleteButton: {
|
||||||
import IconCancel from "@mui/icons-material/Cancel";
|
color: theme.palette.error.main,
|
||||||
import LockIcon from "@mui/icons-material/Lock";
|
"&:hover": {
|
||||||
import LockOpenIcon from "@mui/icons-material/LockOpen";
|
backgroundColor: fade(theme.palette.error.main, 0.12),
|
||||||
import { alpha, useTheme } from "@mui/material/styles";
|
// Reset on mouse devices
|
||||||
|
"@media (hover: none)": {
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{ name: "RaDeleteDeviceButton" }
|
||||||
|
);
|
||||||
|
|
||||||
const DeleteMediaDialog = ({ open, loading, onClose, onSend }) => {
|
const DeleteMediaDialog = ({ open, loading, onClose, onSend }) => {
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
@@ -63,6 +71,7 @@ const DeleteMediaDialog = ({ open, loading, onClose, onSend }) => {
|
|||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
<SimpleForm
|
<SimpleForm
|
||||||
toolbar={<DeleteMediaToolbar />}
|
toolbar={<DeleteMediaToolbar />}
|
||||||
|
submitOnEnter={false}
|
||||||
redirect={false}
|
redirect={false}
|
||||||
save={onSend}
|
save={onSend}
|
||||||
>
|
>
|
||||||
@@ -94,10 +103,10 @@ const DeleteMediaDialog = ({ open, loading, onClose, onSend }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const DeleteMediaButton = props => {
|
export const DeleteMediaButton = props => {
|
||||||
const theme = useTheme();
|
const classes = useStyles(props);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
const [deleteOne, { isLoading }] = useDelete("delete_media");
|
const [deleteOne, { loading }] = useDelete("delete_media");
|
||||||
|
|
||||||
const handleDialogOpen = () => setOpen(true);
|
const handleDialogOpen = () => setOpen(true);
|
||||||
const handleDialogClose = () => setOpen(false);
|
const handleDialogClose = () => setOpen(false);
|
||||||
@@ -110,10 +119,8 @@ export const DeleteMediaButton = props => {
|
|||||||
notify("resources.delete_media.action.send_success");
|
notify("resources.delete_media.action.send_success");
|
||||||
handleDialogClose();
|
handleDialogClose();
|
||||||
},
|
},
|
||||||
onError: () =>
|
onFailure: () =>
|
||||||
notify("resources.delete_media.action.send_failure", {
|
notify("resources.delete_media.action.send_failure", "error"),
|
||||||
type: "error",
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -123,17 +130,8 @@ export const DeleteMediaButton = props => {
|
|||||||
<Button
|
<Button
|
||||||
label="resources.delete_media.action.send"
|
label="resources.delete_media.action.send"
|
||||||
onClick={handleDialogOpen}
|
onClick={handleDialogOpen}
|
||||||
disabled={isLoading}
|
disabled={loading}
|
||||||
sx={{
|
className={classnames("ra-delete-button", classes.deleteButton)}
|
||||||
color: theme.palette.error.main,
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: alpha(theme.palette.error.main, 0.12),
|
|
||||||
// Reset on mouse devices
|
|
||||||
"@media (hover: none)": {
|
|
||||||
backgroundColor: "transparent",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<DeleteSweepIcon />
|
<DeleteSweepIcon />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -145,186 +143,3 @@ export const DeleteMediaButton = props => {
|
|||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ProtectMediaButton = props => {
|
|
||||||
const record = useRecordContext();
|
|
||||||
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();
|
|
||||||
},
|
|
||||||
onError: () =>
|
|
||||||
notify("resources.protect_media.action.send_failure", {
|
|
||||||
type: "error",
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleUnprotect = () => {
|
|
||||||
deleteOne(
|
|
||||||
{ payload: { ...record } },
|
|
||||||
{
|
|
||||||
onSuccess: () => {
|
|
||||||
notify("resources.protect_media.action.send_success");
|
|
||||||
refresh();
|
|
||||||
},
|
|
||||||
onError: () =>
|
|
||||||
notify("resources.protect_media.action.send_failure", {
|
|
||||||
type: "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 = useRecordContext();
|
|
||||||
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();
|
|
||||||
},
|
|
||||||
onError: () =>
|
|
||||||
notify("resources.quarantine_media.action.send_failure", {
|
|
||||||
type: "error",
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRemoveQuarantaine = () => {
|
|
||||||
deleteOne(
|
|
||||||
{ payload: { ...record } },
|
|
||||||
{
|
|
||||||
onSuccess: () => {
|
|
||||||
notify("resources.quarantine_media.action.send_success");
|
|
||||||
refresh();
|
|
||||||
},
|
|
||||||
onError: () =>
|
|
||||||
notify("resources.quarantine_media.action.send_failure", {
|
|
||||||
type: "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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|||||||
+108
-170
@@ -1,62 +1,64 @@
|
|||||||
import React, { Fragment } from "react";
|
import React, { Fragment } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
import {
|
import {
|
||||||
BooleanField,
|
BooleanField,
|
||||||
BulkDeleteButton,
|
BulkDeleteWithConfirmButton,
|
||||||
DateField,
|
|
||||||
Datagrid,
|
Datagrid,
|
||||||
DatagridConfigurable,
|
|
||||||
DeleteButton,
|
DeleteButton,
|
||||||
ExportButton,
|
|
||||||
Filter,
|
Filter,
|
||||||
FunctionField,
|
|
||||||
List,
|
List,
|
||||||
NumberField,
|
|
||||||
Pagination,
|
Pagination,
|
||||||
ReferenceField,
|
ReferenceField,
|
||||||
ReferenceManyField,
|
ReferenceManyField,
|
||||||
SearchInput,
|
SearchInput,
|
||||||
SelectColumnsButton,
|
|
||||||
SelectField,
|
SelectField,
|
||||||
Show,
|
Show,
|
||||||
Tab,
|
Tab,
|
||||||
TabbedShowLayout,
|
TabbedShowLayout,
|
||||||
TextField,
|
TextField,
|
||||||
TopToolbar,
|
TopToolbar,
|
||||||
useRecordContext,
|
|
||||||
useTranslate,
|
useTranslate,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import { useTheme } from "@mui/material/styles";
|
import get from "lodash/get";
|
||||||
import Box from "@mui/material/Box";
|
import { Tooltip, Typography, Chip } from "@material-ui/core";
|
||||||
import FastForwardIcon from "@mui/icons-material/FastForward";
|
import HttpsIcon from "@material-ui/icons/Https";
|
||||||
import HttpsIcon from "@mui/icons-material/Https";
|
import NoEncryptionIcon from "@material-ui/icons/NoEncryption";
|
||||||
import NoEncryptionIcon from "@mui/icons-material/NoEncryption";
|
import PageviewIcon from "@material-ui/icons/Pageview";
|
||||||
import PageviewIcon from "@mui/icons-material/Pageview";
|
import UserIcon from "@material-ui/icons/Group";
|
||||||
import UserIcon from "@mui/icons-material/Group";
|
import ViewListIcon from "@material-ui/icons/ViewList";
|
||||||
import ViewListIcon from "@mui/icons-material/ViewList";
|
import VisibilityIcon from "@material-ui/icons/Visibility";
|
||||||
import VisibilityIcon from "@mui/icons-material/Visibility";
|
|
||||||
import EventIcon from "@mui/icons-material/Event";
|
|
||||||
import {
|
|
||||||
RoomDirectoryBulkDeleteButton,
|
|
||||||
RoomDirectoryBulkSaveButton,
|
|
||||||
RoomDirectoryDeleteButton,
|
|
||||||
RoomDirectorySaveButton,
|
|
||||||
} from "./RoomDirectory";
|
|
||||||
|
|
||||||
const date_format = {
|
|
||||||
year: "numeric",
|
|
||||||
month: "2-digit",
|
|
||||||
day: "2-digit",
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
second: "2-digit",
|
|
||||||
};
|
|
||||||
|
|
||||||
const RoomPagination = props => (
|
const RoomPagination = props => (
|
||||||
<Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
|
<Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
|
||||||
);
|
);
|
||||||
|
|
||||||
const RoomTitle = props => {
|
const EncryptionField = ({ source, record = {}, emptyText }) => {
|
||||||
const record = useRecordContext();
|
const translate = useTranslate();
|
||||||
|
const value = get(record, source);
|
||||||
|
let ariaLabel = value === false ? "ra.boolean.false" : "ra.boolean.true";
|
||||||
|
|
||||||
|
if (value === false || value === true) {
|
||||||
|
return (
|
||||||
|
<Typography component="span" variant="body2">
|
||||||
|
<Tooltip title={translate(ariaLabel, { _: ariaLabel })}>
|
||||||
|
{value === true ? (
|
||||||
|
<HttpsIcon data-testid="true" htmlColor="limegreen" />
|
||||||
|
) : (
|
||||||
|
<NoEncryptionIcon data-testid="false" color="error" />
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Typography component="span" variant="body2">
|
||||||
|
{emptyText}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const RoomTitle = ({ record }) => {
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
var name = "";
|
var name = "";
|
||||||
if (record) {
|
if (record) {
|
||||||
@@ -70,26 +72,17 @@ const RoomTitle = props => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const RoomShowActions = ({ data, resource }) => {
|
const RoomShowActions = ({ basePath, data, resource }) => {
|
||||||
var roomDirectoryStatus = "";
|
const translate = useTranslate();
|
||||||
if (data) {
|
|
||||||
roomDirectoryStatus = data.public;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TopToolbar>
|
<TopToolbar>
|
||||||
{roomDirectoryStatus === false && (
|
|
||||||
<RoomDirectorySaveButton record={data} />
|
|
||||||
)}
|
|
||||||
{roomDirectoryStatus === true && (
|
|
||||||
<RoomDirectoryDeleteButton record={data} />
|
|
||||||
)}
|
|
||||||
<DeleteButton
|
<DeleteButton
|
||||||
|
basePath={basePath}
|
||||||
record={data}
|
record={data}
|
||||||
resource={resource}
|
resource={resource}
|
||||||
mutationMode="pessimistic"
|
undoable={false}
|
||||||
confirmTitle="resources.rooms.action.erase.title"
|
confirmTitle={translate("synapseadmin.rooms.delete.title")}
|
||||||
confirmContent="resources.rooms.action.erase.content"
|
confirmContent={translate("synapseadmin.rooms.delete.message")}
|
||||||
/>
|
/>
|
||||||
</TopToolbar>
|
</TopToolbar>
|
||||||
);
|
);
|
||||||
@@ -104,9 +97,7 @@ export const RoomShow = props => {
|
|||||||
<TextField source="room_id" />
|
<TextField source="room_id" />
|
||||||
<TextField source="name" />
|
<TextField source="name" />
|
||||||
<TextField source="canonical_alias" />
|
<TextField source="canonical_alias" />
|
||||||
<ReferenceField source="creator" reference="users">
|
<TextField source="creator" />
|
||||||
<TextField source="id" />
|
|
||||||
</ReferenceField>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab
|
<Tab
|
||||||
@@ -116,7 +107,6 @@ export const RoomShow = props => {
|
|||||||
>
|
>
|
||||||
<TextField source="joined_members" />
|
<TextField source="joined_members" />
|
||||||
<TextField source="joined_local_members" />
|
<TextField source="joined_local_members" />
|
||||||
<TextField source="joined_local_devices" />
|
|
||||||
<TextField source="state_events" />
|
<TextField source="state_events" />
|
||||||
<TextField source="version" />
|
<TextField source="version" />
|
||||||
<TextField
|
<TextField
|
||||||
@@ -125,11 +115,7 @@ export const RoomShow = props => {
|
|||||||
/>
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab
|
<Tab label="synapseadmin.rooms.tabs.members" icon={<UserIcon />}>
|
||||||
label="synapseadmin.rooms.tabs.members"
|
|
||||||
icon={<UserIcon />}
|
|
||||||
path="members"
|
|
||||||
>
|
|
||||||
<ReferenceManyField
|
<ReferenceManyField
|
||||||
reference="room_members"
|
reference="room_members"
|
||||||
target="room_id"
|
target="room_id"
|
||||||
@@ -137,7 +123,7 @@ export const RoomShow = props => {
|
|||||||
>
|
>
|
||||||
<Datagrid
|
<Datagrid
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
rowClick={(id, resource, record) => "/users/" + id}
|
rowClick={(id, basePath, record) => "/users/" + id}
|
||||||
>
|
>
|
||||||
<TextField
|
<TextField
|
||||||
source="id"
|
source="id"
|
||||||
@@ -211,142 +197,94 @@ export const RoomShow = props => {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab
|
|
||||||
label={translate("resources.room_state.name", { smart_count: 2 })}
|
|
||||||
icon={<EventIcon />}
|
|
||||||
path="state"
|
|
||||||
>
|
|
||||||
<ReferenceManyField
|
|
||||||
reference="room_state"
|
|
||||||
target="room_id"
|
|
||||||
addLabel={false}
|
|
||||||
>
|
|
||||||
<Datagrid style={{ width: "100%" }}>
|
|
||||||
<TextField source="type" sortable={false} />
|
|
||||||
<DateField
|
|
||||||
source="origin_server_ts"
|
|
||||||
showTime
|
|
||||||
options={date_format}
|
|
||||||
sortable={false}
|
|
||||||
/>
|
|
||||||
<TextField source="content" sortable={false} />
|
|
||||||
<ReferenceField
|
|
||||||
source="sender"
|
|
||||||
reference="users"
|
|
||||||
sortable={false}
|
|
||||||
>
|
|
||||||
<TextField source="id" />
|
|
||||||
</ReferenceField>
|
|
||||||
</Datagrid>
|
|
||||||
</ReferenceManyField>
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab
|
|
||||||
label="resources.forward_extremities.name"
|
|
||||||
icon={<FastForwardIcon />}
|
|
||||||
path="forward_extremities"
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
fontFamily: "Roboto, Helvetica, Arial, sans-serif",
|
|
||||||
margin: "0.5em",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{translate("resources.rooms.helper.forward_extremities")}
|
|
||||||
</Box>
|
|
||||||
<ReferenceManyField
|
|
||||||
reference="forward_extremities"
|
|
||||||
target="room_id"
|
|
||||||
addLabel={false}
|
|
||||||
>
|
|
||||||
<Datagrid style={{ width: "100%" }}>
|
|
||||||
<TextField source="id" sortable={false} />
|
|
||||||
<DateField
|
|
||||||
source="received_ts"
|
|
||||||
showTime
|
|
||||||
options={date_format}
|
|
||||||
sortable={false}
|
|
||||||
/>
|
|
||||||
<NumberField source="depth" sortable={false} />
|
|
||||||
<TextField source="state_group" sortable={false} />
|
|
||||||
</Datagrid>
|
|
||||||
</ReferenceManyField>
|
|
||||||
</Tab>
|
|
||||||
</TabbedShowLayout>
|
</TabbedShowLayout>
|
||||||
</Show>
|
</Show>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const RoomBulkActionButtons = () => (
|
const RoomBulkActionButtons = props => (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<RoomDirectoryBulkSaveButton />
|
<BulkDeleteWithConfirmButton {...props} />
|
||||||
<RoomDirectoryBulkDeleteButton />
|
|
||||||
<BulkDeleteButton
|
|
||||||
confirmTitle="resources.rooms.action.erase.title"
|
|
||||||
confirmContent="resources.rooms.action.erase.content"
|
|
||||||
mutationMode="pessimistic"
|
|
||||||
/>
|
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|
||||||
const RoomFilter = props => (
|
const RoomFilter = ({ ...props }) => {
|
||||||
|
const translate = useTranslate();
|
||||||
|
return (
|
||||||
<Filter {...props}>
|
<Filter {...props}>
|
||||||
<SearchInput source="search_term" alwaysOn />
|
<SearchInput source="search_term" alwaysOn />
|
||||||
|
<Chip
|
||||||
|
label={translate("resources.rooms.fields.joined_local_members")}
|
||||||
|
source="joined_local_members"
|
||||||
|
defaultValue={false}
|
||||||
|
style={{ marginBottom: 8 }}
|
||||||
|
/>
|
||||||
|
<Chip
|
||||||
|
label={translate("resources.rooms.fields.state_events")}
|
||||||
|
source="state_events"
|
||||||
|
defaultValue={false}
|
||||||
|
style={{ marginBottom: 8 }}
|
||||||
|
/>
|
||||||
|
<Chip
|
||||||
|
label={translate("resources.rooms.fields.version")}
|
||||||
|
source="version"
|
||||||
|
defaultValue={false}
|
||||||
|
style={{ marginBottom: 8 }}
|
||||||
|
/>
|
||||||
|
<Chip
|
||||||
|
label={translate("resources.rooms.fields.federatable")}
|
||||||
|
source="federatable"
|
||||||
|
defaultValue={false}
|
||||||
|
style={{ marginBottom: 8 }}
|
||||||
|
/>
|
||||||
</Filter>
|
</Filter>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const RoomListActions = () => (
|
const FilterableRoomList = ({ ...props }) => {
|
||||||
<TopToolbar>
|
const filter = props.roomFilters;
|
||||||
<SelectColumnsButton />
|
const localMembersFilter =
|
||||||
<ExportButton />
|
filter && filter.joined_local_members ? true : false;
|
||||||
</TopToolbar>
|
const stateEventsFilter = filter && filter.state_events ? true : false;
|
||||||
);
|
const versionFilter = filter && filter.version ? true : false;
|
||||||
|
const federateableFilter = filter && filter.federatable ? true : false;
|
||||||
export const RoomList = () => {
|
const translate = useTranslate();
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<List
|
<List
|
||||||
|
{...props}
|
||||||
pagination={<RoomPagination />}
|
pagination={<RoomPagination />}
|
||||||
sort={{ field: "name", order: "ASC" }}
|
sort={{ field: "name", order: "ASC" }}
|
||||||
filters={<RoomFilter />}
|
filters={<RoomFilter />}
|
||||||
actions={<RoomListActions />}
|
bulkActionButtons={
|
||||||
|
<RoomBulkActionButtons
|
||||||
|
confirmTitle={translate("synapseadmin.rooms.delete.title")}
|
||||||
|
confirmContent={translate("synapseadmin.rooms.delete.message")}
|
||||||
|
/>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<DatagridConfigurable
|
<Datagrid rowClick="show">
|
||||||
rowClick="show"
|
<EncryptionField
|
||||||
bulkActionButtons={<RoomBulkActionButtons />}
|
|
||||||
omit={[
|
|
||||||
"joined_local_members",
|
|
||||||
"state_events",
|
|
||||||
"version",
|
|
||||||
"federatable",
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<BooleanField
|
|
||||||
source="is_encrypted"
|
source="is_encrypted"
|
||||||
sortBy="encryption"
|
sortBy="encryption"
|
||||||
TrueIcon={HttpsIcon}
|
|
||||||
FalseIcon={NoEncryptionIcon}
|
|
||||||
label={<HttpsIcon />}
|
label={<HttpsIcon />}
|
||||||
sx={{
|
|
||||||
[`& [data-testid="true"]`]: { color: theme.palette.success.main },
|
|
||||||
[`& [data-testid="false"]`]: { color: theme.palette.error.main },
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<FunctionField
|
|
||||||
source="name"
|
|
||||||
render={record =>
|
|
||||||
record["name"] || record["canonical_alias"] || record["id"]
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
<TextField source="name" />
|
||||||
<TextField source="joined_members" />
|
<TextField source="joined_members" />
|
||||||
<TextField source="joined_local_members" />
|
{localMembersFilter && <TextField source="joined_local_members" />}
|
||||||
<TextField source="state_events" />
|
{stateEventsFilter && <TextField source="state_events" />}
|
||||||
<TextField source="version" />
|
{versionFilter && <TextField source="version" />}
|
||||||
<BooleanField source="federatable" />
|
{federateableFilter && <BooleanField source="federatable" />}
|
||||||
<BooleanField source="public" />
|
<BooleanField source="public" />
|
||||||
</DatagridConfigurable>
|
</Datagrid>
|
||||||
</List>
|
</List>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return {
|
||||||
|
roomFilters: state.admin.resources.rooms.list.params.displayedFilters,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RoomList = connect(mapStateToProps)(FilterableRoomList);
|
||||||
|
|||||||
@@ -65,11 +65,9 @@ export const UserMediaStatsList = props => {
|
|||||||
filters={<UserMediaStatsFilter />}
|
filters={<UserMediaStatsFilter />}
|
||||||
pagination={<UserMediaStatsPagination />}
|
pagination={<UserMediaStatsPagination />}
|
||||||
sort={{ field: "media_length", order: "DESC" }}
|
sort={{ field: "media_length", order: "DESC" }}
|
||||||
>
|
|
||||||
<Datagrid
|
|
||||||
rowClick={(id, resource, record) => "/users/" + id + "/media"}
|
|
||||||
bulkActionButtons={false}
|
bulkActionButtons={false}
|
||||||
>
|
>
|
||||||
|
<Datagrid rowClick={(id, basePath, record) => "/users/" + id + "/media"}>
|
||||||
<TextField source="user_id" label="resources.users.fields.id" />
|
<TextField source="user_id" label="resources.users.fields.id" />
|
||||||
<TextField
|
<TextField
|
||||||
source="displayname"
|
source="displayname"
|
||||||
|
|||||||
+145
-159
@@ -1,13 +1,13 @@
|
|||||||
import React, { cloneElement, Fragment } from "react";
|
import React, { cloneElement, Fragment } from "react";
|
||||||
import AssignmentIndIcon from "@mui/icons-material/AssignmentInd";
|
import Avatar from "@material-ui/core/Avatar";
|
||||||
import ContactMailIcon from "@mui/icons-material/ContactMail";
|
import PersonPinIcon from "@material-ui/icons/PersonPin";
|
||||||
import DevicesIcon from "@mui/icons-material/Devices";
|
import ContactMailIcon from "@material-ui/icons/ContactMail";
|
||||||
import GetAppIcon from "@mui/icons-material/GetApp";
|
import DevicesIcon from "@material-ui/icons/Devices";
|
||||||
import NotificationsIcon from "@mui/icons-material/Notifications";
|
import GetAppIcon from "@material-ui/icons/GetApp";
|
||||||
import PermMediaIcon from "@mui/icons-material/PermMedia";
|
import SettingsInputComponentIcon from "@material-ui/icons/SettingsInputComponent";
|
||||||
import PersonPinIcon from "@mui/icons-material/PersonPin";
|
import NotificationsIcon from "@material-ui/icons/Notifications";
|
||||||
import SettingsInputComponentIcon from "@mui/icons-material/SettingsInputComponent";
|
import PermMediaIcon from "@material-ui/icons/PermMedia";
|
||||||
import ViewListIcon from "@mui/icons-material/ViewList";
|
import ViewListIcon from "@material-ui/icons/ViewList";
|
||||||
import {
|
import {
|
||||||
ArrayInput,
|
ArrayInput,
|
||||||
ArrayField,
|
ArrayField,
|
||||||
@@ -35,10 +35,8 @@ import {
|
|||||||
BulkDeleteButton,
|
BulkDeleteButton,
|
||||||
DeleteButton,
|
DeleteButton,
|
||||||
SaveButton,
|
SaveButton,
|
||||||
maxLength,
|
|
||||||
regex,
|
regex,
|
||||||
required,
|
useRedirect,
|
||||||
useRecordContext,
|
|
||||||
useTranslate,
|
useTranslate,
|
||||||
Pagination,
|
Pagination,
|
||||||
CreateButton,
|
CreateButton,
|
||||||
@@ -47,31 +45,28 @@ import {
|
|||||||
sanitizeListRestProps,
|
sanitizeListRestProps,
|
||||||
NumberField,
|
NumberField,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import AvatarField from "./AvatarField";
|
|
||||||
import { ServerNoticeButton, ServerNoticeBulkButton } from "./ServerNotices";
|
import { ServerNoticeButton, ServerNoticeBulkButton } from "./ServerNotices";
|
||||||
import { DeviceRemoveButton } from "./devices";
|
import { DeviceRemoveButton } from "./devices";
|
||||||
import { ProtectMediaButton, QuarantineMediaButton } from "./media";
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
|
|
||||||
const choices_medium = [
|
const redirect = (basePath, id, data) => {
|
||||||
{ id: "email", name: "resources.users.email" },
|
return {
|
||||||
{ id: "msisdn", name: "resources.users.msisdn" },
|
pathname: "/import_users",
|
||||||
];
|
};
|
||||||
|
|
||||||
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 useStyles = makeStyles({
|
||||||
|
small: {
|
||||||
|
height: "40px",
|
||||||
|
width: "40px",
|
||||||
|
},
|
||||||
|
large: {
|
||||||
|
height: "120px",
|
||||||
|
width: "120px",
|
||||||
|
float: "right",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const UserListActions = ({
|
const UserListActions = ({
|
||||||
currentSort,
|
currentSort,
|
||||||
className,
|
className,
|
||||||
@@ -82,6 +77,7 @@ const UserListActions = ({
|
|||||||
filterValues,
|
filterValues,
|
||||||
permanentFilter,
|
permanentFilter,
|
||||||
hasCreate, // you can hide CreateButton if hasCreate = false
|
hasCreate, // you can hide CreateButton if hasCreate = false
|
||||||
|
basePath,
|
||||||
selectedIds,
|
selectedIds,
|
||||||
onUnselectItems,
|
onUnselectItems,
|
||||||
showFilter,
|
showFilter,
|
||||||
@@ -89,6 +85,7 @@ const UserListActions = ({
|
|||||||
total,
|
total,
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
|
const redirectTo = useRedirect();
|
||||||
return (
|
return (
|
||||||
<TopToolbar className={className} {...sanitizeListRestProps(rest)}>
|
<TopToolbar className={className} {...sanitizeListRestProps(rest)}>
|
||||||
{filters &&
|
{filters &&
|
||||||
@@ -99,7 +96,7 @@ const UserListActions = ({
|
|||||||
filterValues,
|
filterValues,
|
||||||
context: "button",
|
context: "button",
|
||||||
})}
|
})}
|
||||||
<CreateButton />
|
<CreateButton basePath={basePath} />
|
||||||
<ExportButton
|
<ExportButton
|
||||||
disabled={total === 0}
|
disabled={total === 0}
|
||||||
resource={resource}
|
resource={resource}
|
||||||
@@ -109,8 +106,13 @@ const UserListActions = ({
|
|||||||
maxResults={maxResults}
|
maxResults={maxResults}
|
||||||
/>
|
/>
|
||||||
{/* Add your custom actions */}
|
{/* Add your custom actions */}
|
||||||
<Button component={Link} to="/import_users" label="CSV Import">
|
<Button
|
||||||
<GetAppIcon sx={{ transform: "rotate(180deg)", fontSize: "20px" }} />
|
onClick={() => {
|
||||||
|
redirectTo(redirect);
|
||||||
|
}}
|
||||||
|
label="CSV Import"
|
||||||
|
>
|
||||||
|
<GetAppIcon style={{ transform: "rotate(180deg)", fontSize: "20" }} />
|
||||||
</Button>
|
</Button>
|
||||||
</TopToolbar>
|
</TopToolbar>
|
||||||
);
|
);
|
||||||
@@ -137,61 +139,56 @@ const UserFilter = props => (
|
|||||||
</Filter>
|
</Filter>
|
||||||
);
|
);
|
||||||
|
|
||||||
const UserBulkActionButtons = props => (
|
const UserBulkActionButtons = props => {
|
||||||
|
const translate = useTranslate();
|
||||||
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<ServerNoticeBulkButton {...props} />
|
<ServerNoticeBulkButton {...props} />
|
||||||
<BulkDeleteButton
|
<BulkDeleteButton
|
||||||
{...props}
|
{...props}
|
||||||
label="resources.users.action.erase"
|
label="resources.users.action.erase"
|
||||||
confirmTitle="resources.users.helper.erase"
|
title={translate("resources.users.helper.erase")}
|
||||||
mutationMode="pessimistic"
|
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AvatarField = ({ source, className, record = {} }) => (
|
||||||
|
<Avatar src={record[source]} className={className} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export const UserList = props => {
|
export const UserList = props => {
|
||||||
|
const classes = useStyles();
|
||||||
return (
|
return (
|
||||||
<List
|
<List
|
||||||
{...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 />}
|
||||||
pagination={<UserPagination />}
|
pagination={<UserPagination />}
|
||||||
>
|
>
|
||||||
<Datagrid rowClick="edit" bulkActionButtons={<UserBulkActionButtons />}>
|
<Datagrid rowClick="edit">
|
||||||
<AvatarField
|
<AvatarField
|
||||||
source="avatar_src"
|
source="avatar_src"
|
||||||
sx={{ height: "40px", width: "40px" }}
|
sortable={false}
|
||||||
sortBy="avatar_url"
|
className={classes.small}
|
||||||
/>
|
|
||||||
<TextField source="id" sortBy="name" />
|
|
||||||
<TextField source="displayname" />
|
|
||||||
<BooleanField source="is_guest" />
|
|
||||||
<BooleanField source="admin" />
|
|
||||||
<BooleanField source="deactivated" />
|
|
||||||
<DateField
|
|
||||||
source="creation_ts"
|
|
||||||
label="resources.users.fields.creation_ts_ms"
|
|
||||||
showTime
|
|
||||||
options={date_format}
|
|
||||||
/>
|
/>
|
||||||
|
<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} />
|
||||||
</Datagrid>
|
</Datagrid>
|
||||||
</List>
|
</List>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://matrix.org/docs/spec/appendices#user-identifiers
|
// https://matrix.org/docs/spec/appendices#user-identifiers
|
||||||
// here only local part of user_id
|
const validateUser = regex(
|
||||||
// maxLength = 255 - "@" - ":" - localStorage.getItem("home_server").length
|
/^@[a-z0-9._=\-/]+:.*/,
|
||||||
// localStorage.getItem("home_server").length is not valid here
|
"synapseadmin.users.invalid_user_id"
|
||||||
const validateUser = [
|
);
|
||||||
required(),
|
|
||||||
maxLength(253),
|
|
||||||
regex(/^[a-z0-9._=\-/]+$/, "synapseadmin.users.invalid_user_id"),
|
|
||||||
];
|
|
||||||
|
|
||||||
const validateAddress = [required(), maxLength(255)];
|
|
||||||
|
|
||||||
export function generateRandomUser() {
|
export function generateRandomUser() {
|
||||||
const homeserver = localStorage.getItem("home_server");
|
const homeserver = localStorage.getItem("home_server");
|
||||||
@@ -234,31 +231,17 @@ export function generateRandomUser() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserEditToolbar = props => (
|
const UserEditToolbar = props => {
|
||||||
<Toolbar {...props}>
|
|
||||||
<SaveButton disabled={props.pristine} />
|
|
||||||
</Toolbar>
|
|
||||||
);
|
|
||||||
|
|
||||||
const UserEditActions = ({ data }) => {
|
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
var userStatus = "";
|
|
||||||
if (data) {
|
|
||||||
userStatus = data.deactivated;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TopToolbar>
|
<Toolbar {...props}>
|
||||||
{!userStatus && <ServerNoticeButton record={data} />}
|
<SaveButton submitOnEnter={true} />
|
||||||
<DeleteButton
|
<DeleteButton
|
||||||
record={data}
|
|
||||||
label="resources.users.action.erase"
|
label="resources.users.action.erase"
|
||||||
confirmTitle={translate("resources.users.helper.erase", {
|
title={translate("resources.users.helper.erase")}
|
||||||
smart_count: 1,
|
|
||||||
})}
|
|
||||||
mutationMode="pessimistic"
|
|
||||||
/>
|
/>
|
||||||
</TopToolbar>
|
<ServerNoticeButton />
|
||||||
|
</Toolbar>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -266,45 +249,26 @@ export const UserCreate = props => (
|
|||||||
<Create {...props}>
|
<Create {...props}>
|
||||||
<SimpleForm>
|
<SimpleForm>
|
||||||
<TextInput source="id" autoComplete="off" validate={validateUser} />
|
<TextInput source="id" autoComplete="off" validate={validateUser} />
|
||||||
<TextInput source="displayname" validate={maxLength(256)} />
|
<TextInput source="displayname" />
|
||||||
<PasswordInput
|
<PasswordInput source="password" autoComplete="new-password" />
|
||||||
source="password"
|
|
||||||
autoComplete="new-password"
|
|
||||||
validate={maxLength(512)}
|
|
||||||
/>
|
|
||||||
<SelectInput
|
|
||||||
source="user_type"
|
|
||||||
choices={choices_type}
|
|
||||||
translateChoice={false}
|
|
||||||
resettable
|
|
||||||
/>
|
|
||||||
<BooleanInput source="admin" />
|
<BooleanInput source="admin" />
|
||||||
<ArrayInput source="threepids">
|
<ArrayInput source="threepids">
|
||||||
<SimpleFormIterator disableReordering>
|
<SimpleFormIterator>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
source="medium"
|
source="medium"
|
||||||
choices={choices_medium}
|
choices={[
|
||||||
validate={required()}
|
{ id: "email", name: "resources.users.email" },
|
||||||
/>
|
{ id: "msisdn", name: "resources.users.msisdn" },
|
||||||
<TextInput source="address" validate={validateAddress} />
|
]}
|
||||||
</SimpleFormIterator>
|
|
||||||
</ArrayInput>
|
|
||||||
<ArrayInput source="external_ids" label="synapseadmin.users.tabs.sso">
|
|
||||||
<SimpleFormIterator disableReordering>
|
|
||||||
<TextInput source="auth_provider" validate={required()} />
|
|
||||||
<TextInput
|
|
||||||
source="external_id"
|
|
||||||
label="resources.users.fields.id"
|
|
||||||
validate={required()}
|
|
||||||
/>
|
/>
|
||||||
|
<TextInput source="address" />
|
||||||
</SimpleFormIterator>
|
</SimpleFormIterator>
|
||||||
</ArrayInput>
|
</ArrayInput>
|
||||||
</SimpleForm>
|
</SimpleForm>
|
||||||
</Create>
|
</Create>
|
||||||
);
|
);
|
||||||
|
|
||||||
const UserTitle = props => {
|
const UserTitle = ({ record }) => {
|
||||||
const record = useRecordContext();
|
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
@@ -315,11 +279,11 @@ const UserTitle = props => {
|
|||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const UserEdit = props => {
|
export const UserEdit = props => {
|
||||||
|
const classes = useStyles();
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
return (
|
return (
|
||||||
<Edit {...props} title={<UserTitle />} actions={<UserEditActions />}>
|
<Edit {...props} title={<UserTitle />}>
|
||||||
<TabbedForm toolbar={<UserEditToolbar />}>
|
<TabbedForm toolbar={<UserEditToolbar />}>
|
||||||
<FormTab
|
<FormTab
|
||||||
label={translate("resources.users.name", { smart_count: 1 })}
|
label={translate("resources.users.name", { smart_count: 1 })}
|
||||||
@@ -328,27 +292,28 @@ export const UserEdit = props => {
|
|||||||
<AvatarField
|
<AvatarField
|
||||||
source="avatar_src"
|
source="avatar_src"
|
||||||
sortable={false}
|
sortable={false}
|
||||||
sx={{ height: "120px", width: "120px", float: "right" }}
|
className={classes.large}
|
||||||
/>
|
/>
|
||||||
<TextInput source="id" disabled />
|
<TextInput source="id" disabled />
|
||||||
<TextInput source="displayname" />
|
<TextInput source="displayname" />
|
||||||
<PasswordInput
|
<PasswordInput source="password" autoComplete="new-password" />
|
||||||
source="password"
|
|
||||||
autoComplete="new-password"
|
|
||||||
helperText="resources.users.helper.password"
|
|
||||||
/>
|
|
||||||
<SelectInput
|
|
||||||
source="user_type"
|
|
||||||
choices={choices_type}
|
|
||||||
translateChoice={false}
|
|
||||||
resettable
|
|
||||||
/>
|
|
||||||
<BooleanInput source="admin" />
|
<BooleanInput source="admin" />
|
||||||
<BooleanInput
|
<BooleanInput
|
||||||
source="deactivated"
|
source="deactivated"
|
||||||
helperText="resources.users.helper.deactivate"
|
helperText="resources.users.helper.deactivate"
|
||||||
/>
|
/>
|
||||||
<DateField source="creation_ts_ms" showTime options={date_format} />
|
<DateField
|
||||||
|
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>
|
||||||
|
|
||||||
@@ -358,26 +323,15 @@ export const UserEdit = props => {
|
|||||||
path="threepid"
|
path="threepid"
|
||||||
>
|
>
|
||||||
<ArrayInput source="threepids">
|
<ArrayInput source="threepids">
|
||||||
<SimpleFormIterator disableReordering>
|
<SimpleFormIterator>
|
||||||
<SelectInput source="medium" choices={choices_medium} />
|
<SelectInput
|
||||||
<TextInput source="address" />
|
source="medium"
|
||||||
</SimpleFormIterator>
|
choices={[
|
||||||
</ArrayInput>
|
{ id: "email", name: "resources.users.email" },
|
||||||
</FormTab>
|
{ id: "msisdn", name: "resources.users.msisdn" },
|
||||||
|
]}
|
||||||
<FormTab
|
|
||||||
label="synapseadmin.users.tabs.sso"
|
|
||||||
icon={<AssignmentIndIcon />}
|
|
||||||
path="sso"
|
|
||||||
>
|
|
||||||
<ArrayInput source="external_ids" label={false}>
|
|
||||||
<SimpleFormIterator disableReordering>
|
|
||||||
<TextInput source="auth_provider" validate={required()} />
|
|
||||||
<TextInput
|
|
||||||
source="external_id"
|
|
||||||
label="resources.users.fields.id"
|
|
||||||
validate={required()}
|
|
||||||
/>
|
/>
|
||||||
|
<TextInput source="address" />
|
||||||
</SimpleFormIterator>
|
</SimpleFormIterator>
|
||||||
</ArrayInput>
|
</ArrayInput>
|
||||||
</FormTab>
|
</FormTab>
|
||||||
@@ -399,7 +353,14 @@ export const UserEdit = props => {
|
|||||||
<DateField
|
<DateField
|
||||||
source="last_seen_ts"
|
source="last_seen_ts"
|
||||||
showTime
|
showTime
|
||||||
options={date_format}
|
options={{
|
||||||
|
year: "numeric",
|
||||||
|
month: "2-digit",
|
||||||
|
day: "2-digit",
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
second: "2-digit",
|
||||||
|
}}
|
||||||
sortable={false}
|
sortable={false}
|
||||||
/>
|
/>
|
||||||
<DeviceRemoveButton />
|
<DeviceRemoveButton />
|
||||||
@@ -427,7 +388,14 @@ export const UserEdit = props => {
|
|||||||
<DateField
|
<DateField
|
||||||
source="last_seen"
|
source="last_seen"
|
||||||
showTime
|
showTime
|
||||||
options={date_format}
|
options={{
|
||||||
|
year: "numeric",
|
||||||
|
month: "2-digit",
|
||||||
|
day: "2-digit",
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
second: "2-digit",
|
||||||
|
}}
|
||||||
sortable={false}
|
sortable={false}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
@@ -451,23 +419,41 @@ 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 source="created_ts" showTime options={date_format} />
|
<DateField
|
||||||
|
source="created_ts"
|
||||||
|
showTime
|
||||||
|
options={{
|
||||||
|
year: "numeric",
|
||||||
|
month: "2-digit",
|
||||||
|
day: "2-digit",
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
second: "2-digit",
|
||||||
|
}}
|
||||||
|
sortable={false}
|
||||||
|
/>
|
||||||
<DateField
|
<DateField
|
||||||
source="last_access_ts"
|
source="last_access_ts"
|
||||||
showTime
|
showTime
|
||||||
options={date_format}
|
options={{
|
||||||
|
year: "numeric",
|
||||||
|
month: "2-digit",
|
||||||
|
day: "2-digit",
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
second: "2-digit",
|
||||||
|
}}
|
||||||
|
sortable={false}
|
||||||
/>
|
/>
|
||||||
<TextField source="media_id" />
|
<TextField source="media_id" sortable={false} />
|
||||||
<NumberField source="media_length" />
|
<NumberField source="media_length" sortable={false} />
|
||||||
<TextField source="media_type" />
|
<TextField source="media_type" sortable={false} />
|
||||||
<TextField source="upload_name" />
|
<TextField source="upload_name" sortable={false} />
|
||||||
<TextField source="quarantined_by" />
|
<TextField source="quarantined_by" sortable={false} />
|
||||||
<QuarantineMediaButton label="resources.quarantine_media.action.name" />
|
<BooleanField source="safe_from_quarantine" sortable={false} />
|
||||||
<ProtectMediaButton label="resources.users_media.fields.safe_from_quarantine" />
|
<DeleteButton undoable={false} redirect={false} />
|
||||||
<DeleteButton mutationMode="pessimistic" redirect={false} />
|
|
||||||
</Datagrid>
|
</Datagrid>
|
||||||
</ReferenceManyField>
|
</ReferenceManyField>
|
||||||
</FormTab>
|
</FormTab>
|
||||||
@@ -484,7 +470,7 @@ export const UserEdit = props => {
|
|||||||
>
|
>
|
||||||
<Datagrid
|
<Datagrid
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
rowClick={(id, resource, record) => "/rooms/" + id + "/show"}
|
rowClick={(id, basePath, record) => "/rooms/" + id + "/show"}
|
||||||
>
|
>
|
||||||
<TextField
|
<TextField
|
||||||
source="id"
|
source="id"
|
||||||
|
|||||||
+17
-112
@@ -1,6 +1,6 @@
|
|||||||
import germanMessages from "ra-language-german";
|
import germanMessages from "ra-language-german";
|
||||||
|
|
||||||
const de = {
|
export default {
|
||||||
...germanMessages,
|
...germanMessages,
|
||||||
synapseadmin: {
|
synapseadmin: {
|
||||||
auth: {
|
auth: {
|
||||||
@@ -10,11 +10,10 @@ 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:
|
||||||
tabs: { sso: "SSO" },
|
"Muss eine vollständige Matrix Benutzer-ID sein, z.B. @benutzer_id:homeserver",
|
||||||
},
|
},
|
||||||
rooms: {
|
rooms: {
|
||||||
details: "Raumdetails",
|
details: "Raumdetails",
|
||||||
@@ -24,6 +23,11 @@ const de = {
|
|||||||
detail: "Details",
|
detail: "Details",
|
||||||
permission: "Berechtigungen",
|
permission: "Berechtigungen",
|
||||||
},
|
},
|
||||||
|
delete: {
|
||||||
|
title: "Raum löschen",
|
||||||
|
message:
|
||||||
|
"Sind Sie sicher dass Sie den Raum löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden. Alle Nachrichten und Medien, die der Raum beinhaltet werden vom Server gelöscht!",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
reports: { tabs: { basic: "Allgemein", detail: "Details" } },
|
reports: { tabs: { basic: "Allgemein", detail: "Details" } },
|
||||||
},
|
},
|
||||||
@@ -97,6 +101,7 @@ 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",
|
||||||
@@ -120,12 +125,8 @@ const de = {
|
|||||||
address: "Adresse",
|
address: "Adresse",
|
||||||
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",
|
|
||||||
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",
|
||||||
@@ -142,23 +143,16 @@ const de = {
|
|||||||
canonical_alias: "Alias",
|
canonical_alias: "Alias",
|
||||||
joined_members: "Mitglieder",
|
joined_members: "Mitglieder",
|
||||||
joined_local_members: "Lokale Mitglieder",
|
joined_local_members: "Lokale Mitglieder",
|
||||||
joined_local_devices: "Lokale Endgeräte",
|
state_events: "Ereignisse",
|
||||||
state_events: "Zustandsereignisse / Komplexität",
|
|
||||||
version: "Version",
|
version: "Version",
|
||||||
is_encrypted: "Verschlüsselt",
|
is_encrypted: "Verschlüsselt",
|
||||||
encryption: "Verschlüsselungs-Algorithmus",
|
encryption: "Verschlüsselungs-Algorithmus",
|
||||||
federatable: "Föderierbar",
|
federatable: "Föderierbar",
|
||||||
public: "Sichtbar im Raumverzeichnis",
|
public: "Öffentlich",
|
||||||
creator: "Ersteller",
|
creator: "Ersteller",
|
||||||
join_rules: "Beitrittsregeln",
|
join_rules: "Beitrittsregeln",
|
||||||
guest_access: "Gastzugriff",
|
guest_access: "Gastzugriff",
|
||||||
history_visibility: "Historie-Sichtbarkeit",
|
history_visibility: "Historie-Sichtbarkeit",
|
||||||
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: {
|
enums: {
|
||||||
join_rules: {
|
join_rules: {
|
||||||
@@ -179,13 +173,6 @@ const de = {
|
|||||||
},
|
},
|
||||||
unencrypted: "Nicht verschlüsselt",
|
unencrypted: "Nicht verschlüsselt",
|
||||||
},
|
},
|
||||||
action: {
|
|
||||||
erase: {
|
|
||||||
title: "Raum löschen",
|
|
||||||
content:
|
|
||||||
"Sind Sie sicher dass Sie den Raum löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden. Alle Nachrichten und Medien, die der Raum beinhaltet werden vom Server gelöscht!",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
reports: {
|
reports: {
|
||||||
name: "Ereignisbericht |||| Ereignisberichte",
|
name: "Ereignisbericht |||| Ereignisberichte",
|
||||||
@@ -244,7 +231,7 @@ const de = {
|
|||||||
media_type: "Typ",
|
media_type: "Typ",
|
||||||
upload_name: "Dateiname",
|
upload_name: "Dateiname",
|
||||||
quarantined_by: "Zur Quarantäne hinzugefügt",
|
quarantined_by: "Zur Quarantäne hinzugefügt",
|
||||||
safe_from_quarantine: "Schutz vor Quarantäne",
|
safe_from_quarantine: "Geschützt vor Quarantäne",
|
||||||
created_ts: "Erstellt",
|
created_ts: "Erstellt",
|
||||||
last_access_ts: "Letzter Zugriff",
|
last_access_ts: "Letzter Zugriff",
|
||||||
},
|
},
|
||||||
@@ -262,26 +249,8 @@ const de = {
|
|||||||
send_failure: "Beim Versenden ist ein Fehler aufgetreten.",
|
send_failure: "Beim Versenden ist ein Fehler aufgetreten.",
|
||||||
},
|
},
|
||||||
helper: {
|
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: {
|
pushers: {
|
||||||
@@ -310,7 +279,8 @@ const de = {
|
|||||||
send_failure: "Beim Versenden ist ein Fehler aufgetreten.",
|
send_failure: "Beim Versenden ist ein Fehler aufgetreten.",
|
||||||
},
|
},
|
||||||
helper: {
|
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: {
|
user_media_statistics: {
|
||||||
@@ -320,66 +290,6 @@ const de = {
|
|||||||
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: {
|
|
||||||
name: "Zustandsereignisse",
|
|
||||||
fields: {
|
|
||||||
type: "Typ",
|
|
||||||
content: "Inhalt",
|
|
||||||
origin_server_ts: "Sendezeit",
|
|
||||||
sender: "Absender",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
room_directory: {
|
|
||||||
name: "Raumverzeichnis",
|
|
||||||
fields: {
|
|
||||||
world_readable: "Gastbenutzer dürfen ohne Beitritt lesen",
|
|
||||||
guest_can_join: "Gastbenutzer dürfen beitreten",
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
title:
|
|
||||||
"Raum aus Verzeichnis löschen |||| %{smart_count} Räume aus Verzeichnis löschen",
|
|
||||||
content:
|
|
||||||
"Möchten Sie den Raum wirklich aus dem Raumverzeichnis löschen? |||| Möchten Sie die %{smart_count} Räume wirklich aus dem Raumverzeichnis löschen?",
|
|
||||||
erase: "Lösche aus Verzeichnis",
|
|
||||||
create: "Eintragen ins Verzeichnis",
|
|
||||||
send_success: "Raum erfolgreich eingetragen.",
|
|
||||||
send_failure: "Beim Entfernen ist ein Fehler aufgetreten.",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
destinations: {
|
|
||||||
name: "Föderation",
|
|
||||||
fields: {
|
|
||||||
destination: "Ziel",
|
|
||||||
failure_ts: "Fehlerzeitpunkt",
|
|
||||||
retry_last_ts: "Letzter Wiederholungsversuch",
|
|
||||||
retry_interval: "Wiederholungsintervall",
|
|
||||||
last_successful_stream_ordering: "letzte erfogreicher Stream",
|
|
||||||
stream_ordering: "Stream",
|
|
||||||
},
|
|
||||||
action: { reconnect: "Neu verbinden" },
|
|
||||||
},
|
|
||||||
registration_tokens: {
|
|
||||||
name: "Registrierungstoken",
|
|
||||||
fields: {
|
|
||||||
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,
|
||||||
@@ -400,7 +310,7 @@ const de = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
notification: {
|
notification: {
|
||||||
...germanMessages.ra.notification,
|
...germanMessages.ra.notifiaction,
|
||||||
logged_out: "Abgemeldet",
|
logged_out: "Abgemeldet",
|
||||||
},
|
},
|
||||||
page: {
|
page: {
|
||||||
@@ -408,10 +318,5 @@ const de = {
|
|||||||
empty: "Keine Einträge vorhanden",
|
empty: "Keine Einträge vorhanden",
|
||||||
invite: "",
|
invite: "",
|
||||||
},
|
},
|
||||||
navigation: {
|
|
||||||
...germanMessages.ra.navigation,
|
|
||||||
skip_nav: "Zum Inhalt springen",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
export default de;
|
|
||||||
|
|||||||
+17
-107
@@ -1,6 +1,6 @@
|
|||||||
import englishMessages from "ra-language-english";
|
import englishMessages from "ra-language-english";
|
||||||
|
|
||||||
const en = {
|
export default {
|
||||||
...englishMessages,
|
...englishMessages,
|
||||||
synapseadmin: {
|
synapseadmin: {
|
||||||
auth: {
|
auth: {
|
||||||
@@ -10,11 +10,10 @@ 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:
|
||||||
tabs: { sso: "SSO" },
|
"Must be a fully qualified Matrix user-id, e.g. @user_id:homeserver",
|
||||||
},
|
},
|
||||||
rooms: {
|
rooms: {
|
||||||
tabs: {
|
tabs: {
|
||||||
@@ -23,6 +22,11 @@ const en = {
|
|||||||
detail: "Details",
|
detail: "Details",
|
||||||
permission: "Permissions",
|
permission: "Permissions",
|
||||||
},
|
},
|
||||||
|
delete: {
|
||||||
|
title: "Delete room",
|
||||||
|
message:
|
||||||
|
"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: { tabs: { basic: "Basic", detail: "Details" } },
|
reports: { tabs: { basic: "Basic", detail: "Details" } },
|
||||||
},
|
},
|
||||||
@@ -96,6 +100,7 @@ const en = {
|
|||||||
},
|
},
|
||||||
resources: {
|
resources: {
|
||||||
users: {
|
users: {
|
||||||
|
backtolist: "Back to list",
|
||||||
name: "User |||| Users",
|
name: "User |||| Users",
|
||||||
email: "Email",
|
email: "Email",
|
||||||
msisdn: "Phone",
|
msisdn: "Phone",
|
||||||
@@ -119,11 +124,8 @@ const en = {
|
|||||||
address: "Address",
|
address: "Address",
|
||||||
creation_ts_ms: "Creation timestamp",
|
creation_ts_ms: "Creation timestamp",
|
||||||
consent_version: "Consent version",
|
consent_version: "Consent version",
|
||||||
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",
|
||||||
},
|
},
|
||||||
@@ -139,23 +141,16 @@ const en = {
|
|||||||
canonical_alias: "Alias",
|
canonical_alias: "Alias",
|
||||||
joined_members: "Members",
|
joined_members: "Members",
|
||||||
joined_local_members: "Local members",
|
joined_local_members: "Local members",
|
||||||
joined_local_devices: "Local devices",
|
state_events: "State events",
|
||||||
state_events: "State events / Complexity",
|
|
||||||
version: "Version",
|
version: "Version",
|
||||||
is_encrypted: "Encrypted",
|
is_encrypted: "Encrypted",
|
||||||
encryption: "Encryption",
|
encryption: "Encryption",
|
||||||
federatable: "Federatable",
|
federatable: "Federatable",
|
||||||
public: "Visible in room directory",
|
public: "Public",
|
||||||
creator: "Creator",
|
creator: "Creator",
|
||||||
join_rules: "Join rules",
|
join_rules: "Join rules",
|
||||||
guest_access: "Guest access",
|
guest_access: "Guest access",
|
||||||
history_visibility: "History visibility",
|
history_visibility: "History visibility",
|
||||||
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: {
|
enums: {
|
||||||
join_rules: {
|
join_rules: {
|
||||||
@@ -176,13 +171,6 @@ const en = {
|
|||||||
},
|
},
|
||||||
unencrypted: "Unencrypted",
|
unencrypted: "Unencrypted",
|
||||||
},
|
},
|
||||||
action: {
|
|
||||||
erase: {
|
|
||||||
title: "Delete room",
|
|
||||||
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: {
|
||||||
name: "Reported event |||| Reported events",
|
name: "Reported event |||| Reported events",
|
||||||
@@ -197,7 +185,7 @@ const en = {
|
|||||||
event_json: {
|
event_json: {
|
||||||
origin: "origin server",
|
origin: "origin server",
|
||||||
origin_server_ts: "time of send",
|
origin_server_ts: "time of send",
|
||||||
type: "event type",
|
type: "event typ",
|
||||||
content: {
|
content: {
|
||||||
msgtype: "content type",
|
msgtype: "content type",
|
||||||
body: "content",
|
body: "content",
|
||||||
@@ -237,7 +225,7 @@ const en = {
|
|||||||
name: "Media",
|
name: "Media",
|
||||||
fields: {
|
fields: {
|
||||||
media_id: "Media ID",
|
media_id: "Media ID",
|
||||||
media_length: "File Size (in Bytes)",
|
media_length: "Lenght",
|
||||||
media_type: "Type",
|
media_type: "Type",
|
||||||
upload_name: "File name",
|
upload_name: "File name",
|
||||||
quarantined_by: "Quarantined by",
|
quarantined_by: "Quarantined by",
|
||||||
@@ -259,26 +247,8 @@ const en = {
|
|||||||
send_failure: "An error has occurred.",
|
send_failure: "An error has occurred.",
|
||||||
},
|
},
|
||||||
helper: {
|
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: {
|
pushers: {
|
||||||
@@ -307,7 +277,8 @@ const en = {
|
|||||||
send_failure: "An error has occurred.",
|
send_failure: "An error has occurred.",
|
||||||
},
|
},
|
||||||
helper: {
|
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: {
|
user_media_statistics: {
|
||||||
@@ -317,66 +288,5 @@ const en = {
|
|||||||
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: {
|
|
||||||
name: "State events",
|
|
||||||
fields: {
|
|
||||||
type: "Type",
|
|
||||||
content: "Content",
|
|
||||||
origin_server_ts: "time of send",
|
|
||||||
sender: "Sender",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
room_directory: {
|
|
||||||
name: "Room directory",
|
|
||||||
fields: {
|
|
||||||
world_readable: "guest users may view without joining",
|
|
||||||
guest_can_join: "guest users may join",
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
title:
|
|
||||||
"Delete room from directory |||| Delete %{smart_count} rooms from directory",
|
|
||||||
content:
|
|
||||||
"Are you sure you want to remove this room from directory? |||| Are you sure you want to remove these %{smart_count} rooms from directory?",
|
|
||||||
erase: "Delete from room directory",
|
|
||||||
create: "Publish in room directory",
|
|
||||||
send_success: "Room successfully published.",
|
|
||||||
send_failure: "An error has occurred.",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
destinations: {
|
|
||||||
name: "Federation",
|
|
||||||
fields: {
|
|
||||||
destination: "Destination",
|
|
||||||
failure_ts: "Failure timestamp",
|
|
||||||
retry_last_ts: "Last retry timestamp",
|
|
||||||
retry_interval: "Retry interval",
|
|
||||||
last_successful_stream_ordering: "Last successful stream",
|
|
||||||
stream_ordering: "Stream",
|
|
||||||
},
|
|
||||||
action: { reconnect: "Reconnect" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
registration_tokens: {
|
|
||||||
name: "Registration tokens",
|
|
||||||
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;
|
|
||||||
|
|||||||
-377
@@ -1,377 +0,0 @@
|
|||||||
import frenchMessages from "ra-language-french";
|
|
||||||
|
|
||||||
const fr = {
|
|
||||||
...frenchMessages,
|
|
||||||
synapseadmin: {
|
|
||||||
auth: {
|
|
||||||
base_url: "URL du serveur d’accueil",
|
|
||||||
welcome: "Bienvenue sur Synapse-admin",
|
|
||||||
server_version: "Version du serveur Synapse",
|
|
||||||
username_error:
|
|
||||||
"Veuillez entrer un nom d'utilisateur complet : « @utilisateur:domaine »",
|
|
||||||
protocol_error: "L'URL doit commencer par « http:// » ou « https:// »",
|
|
||||||
url_error: "L'URL du serveur Matrix n'est pas valide",
|
|
||||||
sso_sign_in: "Se connecter avec l’authentification unique",
|
|
||||||
},
|
|
||||||
users: {
|
|
||||||
invalid_user_id:
|
|
||||||
"Partie locale d'un identifiant utilisateur Matrix sans le nom du serveur d’accueil.",
|
|
||||||
tabs: { sso: "Authentification unique" },
|
|
||||||
},
|
|
||||||
rooms: {
|
|
||||||
tabs: {
|
|
||||||
basic: "Informations de base",
|
|
||||||
members: "Membres",
|
|
||||||
detail: "Détails",
|
|
||||||
permission: "Permissions",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
reports: { tabs: { basic: "Informations de base", detail: "Détails" } },
|
|
||||||
},
|
|
||||||
import_users: {
|
|
||||||
error: {
|
|
||||||
at_entry: "Pour l'entrée %{entry} : %{message}",
|
|
||||||
error: "Erreur",
|
|
||||||
required_field: "Le champ requis « %{field} » est manquant",
|
|
||||||
invalid_value:
|
|
||||||
"Valeur non valide à la ligne %{row}. Le champ « %{field} » ne peut être que « true » ou « false »",
|
|
||||||
unreasonably_big:
|
|
||||||
"Refus de charger un fichier trop volumineux de %{size} mégaoctets",
|
|
||||||
already_in_progress: "Un import est déjà en cours",
|
|
||||||
id_exits: "L'identifiant %{id} déjà présent",
|
|
||||||
},
|
|
||||||
title: "Importer des utilisateurs à partir d'un fichier CSV",
|
|
||||||
goToPdf: "Voir le PDF",
|
|
||||||
cards: {
|
|
||||||
importstats: {
|
|
||||||
header: "Importer des utilisateurs",
|
|
||||||
users_total:
|
|
||||||
"%{smart_count} utilisateur dans le fichier CSV |||| %{smart_count} utilisateurs dans le fichier CSV",
|
|
||||||
guest_count: "%{smart_count} visiteur |||| %{smart_count} visiteurs",
|
|
||||||
admin_count:
|
|
||||||
"%{smart_count} administrateur |||| %{smart_count} administrateurs",
|
|
||||||
},
|
|
||||||
conflicts: {
|
|
||||||
header: "Stratégie de résolution des conflits",
|
|
||||||
mode: {
|
|
||||||
stop: "S'arrêter en cas de conflit",
|
|
||||||
skip: "Afficher l'erreur et ignorer le conflit",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ids: {
|
|
||||||
header: "Identifiants",
|
|
||||||
all_ids_present: "Identifiants présents pour chaque entrée",
|
|
||||||
count_ids_present:
|
|
||||||
"%{smart_count} entrée avec identifiant |||| %{smart_count} entrées avec identifiant",
|
|
||||||
mode: {
|
|
||||||
ignore:
|
|
||||||
"Ignorer les identifiants dans le ficher CSV et en créer de nouveaux",
|
|
||||||
update: "Mettre à jour les enregistrements existants",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
passwords: {
|
|
||||||
header: "Mots de passe",
|
|
||||||
all_passwords_present: "Mots de passe présents pour chaque entrée",
|
|
||||||
count_passwords_present:
|
|
||||||
"%{smart_count} entrée avec mot de passe |||| %{smart_count} entrées avec mot de passe",
|
|
||||||
use_passwords: "Utiliser les mots de passe provenant du fichier CSV",
|
|
||||||
},
|
|
||||||
upload: {
|
|
||||||
header: "Fichier CSV en entrée",
|
|
||||||
explanation:
|
|
||||||
"Vous pouvez télécharger ici un fichier contenant des valeurs séparées par des virgules qui sera traité pour créer ou mettre à jour des utilisateurs. Le fichier doit inclure les champs « id » et « displayname ». Vous pouvez télécharger et adapter un fichier d'exemple ici : ",
|
|
||||||
},
|
|
||||||
startImport: {
|
|
||||||
simulate_only: "Simuler",
|
|
||||||
run_import: "Importer",
|
|
||||||
},
|
|
||||||
results: {
|
|
||||||
header: "Résultats de l'import",
|
|
||||||
total:
|
|
||||||
"%{smart_count} entrée au total |||| %{smart_count} entrées au total",
|
|
||||||
successful: "%{smart_count} entrées importées avec succès",
|
|
||||||
skipped: "%{smart_count} entrées ignorées",
|
|
||||||
download_skipped: "Télécharger les entrées ignorées",
|
|
||||||
with_error:
|
|
||||||
"%{smart_count} entrée avec des erreurs ||| %{smart_count} entrées avec des erreurs",
|
|
||||||
simulated_only: "L'import était simulé",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
resources: {
|
|
||||||
users: {
|
|
||||||
name: "Utilisateur |||| Utilisateurs",
|
|
||||||
email: "Adresse électronique",
|
|
||||||
msisdn: "Numéro de téléphone",
|
|
||||||
threepid: "Adresse électronique / Numéro de téléphone",
|
|
||||||
fields: {
|
|
||||||
avatar: "Avatar",
|
|
||||||
id: "Identifiant",
|
|
||||||
name: "Nom",
|
|
||||||
is_guest: "Visiteur",
|
|
||||||
admin: "Administrateur du serveur",
|
|
||||||
deactivated: "Désactivé",
|
|
||||||
guests: "Afficher les visiteurs",
|
|
||||||
show_deactivated: "Afficher les utilisateurs désactivés",
|
|
||||||
user_id: "Rechercher un utilisateur",
|
|
||||||
displayname: "Nom d'affichage",
|
|
||||||
password: "Mot de passe",
|
|
||||||
avatar_url: "URL de l'avatar",
|
|
||||||
avatar_src: "Avatar",
|
|
||||||
medium: "Type",
|
|
||||||
threepids: "Identifiants tiers",
|
|
||||||
address: "Adresse",
|
|
||||||
creation_ts_ms: "Date de création",
|
|
||||||
consent_version: "Version du consentement",
|
|
||||||
auth_provider: "Fournisseur d'identité",
|
|
||||||
},
|
|
||||||
helper: {
|
|
||||||
deactivate:
|
|
||||||
"Vous devrez fournir un mot de passe pour réactiver le compte.",
|
|
||||||
erase: "Marquer l'utilisateur comme effacé conformément au RGPD",
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
erase: "Effacer les données de l'utilisateur",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rooms: {
|
|
||||||
name: "Salon |||| Salons",
|
|
||||||
fields: {
|
|
||||||
room_id: "Identifiant du salon",
|
|
||||||
name: "Nom",
|
|
||||||
canonical_alias: "Alias",
|
|
||||||
joined_members: "Membres",
|
|
||||||
joined_local_members: "Membres locaux",
|
|
||||||
joined_local_devices: "Appareils locaux",
|
|
||||||
state_events: "Événements d'État / Complexité",
|
|
||||||
version: "Version",
|
|
||||||
is_encrypted: "Chiffré",
|
|
||||||
encryption: "Chiffrement",
|
|
||||||
federatable: "Fédérable",
|
|
||||||
public: "Visible dans le répertoire des salons",
|
|
||||||
creator: "Créateur",
|
|
||||||
join_rules: "Règles d'adhésion",
|
|
||||||
guest_access: "Accès des visiteurs",
|
|
||||||
history_visibility: "Visibilité de l'historique",
|
|
||||||
topic: "Sujet",
|
|
||||||
avatar: "Avatar",
|
|
||||||
},
|
|
||||||
helper: {
|
|
||||||
forward_extremities:
|
|
||||||
"Les extrémités avant sont les événements feuilles à la fin d'un graphe orienté acyclique (DAG) dans un salon, c'est-à-dire les événements qui n'ont pas de descendants. Plus il y en a dans un salon, plus la résolution d'état que Synapse doit effectuer est importante (indice : c'est une opération coûteuse). Bien que Synapse dispose d'un algorithme pour éviter qu'un trop grand nombre de ces événements n'existent en même temps dans un salon, des bogues peuvent parfois les faire réapparaître. Si un salon présente plus de 10 extrémités avant, cela vaut la peine d'y prêter attention et éventuellement de les supprimer en utilisant les requêtes SQL mentionnées dans la discussion traitant du problème https://github.com/matrix-org/synapse/issues/1760.",
|
|
||||||
},
|
|
||||||
enums: {
|
|
||||||
join_rules: {
|
|
||||||
public: "Public",
|
|
||||||
knock: "Sur demande",
|
|
||||||
invite: "Sur invitation",
|
|
||||||
private: "Privé",
|
|
||||||
},
|
|
||||||
guest_access: {
|
|
||||||
can_join: "Les visiteurs peuvent rejoindre le salon",
|
|
||||||
forbidden: "Les visiteurs ne peuvent pas rejoindre le salon",
|
|
||||||
},
|
|
||||||
history_visibility: {
|
|
||||||
invited: "Depuis l'invitation",
|
|
||||||
joined: "Depuis l'adhésion",
|
|
||||||
shared: "Depuis le partage",
|
|
||||||
world_readable: "Tout le monde",
|
|
||||||
},
|
|
||||||
unencrypted: "Non chiffré",
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
erase: {
|
|
||||||
title: "Supprimer le salon",
|
|
||||||
content:
|
|
||||||
"Voulez-vous vraiment supprimer le salon ? Cette opération ne peut être annulée. Tous les messages et médias partagés du salon seront supprimés du serveur !",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
reports: {
|
|
||||||
name: "Événement signalé |||| Événements signalés",
|
|
||||||
fields: {
|
|
||||||
id: "Identifiant",
|
|
||||||
received_ts: "Date du rapport",
|
|
||||||
user_id: "Rapporteur",
|
|
||||||
name: "Nom du salon",
|
|
||||||
score: "Score",
|
|
||||||
reason: "Motif",
|
|
||||||
event_id: "Identifiant de l'événement",
|
|
||||||
event_json: {
|
|
||||||
origin: "Serveur d'origine",
|
|
||||||
origin_server_ts: "Date d'envoi",
|
|
||||||
type: "Type d'événement",
|
|
||||||
content: {
|
|
||||||
msgtype: "Type de contenu",
|
|
||||||
body: "Contenu",
|
|
||||||
format: "Format",
|
|
||||||
formatted_body: "Contenu mis en forme",
|
|
||||||
algorithm: "Algorithme",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
connections: {
|
|
||||||
name: "Connexions",
|
|
||||||
fields: {
|
|
||||||
last_seen: "Date",
|
|
||||||
ip: "Adresse IP",
|
|
||||||
user_agent: "Agent utilisateur",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
devices: {
|
|
||||||
name: "Appareil |||| Appareils",
|
|
||||||
fields: {
|
|
||||||
device_id: "Identifiant de l'appareil",
|
|
||||||
display_name: "Nom de l'appareil",
|
|
||||||
last_seen_ts: "Date",
|
|
||||||
last_seen_ip: "Adresse IP",
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
erase: {
|
|
||||||
title: "Suppression de %{id}",
|
|
||||||
content: "Voulez-vous vraiment supprimer l'appareil « %{name} » ?",
|
|
||||||
success: "Appareil supprimé avec succès",
|
|
||||||
failure: "Une erreur s'est produite",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
users_media: {
|
|
||||||
name: "Media",
|
|
||||||
fields: {
|
|
||||||
media_id: "Identifiant du média",
|
|
||||||
media_length: "Taille du fichier (en octets)",
|
|
||||||
media_type: "Type",
|
|
||||||
upload_name: "Nom du fichier",
|
|
||||||
quarantined_by: "Mis en quarantaine par",
|
|
||||||
safe_from_quarantine: "Protection contre la mise en quarantaine",
|
|
||||||
created_ts: "Date de création",
|
|
||||||
last_access_ts: "Dernier accès",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
delete_media: {
|
|
||||||
name: "Media",
|
|
||||||
fields: {
|
|
||||||
before_ts: "Dernier accès avant",
|
|
||||||
size_gt: "Plus grand que (en octets)",
|
|
||||||
keep_profiles: "Conserver les images de profil",
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
send: "Supprimer le média",
|
|
||||||
send_success: "Requête envoyée avec succès",
|
|
||||||
send_failure: "Une erreur s'est produite",
|
|
||||||
},
|
|
||||||
helper: {
|
|
||||||
send: "Cette API supprime les médias locaux du disque de votre propre serveur. Cela inclut toutes les vignettes locales et les copies des médias téléchargés. Cette API n'affectera pas les médias qui ont été téléversés dans des dépôts de médias externes.",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
protect_media: {
|
|
||||||
action: {
|
|
||||||
create: "Protéger",
|
|
||||||
delete: "Révoquer la protection",
|
|
||||||
none: "En quarantaine",
|
|
||||||
send_success: "Le statut de protection a été modifié avec succès",
|
|
||||||
send_failure: "Une erreur s'est produite",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
quarantine_media: {
|
|
||||||
action: {
|
|
||||||
name: "Quarantaine",
|
|
||||||
create: "Mettre en quarantaine",
|
|
||||||
delete: "Révoquer la mise en quarantaine",
|
|
||||||
none: "Protégé contre la mise en quarantaine",
|
|
||||||
send_success: "Le statut de la quarantaine a été modifié avec succès",
|
|
||||||
send_failure: "Une erreur s'est produite",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
pushers: {
|
|
||||||
name: "Émetteur de notifications |||| Émetteurs de notifications",
|
|
||||||
fields: {
|
|
||||||
app: "Application",
|
|
||||||
app_display_name: "Nom d'affichage de l'application",
|
|
||||||
app_id: "Identifiant de l'application",
|
|
||||||
device_display_name: "Nom d'affichage de l'appareil",
|
|
||||||
kind: "Type",
|
|
||||||
lang: "Langue",
|
|
||||||
profile_tag: "Profil",
|
|
||||||
pushkey: "Identifiant de l'émetteur",
|
|
||||||
data: { url: "URL" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
servernotices: {
|
|
||||||
name: "Annonces du serveur",
|
|
||||||
send: "Envoyer des « Annonces du serveur »",
|
|
||||||
fields: {
|
|
||||||
body: "Message",
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
send: "Envoyer une annonce",
|
|
||||||
send_success: "Annonce envoyée avec succès",
|
|
||||||
send_failure: "Une erreur s'est produite",
|
|
||||||
},
|
|
||||||
helper: {
|
|
||||||
send: "Envoie une annonce au nom du serveur aux utilisateurs sélectionnés. La fonction « Annonces du serveur » doit être activée sur le serveur.",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
user_media_statistics: {
|
|
||||||
name: "Médias des utilisateurs",
|
|
||||||
fields: {
|
|
||||||
media_count: "Nombre de médias",
|
|
||||||
media_length: "Taille des médias",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
forward_extremities: {
|
|
||||||
name: "Extrémités avant",
|
|
||||||
fields: {
|
|
||||||
id: "Identifiant de l'événement",
|
|
||||||
received_ts: "Date de réception",
|
|
||||||
depth: "Profondeur",
|
|
||||||
state_group: "Groupe d'état",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
room_state: {
|
|
||||||
name: "Événements d'état",
|
|
||||||
fields: {
|
|
||||||
type: "Type",
|
|
||||||
content: "Contenu",
|
|
||||||
origin_server_ts: "Date d'envoi",
|
|
||||||
sender: "Expéditeur",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
room_directory: {
|
|
||||||
name: "Répertoire des salons",
|
|
||||||
fields: {
|
|
||||||
world_readable:
|
|
||||||
"Tout utilisateur peut avoir un aperçu du salon, sans en devenir membre",
|
|
||||||
guest_can_join: "Les visiteurs peuvent rejoindre le salon",
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
title:
|
|
||||||
"Supprimer un salon du répertoire |||| Supprimer %{smart_count} salons du répertoire",
|
|
||||||
content:
|
|
||||||
"Voulez-vous vraiment supprimer ce salon du répertoire ? |||| Voulez-vous vraiment supprimer ces %{smart_count} salons du répertoire ?",
|
|
||||||
erase: "Supprimer du répertoire des salons",
|
|
||||||
create: "Publier dans le répertoire des salons",
|
|
||||||
send_success: "Salon publié avec succès",
|
|
||||||
send_failure: "Une erreur s'est produite",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
registration_tokens: {
|
|
||||||
name: "Jetons d'inscription",
|
|
||||||
fields: {
|
|
||||||
token: "Jeton",
|
|
||||||
valid: "Jeton valide",
|
|
||||||
uses_allowed: "Nombre d'inscription autorisées",
|
|
||||||
pending: "Nombre d'inscription en cours",
|
|
||||||
completed: "Nombre d'inscription accomplie",
|
|
||||||
expiry_time: "Date d'expiration",
|
|
||||||
length: "Longueur",
|
|
||||||
},
|
|
||||||
helper: {
|
|
||||||
length:
|
|
||||||
"Longueur du jeton généré aléatoirement si aucun jeton n'est pas spécifié",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
export default fr;
|
|
||||||
-385
@@ -1,385 +0,0 @@
|
|||||||
import italianMessages from "ra-language-italian";
|
|
||||||
|
|
||||||
const it = {
|
|
||||||
...italianMessages,
|
|
||||||
synapseadmin: {
|
|
||||||
auth: {
|
|
||||||
base_url: "URL dell'homeserver",
|
|
||||||
welcome: "Benvenuto in Synapse-admin",
|
|
||||||
server_version: "Versione di Synapse",
|
|
||||||
username_error:
|
|
||||||
"Per favore inserisci un ID utente completo: '@utente:dominio'",
|
|
||||||
protocol_error: "L'URL deve iniziare per 'http://' o 'https://'",
|
|
||||||
url_error: "URL del server Matrix non valido",
|
|
||||||
sso_sign_in: "Accedi con SSO",
|
|
||||||
},
|
|
||||||
users: {
|
|
||||||
invalid_user_id: "ID utente non valido su questo homeserver.",
|
|
||||||
tabs: { sso: "SSO" },
|
|
||||||
},
|
|
||||||
rooms: {
|
|
||||||
tabs: {
|
|
||||||
basic: "Semplice",
|
|
||||||
members: "Membro",
|
|
||||||
detail: "Dettagli",
|
|
||||||
permission: "Permessi",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
reports: { tabs: { basic: "Semplice", detail: "Dettagli" } },
|
|
||||||
},
|
|
||||||
import_users: {
|
|
||||||
error: {
|
|
||||||
at_entry: "Alla voce %{entry}: %{message}",
|
|
||||||
error: "Errore",
|
|
||||||
required_field: "Il campo '%{field}' non è presente",
|
|
||||||
invalid_value:
|
|
||||||
"Valore non valido alla riga %{row}. '%{field}' Il campo può essere solo 'true' o 'false'",
|
|
||||||
unreasonably_big:
|
|
||||||
"Impossibile caricare un file così grosso (%{size} megabyte)",
|
|
||||||
already_in_progress: "Un import è attualmente già in caricamento",
|
|
||||||
id_exits: "L'ID %{id} è già presente",
|
|
||||||
},
|
|
||||||
title: "Importa utenti tramite file CSV",
|
|
||||||
goToPdf: "Vai al PDF",
|
|
||||||
cards: {
|
|
||||||
importstats: {
|
|
||||||
header: "Importa utenti",
|
|
||||||
users_total:
|
|
||||||
"%{smart_count} utente nel file CSV |||| %{smart_count} utenti nel file CSV",
|
|
||||||
guest_count: "%{smart_count} ospite |||| %{smart_count} ospiti",
|
|
||||||
admin_count:
|
|
||||||
"%{smart_count} amministratore |||| %{smart_count} amministratori",
|
|
||||||
},
|
|
||||||
conflicts: {
|
|
||||||
header: "Strategia di conflitto",
|
|
||||||
mode: {
|
|
||||||
stop: "Stoppa al conflitto",
|
|
||||||
skip: "Mostra l'errore e ignora il conflitto",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ids: {
|
|
||||||
header: "ID",
|
|
||||||
all_ids_present: "ID presenti in ogni voce",
|
|
||||||
count_ids_present:
|
|
||||||
"%{smart_count} voce con ID |||| %{smart_count} voci con ID",
|
|
||||||
mode: {
|
|
||||||
ignore: "Ignora gli ID nel file CSV e creane di nuovi",
|
|
||||||
update: "Aggiorna le voci esistenti",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
passwords: {
|
|
||||||
header: "Passwords",
|
|
||||||
all_passwords_present: "Password presenti in ogni voce",
|
|
||||||
count_passwords_present:
|
|
||||||
"%{smart_count} voce con password |||| %{smart_count} voci con password",
|
|
||||||
use_passwords: "Usa le password dal file CSV",
|
|
||||||
},
|
|
||||||
upload: {
|
|
||||||
header: "Input file CSV",
|
|
||||||
explanation:
|
|
||||||
"Qui puoi caricare un file con valori separati da virgole che verrà poi utilizzato per creare o aggiornare gli utenti. Il file deve includere i campi 'id' and 'displayname'. Puoi scaricare un file di esempio per adattarlo: ",
|
|
||||||
},
|
|
||||||
startImport: {
|
|
||||||
simulate_only: "Solo simulazione",
|
|
||||||
run_import: "Importa",
|
|
||||||
},
|
|
||||||
results: {
|
|
||||||
header: "Importa i risultati",
|
|
||||||
total:
|
|
||||||
"%{smart_count} voce in totale |||| %{smart_count} voci in totale",
|
|
||||||
successful: "%{smart_count} voci importate con successo",
|
|
||||||
skipped: "%{smart_count} voci ignorate",
|
|
||||||
download_skipped: "Scarica le voci ignorate",
|
|
||||||
with_error:
|
|
||||||
"%{smart_count} voce con errori ||| %{smart_count} voci con errori",
|
|
||||||
simulated_only: "Il processo era stato solamente simulato",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
resources: {
|
|
||||||
users: {
|
|
||||||
name: "Utente |||| Utenti",
|
|
||||||
email: "Email",
|
|
||||||
msisdn: "Telefono",
|
|
||||||
threepid: "Email / Telefono",
|
|
||||||
fields: {
|
|
||||||
avatar: "Avatar",
|
|
||||||
id: "ID utente",
|
|
||||||
name: "Nome",
|
|
||||||
is_guest: "Ospite",
|
|
||||||
admin: "Amministratore",
|
|
||||||
deactivated: "Disattivato",
|
|
||||||
guests: "Mostra gli ospiti",
|
|
||||||
show_deactivated: "Mostra gli utenti disattivati",
|
|
||||||
user_id: "Cerca utente",
|
|
||||||
displayname: "Nickname",
|
|
||||||
password: "Password",
|
|
||||||
avatar_url: "URL dell'avatar",
|
|
||||||
avatar_src: "Avatar",
|
|
||||||
medium: "Medium",
|
|
||||||
threepids: "3PID",
|
|
||||||
address: "Indirizzo",
|
|
||||||
creation_ts_ms: "Creazione del timestamp",
|
|
||||||
consent_version: "Versione minima richiesta",
|
|
||||||
auth_provider: "Provider",
|
|
||||||
user_type: "Tipo d'utente",
|
|
||||||
},
|
|
||||||
helper: {
|
|
||||||
password:
|
|
||||||
"Cambiando la password l'utente verrà disconnesso da tutte le sessioni attive.",
|
|
||||||
deactivate: "Devi fornire una password per riattivare l'account.",
|
|
||||||
erase: "Constrassegna l'utente come cancellato dal GDPR",
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
erase: "Cancella i dati dell'utente",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rooms: {
|
|
||||||
name: "Stanza |||| Stanze",
|
|
||||||
fields: {
|
|
||||||
room_id: "ID della stanza",
|
|
||||||
name: "Nome",
|
|
||||||
canonical_alias: "Alias",
|
|
||||||
joined_members: "Membri",
|
|
||||||
joined_local_members: "Membri locali",
|
|
||||||
joined_local_devices: "Dispositivi locali",
|
|
||||||
state_events: "Eventi di stato / Complessità",
|
|
||||||
version: "Versione",
|
|
||||||
is_encrypted: "Criptato",
|
|
||||||
encryption: "Crittografia",
|
|
||||||
federatable: "Federabile",
|
|
||||||
public: "Visibile nella cartella della stanza",
|
|
||||||
creator: "Creatore",
|
|
||||||
join_rules: "Regole per entrare",
|
|
||||||
guest_access: "Entra come ospite",
|
|
||||||
history_visibility: "Visibilità temporale",
|
|
||||||
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: "Pubblica",
|
|
||||||
knock: "Bussa",
|
|
||||||
invite: "Invita",
|
|
||||||
private: "Privata",
|
|
||||||
},
|
|
||||||
guest_access: {
|
|
||||||
can_join: "Gli utenti ospiti possono entrare",
|
|
||||||
forbidden: "Gli utenti ospiti non possono entrare",
|
|
||||||
},
|
|
||||||
history_visibility: {
|
|
||||||
invited: "Dall'invito",
|
|
||||||
joined: "Dall'entrata",
|
|
||||||
shared: "Dalla condivisione",
|
|
||||||
world_readable: "Chiunque",
|
|
||||||
},
|
|
||||||
unencrypted: "Non criptata",
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
erase: {
|
|
||||||
title: "Cancella stanza",
|
|
||||||
content:
|
|
||||||
"Sei sicuro di voler eliminare questa stanza? Questa azione è definitiva. Tutti i messaggi e i media condivisi in questa stanza verranno eliminati dal server!",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
reports: {
|
|
||||||
name: "Evento segnalato |||| Eventi segnalati",
|
|
||||||
fields: {
|
|
||||||
id: "ID",
|
|
||||||
received_ts: "Orario del report",
|
|
||||||
user_id: "richiedente",
|
|
||||||
name: "nome della stanza",
|
|
||||||
score: "punteggio",
|
|
||||||
reason: "ragione",
|
|
||||||
event_id: "ID dell'evento",
|
|
||||||
event_json: {
|
|
||||||
origin: "server di origine",
|
|
||||||
origin_server_ts: "ora dell'invio",
|
|
||||||
type: "tipo di evento",
|
|
||||||
content: {
|
|
||||||
msgtype: "tipo di contenuto",
|
|
||||||
body: "contenuto",
|
|
||||||
format: "formato",
|
|
||||||
formatted_body: "contenuto formattato",
|
|
||||||
algorithm: "algoritmo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
connections: {
|
|
||||||
name: "Connessioni",
|
|
||||||
fields: {
|
|
||||||
last_seen: "Data",
|
|
||||||
ip: "Indirizzo IP",
|
|
||||||
user_agent: "agente utente",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
devices: {
|
|
||||||
name: "Dispositivo |||| Dispositivi",
|
|
||||||
fields: {
|
|
||||||
device_id: "ID del dispositivo",
|
|
||||||
display_name: "Nome del dispositivo",
|
|
||||||
last_seen_ts: "Timestamp",
|
|
||||||
last_seen_ip: "Indirizzo IP",
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
erase: {
|
|
||||||
title: "Rimozione del dispositivo %{id}",
|
|
||||||
content: 'Sei sicuro di voler rimuovere il dispositivo "%{name}"?',
|
|
||||||
success: "Dispositivo rimosso con successo.",
|
|
||||||
failure: "C'è stato un errore.",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
users_media: {
|
|
||||||
name: "Media",
|
|
||||||
fields: {
|
|
||||||
media_id: "ID del media",
|
|
||||||
media_length: "Peso del file (in Byte)",
|
|
||||||
media_type: "Tipo",
|
|
||||||
upload_name: "Nome del file",
|
|
||||||
quarantined_by: "In quarantena da",
|
|
||||||
safe_from_quarantine: "Protetto dalla quarantena",
|
|
||||||
created_ts: "Creato",
|
|
||||||
last_access_ts: "Ultimo accesso",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
delete_media: {
|
|
||||||
name: "Media",
|
|
||||||
fields: {
|
|
||||||
before_ts: "ultimo accesso effettuato prima",
|
|
||||||
size_gt: "Più grande di (in byte)",
|
|
||||||
keep_profiles: "Mantieni le immagini del profilo",
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
send: "Cancella media",
|
|
||||||
send_success: "Richiesta inviata con successo.",
|
|
||||||
send_failure: "C'è stato un errore.",
|
|
||||||
},
|
|
||||||
helper: {
|
|
||||||
send: "Questa API cancella i media locali dal disco del tuo server. Questo include anche ogni miniatura e copia del media scaricato. Questa API non inciderà sui media che sono stati caricati nei repository esterni.",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
protect_media: {
|
|
||||||
action: {
|
|
||||||
create: "Non protetto, proteggi",
|
|
||||||
delete: "Protetto, rimuovi protezione",
|
|
||||||
none: "In quarantena",
|
|
||||||
send_success: "Stato della protezione cambiato con successo.",
|
|
||||||
send_failure: "C'è stato un errore.",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
quarantine_media: {
|
|
||||||
action: {
|
|
||||||
name: "Quarantina",
|
|
||||||
create: "Aggiungi alla quarantena",
|
|
||||||
delete: "In quarantena, rimuovi dalla quarantena",
|
|
||||||
none: "Protetto dalla quarantena",
|
|
||||||
send_success: "Stato della quarantena cambiato con successo.",
|
|
||||||
send_failure: "C'è stato un errore.",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
pushers: {
|
|
||||||
name: "Pusher |||| Pusher",
|
|
||||||
fields: {
|
|
||||||
app: "App",
|
|
||||||
app_display_name: "Nome dell'app",
|
|
||||||
app_id: "ID dell'app",
|
|
||||||
device_display_name: "Nome del dispositivo",
|
|
||||||
kind: "Tipo",
|
|
||||||
lang: "Lingua",
|
|
||||||
profile_tag: "Tag del profilo",
|
|
||||||
pushkey: "Pushkey",
|
|
||||||
data: { url: "URL" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
servernotices: {
|
|
||||||
name: "Avvisi del server",
|
|
||||||
send: "Invia avvisi",
|
|
||||||
fields: {
|
|
||||||
body: "Messaggio",
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
send: "Invia nota",
|
|
||||||
send_success: "Avviso inviato con successo.",
|
|
||||||
send_failure: "C'è stato un errore.",
|
|
||||||
},
|
|
||||||
helper: {
|
|
||||||
send: 'Invia un avviso dal server agli utenti selezionati. La feature "Avvisi del server" è stata attivata sul server.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
user_media_statistics: {
|
|
||||||
name: "Media degli utenti",
|
|
||||||
fields: {
|
|
||||||
media_count: "Numero media",
|
|
||||||
media_length: "Lunghezza media",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
forward_extremities: {
|
|
||||||
name: "Invia estremità",
|
|
||||||
fields: {
|
|
||||||
id: "Event ID",
|
|
||||||
received_ts: "Timestamp",
|
|
||||||
depth: "Profondità",
|
|
||||||
state_group: "State group",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
room_state: {
|
|
||||||
name: "Eventi di stato",
|
|
||||||
fields: {
|
|
||||||
type: "Tipo",
|
|
||||||
content: "Contenuto",
|
|
||||||
origin_server_ts: "Ora dell'invio",
|
|
||||||
sender: "Mittente",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
room_directory: {
|
|
||||||
name: "Elenco delle stanze",
|
|
||||||
fields: {
|
|
||||||
world_readable: "gli utenti ospite possono vedere senza entrare",
|
|
||||||
guest_can_join: "gli utenti ospite possono entrare",
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
title:
|
|
||||||
"Cancella stanza dall'elenco |||| Cancella %{smart_count} stanze dall'elenco",
|
|
||||||
content:
|
|
||||||
"Sei sicuro di voler rimuovere questa stanza dall'elenco? |||| Sei sicuro di voler rimuovere %{smart_count} stanze dall'elenco?",
|
|
||||||
erase: "Rimuovi dall'elenco",
|
|
||||||
create: "Crea",
|
|
||||||
send_success: "Stanza creata con successo.",
|
|
||||||
send_failure: "C'è stato un errore.",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
destinations: {
|
|
||||||
name: "Federazione",
|
|
||||||
fields: {
|
|
||||||
destination: "Destinazione",
|
|
||||||
failure_ts: "Timestamp dell'errore",
|
|
||||||
retry_last_ts: "Tentativo ultimo timestamp",
|
|
||||||
retry_interval: "Intervallo dei tentativi",
|
|
||||||
last_successful_stream_ordering: "Ultimo flusso riuscito con successo",
|
|
||||||
stream_ordering: "Flusso",
|
|
||||||
},
|
|
||||||
action: { reconnect: "Riconnetti" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
registration_tokens: {
|
|
||||||
name: "Token di registrazione",
|
|
||||||
fields: {
|
|
||||||
token: "Token",
|
|
||||||
valid: "Token valido",
|
|
||||||
uses_allowed: "Usi permessi",
|
|
||||||
pending: "In attesa",
|
|
||||||
completed: "Completato",
|
|
||||||
expiry_time: "Data della scadenza",
|
|
||||||
length: "Lunghezza",
|
|
||||||
},
|
|
||||||
helper: { length: "Lunghezza del token se non viene dato alcun token." },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
export default it;
|
|
||||||
-290
@@ -1,290 +0,0 @@
|
|||||||
import chineseMessages from "ra-language-chinese";
|
|
||||||
|
|
||||||
const zh = {
|
|
||||||
...chineseMessages,
|
|
||||||
synapseadmin: {
|
|
||||||
auth: {
|
|
||||||
base_url: "服务器 URL",
|
|
||||||
welcome: "欢迎来到 Synapse-admin",
|
|
||||||
server_version: "Synapse 版本",
|
|
||||||
username_error: "请输入完整有效的用户 ID: '@user:domain'",
|
|
||||||
protocol_error: "URL 需要以'http://'或'https://'作为起始",
|
|
||||||
url_error: "不是一个有效的 Matrix 服务器地址",
|
|
||||||
sso_sign_in: "使用 SSO 登录",
|
|
||||||
},
|
|
||||||
users: {
|
|
||||||
invalid_user_id:
|
|
||||||
"必须要是一个有效的 Matrix 用户 ID ,例如 @user_id:homeserver",
|
|
||||||
tabs: { sso: "SSO" },
|
|
||||||
},
|
|
||||||
rooms: {
|
|
||||||
tabs: {
|
|
||||||
basic: "基本",
|
|
||||||
members: "成员",
|
|
||||||
detail: "细节",
|
|
||||||
permission: "权限",
|
|
||||||
},
|
|
||||||
delete: {
|
|
||||||
title: "删除房间",
|
|
||||||
message:
|
|
||||||
"您确定要删除这个房间吗?该操作无法被撤销。这个房间里所有的消息和分享的媒体都将被从服务器上删除!",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
reports: { tabs: { basic: "基本", detail: "细节" } },
|
|
||||||
},
|
|
||||||
import_users: {
|
|
||||||
error: {
|
|
||||||
at_entry: "在条目 %{entry}: %{message}",
|
|
||||||
error: "错误",
|
|
||||||
required_field: "需要的值 '%{field}' 未被设置。",
|
|
||||||
invalid_value:
|
|
||||||
"第 %{row} 行出现无效值。 '%{field}' 只可以是 'true' 或 'false'。",
|
|
||||||
unreasonably_big: "拒绝加载过大的文件: %{size} MB",
|
|
||||||
already_in_progress: "一个导入进程已经在运行中",
|
|
||||||
id_exits: "ID %{id} 已经存在",
|
|
||||||
},
|
|
||||||
title: "通过 CSV 导入用户",
|
|
||||||
goToPdf: "转到 PDF",
|
|
||||||
cards: {
|
|
||||||
importstats: {
|
|
||||||
header: "导入用户",
|
|
||||||
users_total:
|
|
||||||
"%{smart_count} 用户在 CSV 文件中 |||| %{smart_count} 用户在 CSV 文件中",
|
|
||||||
guest_count: "%{smart_count} 访客 |||| %{smart_count} 访客",
|
|
||||||
admin_count: "%{smart_count} 管理员 |||| %{smart_count} 管理员",
|
|
||||||
},
|
|
||||||
conflicts: {
|
|
||||||
header: "冲突处理策略",
|
|
||||||
mode: {
|
|
||||||
stop: "在冲突处停止",
|
|
||||||
skip: "显示错误并跳过冲突",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ids: {
|
|
||||||
header: "IDs",
|
|
||||||
all_ids_present: "每条记录的 ID",
|
|
||||||
count_ids_present:
|
|
||||||
"%{smart_count} 个含 ID 的记录 |||| %{smart_count} 个含 ID 的记录",
|
|
||||||
mode: {
|
|
||||||
ignore: "忽略 CSV 中的 ID 并创建新的",
|
|
||||||
update: "更新已经存在的记录",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
passwords: {
|
|
||||||
header: "密码",
|
|
||||||
all_passwords_present: "每条记录的密码",
|
|
||||||
count_passwords_present:
|
|
||||||
"%{smart_count} 个含密码的记录 |||| %{smart_count} 个含密码的记录",
|
|
||||||
use_passwords: "使用 CSV 中标记的密码",
|
|
||||||
},
|
|
||||||
upload: {
|
|
||||||
header: "导入 CSV 文件",
|
|
||||||
explanation:
|
|
||||||
"在这里,你可以上传一个用逗号分隔的文件,用于创建或更新用户。该文件必须包括 'id' 和 'displayname' 字段。你可以在这里下载并修改一个示例文件:",
|
|
||||||
},
|
|
||||||
startImport: {
|
|
||||||
simulate_only: "模拟模式",
|
|
||||||
run_import: "导入",
|
|
||||||
},
|
|
||||||
results: {
|
|
||||||
header: "导入结果",
|
|
||||||
total: "共计 %{smart_count} 条记录 |||| 共计 %{smart_count} 条记录",
|
|
||||||
successful: "%{smart_count} 条记录导入成功",
|
|
||||||
skipped: "跳过 %{smart_count} 条记录",
|
|
||||||
download_skipped: "下载跳过的记录",
|
|
||||||
with_error:
|
|
||||||
"%{smart_count} 条记录出现错误 ||| %{smart_count} 条记录出现错误",
|
|
||||||
simulated_only: "只是一次模拟运行",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
resources: {
|
|
||||||
users: {
|
|
||||||
name: "用户",
|
|
||||||
email: "邮箱",
|
|
||||||
msisdn: "电话",
|
|
||||||
threepid: "邮箱 / 电话",
|
|
||||||
fields: {
|
|
||||||
avatar: "邮箱",
|
|
||||||
id: "用户 ID",
|
|
||||||
name: "用户名",
|
|
||||||
is_guest: "访客",
|
|
||||||
admin: "服务器管理员",
|
|
||||||
deactivated: "被禁用",
|
|
||||||
guests: "显示访客",
|
|
||||||
show_deactivated: "显示被禁用的账户",
|
|
||||||
user_id: "搜索用户",
|
|
||||||
displayname: "显示名字",
|
|
||||||
password: "密码",
|
|
||||||
avatar_url: "头像 URL",
|
|
||||||
avatar_src: "头像",
|
|
||||||
medium: "Medium",
|
|
||||||
threepids: "3PIDs",
|
|
||||||
address: "地址",
|
|
||||||
creation_ts_ms: "创建时间戳",
|
|
||||||
consent_version: "协议版本",
|
|
||||||
},
|
|
||||||
helper: {
|
|
||||||
deactivate: "您必须提供一串密码来激活账户。",
|
|
||||||
erase: "将用户标记为根据 GDPR 的要求抹除了",
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
erase: "抹除用户信息",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rooms: {
|
|
||||||
name: "房间",
|
|
||||||
fields: {
|
|
||||||
room_id: "房间 ID",
|
|
||||||
name: "房间名",
|
|
||||||
canonical_alias: "别名",
|
|
||||||
joined_members: "成员",
|
|
||||||
joined_local_members: "本地成员",
|
|
||||||
state_events: "状态事件",
|
|
||||||
version: "版本",
|
|
||||||
is_encrypted: "已经加密",
|
|
||||||
encryption: "加密",
|
|
||||||
federatable: "可联合的",
|
|
||||||
public: "公开",
|
|
||||||
creator: "创建者",
|
|
||||||
join_rules: "加入规则",
|
|
||||||
guest_access: "访客访问",
|
|
||||||
history_visibility: "历史可见性",
|
|
||||||
},
|
|
||||||
enums: {
|
|
||||||
join_rules: {
|
|
||||||
public: "公开",
|
|
||||||
knock: "申请",
|
|
||||||
invite: "邀请",
|
|
||||||
private: "私有",
|
|
||||||
},
|
|
||||||
guest_access: {
|
|
||||||
can_join: "访客可以加入",
|
|
||||||
forbidden: "访客不可加入",
|
|
||||||
},
|
|
||||||
history_visibility: {
|
|
||||||
invited: "自从被邀请",
|
|
||||||
joined: "自从加入",
|
|
||||||
shared: "自从分享",
|
|
||||||
world_readable: "任何人",
|
|
||||||
},
|
|
||||||
unencrypted: "未加密",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
reports: {
|
|
||||||
name: "报告事件",
|
|
||||||
fields: {
|
|
||||||
id: "ID",
|
|
||||||
received_ts: "报告时间",
|
|
||||||
user_id: "报告者",
|
|
||||||
name: "房间名",
|
|
||||||
score: "分数",
|
|
||||||
reason: "原因",
|
|
||||||
event_id: "事件 ID",
|
|
||||||
event_json: {
|
|
||||||
origin: "原始服务器",
|
|
||||||
origin_server_ts: "发送时间",
|
|
||||||
type: "事件类型",
|
|
||||||
content: {
|
|
||||||
msgtype: "内容类型",
|
|
||||||
body: "内容",
|
|
||||||
format: "格式",
|
|
||||||
formatted_body: "格式化的数据",
|
|
||||||
algorithm: "算法",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
connections: {
|
|
||||||
name: "连接",
|
|
||||||
fields: {
|
|
||||||
last_seen: "日期",
|
|
||||||
ip: "IP 地址",
|
|
||||||
user_agent: "用户代理 (UA)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
devices: {
|
|
||||||
name: "设备",
|
|
||||||
fields: {
|
|
||||||
device_id: "设备 ID",
|
|
||||||
display_name: "设备名",
|
|
||||||
last_seen_ts: "时间戳",
|
|
||||||
last_seen_ip: "IP 地址",
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
erase: {
|
|
||||||
title: "移除 %{id}",
|
|
||||||
content: '您确定要移除设备 "%{name}"?',
|
|
||||||
success: "设备移除成功。",
|
|
||||||
failure: "出现了一个错误。",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
users_media: {
|
|
||||||
name: "媒体文件",
|
|
||||||
fields: {
|
|
||||||
media_id: "媒体文件 ID",
|
|
||||||
media_length: "长度",
|
|
||||||
media_type: "类型",
|
|
||||||
upload_name: "文件名",
|
|
||||||
quarantined_by: "被隔离",
|
|
||||||
safe_from_quarantine: "取消隔离",
|
|
||||||
created_ts: "创建",
|
|
||||||
last_access_ts: "上一次访问",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
delete_media: {
|
|
||||||
name: "媒体文件",
|
|
||||||
fields: {
|
|
||||||
before_ts: "最后访问时间",
|
|
||||||
size_gt: "大于 (字节)",
|
|
||||||
keep_profiles: "保留头像",
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
send: "删除媒体",
|
|
||||||
send_success: "请求发送成功。",
|
|
||||||
send_failure: "出现了一个错误。",
|
|
||||||
},
|
|
||||||
helper: {
|
|
||||||
send: "这个API会删除您硬盘上的本地媒体。包含了任何的本地缓存和下载的媒体备份。这个API不会影响上传到外部媒体存储库上的媒体文件。",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
pushers: {
|
|
||||||
name: "发布者",
|
|
||||||
fields: {
|
|
||||||
app: "App",
|
|
||||||
app_display_name: "App 名称",
|
|
||||||
app_id: "App ID",
|
|
||||||
device_display_name: "设备显示名",
|
|
||||||
kind: "类型",
|
|
||||||
lang: "语言",
|
|
||||||
profile_tag: "数据标签",
|
|
||||||
pushkey: "Pushkey",
|
|
||||||
data: { url: "URL" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
servernotices: {
|
|
||||||
name: "服务器提示",
|
|
||||||
send: "发送服务器提示",
|
|
||||||
fields: {
|
|
||||||
body: "信息",
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
send: "发送提示",
|
|
||||||
send_success: "服务器提示发送成功。",
|
|
||||||
send_failure: "出现了一个错误。",
|
|
||||||
},
|
|
||||||
helper: {
|
|
||||||
send: '向选中的用户发送服务器提示。服务器配置中的 "服务器提示(Server Notices)" 选项需要被设置为启用。',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
user_media_statistics: {
|
|
||||||
name: "用户的媒体文件",
|
|
||||||
fields: {
|
|
||||||
media_count: "媒体文件统计",
|
|
||||||
media_length: "媒体文件长度",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
export default zh;
|
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
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();
|
||||||
|
|||||||
@@ -2,37 +2,21 @@ 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, loginToken }) => {
|
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",
|
||||||
body: JSON.stringify(
|
body: JSON.stringify({
|
||||||
Object.assign(
|
|
||||||
{
|
|
||||||
device_id: localStorage.getItem("device_id"),
|
|
||||||
initial_device_display_name: "Synapse Admin",
|
|
||||||
},
|
|
||||||
loginToken
|
|
||||||
? {
|
|
||||||
type: "m.login.token",
|
|
||||||
token: loginToken,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
type: "m.login.password",
|
type: "m.login.password",
|
||||||
user: username,
|
user: username,
|
||||||
password: password,
|
password: password,
|
||||||
}
|
initial_device_display_name: "Synapse Admin",
|
||||||
)
|
}),
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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);
|
||||||
@@ -64,6 +48,7 @@ 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();
|
||||||
|
|||||||
+17
-171
@@ -41,16 +41,12 @@ const resourceMap = {
|
|||||||
data: "users",
|
data: "users",
|
||||||
total: json => json.total,
|
total: json => json.total,
|
||||||
create: data => ({
|
create: data => ({
|
||||||
endpoint: `/_synapse/admin/v2/users/@${encodeURIComponent(
|
endpoint: `/_synapse/admin/v2/users/${data.id}`,
|
||||||
data.id
|
|
||||||
)}:${localStorage.getItem("home_server")}`,
|
|
||||||
body: data,
|
body: data,
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
}),
|
}),
|
||||||
delete: params => ({
|
delete: params => ({
|
||||||
endpoint: `/_synapse/admin/v1/deactivate/${encodeURIComponent(
|
endpoint: `/_synapse/admin/v1/deactivate/${params.id}`,
|
||||||
params.id
|
|
||||||
)}`,
|
|
||||||
body: { erase: true },
|
body: { erase: true },
|
||||||
method: "POST",
|
method: "POST",
|
||||||
}),
|
}),
|
||||||
@@ -71,8 +67,9 @@ const resourceMap = {
|
|||||||
return json.total_rooms;
|
return json.total_rooms;
|
||||||
},
|
},
|
||||||
delete: params => ({
|
delete: params => ({
|
||||||
endpoint: `/_synapse/admin/v2/rooms/${params.id}`,
|
endpoint: `/_synapse/admin/v1/rooms/${params.id}/delete`,
|
||||||
body: { block: false },
|
body: { block: false },
|
||||||
|
method: "POST",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
reports: {
|
reports: {
|
||||||
@@ -94,12 +91,10 @@ const resourceMap = {
|
|||||||
return json.total;
|
return json.total;
|
||||||
},
|
},
|
||||||
reference: id => ({
|
reference: id => ({
|
||||||
endpoint: `/_synapse/admin/v2/users/${encodeURIComponent(id)}/devices`,
|
endpoint: `/_synapse/admin/v2/users/${id}/devices`,
|
||||||
}),
|
}),
|
||||||
delete: params => ({
|
delete: params => ({
|
||||||
endpoint: `/_synapse/admin/v2/users/${encodeURIComponent(
|
endpoint: `/_synapse/admin/v2/users/${params.user_id}/devices/${params.id}`,
|
||||||
params.user_id
|
|
||||||
)}/devices/${params.id}`,
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
connections: {
|
connections: {
|
||||||
@@ -122,26 +117,13 @@ const resourceMap = {
|
|||||||
return json.total;
|
return json.total;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
room_state: {
|
|
||||||
map: rs => ({
|
|
||||||
...rs,
|
|
||||||
id: rs.event_id,
|
|
||||||
}),
|
|
||||||
reference: id => ({
|
|
||||||
endpoint: `/_synapse/admin/v1/rooms/${id}/state`,
|
|
||||||
}),
|
|
||||||
data: "state",
|
|
||||||
total: json => {
|
|
||||||
return json.state.length;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
pushers: {
|
pushers: {
|
||||||
map: p => ({
|
map: p => ({
|
||||||
...p,
|
...p,
|
||||||
id: p.pushkey,
|
id: p.pushkey,
|
||||||
}),
|
}),
|
||||||
reference: id => ({
|
reference: id => ({
|
||||||
endpoint: `/_synapse/admin/v1/users/${encodeURIComponent(id)}/pushers`,
|
endpoint: `/_synapse/admin/v1/users/${id}/pushers`,
|
||||||
}),
|
}),
|
||||||
data: "pushers",
|
data: "pushers",
|
||||||
total: json => {
|
total: json => {
|
||||||
@@ -153,9 +135,7 @@ const resourceMap = {
|
|||||||
id: jr,
|
id: jr,
|
||||||
}),
|
}),
|
||||||
reference: id => ({
|
reference: id => ({
|
||||||
endpoint: `/_synapse/admin/v1/users/${encodeURIComponent(
|
endpoint: `/_synapse/admin/v1/users/${id}/joined_rooms`,
|
||||||
id
|
|
||||||
)}/joined_rooms`,
|
|
||||||
}),
|
}),
|
||||||
data: "joined_rooms",
|
data: "joined_rooms",
|
||||||
total: json => {
|
total: json => {
|
||||||
@@ -168,7 +148,7 @@ const resourceMap = {
|
|||||||
id: um.media_id,
|
id: um.media_id,
|
||||||
}),
|
}),
|
||||||
reference: id => ({
|
reference: id => ({
|
||||||
endpoint: `/_synapse/admin/v1/users/${encodeURIComponent(id)}/media`,
|
endpoint: `/_synapse/admin/v1/users/${id}/media`,
|
||||||
}),
|
}),
|
||||||
data: "media",
|
data: "media",
|
||||||
total: json => {
|
total: json => {
|
||||||
@@ -190,32 +170,6 @@ const resourceMap = {
|
|||||||
method: "POST",
|
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: {
|
servernotices: {
|
||||||
map: n => ({ id: n.event_id }),
|
map: n => ({ id: n.event_id }),
|
||||||
create: data => ({
|
create: data => ({
|
||||||
@@ -241,99 +195,11 @@ 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: {
|
|
||||||
path: "/_matrix/client/r0/publicRooms",
|
|
||||||
map: rd => ({
|
|
||||||
...rd,
|
|
||||||
id: rd.room_id,
|
|
||||||
public: !!rd.public,
|
|
||||||
guest_access: !!rd.guest_access,
|
|
||||||
avatar_src: mxcUrlToHttp(rd.avatar_url),
|
|
||||||
}),
|
|
||||||
data: "chunk",
|
|
||||||
total: json => {
|
|
||||||
return json.total_room_count_estimate;
|
|
||||||
},
|
|
||||||
create: params => ({
|
|
||||||
endpoint: `/_matrix/client/r0/directory/list/room/${params.id}`,
|
|
||||||
body: { visibility: "public" },
|
|
||||||
method: "PUT",
|
|
||||||
}),
|
|
||||||
delete: params => ({
|
|
||||||
endpoint: `/_matrix/client/r0/directory/list/room/${params.id}`,
|
|
||||||
body: { visibility: "private" },
|
|
||||||
method: "PUT",
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
destinations: {
|
|
||||||
path: "/_synapse/admin/v1/federation/destinations",
|
|
||||||
map: dst => ({
|
|
||||||
...dst,
|
|
||||||
id: dst.destination,
|
|
||||||
}),
|
|
||||||
data: "destinations",
|
|
||||||
total: json => {
|
|
||||||
return json.total;
|
|
||||||
},
|
|
||||||
delete: params => ({
|
|
||||||
endpoint: `/_synapse/admin/v1/federation/destinations/${params.id}/reset_connection`,
|
|
||||||
method: "POST",
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
destination_rooms: {
|
|
||||||
map: dstroom => ({
|
|
||||||
...dstroom,
|
|
||||||
id: dstroom.room_id,
|
|
||||||
}),
|
|
||||||
reference: id => ({
|
|
||||||
endpoint: `/_synapse/admin/v1/federation/destinations/${id}/rooms`,
|
|
||||||
}),
|
|
||||||
data: "rooms",
|
|
||||||
total: json => {
|
|
||||||
return json.total;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
registration_tokens: {
|
|
||||||
path: "/_synapse/admin/v1/registration_tokens",
|
|
||||||
map: rt => ({
|
|
||||||
...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
|
||||||
// to reset user_type from user, it must be null
|
if (value === null) {
|
||||||
if (value === null && key !== "user_type") {
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
@@ -350,15 +216,7 @@ function getSearchOrder(order) {
|
|||||||
const dataProvider = {
|
const dataProvider = {
|
||||||
getList: (resource, params) => {
|
getList: (resource, params) => {
|
||||||
console.log("getList " + resource);
|
console.log("getList " + resource);
|
||||||
const {
|
const { user_id, name, guests, deactivated, search_term } = params.filter;
|
||||||
user_id,
|
|
||||||
name,
|
|
||||||
guests,
|
|
||||||
deactivated,
|
|
||||||
search_term,
|
|
||||||
destination,
|
|
||||||
valid,
|
|
||||||
} = params.filter;
|
|
||||||
const { page, perPage } = params.pagination;
|
const { page, perPage } = params.pagination;
|
||||||
const { field, order } = params.sort;
|
const { field, order } = params.sort;
|
||||||
const from = (page - 1) * perPage;
|
const from = (page - 1) * perPage;
|
||||||
@@ -368,10 +226,8 @@ const dataProvider = {
|
|||||||
user_id: user_id,
|
user_id: user_id,
|
||||||
search_term: search_term,
|
search_term: search_term,
|
||||||
name: name,
|
name: name,
|
||||||
destination: destination,
|
|
||||||
guests: guests,
|
guests: guests,
|
||||||
deactivated: deactivated,
|
deactivated: deactivated,
|
||||||
valid: valid,
|
|
||||||
order_by: field,
|
order_by: field,
|
||||||
dir: getSearchOrder(order),
|
dir: getSearchOrder(order),
|
||||||
};
|
};
|
||||||
@@ -397,11 +253,9 @@ 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}/${encodeURIComponent(params.id)}`).then(
|
return jsonClient(`${endpoint_url}/${params.id}`).then(({ json }) => ({
|
||||||
({ json }) => ({
|
|
||||||
data: res.map(json),
|
data: res.map(json),
|
||||||
})
|
}));
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getMany: (resource, params) => {
|
getMany: (resource, params) => {
|
||||||
@@ -413,9 +267,7 @@ const dataProvider = {
|
|||||||
|
|
||||||
const endpoint_url = homeserver + res.path;
|
const endpoint_url = homeserver + res.path;
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
params.ids.map(id =>
|
params.ids.map(id => jsonClient(`${endpoint_url}/${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,
|
||||||
@@ -425,13 +277,10 @@ 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");
|
||||||
@@ -456,7 +305,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}/${encodeURIComponent(params.data.id)}`, {
|
return jsonClient(`${endpoint_url}/${params.data.id}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: JSON.stringify(params.data, filterNullValues),
|
body: JSON.stringify(params.data, filterNullValues),
|
||||||
}).then(({ json }) => ({
|
}).then(({ json }) => ({
|
||||||
@@ -473,13 +322,10 @@ const dataProvider = {
|
|||||||
|
|
||||||
const endpoint_url = homeserver + res.path;
|
const endpoint_url = homeserver + res.path;
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
params.ids.map(
|
params.ids.map(id => jsonClient(`${endpoint_url}/${id}`), {
|
||||||
id => jsonClient(`${endpoint_url}/${encodeURIComponent(id)}`),
|
|
||||||
{
|
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: JSON.stringify(params.data, filterNullValues),
|
body: JSON.stringify(params.data, filterNullValues),
|
||||||
}
|
})
|
||||||
)
|
|
||||||
).then(responses => ({
|
).then(responses => ({
|
||||||
data: responses.map(({ json }) => json),
|
data: responses.map(({ json }) => json),
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user