Compare commits
35 Commits
Author | SHA1 | Date | |
---|---|---|---|
311db77306 | |||
7c08f846a5 | |||
f2f096d8a5 | |||
16191d9cc8 | |||
703129f88b | |||
|
c9364f631b | ||
|
08dc5f6271 | ||
|
c9cb9aa9e0 | ||
|
25020c2d5b | ||
|
1acffdb618 | ||
|
0b4f3a60c0 | ||
|
33d29e01b1 | ||
|
a2e47cb793 | ||
|
7deb9bcf7e | ||
|
8185d7f0b0 | ||
|
37e1fcc96d | ||
|
f6e193c51c | ||
|
fa1f86491f | ||
|
b6546b89ad | ||
|
baa8e4ad95 | ||
|
bd6d5847e1 | ||
|
32c5867e2a | ||
|
f68a5de64a | ||
|
e326599da2 | ||
|
8d6852ca8c | ||
|
ee859a2926 | ||
|
0c4ca1459f | ||
|
ebba0f66f7 | ||
|
7d4d765ab4 | ||
|
dfee94af96 | ||
|
ae7f6e18e5 | ||
|
d1e9f38b14 | ||
|
4054249359 | ||
|
f240318525 | ||
|
0852b54a8e |
@ -1,7 +1,6 @@
|
|||||||
# Exclude a bunch of stuff which can make the build context a larger than it needs to be
|
# Exclude a bunch of stuff which can make the build context a larger than it needs to be
|
||||||
tests/
|
tests/
|
||||||
build/
|
build/
|
||||||
dist/
|
|
||||||
lib/
|
lib/
|
||||||
node_modules/
|
node_modules/
|
||||||
electron_app/
|
electron_app/
|
||||||
|
2
.github/workflows/build-test.yml
vendored
2
.github/workflows/build-test.yml
vendored
@ -16,6 +16,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn --frozen-lockfile
|
run: yarn --immutable
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: yarn test
|
run: yarn test
|
||||||
|
50
.github/workflows/docker-release.yml
vendored
50
.github/workflows/docker-release.yml
vendored
@ -1,51 +1,63 @@
|
|||||||
name: Create docker image(s) and push to docker hub
|
name: Create docker image(s) and push to docker hub and ghcr.io
|
||||||
|
# see https://docs.github.com/en/actions/publishing-packages/publishing-docker-images#publishing-images-to-docker-hub-and-github-packages
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
# Sequence of patterns matched against refs/heads
|
# Sequence of patterns matched against refs/heads
|
||||||
# prettier-ignore
|
# prettier-ignore
|
||||||
branches:
|
branches:
|
||||||
# Push events on master branch
|
# Push events on master branch
|
||||||
- master
|
- master
|
||||||
# Sequence of patterns matched against refs/tags
|
# Sequence of patterns matched against refs/tags
|
||||||
tags:
|
tags:
|
||||||
- '[0-9]+\.[0-9]+\.[0-9]+' # Push events to 0.X.X tag
|
- '[0-9]+\.[0-9]+\.[0-9]+' # Push events to 0.X.X tag
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker:
|
docker:
|
||||||
|
name: Push Docker image to multiple registries
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-tags: true
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- name: Calculate docker image tag
|
|
||||||
id: set-tag
|
- name: Login to GHCR
|
||||||
run: |
|
uses: docker/login-action@v3
|
||||||
case "${GITHUB_REF}" in
|
with:
|
||||||
refs/heads/master|refs/heads/main)
|
registry: ghcr.io
|
||||||
tag=latest
|
username: ${{ github.actor }}
|
||||||
;;
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
refs/tags/*)
|
|
||||||
tag=${GITHUB_REF#refs/tags/}
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
;;
|
id: meta
|
||||||
*)
|
uses: docker/metadata-action@v5
|
||||||
tag=${GITHUB_SHA}
|
with:
|
||||||
;;
|
images: |
|
||||||
esac
|
awesometechnologies/synapse-admin
|
||||||
echo "::set-output name=tag::$tag"
|
ghcr.io/${{ github.repository }}
|
||||||
|
|
||||||
- name: Build and Push Tag
|
- name: Build and Push Tag
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
tags: "awesometechnologies/synapse-admin:${{ steps.set-tag.outputs.tag }}"
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
4
.github/workflows/edge_ghpage.yml
vendored
4
.github/workflows/edge_ghpage.yml
vendored
@ -11,12 +11,14 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout 🛎️
|
- name: Checkout 🛎️
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-tags: true
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
- name: Install and Build 🔧
|
- name: Install and Build 🔧
|
||||||
run: |
|
run: |
|
||||||
yarn install
|
yarn install --immutable
|
||||||
yarn build
|
yarn build
|
||||||
|
|
||||||
- name: Deploy 🚀
|
- name: Deploy 🚀
|
||||||
|
6
.github/workflows/github-release.yml
vendored
6
.github/workflows/github-release.yml
vendored
@ -14,17 +14,19 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-tags: true
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
- run: yarn install
|
- run: yarn install --immutable
|
||||||
- run: yarn build
|
- run: yarn build
|
||||||
- run: |
|
- run: |
|
||||||
version=`git describe --dirty --tags || echo unknown`
|
version=`git describe --dirty --tags || echo unknown`
|
||||||
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
|
- uses: softprops/action-gh-release@3198ee18f814cdf787321b4a32a26ddbf37acc52
|
||||||
with:
|
with:
|
||||||
files: dist/*.tar.gz
|
files: dist/*.tar.gz
|
||||||
env:
|
env:
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -10,7 +10,6 @@
|
|||||||
|
|
||||||
# production
|
# production
|
||||||
/build
|
/build
|
||||||
/dist
|
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
dist: focal
|
|
||||||
language: node_js
|
|
||||||
node_js:
|
|
||||||
- 18
|
|
||||||
|
|
||||||
cache: yarn
|
|
@ -1,12 +1,12 @@
|
|||||||
# Builder
|
# Builder
|
||||||
FROM node:lts as builder
|
FROM node:lts as builder
|
||||||
|
LABEL org.opencontainers.image.url=https://github.com/Awesome-Technologies/synapse-admin org.opencontainers.image.source=https://github.com/Awesome-Technologies/synapse-admin
|
||||||
ARG REACT_APP_SERVER
|
ARG REACT_APP_SERVER
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
COPY . /src
|
COPY . /src
|
||||||
RUN yarn --network-timeout=300000 install
|
RUN yarn --network-timeout=300000 install --immutable
|
||||||
RUN REACT_APP_SERVER=$REACT_APP_SERVER yarn build
|
RUN REACT_APP_SERVER=$REACT_APP_SERVER yarn build
|
||||||
|
|
||||||
|
|
||||||
|
149
index.html
149
index.html
@ -1,149 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<link rel="shortcut icon" href="./favicon.ico" />
|
|
||||||
<meta
|
|
||||||
name="viewport"
|
|
||||||
content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no"
|
|
||||||
/>
|
|
||||||
<meta name="theme-color" content="#000000" />
|
|
||||||
<meta
|
|
||||||
name="description"
|
|
||||||
content="Synapse-Admin"
|
|
||||||
/>
|
|
||||||
<!--
|
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
|
||||||
-->
|
|
||||||
<link rel="manifest" href="./manifest.json" />
|
|
||||||
<!--
|
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
|
||||||
Only files inside the `public` folder can be referenced from the HTML.
|
|
||||||
|
|
||||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
|
||||||
work correctly both with client-side routing and a non-root public URL.
|
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
|
||||||
-->
|
|
||||||
<title>Synapse-Admin</title>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-direction: column;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background-color: #fafafa;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* CSS Spinner from https://projects.lukehaas.me/css-loaders/ */
|
|
||||||
|
|
||||||
.loader,
|
|
||||||
.loader:before,
|
|
||||||
.loader:after {
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader {
|
|
||||||
color: #283593;
|
|
||||||
font-size: 11px;
|
|
||||||
text-indent: -99999em;
|
|
||||||
margin: 55px auto;
|
|
||||||
position: relative;
|
|
||||||
width: 10em;
|
|
||||||
height: 10em;
|
|
||||||
box-shadow: inset 0 0 0 1em;
|
|
||||||
-webkit-transform: translateZ(0);
|
|
||||||
-ms-transform: translateZ(0);
|
|
||||||
transform: translateZ(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader:before,
|
|
||||||
.loader:after {
|
|
||||||
position: absolute;
|
|
||||||
content: '';
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader:before {
|
|
||||||
width: 5.2em;
|
|
||||||
height: 10.2em;
|
|
||||||
background: #fafafa;
|
|
||||||
border-radius: 10.2em 0 0 10.2em;
|
|
||||||
top: -0.1em;
|
|
||||||
left: -0.1em;
|
|
||||||
-webkit-transform-origin: 5.2em 5.1em;
|
|
||||||
transform-origin: 5.2em 5.1em;
|
|
||||||
-webkit-animation: load2 2s infinite ease 1.5s;
|
|
||||||
animation: load2 2s infinite ease 1.5s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader:after {
|
|
||||||
width: 5.2em;
|
|
||||||
height: 10.2em;
|
|
||||||
background: #fafafa;
|
|
||||||
border-radius: 0 10.2em 10.2em 0;
|
|
||||||
top: -0.1em;
|
|
||||||
left: 5.1em;
|
|
||||||
-webkit-transform-origin: 0px 5.1em;
|
|
||||||
transform-origin: 0px 5.1em;
|
|
||||||
-webkit-animation: load2 2s infinite ease;
|
|
||||||
animation: load2 2s infinite ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
@-webkit-keyframes load2 {
|
|
||||||
0% {
|
|
||||||
-webkit-transform: rotate(0deg);
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
-webkit-transform: rotate(360deg);
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes load2 {
|
|
||||||
0% {
|
|
||||||
-webkit-transform: rotate(0deg);
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
-webkit-transform: rotate(360deg);
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" />
|
|
||||||
<link
|
|
||||||
href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
||||||
<div id="root">
|
|
||||||
<div class="loader-container">
|
|
||||||
<div class="loader">Loading...</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<footer
|
|
||||||
style="position: relative; z-index: 2; height: 2em; margin-top: -2em; line-height: 2em; background-color: #eee; border: 0.5px solid #ddd">
|
|
||||||
<a id="copyright" href="https://github.com/Awesome-Technologies/synapse-admin"
|
|
||||||
style="margin-left: 1em; color: #888; font-family: Roboto, Helvetica, Arial, sans-serif; font-weight: 100; font-size: 0.8em; text-decoration: none;">
|
|
||||||
Synapse-Admin <b>(%REACT_APP_VERSION%)</b> by Awesome Technologies Innovationslabor GmbH
|
|
||||||
</a>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
<script type="module" src="/src/index.jsx"></script>
|
|
||||||
</html>
|
|
32
package.json
32
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "synapse-admin",
|
"name": "synapse-admin",
|
||||||
"version": "0.9.0",
|
"version": "0.9.2",
|
||||||
"description": "Admin GUI for the Matrix.org server Synapse",
|
"description": "Admin GUI for the Matrix.org server Synapse",
|
||||||
"author": "Awesome Technologies Innovationslabor GmbH",
|
"author": "Awesome Technologies Innovationslabor GmbH",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
@ -11,33 +11,34 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/jest-dom": "^6.0.0",
|
"@testing-library/jest-dom": "^6.0.0",
|
||||||
"@testing-library/react": "^14.0.0",
|
"@testing-library/react": "^15.0.2",
|
||||||
"@testing-library/user-event": "^14.5.2",
|
"@testing-library/user-event": "^14.5.2",
|
||||||
"@vitejs/plugin-react": "^4.0.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint": "^8.56.0",
|
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
"eslint-config-react-app": "^7.0.1",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"jest-fetch-mock": "^3.0.3",
|
"jest-fetch-mock": "^3.0.3",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5"
|
||||||
"vite": "^4.0.0"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mui/icons-material": "^5.15.7",
|
"@mui/icons-material": "^5.15.15",
|
||||||
"@mui/material": "^5.15.7",
|
"@mui/material": "^5.15.15",
|
||||||
"@mui/styles": "^5.15.8",
|
"@mui/styles": "^5.15.15",
|
||||||
"papaparse": "^5.4.1",
|
"papaparse": "^5.4.1",
|
||||||
"ra-language-chinese": "^2.0.10",
|
"ra-language-chinese": "^2.0.10",
|
||||||
"ra-language-french": "^4.16.9",
|
"ra-language-french": "^4.16.15",
|
||||||
"ra-language-german": "^3.13.4",
|
"ra-language-german": "^3.13.4",
|
||||||
"ra-language-italian": "^3.13.1",
|
"ra-language-italian": "^3.13.1",
|
||||||
|
"ra-language-farsi": "^4.2.0",
|
||||||
|
"ra-language-russian": "^4.14.2",
|
||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
"react-admin": "^4.16.9",
|
"react-admin": "^4.16.15",
|
||||||
"react-dom": "^18.0.0"
|
"react-dom": "^18.0.0",
|
||||||
|
"react-scripts": "^5.0.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "REACT_APP_VERSION=$(git describe --tags) vite serve",
|
"start": "REACT_APP_VERSION=$(git describe --tags) react-scripts start",
|
||||||
"build": "REACT_APP_VERSION=$(git describe --tags) vite build",
|
"build": "REACT_APP_VERSION=$(git describe --tags) react-scripts build",
|
||||||
"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",
|
||||||
@ -45,7 +46,8 @@
|
|||||||
"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",
|
||||||
"test": "yarn test:style && yarn test:lint && yarn test:code"
|
"test": "yarn test:style && yarn test:lint && yarn test:code",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": "react-app"
|
"extends": "react-app"
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
id,displayname,password,is_guest,admin,deactivated
|
id,displayname,password,is_guest,admin,deactivated
|
||||||
@testuser22:example.org,Jane Doe,secretpassword,false,true,false
|
testuser22,Jane Doe,secretpassword,false,true,false
|
||||||
,John Doe,,false,false,false
|
,John Doe,,false,false,false
|
||||||
|
|
@ -46,4 +46,4 @@
|
|||||||
</a>
|
</a>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -14,7 +14,6 @@ import userMediaStats from "./components/statistics";
|
|||||||
import reports from "./components/EventReports";
|
import reports from "./components/EventReports";
|
||||||
import roomDirectory from "./components/RoomDirectory";
|
import roomDirectory from "./components/RoomDirectory";
|
||||||
import destinations from "./components/destinations";
|
import destinations from "./components/destinations";
|
||||||
import registrationToken from "./components/RegistrationTokens";
|
|
||||||
import LoginPage from "./components/LoginPage";
|
import LoginPage from "./components/LoginPage";
|
||||||
import { ImportFeature } from "./components/ImportFeature";
|
import { ImportFeature } from "./components/ImportFeature";
|
||||||
import { Route } from "react-router-dom";
|
import { Route } from "react-router-dom";
|
||||||
@ -23,8 +22,7 @@ import englishMessages from "./i18n/en";
|
|||||||
import frenchMessages from "./i18n/fr";
|
import frenchMessages from "./i18n/fr";
|
||||||
import chineseMessages from "./i18n/zh";
|
import chineseMessages from "./i18n/zh";
|
||||||
import italianMessages from "./i18n/it";
|
import italianMessages from "./i18n/it";
|
||||||
|
import russianMessages from "./i18n/ru";
|
||||||
const fixed_base_url = undefined; // FIXME: process.env.REACT_APP_SERVER;
|
|
||||||
|
|
||||||
// TODO: Can we use lazy loading together with browser locale?
|
// TODO: Can we use lazy loading together with browser locale?
|
||||||
const messages = {
|
const messages = {
|
||||||
@ -33,6 +31,7 @@ const messages = {
|
|||||||
fr: frenchMessages,
|
fr: frenchMessages,
|
||||||
it: italianMessages,
|
it: italianMessages,
|
||||||
zh: chineseMessages,
|
zh: chineseMessages,
|
||||||
|
ru: russianMessages
|
||||||
};
|
};
|
||||||
const i18nProvider = polyglotI18nProvider(
|
const i18nProvider = polyglotI18nProvider(
|
||||||
locale => (messages[locale] ? messages[locale] : messages.en),
|
locale => (messages[locale] ? messages[locale] : messages.en),
|
||||||
@ -44,7 +43,7 @@ const App = () => (
|
|||||||
disableTelemetry
|
disableTelemetry
|
||||||
requireAuth
|
requireAuth
|
||||||
loginPage={LoginPage}
|
loginPage={LoginPage}
|
||||||
authProvider={authProvider(fixed_base_url)}
|
authProvider={authProvider}
|
||||||
dataProvider={dataProvider}
|
dataProvider={dataProvider}
|
||||||
i18nProvider={i18nProvider}
|
i18nProvider={i18nProvider}
|
||||||
>
|
>
|
||||||
@ -57,7 +56,6 @@ const App = () => (
|
|||||||
<Resource {...reports} />
|
<Resource {...reports} />
|
||||||
<Resource {...roomDirectory} />
|
<Resource {...roomDirectory} />
|
||||||
<Resource {...destinations} />
|
<Resource {...destinations} />
|
||||||
<Resource {...registrationToken} />
|
|
||||||
<Resource name="connections" />
|
<Resource name="connections" />
|
||||||
<Resource name="devices" />
|
<Resource name="devices" />
|
||||||
<Resource name="room_members" />
|
<Resource name="room_members" />
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { render } from "@testing-library/react";
|
import { render, screen } from "@testing-library/react";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
|
|
||||||
describe("App", () => {
|
describe("App", () => {
|
||||||
it("renders", () => {
|
it("renders", async () => {
|
||||||
render(<App />);
|
render(<App />);
|
||||||
|
await screen.findAllByText("Welcome to Synapse-admin");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -7,7 +7,6 @@ import {
|
|||||||
useLogin,
|
useLogin,
|
||||||
useNotify,
|
useNotify,
|
||||||
useLocaleState,
|
useLocaleState,
|
||||||
useStoreContext,
|
|
||||||
useTranslate,
|
useTranslate,
|
||||||
PasswordInput,
|
PasswordInput,
|
||||||
TextInput,
|
TextInput,
|
||||||
@ -22,13 +21,13 @@ import {
|
|||||||
CircularProgress,
|
CircularProgress,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
Select,
|
Select,
|
||||||
TextField,
|
|
||||||
Typography,
|
Typography,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { styled } from "@mui/material/styles";
|
import { styled } from "@mui/material/styles";
|
||||||
import LockIcon from "@mui/icons-material/Lock";
|
import LockIcon from "@mui/icons-material/Lock";
|
||||||
import {
|
import {
|
||||||
getServerVersion,
|
getServerVersion,
|
||||||
|
getSupportedFeatures,
|
||||||
getSupportedLoginFlows,
|
getSupportedLoginFlows,
|
||||||
getWellKnownUrl,
|
getWellKnownUrl,
|
||||||
isValidBaseUrl,
|
isValidBaseUrl,
|
||||||
@ -38,7 +37,7 @@ import {
|
|||||||
const FormBox = styled(Box)(({ theme }) => ({
|
const FormBox = styled(Box)(({ theme }) => ({
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
minHeight: "calc(100vh - 1em)",
|
minHeight: "calc(100vh - 1rem)",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "flex-start",
|
justifyContent: "flex-start",
|
||||||
background: "url(./images/floating-cogs.svg)",
|
background: "url(./images/floating-cogs.svg)",
|
||||||
@ -47,12 +46,12 @@ const FormBox = styled(Box)(({ theme }) => ({
|
|||||||
backgroundSize: "cover",
|
backgroundSize: "cover",
|
||||||
|
|
||||||
[`& .card`]: {
|
[`& .card`]: {
|
||||||
minWidth: "30em",
|
width: "30rem",
|
||||||
marginTop: "6em",
|
marginTop: "6rem",
|
||||||
marginBottom: "6em",
|
marginBottom: "6rem",
|
||||||
},
|
},
|
||||||
[`& .avatar`]: {
|
[`& .avatar`]: {
|
||||||
margin: "1em",
|
margin: "1rem",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
},
|
},
|
||||||
@ -61,36 +60,43 @@ const FormBox = styled(Box)(({ theme }) => ({
|
|||||||
},
|
},
|
||||||
[`& .hint`]: {
|
[`& .hint`]: {
|
||||||
marginTop: "1em",
|
marginTop: "1em",
|
||||||
|
marginBottom: "1em",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
color: theme.palette.grey[600],
|
color: theme.palette.grey[600],
|
||||||
},
|
},
|
||||||
[`& .form`]: {
|
[`& .form`]: {
|
||||||
padding: "0 1em 1em 1em",
|
padding: "0 1rem 1rem 1rem",
|
||||||
},
|
},
|
||||||
[`& .input`]: {
|
[`& .select`]: {
|
||||||
marginTop: "1em",
|
marginBottom: "2rem",
|
||||||
},
|
},
|
||||||
[`& .actions`]: {
|
[`& .actions`]: {
|
||||||
padding: "0 1em 1em 1em",
|
padding: "0 1rem 1rem 1rem",
|
||||||
},
|
},
|
||||||
[`& .serverVersion`]: {
|
[`& .serverVersion`]: {
|
||||||
color: theme.palette.grey[500],
|
color: theme.palette.grey[500],
|
||||||
fontFamily: "Roboto, Helvetica, Arial, sans-serif",
|
fontFamily: "Roboto, Helvetica, Arial, sans-serif",
|
||||||
marginBottom: "1em",
|
marginLeft: "0.5rem",
|
||||||
marginLeft: "0.5em",
|
},
|
||||||
|
[`& .matrixVersions`]: {
|
||||||
|
color: theme.palette.grey[500],
|
||||||
|
fontFamily: "Roboto, Helvetica, Arial, sans-serif",
|
||||||
|
fontSize: "0.8rem",
|
||||||
|
marginBottom: "1rem",
|
||||||
|
marginLeft: "0.5rem",
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const LoginPage = ({ cfg_base_url }) => {
|
const LoginPage = () => {
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
const store = useStoreContext();
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [supportPassAuth, setSupportPassAuth] = useState(true);
|
const [supportPassAuth, setSupportPassAuth] = useState(true);
|
||||||
const [locale, setLocale] = useLocaleState();
|
const [locale, setLocale] = useLocaleState();
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
const base_url = store.getItem("base_url");
|
const base_url = localStorage.getItem("base_url");
|
||||||
|
const cfg_base_url = process.env.REACT_APP_SERVER;
|
||||||
const [ssoBaseUrl, setSSOBaseUrl] = useState("");
|
const [ssoBaseUrl, setSSOBaseUrl] = useState("");
|
||||||
const loginToken = /\?loginToken=([a-zA-Z0-9_-]+)/.exec(window.location.href);
|
const loginToken = /\?loginToken=([a-zA-Z0-9_-]+)/.exec(window.location.href);
|
||||||
|
|
||||||
@ -103,8 +109,8 @@ const LoginPage = ({ cfg_base_url }) => {
|
|||||||
"",
|
"",
|
||||||
window.location.href.replace(loginToken[0], "#").split("#")[0]
|
window.location.href.replace(loginToken[0], "#").split("#")[0]
|
||||||
);
|
);
|
||||||
const baseUrl = store.getItem("sso_base_url");
|
const baseUrl = localStorage.getItem("sso_base_url");
|
||||||
store.removeItem("sso_base_url");
|
localStorage.removeItem("sso_base_url");
|
||||||
if (baseUrl) {
|
if (baseUrl) {
|
||||||
const auth = {
|
const auth = {
|
||||||
base_url: baseUrl,
|
base_url: baseUrl,
|
||||||
@ -128,20 +134,6 @@ const LoginPage = ({ cfg_base_url }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderInput = ({
|
|
||||||
meta: { touched, error } = {},
|
|
||||||
input: { ...inputProps },
|
|
||||||
...props
|
|
||||||
}) => (
|
|
||||||
<TextField
|
|
||||||
error={!!(touched && error)}
|
|
||||||
helperText={touched && error}
|
|
||||||
{...inputProps}
|
|
||||||
{...props}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const validateBaseUrl = value => {
|
const validateBaseUrl = value => {
|
||||||
if (!value.match(/^(http|https):\/\//)) {
|
if (!value.match(/^(http|https):\/\//)) {
|
||||||
return translate("synapseadmin.auth.protocol_error");
|
return translate("synapseadmin.auth.protocol_error");
|
||||||
@ -170,7 +162,7 @@ const LoginPage = ({ cfg_base_url }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSSO = () => {
|
const handleSSO = () => {
|
||||||
store.setItem("sso_base_url", ssoBaseUrl);
|
localStorage.setItem("sso_base_url", ssoBaseUrl);
|
||||||
const ssoFullUrl = `${ssoBaseUrl}/_matrix/client/r0/login/sso/redirect?redirectUrl=${encodeURIComponent(
|
const ssoFullUrl = `${ssoBaseUrl}/_matrix/client/r0/login/sso/redirect?redirectUrl=${encodeURIComponent(
|
||||||
window.location.href
|
window.location.href
|
||||||
)}`;
|
)}`;
|
||||||
@ -180,6 +172,7 @@ const LoginPage = ({ cfg_base_url }) => {
|
|||||||
const UserData = ({ formData }) => {
|
const UserData = ({ formData }) => {
|
||||||
const form = useFormContext();
|
const form = useFormContext();
|
||||||
const [serverVersion, setServerVersion] = useState("");
|
const [serverVersion, setServerVersion] = useState("");
|
||||||
|
const [matrixVersions, setMatrixVersions] = useState("");
|
||||||
|
|
||||||
const handleUsernameChange = _ => {
|
const handleUsernameChange = _ => {
|
||||||
if (formData.base_url || cfg_base_url) return;
|
if (formData.base_url || cfg_base_url) return;
|
||||||
@ -201,6 +194,14 @@ const LoginPage = ({ cfg_base_url }) => {
|
|||||||
)
|
)
|
||||||
.catch(() => setServerVersion(""));
|
.catch(() => setServerVersion(""));
|
||||||
|
|
||||||
|
getSupportedFeatures(formData.base_url)
|
||||||
|
.then(features =>
|
||||||
|
setMatrixVersions(
|
||||||
|
`${translate("synapseadmin.auth.supports_specs")} ${features.versions.join(", ")}`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.catch(() => setMatrixVersions(""));
|
||||||
|
|
||||||
// Set SSO Url
|
// Set SSO Url
|
||||||
getSupportedLoginFlows(formData.base_url)
|
getSupportedLoginFlows(formData.base_url)
|
||||||
.then(loginFlows => {
|
.then(loginFlows => {
|
||||||
@ -220,7 +221,6 @@ const LoginPage = ({ cfg_base_url }) => {
|
|||||||
<TextInput
|
<TextInput
|
||||||
autoFocus
|
autoFocus
|
||||||
name="username"
|
name="username"
|
||||||
component={renderInput}
|
|
||||||
label="ra.auth.username"
|
label="ra.auth.username"
|
||||||
disabled={loading || !supportPassAuth}
|
disabled={loading || !supportPassAuth}
|
||||||
onBlur={handleUsernameChange}
|
onBlur={handleUsernameChange}
|
||||||
@ -233,7 +233,6 @@ const LoginPage = ({ cfg_base_url }) => {
|
|||||||
<Box>
|
<Box>
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
name="password"
|
name="password"
|
||||||
component={renderInput}
|
|
||||||
label="ra.auth.password"
|
label="ra.auth.password"
|
||||||
type="password"
|
type="password"
|
||||||
disabled={loading || !supportPassAuth}
|
disabled={loading || !supportPassAuth}
|
||||||
@ -246,9 +245,8 @@ const LoginPage = ({ cfg_base_url }) => {
|
|||||||
<Box>
|
<Box>
|
||||||
<TextInput
|
<TextInput
|
||||||
name="base_url"
|
name="base_url"
|
||||||
component={renderInput}
|
|
||||||
label="synapseadmin.auth.base_url"
|
label="synapseadmin.auth.base_url"
|
||||||
disabled={cfg_base_url != null || loading}
|
disabled={cfg_base_url || loading}
|
||||||
resettable
|
resettable
|
||||||
fullWidth
|
fullWidth
|
||||||
className="input"
|
className="input"
|
||||||
@ -256,6 +254,7 @@ const LoginPage = ({ cfg_base_url }) => {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Typography className="serverVersion">{serverVersion}</Typography>
|
<Typography className="serverVersion">{serverVersion}</Typography>
|
||||||
|
<Typography className="matrixVersions">{matrixVersions}</Typography>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -286,13 +285,15 @@ const LoginPage = ({ cfg_base_url }) => {
|
|||||||
}}
|
}}
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="input"
|
className="select"
|
||||||
>
|
>
|
||||||
<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="fr">Français</MenuItem>
|
||||||
<MenuItem value="it">Italiano</MenuItem>
|
<MenuItem value="it">Italiano</MenuItem>
|
||||||
<MenuItem value="zh">简体中文</MenuItem>
|
<MenuItem value="zh">简体中文</MenuItem>
|
||||||
|
<MenuItem value="fa">Persian(فارسی)</MenuItem>
|
||||||
|
<MenuItem value="ru">Русский</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
<FormDataConsumer>
|
<FormDataConsumer>
|
||||||
{formDataProps => <UserData {...formDataProps} />}
|
{formDataProps => <UserData {...formDataProps} />}
|
||||||
|
@ -132,7 +132,7 @@ export const RegistrationTokenEdit = props => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
const resource = {
|
const resource = {
|
||||||
name: "users",
|
name: "registration_tokens",
|
||||||
icon: RegistrationTokenIcon,
|
icon: RegistrationTokenIcon,
|
||||||
list: RegistrationTokenList,
|
list: RegistrationTokenList,
|
||||||
edit: RegistrationTokenEdit,
|
edit: RegistrationTokenEdit,
|
||||||
|
@ -134,6 +134,7 @@ export const RoomShow = props => {
|
|||||||
<Datagrid
|
<Datagrid
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
rowClick={(id, resource, record) => "/users/" + id}
|
rowClick={(id, resource, record) => "/users/" + id}
|
||||||
|
bulkActionButtons={false}
|
||||||
>
|
>
|
||||||
<TextField
|
<TextField
|
||||||
source="id"
|
source="id"
|
||||||
@ -218,7 +219,7 @@ export const RoomShow = props => {
|
|||||||
target="room_id"
|
target="room_id"
|
||||||
addLabel={false}
|
addLabel={false}
|
||||||
>
|
>
|
||||||
<Datagrid style={{ width: "100%" }}>
|
<Datagrid style={{ width: "100%" }} bulkActionButtons={false}>
|
||||||
<TextField source="type" sortable={false} />
|
<TextField source="type" sortable={false} />
|
||||||
<DateField
|
<DateField
|
||||||
source="origin_server_ts"
|
source="origin_server_ts"
|
||||||
@ -256,7 +257,7 @@ export const RoomShow = props => {
|
|||||||
target="room_id"
|
target="room_id"
|
||||||
addLabel={false}
|
addLabel={false}
|
||||||
>
|
>
|
||||||
<Datagrid style={{ width: "100%" }}>
|
<Datagrid style={{ width: "100%" }} bulkActionButtons={false}>
|
||||||
<TextField source="id" sortable={false} />
|
<TextField source="id" sortable={false} />
|
||||||
<DateField
|
<DateField
|
||||||
source="received_ts"
|
source="received_ts"
|
||||||
|
@ -417,7 +417,7 @@ export const UserEdit = props => {
|
|||||||
source="devices[].sessions[0].connections"
|
source="devices[].sessions[0].connections"
|
||||||
label="resources.connections.name"
|
label="resources.connections.name"
|
||||||
>
|
>
|
||||||
<Datagrid style={{ width: "100%" }}>
|
<Datagrid style={{ width: "100%" }} bulkActionButtons={false}>
|
||||||
<TextField source="ip" sortable={false} />
|
<TextField source="ip" sortable={false} />
|
||||||
<DateField
|
<DateField
|
||||||
source="last_seen"
|
source="last_seen"
|
||||||
@ -480,6 +480,7 @@ export const UserEdit = props => {
|
|||||||
<Datagrid
|
<Datagrid
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
rowClick={(id, resource, record) => "/rooms/" + id + "/show"}
|
rowClick={(id, resource, record) => "/rooms/" + id + "/show"}
|
||||||
|
bulkActionButtons={false}
|
||||||
>
|
>
|
||||||
<TextField
|
<TextField
|
||||||
source="id"
|
source="id"
|
||||||
@ -509,7 +510,7 @@ export const UserEdit = props => {
|
|||||||
target="user_id"
|
target="user_id"
|
||||||
addLabel={false}
|
addLabel={false}
|
||||||
>
|
>
|
||||||
<Datagrid style={{ width: "100%" }}>
|
<Datagrid style={{ width: "100%" }} bulkActionButtons={false}>
|
||||||
<TextField source="kind" sortable={false} />
|
<TextField source="kind" sortable={false} />
|
||||||
<TextField source="app_display_name" sortable={false} />
|
<TextField source="app_display_name" sortable={false} />
|
||||||
<TextField source="app_id" sortable={false} />
|
<TextField source="app_id" sortable={false} />
|
||||||
|
@ -7,6 +7,7 @@ const de = {
|
|||||||
base_url: "Heimserver URL",
|
base_url: "Heimserver URL",
|
||||||
welcome: "Willkommen bei Synapse-admin",
|
welcome: "Willkommen bei Synapse-admin",
|
||||||
server_version: "Synapse Version",
|
server_version: "Synapse Version",
|
||||||
|
supports_specs: "unterstützt Matrix-Specs",
|
||||||
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",
|
||||||
|
@ -7,6 +7,7 @@ const en = {
|
|||||||
base_url: "Homeserver URL",
|
base_url: "Homeserver URL",
|
||||||
welcome: "Welcome to Synapse-admin",
|
welcome: "Welcome to Synapse-admin",
|
||||||
server_version: "Synapse version",
|
server_version: "Synapse version",
|
||||||
|
supports_specs: "supports Matrix specs",
|
||||||
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",
|
||||||
|
382
src/i18n/fa.js
Normal file
382
src/i18n/fa.js
Normal file
@ -0,0 +1,382 @@
|
|||||||
|
import farsiMessages from "ra-language-farsi";
|
||||||
|
|
||||||
|
const fa = {
|
||||||
|
...farsiMessages,
|
||||||
|
synapseadmin: {
|
||||||
|
auth: {
|
||||||
|
base_url: "آدرس سرور",
|
||||||
|
welcome: "به پنل مدیریت سیناپس خوش آمدید!",
|
||||||
|
server_version: "نسخه",
|
||||||
|
username_error: "لطفاً شناسه کاربر را وارد کنید: '@user:domain'",
|
||||||
|
protocol_error: "URL باید با 'http://' یا 'https://' شروع شود",
|
||||||
|
url_error: "آدرس وارد شده یک سرور معتبر نیست",
|
||||||
|
sso_sign_in: "با SSO وارد شوید",
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
invalid_user_id: "بخش محلی یک شناسه کاربری ماتریکس بدون سرور خانگی.",
|
||||||
|
tabs: { sso: "SSO" },
|
||||||
|
},
|
||||||
|
rooms: {
|
||||||
|
tabs: {
|
||||||
|
basic: "اصلی",
|
||||||
|
members: "اعضا",
|
||||||
|
detail: "جزئیات",
|
||||||
|
permission: "مجوزها",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
reports: { tabs: { basic: "اصلی", detail: "جزئیات" } },
|
||||||
|
},
|
||||||
|
import_users: {
|
||||||
|
error: {
|
||||||
|
at_entry: "در هنگام ورود %{entry}: %{message}",
|
||||||
|
error: "Error",
|
||||||
|
required_field: "فیلد الزامی '%{field}' وجود ندارد",
|
||||||
|
invalid_value:
|
||||||
|
"خطا در خط %{row}. '%{field}' فیلد ممکن است فقط 'درست' یا 'نادرست' باشد",
|
||||||
|
unreasonably_big:
|
||||||
|
"از بارگذاری فایل هایی با حجم غیر منطقی خودداری کنید %{size} مگابایت",
|
||||||
|
already_in_progress: "یک بارگذاری از قبل در حال انجام است",
|
||||||
|
id_exits: "شناسه %{id} موجود است",
|
||||||
|
},
|
||||||
|
title: "کاربران را از طریق فایل CSV وارد کنید",
|
||||||
|
goToPdf: "رفتن به PDF",
|
||||||
|
cards: {
|
||||||
|
importstats: {
|
||||||
|
header: "وارد کردن کاربران",
|
||||||
|
users_total:
|
||||||
|
"%{smart_count} user in CSV file |||| %{smart_count} users in CSV file",
|
||||||
|
guest_count: "%{smart_count} guest |||| %{smart_count} guests",
|
||||||
|
admin_count: "%{smart_count} admin |||| %{smart_count} admins",
|
||||||
|
},
|
||||||
|
conflicts: {
|
||||||
|
header: "استراتژی متغارض",
|
||||||
|
mode: {
|
||||||
|
stop: "توقف",
|
||||||
|
skip: "نمایش خطا و رد شدن",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ids: {
|
||||||
|
header: "شناسنامه ها",
|
||||||
|
all_ids_present: "شناسه های موجود در هر ورودی",
|
||||||
|
count_ids_present:
|
||||||
|
"%{smart_count} ورود با شناسه |||| %{smart_count} ورودی با شناسه",
|
||||||
|
mode: {
|
||||||
|
ignore: "شناسه ها را در CSV نادیده بگیر و شناسه های جدید ایجاد کن",
|
||||||
|
update: "سوابق موجود را به روز کنید",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
passwords: {
|
||||||
|
header: "رمز عبور",
|
||||||
|
all_passwords_present: "رمزهای عبور موجود در هر ورودی",
|
||||||
|
count_passwords_present:
|
||||||
|
"%{smart_count} ورود با رمز عبور |||| %{smart_count} ورودی با رمز عبور",
|
||||||
|
use_passwords: "از پسوردهای CSV استفاده کنید",
|
||||||
|
},
|
||||||
|
upload: {
|
||||||
|
header: "Input CSV file",
|
||||||
|
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: "شناسه کاربر",
|
||||||
|
name: "نام",
|
||||||
|
is_guest: "مهمان",
|
||||||
|
admin: "مدیر سرور",
|
||||||
|
deactivated: "غیرفعال",
|
||||||
|
guests: "نمایش مهمانان",
|
||||||
|
show_deactivated: "نمایش کاربران غیرفعال شده",
|
||||||
|
user_id: "جستجوی کاربر",
|
||||||
|
displayname: "نام نمایشی",
|
||||||
|
password: "رمز عبور",
|
||||||
|
avatar_url: "آواتار سرور",
|
||||||
|
avatar_src: "آواتار",
|
||||||
|
medium: "متوسط",
|
||||||
|
threepids: "سرویس احراز هویت",
|
||||||
|
address: "آدرس",
|
||||||
|
creation_ts_ms: "ساخته شده در",
|
||||||
|
consent_version: "Consent نسخه",
|
||||||
|
auth_provider: "ارائه دهنده",
|
||||||
|
user_type: "نوع کاربر",
|
||||||
|
},
|
||||||
|
helper: {
|
||||||
|
password: "با تغییر رمز عبور کاربر از تمام دستگاه ها خارج می شود.",
|
||||||
|
deactivate: "برای فعالسازی مجدد حساب باید رمز عبور وارد کنید.",
|
||||||
|
erase: "کاربر را به عنوان GDPR پاک شده علامت گذاری کنید",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
erase: "پاک کردن اطلاعات کاربر",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rooms: {
|
||||||
|
name: "اتاق |||| اتاق ها",
|
||||||
|
fields: {
|
||||||
|
room_id: "شناسه اتاق",
|
||||||
|
name: "نام",
|
||||||
|
canonical_alias: "نام مستعار",
|
||||||
|
joined_members: "اعضا",
|
||||||
|
joined_local_members: "اعضای محلی",
|
||||||
|
joined_local_devices: "دستگاه های محلی",
|
||||||
|
state_events: "رویدادهای حالت / پیچیدگی",
|
||||||
|
version: "نسخه",
|
||||||
|
is_encrypted: "رمزگذاری شده است",
|
||||||
|
encryption: "رمزگذاری",
|
||||||
|
federatable: "Federatable",
|
||||||
|
public: "قابل مشاهده در فهرست اتاق",
|
||||||
|
creator: "سازنده",
|
||||||
|
join_rules: "به قوانین بپیوندید",
|
||||||
|
guest_access: "دسترسی مهمان",
|
||||||
|
history_visibility: "مشاهده تاریخچه",
|
||||||
|
topic: "موضوع",
|
||||||
|
avatar: "آواتار",
|
||||||
|
},
|
||||||
|
helper: {
|
||||||
|
forward_extremities:
|
||||||
|
"اندام های رو به جلو، رویدادهای برگ در انتهای نمودار غیر چرخه ای جهت دار (DAG) در یک اتاق هستند، رویدادهایی که فرزند ندارند. هر چه تعداد بیشتری در یک اتاق وجود داشته باشد، وضوح حالت بیشتری را که سیناپس باید انجام دهد (نکته: این یک عملیات گران است). در حالی که Synapse کدی برای جلوگیری از وجود تعداد زیادی از این موارد در یک زمان در اتاق دارد، گاهی اوقات باگها میتوانند دوباره ظاهر شوند. اگر اتاقی بیش از 10 انتهای رو به جلو دارد، بهتر است بررسی کنید که کدام اتاق مقصر است و احتمالاً آنها را با استفاده از جستارهای SQL ذکر شده در آن حذف کنید. #1760.",
|
||||||
|
},
|
||||||
|
enums: {
|
||||||
|
join_rules: {
|
||||||
|
public: "عمومی",
|
||||||
|
knock: "در زدن",
|
||||||
|
invite: "دعوت کردن",
|
||||||
|
private: "خصوصی",
|
||||||
|
},
|
||||||
|
guest_access: {
|
||||||
|
can_join: "مهمانان می توانند ملحق شوند",
|
||||||
|
forbidden: "مهمانان نمی توانند ملحق شوند",
|
||||||
|
},
|
||||||
|
history_visibility: {
|
||||||
|
invited: "از آنجایی که دعوت شده است",
|
||||||
|
joined: "از زمانی که پیوست",
|
||||||
|
shared: "از آنجایی که به اشتراک گذاشته شده است",
|
||||||
|
world_readable: "هر کسی",
|
||||||
|
},
|
||||||
|
unencrypted: "رمزگذاری نشده",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
erase: {
|
||||||
|
title: "حذف اتاق",
|
||||||
|
content:
|
||||||
|
"آیا مطمئن هستید که می خواهید اتاق را حذف کنید؟ این قابل بازگشت نیست. همه پیام ها و رسانه های مشترک در اتاق از سرور حذف می شوند!",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
reports: {
|
||||||
|
name: "رویداد گزارش شده |||| رویدادهای گزارش شده",
|
||||||
|
fields: {
|
||||||
|
id: "شناسه",
|
||||||
|
received_ts: "زمان گزارش",
|
||||||
|
user_id: "گوینده",
|
||||||
|
name: "نام اتاق",
|
||||||
|
score: "نمره",
|
||||||
|
reason: "دلیل",
|
||||||
|
event_id: "شناسه رویداد",
|
||||||
|
event_json: {
|
||||||
|
origin: "سرور مبدا",
|
||||||
|
origin_server_ts: "زمان ارسال",
|
||||||
|
type: "نوع رویداد",
|
||||||
|
content: {
|
||||||
|
msgtype: "نوع محتوا",
|
||||||
|
body: "محتوا",
|
||||||
|
format: "قالب",
|
||||||
|
formatted_body: "محتوای قالب بندی شده",
|
||||||
|
algorithm: "الگوریتم",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
connections: {
|
||||||
|
name: "اتصالات",
|
||||||
|
fields: {
|
||||||
|
last_seen: "تاریخ",
|
||||||
|
ip: "آدرس آی پی",
|
||||||
|
user_agent: "نماینده کاربر",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
devices: {
|
||||||
|
name: "دستگاه |||| دستگاه ها",
|
||||||
|
fields: {
|
||||||
|
device_id: "شناسه دستگاه",
|
||||||
|
display_name: "نام دستگاه",
|
||||||
|
last_seen_ts: "مهر زمان",
|
||||||
|
last_seen_ip: "آدرس آی پی",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
erase: {
|
||||||
|
title: "حذف کردن %{id}",
|
||||||
|
content:
|
||||||
|
'آیا مطمئن هستید که می خواهید دستگاه را حذف کنید؟ "%{name}"?',
|
||||||
|
success: "دستگاه با موفقیت حذف شد.",
|
||||||
|
failure: "خطایی رخ داده است.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
users_media: {
|
||||||
|
name: "رسانه ها",
|
||||||
|
fields: {
|
||||||
|
media_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 بر رسانههایی که در مخازن رسانه خارجی آپلود شدهاند تأثیری نخواهد گذاشت.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
protect_media: {
|
||||||
|
action: {
|
||||||
|
create: "محافظت نشده، حفاظت ایجاد کنید",
|
||||||
|
delete: "محافظت شده، حفاظت را بردارید",
|
||||||
|
none: "در قرنطینه",
|
||||||
|
send_success: "وضعیت حفاظت با موفقیت تغییر کرد.",
|
||||||
|
send_failure: "خطایی رخ داده است.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
quarantine_media: {
|
||||||
|
action: {
|
||||||
|
name: "قرنطینه",
|
||||||
|
create: "به قرنطینه اضافه کنید",
|
||||||
|
delete: "در قرنطینه، غیر قرنطینه",
|
||||||
|
none: "از قرنطینه محافظت می شود",
|
||||||
|
send_success: "وضعیت قرنطینه با موفقیت تغییر کرد.",
|
||||||
|
send_failure: "خطایی رخ داده است.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pushers: {
|
||||||
|
name: "هل دهنده |||| هل دهنده ها",
|
||||||
|
fields: {
|
||||||
|
app: "برنامه",
|
||||||
|
app_display_name: "نام نمایش برنامه",
|
||||||
|
app_id: "شناسه برنامه",
|
||||||
|
device_display_name: "نام نمایشی برنامه",
|
||||||
|
kind: "نوع",
|
||||||
|
lang: "زبان",
|
||||||
|
profile_tag: "برچسب پروفایل",
|
||||||
|
pushkey: "کلید",
|
||||||
|
data: { url: "URL" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
servernotices: {
|
||||||
|
name: "اطلاعیه های سرور",
|
||||||
|
send: "ارسال اعلانات سرور",
|
||||||
|
fields: {
|
||||||
|
body: "پیام",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
send: "ارسال یادداشت",
|
||||||
|
send_success: "اعلان سرور با موفقیت ارسال شد.",
|
||||||
|
send_failure: "خطایی رخ داده است.",
|
||||||
|
},
|
||||||
|
helper: {
|
||||||
|
send: "اعلان سرور را برای کاربران انتخاب شده ارسال می کند. ویژگی 'اعلامیه های سرور' باید در سرور فعال شود.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
user_media_statistics: {
|
||||||
|
name: "رسانه کاربران",
|
||||||
|
fields: {
|
||||||
|
media_count: "شمارش رسانه ها",
|
||||||
|
media_length: "طول رسانه",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
forward_extremities: {
|
||||||
|
name: "Forward Extremities",
|
||||||
|
fields: {
|
||||||
|
id: "شناسه رویداد",
|
||||||
|
received_ts: "مهر زمان",
|
||||||
|
depth: "عمق",
|
||||||
|
state_group: "گروه دولتی",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
room_state: {
|
||||||
|
name: "رویدادهای وضعیت",
|
||||||
|
fields: {
|
||||||
|
type: "نوع",
|
||||||
|
content: "محتوا",
|
||||||
|
origin_server_ts: "زمان ارسال",
|
||||||
|
sender: "فرستنده",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
room_directory: {
|
||||||
|
name: "راهنمای اتاق",
|
||||||
|
fields: {
|
||||||
|
world_readable: "کاربران مهمان می توانند بدون عضویت مشاهده کنند",
|
||||||
|
guest_can_join: "کاربران مهمان ممکن است ملحق شوند",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
title:
|
||||||
|
"اتاق را از فهرست حذف کنید |||| حذف کنید %{smart_count} اتاق ها از دایرکتوری",
|
||||||
|
content:
|
||||||
|
"آیا مطمئنید که می خواهید این اتاق را از فهرست راهنمای حذف کنید؟ |||| آیا مطمئن هستید که می خواهید این موارد را %{smart_count} از راهنمای اتاق ها حذف کنید؟",
|
||||||
|
erase: "حذف از فهرست اتاق",
|
||||||
|
create: "انتشار در راهنما اتاق",
|
||||||
|
send_success: "اتاق با موفقیت منتشر شد.",
|
||||||
|
send_failure: "خطایی رخ داده است.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
destinations: {
|
||||||
|
name: "سرور های مرتبط",
|
||||||
|
fields: {
|
||||||
|
destination: "آدرس",
|
||||||
|
failure_ts: "زمان شکست",
|
||||||
|
retry_last_ts: "آخرین زمان اتصال",
|
||||||
|
retry_interval: "بازه امتحان مجدد",
|
||||||
|
last_successful_stream_ordering: "آخرین جریان موفق",
|
||||||
|
stream_ordering: "جریان",
|
||||||
|
},
|
||||||
|
action: { reconnect: "دوباره وصل شوید" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
registration_tokens: {
|
||||||
|
name: "توکن های ثبت نام",
|
||||||
|
fields: {
|
||||||
|
token: "توکن",
|
||||||
|
valid: "توکن معتبر",
|
||||||
|
uses_allowed: "موارد استفاده مجاز",
|
||||||
|
pending: "انتظار",
|
||||||
|
completed: "تکمیل شد",
|
||||||
|
expiry_time: "زمان انقضا",
|
||||||
|
length: "طول",
|
||||||
|
},
|
||||||
|
helper: { length: "طول توکن در صورت عدم ارائه توکن." },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
export default fa;
|
390
src/i18n/ru.js
Normal file
390
src/i18n/ru.js
Normal file
@ -0,0 +1,390 @@
|
|||||||
|
import russianMessages from "ra-language-russian";
|
||||||
|
|
||||||
|
const ru = {
|
||||||
|
...russianMessages,
|
||||||
|
synapseadmin: {
|
||||||
|
auth: {
|
||||||
|
base_url: "Домашняя страница",
|
||||||
|
welcome: "Добро пожаловать в Synapse-admin",
|
||||||
|
server_version: "Версия Synapse",
|
||||||
|
supports_specs: "поддерживает спецификации Matrix",
|
||||||
|
username_error: "Введите полный идентификатор пользователя: '@user:domain'",
|
||||||
|
protocol_error: "Адрес должен начинаться с 'http://' или 'https://'",
|
||||||
|
url_error: "Некорректный сервер Matrix",
|
||||||
|
sso_sign_in: "Присоединиться с помощью SSO",
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
invalid_user_id: "Локальная часть идентификатора пользователя Matrix без домашнего сервера.",
|
||||||
|
tabs: { sso: "SSO" },
|
||||||
|
},
|
||||||
|
rooms: {
|
||||||
|
tabs: {
|
||||||
|
basic: "Основное",
|
||||||
|
members: "Участники",
|
||||||
|
detail: "Подробности",
|
||||||
|
permission: "Права",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
reports: { tabs: { basic: "Основное", detail: "Подробности" } },
|
||||||
|
},
|
||||||
|
import_users: {
|
||||||
|
error: {
|
||||||
|
at_entry: "При входе %{entry}: %{message}",
|
||||||
|
error: "Ошибка",
|
||||||
|
required_field: "Обязательное поле '%{field}' не представленно",
|
||||||
|
invalid_value:
|
||||||
|
"Недопустимое значение в строке %{row}. '%{field}' поле может быть только 'true' или 'false'",
|
||||||
|
unreasonably_big:
|
||||||
|
"Отказался загружать неоправданно большой файл %{size} Мбайт",
|
||||||
|
already_in_progress: "Импорт уже запущен",
|
||||||
|
id_exits: "Идентификатор %{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: "Идентификаторы",
|
||||||
|
all_ids_present: "Идентификаторы представлены для каждой записи",
|
||||||
|
count_ids_present:
|
||||||
|
"%{smart_count} запись с идентификатором |||| %{smart_count} записей с идентификатором",
|
||||||
|
mode: {
|
||||||
|
ignore: "Игнорировать идентификаторы в CSV и создавать новые",
|
||||||
|
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: "Идентификатор пользователя",
|
||||||
|
name: "Имя",
|
||||||
|
is_guest: "Гость",
|
||||||
|
admin: "Администратор",
|
||||||
|
deactivated: "Деактивирован",
|
||||||
|
guests: "Показать гостей",
|
||||||
|
show_deactivated: "Показать деактивированных пользователей",
|
||||||
|
user_id: "Найти пользователя",
|
||||||
|
displayname: "Отображаемое имя",
|
||||||
|
password: "Пароль",
|
||||||
|
avatar_url: "Ссылка на аватар",
|
||||||
|
avatar_src: "Аватар",
|
||||||
|
medium: "Тип",
|
||||||
|
threepids: "Иной идентификатор",
|
||||||
|
address: "Адрес",
|
||||||
|
creation_ts_ms: "Время создания",
|
||||||
|
consent_version: "Версия соглашения",
|
||||||
|
auth_provider: "Поставщик",
|
||||||
|
user_type: "Тип пользователя",
|
||||||
|
},
|
||||||
|
helper: {
|
||||||
|
password: "Изменение пароля приведет к выходу пользователя из всех сеансов.",
|
||||||
|
deactivate: "Вы должны ввести пароль для повторной активации учетной записи.",
|
||||||
|
erase: "Пометить пользователя как удаленного в связи с защитой персональных данных",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
erase: "Удаление пользовательских данных",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rooms: {
|
||||||
|
name: "Комнат |||| Комнаты",
|
||||||
|
fields: {
|
||||||
|
room_id: "Идентификатор комнаты",
|
||||||
|
name: "Название",
|
||||||
|
canonical_alias: "Псевдоним",
|
||||||
|
joined_members: "Участники",
|
||||||
|
joined_local_members: "Внутренние участники",
|
||||||
|
joined_local_devices: "Используемые устройства",
|
||||||
|
state_events: "События изменения состояния",
|
||||||
|
version: "Версия",
|
||||||
|
is_encrypted: "Зашифрованно",
|
||||||
|
encryption: "Шифрование",
|
||||||
|
federatable: "Федерация",
|
||||||
|
public: "Видимость в списке комнат",
|
||||||
|
creator: "Создатель",
|
||||||
|
join_rules: "Правила присоединения",
|
||||||
|
guest_access: "Гостевой доступ",
|
||||||
|
history_visibility: "Видимость истории",
|
||||||
|
topic: "Тема",
|
||||||
|
avatar: "Аватар",
|
||||||
|
},
|
||||||
|
helper: {
|
||||||
|
forward_extremities:
|
||||||
|
"Перенаправленные заключения, это такие события, которые не имеют потомков в рамках графа, отвечающего за их репрезентацию. Чем больше пользователей находится в комнате, тем больше операций требуется выполнить Synapse для разрешения коллиций, которые возникают при их проверке наступления событий (это дорогостоящая операция). Хотя в Synapse есть код, предотвращающий одновременное присутствие слишком большого количества таких объектов в комнате, ошибки иногда могут привести к их повторному появлению. Если в комнате содержится >10 перенаправленных заключений, имеет смысл выяснить, какая комната стала причиной их появления и, возможно, удалить их, используя SQL-запросы, упомянутые issue #1760.",
|
||||||
|
},
|
||||||
|
enums: {
|
||||||
|
join_rules: {
|
||||||
|
public: "Публичный",
|
||||||
|
knock: "По запросу",
|
||||||
|
invite: "По приглашению",
|
||||||
|
private: "Приватный",
|
||||||
|
},
|
||||||
|
guest_access: {
|
||||||
|
can_join: "Гости могут присоединиться",
|
||||||
|
forbidden: "Гости не могут присоединиться",
|
||||||
|
},
|
||||||
|
history_visibility: {
|
||||||
|
invited: "С момента приглашения",
|
||||||
|
joined: "С момента присоединения",
|
||||||
|
shared: "С момента разрешения",
|
||||||
|
world_readable: "Всегда",
|
||||||
|
},
|
||||||
|
unencrypted: "Не зашифрованно",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
erase: {
|
||||||
|
title: "Удалить комнату",
|
||||||
|
content:
|
||||||
|
"Вы уверены, что хотите удалить комнату? Это невозможно отменить. Все сообщения и медиафайлы, находящиеся в общем доступе в комнате, будут удалены с сервера!",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
reports: {
|
||||||
|
name: "Жалоб |||| Жалобы",
|
||||||
|
fields: {
|
||||||
|
id: "идентификатор",
|
||||||
|
received_ts: "время жалобы",
|
||||||
|
user_id: "коментатор",
|
||||||
|
name: "название комнаты",
|
||||||
|
score: "оценка",
|
||||||
|
reason: "причина",
|
||||||
|
event_id: "идентификатор события",
|
||||||
|
event_json: {
|
||||||
|
origin: "сервер",
|
||||||
|
origin_server_ts: "время отправки",
|
||||||
|
type: "тип события",
|
||||||
|
content: {
|
||||||
|
msgtype: "тип содержания",
|
||||||
|
body: "содержание",
|
||||||
|
format: "формат",
|
||||||
|
formatted_body: "форматированное содержание",
|
||||||
|
algorithm: "алгоритм",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
erase: {
|
||||||
|
title: "Удалить жалобу",
|
||||||
|
content:
|
||||||
|
"Вы уверены, что хотите удалить жалобу? Это невозможно отменить.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
connections: {
|
||||||
|
name: "Подключения",
|
||||||
|
fields: {
|
||||||
|
last_seen: "Дата",
|
||||||
|
ip: "IP адрес",
|
||||||
|
user_agent: "User agent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
devices: {
|
||||||
|
name: "Устройств |||| Устройства",
|
||||||
|
fields: {
|
||||||
|
device_id: "Идентификатор устройства",
|
||||||
|
display_name: "Название устройства",
|
||||||
|
last_seen_ts: "Метка времени",
|
||||||
|
last_seen_ip: "IP адрес",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
erase: {
|
||||||
|
title: "Удаление %{id}",
|
||||||
|
content: 'Вы уверены, что хотите удалить устройство "%{name}"?',
|
||||||
|
success: "Устройство успешно удалено.",
|
||||||
|
failure: "Произошла ошибка.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
users_media: {
|
||||||
|
name: "Медиа",
|
||||||
|
fields: {
|
||||||
|
media_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: "Этот метод удаляет медиафайлы с диска сервера. Это включает в себя любые миниатюры и копии загруженных файлов. Этот метод не повлияет на файлы, загруженные во внешние хранилища.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
protect_media: {
|
||||||
|
action: {
|
||||||
|
create: "Не защищено, установить защиту",
|
||||||
|
delete: "Защищено, удалить защиту",
|
||||||
|
none: "В карантине",
|
||||||
|
send_success: "Успешно изменен статус защиты.",
|
||||||
|
send_failure: "Произошла ошибка.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
quarantine_media: {
|
||||||
|
action: {
|
||||||
|
name: "Карантин",
|
||||||
|
create: "Добавить в карантин",
|
||||||
|
delete: "В карантине, вывести из карантина",
|
||||||
|
none: "Защищено от карантина",
|
||||||
|
send_success: "Успешно изменен статус карантина.",
|
||||||
|
send_failure: "Произошла ошибка.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pushers: {
|
||||||
|
name: "Уведомление |||| Уведомления",
|
||||||
|
fields: {
|
||||||
|
app: "Приложение",
|
||||||
|
app_display_name: "Отображаемое имя приложения",
|
||||||
|
app_id: "Идентификатор приложения",
|
||||||
|
device_display_name: "Отображаемое имя устройства",
|
||||||
|
kind: "Тип",
|
||||||
|
lang: "Язык",
|
||||||
|
profile_tag: "Тег профиля",
|
||||||
|
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: "Длина медиафайлов",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
forward_extremities: {
|
||||||
|
name: "Перенаправленные заключения",
|
||||||
|
fields: {
|
||||||
|
id: "Идентификатор события",
|
||||||
|
received_ts: "Время",
|
||||||
|
depth: "Глубина",
|
||||||
|
state_group: "Группа состояния",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
room_state: {
|
||||||
|
name: "События изменения состояния",
|
||||||
|
fields: {
|
||||||
|
type: "Тип",
|
||||||
|
content: "Содержание",
|
||||||
|
origin_server_ts: "время отправления",
|
||||||
|
sender: "Отправитель",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
room_directory: {
|
||||||
|
name: "Публичных комнат |||| Публичные комнаты",
|
||||||
|
fields: {
|
||||||
|
world_readable: "Видимо для гостей",
|
||||||
|
guest_can_join: "Гости могут присоединиться",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
title:
|
||||||
|
"Удалить комнату |||| Удалить %{smart_count} комнат",
|
||||||
|
content:
|
||||||
|
"Вы уверены, что хотите удалить комнату? |||| Вы уверены, что хотите удалить %{smart_count} комнат?",
|
||||||
|
erase: "Удалить комнату",
|
||||||
|
create: "Опубликовать комнату",
|
||||||
|
send_success: "Комната успешно опубликована.",
|
||||||
|
send_failure: "Произошла ошибка.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
destinations: {
|
||||||
|
name: "Федераций |||| Федерация",
|
||||||
|
fields: {
|
||||||
|
destination: "Назначение",
|
||||||
|
failure_ts: "Время сбоя",
|
||||||
|
retry_last_ts: "Время последней попытки",
|
||||||
|
retry_interval: "Интервал повторения",
|
||||||
|
last_successful_stream_ordering: "Последнее успешное соединение",
|
||||||
|
stream_ordering: "Соединение",
|
||||||
|
},
|
||||||
|
action: { reconnect: "Переподключиться" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
registration_tokens: {
|
||||||
|
name: "Токены регистрации",
|
||||||
|
fields: {
|
||||||
|
token: "Токен",
|
||||||
|
valid: "Допустимый токен",
|
||||||
|
uses_allowed: "Разрешено",
|
||||||
|
pending: "Ожидается",
|
||||||
|
completed: "Использован",
|
||||||
|
expiry_time: "Время истечения срока действия",
|
||||||
|
length: "Длина",
|
||||||
|
},
|
||||||
|
helper: { length: "Длина токена, если токен не указан." },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
export default ru;
|
@ -1,10 +1,8 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import ReactDOM from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
|
|
||||||
const REACT_APP_SERVER = import.meta.env.VITE_APP_SERVER;
|
createRoot(document.getElementById("root")).render(
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root")).render(
|
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<App />
|
<App />
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { fetchUtils } from "react-admin";
|
import { fetchUtils } from "react-admin";
|
||||||
|
|
||||||
const authProvider = (fixed_base_url, store) => ({
|
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: async ({ base_url, username, password, loginToken }) => {
|
||||||
// force homeserver for protection in case the form is manipulated
|
// force homeserver for protection in case the form is manipulated
|
||||||
base_url = fixed_base_url || base_url;
|
base_url = process.env.REACT_APP_SERVER || base_url;
|
||||||
|
|
||||||
console.log("login ");
|
console.log("login ");
|
||||||
const options = {
|
const options = {
|
||||||
@ -12,7 +12,7 @@ const authProvider = (fixed_base_url, store) => ({
|
|||||||
body: JSON.stringify(
|
body: JSON.stringify(
|
||||||
Object.assign(
|
Object.assign(
|
||||||
{
|
{
|
||||||
device_id: store.getItem("device_id"),
|
device_id: localStorage.getItem("device_id"),
|
||||||
initial_device_display_name: "Synapse Admin",
|
initial_device_display_name: "Synapse Admin",
|
||||||
},
|
},
|
||||||
loginToken
|
loginToken
|
||||||
@ -33,20 +33,19 @@ const authProvider = (fixed_base_url, store) => ({
|
|||||||
// 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, "");
|
base_url = base_url.replace(/\/+$/g, "");
|
||||||
store.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);
|
||||||
const login_api_url = decoded_base_url + "/_matrix/client/r0/login";
|
const login_api_url = decoded_base_url + "/_matrix/client/r0/login";
|
||||||
|
|
||||||
return fetchUtils.fetchJson(login_api_url, options).then(({ json }) => {
|
const { json } = await fetchUtils.fetchJson(login_api_url, options);
|
||||||
store.setItem("home_server", json.home_server);
|
localStorage.setItem("home_server", json.home_server);
|
||||||
store.setItem("user_id", json.user_id);
|
localStorage.setItem("user_id", json.user_id);
|
||||||
store.setItem("access_token", json.access_token);
|
localStorage.setItem("access_token", json.access_token);
|
||||||
store.setItem("device_id", json.device_id);
|
localStorage.setItem("device_id", json.device_id);
|
||||||
});
|
|
||||||
},
|
},
|
||||||
// called when the user clicks on the logout button
|
// called when the user clicks on the logout button
|
||||||
logout: () => {
|
logout: async () => {
|
||||||
console.log("logout");
|
console.log("logout");
|
||||||
|
|
||||||
const logout_api_url =
|
const logout_api_url =
|
||||||
@ -62,11 +61,9 @@ const authProvider = (fixed_base_url, store) => ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (typeof access_token === "string") {
|
if (typeof access_token === "string") {
|
||||||
fetchUtils.fetchJson(logout_api_url, options).then(({ json }) => {
|
await fetchUtils.fetchJson(logout_api_url, options);
|
||||||
localStorage.removeItem("access_token");
|
localStorage.removeItem("access_token");
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
|
||||||
},
|
},
|
||||||
// called when the API returns an error
|
// called when the API returns an error
|
||||||
checkError: ({ status }) => {
|
checkError: ({ status }) => {
|
||||||
@ -86,6 +83,6 @@ const authProvider = (fixed_base_url, store) => ({
|
|||||||
},
|
},
|
||||||
// called when the user navigates to a new location, to check for permissions / roles
|
// called when the user navigates to a new location, to check for permissions / roles
|
||||||
getPermissions: () => Promise.resolve(),
|
getPermissions: () => Promise.resolve(),
|
||||||
});
|
};
|
||||||
|
|
||||||
export default authProvider;
|
export default authProvider;
|
||||||
|
135
src/synapse/authProvider.test.js
Normal file
135
src/synapse/authProvider.test.js
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
import authProvider from "./authProvider";
|
||||||
|
|
||||||
|
describe("authProvider", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
fetch.resetMocks();
|
||||||
|
localStorage.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("login", () => {
|
||||||
|
it("should successfully login with username and password", async () => {
|
||||||
|
fetch.once(
|
||||||
|
JSON.stringify({
|
||||||
|
home_server: "example.com",
|
||||||
|
user_id: "@user:example.com",
|
||||||
|
access_token: "foobar",
|
||||||
|
device_id: "some_device",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const ret = await authProvider.login({
|
||||||
|
base_url: "http://example.com",
|
||||||
|
username: "@user:example.com",
|
||||||
|
password: "secret",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(ret).toBe(undefined);
|
||||||
|
expect(fetch).toBeCalledWith(
|
||||||
|
"http://example.com/_matrix/client/r0/login",
|
||||||
|
{
|
||||||
|
body: '{"device_id":null,"initial_device_display_name":"Synapse Admin","type":"m.login.password","user":"@user:example.com","password":"secret"}',
|
||||||
|
headers: new Headers({
|
||||||
|
Accept: ["application/json"],
|
||||||
|
"Content-Type": ["application/json"],
|
||||||
|
}),
|
||||||
|
method: "POST",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(localStorage.getItem("base_url")).toEqual("http://example.com");
|
||||||
|
expect(localStorage.getItem("user_id")).toEqual("@user:example.com");
|
||||||
|
expect(localStorage.getItem("access_token")).toEqual("foobar");
|
||||||
|
expect(localStorage.getItem("device_id")).toEqual("some_device");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should successfully login with token", async () => {
|
||||||
|
fetch.once(
|
||||||
|
JSON.stringify({
|
||||||
|
home_server: "example.com",
|
||||||
|
user_id: "@user:example.com",
|
||||||
|
access_token: "foobar",
|
||||||
|
device_id: "some_device",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const ret = await authProvider.login({
|
||||||
|
base_url: "https://example.com/",
|
||||||
|
loginToken: "login_token",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(ret).toBe(undefined);
|
||||||
|
expect(fetch).toBeCalledWith(
|
||||||
|
"https://example.com/_matrix/client/r0/login",
|
||||||
|
{
|
||||||
|
body: '{"device_id":null,"initial_device_display_name":"Synapse Admin","type":"m.login.token","token":"login_token"}',
|
||||||
|
headers: new Headers({
|
||||||
|
Accept: ["application/json"],
|
||||||
|
"Content-Type": ["application/json"],
|
||||||
|
}),
|
||||||
|
method: "POST",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(localStorage.getItem("base_url")).toEqual("https://example.com");
|
||||||
|
expect(localStorage.getItem("user_id")).toEqual("@user:example.com");
|
||||||
|
expect(localStorage.getItem("access_token")).toEqual("foobar");
|
||||||
|
expect(localStorage.getItem("device_id")).toEqual("some_device");
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("logout", () => {
|
||||||
|
it("should remove the access_token from localStorage", async () => {
|
||||||
|
localStorage.setItem("base_url", "example.com");
|
||||||
|
localStorage.setItem("access_token", "foo");
|
||||||
|
fetch.mockResponse(JSON.stringify({}));
|
||||||
|
|
||||||
|
await authProvider.logout();
|
||||||
|
|
||||||
|
expect(fetch).toBeCalledWith("example.com/_matrix/client/r0/logout", {
|
||||||
|
headers: new Headers({
|
||||||
|
Accept: ["application/json"],
|
||||||
|
Authorization: ["Bearer foo"],
|
||||||
|
}),
|
||||||
|
method: "POST",
|
||||||
|
user: { authenticated: true, token: "Bearer foo" },
|
||||||
|
});
|
||||||
|
expect(localStorage.getItem("access_token")).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("checkError", () => {
|
||||||
|
it("should resolve if error.status is not 401 or 403", async () => {
|
||||||
|
await expect(
|
||||||
|
authProvider.checkError({ status: 200 })
|
||||||
|
).resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should reject if error.status is 401", async () => {
|
||||||
|
await expect(
|
||||||
|
authProvider.checkError({ status: 401 })
|
||||||
|
).rejects.toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should reject if error.status is 403", async () => {
|
||||||
|
await expect(
|
||||||
|
authProvider.checkError({ status: 403 })
|
||||||
|
).rejects.toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("checkAuth", () => {
|
||||||
|
it("should reject when not logged in", async () => {
|
||||||
|
await expect(authProvider.checkAuth({})).rejects.toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should resolve when logged in", async () => {
|
||||||
|
localStorage.setItem("access_token", "foobar");
|
||||||
|
|
||||||
|
await expect(authProvider.checkAuth({})).resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getPermissions", () => {
|
||||||
|
it("should do nothing", async () => {
|
||||||
|
await expect(authProvider.getPermissions()).resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -348,7 +348,7 @@ function getSearchOrder(order) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dataProvider = {
|
const dataProvider = {
|
||||||
getList: (resource, params) => {
|
getList: async (resource, params) => {
|
||||||
console.log("getList " + resource);
|
console.log("getList " + resource);
|
||||||
const {
|
const {
|
||||||
user_id,
|
user_id,
|
||||||
@ -383,13 +383,14 @@ const dataProvider = {
|
|||||||
const endpoint_url = homeserver + res.path;
|
const endpoint_url = homeserver + res.path;
|
||||||
const url = `${endpoint_url}?${stringify(query)}`;
|
const url = `${endpoint_url}?${stringify(query)}`;
|
||||||
|
|
||||||
return jsonClient(url).then(({ json }) => ({
|
const { json } = await jsonClient(url);
|
||||||
|
return {
|
||||||
data: json[res.data].map(res.map),
|
data: json[res.data].map(res.map),
|
||||||
total: res.total(json, from, perPage),
|
total: res.total(json, from, perPage),
|
||||||
}));
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getOne: (resource, params) => {
|
getOne: async (resource, params) => {
|
||||||
console.log("getOne " + resource);
|
console.log("getOne " + resource);
|
||||||
const homeserver = localStorage.getItem("base_url");
|
const homeserver = localStorage.getItem("base_url");
|
||||||
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
||||||
@ -397,14 +398,13 @@ 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(
|
const { json } = await jsonClient(
|
||||||
({ json }) => ({
|
`${endpoint_url}/${encodeURIComponent(params.id)}`
|
||||||
data: res.map(json),
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
return { data: res.map(json) };
|
||||||
},
|
},
|
||||||
|
|
||||||
getMany: (resource, params) => {
|
getMany: async (resource, params) => {
|
||||||
console.log("getMany " + resource);
|
console.log("getMany " + resource);
|
||||||
const homeserver = localStorage.getItem("base_url");
|
const homeserver = localStorage.getItem("base_url");
|
||||||
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
||||||
@ -412,17 +412,18 @@ const dataProvider = {
|
|||||||
const res = resourceMap[resource];
|
const res = resourceMap[resource];
|
||||||
|
|
||||||
const endpoint_url = homeserver + res.path;
|
const endpoint_url = homeserver + res.path;
|
||||||
return Promise.all(
|
const responses = await Promise.all(
|
||||||
params.ids.map(id =>
|
params.ids.map(id =>
|
||||||
jsonClient(`${endpoint_url}/${encodeURIComponent(id)}`)
|
jsonClient(`${endpoint_url}/${encodeURIComponent(id)}`)
|
||||||
)
|
)
|
||||||
).then(responses => ({
|
);
|
||||||
|
return {
|
||||||
data: responses.map(({ json }) => res.map(json)),
|
data: responses.map(({ json }) => res.map(json)),
|
||||||
total: responses.length,
|
total: responses.length,
|
||||||
}));
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getManyReference: (resource, params) => {
|
getManyReference: async (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 { field, order } = params.sort;
|
||||||
@ -442,13 +443,14 @@ const dataProvider = {
|
|||||||
const ref = res["reference"](params.id);
|
const ref = res["reference"](params.id);
|
||||||
const endpoint_url = `${homeserver}${ref.endpoint}?${stringify(query)}`;
|
const endpoint_url = `${homeserver}${ref.endpoint}?${stringify(query)}`;
|
||||||
|
|
||||||
return jsonClient(endpoint_url).then(({ headers, json }) => ({
|
const { json } = await jsonClient(endpoint_url);
|
||||||
|
return {
|
||||||
data: json[res.data].map(res.map),
|
data: json[res.data].map(res.map),
|
||||||
total: res.total(json, from, perPage),
|
total: res.total(json, from, perPage),
|
||||||
}));
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
update: (resource, params) => {
|
update: async (resource, params) => {
|
||||||
console.log("update " + resource);
|
console.log("update " + resource);
|
||||||
const homeserver = localStorage.getItem("base_url");
|
const homeserver = localStorage.getItem("base_url");
|
||||||
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
||||||
@ -456,15 +458,17 @@ 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)}`, {
|
const { json } = await jsonClient(
|
||||||
method: "PUT",
|
`${endpoint_url}/${encodeURIComponent(params.id)}`,
|
||||||
body: JSON.stringify(params.data, filterNullValues),
|
{
|
||||||
}).then(({ json }) => ({
|
method: "PUT",
|
||||||
data: res.map(json),
|
body: JSON.stringify(params.data, filterNullValues),
|
||||||
}));
|
}
|
||||||
|
);
|
||||||
|
return { data: res.map(json) };
|
||||||
},
|
},
|
||||||
|
|
||||||
updateMany: (resource, params) => {
|
updateMany: async (resource, params) => {
|
||||||
console.log("updateMany " + resource);
|
console.log("updateMany " + resource);
|
||||||
const homeserver = localStorage.getItem("base_url");
|
const homeserver = localStorage.getItem("base_url");
|
||||||
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
||||||
@ -472,7 +476,7 @@ const dataProvider = {
|
|||||||
const res = resourceMap[resource];
|
const res = resourceMap[resource];
|
||||||
|
|
||||||
const endpoint_url = homeserver + res.path;
|
const endpoint_url = homeserver + res.path;
|
||||||
return Promise.all(
|
const responses = await Promise.all(
|
||||||
params.ids.map(
|
params.ids.map(
|
||||||
id => jsonClient(`${endpoint_url}/${encodeURIComponent(id)}`),
|
id => jsonClient(`${endpoint_url}/${encodeURIComponent(id)}`),
|
||||||
{
|
{
|
||||||
@ -480,12 +484,11 @@ const dataProvider = {
|
|||||||
body: JSON.stringify(params.data, filterNullValues),
|
body: JSON.stringify(params.data, filterNullValues),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
).then(responses => ({
|
);
|
||||||
data: responses.map(({ json }) => json),
|
return { data: responses.map(({ json }) => json) };
|
||||||
}));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
create: (resource, params) => {
|
create: async (resource, params) => {
|
||||||
console.log("create " + resource);
|
console.log("create " + resource);
|
||||||
const homeserver = localStorage.getItem("base_url");
|
const homeserver = localStorage.getItem("base_url");
|
||||||
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
||||||
@ -495,15 +498,14 @@ const dataProvider = {
|
|||||||
|
|
||||||
const create = res["create"](params.data);
|
const create = res["create"](params.data);
|
||||||
const endpoint_url = homeserver + create.endpoint;
|
const endpoint_url = homeserver + create.endpoint;
|
||||||
return jsonClient(endpoint_url, {
|
const { json } = await jsonClient(endpoint_url, {
|
||||||
method: create.method,
|
method: create.method,
|
||||||
body: JSON.stringify(create.body, filterNullValues),
|
body: JSON.stringify(create.body, filterNullValues),
|
||||||
}).then(({ json }) => ({
|
});
|
||||||
data: res.map(json),
|
return { data: res.map(json) };
|
||||||
}));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
createMany: (resource, params) => {
|
createMany: async (resource, params) => {
|
||||||
console.log("createMany " + resource);
|
console.log("createMany " + resource);
|
||||||
const homeserver = localStorage.getItem("base_url");
|
const homeserver = localStorage.getItem("base_url");
|
||||||
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
||||||
@ -511,7 +513,7 @@ const dataProvider = {
|
|||||||
const res = resourceMap[resource];
|
const res = resourceMap[resource];
|
||||||
if (!("create" in res)) return Promise.reject();
|
if (!("create" in res)) return Promise.reject();
|
||||||
|
|
||||||
return Promise.all(
|
const responses = await Promise.all(
|
||||||
params.ids.map(id => {
|
params.ids.map(id => {
|
||||||
params.data.id = id;
|
params.data.id = id;
|
||||||
const cre = res["create"](params.data);
|
const cre = res["create"](params.data);
|
||||||
@ -521,12 +523,11 @@ const dataProvider = {
|
|||||||
body: JSON.stringify(cre.body, filterNullValues),
|
body: JSON.stringify(cre.body, filterNullValues),
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
).then(responses => ({
|
);
|
||||||
data: responses.map(({ json }) => json),
|
return { data: responses.map(({ json }) => json) };
|
||||||
}));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
delete: (resource, params) => {
|
delete: async (resource, params) => {
|
||||||
console.log("delete " + resource);
|
console.log("delete " + resource);
|
||||||
const homeserver = localStorage.getItem("base_url");
|
const homeserver = localStorage.getItem("base_url");
|
||||||
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
||||||
@ -536,24 +537,22 @@ const dataProvider = {
|
|||||||
if ("delete" in res) {
|
if ("delete" in res) {
|
||||||
const del = res["delete"](params);
|
const del = res["delete"](params);
|
||||||
const endpoint_url = homeserver + del.endpoint;
|
const endpoint_url = homeserver + del.endpoint;
|
||||||
return jsonClient(endpoint_url, {
|
const { json } = await jsonClient(endpoint_url, {
|
||||||
method: "method" in del ? del.method : "DELETE",
|
method: "method" in del ? del.method : "DELETE",
|
||||||
body: "body" in del ? JSON.stringify(del.body) : null,
|
body: "body" in del ? JSON.stringify(del.body) : null,
|
||||||
}).then(({ json }) => ({
|
});
|
||||||
data: json,
|
return { data: json };
|
||||||
}));
|
|
||||||
} else {
|
} else {
|
||||||
const endpoint_url = homeserver + res.path;
|
const endpoint_url = homeserver + res.path;
|
||||||
return jsonClient(`${endpoint_url}/${params.id}`, {
|
const { json } = await jsonClient(`${endpoint_url}/${params.id}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
body: JSON.stringify(params.previousData, filterNullValues),
|
body: JSON.stringify(params.previousData, filterNullValues),
|
||||||
}).then(({ json }) => ({
|
});
|
||||||
data: json,
|
return { data: json };
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteMany: (resource, params) => {
|
deleteMany: async (resource, params) => {
|
||||||
console.log("deleteMany " + resource);
|
console.log("deleteMany " + resource);
|
||||||
const homeserver = localStorage.getItem("base_url");
|
const homeserver = localStorage.getItem("base_url");
|
||||||
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
||||||
@ -561,7 +560,7 @@ const dataProvider = {
|
|||||||
const res = resourceMap[resource];
|
const res = resourceMap[resource];
|
||||||
|
|
||||||
if ("delete" in res) {
|
if ("delete" in res) {
|
||||||
return Promise.all(
|
const responses = await Promise.all(
|
||||||
params.ids.map(id => {
|
params.ids.map(id => {
|
||||||
const del = res["delete"]({ ...params, id: id });
|
const del = res["delete"]({ ...params, id: id });
|
||||||
const endpoint_url = homeserver + del.endpoint;
|
const endpoint_url = homeserver + del.endpoint;
|
||||||
@ -570,21 +569,21 @@ const dataProvider = {
|
|||||||
body: "body" in del ? JSON.stringify(del.body) : null,
|
body: "body" in del ? JSON.stringify(del.body) : null,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
).then(responses => ({
|
);
|
||||||
|
return {
|
||||||
data: responses.map(({ json }) => json),
|
data: responses.map(({ json }) => json),
|
||||||
}));
|
};
|
||||||
} else {
|
} else {
|
||||||
const endpoint_url = homeserver + res.path;
|
const endpoint_url = homeserver + res.path;
|
||||||
return Promise.all(
|
const responses = await Promise.all(
|
||||||
params.ids.map(id =>
|
params.ids.map(id =>
|
||||||
jsonClient(`${endpoint_url}/${id}`, {
|
jsonClient(`${endpoint_url}/${id}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
body: JSON.stringify(params.data, filterNullValues),
|
body: JSON.stringify(params.data, filterNullValues),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
).then(responses => ({
|
);
|
||||||
data: responses.map(({ json }) => json),
|
return { data: responses.map(({ json }) => json) };
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -36,6 +36,13 @@ export const getServerVersion = async baseUrl => {
|
|||||||
return response.json.server_version;
|
return response.json.server_version;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Get supported Matrix features */
|
||||||
|
export const getSupportedFeatures = async baseUrl => {
|
||||||
|
const versionUrl = `${baseUrl}/_matrix/client/versions`;
|
||||||
|
const response = await fetchUtils.fetchJson(versionUrl, { method: "GET" });
|
||||||
|
return response.json;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get supported login flows
|
* Get supported login flows
|
||||||
* @param baseUrl the base URL of the homeserver
|
* @param baseUrl the base URL of the homeserver
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
import { defineConfig } from "vite";
|
|
||||||
import react from "@vitejs/plugin-react";
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [react()],
|
|
||||||
});
|
|
Loading…
Reference in New Issue
Block a user