mirror of
https://github.com/UA-Fediland/synapse-admin.git
synced 2024-10-30 03:14:51 +00:00
Compare commits
No commits in common. "b112689b8c796686872cb573ad2a582dcaacf3b5" and "7deb9bcf7e54cd4c4e634910c5d14f90ab85f405" have entirely different histories.
b112689b8c
...
7deb9bcf7e
55 changed files with 10874 additions and 11297 deletions
|
@ -1,13 +1,10 @@
|
||||||
# Exclude a bunch of stuff which can make the build context a larger than it needs to be
|
# Exclude a bunch of stuff which can make the build context a larger than it needs to be
|
||||||
tests/
|
tests/
|
||||||
build/
|
build/
|
||||||
dist/
|
|
||||||
lib/
|
lib/
|
||||||
node_modules/
|
node_modules/
|
||||||
electron_app/
|
electron_app/
|
||||||
karma-reports/
|
karma-reports/
|
||||||
.pnp.cjs
|
|
||||||
.pnp.loader.mjs
|
|
||||||
.idea/
|
.idea/
|
||||||
.tmp/
|
.tmp/
|
||||||
config.json*
|
config.json*
|
||||||
|
|
5
.env
Normal file
5
.env
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# This setting allows to fix the homeserver.
|
||||||
|
# If you set this setting, the user will not be able to select
|
||||||
|
# the server and have to use synapse-admin with this server.
|
||||||
|
|
||||||
|
#REACT_APP_SERVER=https://yourmatrixserver.example.com
|
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -1 +0,0 @@
|
||||||
yarn*.cjs binary
|
|
46
.github/workflows/docker-release.yml
vendored
46
.github/workflows/docker-release.yml
vendored
|
@ -1,5 +1,4 @@
|
||||||
name: Create docker image(s) and push to docker hub and ghcr.io
|
name: Create docker image(s) and push to docker hub
|
||||||
# see https://docs.github.com/en/actions/publishing-packages/publishing-docker-images#publishing-images-to-docker-hub-and-github-packages
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
@ -14,50 +13,39 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker:
|
docker:
|
||||||
name: Push Docker image to multiple registries
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
packages: write
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Calculate docker image tag
|
||||||
- name: Login to GHCR
|
id: set-tag
|
||||||
uses: docker/login-action@v3
|
run: |
|
||||||
with:
|
case "${GITHUB_REF}" in
|
||||||
registry: ghcr.io
|
refs/heads/master|refs/heads/main)
|
||||||
username: ${{ github.repository_owner }}
|
tag=latest
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
;;
|
||||||
|
refs/tags/*)
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
tag=${GITHUB_REF#refs/tags/}
|
||||||
id: meta
|
;;
|
||||||
uses: docker/metadata-action@v5
|
*)
|
||||||
with:
|
tag=${GITHUB_SHA}
|
||||||
images: |
|
;;
|
||||||
awesometechnologies/synapse-admin
|
esac
|
||||||
ghcr.io/${{ github.repository }}
|
echo "::set-output name=tag::$tag"
|
||||||
|
|
||||||
- name: Build and Push Tag
|
- name: Build and Push Tag
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: "awesometechnologies/synapse-admin:${{ steps.set-tag.outputs.tag }}"
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|
11
.github/workflows/edge_ghpage.yml
vendored
11
.github/workflows/edge_ghpage.yml
vendored
|
@ -11,19 +11,16 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout 🛎️
|
- name: Checkout 🛎️
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
fetch-depth: 100
|
|
||||||
fetch-tags: true
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "20"
|
node-version: "18"
|
||||||
- name: Install and Build 🔧
|
- name: Install and Build 🔧
|
||||||
run: |
|
run: |
|
||||||
yarn install --immutable
|
yarn install --immutable
|
||||||
yarn build --base=/synapse-admin
|
yarn build
|
||||||
|
|
||||||
- name: Deploy 🚀
|
- name: Deploy 🚀
|
||||||
uses: JamesIves/github-pages-deploy-action@v4.6.0
|
uses: JamesIves/github-pages-deploy-action@v4.5.0
|
||||||
with:
|
with:
|
||||||
branch: gh-pages
|
branch: gh-pages
|
||||||
folder: dist
|
folder: build
|
||||||
|
|
7
.github/workflows/github-release.yml
vendored
7
.github/workflows/github-release.yml
vendored
|
@ -16,14 +16,15 @@ jobs:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "20"
|
node-version: "18"
|
||||||
- run: yarn install --immutable
|
- run: yarn install --immutable
|
||||||
- run: yarn build
|
- run: yarn build
|
||||||
- run: |
|
- run: |
|
||||||
version=`git describe --dirty --tags || echo unknown`
|
version=`git describe --dirty --tags || echo unknown`
|
||||||
cp -r dist synapse-admin-$version
|
mkdir -p dist
|
||||||
|
cp -r build synapse-admin-$version
|
||||||
tar chvzf dist/synapse-admin-$version.tar.gz synapse-admin-$version
|
tar chvzf dist/synapse-admin-$version.tar.gz synapse-admin-$version
|
||||||
- uses: softprops/action-gh-release@9d7c94cfd0a1f3ed45544c887983e9fa900f0564
|
- uses: softprops/action-gh-release@3198ee18f814cdf787321b4a32a26ddbf37acc52
|
||||||
with:
|
with:
|
||||||
files: dist/*.tar.gz
|
files: dist/*.tar.gz
|
||||||
env:
|
env:
|
||||||
|
|
51
.github/workflows/test-docker-image.yml
vendored
Normal file
51
.github/workflows/test-docker-image.yml
vendored
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
name: Test docker image creation
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
# Sequence of patterns matched against refs/heads
|
||||||
|
# prettier-ignore
|
||||||
|
branches:
|
||||||
|
# Push events on branch fix_docker_cd
|
||||||
|
- fix_docker_cd
|
||||||
|
# Sequence of patterns matched against refs/tags
|
||||||
|
tags:
|
||||||
|
- '[0-9]+\.[0-9]+\.[0-9]+' # Push events to 0.X.X tag
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Calculate docker image tag
|
||||||
|
id: set-tag
|
||||||
|
run: |
|
||||||
|
case "${GITHUB_REF}" in
|
||||||
|
refs/heads/master|refs/heads/main)
|
||||||
|
tag=latest
|
||||||
|
;;
|
||||||
|
refs/tags/*)
|
||||||
|
tag=${GITHUB_REF#refs/tags/}
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
tag=${GITHUB_SHA}
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
echo "::set-output name=tag::$tag"
|
||||||
|
- name: Build and Push Tag
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: false
|
||||||
|
tags: "awesometechnologies/synapse-admin:${{ steps.set-tag.outputs.tag }}"
|
||||||
|
platforms: linux/amd64,linux/arm64
|
200
.gitignore
vendored
200
.gitignore
vendored
|
@ -1,193 +1,23 @@
|
||||||
# Created by https://www.toptal.com/developers/gitignore/api/node,yarn,react,visualstudiocode
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
# Edit at https://www.toptal.com/developers/gitignore?templates=node,yarn,react,visualstudiocode
|
|
||||||
|
|
||||||
### Node ###
|
# dependencies
|
||||||
# Logs
|
/node_modules
|
||||||
logs
|
/.pnp
|
||||||
*.log
|
.pnp.js
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
.pnpm-debug.log*
|
|
||||||
|
|
||||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
# testing
|
||||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
/coverage
|
||||||
|
|
||||||
# Runtime data
|
# production
|
||||||
pids
|
/build
|
||||||
*.pid
|
|
||||||
*.seed
|
|
||||||
*.pid.lock
|
|
||||||
|
|
||||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
# misc
|
||||||
lib-cov
|
.DS_Store
|
||||||
|
.env.local
|
||||||
# Coverage directory used by tools like istanbul
|
|
||||||
coverage
|
|
||||||
*.lcov
|
|
||||||
|
|
||||||
# nyc test coverage
|
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
|
||||||
.grunt
|
|
||||||
|
|
||||||
# Bower dependency directory (https://bower.io/)
|
|
||||||
bower_components
|
|
||||||
|
|
||||||
# node-waf configuration
|
|
||||||
.lock-wscript
|
|
||||||
|
|
||||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
|
||||||
build/Release
|
|
||||||
|
|
||||||
# Dependency directories
|
|
||||||
node_modules/
|
|
||||||
jspm_packages/
|
|
||||||
|
|
||||||
# Snowpack dependency directory (https://snowpack.dev/)
|
|
||||||
web_modules/
|
|
||||||
|
|
||||||
# TypeScript cache
|
|
||||||
*.tsbuildinfo
|
|
||||||
|
|
||||||
# Optional npm cache directory
|
|
||||||
.npm
|
|
||||||
|
|
||||||
# Optional eslint cache
|
|
||||||
.eslintcache
|
|
||||||
|
|
||||||
# Optional stylelint cache
|
|
||||||
.stylelintcache
|
|
||||||
|
|
||||||
# Microbundle cache
|
|
||||||
.rpt2_cache/
|
|
||||||
.rts2_cache_cjs/
|
|
||||||
.rts2_cache_es/
|
|
||||||
.rts2_cache_umd/
|
|
||||||
|
|
||||||
# Optional REPL history
|
|
||||||
.node_repl_history
|
|
||||||
|
|
||||||
# Output of 'npm pack'
|
|
||||||
*.tgz
|
|
||||||
|
|
||||||
# Yarn Integrity file
|
|
||||||
.yarn-integrity
|
|
||||||
|
|
||||||
# dotenv environment variable files
|
|
||||||
.env
|
|
||||||
.env.development.local
|
.env.development.local
|
||||||
.env.test.local
|
.env.test.local
|
||||||
.env.production.local
|
.env.production.local
|
||||||
.env.local
|
|
||||||
|
|
||||||
# parcel-bundler cache (https://parceljs.org/)
|
npm-debug.log*
|
||||||
.cache
|
yarn-debug.log*
|
||||||
.parcel-cache
|
yarn-error.log*
|
||||||
|
|
||||||
# Next.js build output
|
|
||||||
.next
|
|
||||||
out
|
|
||||||
|
|
||||||
# Nuxt.js build / generate output
|
|
||||||
.nuxt
|
|
||||||
dist
|
|
||||||
|
|
||||||
# Gatsby files
|
|
||||||
.cache/
|
|
||||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
|
||||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
|
||||||
# public
|
|
||||||
|
|
||||||
# vuepress build output
|
|
||||||
.vuepress/dist
|
|
||||||
|
|
||||||
# vuepress v2.x temp and cache directory
|
|
||||||
.temp
|
|
||||||
|
|
||||||
# Docusaurus cache and generated files
|
|
||||||
.docusaurus
|
|
||||||
|
|
||||||
# Serverless directories
|
|
||||||
.serverless/
|
|
||||||
|
|
||||||
# FuseBox cache
|
|
||||||
.fusebox/
|
|
||||||
|
|
||||||
# DynamoDB Local files
|
|
||||||
.dynamodb/
|
|
||||||
|
|
||||||
# TernJS port file
|
|
||||||
.tern-port
|
|
||||||
|
|
||||||
# Stores VSCode versions used for testing VSCode extensions
|
|
||||||
.vscode-test
|
|
||||||
|
|
||||||
# yarn v2
|
|
||||||
.yarn/cache
|
|
||||||
.yarn/unplugged
|
|
||||||
.yarn/build-state.yml
|
|
||||||
.yarn/install-state.gz
|
|
||||||
.pnp.*
|
|
||||||
|
|
||||||
### Node Patch ###
|
|
||||||
# Serverless Webpack directories
|
|
||||||
.webpack/
|
|
||||||
|
|
||||||
# Optional stylelint cache
|
|
||||||
|
|
||||||
# SvelteKit build / generate output
|
|
||||||
.svelte-kit
|
|
||||||
|
|
||||||
### react ###
|
|
||||||
.DS_*
|
|
||||||
**/*.backup.*
|
|
||||||
**/*.back.*
|
|
||||||
|
|
||||||
node_modules
|
|
||||||
|
|
||||||
*.sublime*
|
|
||||||
|
|
||||||
psd
|
|
||||||
thumb
|
|
||||||
sketch
|
|
||||||
|
|
||||||
### VisualStudioCode ###
|
|
||||||
.vscode/*
|
|
||||||
!.vscode/settings.json
|
|
||||||
!.vscode/tasks.json
|
|
||||||
!.vscode/launch.json
|
|
||||||
!.vscode/extensions.json
|
|
||||||
!.vscode/*.code-snippets
|
|
||||||
|
|
||||||
# Local History for Visual Studio Code
|
|
||||||
.history/
|
|
||||||
|
|
||||||
# Built Visual Studio Code Extensions
|
|
||||||
*.vsix
|
|
||||||
|
|
||||||
### VisualStudioCode Patch ###
|
|
||||||
# Ignore all local history of files
|
|
||||||
.history
|
|
||||||
.ionide
|
|
||||||
|
|
||||||
### yarn ###
|
|
||||||
# https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored
|
|
||||||
|
|
||||||
.yarn/*
|
|
||||||
!.yarn/releases
|
|
||||||
!.yarn/patches
|
|
||||||
!.yarn/plugins
|
|
||||||
!.yarn/sdks
|
|
||||||
!.yarn/versions
|
|
||||||
|
|
||||||
# if you are NOT using Zero-installs, then:
|
|
||||||
# comment the following lines
|
|
||||||
!.yarn/cache
|
|
||||||
|
|
||||||
# and uncomment the following lines
|
|
||||||
# .pnp.*
|
|
||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/node,yarn,react,visualstudiocode
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
.yarn
|
|
7
.vscode/extensions.json
vendored
7
.vscode/extensions.json
vendored
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"recommendations": [
|
|
||||||
"arcanis.vscode-zipfs",
|
|
||||||
"dbaeumer.vscode-eslint",
|
|
||||||
"esbenp.prettier-vscode"
|
|
||||||
]
|
|
||||||
}
|
|
10
.vscode/settings.json
vendored
10
.vscode/settings.json
vendored
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"search.exclude": {
|
|
||||||
"**/.yarn": true,
|
|
||||||
"**/.pnp.*": true
|
|
||||||
},
|
|
||||||
"eslint.nodePath": ".yarn/sdks",
|
|
||||||
"prettier.prettierPath": ".yarn/sdks/prettier/index.cjs",
|
|
||||||
"typescript.tsdk": ".yarn/sdks/typescript/lib",
|
|
||||||
"typescript.enablePromptUseWorkspaceTsdk": true
|
|
||||||
}
|
|
BIN
.yarn/releases/yarn-4.1.1.cjs
vendored
BIN
.yarn/releases/yarn-4.1.1.cjs
vendored
Binary file not shown.
20
.yarn/sdks/eslint/bin/eslint.js
vendored
20
.yarn/sdks/eslint/bin/eslint.js
vendored
|
@ -1,20 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
const {existsSync} = require(`fs`);
|
|
||||||
const {createRequire} = require(`module`);
|
|
||||||
const {resolve} = require(`path`);
|
|
||||||
|
|
||||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
|
||||||
|
|
||||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
|
||||||
const absRequire = createRequire(absPnpApiPath);
|
|
||||||
|
|
||||||
if (existsSync(absPnpApiPath)) {
|
|
||||||
if (!process.versions.pnp) {
|
|
||||||
// Setup the environment to be able to require eslint/bin/eslint.js
|
|
||||||
require(absPnpApiPath).setup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defer to the real eslint/bin/eslint.js your application uses
|
|
||||||
module.exports = absRequire(`eslint/bin/eslint.js`);
|
|
20
.yarn/sdks/eslint/lib/api.js
vendored
20
.yarn/sdks/eslint/lib/api.js
vendored
|
@ -1,20 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
const {existsSync} = require(`fs`);
|
|
||||||
const {createRequire} = require(`module`);
|
|
||||||
const {resolve} = require(`path`);
|
|
||||||
|
|
||||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
|
||||||
|
|
||||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
|
||||||
const absRequire = createRequire(absPnpApiPath);
|
|
||||||
|
|
||||||
if (existsSync(absPnpApiPath)) {
|
|
||||||
if (!process.versions.pnp) {
|
|
||||||
// Setup the environment to be able to require eslint
|
|
||||||
require(absPnpApiPath).setup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defer to the real eslint your application uses
|
|
||||||
module.exports = absRequire(`eslint`);
|
|
20
.yarn/sdks/eslint/lib/unsupported-api.js
vendored
20
.yarn/sdks/eslint/lib/unsupported-api.js
vendored
|
@ -1,20 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
const {existsSync} = require(`fs`);
|
|
||||||
const {createRequire} = require(`module`);
|
|
||||||
const {resolve} = require(`path`);
|
|
||||||
|
|
||||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
|
||||||
|
|
||||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
|
||||||
const absRequire = createRequire(absPnpApiPath);
|
|
||||||
|
|
||||||
if (existsSync(absPnpApiPath)) {
|
|
||||||
if (!process.versions.pnp) {
|
|
||||||
// Setup the environment to be able to require eslint/use-at-your-own-risk
|
|
||||||
require(absPnpApiPath).setup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defer to the real eslint/use-at-your-own-risk your application uses
|
|
||||||
module.exports = absRequire(`eslint/use-at-your-own-risk`);
|
|
14
.yarn/sdks/eslint/package.json
vendored
14
.yarn/sdks/eslint/package.json
vendored
|
@ -1,14 +0,0 @@
|
||||||
{
|
|
||||||
"name": "eslint",
|
|
||||||
"version": "8.57.0-sdk",
|
|
||||||
"main": "./lib/api.js",
|
|
||||||
"type": "commonjs",
|
|
||||||
"bin": {
|
|
||||||
"eslint": "./bin/eslint.js"
|
|
||||||
},
|
|
||||||
"exports": {
|
|
||||||
"./package.json": "./package.json",
|
|
||||||
".": "./lib/api.js",
|
|
||||||
"./use-at-your-own-risk": "./lib/unsupported-api.js"
|
|
||||||
}
|
|
||||||
}
|
|
5
.yarn/sdks/integrations.yml
vendored
5
.yarn/sdks/integrations.yml
vendored
|
@ -1,5 +0,0 @@
|
||||||
# This file is automatically generated by @yarnpkg/sdks.
|
|
||||||
# Manual changes might be lost!
|
|
||||||
|
|
||||||
integrations:
|
|
||||||
- vscode
|
|
20
.yarn/sdks/prettier/bin/prettier.cjs
vendored
20
.yarn/sdks/prettier/bin/prettier.cjs
vendored
|
@ -1,20 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
const {existsSync} = require(`fs`);
|
|
||||||
const {createRequire} = require(`module`);
|
|
||||||
const {resolve} = require(`path`);
|
|
||||||
|
|
||||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
|
||||||
|
|
||||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
|
||||||
const absRequire = createRequire(absPnpApiPath);
|
|
||||||
|
|
||||||
if (existsSync(absPnpApiPath)) {
|
|
||||||
if (!process.versions.pnp) {
|
|
||||||
// Setup the environment to be able to require prettier/bin/prettier.cjs
|
|
||||||
require(absPnpApiPath).setup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defer to the real prettier/bin/prettier.cjs your application uses
|
|
||||||
module.exports = absRequire(`prettier/bin/prettier.cjs`);
|
|
20
.yarn/sdks/prettier/index.cjs
vendored
20
.yarn/sdks/prettier/index.cjs
vendored
|
@ -1,20 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
const {existsSync} = require(`fs`);
|
|
||||||
const {createRequire} = require(`module`);
|
|
||||||
const {resolve} = require(`path`);
|
|
||||||
|
|
||||||
const relPnpApiPath = "../../../.pnp.cjs";
|
|
||||||
|
|
||||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
|
||||||
const absRequire = createRequire(absPnpApiPath);
|
|
||||||
|
|
||||||
if (existsSync(absPnpApiPath)) {
|
|
||||||
if (!process.versions.pnp) {
|
|
||||||
// Setup the environment to be able to require prettier
|
|
||||||
require(absPnpApiPath).setup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defer to the real prettier your application uses
|
|
||||||
module.exports = absRequire(`prettier`);
|
|
7
.yarn/sdks/prettier/package.json
vendored
7
.yarn/sdks/prettier/package.json
vendored
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"name": "prettier",
|
|
||||||
"version": "3.2.5-sdk",
|
|
||||||
"main": "./index.cjs",
|
|
||||||
"type": "commonjs",
|
|
||||||
"bin": "./bin/prettier.cjs"
|
|
||||||
}
|
|
20
.yarn/sdks/typescript/bin/tsc
vendored
20
.yarn/sdks/typescript/bin/tsc
vendored
|
@ -1,20 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
const {existsSync} = require(`fs`);
|
|
||||||
const {createRequire} = require(`module`);
|
|
||||||
const {resolve} = require(`path`);
|
|
||||||
|
|
||||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
|
||||||
|
|
||||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
|
||||||
const absRequire = createRequire(absPnpApiPath);
|
|
||||||
|
|
||||||
if (existsSync(absPnpApiPath)) {
|
|
||||||
if (!process.versions.pnp) {
|
|
||||||
// Setup the environment to be able to require typescript/bin/tsc
|
|
||||||
require(absPnpApiPath).setup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defer to the real typescript/bin/tsc your application uses
|
|
||||||
module.exports = absRequire(`typescript/bin/tsc`);
|
|
20
.yarn/sdks/typescript/bin/tsserver
vendored
20
.yarn/sdks/typescript/bin/tsserver
vendored
|
@ -1,20 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
const {existsSync} = require(`fs`);
|
|
||||||
const {createRequire} = require(`module`);
|
|
||||||
const {resolve} = require(`path`);
|
|
||||||
|
|
||||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
|
||||||
|
|
||||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
|
||||||
const absRequire = createRequire(absPnpApiPath);
|
|
||||||
|
|
||||||
if (existsSync(absPnpApiPath)) {
|
|
||||||
if (!process.versions.pnp) {
|
|
||||||
// Setup the environment to be able to require typescript/bin/tsserver
|
|
||||||
require(absPnpApiPath).setup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defer to the real typescript/bin/tsserver your application uses
|
|
||||||
module.exports = absRequire(`typescript/bin/tsserver`);
|
|
20
.yarn/sdks/typescript/lib/tsc.js
vendored
20
.yarn/sdks/typescript/lib/tsc.js
vendored
|
@ -1,20 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
const {existsSync} = require(`fs`);
|
|
||||||
const {createRequire} = require(`module`);
|
|
||||||
const {resolve} = require(`path`);
|
|
||||||
|
|
||||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
|
||||||
|
|
||||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
|
||||||
const absRequire = createRequire(absPnpApiPath);
|
|
||||||
|
|
||||||
if (existsSync(absPnpApiPath)) {
|
|
||||||
if (!process.versions.pnp) {
|
|
||||||
// Setup the environment to be able to require typescript/lib/tsc.js
|
|
||||||
require(absPnpApiPath).setup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defer to the real typescript/lib/tsc.js your application uses
|
|
||||||
module.exports = absRequire(`typescript/lib/tsc.js`);
|
|
225
.yarn/sdks/typescript/lib/tsserver.js
vendored
225
.yarn/sdks/typescript/lib/tsserver.js
vendored
|
@ -1,225 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
const {existsSync} = require(`fs`);
|
|
||||||
const {createRequire} = require(`module`);
|
|
||||||
const {resolve} = require(`path`);
|
|
||||||
|
|
||||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
|
||||||
|
|
||||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
|
||||||
const absRequire = createRequire(absPnpApiPath);
|
|
||||||
|
|
||||||
const moduleWrapper = tsserver => {
|
|
||||||
if (!process.versions.pnp) {
|
|
||||||
return tsserver;
|
|
||||||
}
|
|
||||||
|
|
||||||
const {isAbsolute} = require(`path`);
|
|
||||||
const pnpApi = require(`pnpapi`);
|
|
||||||
|
|
||||||
const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//);
|
|
||||||
const isPortal = str => str.startsWith("portal:/");
|
|
||||||
const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`);
|
|
||||||
|
|
||||||
const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => {
|
|
||||||
return `${locator.name}@${locator.reference}`;
|
|
||||||
}));
|
|
||||||
|
|
||||||
// VSCode sends the zip paths to TS using the "zip://" prefix, that TS
|
|
||||||
// doesn't understand. This layer makes sure to remove the protocol
|
|
||||||
// before forwarding it to TS, and to add it back on all returned paths.
|
|
||||||
|
|
||||||
function toEditorPath(str) {
|
|
||||||
// We add the `zip:` prefix to both `.zip/` paths and virtual paths
|
|
||||||
if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) {
|
|
||||||
// We also take the opportunity to turn virtual paths into physical ones;
|
|
||||||
// this makes it much easier to work with workspaces that list peer
|
|
||||||
// dependencies, since otherwise Ctrl+Click would bring us to the virtual
|
|
||||||
// file instances instead of the real ones.
|
|
||||||
//
|
|
||||||
// We only do this to modules owned by the the dependency tree roots.
|
|
||||||
// This avoids breaking the resolution when jumping inside a vendor
|
|
||||||
// with peer dep (otherwise jumping into react-dom would show resolution
|
|
||||||
// errors on react).
|
|
||||||
//
|
|
||||||
const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str;
|
|
||||||
if (resolved) {
|
|
||||||
const locator = pnpApi.findPackageLocator(resolved);
|
|
||||||
if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) {
|
|
||||||
str = resolved;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
str = normalize(str);
|
|
||||||
|
|
||||||
if (str.match(/\.zip\//)) {
|
|
||||||
switch (hostInfo) {
|
|
||||||
// Absolute VSCode `Uri.fsPath`s need to start with a slash.
|
|
||||||
// VSCode only adds it automatically for supported schemes,
|
|
||||||
// so we have to do it manually for the `zip` scheme.
|
|
||||||
// The path needs to start with a caret otherwise VSCode doesn't handle the protocol
|
|
||||||
//
|
|
||||||
// Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910
|
|
||||||
//
|
|
||||||
// 2021-10-08: VSCode changed the format in 1.61.
|
|
||||||
// Before | ^zip:/c:/foo/bar.zip/package.json
|
|
||||||
// After | ^/zip//c:/foo/bar.zip/package.json
|
|
||||||
//
|
|
||||||
// 2022-04-06: VSCode changed the format in 1.66.
|
|
||||||
// Before | ^/zip//c:/foo/bar.zip/package.json
|
|
||||||
// After | ^/zip/c:/foo/bar.zip/package.json
|
|
||||||
//
|
|
||||||
// 2022-05-06: VSCode changed the format in 1.68
|
|
||||||
// Before | ^/zip/c:/foo/bar.zip/package.json
|
|
||||||
// After | ^/zip//c:/foo/bar.zip/package.json
|
|
||||||
//
|
|
||||||
case `vscode <1.61`: {
|
|
||||||
str = `^zip:${str}`;
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case `vscode <1.66`: {
|
|
||||||
str = `^/zip/${str}`;
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case `vscode <1.68`: {
|
|
||||||
str = `^/zip${str}`;
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case `vscode`: {
|
|
||||||
str = `^/zip/${str}`;
|
|
||||||
} break;
|
|
||||||
|
|
||||||
// To make "go to definition" work,
|
|
||||||
// We have to resolve the actual file system path from virtual path
|
|
||||||
// and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip)
|
|
||||||
case `coc-nvim`: {
|
|
||||||
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
|
||||||
str = resolve(`zipfile:${str}`);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
// Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server)
|
|
||||||
// We have to resolve the actual file system path from virtual path,
|
|
||||||
// everything else is up to neovim
|
|
||||||
case `neovim`: {
|
|
||||||
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
|
||||||
str = `zipfile://${str}`;
|
|
||||||
} break;
|
|
||||||
|
|
||||||
default: {
|
|
||||||
str = `zip:${str}`;
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
function fromEditorPath(str) {
|
|
||||||
switch (hostInfo) {
|
|
||||||
case `coc-nvim`: {
|
|
||||||
str = str.replace(/\.zip::/, `.zip/`);
|
|
||||||
// The path for coc-nvim is in format of /<pwd>/zipfile:/<pwd>/.yarn/...
|
|
||||||
// So in order to convert it back, we use .* to match all the thing
|
|
||||||
// before `zipfile:`
|
|
||||||
return process.platform === `win32`
|
|
||||||
? str.replace(/^.*zipfile:\//, ``)
|
|
||||||
: str.replace(/^.*zipfile:/, ``);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case `neovim`: {
|
|
||||||
str = str.replace(/\.zip::/, `.zip/`);
|
|
||||||
// The path for neovim is in format of zipfile:///<pwd>/.yarn/...
|
|
||||||
return str.replace(/^zipfile:\/\//, ``);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case `vscode`:
|
|
||||||
default: {
|
|
||||||
return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`)
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force enable 'allowLocalPluginLoads'
|
|
||||||
// TypeScript tries to resolve plugins using a path relative to itself
|
|
||||||
// which doesn't work when using the global cache
|
|
||||||
// https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238
|
|
||||||
// VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but
|
|
||||||
// TypeScript already does local loads and if this code is running the user trusts the workspace
|
|
||||||
// https://github.com/microsoft/vscode/issues/45856
|
|
||||||
const ConfiguredProject = tsserver.server.ConfiguredProject;
|
|
||||||
const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype;
|
|
||||||
ConfiguredProject.prototype.enablePluginsWithOptions = function() {
|
|
||||||
this.projectService.allowLocalPluginLoads = true;
|
|
||||||
return originalEnablePluginsWithOptions.apply(this, arguments);
|
|
||||||
};
|
|
||||||
|
|
||||||
// And here is the point where we hijack the VSCode <-> TS communications
|
|
||||||
// by adding ourselves in the middle. We locate everything that looks
|
|
||||||
// like an absolute path of ours and normalize it.
|
|
||||||
|
|
||||||
const Session = tsserver.server.Session;
|
|
||||||
const {onMessage: originalOnMessage, send: originalSend} = Session.prototype;
|
|
||||||
let hostInfo = `unknown`;
|
|
||||||
|
|
||||||
Object.assign(Session.prototype, {
|
|
||||||
onMessage(/** @type {string | object} */ message) {
|
|
||||||
const isStringMessage = typeof message === 'string';
|
|
||||||
const parsedMessage = isStringMessage ? JSON.parse(message) : message;
|
|
||||||
|
|
||||||
if (
|
|
||||||
parsedMessage != null &&
|
|
||||||
typeof parsedMessage === `object` &&
|
|
||||||
parsedMessage.arguments &&
|
|
||||||
typeof parsedMessage.arguments.hostInfo === `string`
|
|
||||||
) {
|
|
||||||
hostInfo = parsedMessage.arguments.hostInfo;
|
|
||||||
if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) {
|
|
||||||
const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match(
|
|
||||||
// The RegExp from https://semver.org/ but without the caret at the start
|
|
||||||
/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
|
|
||||||
) ?? []).map(Number)
|
|
||||||
|
|
||||||
if (major === 1) {
|
|
||||||
if (minor < 61) {
|
|
||||||
hostInfo += ` <1.61`;
|
|
||||||
} else if (minor < 66) {
|
|
||||||
hostInfo += ` <1.66`;
|
|
||||||
} else if (minor < 68) {
|
|
||||||
hostInfo += ` <1.68`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => {
|
|
||||||
return typeof value === 'string' ? fromEditorPath(value) : value;
|
|
||||||
});
|
|
||||||
|
|
||||||
return originalOnMessage.call(
|
|
||||||
this,
|
|
||||||
isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
send(/** @type {any} */ msg) {
|
|
||||||
return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => {
|
|
||||||
return typeof value === `string` ? toEditorPath(value) : value;
|
|
||||||
})));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return tsserver;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (existsSync(absPnpApiPath)) {
|
|
||||||
if (!process.versions.pnp) {
|
|
||||||
// Setup the environment to be able to require typescript/lib/tsserver.js
|
|
||||||
require(absPnpApiPath).setup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defer to the real typescript/lib/tsserver.js your application uses
|
|
||||||
module.exports = moduleWrapper(absRequire(`typescript/lib/tsserver.js`));
|
|
225
.yarn/sdks/typescript/lib/tsserverlibrary.js
vendored
225
.yarn/sdks/typescript/lib/tsserverlibrary.js
vendored
|
@ -1,225 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
const {existsSync} = require(`fs`);
|
|
||||||
const {createRequire} = require(`module`);
|
|
||||||
const {resolve} = require(`path`);
|
|
||||||
|
|
||||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
|
||||||
|
|
||||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
|
||||||
const absRequire = createRequire(absPnpApiPath);
|
|
||||||
|
|
||||||
const moduleWrapper = tsserver => {
|
|
||||||
if (!process.versions.pnp) {
|
|
||||||
return tsserver;
|
|
||||||
}
|
|
||||||
|
|
||||||
const {isAbsolute} = require(`path`);
|
|
||||||
const pnpApi = require(`pnpapi`);
|
|
||||||
|
|
||||||
const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//);
|
|
||||||
const isPortal = str => str.startsWith("portal:/");
|
|
||||||
const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`);
|
|
||||||
|
|
||||||
const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => {
|
|
||||||
return `${locator.name}@${locator.reference}`;
|
|
||||||
}));
|
|
||||||
|
|
||||||
// VSCode sends the zip paths to TS using the "zip://" prefix, that TS
|
|
||||||
// doesn't understand. This layer makes sure to remove the protocol
|
|
||||||
// before forwarding it to TS, and to add it back on all returned paths.
|
|
||||||
|
|
||||||
function toEditorPath(str) {
|
|
||||||
// We add the `zip:` prefix to both `.zip/` paths and virtual paths
|
|
||||||
if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) {
|
|
||||||
// We also take the opportunity to turn virtual paths into physical ones;
|
|
||||||
// this makes it much easier to work with workspaces that list peer
|
|
||||||
// dependencies, since otherwise Ctrl+Click would bring us to the virtual
|
|
||||||
// file instances instead of the real ones.
|
|
||||||
//
|
|
||||||
// We only do this to modules owned by the the dependency tree roots.
|
|
||||||
// This avoids breaking the resolution when jumping inside a vendor
|
|
||||||
// with peer dep (otherwise jumping into react-dom would show resolution
|
|
||||||
// errors on react).
|
|
||||||
//
|
|
||||||
const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str;
|
|
||||||
if (resolved) {
|
|
||||||
const locator = pnpApi.findPackageLocator(resolved);
|
|
||||||
if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) {
|
|
||||||
str = resolved;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
str = normalize(str);
|
|
||||||
|
|
||||||
if (str.match(/\.zip\//)) {
|
|
||||||
switch (hostInfo) {
|
|
||||||
// Absolute VSCode `Uri.fsPath`s need to start with a slash.
|
|
||||||
// VSCode only adds it automatically for supported schemes,
|
|
||||||
// so we have to do it manually for the `zip` scheme.
|
|
||||||
// The path needs to start with a caret otherwise VSCode doesn't handle the protocol
|
|
||||||
//
|
|
||||||
// Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910
|
|
||||||
//
|
|
||||||
// 2021-10-08: VSCode changed the format in 1.61.
|
|
||||||
// Before | ^zip:/c:/foo/bar.zip/package.json
|
|
||||||
// After | ^/zip//c:/foo/bar.zip/package.json
|
|
||||||
//
|
|
||||||
// 2022-04-06: VSCode changed the format in 1.66.
|
|
||||||
// Before | ^/zip//c:/foo/bar.zip/package.json
|
|
||||||
// After | ^/zip/c:/foo/bar.zip/package.json
|
|
||||||
//
|
|
||||||
// 2022-05-06: VSCode changed the format in 1.68
|
|
||||||
// Before | ^/zip/c:/foo/bar.zip/package.json
|
|
||||||
// After | ^/zip//c:/foo/bar.zip/package.json
|
|
||||||
//
|
|
||||||
case `vscode <1.61`: {
|
|
||||||
str = `^zip:${str}`;
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case `vscode <1.66`: {
|
|
||||||
str = `^/zip/${str}`;
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case `vscode <1.68`: {
|
|
||||||
str = `^/zip${str}`;
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case `vscode`: {
|
|
||||||
str = `^/zip/${str}`;
|
|
||||||
} break;
|
|
||||||
|
|
||||||
// To make "go to definition" work,
|
|
||||||
// We have to resolve the actual file system path from virtual path
|
|
||||||
// and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip)
|
|
||||||
case `coc-nvim`: {
|
|
||||||
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
|
||||||
str = resolve(`zipfile:${str}`);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
// Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server)
|
|
||||||
// We have to resolve the actual file system path from virtual path,
|
|
||||||
// everything else is up to neovim
|
|
||||||
case `neovim`: {
|
|
||||||
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
|
||||||
str = `zipfile://${str}`;
|
|
||||||
} break;
|
|
||||||
|
|
||||||
default: {
|
|
||||||
str = `zip:${str}`;
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
function fromEditorPath(str) {
|
|
||||||
switch (hostInfo) {
|
|
||||||
case `coc-nvim`: {
|
|
||||||
str = str.replace(/\.zip::/, `.zip/`);
|
|
||||||
// The path for coc-nvim is in format of /<pwd>/zipfile:/<pwd>/.yarn/...
|
|
||||||
// So in order to convert it back, we use .* to match all the thing
|
|
||||||
// before `zipfile:`
|
|
||||||
return process.platform === `win32`
|
|
||||||
? str.replace(/^.*zipfile:\//, ``)
|
|
||||||
: str.replace(/^.*zipfile:/, ``);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case `neovim`: {
|
|
||||||
str = str.replace(/\.zip::/, `.zip/`);
|
|
||||||
// The path for neovim is in format of zipfile:///<pwd>/.yarn/...
|
|
||||||
return str.replace(/^zipfile:\/\//, ``);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case `vscode`:
|
|
||||||
default: {
|
|
||||||
return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`)
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force enable 'allowLocalPluginLoads'
|
|
||||||
// TypeScript tries to resolve plugins using a path relative to itself
|
|
||||||
// which doesn't work when using the global cache
|
|
||||||
// https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238
|
|
||||||
// VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but
|
|
||||||
// TypeScript already does local loads and if this code is running the user trusts the workspace
|
|
||||||
// https://github.com/microsoft/vscode/issues/45856
|
|
||||||
const ConfiguredProject = tsserver.server.ConfiguredProject;
|
|
||||||
const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype;
|
|
||||||
ConfiguredProject.prototype.enablePluginsWithOptions = function() {
|
|
||||||
this.projectService.allowLocalPluginLoads = true;
|
|
||||||
return originalEnablePluginsWithOptions.apply(this, arguments);
|
|
||||||
};
|
|
||||||
|
|
||||||
// And here is the point where we hijack the VSCode <-> TS communications
|
|
||||||
// by adding ourselves in the middle. We locate everything that looks
|
|
||||||
// like an absolute path of ours and normalize it.
|
|
||||||
|
|
||||||
const Session = tsserver.server.Session;
|
|
||||||
const {onMessage: originalOnMessage, send: originalSend} = Session.prototype;
|
|
||||||
let hostInfo = `unknown`;
|
|
||||||
|
|
||||||
Object.assign(Session.prototype, {
|
|
||||||
onMessage(/** @type {string | object} */ message) {
|
|
||||||
const isStringMessage = typeof message === 'string';
|
|
||||||
const parsedMessage = isStringMessage ? JSON.parse(message) : message;
|
|
||||||
|
|
||||||
if (
|
|
||||||
parsedMessage != null &&
|
|
||||||
typeof parsedMessage === `object` &&
|
|
||||||
parsedMessage.arguments &&
|
|
||||||
typeof parsedMessage.arguments.hostInfo === `string`
|
|
||||||
) {
|
|
||||||
hostInfo = parsedMessage.arguments.hostInfo;
|
|
||||||
if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) {
|
|
||||||
const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match(
|
|
||||||
// The RegExp from https://semver.org/ but without the caret at the start
|
|
||||||
/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
|
|
||||||
) ?? []).map(Number)
|
|
||||||
|
|
||||||
if (major === 1) {
|
|
||||||
if (minor < 61) {
|
|
||||||
hostInfo += ` <1.61`;
|
|
||||||
} else if (minor < 66) {
|
|
||||||
hostInfo += ` <1.66`;
|
|
||||||
} else if (minor < 68) {
|
|
||||||
hostInfo += ` <1.68`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => {
|
|
||||||
return typeof value === 'string' ? fromEditorPath(value) : value;
|
|
||||||
});
|
|
||||||
|
|
||||||
return originalOnMessage.call(
|
|
||||||
this,
|
|
||||||
isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
send(/** @type {any} */ msg) {
|
|
||||||
return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => {
|
|
||||||
return typeof value === `string` ? toEditorPath(value) : value;
|
|
||||||
})));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return tsserver;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (existsSync(absPnpApiPath)) {
|
|
||||||
if (!process.versions.pnp) {
|
|
||||||
// Setup the environment to be able to require typescript/lib/tsserverlibrary.js
|
|
||||||
require(absPnpApiPath).setup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defer to the real typescript/lib/tsserverlibrary.js your application uses
|
|
||||||
module.exports = moduleWrapper(absRequire(`typescript/lib/tsserverlibrary.js`));
|
|
20
.yarn/sdks/typescript/lib/typescript.js
vendored
20
.yarn/sdks/typescript/lib/typescript.js
vendored
|
@ -1,20 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
const {existsSync} = require(`fs`);
|
|
||||||
const {createRequire} = require(`module`);
|
|
||||||
const {resolve} = require(`path`);
|
|
||||||
|
|
||||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
|
||||||
|
|
||||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
|
||||||
const absRequire = createRequire(absPnpApiPath);
|
|
||||||
|
|
||||||
if (existsSync(absPnpApiPath)) {
|
|
||||||
if (!process.versions.pnp) {
|
|
||||||
// Setup the environment to be able to require typescript
|
|
||||||
require(absPnpApiPath).setup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defer to the real typescript your application uses
|
|
||||||
module.exports = absRequire(`typescript`);
|
|
10
.yarn/sdks/typescript/package.json
vendored
10
.yarn/sdks/typescript/package.json
vendored
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"name": "typescript",
|
|
||||||
"version": "5.4.5-sdk",
|
|
||||||
"main": "./lib/typescript.js",
|
|
||||||
"type": "commonjs",
|
|
||||||
"bin": {
|
|
||||||
"tsc": "./bin/tsc",
|
|
||||||
"tsserver": "./bin/tsserver"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
yarnPath: .yarn/releases/yarn-4.1.1.cjs
|
|
21
Dockerfile
21
Dockerfile
|
@ -1,26 +1,19 @@
|
||||||
# Builder
|
# Builder
|
||||||
FROM node:lts as builder
|
FROM node:lts as builder
|
||||||
LABEL org.opencontainers.image.url=https://github.com/Awesome-Technologies/synapse-admin org.opencontainers.image.source=https://github.com/Awesome-Technologies/synapse-admin
|
|
||||||
# Base path for synapse admin
|
ARG REACT_APP_SERVER
|
||||||
ARG BASE_PATH=./
|
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
# 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
|
COPY . /src
|
||||||
RUN yarn build --base=$BASE_PATH
|
RUN yarn --network-timeout=300000 install --immutable
|
||||||
|
RUN REACT_APP_SERVER=$REACT_APP_SERVER yarn build
|
||||||
|
|
||||||
|
|
||||||
# App
|
# App
|
||||||
FROM nginx:stable-alpine
|
FROM nginx:alpine
|
||||||
|
|
||||||
COPY --from=builder /src/dist /app
|
COPY --from=builder /src/build /app
|
||||||
|
|
||||||
RUN rm -rf /usr/share/nginx/html \
|
RUN rm -rf /usr/share/nginx/html \
|
||||||
&& ln -s /app /usr/share/nginx/html
|
&& ln -s /app /usr/share/nginx/html
|
||||||
|
|
86
README.md
86
README.md
|
@ -64,6 +64,11 @@ You have three options:
|
||||||
- download dependencies: `yarn install`
|
- download dependencies: `yarn install`
|
||||||
- start web server: `yarn start`
|
- start web server: `yarn start`
|
||||||
|
|
||||||
|
You can fix the homeserver, so that the user can no longer define it himself.
|
||||||
|
Either you define it at startup (e.g. `REACT_APP_SERVER=https://yourmatrixserver.example.com yarn start`)
|
||||||
|
or by editing it in the [.env](.env) file. See also the
|
||||||
|
[documentation](https://create-react-app.dev/docs/adding-custom-environment-variables/).
|
||||||
|
|
||||||
#### Steps for 3)
|
#### Steps for 3)
|
||||||
|
|
||||||
- run the Docker container from the public docker registry: `docker run -p 8080:80 awesometechnologies/synapse-admin` or use the [docker-compose.yml](docker-compose.yml): `docker-compose up -d`
|
- run the Docker container from the public docker registry: `docker run -p 8080:80 awesometechnologies/synapse-admin` or use the [docker-compose.yml](docker-compose.yml): `docker-compose up -d`
|
||||||
|
@ -71,16 +76,19 @@ You have three options:
|
||||||
> note: if you're building on an architecture other than amd64 (for example a raspberry pi), make sure to define a maximum ram for node. otherwise the build will fail.
|
> note: if you're building on an architecture other than amd64 (for example a raspberry pi), make sure to define a maximum ram for node. otherwise the build will fail.
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
|
version: "3"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
synapse-admin:
|
synapse-admin:
|
||||||
container_name: synapse-admin
|
container_name: synapse-admin
|
||||||
hostname: synapse-admin
|
hostname: synapse-admin
|
||||||
build:
|
build:
|
||||||
context: https://github.com/Awesome-Technologies/synapse-admin.git
|
context: https://github.com/Awesome-Technologies/synapse-admin.git
|
||||||
args:
|
# args:
|
||||||
- BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
|
|
||||||
# - NODE_OPTIONS="--max_old_space_size=1024"
|
# - NODE_OPTIONS="--max_old_space_size=1024"
|
||||||
# - BASE_PATH="/synapse-admin"
|
# # see #266, PUBLIC_URL must be without surrounding quotation marks
|
||||||
|
# - PUBLIC_URL=/synapse-admin
|
||||||
|
# - REACT_APP_SERVER="https://matrix.example.com"
|
||||||
ports:
|
ports:
|
||||||
- "8080:80"
|
- "8080:80"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
@ -88,83 +96,11 @@ You have three options:
|
||||||
|
|
||||||
- browse to http://localhost:8080
|
- browse to http://localhost:8080
|
||||||
|
|
||||||
### Restricting available homeserver
|
|
||||||
|
|
||||||
You can restrict the homeserver(s), so that the user can no longer define it himself.
|
|
||||||
|
|
||||||
Edit `config.json` to restrict either to a single homeserver:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"restrictBaseUrl": "https://your-matrixs-erver.example.com"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
or to a list of homeservers:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"restrictBaseUrl": [
|
|
||||||
"https://your-first-matrix-server.example.com",
|
|
||||||
"https://your-second-matrix-server.example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `config.json` can be injected into a Docker container using a bind mount.
|
|
||||||
|
|
||||||
```yml
|
|
||||||
services:
|
|
||||||
synapse-admin:
|
|
||||||
...
|
|
||||||
volumes:
|
|
||||||
./config.json:/app/config.json:ro
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
### Serving Synapse-Admin on a different path
|
|
||||||
|
|
||||||
The path prefix where synapse-admin is served can only be changed during the build step.
|
|
||||||
|
|
||||||
If you downloaded the source code, use `yarn build --base=/my-prefix` to set a path prefix.
|
|
||||||
|
|
||||||
If you want to build your own Docker container, use the `BASE_PATH` argument.
|
|
||||||
|
|
||||||
We do not support directly changing the path where Synapse-Admin is served in the pre-built Docker container. Instead please use a reverse proxy if you need to move Synapse-Admin to a different base path. If you want to serve multiple applications with different paths on the same domain, you need a reverse proxy anyway.
|
|
||||||
|
|
||||||
Example for Traefik:
|
|
||||||
|
|
||||||
`docker-compose.yml`
|
|
||||||
|
|
||||||
```yml
|
|
||||||
services:
|
|
||||||
traefik:
|
|
||||||
image: traefik:mimolette
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- 80:80
|
|
||||||
- 443:443
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
||||||
|
|
||||||
synapse-admin:
|
|
||||||
image: awesometechnologies/synapse-admin:latest
|
|
||||||
restart: unless-stopped
|
|
||||||
labels:
|
|
||||||
- "traefik.enable=true"
|
|
||||||
- "traefik.http.routers.synapse-admin.rule=Host(`example.com`)&&PathPrefix(`/admin`)"
|
|
||||||
- "traefik.http.routers.synapse-admin.middlewares=admin,admin_path"
|
|
||||||
- "traefik.http.middlewares.admin.redirectregex.regex=^(.*)/admin/?"
|
|
||||||
- "traefik.http.middlewares.admin.redirectregex.replacement=$${1}/admin/"
|
|
||||||
- "traefik.http.middlewares.admin_path.stripprefix.prefixes=/admin"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
![Screenshots](./screenshots.jpg)
|
![Screenshots](./screenshots.jpg)
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
- See https://yarnpkg.com/getting-started/editor-sdks how to setup your IDE
|
|
||||||
- Use `yarn test` to run all style, lint and unit tests
|
- Use `yarn test` to run all style, lint and unit tests
|
||||||
- Use `yarn fix` to fix the coding style
|
- Use `yarn fix` to fix the coding style
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
version: "3"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
synapse-admin:
|
synapse-admin:
|
||||||
container_name: synapse-admin
|
container_name: synapse-admin
|
||||||
|
@ -11,11 +13,14 @@ services:
|
||||||
# context: https://github.com/Awesome-Technologies/synapse-admin.git
|
# context: https://github.com/Awesome-Technologies/synapse-admin.git
|
||||||
|
|
||||||
# args:
|
# args:
|
||||||
# - BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
|
|
||||||
# if you're building on an architecture other than amd64, make sure
|
# if you're building on an architecture other than amd64, make sure
|
||||||
# to define a maximum ram for node. otherwise the build will fail.
|
# to define a maximum ram for node. otherwise the build will fail.
|
||||||
# - NODE_OPTIONS="--max_old_space_size=1024"
|
# - NODE_OPTIONS="--max_old_space_size=1024"
|
||||||
# - BASE_PATH="/synapse-admin"
|
# default is .
|
||||||
|
# - PUBLIC_URL=/synapse-admin
|
||||||
|
# You can use a fixed homeserver, so that the user can no longer
|
||||||
|
# define it himself
|
||||||
|
# - REACT_APP_SERVER="https://matrix.example.com"
|
||||||
ports:
|
ports:
|
||||||
- "8080:80"
|
- "8080:80"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
132
index.html
132
index.html
|
@ -1,132 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<meta name="theme-color" content="#000000" />
|
|
||||||
<meta
|
|
||||||
name="description"
|
|
||||||
content="Synapse-Admin"
|
|
||||||
/>
|
|
||||||
<!--
|
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
|
||||||
-->
|
|
||||||
<link rel="manifest" href="./manifest.json" />
|
|
||||||
<link rel="shortcut icon" href="./favicon.ico" />
|
|
||||||
<title>Synapse-Admin</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-direction: column;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background-color: #fafafa;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* CSS Spinner from https://projects.lukehaas.me/css-loaders/ */
|
|
||||||
|
|
||||||
.loader,
|
|
||||||
.loader:before,
|
|
||||||
.loader:after {
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader {
|
|
||||||
color: #283593;
|
|
||||||
font-size: 11px;
|
|
||||||
text-indent: -99999em;
|
|
||||||
margin: 55px auto;
|
|
||||||
position: relative;
|
|
||||||
width: 10em;
|
|
||||||
height: 10em;
|
|
||||||
box-shadow: inset 0 0 0 1em;
|
|
||||||
-webkit-transform: translateZ(0);
|
|
||||||
-ms-transform: translateZ(0);
|
|
||||||
transform: translateZ(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader:before,
|
|
||||||
.loader:after {
|
|
||||||
position: absolute;
|
|
||||||
content: '';
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader:before {
|
|
||||||
width: 5.2em;
|
|
||||||
height: 10.2em;
|
|
||||||
background: #fafafa;
|
|
||||||
border-radius: 10.2em 0 0 10.2em;
|
|
||||||
top: -0.1em;
|
|
||||||
left: -0.1em;
|
|
||||||
-webkit-transform-origin: 5.2em 5.1em;
|
|
||||||
transform-origin: 5.2em 5.1em;
|
|
||||||
-webkit-animation: load2 2s infinite ease 1.5s;
|
|
||||||
animation: load2 2s infinite ease 1.5s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader:after {
|
|
||||||
width: 5.2em;
|
|
||||||
height: 10.2em;
|
|
||||||
background: #fafafa;
|
|
||||||
border-radius: 0 10.2em 10.2em 0;
|
|
||||||
top: -0.1em;
|
|
||||||
left: 5.1em;
|
|
||||||
-webkit-transform-origin: 0px 5.1em;
|
|
||||||
transform-origin: 0px 5.1em;
|
|
||||||
-webkit-animation: load2 2s infinite ease;
|
|
||||||
animation: load2 2s infinite ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
@-webkit-keyframes load2 {
|
|
||||||
0% {
|
|
||||||
-webkit-transform: rotate(0deg);
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
-webkit-transform: rotate(360deg);
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes load2 {
|
|
||||||
0% {
|
|
||||||
-webkit-transform: rotate(0deg);
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
-webkit-transform: rotate(360deg);
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
||||||
<div id="root">
|
|
||||||
<div class="loader-container">
|
|
||||||
<div class="loader">Loading...</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script type="module" src="/src/index.jsx"></script>
|
|
||||||
<footer
|
|
||||||
style="position: relative; z-index: 2; height: 2em; margin-top: -2em; line-height: 2em; background-color: #eee; border: 0.5px solid #ddd">
|
|
||||||
<a id="copyright" href="https://github.com/Awesome-Technologies/synapse-admin"
|
|
||||||
style="margin-left: 1em; color: #888; font-family: Roboto, Helvetica, Arial, sans-serif; font-weight: 100; font-size: 0.8em; text-decoration: none;">
|
|
||||||
Synapse-Admin <b><span id="version"></span></b> by Awesome Technologies Innovationslabor GmbH
|
|
||||||
</a>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
<script>document.getElementById("version").textContent = __SYNAPSE_ADMIN_VERSION__</script>
|
|
||||||
</html>
|
|
68
package.json
68
package.json
|
@ -1,8 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "synapse-admin",
|
"name": "synapse-admin",
|
||||||
"version": "0.10.1",
|
"version": "0.9.2",
|
||||||
"description": "Admin GUI for the Matrix.org server Synapse",
|
"description": "Admin GUI for the Matrix.org server Synapse",
|
||||||
"type": "module",
|
|
||||||
"author": "Awesome Technologies Innovationslabor GmbH",
|
"author": "Awesome Technologies Innovationslabor GmbH",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"homepage": ".",
|
"homepage": ".",
|
||||||
|
@ -10,87 +9,48 @@
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/Awesome-Technologies/synapse-admin"
|
"url": "https://github.com/Awesome-Technologies/synapse-admin"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@4.1.1",
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.24.4",
|
|
||||||
"@babel/preset-env": "^7.24.4",
|
|
||||||
"@babel/preset-react": "^7.24.1",
|
|
||||||
"@testing-library/dom": "^10.0.0",
|
|
||||||
"@testing-library/jest-dom": "^6.0.0",
|
"@testing-library/jest-dom": "^6.0.0",
|
||||||
"@testing-library/react": "^15.0.2",
|
"@testing-library/react": "^15.0.2",
|
||||||
"@testing-library/user-event": "^14.5.2",
|
"@testing-library/user-event": "^14.5.2",
|
||||||
"@vitejs/plugin-react": "^4.0.0",
|
|
||||||
"babel-jest": "^29.7.0",
|
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
"eslint-config-react-app": "^7.0.1",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"jest": "^29.7.0",
|
|
||||||
"jest-environment-jsdom": "^29.7.0",
|
|
||||||
"jest-fetch-mock": "^3.0.3",
|
"jest-fetch-mock": "^3.0.3",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5"
|
||||||
"react-test-renderer": "^18.2.0",
|
|
||||||
"vite": "^5.0.0",
|
|
||||||
"vite-plugin-version-mark": "^0.0.13"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.4.1",
|
|
||||||
"@emotion/styled": "^11.3.0",
|
|
||||||
"@haleos/ra-language-german": "^1.0.0",
|
|
||||||
"@haxqer/ra-language-chinese": "^4.16.2",
|
|
||||||
"@mui/icons-material": "^5.15.15",
|
"@mui/icons-material": "^5.15.15",
|
||||||
"@mui/material": "^5.15.15",
|
"@mui/material": "^5.15.15",
|
||||||
"history": "^5.1.0",
|
"@mui/styles": "^5.15.15",
|
||||||
"lodash": "^4.17.21",
|
|
||||||
"papaparse": "^5.4.1",
|
"papaparse": "^5.4.1",
|
||||||
"query-string": "^7.1.1",
|
"ra-language-chinese": "^2.0.10",
|
||||||
"ra-core": "^4.16.15",
|
|
||||||
"ra-i18n-polyglot": "^4.16.15",
|
|
||||||
"ra-language-english": "^4.16.15",
|
|
||||||
"ra-language-farsi": "^4.2.0",
|
|
||||||
"ra-language-french": "^4.16.15",
|
"ra-language-french": "^4.16.15",
|
||||||
|
"ra-language-german": "^3.13.4",
|
||||||
"ra-language-italian": "^3.13.1",
|
"ra-language-italian": "^3.13.1",
|
||||||
|
"ra-language-farsi": "^4.2.0",
|
||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
"react-admin": "^4.16.15",
|
"react-admin": "^4.16.15",
|
||||||
"react-dom": "^18.0.0",
|
"react-dom": "^18.0.0",
|
||||||
"react-hook-form": "^7.43.9",
|
"react-scripts": "^5.0.1"
|
||||||
"react-is": "^18.2.0",
|
|
||||||
"react-query": "^3.32.1",
|
|
||||||
"react-router": "^6.1.0",
|
|
||||||
"react-router-dom": "^6.1.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite serve",
|
"start": "REACT_APP_VERSION=$(git describe --tags) react-scripts start",
|
||||||
"build": "vite build",
|
"build": "REACT_APP_VERSION=$(git describe --tags) react-scripts build",
|
||||||
"fix:other": "yarn prettier --write",
|
"fix:other": "yarn prettier --write",
|
||||||
"fix:code": "yarn test:lint --fix",
|
"fix:code": "yarn test:lint --fix",
|
||||||
"fix": "yarn fix:code && yarn fix:other",
|
"fix": "yarn fix:code && yarn fix:other",
|
||||||
"prettier": "prettier \"**/*.{js,jsx,json,md,scss,yaml,yml}\"",
|
"prettier": "prettier --ignore-path .gitignore \"**/*.{js,jsx,json,md,scss,yaml,yml}\"",
|
||||||
"test:code": "jest",
|
"test:code": "react-scripts test",
|
||||||
"test:lint": "eslint --ignore-path .gitignore --ext .js,.jsx .",
|
"test:lint": "eslint --ignore-path .gitignore --ext .js,.jsx .",
|
||||||
"test:style": "yarn prettier --check",
|
"test:style": "yarn prettier --list-different",
|
||||||
"test": "yarn test:style && yarn test:lint && yarn test:code"
|
"test": "yarn test:style && yarn test:lint && yarn test:code",
|
||||||
},
|
"eject": "react-scripts eject"
|
||||||
"babel": {
|
|
||||||
"presets": [
|
|
||||||
"@babel/preset-env",
|
|
||||||
[
|
|
||||||
"@babel/preset-react",
|
|
||||||
{
|
|
||||||
"runtime": "automatic"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": "react-app"
|
"extends": "react-app"
|
||||||
},
|
},
|
||||||
"jest": {
|
|
||||||
"testEnvironment": "jsdom",
|
|
||||||
"setupFilesAfterEnv": [
|
|
||||||
"<rootDir>/src/setupTests.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
">0.2%",
|
">0.2%",
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
{}
|
|
49
public/index.html
Normal file
49
public/index.html
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Synapse-Admin"
|
||||||
|
/>
|
||||||
|
<!--
|
||||||
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
|
-->
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
|
<!--
|
||||||
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
|
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||||
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
|
-->
|
||||||
|
<title>Synapse-Admin</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<!--
|
||||||
|
This HTML file is a template.
|
||||||
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|
||||||
|
You can add webfonts, meta tags, or analytics to this file.
|
||||||
|
The build step will place the bundled scripts into the <body> tag.
|
||||||
|
|
||||||
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
|
-->
|
||||||
|
<footer
|
||||||
|
style="position: relative; z-index: 2; height: 2em; margin-top: -2em; line-height: 2em; background-color: #eee; border: 0.5px solid #ddd">
|
||||||
|
<a id="copyright" href="https://github.com/Awesome-Technologies/synapse-admin"
|
||||||
|
style="margin-left: 1em; color: #888; font-family: Roboto, Helvetica, Arial, sans-serif; font-weight: 100; font-size: 0.8em; text-decoration: none;">
|
||||||
|
Synapse-Admin <b>(%REACT_APP_VERSION%)</b> by Awesome Technologies Innovationslabor GmbH
|
||||||
|
</a>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
14
src/App.jsx
14
src/App.jsx
|
@ -6,7 +6,6 @@ import {
|
||||||
resolveBrowserLocale,
|
resolveBrowserLocale,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import polyglotI18nProvider from "ra-i18n-polyglot";
|
import polyglotI18nProvider from "ra-i18n-polyglot";
|
||||||
import merge from "lodash/merge";
|
|
||||||
import authProvider from "./synapse/authProvider";
|
import authProvider from "./synapse/authProvider";
|
||||||
import dataProvider from "./synapse/dataProvider";
|
import dataProvider from "./synapse/dataProvider";
|
||||||
import users from "./components/users";
|
import users from "./components/users";
|
||||||
|
@ -34,17 +33,8 @@ const messages = {
|
||||||
zh: chineseMessages,
|
zh: chineseMessages,
|
||||||
};
|
};
|
||||||
const i18nProvider = polyglotI18nProvider(
|
const i18nProvider = polyglotI18nProvider(
|
||||||
locale =>
|
locale => (messages[locale] ? messages[locale] : messages.en),
|
||||||
messages[locale] ? merge({}, messages.en, messages[locale]) : messages.en,
|
resolveBrowserLocale()
|
||||||
resolveBrowserLocale(),
|
|
||||||
[
|
|
||||||
{ locale: "en", name: "English" },
|
|
||||||
{ locale: "de", name: "Deutsch" },
|
|
||||||
{ locale: "fr", name: "Français" },
|
|
||||||
{ locale: "it", name: "Italiano" },
|
|
||||||
{ locale: "fa", name: "Persian(فارسی)" },
|
|
||||||
{ locale: "zh", name: "简体中文" },
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
import { createContext, useContext } from "react";
|
|
||||||
|
|
||||||
export const AppContext = createContext({});
|
|
||||||
|
|
||||||
export const useAppContext = () => useContext(AppContext);
|
|
|
@ -15,7 +15,6 @@ import {
|
||||||
useRecordContext,
|
useRecordContext,
|
||||||
useTranslate,
|
useTranslate,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import { MXCField } from "./media";
|
|
||||||
import PageviewIcon from "@mui/icons-material/Pageview";
|
import PageviewIcon from "@mui/icons-material/Pageview";
|
||||||
import ReportIcon from "@mui/icons-material/Warning";
|
import ReportIcon from "@mui/icons-material/Warning";
|
||||||
import ViewListIcon from "@mui/icons-material/ViewList";
|
import ViewListIcon from "@mui/icons-material/ViewList";
|
||||||
|
@ -90,8 +89,6 @@ export const ReportShow = props => {
|
||||||
<TextField source="event_json.type" />
|
<TextField source="event_json.type" />
|
||||||
<TextField source="event_json.content.msgtype" />
|
<TextField source="event_json.content.msgtype" />
|
||||||
<TextField source="event_json.content.body" />
|
<TextField source="event_json.content.body" />
|
||||||
<TextField source="event_json.content.info.mimetype" />
|
|
||||||
<MXCField source="event_json.content.url" />
|
|
||||||
<TextField source="event_json.content.format" />
|
<TextField source="event_json.content.format" />
|
||||||
<TextField source="event_json.content.formatted_body" />
|
<TextField source="event_json.content.formatted_body" />
|
||||||
<TextField source="event_json.content.algorithm" />
|
<TextField source="event_json.content.algorithm" />
|
||||||
|
|
|
@ -10,7 +10,6 @@ import {
|
||||||
useTranslate,
|
useTranslate,
|
||||||
PasswordInput,
|
PasswordInput,
|
||||||
TextInput,
|
TextInput,
|
||||||
useLocales,
|
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import { useFormContext } from "react-hook-form";
|
import { useFormContext } from "react-hook-form";
|
||||||
import {
|
import {
|
||||||
|
@ -22,15 +21,13 @@ import {
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
Select,
|
Select,
|
||||||
|
TextField,
|
||||||
Typography,
|
Typography,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { styled } from "@mui/material/styles";
|
import { styled } from "@mui/material/styles";
|
||||||
import LockIcon from "@mui/icons-material/Lock";
|
import LockIcon from "@mui/icons-material/Lock";
|
||||||
|
|
||||||
import { useAppContext } from "../AppContext";
|
|
||||||
import {
|
import {
|
||||||
getServerVersion,
|
getServerVersion,
|
||||||
getSupportedFeatures,
|
|
||||||
getSupportedLoginFlows,
|
getSupportedLoginFlows,
|
||||||
getWellKnownUrl,
|
getWellKnownUrl,
|
||||||
isValidBaseUrl,
|
isValidBaseUrl,
|
||||||
|
@ -40,7 +37,7 @@ import {
|
||||||
const FormBox = styled(Box)(({ theme }) => ({
|
const FormBox = styled(Box)(({ theme }) => ({
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
minHeight: "calc(100vh - 1rem)",
|
minHeight: "calc(100vh - 1em)",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "flex-start",
|
justifyContent: "flex-start",
|
||||||
background: "url(./images/floating-cogs.svg)",
|
background: "url(./images/floating-cogs.svg)",
|
||||||
|
@ -49,12 +46,12 @@ const FormBox = styled(Box)(({ theme }) => ({
|
||||||
backgroundSize: "cover",
|
backgroundSize: "cover",
|
||||||
|
|
||||||
[`& .card`]: {
|
[`& .card`]: {
|
||||||
width: "30rem",
|
minWidth: "30em",
|
||||||
marginTop: "6rem",
|
marginTop: "6em",
|
||||||
marginBottom: "6rem",
|
marginBottom: "6em",
|
||||||
},
|
},
|
||||||
[`& .avatar`]: {
|
[`& .avatar`]: {
|
||||||
margin: "1rem",
|
margin: "1em",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
},
|
},
|
||||||
|
@ -63,49 +60,36 @@ const FormBox = styled(Box)(({ theme }) => ({
|
||||||
},
|
},
|
||||||
[`& .hint`]: {
|
[`& .hint`]: {
|
||||||
marginTop: "1em",
|
marginTop: "1em",
|
||||||
marginBottom: "1em",
|
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
color: theme.palette.grey[600],
|
color: theme.palette.grey[600],
|
||||||
},
|
},
|
||||||
[`& .form`]: {
|
[`& .form`]: {
|
||||||
padding: "0 1rem 1rem 1rem",
|
padding: "0 1em 1em 1em",
|
||||||
},
|
},
|
||||||
[`& .select`]: {
|
[`& .input`]: {
|
||||||
marginBottom: "2rem",
|
marginTop: "1em",
|
||||||
},
|
},
|
||||||
[`& .actions`]: {
|
[`& .actions`]: {
|
||||||
padding: "0 1rem 1rem 1rem",
|
padding: "0 1em 1em 1em",
|
||||||
},
|
},
|
||||||
[`& .serverVersion`]: {
|
[`& .serverVersion`]: {
|
||||||
color: theme.palette.grey[500],
|
color: theme.palette.grey[500],
|
||||||
fontFamily: "Roboto, Helvetica, Arial, sans-serif",
|
fontFamily: "Roboto, Helvetica, Arial, sans-serif",
|
||||||
marginLeft: "0.5rem",
|
marginBottom: "1em",
|
||||||
},
|
marginLeft: "0.5em",
|
||||||
[`& .matrixVersions`]: {
|
|
||||||
color: theme.palette.grey[500],
|
|
||||||
fontFamily: "Roboto, Helvetica, Arial, sans-serif",
|
|
||||||
fontSize: "0.8rem",
|
|
||||||
marginBottom: "1rem",
|
|
||||||
marginLeft: "0.5rem",
|
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const LoginPage = () => {
|
const LoginPage = () => {
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
const { restrictBaseUrl } = useAppContext();
|
|
||||||
const allowSingleBaseUrl = typeof restrictBaseUrl === "string";
|
|
||||||
const allowMultipleBaseUrls = Array.isArray(restrictBaseUrl);
|
|
||||||
const allowAnyBaseUrl = !(allowSingleBaseUrl || allowMultipleBaseUrls);
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [supportPassAuth, setSupportPassAuth] = useState(true);
|
const [supportPassAuth, setSupportPassAuth] = useState(true);
|
||||||
const [locale, setLocale] = useLocaleState();
|
const [locale, setLocale] = useLocaleState();
|
||||||
const locales = useLocales();
|
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
const base_url = allowSingleBaseUrl
|
const base_url = localStorage.getItem("base_url");
|
||||||
? restrictBaseUrl
|
const cfg_base_url = process.env.REACT_APP_SERVER;
|
||||||
: localStorage.getItem("base_url");
|
|
||||||
const [ssoBaseUrl, setSSOBaseUrl] = useState("");
|
const [ssoBaseUrl, setSSOBaseUrl] = useState("");
|
||||||
const loginToken = /\?loginToken=([a-zA-Z0-9_-]+)/.exec(window.location.href);
|
const loginToken = /\?loginToken=([a-zA-Z0-9_-]+)/.exec(window.location.href);
|
||||||
|
|
||||||
|
@ -143,6 +127,20 @@ const LoginPage = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const renderInput = ({
|
||||||
|
meta: { touched, error } = {},
|
||||||
|
input: { ...inputProps },
|
||||||
|
...props
|
||||||
|
}) => (
|
||||||
|
<TextField
|
||||||
|
error={!!(touched && error)}
|
||||||
|
helperText={touched && error}
|
||||||
|
{...inputProps}
|
||||||
|
{...props}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
const validateBaseUrl = value => {
|
const validateBaseUrl = value => {
|
||||||
if (!value.match(/^(http|https):\/\//)) {
|
if (!value.match(/^(http|https):\/\//)) {
|
||||||
return translate("synapseadmin.auth.protocol_error");
|
return translate("synapseadmin.auth.protocol_error");
|
||||||
|
@ -181,27 +179,17 @@ const LoginPage = () => {
|
||||||
const UserData = ({ formData }) => {
|
const UserData = ({ formData }) => {
|
||||||
const form = useFormContext();
|
const form = useFormContext();
|
||||||
const [serverVersion, setServerVersion] = useState("");
|
const [serverVersion, setServerVersion] = useState("");
|
||||||
const [matrixVersions, setMatrixVersions] = useState("");
|
|
||||||
|
|
||||||
const handleUsernameChange = _ => {
|
const handleUsernameChange = _ => {
|
||||||
if (formData.base_url || allowSingleBaseUrl) return;
|
if (formData.base_url || cfg_base_url) 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) {
|
||||||
getWellKnownUrl(domain).then(url => {
|
getWellKnownUrl(domain).then(url => form.setValue("base_url", url));
|
||||||
if (
|
|
||||||
allowAnyBaseUrl ||
|
|
||||||
(allowMultipleBaseUrls && restrictBaseUrl.includes(url))
|
|
||||||
)
|
|
||||||
form.setValue("base_url", url);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (formData.base_url === "" && allowMultipleBaseUrls) {
|
|
||||||
form.setValue("base_url", restrictBaseUrl[0]);
|
|
||||||
}
|
|
||||||
if (!isValidBaseUrl(formData.base_url)) return;
|
if (!isValidBaseUrl(formData.base_url)) return;
|
||||||
|
|
||||||
getServerVersion(formData.base_url)
|
getServerVersion(formData.base_url)
|
||||||
|
@ -212,14 +200,6 @@ const LoginPage = () => {
|
||||||
)
|
)
|
||||||
.catch(() => setServerVersion(""));
|
.catch(() => setServerVersion(""));
|
||||||
|
|
||||||
getSupportedFeatures(formData.base_url)
|
|
||||||
.then(features =>
|
|
||||||
setMatrixVersions(
|
|
||||||
`${translate("synapseadmin.auth.supports_specs")} ${features.versions.join(", ")}`
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.catch(() => setMatrixVersions(""));
|
|
||||||
|
|
||||||
// Set SSO Url
|
// Set SSO Url
|
||||||
getSupportedLoginFlows(formData.base_url)
|
getSupportedLoginFlows(formData.base_url)
|
||||||
.then(loginFlows => {
|
.then(loginFlows => {
|
||||||
|
@ -231,7 +211,7 @@ const LoginPage = () => {
|
||||||
setSSOBaseUrl(supportSSO ? formData.base_url : "");
|
setSSOBaseUrl(supportSSO ? formData.base_url : "");
|
||||||
})
|
})
|
||||||
.catch(() => setSSOBaseUrl(""));
|
.catch(() => setSSOBaseUrl(""));
|
||||||
}, [formData.base_url, form]);
|
}, [formData.base_url]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -239,56 +219,49 @@ const LoginPage = () => {
|
||||||
<TextInput
|
<TextInput
|
||||||
autoFocus
|
autoFocus
|
||||||
name="username"
|
name="username"
|
||||||
|
component={renderInput}
|
||||||
label="ra.auth.username"
|
label="ra.auth.username"
|
||||||
autoComplete="username"
|
|
||||||
disabled={loading || !supportPassAuth}
|
disabled={loading || !supportPassAuth}
|
||||||
onBlur={handleUsernameChange}
|
onBlur={handleUsernameChange}
|
||||||
resettable
|
resettable
|
||||||
fullWidth
|
fullWidth
|
||||||
|
className="input"
|
||||||
validate={required()}
|
validate={required()}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
name="password"
|
name="password"
|
||||||
|
component={renderInput}
|
||||||
label="ra.auth.password"
|
label="ra.auth.password"
|
||||||
type="password"
|
type="password"
|
||||||
autoComplete="current-password"
|
|
||||||
disabled={loading || !supportPassAuth}
|
disabled={loading || !supportPassAuth}
|
||||||
resettable
|
resettable
|
||||||
fullWidth
|
fullWidth
|
||||||
|
className="input"
|
||||||
validate={required()}
|
validate={required()}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<TextInput
|
<TextInput
|
||||||
name="base_url"
|
name="base_url"
|
||||||
|
component={renderInput}
|
||||||
label="synapseadmin.auth.base_url"
|
label="synapseadmin.auth.base_url"
|
||||||
select={allowMultipleBaseUrls}
|
disabled={cfg_base_url || loading}
|
||||||
autoComplete="url"
|
resettable
|
||||||
disabled={loading}
|
|
||||||
readOnly={allowSingleBaseUrl}
|
|
||||||
resettable={allowAnyBaseUrl}
|
|
||||||
fullWidth
|
fullWidth
|
||||||
|
className="input"
|
||||||
validate={[required(), validateBaseUrl]}
|
validate={[required(), validateBaseUrl]}
|
||||||
>
|
/>
|
||||||
{allowMultipleBaseUrls &&
|
|
||||||
restrictBaseUrl.map(url => (
|
|
||||||
<MenuItem key={url} value={url}>
|
|
||||||
{url}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</TextInput>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Typography className="serverVersion">{serverVersion}</Typography>
|
<Typography className="serverVersion">{serverVersion}</Typography>
|
||||||
<Typography className="matrixVersions">{matrixVersions}</Typography>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
defaultValues={{ base_url: base_url }}
|
defaultValues={{ base_url: cfg_base_url || base_url }}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
mode="onTouched"
|
mode="onTouched"
|
||||||
>
|
>
|
||||||
|
@ -307,16 +280,19 @@ const LoginPage = () => {
|
||||||
<Box className="form">
|
<Box className="form">
|
||||||
<Select
|
<Select
|
||||||
value={locale}
|
value={locale}
|
||||||
onChange={e => setLocale(e.target.value)}
|
onChange={e => {
|
||||||
|
setLocale(e.target.value);
|
||||||
|
}}
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="select"
|
className="input"
|
||||||
>
|
>
|
||||||
{locales.map(l => (
|
<MenuItem value="de">Deutsch</MenuItem>
|
||||||
<MenuItem key={l.locale} value={l.locale}>
|
<MenuItem value="en">English</MenuItem>
|
||||||
{l.name}
|
<MenuItem value="fr">Français</MenuItem>
|
||||||
</MenuItem>
|
<MenuItem value="it">Italiano</MenuItem>
|
||||||
))}
|
<MenuItem value="zh">简体中文</MenuItem>
|
||||||
|
<MenuItem value="fa">Persian(فارسی)</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
<FormDataConsumer>
|
<FormDataConsumer>
|
||||||
{formDataProps => <UserData {...formDataProps} />}
|
{formDataProps => <UserData {...formDataProps} />}
|
||||||
|
|
|
@ -1,71 +1,14 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { render, screen } from "@testing-library/react";
|
import { render } from "@testing-library/react";
|
||||||
import { AdminContext } from "react-admin";
|
import { AdminContext } from "react-admin";
|
||||||
import LoginPage from "./LoginPage";
|
import LoginPage from "./LoginPage";
|
||||||
import { AppContext } from "../AppContext";
|
|
||||||
|
|
||||||
describe("LoginForm", () => {
|
describe("LoginForm", () => {
|
||||||
it("renders with no restriction to homeserver", () => {
|
it("renders", () => {
|
||||||
render(
|
render(
|
||||||
<AdminContext>
|
<AdminContext>
|
||||||
<LoginPage />
|
<LoginPage />
|
||||||
</AdminContext>
|
</AdminContext>
|
||||||
);
|
);
|
||||||
|
|
||||||
screen.getByText("synapseadmin.auth.welcome");
|
|
||||||
screen.getByRole("combobox", { name: "" });
|
|
||||||
screen.getByRole("textbox", { name: "ra.auth.username" });
|
|
||||||
screen.getByText("ra.auth.password");
|
|
||||||
const baseUrlInput = screen.getByRole("textbox", {
|
|
||||||
name: "synapseadmin.auth.base_url",
|
|
||||||
});
|
|
||||||
expect(baseUrlInput.className.split(" ")).not.toContain("Mui-readOnly");
|
|
||||||
screen.getByRole("button", { name: "ra.auth.sign_in" });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders with single restricted homeserver", () => {
|
|
||||||
render(
|
|
||||||
<AppContext.Provider
|
|
||||||
value={{ restrictBaseUrl: "https://matrix.example.com" }}
|
|
||||||
>
|
|
||||||
<AdminContext>
|
|
||||||
<LoginPage />
|
|
||||||
</AdminContext>
|
|
||||||
</AppContext.Provider>
|
|
||||||
);
|
|
||||||
|
|
||||||
screen.getByText("synapseadmin.auth.welcome");
|
|
||||||
screen.getByRole("combobox", { name: "" });
|
|
||||||
screen.getByRole("textbox", { name: "ra.auth.username" });
|
|
||||||
screen.getByText("ra.auth.password");
|
|
||||||
const baseUrlInput = screen.getByRole("textbox", {
|
|
||||||
name: "synapseadmin.auth.base_url",
|
|
||||||
});
|
|
||||||
expect(baseUrlInput.className.split(" ")).toContain("Mui-readOnly");
|
|
||||||
screen.getByRole("button", { name: "ra.auth.sign_in" });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders with multiple restricted homeservers", async () => {
|
|
||||||
render(
|
|
||||||
<AppContext.Provider
|
|
||||||
value={{
|
|
||||||
restrictBaseUrl: [
|
|
||||||
"https://matrix.example.com",
|
|
||||||
"https://matrix.example.org",
|
|
||||||
],
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<AdminContext>
|
|
||||||
<LoginPage />
|
|
||||||
</AdminContext>
|
|
||||||
</AppContext.Provider>
|
|
||||||
);
|
|
||||||
|
|
||||||
screen.getByText("synapseadmin.auth.welcome");
|
|
||||||
screen.getByRole("combobox", { name: "" });
|
|
||||||
screen.getByRole("textbox", { name: "ra.auth.username" });
|
|
||||||
screen.getByText("ra.auth.password");
|
|
||||||
screen.getByRole("combobox", { name: "synapseadmin.auth.base_url" });
|
|
||||||
screen.getByRole("button", { name: "ra.auth.sign_in" });
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import get from "lodash/get";
|
|
||||||
import {
|
import {
|
||||||
BooleanInput,
|
BooleanInput,
|
||||||
Button,
|
Button,
|
||||||
|
@ -15,12 +14,10 @@ import {
|
||||||
useRefresh,
|
useRefresh,
|
||||||
useTranslate,
|
useTranslate,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import BlockIcon from "@mui/icons-material/Block";
|
import BlockIcon from "@mui/icons-material/Block";
|
||||||
import ClearIcon from "@mui/icons-material/Clear";
|
import ClearIcon from "@mui/icons-material/Clear";
|
||||||
import DeleteSweepIcon from "@mui/icons-material/DeleteSweep";
|
import DeleteSweepIcon from "@mui/icons-material/DeleteSweep";
|
||||||
import {
|
import {
|
||||||
Box,
|
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogContentText,
|
DialogContentText,
|
||||||
|
@ -30,9 +27,7 @@ import {
|
||||||
import IconCancel from "@mui/icons-material/Cancel";
|
import IconCancel from "@mui/icons-material/Cancel";
|
||||||
import LockIcon from "@mui/icons-material/Lock";
|
import LockIcon from "@mui/icons-material/Lock";
|
||||||
import LockOpenIcon from "@mui/icons-material/LockOpen";
|
import LockOpenIcon from "@mui/icons-material/LockOpen";
|
||||||
import FileOpenIcon from "@mui/icons-material/FileOpen";
|
|
||||||
import { alpha, useTheme } from "@mui/material/styles";
|
import { alpha, useTheme } from "@mui/material/styles";
|
||||||
import { getMediaUrl } from "../synapse/synapse";
|
|
||||||
|
|
||||||
const DeleteMediaDialog = ({ open, loading, onClose, onSubmit }) => {
|
const DeleteMediaDialog = ({ open, loading, onClose, onSubmit }) => {
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
|
@ -338,49 +333,3 @@ export const QuarantineMediaButton = props => {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ViewMediaButton = ({ media_id, label }) => {
|
|
||||||
const translate = useTranslate();
|
|
||||||
const url = getMediaUrl(media_id);
|
|
||||||
return (
|
|
||||||
<Box style={{ whiteSpace: "pre" }}>
|
|
||||||
<Tooltip title={translate("resources.users_media.action.open")}>
|
|
||||||
<span>
|
|
||||||
<Button
|
|
||||||
component={Link}
|
|
||||||
to={url}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
style={{ minWidth: 0, paddingLeft: 0, paddingRight: 0 }}
|
|
||||||
>
|
|
||||||
<FileOpenIcon />
|
|
||||||
</Button>
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
{label}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MediaIDField = ({ source }) => {
|
|
||||||
const homeserver = localStorage.getItem("home_server");
|
|
||||||
const record = useRecordContext();
|
|
||||||
if (!record) return null;
|
|
||||||
|
|
||||||
const src = get(record, source)?.toString();
|
|
||||||
if (!src) return null;
|
|
||||||
|
|
||||||
return <ViewMediaButton media_id={`${homeserver}/${src}`} label={src} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MXCField = ({ source }) => {
|
|
||||||
const record = useRecordContext();
|
|
||||||
if (!record) return null;
|
|
||||||
|
|
||||||
const src = get(record, source)?.toString();
|
|
||||||
if (!src) return null;
|
|
||||||
|
|
||||||
const media_id = src.replace("mxc://", "");
|
|
||||||
|
|
||||||
return <ViewMediaButton media_id={media_id} label={src} />;
|
|
||||||
};
|
|
||||||
|
|
|
@ -51,11 +51,7 @@ import { Link } from "react-router-dom";
|
||||||
import AvatarField from "./AvatarField";
|
import AvatarField from "./AvatarField";
|
||||||
import { ServerNoticeButton, ServerNoticeBulkButton } from "./ServerNotices";
|
import { ServerNoticeButton, ServerNoticeBulkButton } from "./ServerNotices";
|
||||||
import { DeviceRemoveButton } from "./devices";
|
import { DeviceRemoveButton } from "./devices";
|
||||||
import {
|
import { ProtectMediaButton, QuarantineMediaButton } from "./media";
|
||||||
MediaIDField,
|
|
||||||
ProtectMediaButton,
|
|
||||||
QuarantineMediaButton,
|
|
||||||
} from "./media";
|
|
||||||
|
|
||||||
const choices_medium = [
|
const choices_medium = [
|
||||||
{ id: "email", name: "resources.users.email" },
|
{ id: "email", name: "resources.users.email" },
|
||||||
|
@ -453,13 +449,13 @@ export const UserEdit = props => {
|
||||||
sort={{ field: "created_ts", order: "DESC" }}
|
sort={{ field: "created_ts", order: "DESC" }}
|
||||||
>
|
>
|
||||||
<Datagrid style={{ width: "100%" }}>
|
<Datagrid style={{ width: "100%" }}>
|
||||||
<MediaIDField source="media_id" />
|
|
||||||
<DateField source="created_ts" showTime options={date_format} />
|
<DateField source="created_ts" showTime options={date_format} />
|
||||||
<DateField
|
<DateField
|
||||||
source="last_access_ts"
|
source="last_access_ts"
|
||||||
showTime
|
showTime
|
||||||
options={date_format}
|
options={date_format}
|
||||||
/>
|
/>
|
||||||
|
<TextField source="media_id" />
|
||||||
<NumberField source="media_length" />
|
<NumberField source="media_length" />
|
||||||
<TextField source="media_type" />
|
<TextField source="media_type" />
|
||||||
<TextField source="upload_name" />
|
<TextField source="upload_name" />
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
import { formalGermanMessages } from "@haleos/ra-language-german";
|
import germanMessages from "ra-language-german";
|
||||||
|
|
||||||
const de = {
|
const de = {
|
||||||
...formalGermanMessages,
|
...germanMessages,
|
||||||
synapseadmin: {
|
synapseadmin: {
|
||||||
auth: {
|
auth: {
|
||||||
base_url: "Heimserver URL",
|
base_url: "Heimserver URL",
|
||||||
welcome: "Willkommen bei Synapse-admin",
|
welcome: "Willkommen bei Synapse-admin",
|
||||||
server_version: "Synapse Version",
|
server_version: "Synapse Version",
|
||||||
supports_specs: "unterstützt Matrix-Specs",
|
|
||||||
username_error: "Bitte vollständigen Nutzernamen angeben: '@user:domain'",
|
username_error: "Bitte vollständigen Nutzernamen angeben: '@user:domain'",
|
||||||
protocol_error: "Die URL muss mit 'http://' oder 'https://' beginnen",
|
protocol_error: "Die URL muss mit 'http://' oder 'https://' beginnen",
|
||||||
url_error: "Keine gültige Matrix Server URL",
|
url_error: "Keine gültige Matrix Server URL",
|
||||||
|
@ -208,9 +207,6 @@ const de = {
|
||||||
format: "Nachrichtenformat",
|
format: "Nachrichtenformat",
|
||||||
formatted_body: "Formatierter Nachrichteninhalt",
|
formatted_body: "Formatierter Nachrichteninhalt",
|
||||||
algorithm: "Verschlüsselungsalgorithmus",
|
algorithm: "Verschlüsselungsalgorithmus",
|
||||||
info: {
|
|
||||||
mimetype: "Typ",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -259,9 +255,6 @@ const de = {
|
||||||
created_ts: "Erstellt",
|
created_ts: "Erstellt",
|
||||||
last_access_ts: "Letzter Zugriff",
|
last_access_ts: "Letzter Zugriff",
|
||||||
},
|
},
|
||||||
action: {
|
|
||||||
open: "Mediendatei in neuem Fenster öffnen",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
delete_media: {
|
delete_media: {
|
||||||
name: "Medien",
|
name: "Medien",
|
||||||
|
@ -395,5 +388,37 @@ const de = {
|
||||||
helper: { length: "Länge des Tokens, wenn kein Token vorgegeben wird." },
|
helper: { length: "Länge des Tokens, wenn kein Token vorgegeben wird." },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
ra: {
|
||||||
|
...germanMessages.ra,
|
||||||
|
action: {
|
||||||
|
...germanMessages.ra.action,
|
||||||
|
unselect: "Abwählen",
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
...germanMessages.ra.auth,
|
||||||
|
auth_check_error: "Anmeldung fehlgeschlagen",
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
...germanMessages.ra.input,
|
||||||
|
password: {
|
||||||
|
...germanMessages.ra.input.password,
|
||||||
|
toggle_hidden: "Anzeigen",
|
||||||
|
toggle_visible: "Verstecken",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
notification: {
|
||||||
|
...germanMessages.ra.notification,
|
||||||
|
logged_out: "Abgemeldet",
|
||||||
|
},
|
||||||
|
page: {
|
||||||
|
...germanMessages.ra.page,
|
||||||
|
empty: "Keine Einträge vorhanden",
|
||||||
|
invite: "",
|
||||||
|
},
|
||||||
|
navigation: {
|
||||||
|
...germanMessages.ra.navigation,
|
||||||
|
skip_nav: "Zum Inhalt springen",
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
export default de;
|
export default de;
|
||||||
|
|
|
@ -7,7 +7,6 @@ const en = {
|
||||||
base_url: "Homeserver URL",
|
base_url: "Homeserver URL",
|
||||||
welcome: "Welcome to Synapse-admin",
|
welcome: "Welcome to Synapse-admin",
|
||||||
server_version: "Synapse version",
|
server_version: "Synapse version",
|
||||||
supports_specs: "supports Matrix specs",
|
|
||||||
username_error: "Please enter fully qualified user ID: '@user:domain'",
|
username_error: "Please enter fully qualified user ID: '@user:domain'",
|
||||||
protocol_error: "URL has to start with 'http://' or 'https://'",
|
protocol_error: "URL has to start with 'http://' or 'https://'",
|
||||||
url_error: "Not a valid Matrix server URL",
|
url_error: "Not a valid Matrix server URL",
|
||||||
|
@ -205,10 +204,6 @@ const en = {
|
||||||
format: "format",
|
format: "format",
|
||||||
formatted_body: "formatted content",
|
formatted_body: "formatted content",
|
||||||
algorithm: "algorithm",
|
algorithm: "algorithm",
|
||||||
url: "URL",
|
|
||||||
info: {
|
|
||||||
mimetype: "Type",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -257,9 +252,6 @@ const en = {
|
||||||
created_ts: "Created",
|
created_ts: "Created",
|
||||||
last_access_ts: "Last access",
|
last_access_ts: "Last access",
|
||||||
},
|
},
|
||||||
action: {
|
|
||||||
open: "Open media file in new window",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
delete_media: {
|
delete_media: {
|
||||||
name: "Media",
|
name: "Media",
|
||||||
|
@ -379,6 +371,7 @@ const en = {
|
||||||
},
|
},
|
||||||
action: { reconnect: "Reconnect" },
|
action: { reconnect: "Reconnect" },
|
||||||
},
|
},
|
||||||
|
},
|
||||||
registration_tokens: {
|
registration_tokens: {
|
||||||
name: "Registration tokens",
|
name: "Registration tokens",
|
||||||
fields: {
|
fields: {
|
||||||
|
@ -392,6 +385,5 @@ const en = {
|
||||||
},
|
},
|
||||||
helper: { length: "Length of the token if no token is given." },
|
helper: { length: "Length of the token if no token is given." },
|
||||||
},
|
},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
export default en;
|
export default en;
|
||||||
|
|
|
@ -364,6 +364,7 @@ const fa = {
|
||||||
},
|
},
|
||||||
action: { reconnect: "دوباره وصل شوید" },
|
action: { reconnect: "دوباره وصل شوید" },
|
||||||
},
|
},
|
||||||
|
},
|
||||||
registration_tokens: {
|
registration_tokens: {
|
||||||
name: "توکن های ثبت نام",
|
name: "توکن های ثبت نام",
|
||||||
fields: {
|
fields: {
|
||||||
|
@ -377,6 +378,5 @@ const fa = {
|
||||||
},
|
},
|
||||||
helper: { length: "طول توکن در صورت عدم ارائه توکن." },
|
helper: { length: "طول توکن در صورت عدم ارائه توکن." },
|
||||||
},
|
},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
export default fa;
|
export default fa;
|
||||||
|
|
|
@ -356,6 +356,7 @@ const fr = {
|
||||||
send_failure: "Une erreur s'est produite",
|
send_failure: "Une erreur s'est produite",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
registration_tokens: {
|
registration_tokens: {
|
||||||
name: "Jetons d'inscription",
|
name: "Jetons d'inscription",
|
||||||
fields: {
|
fields: {
|
||||||
|
@ -372,6 +373,5 @@ const fr = {
|
||||||
"Longueur du jeton généré aléatoirement si aucun jeton n'est pas spécifié",
|
"Longueur du jeton généré aléatoirement si aucun jeton n'est pas spécifié",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
export default fr;
|
export default fr;
|
||||||
|
|
|
@ -367,6 +367,7 @@ const it = {
|
||||||
},
|
},
|
||||||
action: { reconnect: "Riconnetti" },
|
action: { reconnect: "Riconnetti" },
|
||||||
},
|
},
|
||||||
|
},
|
||||||
registration_tokens: {
|
registration_tokens: {
|
||||||
name: "Token di registrazione",
|
name: "Token di registrazione",
|
||||||
fields: {
|
fields: {
|
||||||
|
@ -380,6 +381,5 @@ const it = {
|
||||||
},
|
},
|
||||||
helper: { length: "Lunghezza del token se non viene dato alcun token." },
|
helper: { length: "Lunghezza del token se non viene dato alcun token." },
|
||||||
},
|
},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
export default it;
|
export default it;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import chineseMessages from "@haxqer/ra-language-chinese";
|
import chineseMessages from "ra-language-chinese";
|
||||||
|
|
||||||
const zh = {
|
const zh = {
|
||||||
...chineseMessages,
|
...chineseMessages,
|
||||||
|
|
|
@ -1,17 +1,9 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
|
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
import { AppContext } from "./AppContext";
|
|
||||||
|
|
||||||
fetch("config.json")
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(props =>
|
|
||||||
createRoot(document.getElementById("root")).render(
|
createRoot(document.getElementById("root")).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<AppContext.Provider value={props}>
|
|
||||||
<App />
|
<App />
|
||||||
</AppContext.Provider>
|
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,7 +2,10 @@ import { fetchUtils } from "react-admin";
|
||||||
|
|
||||||
const authProvider = {
|
const authProvider = {
|
||||||
// called when the user attempts to log in
|
// called when the user attempts to log in
|
||||||
login: async ({ base_url, username, password, loginToken }) => {
|
login: ({ base_url, username, password, loginToken }) => {
|
||||||
|
// force homeserver for protection in case the form is manipulated
|
||||||
|
base_url = process.env.REACT_APP_SERVER || base_url;
|
||||||
|
|
||||||
console.log("login ");
|
console.log("login ");
|
||||||
const options = {
|
const options = {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
@ -35,14 +38,15 @@ const authProvider = {
|
||||||
const decoded_base_url = window.decodeURIComponent(base_url);
|
const decoded_base_url = window.decodeURIComponent(base_url);
|
||||||
const login_api_url = decoded_base_url + "/_matrix/client/r0/login";
|
const login_api_url = decoded_base_url + "/_matrix/client/r0/login";
|
||||||
|
|
||||||
const { json } = await fetchUtils.fetchJson(login_api_url, options);
|
return fetchUtils.fetchJson(login_api_url, options).then(({ json }) => {
|
||||||
localStorage.setItem("home_server", json.home_server);
|
localStorage.setItem("home_server", json.home_server);
|
||||||
localStorage.setItem("user_id", json.user_id);
|
localStorage.setItem("user_id", json.user_id);
|
||||||
localStorage.setItem("access_token", json.access_token);
|
localStorage.setItem("access_token", json.access_token);
|
||||||
localStorage.setItem("device_id", json.device_id);
|
localStorage.setItem("device_id", json.device_id);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
// called when the user clicks on the logout button
|
// called when the user clicks on the logout button
|
||||||
logout: async () => {
|
logout: () => {
|
||||||
console.log("logout");
|
console.log("logout");
|
||||||
|
|
||||||
const logout_api_url =
|
const logout_api_url =
|
||||||
|
@ -58,9 +62,11 @@ const authProvider = {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (typeof access_token === "string") {
|
if (typeof access_token === "string") {
|
||||||
await fetchUtils.fetchJson(logout_api_url, options);
|
fetchUtils.fetchJson(logout_api_url, options).then(({ json }) => {
|
||||||
localStorage.removeItem("access_token");
|
localStorage.removeItem("access_token");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
// called when the API returns an error
|
// called when the API returns an error
|
||||||
checkError: ({ status }) => {
|
checkError: ({ status }) => {
|
||||||
|
|
|
@ -1,135 +0,0 @@
|
||||||
import authProvider from "./authProvider";
|
|
||||||
|
|
||||||
describe("authProvider", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
fetch.resetMocks();
|
|
||||||
localStorage.clear();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("login", () => {
|
|
||||||
it("should successfully login with username and password", async () => {
|
|
||||||
fetch.once(
|
|
||||||
JSON.stringify({
|
|
||||||
home_server: "example.com",
|
|
||||||
user_id: "@user:example.com",
|
|
||||||
access_token: "foobar",
|
|
||||||
device_id: "some_device",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const ret = await authProvider.login({
|
|
||||||
base_url: "http://example.com",
|
|
||||||
username: "@user:example.com",
|
|
||||||
password: "secret",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(ret).toBe(undefined);
|
|
||||||
expect(fetch).toBeCalledWith(
|
|
||||||
"http://example.com/_matrix/client/r0/login",
|
|
||||||
{
|
|
||||||
body: '{"device_id":null,"initial_device_display_name":"Synapse Admin","type":"m.login.password","user":"@user:example.com","password":"secret"}',
|
|
||||||
headers: new Headers({
|
|
||||||
Accept: ["application/json"],
|
|
||||||
"Content-Type": ["application/json"],
|
|
||||||
}),
|
|
||||||
method: "POST",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
expect(localStorage.getItem("base_url")).toEqual("http://example.com");
|
|
||||||
expect(localStorage.getItem("user_id")).toEqual("@user:example.com");
|
|
||||||
expect(localStorage.getItem("access_token")).toEqual("foobar");
|
|
||||||
expect(localStorage.getItem("device_id")).toEqual("some_device");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should successfully login with token", async () => {
|
|
||||||
fetch.once(
|
|
||||||
JSON.stringify({
|
|
||||||
home_server: "example.com",
|
|
||||||
user_id: "@user:example.com",
|
|
||||||
access_token: "foobar",
|
|
||||||
device_id: "some_device",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const ret = await authProvider.login({
|
|
||||||
base_url: "https://example.com/",
|
|
||||||
loginToken: "login_token",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(ret).toBe(undefined);
|
|
||||||
expect(fetch).toBeCalledWith(
|
|
||||||
"https://example.com/_matrix/client/r0/login",
|
|
||||||
{
|
|
||||||
body: '{"device_id":null,"initial_device_display_name":"Synapse Admin","type":"m.login.token","token":"login_token"}',
|
|
||||||
headers: new Headers({
|
|
||||||
Accept: ["application/json"],
|
|
||||||
"Content-Type": ["application/json"],
|
|
||||||
}),
|
|
||||||
method: "POST",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
expect(localStorage.getItem("base_url")).toEqual("https://example.com");
|
|
||||||
expect(localStorage.getItem("user_id")).toEqual("@user:example.com");
|
|
||||||
expect(localStorage.getItem("access_token")).toEqual("foobar");
|
|
||||||
expect(localStorage.getItem("device_id")).toEqual("some_device");
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("logout", () => {
|
|
||||||
it("should remove the access_token from localStorage", async () => {
|
|
||||||
localStorage.setItem("base_url", "example.com");
|
|
||||||
localStorage.setItem("access_token", "foo");
|
|
||||||
fetch.mockResponse(JSON.stringify({}));
|
|
||||||
|
|
||||||
await authProvider.logout();
|
|
||||||
|
|
||||||
expect(fetch).toBeCalledWith("example.com/_matrix/client/r0/logout", {
|
|
||||||
headers: new Headers({
|
|
||||||
Accept: ["application/json"],
|
|
||||||
Authorization: ["Bearer foo"],
|
|
||||||
}),
|
|
||||||
method: "POST",
|
|
||||||
user: { authenticated: true, token: "Bearer foo" },
|
|
||||||
});
|
|
||||||
expect(localStorage.getItem("access_token")).toBeNull();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("checkError", () => {
|
|
||||||
it("should resolve if error.status is not 401 or 403", async () => {
|
|
||||||
await expect(
|
|
||||||
authProvider.checkError({ status: 200 })
|
|
||||||
).resolves.toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should reject if error.status is 401", async () => {
|
|
||||||
await expect(
|
|
||||||
authProvider.checkError({ status: 401 })
|
|
||||||
).rejects.toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should reject if error.status is 403", async () => {
|
|
||||||
await expect(
|
|
||||||
authProvider.checkError({ status: 403 })
|
|
||||||
).rejects.toBeUndefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("checkAuth", () => {
|
|
||||||
it("should reject when not logged in", async () => {
|
|
||||||
await expect(authProvider.checkAuth({})).rejects.toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should resolve when logged in", async () => {
|
|
||||||
localStorage.setItem("access_token", "foobar");
|
|
||||||
|
|
||||||
await expect(authProvider.checkAuth({})).resolves.toBeUndefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("getPermissions", () => {
|
|
||||||
it("should do nothing", async () => {
|
|
||||||
await expect(authProvider.getPermissions()).resolves.toBeUndefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -348,7 +348,7 @@ function getSearchOrder(order) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const dataProvider = {
|
const dataProvider = {
|
||||||
getList: async (resource, params) => {
|
getList: (resource, params) => {
|
||||||
console.log("getList " + resource);
|
console.log("getList " + resource);
|
||||||
const {
|
const {
|
||||||
user_id,
|
user_id,
|
||||||
|
@ -383,14 +383,13 @@ const dataProvider = {
|
||||||
const endpoint_url = homeserver + res.path;
|
const endpoint_url = homeserver + res.path;
|
||||||
const url = `${endpoint_url}?${stringify(query)}`;
|
const url = `${endpoint_url}?${stringify(query)}`;
|
||||||
|
|
||||||
const { json } = await jsonClient(url);
|
return jsonClient(url).then(({ json }) => ({
|
||||||
return {
|
|
||||||
data: json[res.data].map(res.map),
|
data: json[res.data].map(res.map),
|
||||||
total: res.total(json, from, perPage),
|
total: res.total(json, from, perPage),
|
||||||
};
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
getOne: async (resource, params) => {
|
getOne: (resource, params) => {
|
||||||
console.log("getOne " + resource);
|
console.log("getOne " + resource);
|
||||||
const homeserver = localStorage.getItem("base_url");
|
const homeserver = localStorage.getItem("base_url");
|
||||||
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
||||||
|
@ -398,13 +397,14 @@ const dataProvider = {
|
||||||
const res = resourceMap[resource];
|
const res = resourceMap[resource];
|
||||||
|
|
||||||
const endpoint_url = homeserver + res.path;
|
const endpoint_url = homeserver + res.path;
|
||||||
const { json } = await jsonClient(
|
return jsonClient(`${endpoint_url}/${encodeURIComponent(params.id)}`).then(
|
||||||
`${endpoint_url}/${encodeURIComponent(params.id)}`
|
({ json }) => ({
|
||||||
|
data: res.map(json),
|
||||||
|
})
|
||||||
);
|
);
|
||||||
return { data: res.map(json) };
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getMany: async (resource, params) => {
|
getMany: (resource, params) => {
|
||||||
console.log("getMany " + resource);
|
console.log("getMany " + resource);
|
||||||
const homeserver = localStorage.getItem("base_url");
|
const homeserver = localStorage.getItem("base_url");
|
||||||
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
||||||
|
@ -412,18 +412,17 @@ const dataProvider = {
|
||||||
const res = resourceMap[resource];
|
const res = resourceMap[resource];
|
||||||
|
|
||||||
const endpoint_url = homeserver + res.path;
|
const endpoint_url = homeserver + res.path;
|
||||||
const responses = await Promise.all(
|
return Promise.all(
|
||||||
params.ids.map(id =>
|
params.ids.map(id =>
|
||||||
jsonClient(`${endpoint_url}/${encodeURIComponent(id)}`)
|
jsonClient(`${endpoint_url}/${encodeURIComponent(id)}`)
|
||||||
)
|
)
|
||||||
);
|
).then(responses => ({
|
||||||
return {
|
|
||||||
data: responses.map(({ json }) => res.map(json)),
|
data: responses.map(({ json }) => res.map(json)),
|
||||||
total: responses.length,
|
total: responses.length,
|
||||||
};
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
getManyReference: async (resource, params) => {
|
getManyReference: (resource, params) => {
|
||||||
console.log("getManyReference " + resource);
|
console.log("getManyReference " + resource);
|
||||||
const { page, perPage } = params.pagination;
|
const { page, perPage } = params.pagination;
|
||||||
const { field, order } = params.sort;
|
const { field, order } = params.sort;
|
||||||
|
@ -443,14 +442,13 @@ const dataProvider = {
|
||||||
const ref = res["reference"](params.id);
|
const ref = res["reference"](params.id);
|
||||||
const endpoint_url = `${homeserver}${ref.endpoint}?${stringify(query)}`;
|
const endpoint_url = `${homeserver}${ref.endpoint}?${stringify(query)}`;
|
||||||
|
|
||||||
const { json } = await jsonClient(endpoint_url);
|
return jsonClient(endpoint_url).then(({ headers, json }) => ({
|
||||||
return {
|
|
||||||
data: json[res.data].map(res.map),
|
data: json[res.data].map(res.map),
|
||||||
total: res.total(json, from, perPage),
|
total: res.total(json, from, perPage),
|
||||||
};
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
update: async (resource, params) => {
|
update: (resource, params) => {
|
||||||
console.log("update " + resource);
|
console.log("update " + resource);
|
||||||
const homeserver = localStorage.getItem("base_url");
|
const homeserver = localStorage.getItem("base_url");
|
||||||
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
||||||
|
@ -458,17 +456,15 @@ const dataProvider = {
|
||||||
const res = resourceMap[resource];
|
const res = resourceMap[resource];
|
||||||
|
|
||||||
const endpoint_url = homeserver + res.path;
|
const endpoint_url = homeserver + res.path;
|
||||||
const { json } = await jsonClient(
|
return jsonClient(`${endpoint_url}/${encodeURIComponent(params.id)}`, {
|
||||||
`${endpoint_url}/${encodeURIComponent(params.id)}`,
|
|
||||||
{
|
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: JSON.stringify(params.data, filterNullValues),
|
body: JSON.stringify(params.data, filterNullValues),
|
||||||
}
|
}).then(({ json }) => ({
|
||||||
);
|
data: res.map(json),
|
||||||
return { data: res.map(json) };
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
updateMany: async (resource, params) => {
|
updateMany: (resource, params) => {
|
||||||
console.log("updateMany " + resource);
|
console.log("updateMany " + resource);
|
||||||
const homeserver = localStorage.getItem("base_url");
|
const homeserver = localStorage.getItem("base_url");
|
||||||
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
||||||
|
@ -476,7 +472,7 @@ const dataProvider = {
|
||||||
const res = resourceMap[resource];
|
const res = resourceMap[resource];
|
||||||
|
|
||||||
const endpoint_url = homeserver + res.path;
|
const endpoint_url = homeserver + res.path;
|
||||||
const responses = await Promise.all(
|
return Promise.all(
|
||||||
params.ids.map(
|
params.ids.map(
|
||||||
id => jsonClient(`${endpoint_url}/${encodeURIComponent(id)}`),
|
id => jsonClient(`${endpoint_url}/${encodeURIComponent(id)}`),
|
||||||
{
|
{
|
||||||
|
@ -484,11 +480,12 @@ const dataProvider = {
|
||||||
body: JSON.stringify(params.data, filterNullValues),
|
body: JSON.stringify(params.data, filterNullValues),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
).then(responses => ({
|
||||||
return { data: responses.map(({ json }) => json) };
|
data: responses.map(({ json }) => json),
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
create: async (resource, params) => {
|
create: (resource, params) => {
|
||||||
console.log("create " + resource);
|
console.log("create " + resource);
|
||||||
const homeserver = localStorage.getItem("base_url");
|
const homeserver = localStorage.getItem("base_url");
|
||||||
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
||||||
|
@ -498,14 +495,15 @@ const dataProvider = {
|
||||||
|
|
||||||
const create = res["create"](params.data);
|
const create = res["create"](params.data);
|
||||||
const endpoint_url = homeserver + create.endpoint;
|
const endpoint_url = homeserver + create.endpoint;
|
||||||
const { json } = await jsonClient(endpoint_url, {
|
return jsonClient(endpoint_url, {
|
||||||
method: create.method,
|
method: create.method,
|
||||||
body: JSON.stringify(create.body, filterNullValues),
|
body: JSON.stringify(create.body, filterNullValues),
|
||||||
});
|
}).then(({ json }) => ({
|
||||||
return { data: res.map(json) };
|
data: res.map(json),
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
createMany: async (resource, params) => {
|
createMany: (resource, params) => {
|
||||||
console.log("createMany " + resource);
|
console.log("createMany " + resource);
|
||||||
const homeserver = localStorage.getItem("base_url");
|
const homeserver = localStorage.getItem("base_url");
|
||||||
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
||||||
|
@ -513,7 +511,7 @@ const dataProvider = {
|
||||||
const res = resourceMap[resource];
|
const res = resourceMap[resource];
|
||||||
if (!("create" in res)) return Promise.reject();
|
if (!("create" in res)) return Promise.reject();
|
||||||
|
|
||||||
const responses = await Promise.all(
|
return Promise.all(
|
||||||
params.ids.map(id => {
|
params.ids.map(id => {
|
||||||
params.data.id = id;
|
params.data.id = id;
|
||||||
const cre = res["create"](params.data);
|
const cre = res["create"](params.data);
|
||||||
|
@ -523,11 +521,12 @@ const dataProvider = {
|
||||||
body: JSON.stringify(cre.body, filterNullValues),
|
body: JSON.stringify(cre.body, filterNullValues),
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
).then(responses => ({
|
||||||
return { data: responses.map(({ json }) => json) };
|
data: responses.map(({ json }) => json),
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
delete: async (resource, params) => {
|
delete: (resource, params) => {
|
||||||
console.log("delete " + resource);
|
console.log("delete " + resource);
|
||||||
const homeserver = localStorage.getItem("base_url");
|
const homeserver = localStorage.getItem("base_url");
|
||||||
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
||||||
|
@ -537,22 +536,24 @@ const dataProvider = {
|
||||||
if ("delete" in res) {
|
if ("delete" in res) {
|
||||||
const del = res["delete"](params);
|
const del = res["delete"](params);
|
||||||
const endpoint_url = homeserver + del.endpoint;
|
const endpoint_url = homeserver + del.endpoint;
|
||||||
const { json } = await jsonClient(endpoint_url, {
|
return jsonClient(endpoint_url, {
|
||||||
method: "method" in del ? del.method : "DELETE",
|
method: "method" in del ? del.method : "DELETE",
|
||||||
body: "body" in del ? JSON.stringify(del.body) : null,
|
body: "body" in del ? JSON.stringify(del.body) : null,
|
||||||
});
|
}).then(({ json }) => ({
|
||||||
return { data: json };
|
data: json,
|
||||||
|
}));
|
||||||
} else {
|
} else {
|
||||||
const endpoint_url = homeserver + res.path;
|
const endpoint_url = homeserver + res.path;
|
||||||
const { json } = await jsonClient(`${endpoint_url}/${params.id}`, {
|
return jsonClient(`${endpoint_url}/${params.id}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
body: JSON.stringify(params.previousData, filterNullValues),
|
body: JSON.stringify(params.previousData, filterNullValues),
|
||||||
});
|
}).then(({ json }) => ({
|
||||||
return { data: json };
|
data: json,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteMany: async (resource, params) => {
|
deleteMany: (resource, params) => {
|
||||||
console.log("deleteMany " + resource);
|
console.log("deleteMany " + resource);
|
||||||
const homeserver = localStorage.getItem("base_url");
|
const homeserver = localStorage.getItem("base_url");
|
||||||
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
||||||
|
@ -560,7 +561,7 @@ const dataProvider = {
|
||||||
const res = resourceMap[resource];
|
const res = resourceMap[resource];
|
||||||
|
|
||||||
if ("delete" in res) {
|
if ("delete" in res) {
|
||||||
const responses = await Promise.all(
|
return Promise.all(
|
||||||
params.ids.map(id => {
|
params.ids.map(id => {
|
||||||
const del = res["delete"]({ ...params, id: id });
|
const del = res["delete"]({ ...params, id: id });
|
||||||
const endpoint_url = homeserver + del.endpoint;
|
const endpoint_url = homeserver + del.endpoint;
|
||||||
|
@ -569,21 +570,21 @@ const dataProvider = {
|
||||||
body: "body" in del ? JSON.stringify(del.body) : null,
|
body: "body" in del ? JSON.stringify(del.body) : null,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
).then(responses => ({
|
||||||
return {
|
|
||||||
data: responses.map(({ json }) => json),
|
data: responses.map(({ json }) => json),
|
||||||
};
|
}));
|
||||||
} else {
|
} else {
|
||||||
const endpoint_url = homeserver + res.path;
|
const endpoint_url = homeserver + res.path;
|
||||||
const responses = await Promise.all(
|
return Promise.all(
|
||||||
params.ids.map(id =>
|
params.ids.map(id =>
|
||||||
jsonClient(`${endpoint_url}/${id}`, {
|
jsonClient(`${endpoint_url}/${id}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
body: JSON.stringify(params.data, filterNullValues),
|
body: JSON.stringify(params.data, filterNullValues),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
).then(responses => ({
|
||||||
return { data: responses.map(({ json }) => json) };
|
data: responses.map(({ json }) => json),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -36,13 +36,6 @@ export const getServerVersion = async baseUrl => {
|
||||||
return response.json.server_version;
|
return response.json.server_version;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Get supported Matrix features */
|
|
||||||
export const getSupportedFeatures = async baseUrl => {
|
|
||||||
const versionUrl = `${baseUrl}/_matrix/client/versions`;
|
|
||||||
const response = await fetchUtils.fetchJson(versionUrl, { method: "GET" });
|
|
||||||
return response.json;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get supported login flows
|
* Get supported login flows
|
||||||
* @param baseUrl the base URL of the homeserver
|
* @param baseUrl the base URL of the homeserver
|
||||||
|
@ -53,8 +46,3 @@ export const getSupportedLoginFlows = async baseUrl => {
|
||||||
const response = await fetchUtils.fetchJson(loginFlowsUrl, { method: "GET" });
|
const response = await fetchUtils.fetchJson(loginFlowsUrl, { method: "GET" });
|
||||||
return response.json.flows;
|
return response.json.flows;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMediaUrl = media_id => {
|
|
||||||
const baseUrl = localStorage.getItem("base_url");
|
|
||||||
return `${baseUrl}/_matrix/media/v1/download/${media_id}?allow_redirect=true`;
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
import { defineConfig } from "vite";
|
|
||||||
import react from "@vitejs/plugin-react";
|
|
||||||
import { vitePluginVersionMark } from "vite-plugin-version-mark";
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [
|
|
||||||
react(),
|
|
||||||
vitePluginVersionMark({
|
|
||||||
command: "git describe --tags",
|
|
||||||
ifMeta: true,
|
|
||||||
ifLog: true,
|
|
||||||
ifGlobal: true,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
});
|
|
Loading…
Reference in a new issue