Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
311db77306 | |||
7c08f846a5 | |||
f2f096d8a5 | |||
16191d9cc8 | |||
703129f88b | |||
![]() |
c9364f631b | ||
![]() |
08dc5f6271 | ||
![]() |
c9cb9aa9e0 | ||
![]() |
25020c2d5b | ||
![]() |
1acffdb618 | ||
![]() |
0b4f3a60c0 | ||
![]() |
33d29e01b1 | ||
![]() |
a2e47cb793 |
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
|
||||||
|
2
.github/workflows/edge_ghpage.yml
vendored
2
.github/workflows/edge_ghpage.yml
vendored
@ -11,6 +11,8 @@ 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"
|
||||||
|
2
.github/workflows/github-release.yml
vendored
2
.github/workflows/github-release.yml
vendored
@ -14,6 +14,8 @@ 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"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# 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
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
"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-farsi": "^4.2.0",
|
||||||
|
"ra-language-russian": "^4.14.2",
|
||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
"react-admin": "^4.16.15",
|
"react-admin": "^4.16.15",
|
||||||
"react-dom": "^18.0.0",
|
"react-dom": "^18.0.0",
|
||||||
|
@ -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,6 +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";
|
||||||
|
|
||||||
// TODO: Can we use lazy loading together with browser locale?
|
// TODO: Can we use lazy loading together with browser locale?
|
||||||
const messages = {
|
const messages = {
|
||||||
@ -31,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),
|
||||||
@ -55,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" />
|
||||||
|
@ -21,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,
|
||||||
@ -37,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)",
|
||||||
@ -46,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",
|
||||||
},
|
},
|
||||||
@ -60,24 +60,31 @@ 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",
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -127,20 +134,6 @@ const LoginPage = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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");
|
||||||
@ -179,6 +172,7 @@ const LoginPage = () => {
|
|||||||
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;
|
||||||
@ -200,6 +194,14 @@ const LoginPage = () => {
|
|||||||
)
|
)
|
||||||
.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 => {
|
||||||
@ -219,7 +221,6 @@ const LoginPage = () => {
|
|||||||
<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}
|
||||||
@ -232,7 +233,6 @@ const LoginPage = () => {
|
|||||||
<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}
|
||||||
@ -245,7 +245,6 @@ const LoginPage = () => {
|
|||||||
<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 || loading}
|
disabled={cfg_base_url || loading}
|
||||||
resettable
|
resettable
|
||||||
@ -255,6 +254,7 @@ const LoginPage = () => {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Typography className="serverVersion">{serverVersion}</Typography>
|
<Typography className="serverVersion">{serverVersion}</Typography>
|
||||||
|
<Typography className="matrixVersions">{matrixVersions}</Typography>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -285,7 +285,7 @@ const LoginPage = () => {
|
|||||||
}}
|
}}
|
||||||
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>
|
||||||
@ -293,6 +293,7 @@ const LoginPage = () => {
|
|||||||
<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="fa">Persian(فارسی)</MenuItem>
|
||||||
|
<MenuItem value="ru">Русский</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
<FormDataConsumer>
|
<FormDataConsumer>
|
||||||
{formDataProps => <UserData {...formDataProps} />}
|
{formDataProps => <UserData {...formDataProps} />}
|
||||||
|
@ -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",
|
||||||
|
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;
|
@ -2,7 +2,7 @@ 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: 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 = process.env.REACT_APP_SERVER || base_url;
|
base_url = process.env.REACT_APP_SERVER || base_url;
|
||||||
|
|
||||||
@ -38,15 +38,14 @@ const authProvider = {
|
|||||||
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);
|
||||||
localStorage.setItem("home_server", json.home_server);
|
localStorage.setItem("home_server", json.home_server);
|
||||||
localStorage.setItem("user_id", json.user_id);
|
localStorage.setItem("user_id", json.user_id);
|
||||||
localStorage.setItem("access_token", json.access_token);
|
localStorage.setItem("access_token", json.access_token);
|
||||||
localStorage.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 = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
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 }) => {
|
||||||
|
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
|
||||||
|
Loading…
Reference in New Issue
Block a user