Compare commits
21 Commits
v0.10.3-et
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa3f2437a3 | ||
|
|
8dc5238fcb | ||
|
|
238350b940 | ||
|
|
99bf7b1889 | ||
|
|
d72c91644d | ||
|
|
e8e28b5df1 | ||
|
|
d5c10b6e02 | ||
|
|
3085b9ffa0 | ||
|
|
b2a3fb0f87 | ||
|
|
1e8b4cc885 | ||
|
|
4d1a9cc147 | ||
|
|
1b8b702270 | ||
|
|
61c32fb473 | ||
|
|
ad876bb790 | ||
|
|
2524848dae | ||
|
|
669c1f3079 | ||
|
|
590f673167 | ||
|
|
307793f000 | ||
|
|
96f549fe42 | ||
|
|
3de4332477 | ||
|
|
9fc005032c |
13
.dockerignore
Normal file
13
.dockerignore
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Exclude a bunch of stuff which can make the build context a larger than it needs to be
|
||||||
|
tests/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
lib/
|
||||||
|
node_modules/
|
||||||
|
electron_app/
|
||||||
|
karma-reports/
|
||||||
|
.pnp.cjs
|
||||||
|
.pnp.loader.mjs
|
||||||
|
.idea/
|
||||||
|
.tmp/
|
||||||
|
config.json*
|
||||||
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1 +0,0 @@
|
|||||||
liberapay: etkecc
|
|
||||||
20
.github/dependabot.yml
vendored
Normal file
20
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
ignore:
|
||||||
|
# Major updates for react-admin have breaking changes
|
||||||
|
- dependency-name: "react-admin"
|
||||||
|
update-types: ["version-update:semver-major"]
|
||||||
|
|
||||||
|
- package-ecosystem: "docker"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
26
.github/workflows/build-test.yml
vendored
Normal file
26
.github/workflows/build-test.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: build-test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["master"]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Setup node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "18"
|
||||||
|
- name: Install dependencies
|
||||||
|
run: yarn --immutable
|
||||||
|
- name: Run checks
|
||||||
|
run: yarn lint
|
||||||
|
- name: Run tests
|
||||||
|
run: yarn test
|
||||||
63
.github/workflows/docker-release.yml
vendored
Normal file
63
.github/workflows/docker-release.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
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:
|
||||||
|
push:
|
||||||
|
# Sequence of patterns matched against refs/heads
|
||||||
|
# prettier-ignore
|
||||||
|
branches:
|
||||||
|
# Push events on master branch
|
||||||
|
- master
|
||||||
|
# Sequence of patterns matched against refs/tags
|
||||||
|
tags:
|
||||||
|
- '[0-9]+\.[0-9]+\.[0-9]+' # Push events to 0.X.X tag
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker:
|
||||||
|
name: Push Docker image to multiple registries
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to GHCR
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
awesometechnologies/synapse-admin
|
||||||
|
ghcr.io/${{ github.repository }}
|
||||||
|
|
||||||
|
- name: Build and Push Tag
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
31
.github/workflows/edge_ghpage.yml
vendored
Normal file
31
.github/workflows/edge_ghpage.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
name: Build and Deploy Edge version to GH Pages
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- master
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
jobs:
|
||||||
|
build-and-deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout 🛎️
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 100
|
||||||
|
fetch-tags: true
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "20"
|
||||||
|
- name: Install and Build 🔧
|
||||||
|
run: |
|
||||||
|
yarn install --immutable
|
||||||
|
yarn build --base=/synapse-admin
|
||||||
|
|
||||||
|
- name: Deploy 🚀
|
||||||
|
uses: JamesIves/github-pages-deploy-action@v4.7.3
|
||||||
|
with:
|
||||||
|
branch: gh-pages
|
||||||
|
folder: dist
|
||||||
30
.github/workflows/github-release.yml
vendored
Normal file
30
.github/workflows/github-release.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
name: Create release tarball and attach to tag
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "20"
|
||||||
|
- run: yarn install --immutable
|
||||||
|
- run: yarn build
|
||||||
|
- run: |
|
||||||
|
version=`git describe --dirty --tags || echo unknown`
|
||||||
|
cp -r dist synapse-admin-$version
|
||||||
|
tar chvzf dist/synapse-admin-$version.tar.gz synapse-admin-$version
|
||||||
|
- uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631
|
||||||
|
with:
|
||||||
|
files: dist/*.tar.gz
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
119
.github/workflows/workflow.yml
vendored
119
.github/workflows/workflow.yml
vendored
@@ -1,119 +0,0 @@
|
|||||||
name: CI
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "main" ]
|
|
||||||
tags: [ "v*" ]
|
|
||||||
env:
|
|
||||||
bunny_version: v0.1.0
|
|
||||||
base_path: ./
|
|
||||||
permissions:
|
|
||||||
checks: write
|
|
||||||
contents: write
|
|
||||||
packages: write
|
|
||||||
pull-requests: read
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: lts/*
|
|
||||||
cache: yarn
|
|
||||||
- name: Install dependencies
|
|
||||||
run: yarn install --immutable --network-timeout=300000
|
|
||||||
- name: Set version into manifest.json
|
|
||||||
run: |
|
|
||||||
TAG=$(git describe --tags --abbrev=0 || echo "latest")
|
|
||||||
sed -i "s|\"icons\"|\"version\": \"$TAG\",\\n \"icons\"|g" public/manifest.json
|
|
||||||
- name: Build
|
|
||||||
run: yarn build --base=${{ env.base_path }}
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
path: dist/
|
|
||||||
name: dist
|
|
||||||
if-no-files-found: error
|
|
||||||
retention-days: 1
|
|
||||||
compression-level: 0
|
|
||||||
overwrite: true
|
|
||||||
include-hidden-files: true
|
|
||||||
|
|
||||||
docker:
|
|
||||||
name: Docker
|
|
||||||
needs: build
|
|
||||||
runs-on: self-hosted
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: dist
|
|
||||||
path: dist/
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
- name: Login to ghcr.io
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v5
|
|
||||||
with:
|
|
||||||
images: |
|
|
||||||
ghcr.io/${{ github.repository }}
|
|
||||||
registry.etke.cc/${{ github.repository }}
|
|
||||||
tags: |
|
|
||||||
type=raw,value=latest,enable=${{ github.ref_name == 'main' }}
|
|
||||||
type=semver,pattern={{raw}}
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
|
|
||||||
cdn:
|
|
||||||
name: CDN
|
|
||||||
needs: build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: dist
|
|
||||||
path: dist/
|
|
||||||
- name: Upload
|
|
||||||
run: |
|
|
||||||
wget -O bunny-upload.tar.gz https://github.com/etkecc/bunny-upload/releases/download/${{ env.bunny_version }}/bunny-upload_Linux_x86_64.tar.gz
|
|
||||||
tar -xzf bunny-upload.tar.gz
|
|
||||||
echo "${{ secrets.BUNNY_CONFIG }}" > bunny-config.yaml
|
|
||||||
./bunny-upload -c bunny-config.yaml
|
|
||||||
|
|
||||||
github-release:
|
|
||||||
name: Github Release
|
|
||||||
needs: build
|
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: dist
|
|
||||||
path: dist/
|
|
||||||
- name: Prepare release
|
|
||||||
run: |
|
|
||||||
mv dist synapse-admin
|
|
||||||
tar chvzf synapse-admin.tar.gz synapse-admin
|
|
||||||
- uses: softprops/action-gh-release@v2
|
|
||||||
with:
|
|
||||||
files: synapse-admin.tar.gz
|
|
||||||
generate_release_notes: true
|
|
||||||
make_latest: "true"
|
|
||||||
draft: false
|
|
||||||
prerelease: false
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -191,6 +191,3 @@ sketch
|
|||||||
# .pnp.*
|
# .pnp.*
|
||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/node,yarn,react,visualstudiocode
|
# End of https://www.toptal.com/developers/gitignore/api/node,yarn,react,visualstudiocode
|
||||||
|
|
||||||
/testdata/synapse.data
|
|
||||||
/testdata/postgres.data
|
|
||||||
|
|||||||
893
.yarn/releases/yarn-4.1.1.cjs
vendored
893
.yarn/releases/yarn-4.1.1.cjs
vendored
File diff suppressed because one or more lines are too long
925
.yarn/releases/yarn-4.4.1.cjs
vendored
Executable file
925
.yarn/releases/yarn-4.4.1.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
|||||||
yarnPath: .yarn/releases/yarn-4.1.1.cjs
|
yarnPath: .yarn/releases/yarn-4.4.1.cjs
|
||||||
|
|||||||
27
Dockerfile
27
Dockerfile
@@ -1,5 +1,26 @@
|
|||||||
FROM ghcr.io/static-web-server/static-web-server:2
|
# 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
|
||||||
|
# Base path for synapse admin
|
||||||
|
ARG BASE_PATH=./
|
||||||
|
|
||||||
ENV SERVER_ROOT=/app
|
WORKDIR /src
|
||||||
|
|
||||||
COPY ./dist /app
|
# Copy .yarn directory to the working directory (must be on a separate line!)
|
||||||
|
# Use https://docs.docker.com/engine/reference/builder/#copy---parents when available
|
||||||
|
COPY .yarn .yarn
|
||||||
|
COPY package.json .yarnrc.yml yarn.lock ./
|
||||||
|
|
||||||
|
# Disable telemetry and install packages
|
||||||
|
RUN yarn config set enableTelemetry 0 && yarn install --immutable --network-timeout=300000
|
||||||
|
|
||||||
|
COPY . /src
|
||||||
|
RUN yarn build --base=$BASE_PATH
|
||||||
|
|
||||||
|
# App
|
||||||
|
FROM nginx:stable-alpine
|
||||||
|
|
||||||
|
COPY --from=builder /src/dist /app
|
||||||
|
|
||||||
|
RUN rm -rf /usr/share/nginx/html \
|
||||||
|
&& ln -s /app /usr/share/nginx/html
|
||||||
|
|||||||
53
README.md
53
README.md
@@ -1,51 +1,14 @@
|
|||||||
# Synapse Admin UI [](https://github.com/Awesome-Technologies/synapse-admin/blob/master/LICENSE)
|
[](https://github.com/Awesome-Technologies/synapse-admin/blob/master/LICENSE)
|
||||||
|
[](https://app.travis-ci.com/github/Awesome-Technologies/synapse-admin)
|
||||||
|
[](https://github.com/Awesome-Technologies/synapse-admin/actions/workflows/build-test.yml)
|
||||||
|
[](https://awesome-technologies.github.io/synapse-admin/)
|
||||||
|
[](https://hub.docker.com/r/awesometechnologies/synapse-admin)
|
||||||
|
[](https://github.com/Awesome-Technologies/synapse-admin/releases)
|
||||||
|
|
||||||
|
# Synapse admin ui
|
||||||
|
|
||||||
This project is built using [react-admin](https://marmelab.com/react-admin/).
|
This project is built using [react-admin](https://marmelab.com/react-admin/).
|
||||||
|
|
||||||
## Fork differences
|
|
||||||
|
|
||||||
With [Awesome-Technologies/synapse-admin](https://github.com/Awesome-Technologies/synapse-admin) as the upstream, this
|
|
||||||
fork is intended to be a more feature-rich version of the original project. The main goal is to provide a more
|
|
||||||
user-friendly interface for managing Synapse homeservers.
|
|
||||||
|
|
||||||
### Available via CDN
|
|
||||||
|
|
||||||
On [admin.etke.cc](https://admin.etke.cc) you can find the latest version of this fork.
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
|
|
||||||
The following changes are already implemented:
|
|
||||||
|
|
||||||
* [Prevent admins from deleting themselves](https://github.com/etkecc/synapse-admin/pull/1)
|
|
||||||
* [Fix user's default tab not being shown](https://github.com/etkecc/synapse-admin/pull/8)
|
|
||||||
* [Add identifier when authorizing with password](https://github.com/Awesome-Technologies/synapse-admin/pull/601)
|
|
||||||
* [Add ability to toggle whether to show locked users](https://github.com/Awesome-Technologies/synapse-admin/pull/573)
|
|
||||||
* [Fix user's display name in header on user's page](https://github.com/etkecc/synapse-admin/pull/9)
|
|
||||||
* [Fix footer overlapping content](https://github.com/Awesome-Technologies/synapse-admin/issues/574)
|
|
||||||
* Switch from nginx to [SWS](https://static-web-server.net/) for serving the app, reducing the size of the Docker image
|
|
||||||
* [Fix redirect URL after user creation](https://github.com/etkecc/synapse-admin/pull/16)
|
|
||||||
* [Display actual Synapse errors](https://github.com/etkecc/synapse-admin/pull/17)
|
|
||||||
* [Fix base_url being undefined on unsuccessful login](https://github.com/etkecc/synapse-admin/pull/18)
|
|
||||||
* [Put the version into manifest.json](https://github.com/Awesome-Technologies/synapse-admin/issues/507) (CI only)
|
|
||||||
* [Federation page improvements](https://github.com/Awesome-Technologies/synapse-admin/pull/583) (using theme colors)
|
|
||||||
* [Add UI option to block deleted rooms from being rejoined](https://github.com/etkecc/synapse-admin/pull/26)
|
|
||||||
* [Fix required fields check on Bulk registration CSV upload](https://github.com/etkecc/synapse-admin/pull/32)
|
|
||||||
* [Fix requests with invalid MXIDs on Bulk registration](https://github.com/etkecc/synapse-admin/pull/33)
|
|
||||||
* [Expose user avatar URL field in the UI](https://github.com/etkecc/synapse-admin/pull/27)
|
|
||||||
|
|
||||||
_the list will be updated as new changes are added_
|
|
||||||
|
|
||||||
### Development
|
|
||||||
|
|
||||||
`just run-dev` to start the development stack (depending on your system speed, you may want to re-run this command if
|
|
||||||
user creation fails)
|
|
||||||
|
|
||||||
After that open `http://localhost:5173` in your browser, login using the following credentials:
|
|
||||||
|
|
||||||
* Login: admin
|
|
||||||
* Password: admin
|
|
||||||
* Homeserver URL: http://localhost:8008
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Supported Synapse
|
### Supported Synapse
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
services:
|
|
||||||
synapse:
|
|
||||||
image: ghcr.io/element-hq/synapse:latest
|
|
||||||
entrypoint: python
|
|
||||||
command: "-m synapse.app.homeserver -c /config/homeserver.yaml"
|
|
||||||
ports:
|
|
||||||
- "8008:8008"
|
|
||||||
volumes:
|
|
||||||
- ./testdata/synapse:/config
|
|
||||||
- ./testdata/synapse.data:/media-store
|
|
||||||
|
|
||||||
postgres:
|
|
||||||
image: postgres:alpine
|
|
||||||
volumes:
|
|
||||||
- ./testdata/postgres.data:/var/lib/postgresql/data
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: synapse
|
|
||||||
POSTGRES_PASSWORD: synapse
|
|
||||||
POSTGRES_DB: synapse
|
|
||||||
POSTGRES_INITDB_ARGS: "--lc-collate C --lc-ctype C --encoding UTF8"
|
|
||||||
@@ -2,13 +2,13 @@ services:
|
|||||||
synapse-admin:
|
synapse-admin:
|
||||||
container_name: synapse-admin
|
container_name: synapse-admin
|
||||||
hostname: synapse-admin
|
hostname: synapse-admin
|
||||||
image: ghcr.io/etkecc/synapse-admin:latest
|
image: awesometechnologies/synapse-admin:latest
|
||||||
# build:
|
# build:
|
||||||
# context: .
|
# context: .
|
||||||
|
|
||||||
# to use the docker-compose as standalone without a local repo clone,
|
# to use the docker-compose as standalone without a local repo clone,
|
||||||
# replace the context definition with this:
|
# replace the context definition with this:
|
||||||
# context: https://github.com/etkecc/synapse-admin.git
|
# context: https://github.com/Awesome-Technologies/synapse-admin.git
|
||||||
|
|
||||||
# args:
|
# args:
|
||||||
# - BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
|
# - BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
|
||||||
|
|||||||
@@ -121,8 +121,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<script type="module" src="/src/index.tsx"></script>
|
<script type="module" src="/src/index.tsx"></script>
|
||||||
<footer
|
<footer
|
||||||
style="position: relative; z-index: 2; height: 2em; margin-top: 0; line-height: 2em; background-color: #eee; border: 0.5px solid #ddd">
|
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/etkecc/synapse-admin"
|
<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;">
|
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><span id="version"></span></b> by Awesome Technologies Innovationslabor GmbH
|
Synapse-Admin <b><span id="version"></span></b> by Awesome Technologies Innovationslabor GmbH
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
40
justfile
40
justfile
@@ -1,40 +0,0 @@
|
|||||||
# Shows help
|
|
||||||
default:
|
|
||||||
@just --list --justfile {{ justfile() }}
|
|
||||||
|
|
||||||
# build the app
|
|
||||||
build: __install
|
|
||||||
@yarn run build --base=./
|
|
||||||
|
|
||||||
# run the app in a development mode
|
|
||||||
run:
|
|
||||||
@yarn start --host 0.0.0.0
|
|
||||||
|
|
||||||
# run dev stack and start the app in a development mode
|
|
||||||
run-dev:
|
|
||||||
@echo "Starting the database..."
|
|
||||||
@docker-compose -f docker-compose-dev.yml up -d postgres
|
|
||||||
@echo "Starting Synapse..."
|
|
||||||
@docker-compose -f docker-compose-dev.yml up -d synapse
|
|
||||||
@echo "Ensure admin user is registered..."
|
|
||||||
@docker-compose -f docker-compose-dev.yml exec synapse register_new_matrix_user --admin -u admin -p admin -c /config/homeserver.yaml http://localhost:8008 || true
|
|
||||||
@echo "Starting the app..."
|
|
||||||
@yarn start --host 0.0.0.0
|
|
||||||
|
|
||||||
# stop the dev stack
|
|
||||||
stop-dev:
|
|
||||||
@docker-compose -f docker-compose-dev.yml stop
|
|
||||||
|
|
||||||
|
|
||||||
register-user localpart password *admin:
|
|
||||||
docker-compose exec synapse register_new_matrix_user {{ if admin == "1" {"--admin"} else {"--no-admin"} }} -u {{ localpart }} -p {{ password }} -c /config/homeserver.yaml http://localhost:8008
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# run the app in a production mode
|
|
||||||
run-prod: build
|
|
||||||
@python -m http.server -d dist 1313
|
|
||||||
|
|
||||||
# install the project
|
|
||||||
__install:
|
|
||||||
@yarn install --immutable --network-timeout=300000
|
|
||||||
31
package.json
31
package.json
@@ -8,12 +8,13 @@
|
|||||||
"homepage": ".",
|
"homepage": ".",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/etkecc/synapse-admin"
|
"url": "https://github.com/Awesome-Technologies/synapse-admin"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@4.1.1",
|
"packageManager": "yarn@4.4.1",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.7.0",
|
"@eslint/js": "^9.7.0",
|
||||||
"@testing-library/dom": "^10.0.0",
|
"@mui/utils": "^6.1.3",
|
||||||
|
"@testing-library/dom": "^10.4.0",
|
||||||
"@testing-library/jest-dom": "^6.0.0",
|
"@testing-library/jest-dom": "^6.0.0",
|
||||||
"@testing-library/react": "^16.0.0",
|
"@testing-library/react": "^16.0.0",
|
||||||
"@testing-library/user-event": "^14.5.2",
|
"@testing-library/user-event": "^14.5.2",
|
||||||
@@ -30,7 +31,7 @@
|
|||||||
"eslint-plugin-import": "^2.29.1",
|
"eslint-plugin-import": "^2.29.1",
|
||||||
"eslint-plugin-jsx-a11y": "^6.9.0",
|
"eslint-plugin-jsx-a11y": "^6.9.0",
|
||||||
"eslint-plugin-prettier": "^5.2.1",
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
"eslint-plugin-unused-imports": "^3.2.0",
|
"eslint-plugin-unused-imports": "^4.1.4",
|
||||||
"eslint-plugin-yaml": "^1.0.3",
|
"eslint-plugin-yaml": "^1.0.3",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"jest-environment-jsdom": "^29.7.0",
|
"jest-environment-jsdom": "^29.7.0",
|
||||||
@@ -40,8 +41,8 @@
|
|||||||
"ts-jest": "^29.2.3",
|
"ts-jest": "^29.2.3",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.4.5",
|
"typescript": "^5.4.5",
|
||||||
"typescript-eslint": "^7.16.1",
|
"typescript-eslint": "^8.32.1",
|
||||||
"vite": "^5.3.4",
|
"vite": "^6.3.5",
|
||||||
"vite-plugin-version-mark": "^0.1.0"
|
"vite-plugin-version-mark": "^0.1.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -51,25 +52,25 @@
|
|||||||
"@haxqer/ra-language-chinese": "^4.16.2",
|
"@haxqer/ra-language-chinese": "^4.16.2",
|
||||||
"@mui/icons-material": "^5.16.4",
|
"@mui/icons-material": "^5.16.4",
|
||||||
"@mui/material": "^5.16.4",
|
"@mui/material": "^5.16.4",
|
||||||
|
"@tanstack/react-query": "^5.59.12",
|
||||||
"history": "^5.3.0",
|
"history": "^5.3.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"papaparse": "^5.4.1",
|
"papaparse": "^5.4.1",
|
||||||
"query-string": "^7.1.3",
|
"query-string": "^7.1.3",
|
||||||
"ra-core": "^4.16.20",
|
"ra-core": "^5.2.3",
|
||||||
"ra-i18n-polyglot": "^4.16.20",
|
"ra-i18n-polyglot": "^5.2.3",
|
||||||
"ra-language-english": "^4.16.20",
|
"ra-language-english": "^5.8.2",
|
||||||
"ra-language-farsi": "^4.2.0",
|
"ra-language-farsi": "^5.0.0",
|
||||||
"ra-language-french": "^4.16.20",
|
"ra-language-french": "^5.2.3",
|
||||||
"ra-language-italian": "^3.13.1",
|
"ra-language-italian": "^3.13.1",
|
||||||
"ra-language-russian": "^4.14.2",
|
"ra-language-russian": "^4.14.2",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-admin": "^4.16.20",
|
"react-admin": "^5.2.3",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-hook-form": "^7.52.1",
|
"react-hook-form": "^7.52.1",
|
||||||
"react-is": "^18.3.1",
|
"react-is": "^18.3.1",
|
||||||
"react-query": "^3.39.3",
|
"react-router": "^6.28.1",
|
||||||
"react-router": "^6.25.1",
|
"react-router-dom": "^6.28.1"
|
||||||
"react-router-dom": "^6.25.1"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite serve",
|
"start": "vite serve",
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { render, screen } from "@testing-library/react";
|
import { render, screen } from "@testing-library/react";
|
||||||
|
import fetchMock from "jest-fetch-mock";
|
||||||
|
fetchMock.enableMocks();
|
||||||
|
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ const App = () => (
|
|||||||
authProvider={authProvider}
|
authProvider={authProvider}
|
||||||
dataProvider={dataProvider}
|
dataProvider={dataProvider}
|
||||||
i18nProvider={i18nProvider}
|
i18nProvider={i18nProvider}
|
||||||
darkTheme={{ palette: { mode: "dark" } }}
|
|
||||||
>
|
>
|
||||||
<CustomRoutes>
|
<CustomRoutes>
|
||||||
<Route path="/import_users" element={<ImportFeature />} />
|
<Route path="/import_users" element={<ImportFeature />} />
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle } from "@mui/material";
|
|
||||||
import { Fragment, useState } from "react";
|
|
||||||
import { SimpleForm, BooleanInput, useTranslate, RaRecord, useNotify, useRedirect, useDelete, NotificationType, useDeleteMany, Identifier, useUnselectAll } from "react-admin";
|
|
||||||
import ActionDelete from "@mui/icons-material/Delete";
|
|
||||||
import ActionCheck from "@mui/icons-material/CheckCircle";
|
|
||||||
import AlertError from "@mui/icons-material/ErrorOutline";
|
|
||||||
|
|
||||||
interface DeleteRoomButtonProps {
|
|
||||||
selectedIds: Identifier[];
|
|
||||||
confirmTitle: string;
|
|
||||||
confirmContent: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const resourceName = "rooms";
|
|
||||||
|
|
||||||
const DeleteRoomButton: React.FC<DeleteRoomButtonProps> = (props) => {
|
|
||||||
const translate = useTranslate();
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const [block, setBlock] = useState(true);
|
|
||||||
|
|
||||||
const notify = useNotify();
|
|
||||||
const redirect = useRedirect();
|
|
||||||
|
|
||||||
const [deleteMany, { isLoading }] = useDeleteMany();
|
|
||||||
const unselectAll = useUnselectAll(resourceName);
|
|
||||||
const recordIds = props.selectedIds;
|
|
||||||
|
|
||||||
const handleDialogOpen = () => setOpen(true);
|
|
||||||
const handleDialogClose = () => setOpen(false);
|
|
||||||
|
|
||||||
const handleDelete = (values: {block: boolean}) => {
|
|
||||||
deleteMany(
|
|
||||||
resourceName,
|
|
||||||
{ ids: recordIds, meta: values },
|
|
||||||
{
|
|
||||||
onSuccess: () => {
|
|
||||||
notify("resources.rooms.action.erase.success");
|
|
||||||
handleDialogClose();
|
|
||||||
unselectAll();
|
|
||||||
redirect("/rooms");
|
|
||||||
},
|
|
||||||
onError: (error) =>
|
|
||||||
notify("resources.rooms.action.erase.failure", { type: 'error' as NotificationType }),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleConfirm = () => {
|
|
||||||
setOpen(false);
|
|
||||||
handleDelete({ block: block });
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<Button
|
|
||||||
onClick={handleDialogOpen}
|
|
||||||
disabled={isLoading}
|
|
||||||
className={"ra-delete-button"}
|
|
||||||
key="button"
|
|
||||||
size="small"
|
|
||||||
sx={{
|
|
||||||
"&.MuiButton-sizeSmall": {
|
|
||||||
lineHeight: 1.5,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
color={"error"}
|
|
||||||
startIcon={<ActionDelete />}
|
|
||||||
>
|
|
||||||
{translate("ra.action.delete")}
|
|
||||||
</Button>
|
|
||||||
<Dialog open={open} onClose={handleDialogClose}>
|
|
||||||
<DialogTitle>{translate(props.confirmTitle)}</DialogTitle>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogContentText>{translate(props.confirmContent)}</DialogContentText>
|
|
||||||
<SimpleForm toolbar={false}>
|
|
||||||
<BooleanInput
|
|
||||||
fullWidth
|
|
||||||
source="block"
|
|
||||||
value={block}
|
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => setBlock(event.target.checked)}
|
|
||||||
label="resources.rooms.action.erase.fields.block"
|
|
||||||
defaultValue={true}
|
|
||||||
/>
|
|
||||||
</SimpleForm>
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
<Button disabled={false} onClick={handleDialogClose} startIcon={<AlertError />}>
|
|
||||||
{translate("ra.action.cancel")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
disabled={false}
|
|
||||||
onClick={handleConfirm}
|
|
||||||
className={"ra-confirm RaConfirm-confirmPrimary"}
|
|
||||||
autoFocus
|
|
||||||
startIcon={<ActionCheck />}
|
|
||||||
>
|
|
||||||
{translate("ra.action.confirm")}
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DeleteRoomButton;
|
|
||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
import { DataProvider, useTranslate } from "ra-core";
|
import { DataProvider, useTranslate } from "ra-core";
|
||||||
import { useDataProvider, useNotify, RaRecord, Title } from "react-admin";
|
import { useDataProvider, useNotify, RaRecord, Title } from "react-admin";
|
||||||
|
|
||||||
import { generateRandomMxId, generateRandomPassword, returnMXID } from "../synapse/synapse";
|
import { generateRandomMxId, generateRandomPassword } from "../synapse/synapse";
|
||||||
|
|
||||||
const LOGGING = true;
|
const LOGGING = true;
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ const FilePicker = () => {
|
|||||||
|
|
||||||
const [conflictMode, setConflictMode] = useState("stop");
|
const [conflictMode, setConflictMode] = useState("stop");
|
||||||
const [passwordMode, setPasswordMode] = useState(true);
|
const [passwordMode, setPasswordMode] = useState(true);
|
||||||
const [useridMode, setUseridMode] = useState("update");
|
const [useridMode, setUseridMode] = useState("ignore");
|
||||||
|
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
@@ -121,11 +121,7 @@ const FilePicker = () => {
|
|||||||
|
|
||||||
const verifyCsv = ({ data, meta, errors }: ParseResult<ImportLine>, { setValues, setStats, setError }) => {
|
const verifyCsv = ({ data, meta, errors }: ParseResult<ImportLine>, { setValues, setStats, setError }) => {
|
||||||
/* First, verify the presence of required fields */
|
/* First, verify the presence of required fields */
|
||||||
const missingFields = expectedFields.filter(eF => {
|
const missingFields = expectedFields.filter(eF => !meta.fields?.includes(eF));
|
||||||
const result = meta.fields?.find(mF => eF === mF);
|
|
||||||
if (result === undefined) { return eF; } // missing field
|
|
||||||
return undefined; // field found
|
|
||||||
});
|
|
||||||
|
|
||||||
if (missingFields.length > 0) {
|
if (missingFields.length > 0) {
|
||||||
setError(translate("import_users.error.required_field", { field: missingFields[0] }));
|
setError(translate("import_users.error.required_field", { field: missingFields[0] }));
|
||||||
@@ -266,15 +262,12 @@ const FilePicker = () => {
|
|||||||
const userRecord = { ...entry };
|
const userRecord = { ...entry };
|
||||||
// No need to do a bunch of cryptographic random number getting if
|
// No need to do a bunch of cryptographic random number getting if
|
||||||
// we are using neither a generated password nor a generated user id.
|
// we are using neither a generated password nor a generated user id.
|
||||||
if (useridMode === "ignore" || userRecord.id === undefined || userRecord.id === "") {
|
if (useridMode === "ignore" || userRecord.id === undefined) {
|
||||||
userRecord.id = generateRandomMxId();
|
userRecord.id = generateRandomMxId();
|
||||||
}
|
}
|
||||||
if (passwordMode === false || entry.password === undefined || entry.password === "") {
|
if (passwordMode === false || entry.password === undefined) {
|
||||||
userRecord.password = generateRandomPassword();
|
userRecord.password = generateRandomPassword();
|
||||||
}
|
}
|
||||||
// we want to ensure that the ID is always full MXID, otherwise randomly-generated MXIDs will be in the full
|
|
||||||
// form, but the ones from the CSV will be localpart-only.
|
|
||||||
userRecord.id = returnMXID(userRecord.id);
|
|
||||||
/* TODO record update stats (especially admin no -> yes, deactivated x -> !x, ... */
|
/* TODO record update stats (especially admin no -> yes, deactivated x -> !x, ... */
|
||||||
|
|
||||||
/* For these modes we will consider the ID that's in the record.
|
/* For these modes we will consider the ID that's in the record.
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
useTranslate,
|
useTranslate,
|
||||||
useUnselectAll,
|
useUnselectAll,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import { useMutation } from "react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
|
|
||||||
const ServerNoticeDialog = ({ open, onClose, onSubmit }) => {
|
const ServerNoticeDialog = ({ open, onClose, onSubmit }) => {
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
@@ -43,7 +43,6 @@ const ServerNoticeDialog = ({ open, onClose, onSubmit }) => {
|
|||||||
<TextInput
|
<TextInput
|
||||||
source="body"
|
source="body"
|
||||||
label="resources.servernotices.fields.body"
|
label="resources.servernotices.fields.body"
|
||||||
fullWidth
|
|
||||||
multiline
|
multiline
|
||||||
rows="4"
|
rows="4"
|
||||||
resettable
|
resettable
|
||||||
@@ -64,6 +63,10 @@ export const ServerNoticeButton = () => {
|
|||||||
const handleDialogOpen = () => setOpen(true);
|
const handleDialogOpen = () => setOpen(true);
|
||||||
const handleDialogClose = () => setOpen(false);
|
const handleDialogClose = () => setOpen(false);
|
||||||
|
|
||||||
|
if (!record) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const handleSend = (values: Partial<RaRecord>) => {
|
const handleSend = (values: Partial<RaRecord>) => {
|
||||||
create(
|
create(
|
||||||
"servernotices",
|
"servernotices",
|
||||||
@@ -100,13 +103,12 @@ export const ServerNoticeBulkButton = () => {
|
|||||||
const unselectAllUsers = useUnselectAll("users");
|
const unselectAllUsers = useUnselectAll("users");
|
||||||
const dataProvider = useDataProvider();
|
const dataProvider = useDataProvider();
|
||||||
|
|
||||||
const { mutate: sendNotices, isLoading } = useMutation(
|
const { mutate: sendNotices, isPending } = useMutation({
|
||||||
data =>
|
mutationFn: (data) =>
|
||||||
dataProvider.createMany("servernotices", {
|
dataProvider.createMany("servernotices", {
|
||||||
ids: selectedIds,
|
ids: selectedIds,
|
||||||
data: data,
|
data: data,
|
||||||
}),
|
}),
|
||||||
{
|
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notify("resources.servernotices.action.send_success");
|
notify("resources.servernotices.action.send_success");
|
||||||
unselectAllUsers();
|
unselectAllUsers();
|
||||||
@@ -116,12 +118,11 @@ export const ServerNoticeBulkButton = () => {
|
|||||||
notify("resources.servernotices.action.send_failure", {
|
notify("resources.servernotices.action.send_failure", {
|
||||||
type: "error",
|
type: "error",
|
||||||
}),
|
}),
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button label="resources.servernotices.send" onClick={openDialog} disabled={isLoading}>
|
<Button label="resources.servernotices.send" onClick={openDialog} disabled={isPending}>
|
||||||
<MessageIcon />
|
<MessageIcon />
|
||||||
</Button>
|
</Button>
|
||||||
<ServerNoticeDialog open={open} onClose={closeDialog} onSubmit={sendNotices} />
|
<ServerNoticeDialog open={open} onClose={closeDialog} onSubmit={sendNotices} />
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
export type MatrixError = {
|
|
||||||
errcode: string;
|
|
||||||
error: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const displayError = (errcode: string, status: number, message: string) => `${errcode} (${status}): ${message}`;
|
|
||||||
@@ -28,7 +28,7 @@ import {
|
|||||||
useRefresh,
|
useRefresh,
|
||||||
useTranslate,
|
useTranslate,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import { useMutation } from "react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
import { dateParser } from "./date";
|
import { dateParser } from "./date";
|
||||||
@@ -55,14 +55,12 @@ const DeleteMediaDialog = ({ open, onClose, onSubmit }) => {
|
|||||||
<DialogContentText>{translate("delete_media.helper.send")}</DialogContentText>
|
<DialogContentText>{translate("delete_media.helper.send")}</DialogContentText>
|
||||||
<SimpleForm toolbar={<DeleteMediaToolbar />} onSubmit={onSubmit}>
|
<SimpleForm toolbar={<DeleteMediaToolbar />} onSubmit={onSubmit}>
|
||||||
<DateTimeInput
|
<DateTimeInput
|
||||||
fullWidth
|
|
||||||
source="before_ts"
|
source="before_ts"
|
||||||
label="delete_media.fields.before_ts"
|
label="delete_media.fields.before_ts"
|
||||||
defaultValue={0}
|
defaultValue={0}
|
||||||
parse={dateParser}
|
parse={dateParser}
|
||||||
/>
|
/>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
fullWidth
|
|
||||||
source="size_gt"
|
source="size_gt"
|
||||||
label="delete_media.fields.size_gt"
|
label="delete_media.fields.size_gt"
|
||||||
defaultValue={0}
|
defaultValue={0}
|
||||||
@@ -70,7 +68,6 @@ const DeleteMediaDialog = ({ open, onClose, onSubmit }) => {
|
|||||||
step={1024}
|
step={1024}
|
||||||
/>
|
/>
|
||||||
<BooleanInput
|
<BooleanInput
|
||||||
fullWidth
|
|
||||||
source="keep_profiles"
|
source="keep_profiles"
|
||||||
label="delete_media.fields.keep_profiles"
|
label="delete_media.fields.keep_profiles"
|
||||||
defaultValue={true}
|
defaultValue={true}
|
||||||
@@ -86,9 +83,8 @@ export const DeleteMediaButton = (props: ButtonProps) => {
|
|||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
const dataProvider = useDataProvider<SynapseDataProvider>();
|
const dataProvider = useDataProvider<SynapseDataProvider>();
|
||||||
const { mutate: deleteMedia, isLoading } = useMutation(
|
const { mutate: deleteMedia, isPending } = useMutation({
|
||||||
(values: DeleteMediaParams) => dataProvider.deleteMedia(values),
|
mutationFn: (values: DeleteMediaParams) => dataProvider.deleteMedia(values),
|
||||||
{
|
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notify("delete_media.action.send_success");
|
notify("delete_media.action.send_success");
|
||||||
closeDialog();
|
closeDialog();
|
||||||
@@ -98,8 +94,7 @@ export const DeleteMediaButton = (props: ButtonProps) => {
|
|||||||
type: "error",
|
type: "error",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const openDialog = () => setOpen(true);
|
const openDialog = () => setOpen(true);
|
||||||
const closeDialog = () => setOpen(false);
|
const closeDialog = () => setOpen(false);
|
||||||
@@ -110,7 +105,7 @@ export const DeleteMediaButton = (props: ButtonProps) => {
|
|||||||
{...props}
|
{...props}
|
||||||
label="delete_media.action.send"
|
label="delete_media.action.send"
|
||||||
onClick={openDialog}
|
onClick={openDialog}
|
||||||
disabled={isLoading}
|
disabled={isPending}
|
||||||
sx={{
|
sx={{
|
||||||
color: theme.palette.error.main,
|
color: theme.palette.error.main,
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
|
|||||||
@@ -4,6 +4,14 @@ import { SynapseTranslationMessages } from ".";
|
|||||||
|
|
||||||
const de: SynapseTranslationMessages = {
|
const de: SynapseTranslationMessages = {
|
||||||
...formalGermanMessages,
|
...formalGermanMessages,
|
||||||
|
ra: {
|
||||||
|
...formalGermanMessages.ra,
|
||||||
|
navigation: {
|
||||||
|
...formalGermanMessages.ra.navigation,
|
||||||
|
no_filtered_results: "Keine Ergebnisse",
|
||||||
|
clear_filters: "Alle Filter entfernen",
|
||||||
|
},
|
||||||
|
},
|
||||||
synapseadmin: {
|
synapseadmin: {
|
||||||
auth: {
|
auth: {
|
||||||
base_url: "Heimserver URL",
|
base_url: "Heimserver URL",
|
||||||
@@ -125,7 +133,6 @@ const de: SynapseTranslationMessages = {
|
|||||||
erased: "Gelöscht",
|
erased: "Gelöscht",
|
||||||
guests: "Zeige Gäste",
|
guests: "Zeige Gäste",
|
||||||
show_deactivated: "Zeige deaktivierte Benutzer",
|
show_deactivated: "Zeige deaktivierte Benutzer",
|
||||||
show_locked: "Zeige gesperrte Benutzer",
|
|
||||||
user_id: "Suche Benutzer",
|
user_id: "Suche Benutzer",
|
||||||
displayname: "Anzeigename",
|
displayname: "Anzeigename",
|
||||||
password: "Passwort",
|
password: "Passwort",
|
||||||
@@ -143,11 +150,9 @@ const de: SynapseTranslationMessages = {
|
|||||||
password: "Durch die Änderung des Passworts wird der Benutzer von allen Sitzungen abgemeldet.",
|
password: "Durch die Änderung des Passworts wird der Benutzer von allen Sitzungen abgemeldet.",
|
||||||
deactivate: "Sie müssen ein Passwort angeben, um ein Konto wieder zu aktivieren.",
|
deactivate: "Sie müssen ein Passwort angeben, um ein Konto wieder zu aktivieren.",
|
||||||
erase: "DSGVO konformes Löschen der Benutzerdaten",
|
erase: "DSGVO konformes Löschen der Benutzerdaten",
|
||||||
erase_admin_error: "Das Löschen des eigenen Benutzers ist nicht erlaubt",
|
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
erase: "Lösche Benutzerdaten",
|
erase: "Lösche Benutzerdaten",
|
||||||
erase_avatar: "Avatar löschen"
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
rooms: {
|
rooms: {
|
||||||
@@ -200,11 +205,6 @@ const de: SynapseTranslationMessages = {
|
|||||||
title: "Raum löschen",
|
title: "Raum löschen",
|
||||||
content:
|
content:
|
||||||
"Sind Sie sicher dass Sie den Raum löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden. Alle Nachrichten und Medien, die der Raum beinhaltet werden vom Server gelöscht!",
|
"Sind Sie sicher dass Sie den Raum löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden. Alle Nachrichten und Medien, die der Raum beinhaltet werden vom Server gelöscht!",
|
||||||
fields: {
|
|
||||||
block: "Benutzer blockieren und daran hindern, dem Raum beizutreten",
|
|
||||||
},
|
|
||||||
success: "Raum/Räume erfolgreich gelöscht.",
|
|
||||||
failure: "Der/die Raum/Räume konnten nicht gelöscht werden.",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -142,11 +142,9 @@ const en: SynapseTranslationMessages = {
|
|||||||
password: "Changing password will log user out of all sessions.",
|
password: "Changing password will log user out of all sessions.",
|
||||||
deactivate: "You must provide a password to re-activate an account.",
|
deactivate: "You must provide a password to re-activate an account.",
|
||||||
erase: "Mark the user as GDPR-erased",
|
erase: "Mark the user as GDPR-erased",
|
||||||
erase_admin_error: "Deleting own user is not allowed.",
|
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
erase: "Erase user data",
|
erase: "Erase user data",
|
||||||
erase_avatar: "Erase avatar"
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
rooms: {
|
rooms: {
|
||||||
@@ -199,11 +197,6 @@ const en: SynapseTranslationMessages = {
|
|||||||
title: "Delete room",
|
title: "Delete room",
|
||||||
content:
|
content:
|
||||||
"Are you sure you want to delete the room? This cannot be undone. All messages and shared media in the room will be deleted from the server!",
|
"Are you sure you want to delete the room? This cannot be undone. All messages and shared media in the room will be deleted from the server!",
|
||||||
fields: {
|
|
||||||
block: "Block and prevent users from joining the room",
|
|
||||||
},
|
|
||||||
success: "Room/s successfully deleted.",
|
|
||||||
failure: "The room/s could not be deleted.",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -120,7 +120,6 @@ const fa: SynapseTranslationMessages = {
|
|||||||
deactivated: "غیرفعال",
|
deactivated: "غیرفعال",
|
||||||
guests: "نمایش مهمانان",
|
guests: "نمایش مهمانان",
|
||||||
show_deactivated: "نمایش کاربران غیرفعال شده",
|
show_deactivated: "نمایش کاربران غیرفعال شده",
|
||||||
show_locked: "نمایش کاربران قفل شده",
|
|
||||||
user_id: "جستجوی کاربر",
|
user_id: "جستجوی کاربر",
|
||||||
displayname: "نام نمایشی",
|
displayname: "نام نمایشی",
|
||||||
password: "رمز عبور",
|
password: "رمز عبور",
|
||||||
|
|||||||
@@ -124,7 +124,6 @@ const fr: SynapseTranslationMessages = {
|
|||||||
deactivated: "Désactivé",
|
deactivated: "Désactivé",
|
||||||
guests: "Afficher les visiteurs",
|
guests: "Afficher les visiteurs",
|
||||||
show_deactivated: "Afficher les utilisateurs désactivés",
|
show_deactivated: "Afficher les utilisateurs désactivés",
|
||||||
show_locked: "Afficher les utilisateurs verrouillés",
|
|
||||||
user_id: "Rechercher un utilisateur",
|
user_id: "Rechercher un utilisateur",
|
||||||
displayname: "Nom d'affichage",
|
displayname: "Nom d'affichage",
|
||||||
password: "Mot de passe",
|
password: "Mot de passe",
|
||||||
@@ -140,11 +139,9 @@ const fr: SynapseTranslationMessages = {
|
|||||||
helper: {
|
helper: {
|
||||||
deactivate: "Vous devrez fournir un mot de passe pour réactiver le compte.",
|
deactivate: "Vous devrez fournir un mot de passe pour réactiver le compte.",
|
||||||
erase: "Marquer l'utilisateur comme effacé conformément au RGPD",
|
erase: "Marquer l'utilisateur comme effacé conformément au RGPD",
|
||||||
erase_admin_error: "La suppression de son propre utilisateur n'est pas autorisée.",
|
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
erase: "Effacer les données de l'utilisateur",
|
erase: "Effacer les données de l'utilisateur",
|
||||||
erase_avatar: "Effacer l'avatar",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
rooms: {
|
rooms: {
|
||||||
@@ -197,11 +194,6 @@ const fr: SynapseTranslationMessages = {
|
|||||||
title: "Supprimer le salon",
|
title: "Supprimer le salon",
|
||||||
content:
|
content:
|
||||||
"Voulez-vous vraiment supprimer le salon ? Cette opération ne peut être annulée. Tous les messages et médias partagés du salon seront supprimés du serveur !",
|
"Voulez-vous vraiment supprimer le salon ? Cette opération ne peut être annulée. Tous les messages et médias partagés du salon seront supprimés du serveur !",
|
||||||
fields: {
|
|
||||||
block: "Bloquer et empêcher les utilisateurs de rejoindre la salle",
|
|
||||||
},
|
|
||||||
success: "Salle/s supprimées avec succès.",
|
|
||||||
failure: "La/les salle/s n'ont pas pu être supprimées.",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
7
src/i18n/index.d.ts
vendored
7
src/i18n/index.d.ts
vendored
@@ -138,11 +138,9 @@ interface SynapseTranslationMessages extends TranslationMessages {
|
|||||||
password?: string;
|
password?: string;
|
||||||
deactivate: string;
|
deactivate: string;
|
||||||
erase: string;
|
erase: string;
|
||||||
erase_admin_error: string;
|
|
||||||
};
|
};
|
||||||
action: {
|
action: {
|
||||||
erase: string;
|
erase: string;
|
||||||
erase_avatar: string;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
rooms: {
|
rooms: {
|
||||||
@@ -193,11 +191,6 @@ interface SynapseTranslationMessages extends TranslationMessages {
|
|||||||
erase: {
|
erase: {
|
||||||
title: string;
|
title: string;
|
||||||
content: string;
|
content: string;
|
||||||
fields: {
|
|
||||||
block: string;
|
|
||||||
},
|
|
||||||
success: string;
|
|
||||||
failure: string;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -121,7 +121,6 @@ const it: SynapseTranslationMessages = {
|
|||||||
deactivated: "Disattivato",
|
deactivated: "Disattivato",
|
||||||
guests: "Mostra gli ospiti",
|
guests: "Mostra gli ospiti",
|
||||||
show_deactivated: "Mostra gli utenti disattivati",
|
show_deactivated: "Mostra gli utenti disattivati",
|
||||||
show_locked: "Mostra gli utenti bloccati",
|
|
||||||
user_id: "Cerca utente",
|
user_id: "Cerca utente",
|
||||||
displayname: "Nickname",
|
displayname: "Nickname",
|
||||||
password: "Password",
|
password: "Password",
|
||||||
@@ -142,7 +141,6 @@ const it: SynapseTranslationMessages = {
|
|||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
erase: "Cancella i dati dell'utente",
|
erase: "Cancella i dati dell'utente",
|
||||||
erase_admin_error: "Non è consentito eliminare il proprio utente.",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
rooms: {
|
rooms: {
|
||||||
|
|||||||
@@ -4,6 +4,14 @@ import { SynapseTranslationMessages } from ".";
|
|||||||
|
|
||||||
const ru: SynapseTranslationMessages = {
|
const ru: SynapseTranslationMessages = {
|
||||||
...russianMessages,
|
...russianMessages,
|
||||||
|
ra: {
|
||||||
|
...russianMessages.ra,
|
||||||
|
navigation: {
|
||||||
|
...russianMessages.ra.navigation,
|
||||||
|
no_filtered_results: "Нет результатов",
|
||||||
|
clear_filters: "Все фильтры сбросить",
|
||||||
|
},
|
||||||
|
},
|
||||||
synapseadmin: {
|
synapseadmin: {
|
||||||
auth: {
|
auth: {
|
||||||
base_url: "Адрес домашнего сервера",
|
base_url: "Адрес домашнего сервера",
|
||||||
@@ -133,7 +141,6 @@ const ru: SynapseTranslationMessages = {
|
|||||||
erased: "Удалён",
|
erased: "Удалён",
|
||||||
guests: "Показывать гостей",
|
guests: "Показывать гостей",
|
||||||
show_deactivated: "Показывать деактивированных",
|
show_deactivated: "Показывать деактивированных",
|
||||||
show_locked: "Показывать заблокированных",
|
|
||||||
user_id: "Поиск пользователя",
|
user_id: "Поиск пользователя",
|
||||||
displayname: "Отображаемое имя",
|
displayname: "Отображаемое имя",
|
||||||
password: "Пароль",
|
password: "Пароль",
|
||||||
@@ -151,11 +158,9 @@ const ru: SynapseTranslationMessages = {
|
|||||||
password: "Смена пароля завершит все сессии пользователя.",
|
password: "Смена пароля завершит все сессии пользователя.",
|
||||||
deactivate: "Вы должны предоставить пароль для реактивации учётной записи.",
|
deactivate: "Вы должны предоставить пароль для реактивации учётной записи.",
|
||||||
erase: "Пометить пользователя как удалённого в соответствии с GDPR",
|
erase: "Пометить пользователя как удалённого в соответствии с GDPR",
|
||||||
erase_admin_error: "Удаление собственного пользователя запрещено.",
|
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
erase: "Удалить данные пользователя",
|
erase: "Удалить данные пользователя",
|
||||||
erase_avatar: "Удалить аватар",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
rooms: {
|
rooms: {
|
||||||
@@ -211,11 +216,6 @@ const ru: SynapseTranslationMessages = {
|
|||||||
title: "Удалить комнату",
|
title: "Удалить комнату",
|
||||||
content:
|
content:
|
||||||
"Действительно удалить эту комнату? Это действие будет невозможно отменить. Все сообщения и файлы в комнате будут удалены с сервера!",
|
"Действительно удалить эту комнату? Это действие будет невозможно отменить. Все сообщения и файлы в комнате будут удалены с сервера!",
|
||||||
fields: {
|
|
||||||
block: "Заблокировать и запретить пользователям присоединяться к комнате",
|
|
||||||
},
|
|
||||||
success: "Комната/ы успешно удалены",
|
|
||||||
failure: "Комната/ы не могут быть удалены.",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,14 @@ import { SynapseTranslationMessages } from ".";
|
|||||||
|
|
||||||
const zh: SynapseTranslationMessages = {
|
const zh: SynapseTranslationMessages = {
|
||||||
...chineseMessages,
|
...chineseMessages,
|
||||||
|
ra: {
|
||||||
|
...chineseMessages.ra,
|
||||||
|
navigation: {
|
||||||
|
...chineseMessages.ra.navigation,
|
||||||
|
no_filtered_results: "没有结果",
|
||||||
|
clear_filters: "清除所有过滤器",
|
||||||
|
},
|
||||||
|
},
|
||||||
synapseadmin: {
|
synapseadmin: {
|
||||||
auth: {
|
auth: {
|
||||||
base_url: "服务器 URL",
|
base_url: "服务器 URL",
|
||||||
@@ -120,7 +128,6 @@ const zh: SynapseTranslationMessages = {
|
|||||||
deactivated: "被禁用",
|
deactivated: "被禁用",
|
||||||
guests: "显示访客",
|
guests: "显示访客",
|
||||||
show_deactivated: "显示被禁用的账户",
|
show_deactivated: "显示被禁用的账户",
|
||||||
show_locked: "显示被锁定的账户",
|
|
||||||
user_id: "搜索用户",
|
user_id: "搜索用户",
|
||||||
displayname: "显示名字",
|
displayname: "显示名字",
|
||||||
password: "密码",
|
password: "密码",
|
||||||
@@ -135,11 +142,9 @@ const zh: SynapseTranslationMessages = {
|
|||||||
helper: {
|
helper: {
|
||||||
deactivate: "您必须提供一串密码来激活账户。",
|
deactivate: "您必须提供一串密码来激活账户。",
|
||||||
erase: "将用户标记为根据 GDPR 的要求抹除了",
|
erase: "将用户标记为根据 GDPR 的要求抹除了",
|
||||||
erase_admin_error: "不允许删除自己的用户",
|
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
erase: "抹除用户信息",
|
erase: "抹除用户信息",
|
||||||
erase_avatar: "抹掉头像",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
rooms: {
|
rooms: {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { createRoot } from "react-dom/client";
|
|||||||
import App from "./App";
|
import App from "./App";
|
||||||
import { AppContext } from "./AppContext";
|
import { AppContext } from "./AppContext";
|
||||||
|
|
||||||
fetch("config.json")
|
fetch(`${import.meta.env.BASE_URL}/config.json`)
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(props =>
|
.then(props =>
|
||||||
createRoot(document.getElementById("root")).render(
|
createRoot(document.getElementById("root")).render(
|
||||||
|
|||||||
@@ -168,9 +168,7 @@ const LoginPage = () => {
|
|||||||
const [matrixVersions, setMatrixVersions] = useState("");
|
const [matrixVersions, setMatrixVersions] = useState("");
|
||||||
|
|
||||||
const handleUsernameChange = () => {
|
const handleUsernameChange = () => {
|
||||||
if (formData.base_url || allowSingleBaseUrl) {
|
if (formData.base_url || allowSingleBaseUrl) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
// check if username is a full qualified userId then set base_url accordingly
|
// check if username is a full qualified userId then set base_url accordingly
|
||||||
const domain = splitMxid(formData.username)?.domain;
|
const domain = splitMxid(formData.username)?.domain;
|
||||||
if (domain) {
|
if (domain) {
|
||||||
@@ -182,9 +180,6 @@ const LoginPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!formData.base_url) {
|
|
||||||
form.setValue("base_url", "");
|
|
||||||
}
|
|
||||||
if (formData.base_url === "" && allowMultipleBaseUrls) {
|
if (formData.base_url === "" && allowMultipleBaseUrls) {
|
||||||
form.setValue("base_url", restrictBaseUrl[0]);
|
form.setValue("base_url", restrictBaseUrl[0]);
|
||||||
}
|
}
|
||||||
@@ -222,7 +217,6 @@ const LoginPage = () => {
|
|||||||
disabled={loading || !supportPassAuth}
|
disabled={loading || !supportPassAuth}
|
||||||
onBlur={handleUsernameChange}
|
onBlur={handleUsernameChange}
|
||||||
resettable
|
resettable
|
||||||
fullWidth
|
|
||||||
validate={required()}
|
validate={required()}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -234,7 +228,6 @@ const LoginPage = () => {
|
|||||||
autoComplete="current-password"
|
autoComplete="current-password"
|
||||||
disabled={loading || !supportPassAuth}
|
disabled={loading || !supportPassAuth}
|
||||||
resettable
|
resettable
|
||||||
fullWidth
|
|
||||||
validate={required()}
|
validate={required()}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -247,7 +240,6 @@ const LoginPage = () => {
|
|||||||
disabled={loading}
|
disabled={loading}
|
||||||
readOnly={allowSingleBaseUrl}
|
readOnly={allowSingleBaseUrl}
|
||||||
resettable={allowAnyBaseUrl}
|
resettable={allowAnyBaseUrl}
|
||||||
fullWidth
|
|
||||||
validate={[required(), validateBaseUrl]}
|
validate={[required(), validateBaseUrl]}
|
||||||
>
|
>
|
||||||
{allowMultipleBaseUrls &&
|
{allowMultipleBaseUrls &&
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
import { get } from "lodash";
|
||||||
import { MouseEvent } from "react";
|
import { MouseEvent } from "react";
|
||||||
|
|
||||||
import AutorenewIcon from "@mui/icons-material/Autorenew";
|
import AutorenewIcon from "@mui/icons-material/Autorenew";
|
||||||
import DestinationsIcon from "@mui/icons-material/CloudQueue";
|
import DestinationsIcon from "@mui/icons-material/CloudQueue";
|
||||||
import FolderSharedIcon from "@mui/icons-material/FolderShared";
|
import FolderSharedIcon from "@mui/icons-material/FolderShared";
|
||||||
import ViewListIcon from "@mui/icons-material/ViewList";
|
import ViewListIcon from "@mui/icons-material/ViewList";
|
||||||
|
import { blue } from "@mui/material/colors";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Datagrid,
|
Datagrid,
|
||||||
@@ -31,17 +33,10 @@ import {
|
|||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
|
|
||||||
import { DATE_FORMAT } from "../components/date";
|
import { DATE_FORMAT } from "../components/date";
|
||||||
import { get } from "lodash";
|
import { lighten, useTheme } from '@mui/material';
|
||||||
|
|
||||||
const DestinationPagination = () => <Pagination rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />;
|
const DestinationPagination = () => <Pagination rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />;
|
||||||
|
|
||||||
const destinationRowSx = (record: RaRecord) => ({
|
|
||||||
backgroundColor: record.retry_last_ts > 0 ? "warning.light" : "primary.contrastText",
|
|
||||||
"& .MuiButtonBase-root": {
|
|
||||||
color: "primary.dark",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const destinationFilters = [<SearchInput source="destination" alwaysOn />];
|
const destinationFilters = [<SearchInput source="destination" alwaysOn />];
|
||||||
|
|
||||||
export const DestinationReconnectButton = () => {
|
export const DestinationReconnectButton = () => {
|
||||||
@@ -106,6 +101,16 @@ const RetryDateField = (props: DateFieldProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const DestinationList = (props: ListProps) => {
|
export const DestinationList = (props: ListProps) => {
|
||||||
|
const { palette: { error, mode }, } = useTheme();
|
||||||
|
const destinationRowSx = (record: RaRecord) => ({
|
||||||
|
backgroundColor: record.retry_last_ts > 0 ? lighten(error[mode], 0.5) : undefined,
|
||||||
|
"& > td": mode === 'dark' ? {
|
||||||
|
color: record.retry_last_ts > 0 ? "black" : "white",
|
||||||
|
"& > button": {
|
||||||
|
color: blue[700],
|
||||||
|
},
|
||||||
|
} : undefined,
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<List
|
<List
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
useRefresh,
|
useRefresh,
|
||||||
useUnselectAll,
|
useUnselectAll,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import { useMutation } from "react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
|
|
||||||
import AvatarField from "../components/AvatarField";
|
import AvatarField from "../components/AvatarField";
|
||||||
|
|
||||||
@@ -70,13 +70,12 @@ export const RoomDirectoryBulkPublishButton = (props: ButtonProps) => {
|
|||||||
const refresh = useRefresh();
|
const refresh = useRefresh();
|
||||||
const unselectAllRooms = useUnselectAll("rooms");
|
const unselectAllRooms = useUnselectAll("rooms");
|
||||||
const dataProvider = useDataProvider();
|
const dataProvider = useDataProvider();
|
||||||
const { mutate, isLoading } = useMutation(
|
const { mutate, isPending } = useMutation({
|
||||||
() =>
|
mutationFn: () =>
|
||||||
dataProvider.createMany("room_directory", {
|
dataProvider.createMany("room_directory", {
|
||||||
ids: selectedIds,
|
ids: selectedIds,
|
||||||
data: {},
|
data: {},
|
||||||
}),
|
}),
|
||||||
{
|
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notify("resources.room_directory.action.send_success");
|
notify("resources.room_directory.action.send_success");
|
||||||
unselectAllRooms();
|
unselectAllRooms();
|
||||||
@@ -86,11 +85,10 @@ export const RoomDirectoryBulkPublishButton = (props: ButtonProps) => {
|
|||||||
notify("resources.room_directory.action.send_failure", {
|
notify("resources.room_directory.action.send_failure", {
|
||||||
type: "error",
|
type: "error",
|
||||||
}),
|
}),
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button {...props} label="resources.room_directory.action.create" onClick={mutate} disabled={isLoading}>
|
<Button {...props} label="resources.room_directory.action.create" onClick={mutate} disabled={isPending}>
|
||||||
<RoomDirectoryIcon />
|
<RoomDirectoryIcon />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
@@ -102,6 +100,10 @@ export const RoomDirectoryPublishButton = (props: ButtonProps) => {
|
|||||||
const refresh = useRefresh();
|
const refresh = useRefresh();
|
||||||
const [create, { isLoading }] = useCreate();
|
const [create, { isLoading }] = useCreate();
|
||||||
|
|
||||||
|
if (!record) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const handleSend = () => {
|
const handleSend = () => {
|
||||||
create(
|
create(
|
||||||
"room_directory",
|
"room_directory",
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import {
|
|||||||
TopToolbar,
|
TopToolbar,
|
||||||
useRecordContext,
|
useRecordContext,
|
||||||
useTranslate,
|
useTranslate,
|
||||||
useListContext,
|
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -46,7 +45,6 @@ import {
|
|||||||
RoomDirectoryPublishButton,
|
RoomDirectoryPublishButton,
|
||||||
} from "./room_directory";
|
} from "./room_directory";
|
||||||
import { DATE_FORMAT } from "../components/date";
|
import { DATE_FORMAT } from "../components/date";
|
||||||
import DeleteRoomButton from "../components/DeleteRoomButton";
|
|
||||||
|
|
||||||
const RoomPagination = () => <Pagination rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />;
|
const RoomPagination = () => <Pagination rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />;
|
||||||
|
|
||||||
@@ -72,8 +70,8 @@ const RoomShowActions = () => {
|
|||||||
return (
|
return (
|
||||||
<TopToolbar>
|
<TopToolbar>
|
||||||
{publishButton}
|
{publishButton}
|
||||||
<DeleteRoomButton
|
<DeleteButton
|
||||||
selectedIds={[record.id]}
|
mutationMode="pessimistic"
|
||||||
confirmTitle="resources.rooms.action.erase.title"
|
confirmTitle="resources.rooms.action.erase.title"
|
||||||
confirmContent="resources.rooms.action.erase.content"
|
confirmContent="resources.rooms.action.erase.content"
|
||||||
/>
|
/>
|
||||||
@@ -209,20 +207,17 @@ export const RoomShow = (props: ShowProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const RoomBulkActionButtons = () => {
|
const RoomBulkActionButtons = () => (
|
||||||
const record = useListContext();
|
|
||||||
return (
|
|
||||||
<>
|
<>
|
||||||
<RoomDirectoryBulkPublishButton />
|
<RoomDirectoryBulkPublishButton />
|
||||||
<RoomDirectoryBulkUnpublishButton />
|
<RoomDirectoryBulkUnpublishButton />
|
||||||
<DeleteRoomButton
|
<BulkDeleteButton
|
||||||
selectedIds={record.selectedIds}
|
|
||||||
confirmTitle="resources.rooms.action.erase.title"
|
confirmTitle="resources.rooms.action.erase.title"
|
||||||
confirmContent="resources.rooms.action.erase.content"
|
confirmContent="resources.rooms.action.erase.content"
|
||||||
|
mutationMode="pessimistic"
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
const roomFilters = [<SearchInput source="search_term" alwaysOn />];
|
const roomFilters = [<SearchInput source="search_term" alwaysOn />];
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import PermMediaIcon from "@mui/icons-material/PermMedia";
|
|||||||
import PersonPinIcon from "@mui/icons-material/PersonPin";
|
import PersonPinIcon from "@mui/icons-material/PersonPin";
|
||||||
import SettingsInputComponentIcon from "@mui/icons-material/SettingsInputComponent";
|
import SettingsInputComponentIcon from "@mui/icons-material/SettingsInputComponent";
|
||||||
import ViewListIcon from "@mui/icons-material/ViewList";
|
import ViewListIcon from "@mui/icons-material/ViewList";
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { Alert, ownerDocument } from "@mui/material";
|
|
||||||
import {
|
import {
|
||||||
ArrayInput,
|
ArrayInput,
|
||||||
ArrayField,
|
ArrayField,
|
||||||
@@ -44,19 +42,12 @@ import {
|
|||||||
useRecordContext,
|
useRecordContext,
|
||||||
useTranslate,
|
useTranslate,
|
||||||
Pagination,
|
Pagination,
|
||||||
SaveButton,
|
|
||||||
CreateButton,
|
CreateButton,
|
||||||
ExportButton,
|
ExportButton,
|
||||||
TopToolbar,
|
TopToolbar,
|
||||||
Toolbar,
|
|
||||||
NumberField,
|
NumberField,
|
||||||
useListContext,
|
useListContext,
|
||||||
useNotify,
|
|
||||||
ToolbarClasses,
|
|
||||||
Identifier,
|
Identifier,
|
||||||
RaRecord,
|
|
||||||
ImageInput,
|
|
||||||
ImageField,
|
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
@@ -103,50 +94,16 @@ const userFilters = [
|
|||||||
<BooleanInput label="resources.users.fields.show_locked" source="locked" alwaysOn />,
|
<BooleanInput label="resources.users.fields.show_locked" source="locked" alwaysOn />,
|
||||||
];
|
];
|
||||||
|
|
||||||
const UserPreventSelfDelete: React.FC<{ children: React.ReactNode; ownUserIsSelected: boolean }> = props => {
|
const UserBulkActionButtons = () => (
|
||||||
const ownUserIsSelected = props.ownUserIsSelected;
|
|
||||||
const notify = useNotify();
|
|
||||||
const translate = useTranslate();
|
|
||||||
|
|
||||||
const handleDeleteClick = (ev: React.MouseEvent<HTMLDivElement>) => {
|
|
||||||
if (ownUserIsSelected) {
|
|
||||||
notify(<Alert severity="error">{translate("resources.users.helper.erase_admin_error")}</Alert>);
|
|
||||||
ev.stopPropagation();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return <div onClickCapture={handleDeleteClick}>{props.children}</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const UserBulkActionButtons = () => {
|
|
||||||
const record = useListContext();
|
|
||||||
const [ownUserIsSelected, setOwnUserIsSelected] = useState(false);
|
|
||||||
const selectedIds = record.selectedIds;
|
|
||||||
const ownUserId = localStorage.getItem("user_id");
|
|
||||||
const notify = useNotify();
|
|
||||||
const translate = useTranslate();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setOwnUserIsSelected(selectedIds.includes(ownUserId));
|
|
||||||
}, [selectedIds]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
<>
|
||||||
<ServerNoticeBulkButton />
|
<ServerNoticeBulkButton />
|
||||||
<UserPreventSelfDelete ownUserIsSelected={ownUserIsSelected}>
|
|
||||||
<BulkDeleteButton
|
<BulkDeleteButton
|
||||||
label="resources.users.action.erase"
|
label="resources.users.action.erase"
|
||||||
confirmTitle="resources.users.helper.erase"
|
confirmTitle="resources.users.helper.erase"
|
||||||
mutationMode="pessimistic"
|
mutationMode="pessimistic"
|
||||||
/>
|
/>
|
||||||
</UserPreventSelfDelete>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
const usersRowClick = (id: Identifier, resource: string, record: RaRecord): string => {
|
|
||||||
return `/users/${id}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const UserList = (props: ListProps) => (
|
export const UserList = (props: ListProps) => (
|
||||||
<List
|
<List
|
||||||
@@ -157,7 +114,10 @@ export const UserList = (props: ListProps) => (
|
|||||||
actions={<UserListActions />}
|
actions={<UserListActions />}
|
||||||
pagination={<UserPagination />}
|
pagination={<UserPagination />}
|
||||||
>
|
>
|
||||||
<Datagrid rowClick={usersRowClick} bulkActionButtons={<UserBulkActionButtons />}>
|
<Datagrid
|
||||||
|
rowClick={(id: Identifier, resource: string) => `/${resource}/${id}`}
|
||||||
|
bulkActionButtons={<UserBulkActionButtons />}
|
||||||
|
>
|
||||||
<AvatarField source="avatar_src" sx={{ height: "40px", width: "40px" }} sortBy="avatar_url" />
|
<AvatarField source="avatar_src" sx={{ height: "40px", width: "40px" }} sortBy="avatar_url" />
|
||||||
<TextField source="id" sortBy="name" />
|
<TextField source="id" sortBy="name" />
|
||||||
<TextField source="displayname" />
|
<TextField source="displayname" />
|
||||||
@@ -182,16 +142,10 @@ const validateAddress = [required(), maxLength(255)];
|
|||||||
const UserEditActions = () => {
|
const UserEditActions = () => {
|
||||||
const record = useRecordContext();
|
const record = useRecordContext();
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
const ownUserId = localStorage.getItem("user_id");
|
|
||||||
let ownUserIsSelected = false;
|
|
||||||
if (record && record.id) {
|
|
||||||
ownUserIsSelected = record.id === ownUserId;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TopToolbar>
|
<TopToolbar>
|
||||||
{!record?.deactivated && <ServerNoticeButton />}
|
{!record?.deactivated && <ServerNoticeButton />}
|
||||||
<UserPreventSelfDelete ownUserIsSelected={ownUserIsSelected}>
|
|
||||||
<DeleteButton
|
<DeleteButton
|
||||||
label="resources.users.action.erase"
|
label="resources.users.action.erase"
|
||||||
confirmTitle={translate("resources.users.helper.erase", {
|
confirmTitle={translate("resources.users.helper.erase", {
|
||||||
@@ -199,7 +153,6 @@ const UserEditActions = () => {
|
|||||||
})}
|
})}
|
||||||
mutationMode="pessimistic"
|
mutationMode="pessimistic"
|
||||||
/>
|
/>
|
||||||
</UserPreventSelfDelete>
|
|
||||||
</TopToolbar>
|
</TopToolbar>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -207,8 +160,8 @@ const UserEditActions = () => {
|
|||||||
export const UserCreate = (props: CreateProps) => (
|
export const UserCreate = (props: CreateProps) => (
|
||||||
<Create
|
<Create
|
||||||
{...props}
|
{...props}
|
||||||
redirect={(resource, id, data) => {
|
redirect={(resource: string | undefined, id: Identifier | undefined) => {
|
||||||
return `users/${id}`;
|
return `${resource}/${id}`;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SimpleForm>
|
<SimpleForm>
|
||||||
@@ -241,67 +194,25 @@ const UserTitle = () => {
|
|||||||
{translate("resources.users.name", {
|
{translate("resources.users.name", {
|
||||||
smart_count: 1,
|
smart_count: 1,
|
||||||
})}{" "}
|
})}{" "}
|
||||||
{record ? (record.displayname ? `"${record.displayname}"` : `"${record.name}"`) : ""}
|
{record ? `"${record.displayname}"` : ""}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const UserEditToolbar = () => {
|
|
||||||
const record = useRecordContext();
|
|
||||||
const ownUserId = localStorage.getItem("user_id");
|
|
||||||
let ownUserIsSelected = false;
|
|
||||||
if (record && record.id) {
|
|
||||||
ownUserIsSelected = record.id === ownUserId;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className={ToolbarClasses.defaultToolbar}>
|
|
||||||
<Toolbar sx={{ justifyContent: "space-between" }}>
|
|
||||||
<SaveButton />
|
|
||||||
<UserPreventSelfDelete ownUserIsSelected={ownUserIsSelected}>
|
|
||||||
<DeleteButton />
|
|
||||||
</UserPreventSelfDelete>
|
|
||||||
</Toolbar>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const UserBooleanInput = props => {
|
|
||||||
const record = useRecordContext();
|
|
||||||
const ownUserId = localStorage.getItem("user_id");
|
|
||||||
let ownUserIsSelected = false;
|
|
||||||
if (record && record.id === ownUserId) {
|
|
||||||
ownUserIsSelected = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<UserPreventSelfDelete ownUserIsSelected={ownUserIsSelected}>
|
|
||||||
<BooleanInput {...props} disabled={ownUserIsSelected} />
|
|
||||||
</UserPreventSelfDelete>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const UserEdit = (props: EditProps) => {
|
export const UserEdit = (props: EditProps) => {
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Edit {...props} title={<UserTitle />} actions={<UserEditActions />}>
|
<Edit {...props} title={<UserTitle />} actions={<UserEditActions />}>
|
||||||
<TabbedForm toolbar={<UserEditToolbar />}>
|
<TabbedForm>
|
||||||
<FormTab label={translate("resources.users.name", { smart_count: 1 })} icon={<PersonPinIcon />}>
|
<FormTab label={translate("resources.users.name", { smart_count: 1 })} icon={<PersonPinIcon />}>
|
||||||
<AvatarField source="avatar_src" sortable={false} sx={{ height: "120px", width: "120px" }} />
|
<AvatarField source="avatar_src" sortable={false} sx={{ height: "120px", width: "120px", float: "right" }} />
|
||||||
<BooleanInput source="avatar_erase" label="resources.users.action.erase_avatar" />
|
|
||||||
<ImageInput source="avatar_file" label="resources.users.fields.avatar" accept="image/*">
|
|
||||||
<ImageField source="src" title="Avatar" />
|
|
||||||
</ImageInput>
|
|
||||||
<TextInput source="id" disabled />
|
<TextInput source="id" disabled />
|
||||||
<TextInput source="displayname" />
|
<TextInput source="displayname" />
|
||||||
<PasswordInput source="password" autoComplete="new-password" helperText="resources.users.helper.password" />
|
<PasswordInput source="password" autoComplete="new-password" helperText="resources.users.helper.password" />
|
||||||
<SelectInput source="user_type" choices={choices_type} translateChoice={false} resettable />
|
<SelectInput source="user_type" choices={choices_type} translateChoice={false} resettable />
|
||||||
<BooleanInput source="admin" />
|
<BooleanInput source="admin" />
|
||||||
<BooleanInput source="locked" />
|
<BooleanInput source="locked" />
|
||||||
<UserBooleanInput source="deactivated" helperText="resources.users.helper.deactivate" />
|
<BooleanInput source="deactivated" helperText="resources.users.helper.deactivate" />
|
||||||
<BooleanInput source="erased" disabled />
|
<BooleanInput source="erased" disabled />
|
||||||
<DateField source="creation_ts_ms" showTime options={DATE_FORMAT} />
|
<DateField source="creation_ts_ms" showTime options={DATE_FORMAT} />
|
||||||
<TextField source="consent_version" />
|
<TextField source="consent_version" />
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import fetchMock from "jest-fetch-mock";
|
|||||||
|
|
||||||
import authProvider from "./authProvider";
|
import authProvider from "./authProvider";
|
||||||
import storage from "../storage";
|
import storage from "../storage";
|
||||||
import { HttpError } from "ra-core";
|
|
||||||
|
|
||||||
fetchMock.enableMocks();
|
fetchMock.enableMocks();
|
||||||
|
|
||||||
@@ -31,7 +30,17 @@ describe("authProvider", () => {
|
|||||||
|
|
||||||
expect(ret).toBe(undefined);
|
expect(ret).toBe(undefined);
|
||||||
expect(fetch).toBeCalledWith("http://example.com/_matrix/client/r0/login", {
|
expect(fetch).toBeCalledWith("http://example.com/_matrix/client/r0/login", {
|
||||||
body: '{"device_id":null,"initial_device_display_name":"Synapse Admin","type":"m.login.password","identifier":{"type":"m.id.user","user":"@user:example.com"},"password":"secret"}',
|
body: JSON.stringify({
|
||||||
|
device_id: null,
|
||||||
|
initial_device_display_name: "Synapse Admin",
|
||||||
|
type: "m.login.password",
|
||||||
|
user: "@user:example.com",
|
||||||
|
password: "secret",
|
||||||
|
identifier: {
|
||||||
|
type: "m.id.user",
|
||||||
|
user: "@user:example.com",
|
||||||
|
}
|
||||||
|
}),
|
||||||
headers: new Headers({
|
headers: new Headers({
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -101,11 +110,11 @@ describe("authProvider", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should reject if error.status is 401", async () => {
|
it("should reject if error.status is 401", async () => {
|
||||||
await expect(authProvider.checkError(new HttpError("test-error", 401, {errcode: "test-errcode", error: "test-error"}))).rejects.toBeDefined();
|
await expect(authProvider.checkError({ status: 401 })).rejects.toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should reject if error.status is 403", async () => {
|
it("should reject if error.status is 403", async () => {
|
||||||
await expect(authProvider.checkError(new HttpError("test-error", 403, {errcode: "test-errcode", error: "test-error"}))).rejects.toBeDefined();
|
await expect(authProvider.checkError({ status: 403 })).rejects.toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { AuthProvider, HttpError, Options, fetchUtils, useTranslate } from "react-admin";
|
import { AuthProvider, Options, fetchUtils } from "react-admin";
|
||||||
|
|
||||||
import storage from "../storage";
|
import storage from "../storage";
|
||||||
import { MatrixError, displayError } from "../components/error";
|
|
||||||
|
|
||||||
const authProvider: AuthProvider = {
|
const authProvider: AuthProvider = {
|
||||||
// called when the user attempts to log in
|
// called when the user attempts to log in
|
||||||
@@ -32,11 +31,12 @@ const authProvider: AuthProvider = {
|
|||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
type: "m.login.password",
|
type: "m.login.password",
|
||||||
|
user: username,
|
||||||
|
password: password,
|
||||||
identifier: {
|
identifier: {
|
||||||
type: "m.id.user",
|
type: "m.id.user",
|
||||||
user: username,
|
user: username,
|
||||||
},
|
},
|
||||||
password: password,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@@ -45,36 +45,13 @@ const authProvider: AuthProvider = {
|
|||||||
// use the base_url from login instead of the well_known entry from the
|
// use the base_url from login instead of the well_known entry from the
|
||||||
// server, since the admin might want to access the admin API via some
|
// server, since the admin might want to access the admin API via some
|
||||||
// private address
|
// private address
|
||||||
if (!base_url) {
|
|
||||||
// there is some kind of bug with base_url being present in the form, but not submitted
|
|
||||||
// ref: https://github.com/etkecc/synapse-admin/issues/14
|
|
||||||
storage.removeItem("base_url")
|
|
||||||
throw new Error("Homeserver URL is required.");
|
|
||||||
}
|
|
||||||
base_url = base_url.replace(/\/+$/g, "");
|
base_url = base_url.replace(/\/+$/g, "");
|
||||||
storage.setItem("base_url", base_url);
|
storage.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";
|
||||||
|
|
||||||
let response;
|
const { json } = await fetchUtils.fetchJson(login_api_url, options);
|
||||||
try {
|
|
||||||
response = await fetchUtils.fetchJson(login_api_url, options);
|
|
||||||
} catch(err) {
|
|
||||||
const error = err as HttpError;
|
|
||||||
const errorStatus = error.status;
|
|
||||||
const errorBody = error.body as MatrixError;
|
|
||||||
const errMsg = !!errorBody?.errcode ? displayError(errorBody.errcode, errorStatus, errorBody.error) : displayError("M_INVALID", errorStatus, error.message);
|
|
||||||
|
|
||||||
return Promise.reject(
|
|
||||||
new HttpError(
|
|
||||||
errMsg,
|
|
||||||
errorStatus,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const json = response.json;
|
|
||||||
storage.setItem("home_server", json.home_server);
|
storage.setItem("home_server", json.home_server);
|
||||||
storage.setItem("user_id", json.user_id);
|
storage.setItem("user_id", json.user_id);
|
||||||
storage.setItem("access_token", json.access_token);
|
storage.setItem("access_token", json.access_token);
|
||||||
@@ -101,12 +78,10 @@ const authProvider: AuthProvider = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
// called when the API returns an error
|
// called when the API returns an error
|
||||||
checkError: (err: HttpError) => {
|
checkError: ({ status }: { status: number }) => {
|
||||||
const errorBody = err.body as MatrixError;
|
console.log("checkError " + status);
|
||||||
const status = err.status;
|
|
||||||
|
|
||||||
if (status === 401 || status === 403) {
|
if (status === 401 || status === 403) {
|
||||||
return Promise.reject({message: displayError(errorBody.errcode, status, errorBody.error)});
|
return Promise.reject();
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ describe("dataProvider", () => {
|
|||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
users: [
|
users: [
|
||||||
{
|
{
|
||||||
name: "@user_id1:provider",
|
name: "user_id1",
|
||||||
password_hash: "password_hash1",
|
password_hash: "password_hash1",
|
||||||
is_guest: 0,
|
is_guest: 0,
|
||||||
admin: 0,
|
admin: 0,
|
||||||
@@ -27,7 +27,7 @@ describe("dataProvider", () => {
|
|||||||
displayname: "User One",
|
displayname: "User One",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "@user_id2:provider",
|
name: "user_id2",
|
||||||
password_hash: "password_hash2",
|
password_hash: "password_hash2",
|
||||||
is_guest: 0,
|
is_guest: 0,
|
||||||
admin: 1,
|
admin: 1,
|
||||||
@@ -47,7 +47,7 @@ describe("dataProvider", () => {
|
|||||||
filter: { author_id: 12 },
|
filter: { author_id: 12 },
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(users.data[0].id).toEqual("@user_id1:provider");
|
expect(users.data[0].id).toEqual("user_id1");
|
||||||
expect(users.total).toEqual(200);
|
expect(users.total).toEqual(200);
|
||||||
expect(fetch).toHaveBeenCalledTimes(1);
|
expect(fetch).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
@@ -55,7 +55,7 @@ describe("dataProvider", () => {
|
|||||||
it("fetches one user", async () => {
|
it("fetches one user", async () => {
|
||||||
fetchMock.mockResponseOnce(
|
fetchMock.mockResponseOnce(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
name: "@user_id1:provider",
|
name: "user_id1",
|
||||||
password: "user_password",
|
password: "user_password",
|
||||||
displayname: "User",
|
displayname: "User",
|
||||||
threepids: [
|
threepids: [
|
||||||
@@ -74,9 +74,9 @@ describe("dataProvider", () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const user = await dataProvider.getOne("users", { id: "@user_id1:provider" });
|
const user = await dataProvider.getOne("users", { id: "user_id1" });
|
||||||
|
|
||||||
expect(user.data.id).toEqual("@user_id1:provider");
|
expect(user.data.id).toEqual("user_id1");
|
||||||
expect(user.data.displayname).toEqual("User");
|
expect(user.data.displayname).toEqual("User");
|
||||||
expect(fetch).toHaveBeenCalledTimes(1);
|
expect(fetch).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,44 +1,27 @@
|
|||||||
import { stringify } from "query-string";
|
import { stringify } from "query-string";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DataProvider,
|
DataProvider,
|
||||||
DeleteParams,
|
DeleteParams,
|
||||||
HttpError,
|
|
||||||
Identifier,
|
Identifier,
|
||||||
Options,
|
Options,
|
||||||
|
PaginationPayload,
|
||||||
RaRecord,
|
RaRecord,
|
||||||
UpdateParams,
|
SortPayload,
|
||||||
fetchUtils,
|
fetchUtils
|
||||||
withLifecycleCallbacks,
|
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
|
|
||||||
import storage from "../storage";
|
import storage from "../storage";
|
||||||
import { returnMXID } from "./synapse";
|
|
||||||
import { MatrixError, displayError } from "../components/error";
|
|
||||||
|
|
||||||
// Adds the access token to all requests
|
// Adds the access token to all requests
|
||||||
const jsonClient = async (url: string, options: Options = {}) => {
|
const jsonClient = (url: string, options: Options = {}) => {
|
||||||
const token = storage.getItem("access_token");
|
const token = storage.getItem("access_token");
|
||||||
console.log("httpClient " + url);
|
console.log("httpClient " + url);
|
||||||
if (token !== null) {
|
if (token != null) {
|
||||||
options.user = {
|
options.user = {
|
||||||
authenticated: true,
|
authenticated: true,
|
||||||
token: `Bearer ${token}`,
|
token: `Bearer ${token}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
try {
|
return fetchUtils.fetchJson(url, options);
|
||||||
const response = await fetchUtils.fetchJson(url, options);
|
|
||||||
return response;
|
|
||||||
} catch (err: any) {
|
|
||||||
const error = err as HttpError;
|
|
||||||
const errorStatus = error.status;
|
|
||||||
const errorBody = error.body as MatrixError;
|
|
||||||
const errMsg = !!errorBody?.errcode
|
|
||||||
? displayError(errorBody.errcode, errorStatus, errorBody.error)
|
|
||||||
: displayError("M_INVALID", errorStatus, error.message);
|
|
||||||
|
|
||||||
return Promise.reject(new HttpError(errMsg, errorStatus, errorBody));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const mxcUrlToHttp = (mxcUrl: string) => {
|
const mxcUrlToHttp = (mxcUrl: string) => {
|
||||||
@@ -238,19 +221,8 @@ export interface DeleteMediaResult {
|
|||||||
total: number;
|
total: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UploadMediaParams {
|
|
||||||
file: File;
|
|
||||||
filename: string;
|
|
||||||
content_type: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UploadMediaResult {
|
|
||||||
content_uri: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SynapseDataProvider extends DataProvider {
|
export interface SynapseDataProvider extends DataProvider {
|
||||||
deleteMedia: (params: DeleteMediaParams) => Promise<DeleteMediaResult>;
|
deleteMedia: (params: DeleteMediaParams) => Promise<DeleteMediaResult>;
|
||||||
uploadMedia: (params: UploadMediaParams) => Promise<UploadMediaResult>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const resourceMap = {
|
const resourceMap = {
|
||||||
@@ -258,7 +230,7 @@ const resourceMap = {
|
|||||||
path: "/_synapse/admin/v2/users",
|
path: "/_synapse/admin/v2/users",
|
||||||
map: (u: User) => ({
|
map: (u: User) => ({
|
||||||
...u,
|
...u,
|
||||||
id: returnMXID(u.name),
|
id: u.name,
|
||||||
avatar_src: u.avatar_url ? mxcUrlToHttp(u.avatar_url) : undefined,
|
avatar_src: u.avatar_url ? mxcUrlToHttp(u.avatar_url) : undefined,
|
||||||
is_guest: !!u.is_guest,
|
is_guest: !!u.is_guest,
|
||||||
admin: !!u.admin,
|
admin: !!u.admin,
|
||||||
@@ -269,12 +241,12 @@ const resourceMap = {
|
|||||||
data: "users",
|
data: "users",
|
||||||
total: json => json.total,
|
total: json => json.total,
|
||||||
create: (data: RaRecord) => ({
|
create: (data: RaRecord) => ({
|
||||||
endpoint: `/_synapse/admin/v2/users/${encodeURIComponent(returnMXID(data.id))}`,
|
endpoint: `/_synapse/admin/v2/users/@${encodeURIComponent(data.id)}:${storage.getItem("home_server")}`,
|
||||||
body: data,
|
body: data,
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
}),
|
}),
|
||||||
delete: (params: DeleteParams) => ({
|
delete: (params: DeleteParams) => ({
|
||||||
endpoint: `/_synapse/admin/v1/deactivate/${encodeURIComponent(returnMXID(params.id))}`,
|
endpoint: `/_synapse/admin/v1/deactivate/${encodeURIComponent(params.id)}`,
|
||||||
body: { erase: true },
|
body: { erase: true },
|
||||||
method: "POST",
|
method: "POST",
|
||||||
}),
|
}),
|
||||||
@@ -294,7 +266,7 @@ const resourceMap = {
|
|||||||
total: json => json.total_rooms,
|
total: json => json.total_rooms,
|
||||||
delete: (params: DeleteParams) => ({
|
delete: (params: DeleteParams) => ({
|
||||||
endpoint: `/_synapse/admin/v2/rooms/${params.id}`,
|
endpoint: `/_synapse/admin/v2/rooms/${params.id}`,
|
||||||
body: { block: params.meta?.block ?? false },
|
body: { block: false },
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
reports: {
|
reports: {
|
||||||
@@ -373,7 +345,7 @@ const resourceMap = {
|
|||||||
id: um.media_id,
|
id: um.media_id,
|
||||||
}),
|
}),
|
||||||
reference: (id: Identifier) => ({
|
reference: (id: Identifier) => ({
|
||||||
endpoint: `/_synapse/admin/v1/users/${encodeURIComponent(returnMXID(id))}/media`,
|
endpoint: `/_synapse/admin/v1/users/${encodeURIComponent(id)}/media`,
|
||||||
}),
|
}),
|
||||||
data: "media",
|
data: "media",
|
||||||
total: json => json.total,
|
total: json => json.total,
|
||||||
@@ -408,7 +380,7 @@ const resourceMap = {
|
|||||||
create: (data: RaServerNotice) => ({
|
create: (data: RaServerNotice) => ({
|
||||||
endpoint: "/_synapse/admin/v1/send_server_notice",
|
endpoint: "/_synapse/admin/v1/send_server_notice",
|
||||||
body: {
|
body: {
|
||||||
user_id: returnMXID(data.id),
|
user_id: data.id,
|
||||||
content: {
|
content: {
|
||||||
msgtype: "m.text",
|
msgtype: "m.text",
|
||||||
body: data.body,
|
body: data.body,
|
||||||
@@ -421,7 +393,7 @@ const resourceMap = {
|
|||||||
path: "/_synapse/admin/v1/statistics/users/media",
|
path: "/_synapse/admin/v1/statistics/users/media",
|
||||||
map: (usms: UserMediaStatistic) => ({
|
map: (usms: UserMediaStatistic) => ({
|
||||||
...usms,
|
...usms,
|
||||||
id: returnMXID(usms.user_id),
|
id: usms.user_id,
|
||||||
}),
|
}),
|
||||||
data: "users",
|
data: "users",
|
||||||
total: json => json.total,
|
total: json => json.total,
|
||||||
@@ -523,12 +495,12 @@ function getSearchOrder(order: "ASC" | "DESC") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseDataProvider: SynapseDataProvider = {
|
const dataProvider: SynapseDataProvider = {
|
||||||
getList: async (resource, params) => {
|
getList: async (resource, params) => {
|
||||||
console.log("getList " + resource);
|
console.log("getList " + resource);
|
||||||
const { user_id, name, guests, deactivated, locked, search_term, destination, valid } = params.filter;
|
const { user_id, name, guests, deactivated, locked, search_term, destination, valid } = params.filter;
|
||||||
const { page, perPage } = params.pagination;
|
const { page, perPage } = params.pagination as PaginationPayload;
|
||||||
const { field, order } = params.sort;
|
const { field, order } = params.sort as SortPayload;
|
||||||
const from = (page - 1) * perPage;
|
const from = (page - 1) * perPage;
|
||||||
const query = {
|
const query = {
|
||||||
from: from,
|
from: from,
|
||||||
@@ -765,46 +737,6 @@ const baseDataProvider: SynapseDataProvider = {
|
|||||||
const { json } = await jsonClient(endpoint_url, { method: "POST" });
|
const { json } = await jsonClient(endpoint_url, { method: "POST" });
|
||||||
return json as DeleteMediaResult;
|
return json as DeleteMediaResult;
|
||||||
},
|
},
|
||||||
|
|
||||||
uploadMedia: async ({ file, filename, content_type }: UploadMediaParams) => {
|
|
||||||
const base_url = storage.getItem("base_url");
|
|
||||||
const uploadMediaURL = `${base_url}/_matrix/media/v3/upload`;
|
|
||||||
|
|
||||||
const { json } = await jsonClient(`${uploadMediaURL}?filename=${filename}`, {
|
|
||||||
method: "POST",
|
|
||||||
body: file,
|
|
||||||
headers: new Headers({
|
|
||||||
Accept: "application/json",
|
|
||||||
"Content-Type": content_type,
|
|
||||||
}) as Headers,
|
|
||||||
});
|
|
||||||
return json as UploadMediaResult;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const dataProvider = withLifecycleCallbacks(baseDataProvider, [
|
|
||||||
{
|
|
||||||
resource: "users",
|
|
||||||
beforeUpdate: async (params: UpdateParams<any>, dataProvider: DataProvider) => {
|
|
||||||
const avatarFile = params.data.avatar_file?.rawFile;
|
|
||||||
const avatarErase = params.data.avatar_erase;
|
|
||||||
|
|
||||||
if (avatarErase) {
|
|
||||||
params.data.avatar_url = "";
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (avatarFile instanceof File) {
|
|
||||||
const reponse = await dataProvider.uploadMedia({
|
|
||||||
file: avatarFile,
|
|
||||||
filename: params.data.avatar_file.title,
|
|
||||||
content_type: params.data.avatar_file.rawFile.type,
|
|
||||||
});
|
|
||||||
params.data.avatar_url = reponse.content_uri;
|
|
||||||
}
|
|
||||||
return params;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
export default dataProvider;
|
export default dataProvider;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Identifier, fetchUtils } from "react-admin";
|
import { fetchUtils } from "react-admin";
|
||||||
|
|
||||||
import storage from "../storage";
|
import storage from "../storage";
|
||||||
|
|
||||||
@@ -72,26 +72,6 @@ export function generateRandomMxId(): string {
|
|||||||
return `@${localpart}:${homeserver}`;
|
return `@${localpart}:${homeserver}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the full MXID from an arbitrary input
|
|
||||||
* @param input the input string
|
|
||||||
* @returns full MXID as string
|
|
||||||
*/
|
|
||||||
export function returnMXID(input: string | Identifier): string {
|
|
||||||
const homeserver = storage.getItem("home_server");
|
|
||||||
|
|
||||||
// Check if the input already looks like a valid MXID (i.e., starts with "@" and contains ":")
|
|
||||||
const mxidPattern = /^@[^@:]+:[^@:]+$/;
|
|
||||||
if (typeof input === 'string' && mxidPattern.test(input)) {
|
|
||||||
return input; // Already a valid MXID
|
|
||||||
}
|
|
||||||
|
|
||||||
// If input is not a valid MXID, assume it's a localpart and construct the MXID
|
|
||||||
const localpart = typeof input === 'string' && input.startsWith('@') ? input.slice(1) : input;
|
|
||||||
return `@${localpart}:${homeserver}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a random user password
|
* Generate a random user password
|
||||||
* @returns a new random password as string
|
* @returns a new random password as string
|
||||||
|
|||||||
191
testdata/synapse/homeserver.yaml
vendored
191
testdata/synapse/homeserver.yaml
vendored
@@ -1,191 +0,0 @@
|
|||||||
account_threepid_delegates:
|
|
||||||
msisdn: ''
|
|
||||||
alias_creation_rules:
|
|
||||||
- action: allow
|
|
||||||
alias: '*'
|
|
||||||
room_id: '*'
|
|
||||||
user_id: '*'
|
|
||||||
allow_guest_access: false
|
|
||||||
allow_public_rooms_over_federation: true
|
|
||||||
allow_public_rooms_without_auth: true
|
|
||||||
app_service_config_files: []
|
|
||||||
autocreate_auto_join_rooms: true
|
|
||||||
background_updates: null
|
|
||||||
caches:
|
|
||||||
global_factor: 0.5
|
|
||||||
per_cache_factors: null
|
|
||||||
cas_config: null
|
|
||||||
database:
|
|
||||||
args:
|
|
||||||
cp_max: 10
|
|
||||||
cp_min: 5
|
|
||||||
database: synapse
|
|
||||||
host: postgres
|
|
||||||
password: synapse
|
|
||||||
port: 5432
|
|
||||||
user: synapse
|
|
||||||
name: psycopg2
|
|
||||||
txn_limit: 0
|
|
||||||
default_room_version: '10'
|
|
||||||
disable_msisdn_registration: true
|
|
||||||
email:
|
|
||||||
enable_media_repo: true
|
|
||||||
enable_metrics: false
|
|
||||||
enable_registration: false
|
|
||||||
enable_registration_captcha: false
|
|
||||||
enable_registration_without_verification: false
|
|
||||||
enable_room_list_search: true
|
|
||||||
encryption_enabled_by_default_for_room_type: 'off'
|
|
||||||
event_cache_size: 100K
|
|
||||||
federation_rr_transactions_per_room_per_second: 50
|
|
||||||
form_secret: sLKKoFMsQUZgLAW0vU1PQQ8ca1POGMDheurGtKW0uJ20iGqtxR9O7JQ6Knvs44Wi
|
|
||||||
include_profile_data_on_invite: true
|
|
||||||
instance_map: {}
|
|
||||||
limit_profile_requests_to_users_who_share_rooms: false
|
|
||||||
limit_remote_rooms: null
|
|
||||||
listeners:
|
|
||||||
- bind_addresses:
|
|
||||||
- '::'
|
|
||||||
port: 8008
|
|
||||||
resources:
|
|
||||||
- compress: false
|
|
||||||
names:
|
|
||||||
- client
|
|
||||||
tls: false
|
|
||||||
type: http
|
|
||||||
x_forwarded: true
|
|
||||||
log_config: /config/synapse.log.config
|
|
||||||
macaroon_secret_key: Lg8DxGGfy95J367eVJZHLxmqP9XtN4FKdKxWpPvBS3mhviq9at8sw7KHRPkGmyqE
|
|
||||||
manhole_settings: null
|
|
||||||
max_spider_size: 10M
|
|
||||||
max_upload_size: 1024M
|
|
||||||
media_retention:
|
|
||||||
local_media_lifetime: 30d
|
|
||||||
remote_media_lifetime: 7d
|
|
||||||
media_storage_providers: []
|
|
||||||
media_store_path: /media-store
|
|
||||||
metrics_flags: null
|
|
||||||
modules: []
|
|
||||||
oembed: null
|
|
||||||
oidc_providers: null
|
|
||||||
old_signing_keys: null
|
|
||||||
opentracing: null
|
|
||||||
password_config:
|
|
||||||
enabled: true
|
|
||||||
localdb_enabled: true
|
|
||||||
pepper: zfvnYqxe3GTkdJ9BlfZiAqy2zMsjOg02uBTEiWLp2hjQGqlDw33pTSTplE6HoWlF
|
|
||||||
policy: null
|
|
||||||
pid_file: /homeserver.pid
|
|
||||||
presence:
|
|
||||||
enabled: true
|
|
||||||
public_baseurl: http://synapse:8008/
|
|
||||||
push:
|
|
||||||
include_content: true
|
|
||||||
rc_admin_redaction:
|
|
||||||
burst_count: 50
|
|
||||||
per_second: 1
|
|
||||||
rc_federation:
|
|
||||||
concurrent: 3
|
|
||||||
reject_limit: 50
|
|
||||||
sleep_delay: 500
|
|
||||||
sleep_limit: 10
|
|
||||||
window_size: 1000
|
|
||||||
rc_invites:
|
|
||||||
per_issuer:
|
|
||||||
burst_count: 10
|
|
||||||
per_second: 0.3
|
|
||||||
per_room:
|
|
||||||
burst_count: 10
|
|
||||||
per_second: 0.3
|
|
||||||
per_user:
|
|
||||||
burst_count: 5
|
|
||||||
per_second: 0.003
|
|
||||||
rc_joins:
|
|
||||||
local:
|
|
||||||
burst_count: 10
|
|
||||||
per_second: 0.1
|
|
||||||
remote:
|
|
||||||
burst_count: 10
|
|
||||||
per_second: 0.01
|
|
||||||
rc_login:
|
|
||||||
account:
|
|
||||||
burst_count: 3
|
|
||||||
per_second: 0.17
|
|
||||||
address:
|
|
||||||
burst_count: 3
|
|
||||||
per_second: 0.17
|
|
||||||
failed_attempts:
|
|
||||||
burst_count: 3
|
|
||||||
per_second: 0.17
|
|
||||||
rc_message:
|
|
||||||
burst_count: 10
|
|
||||||
per_second: 0.2
|
|
||||||
rc_registration:
|
|
||||||
burst_count: 3
|
|
||||||
per_second: 0.17
|
|
||||||
recaptcha_private_key: ''
|
|
||||||
recaptcha_public_key: ''
|
|
||||||
redaction_retention_period: 5m
|
|
||||||
redis:
|
|
||||||
enabled: false
|
|
||||||
host: null
|
|
||||||
password: null
|
|
||||||
port: 6379
|
|
||||||
registration_requires_token: false
|
|
||||||
registration_shared_secret: jBUKJozByo8s3bvKtYFpB350ZAnxGlzXsDpAZkgOFJuQfKAFHhqbc2dw8D54u4T9
|
|
||||||
report_stats: false
|
|
||||||
require_auth_for_profile_requests: false
|
|
||||||
retention:
|
|
||||||
enabled: true
|
|
||||||
purge_jobs:
|
|
||||||
- interval: 12h
|
|
||||||
room_list_publication_rules:
|
|
||||||
- action: allow
|
|
||||||
alias: '*'
|
|
||||||
room_id: '*'
|
|
||||||
user_id: '*'
|
|
||||||
room_prejoin_state: null
|
|
||||||
saml2_config:
|
|
||||||
sp_config: null
|
|
||||||
user_mapping_provider:
|
|
||||||
config: null
|
|
||||||
server_name: synapse
|
|
||||||
signing_key_path: /config/synapse.signing.key
|
|
||||||
spam_checker: []
|
|
||||||
sso: null
|
|
||||||
stats: null
|
|
||||||
stream_writers: {}
|
|
||||||
templates: null
|
|
||||||
tls_certificate_path: null
|
|
||||||
tls_private_key_path: null
|
|
||||||
trusted_key_servers:
|
|
||||||
- server_name: matrix.org
|
|
||||||
turn_allow_guests: false
|
|
||||||
ui_auth: null
|
|
||||||
url_preview_accept_language:
|
|
||||||
- en-US
|
|
||||||
- en
|
|
||||||
url_preview_enabled: true
|
|
||||||
url_preview_ip_range_blacklist:
|
|
||||||
- 127.0.0.0/8
|
|
||||||
- 10.0.0.0/8
|
|
||||||
- 172.16.0.0/12
|
|
||||||
- 192.168.0.0/16
|
|
||||||
- 100.64.0.0/10
|
|
||||||
- 192.0.0.0/24
|
|
||||||
- 169.254.0.0/16
|
|
||||||
- 192.88.99.0/24
|
|
||||||
- 198.18.0.0/15
|
|
||||||
- 192.0.2.0/24
|
|
||||||
- 198.51.100.0/24
|
|
||||||
- 203.0.113.0/24
|
|
||||||
- 224.0.0.0/4
|
|
||||||
- ::1/128
|
|
||||||
- fe80::/10
|
|
||||||
- fc00::/7
|
|
||||||
- 2001:db8::/32
|
|
||||||
- ff00::/8
|
|
||||||
- fec0::/10
|
|
||||||
user_directory: null
|
|
||||||
user_ips_max_age: 5m
|
|
||||||
|
|
||||||
28
testdata/synapse/synapse.log.config
vendored
28
testdata/synapse/synapse.log.config
vendored
@@ -1,28 +0,0 @@
|
|||||||
version: 1
|
|
||||||
formatters:
|
|
||||||
precise:
|
|
||||||
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
|
|
||||||
filters:
|
|
||||||
context:
|
|
||||||
(): synapse.util.logcontext.LoggingContextFilter
|
|
||||||
request: ""
|
|
||||||
handlers:
|
|
||||||
console:
|
|
||||||
class: logging.StreamHandler
|
|
||||||
formatter: precise
|
|
||||||
filters: [context]
|
|
||||||
loggers:
|
|
||||||
synapse:
|
|
||||||
level: INFO
|
|
||||||
shared_secret_authenticator:
|
|
||||||
level: INFO
|
|
||||||
rest_auth_provider:
|
|
||||||
level: INFO
|
|
||||||
synapse.storage.SQL:
|
|
||||||
# beware: increasing this to DEBUG will make synapse log sensitive
|
|
||||||
# information such as access tokens.
|
|
||||||
level: INFO
|
|
||||||
root:
|
|
||||||
level: INFO
|
|
||||||
handlers: [console]
|
|
||||||
|
|
||||||
1
testdata/synapse/synapse.signing.key
vendored
1
testdata/synapse/synapse.signing.key
vendored
@@ -1 +0,0 @@
|
|||||||
ed25519 a_FswB rsh+VxdR4YUv6rFM6393VmSEJJxzaDrdwlVwLe2rcRo
|
|
||||||
@@ -8,7 +8,7 @@ export default defineConfig({
|
|||||||
plugins: [
|
plugins: [
|
||||||
react(),
|
react(),
|
||||||
vitePluginVersionMark({
|
vitePluginVersionMark({
|
||||||
command: "git describe --tags || git rev-parse --short HEAD",
|
command: "git describe --tags",
|
||||||
ifMeta: true,
|
ifMeta: true,
|
||||||
ifLog: true,
|
ifLog: true,
|
||||||
ifGlobal: true,
|
ifGlobal: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user